import { CLOUD_API_SERVICE_ID } from "../audit/index.mjs"; import { getHeader, parsePositiveInteger, readBody, sendJson } from "./server-http-utils.ts"; import { getSkillTree, listSkills, readSkillTextFile, uploadUserSkill } from "./skills-store.ts"; const DEFAULT_SKILL_UPLOAD_BODY_LIMIT_BYTES = 16 * 1024 * 1024; export async function handleSkillsHttp(request, response, url, options = {}) { try { if (request.method === "GET" && url.pathname === "/v1/skills") { sendJson(response, 200, await listSkills({ env: options.env ?? process.env })); return; } if (request.method === "POST" && url.pathname === "/v1/skills/uploads") { const payload = await readJsonBody(request, skillUploadBodyLimit(options.env)); sendJson(response, 201, await uploadUserSkill(payload, { env: options.env ?? process.env })); return; } const treeMatch = url.pathname.match(/^\/v1\/skills\/([^/]+)\/tree$/u); if (request.method === "GET" && treeMatch) { sendJson(response, 200, await getSkillTree(decodeURIComponent(treeMatch[1]), { env: options.env ?? process.env })); return; } const fileMatch = url.pathname.match(/^\/v1\/skills\/([^/]+)\/files$/u); if (request.method === "GET" && fileMatch) { sendJson( response, 200, await readSkillTextFile(decodeURIComponent(fileMatch[1]), url.searchParams.get("path") || "SKILL.md", { env: options.env ?? process.env }) ); return; } sendJson(response, 404, skillsErrorPayload(404, "skill_route_not_found", "Skill route is not implemented.", request)); } catch (error) { const statusCode = Number.isInteger(error?.statusCode) ? error.statusCode : 500; sendJson(response, statusCode, skillsErrorPayload(statusCode, error?.code || "skill_request_failed", error?.message || "Skill request failed.", request)); } } async function readJsonBody(request, limitBytes) { const body = await readBody(request, limitBytes); try { return body ? JSON.parse(body) : {}; } catch (error) { const wrapped = new Error(`Invalid JSON body: ${error.message}`); Object.assign(wrapped, { statusCode: 400, code: "parse_error" }); throw wrapped; } } function skillUploadBodyLimit(env = process.env) { return parsePositiveInteger(env?.HWLAB_SKILL_UPLOAD_BODY_LIMIT_BYTES, DEFAULT_SKILL_UPLOAD_BODY_LIMIT_BYTES); } function skillsErrorPayload(statusCode, code, message, request) { return { serviceId: CLOUD_API_SERVICE_ID, route: request?.url || "/v1/skills", status: "failed", error: { code, message, httpStatus: statusCode, traceId: getHeader(request, "x-trace-id") || null } }; }