Files
pikasTech-unidesk/scripts/src/fake-responses-provider-service.ts
T
2026-06-28 02:34:20 +00:00

376 lines
13 KiB
TypeScript

// 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<Response>;
health(): Record<string, unknown>;
}
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<Response> {
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<string, unknown> {
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<Response> {
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<string, unknown>;
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<Uint8Array>({
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<string, unknown> {
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<string, unknown> {
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<string, unknown> {
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<string, unknown> {
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<string, unknown> {
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<string, unknown> {
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<string, unknown> {
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<string, unknown> {
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<string, unknown> {
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<string, unknown> {
return {
id: "msg_echo_0",
type: "message",
status,
role: "assistant",
content: [{ type: "output_text", text }],
};
}
function usageFor(prompt: string, output: string): Record<string, unknown> {
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, unknown>): 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, unknown>): string {
return `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
}
function jsonResponse(payload: Record<string, unknown>, status = 200, extraHeaders: Record<string, string> = {}): 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<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}