// SPEC: issue-1190 fake Responses provider P0/P1. // Responsibility: deterministic OpenAI-compatible Responses HTTP provider for HWLAB sentinel ECHO runs. import { createHash, randomUUID } from "node:crypto"; export interface FakeResponsesProviderConfig { readonly serviceName: string; readonly version: string; readonly modelId: string; readonly mode: "echo"; readonly host: string; readonly port: number; readonly responseDelayMs: number; readonly nonEchoPolicy: "error"; } export interface FakeResponsesProviderService { readonly config: FakeResponsesProviderConfig; fetch(request: Request): Promise; health(): Record; } interface ResponseRequestContext { readonly requestId: string; readonly traceId: string | null; readonly bodyBytes: number; readonly bodySha256: string; readonly model: string; readonly stream: boolean; readonly prompt: string | null; } const DEFAULT_VERSION = "issue-1190-p1"; export function loadFakeResponsesProviderConfig(env: NodeJS.ProcessEnv = process.env): FakeResponsesProviderConfig { const modelId = env.FAKE_RESPONSES_MODEL_ID || env.MODEL_ID || "fake-echo"; const host = env.LISTEN_HOST || env.HOST || "0.0.0.0"; const port = boundedPort(env.PORT, 8080); const responseDelayMs = boundedInteger(env.FAKE_RESPONSES_DELAY_MS || env.RESPONSE_DELAY_MS, 0, 0, 30_000, "response delay"); return { serviceName: env.FAKE_RESPONSES_SERVICE_NAME || "hwlab-fake-responses-provider", version: env.FAKE_RESPONSES_VERSION || DEFAULT_VERSION, modelId, mode: "echo", host, port, responseDelayMs, nonEchoPolicy: "error", }; } export function createFakeResponsesProviderService(config: FakeResponsesProviderConfig): FakeResponsesProviderService { const service: FakeResponsesProviderService = { config, async fetch(request: Request): Promise { const url = new URL(request.url); if (request.method === "GET" && url.pathname === "/healthz") return jsonResponse(service.health()); if (request.method === "GET" && url.pathname === "/v1/models") return jsonResponse(modelsPayload(config)); if (request.method === "POST" && url.pathname === "/v1/responses") return await handleResponses(config, request); return jsonResponse({ error: { type: "not_found", message: "not found" }, valuesPrinted: false }, 404); }, health(): Record { return { ok: true, service: config.serviceName, version: config.version, mode: config.mode, model: config.modelId, valuesPrinted: false, }; }, }; return service; } async function handleResponses(config: FakeResponsesProviderConfig, request: Request): Promise { const requestId = request.headers.get("x-request-id") || `fake_${randomUUID()}`; const traceId = request.headers.get("traceparent") || request.headers.get("x-trace-id"); const rawBody = await request.text(); const bodySha256 = sha256(rawBody); let body: Record; try { const parsed = JSON.parse(rawBody) as unknown; if (!isRecord(parsed)) throw new Error("body must be a JSON object"); body = parsed; } catch (error) { logRequest({ requestId, traceId, bodyBytes: Buffer.byteLength(rawBody), bodySha256, model: config.modelId, stream: false, prompt: null }, 400, "invalid-json"); return providerError("invalid_json", error instanceof Error ? error.message : String(error), requestId, 400); } const model = typeof body.model === "string" && body.model.length > 0 ? body.model : config.modelId; const stream = body.stream !== false; const prompt = latestUserText(body); const context: ResponseRequestContext = { requestId, traceId, bodyBytes: Buffer.byteLength(rawBody), bodySha256, model, stream, prompt, }; const echo = parseEcho(prompt); if (echo === null) { logRequest(context, 400, "non-echo-prompt"); return providerError("non_echo_prompt", "fake-echo only accepts latest user input matching /^ECHO\\s+([\\s\\S]*)$/", requestId, 400); } logRequest(context, 200, "echo"); return stream ? streamResponse(config, context, echo) : jsonResponse(completedResponsePayload(context, echo)); } function streamResponse(config: FakeResponsesProviderConfig, context: ResponseRequestContext, text: string): Response { const responseId = responseIdFor(context); const encoder = new TextEncoder(); const body = new ReadableStream({ async start(controller) { const enqueue = (chunk: string) => controller.enqueue(encoder.encode(chunk)); enqueue(sse("response.created", responseCreatedPayload(context, responseId))); enqueue(sse("response.output_item.added", outputItemAddedPayload(responseId))); enqueue(sse("response.content_part.added", contentPartAddedPayload(responseId))); if (config.responseDelayMs > 0) await sleep(config.responseDelayMs); enqueue(sse("response.output_text.delta", outputTextDeltaPayload(responseId, text))); enqueue(sse("response.output_text.done", outputTextDonePayload(responseId, text))); enqueue(sse("response.content_part.done", contentPartDonePayload(responseId, text))); enqueue(sse("response.output_item.done", outputItemDonePayload(responseId, text))); enqueue(sse("response.completed", { type: "response.completed", response: completedResponsePayload(context, text, responseId) })); enqueue("data: [DONE]\n\n"); controller.close(); }, }); return new Response(body, { status: 200, headers: { "content-type": "text/event-stream; charset=utf-8", "cache-control": "no-cache, no-transform", "x-request-id": context.requestId, }, }); } function modelsPayload(config: FakeResponsesProviderConfig): Record { return { object: "list", data: [{ id: config.modelId, object: "model", created: 0, owned_by: "unidesk-fake-provider", }], valuesPrinted: false, }; } function responseCreatedPayload(context: ResponseRequestContext, responseId: string): Record { return { type: "response.created", response: { id: responseId, object: "response", created_at: Math.floor(Date.now() / 1000), status: "in_progress", model: context.model, output: [], parallel_tool_calls: false, }, }; } function outputItemAddedPayload(responseId: string): Record { return { type: "response.output_item.added", response_id: responseId, output_index: 0, item: { id: "msg_echo_0", type: "message", status: "in_progress", role: "assistant", content: [] }, }; } function contentPartAddedPayload(responseId: string): Record { return { type: "response.content_part.added", response_id: responseId, item_id: "msg_echo_0", output_index: 0, content_index: 0, part: { type: "output_text", text: "" }, }; } function outputTextDeltaPayload(responseId: string, text: string): Record { return { type: "response.output_text.delta", response_id: responseId, item_id: "msg_echo_0", output_index: 0, content_index: 0, delta: text, }; } function outputTextDonePayload(responseId: string, text: string): Record { return { type: "response.output_text.done", response_id: responseId, item_id: "msg_echo_0", output_index: 0, content_index: 0, text, }; } function contentPartDonePayload(responseId: string, text: string): Record { return { type: "response.content_part.done", response_id: responseId, item_id: "msg_echo_0", output_index: 0, content_index: 0, part: { type: "output_text", text }, }; } function outputItemDonePayload(responseId: string, text: string): Record { return { type: "response.output_item.done", response_id: responseId, output_index: 0, item: assistantMessage(text, "completed"), }; } function completedResponsePayload(context: ResponseRequestContext, text: string, explicitId?: string): Record { const id = explicitId ?? responseIdFor(context); return { id, object: "response", created_at: Math.floor(Date.now() / 1000), status: "completed", model: context.model, output: [assistantMessage(text, "completed")], output_text: text, usage: usageFor(context.prompt ?? "", text), parallel_tool_calls: false, valuesPrinted: false, }; } function assistantMessage(text: string, status: "in_progress" | "completed"): Record { return { id: "msg_echo_0", type: "message", status, role: "assistant", content: [{ type: "output_text", text }], }; } function usageFor(prompt: string, output: string): Record { return { input_tokens: Math.max(1, Math.ceil(prompt.length / 4)), output_tokens: Math.max(1, Math.ceil(output.length / 4)), total_tokens: Math.max(2, Math.ceil((prompt.length + output.length) / 4)), }; } function providerError(code: string, message: string, requestId: string, status: number): Response { return jsonResponse({ error: { type: "invalid_request_error", code, message, }, requestId, valuesPrinted: false, }, status, { "x-request-id": requestId }); } function latestUserText(body: Record): string | null { const inputText = latestUserTextFromInput(body.input); if (inputText !== null) return inputText; return latestUserTextFromInput(body.messages); } function latestUserTextFromInput(input: unknown): string | null { if (typeof input === "string") return input; if (!Array.isArray(input)) return null; for (let index = input.length - 1; index >= 0; index -= 1) { const item = input[index]; if (typeof item === "string") return item; if (!isRecord(item)) continue; const role = typeof item.role === "string" ? item.role : null; if (role !== null && role !== "user") continue; const direct = contentText(item.content); if (direct !== null) return direct; const text = contentText(item); if (text !== null) return text; } return null; } function contentText(value: unknown): string | null { if (typeof value === "string") return value; if (Array.isArray(value)) { const parts = value.map(contentText).filter((item): item is string => item !== null); return parts.length === 0 ? null : parts.join(""); } if (!isRecord(value)) return null; for (const key of ["text", "input_text"]) { const found = value[key]; if (typeof found === "string") return found; } return null; } function parseEcho(prompt: string | null): string | null { if (prompt === null) return null; const match = /^ECHO\s+([\s\S]*)$/u.exec(prompt); return match?.[1] ?? null; } function responseIdFor(context: ResponseRequestContext): string { return `resp_fake_${sha256(`${context.requestId}\0${context.bodySha256}`).slice(0, 24)}`; } function logRequest(context: ResponseRequestContext, status: number, outcome: string): void { const payload = { event: "fake-responses-provider.request", requestId: context.requestId, traceIdPresent: context.traceId !== null, status, outcome, model: context.model, stream: context.stream, bodyBytes: context.bodyBytes, bodySha256: `sha256:${context.bodySha256}`, promptSha256: context.prompt === null ? null : `sha256:${sha256(context.prompt)}`, valuesPrinted: false, }; console.error(JSON.stringify(payload)); } function sse(event: string, data: Record): string { return `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`; } function jsonResponse(payload: Record, status = 200, extraHeaders: Record = {}): Response { return new Response(`${JSON.stringify(payload)}\n`, { status, headers: { "content-type": "application/json; charset=utf-8", ...extraHeaders, }, }); } function sha256(value: string): string { return createHash("sha256").update(value).digest("hex"); } function boundedPort(raw: string | undefined, fallback: number): number { return boundedInteger(raw, fallback, 1, 65_535, "port"); } function boundedInteger(raw: string | undefined, fallback: number, min: number, max: number, label: string): number { if (raw === undefined || raw.length === 0) return fallback; const value = Number(raw); if (!Number.isInteger(value) || value < min || value > max) throw new Error(`${label} must be an integer between ${min} and ${max}`); return value; } function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); }