/* * SPEC: PJ2026-010103 HWPOD 服务。 * 实现引用: draft-2026-07-20-hwpod-temporal-split。 * 责任: 独立 HWPOD API HTTP 合同。 */ import { validateHwpodOperationInput } from "./contracts.ts"; export function createHwpodHttpApp(options: { runtimeApiUrl: string; runtimeApiAuthorization: string; temporal: any }) { return { async fetch(request: Request) { const url = new URL(request.url); try { if (url.pathname === "/health/live") return json(200, { ok: true, serviceId: "hwlab-hwpod-api", status: "live", valuesPrinted: false }); if (url.pathname === "/health/ready") return readiness(options); if (url.pathname === "/v1/hwpod" && request.method === "GET") return json(200, { ok: true, serviceId: "hwlab-hwpod-api", executionAuthority: "temporal", runtimeApiUrl: options.runtimeApiUrl, valuesPrinted: false }); if (url.pathname === "/v1/hwpod/specs" && request.method === "GET") return proxyGet(options.runtimeApiUrl, `/v1/hwpod/specs${url.search}`, options.runtimeApiAuthorization); if (url.pathname === "/v1/hwpod/topology" && request.method === "GET") return proxyGet(options.runtimeApiUrl, `/v1/hwpod/topology${url.search}`, options.runtimeApiAuthorization); if (url.pathname === "/v1/hwpod-node-ops" && request.method === "GET") return proxyGet(options.runtimeApiUrl, `/v1/hwpod-node-ops${url.search}`, options.runtimeApiAuthorization); if (url.pathname === "/v1/hwpod/operations" && request.method === "POST") { const body = await request.json(); const input = validateHwpodOperationInput({ ...(body as any), runtimeApiUrl: options.runtimeApiUrl, authorization: options.runtimeApiAuthorization }); const started = await options.temporal.start(input); return json(202, { ok: true, status: "accepted", operationId: input.operationId, ...started, authority: "hwpod-temporal", valuesPrinted: false }); } const match = /^\/v1\/hwpod\/operations\/([^/]+)$/u.exec(url.pathname); if (match && request.method === "GET") { const operationId = decodeURIComponent(match[1]); const workflowId = `hwpod-operation-${operationId}`; const description = await options.temporal.describe(workflowId); const status = String((description as any)?.status?.name ?? (description as any)?.status ?? "RUNNING").toLowerCase(); const result = /completed|failed|canceled/u.test(status) ? await options.temporal.result(workflowId).catch((error: any) => ({ ok: false, error: { code: error?.code ?? "hwpod_workflow_result_failed", message: error?.message ?? String(error) } })) : null; return json(200, { ok: true, operationId, workflowId, workflowRunId: (description as any)?.runId ?? null, status, result, valuesPrinted: false }); } return json(404, { ok: false, error: { code: "hwpod_route_not_found", message: "HWPOD API route was not found" } }); } catch (error: any) { const code = error?.code ?? "hwpod_http_error"; return json(code.includes("not_found") ? 404 : 400, { ok: false, error: { code, message: error?.message ?? String(error) }, valuesPrinted: false }); } }, close: async () => options.temporal.close() }; } async function readiness(options: { runtimeApiUrl: string; runtimeApiAuthorization: string; temporal: any }) { try { const response = await fetch(`${options.runtimeApiUrl}/v1/hwpod/topology?resource=node&limit=1`, { headers: { authorization: options.runtimeApiAuthorization }, signal: AbortSignal.timeout(5000) }); return json(response.ok ? 200 : 503, { ok: response.ok, serviceId: "hwlab-hwpod-api", temporal: "configured", runtimeApi: response.ok ? "reachable" : "unreachable", valuesPrinted: false }); } catch { return json(503, { ok: false, serviceId: "hwlab-hwpod-api", temporal: "configured", runtimeApi: "unreachable", valuesPrinted: false }); } } async function proxyGet(base: string, route: string, authorization: string) { const response = await fetch(`${base}${route}`, { headers: { authorization }, signal: AbortSignal.timeout(10_000) }); return new Response(await response.text(), { status: response.status, headers: { "content-type": response.headers.get("content-type") ?? "application/json", "cache-control": "no-store" } }); } function json(status: number, body: unknown) { return Response.json(body, { status, headers: { "cache-control": "no-store" } }); }