Files
command-code-openai-bridge/src/responses.ts
T
2026-08-05 16:43:18 +08:00

1157 lines
36 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { randomUUID } from "node:crypto";
import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
import path from "node:path";
import type { ServerResponse } from "node:http";
import { Ajv, type ErrorObject, type ValidateFunction } from "ajv";
import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
import { z, ZodError } from "zod";
import {
CommandCodeError,
runCommandCode,
type CommandResult,
type InstallationStatus,
} from "./command-code.js";
import type { BridgeConfig } from "./config.js";
import { FinalTurnAccumulator } from "./final-turn.js";
import { openAIError, type CommandUsage } from "./openai.js";
import type { RequestCoordinator } from "./request-coordinator.js";
const responseIdPattern = /^resp_[A-Za-z0-9_-]{1,128}$/;
const responseIdSchema = z.string().regex(responseIdPattern, "Invalid response ID");
const roleSchema = z.enum(["system", "developer", "user", "assistant"]);
const inputTextPartSchema = z.object({
type: z.literal("input_text"),
text: z.string(),
}).strict();
const outputTextPartSchema = z.object({
type: z.literal("output_text"),
text: z.string(),
}).strict();
const inputMessageSchema = z.object({
type: z.literal("message").optional(),
role: roleSchema,
content: z.union([
z.string(),
z.array(z.union([inputTextPartSchema, outputTextPartSchema])).min(1),
]),
}).strict();
const textFormatSchema = z.object({
type: z.literal("text"),
}).strict();
const jsonObjectFormatSchema = z.object({
type: z.literal("json_object"),
}).strict();
const jsonSchemaFormatSchema = z.object({
type: z.literal("json_schema"),
name: z.string().min(1).max(64),
strict: z.boolean().optional().default(true),
schema: z.union([z.record(z.unknown()), z.boolean()]),
}).strict();
const responseTextFormatSchema = z.discriminatedUnion("type", [
textFormatSchema,
jsonObjectFormatSchema,
jsonSchemaFormatSchema,
]);
const metadataSchema = z.record(z.string().max(512)).superRefine((metadata, context) => {
if (Object.keys(metadata).length > 16) {
context.addIssue({ code: z.ZodIssueCode.custom, message: "Metadata supports at most 16 entries" });
}
for (const key of Object.keys(metadata)) {
if (key.length > 64) {
context.addIssue({ code: z.ZodIssueCode.custom, message: "Metadata keys support at most 64 characters" });
break;
}
}
});
export const responseRequestSchema = z.object({
model: z.string().min(1),
input: z.union([z.string(), z.array(inputMessageSchema).min(1)]),
instructions: z.string().nullable().optional().default(null),
stream: z.boolean().optional().default(false),
store: z.boolean().optional().default(true),
previous_response_id: responseIdSchema.nullable().optional().default(null),
metadata: metadataSchema.optional().default({}),
reasoning: z.object({
effort: z.enum(["none", "minimal", "low", "medium", "high", "xhigh", "max"]),
}).strict().optional(),
text: z.object({
format: responseTextFormatSchema,
}).strict().optional().default({ format: { type: "text" } }),
}).strict();
type ResponseRequest = z.infer<typeof responseRequestSchema>;
export type ResponseTextFormat = z.infer<typeof responseTextFormatSchema>;
type ResponseRole = z.infer<typeof roleSchema>;
type ResponseStatus = "in_progress" | "completed" | "incomplete" | "failed" | "cancelled";
interface ResponseInputPart {
type: "input_text" | "output_text";
text: string;
}
interface ResponseInputItem {
id: string;
type: "message";
role: ResponseRole;
content: ResponseInputPart[];
}
interface ResponseOutputText {
type: "output_text";
text: string;
annotations: [];
}
interface ResponseOutputMessage {
id: string;
type: "message";
role: "assistant";
status: "completed" | "incomplete";
content: ResponseOutputText[];
}
interface ResponseUsage {
input_tokens: number;
output_tokens: number;
total_tokens: number;
}
interface ResponseError {
code: string;
message: string;
}
interface LocalResponse {
id: string;
object: "response";
created_at: number;
completed_at: number | null;
status: ResponseStatus;
error: ResponseError | null;
incomplete_details: { reason: string } | null;
instructions: string | null;
model: string;
output: ResponseOutputMessage[];
output_text: string;
previous_response_id: string | null;
reasoning: { effort: string | null; summary: null };
store: boolean;
metadata: Record<string, string>;
text: { format: ResponseTextFormat };
usage: ResponseUsage | null;
}
interface StoredResponse {
response: LocalResponse;
input_items: ResponseInputItem[];
}
interface PromptMessage {
role: ResponseRole;
content: string;
}
interface StructuredValidation {
validate: (text: string) => { valid: boolean; errors: string[] };
}
interface AttemptResult {
result: CommandResult;
finalChunks: string[];
}
interface ExecutionResult {
finalText: string;
chunks: string[];
usage?: CommandUsage;
validationErrors?: string[];
}
class ResponseApiError extends Error {
constructor(
message: string,
readonly statusCode: number,
readonly code: string,
readonly type = "invalid_request_error",
readonly param: string | null = null,
) {
super(message);
this.name = "ResponseApiError";
}
}
class ResponseStoreError extends Error {
constructor(message: string, readonly cause?: unknown) {
super(message);
this.name = "ResponseStoreError";
}
}
class ResponseStore {
readonly directory: string;
constructor(directory: string) {
this.directory = path.resolve(directory);
}
async initialize(): Promise<void> {
await mkdir(this.directory, { recursive: true });
}
private filePath(id: string): string {
if (!responseIdPattern.test(id)) {
throw new ResponseApiError("Invalid response_id", 400, "invalid_response_id", "invalid_request_error", "response_id");
}
const resolved = path.resolve(this.directory, `${id}.json`);
if (path.dirname(resolved) !== this.directory) {
throw new ResponseApiError("Invalid response_id", 400, "invalid_response_id", "invalid_request_error", "response_id");
}
return resolved;
}
async load(id: string): Promise<StoredResponse | undefined> {
const filePath = this.filePath(id);
let source: string;
try {
source = await readFile(filePath, "utf8");
} catch (error) {
if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined;
throw new ResponseStoreError(`Unable to read stored response '${id}'`, error);
}
try {
const value: unknown = JSON.parse(source);
if (!isRecord(value) || !isRecord(value.response) || value.response.id !== id || !Array.isArray(value.input_items)) {
throw new Error("Stored response shape is invalid");
}
return value as unknown as StoredResponse;
} catch (error) {
throw new ResponseStoreError(`Stored response '${id}' is corrupt`, error);
}
}
async loadChain(id: string): Promise<StoredResponse[]> {
const chain: StoredResponse[] = [];
const seen = new Set<string>();
let currentId: string | null = id;
while (currentId) {
if (seen.has(currentId) || seen.size >= 1000) {
throw new ResponseStoreError("Stored response chain contains a cycle or is too deep");
}
seen.add(currentId);
const stored = await this.load(currentId);
if (!stored) {
throw new ResponseApiError(
`Response '${currentId}' was not found`,
404,
"response_not_found",
"invalid_request_error",
"previous_response_id",
);
}
chain.unshift(stored);
currentId = stored.response.previous_response_id;
}
return chain;
}
async save(stored: StoredResponse): Promise<void> {
const target = this.filePath(stored.response.id);
const temporary = path.resolve(this.directory, `.${stored.response.id}.${randomHexId()}.tmp`);
if (path.dirname(temporary) !== this.directory) throw new ResponseStoreError("Invalid temporary response path");
try {
await writeFile(temporary, `${JSON.stringify(stored, null, 2)}\n`, { encoding: "utf8", flag: "wx" });
await rename(temporary, target);
} catch (error) {
try { await unlink(temporary); } catch { /* temporary file may not exist */ }
throw new ResponseStoreError(`Unable to store response '${stored.response.id}'`, error);
}
}
async delete(id: string): Promise<boolean> {
const target = this.filePath(id);
try {
await unlink(target);
return true;
} catch (error) {
if ((error as NodeJS.ErrnoException).code === "ENOENT") return false;
throw new ResponseStoreError(`Unable to delete response '${id}'`, error);
}
}
}
class ResponsesSseWriter {
private sequenceNumber = 0;
private outputStarted = false;
private outputText = "";
constructor(
private readonly response: ServerResponse,
private readonly messageId: string,
) {}
event(type: string, payload: Record<string, unknown>): void {
if (this.response.destroyed || this.response.writableEnded) return;
const event = { type, ...payload, sequence_number: this.sequenceNumber++ };
this.response.write(`event: ${type}\ndata: ${JSON.stringify(event)}\n\n`);
}
begin(initial: LocalResponse): void {
this.event("response.created", { response: initial });
this.event("response.in_progress", { response: initial });
}
addText(chunks: string[]): void {
if (!this.outputStarted) {
this.outputStarted = true;
this.event("response.output_item.added", {
output_index: 0,
item: {
id: this.messageId,
type: "message",
role: "assistant",
status: "in_progress",
content: [],
},
});
this.event("response.content_part.added", {
item_id: this.messageId,
output_index: 0,
content_index: 0,
part: { type: "output_text", text: "", annotations: [] },
});
}
for (const delta of chunks) {
if (delta === "") continue;
this.outputText += delta;
this.event("response.output_text.delta", {
item_id: this.messageId,
output_index: 0,
content_index: 0,
delta,
});
}
}
finish(response: LocalResponse, terminalEvent: "response.completed" | "response.incomplete"): void {
this.ensureFinalText(response.output_text);
if (this.outputStarted) {
const outputMessage = response.output[0];
if (!outputMessage) throw new Error("Response output message is missing");
const part = outputMessage.content[0];
if (!part) throw new Error("Response output text part is missing");
this.event("response.output_text.done", {
item_id: this.messageId,
output_index: 0,
content_index: 0,
text: response.output_text,
});
this.event("response.content_part.done", {
item_id: this.messageId,
output_index: 0,
content_index: 0,
part,
});
this.event("response.output_item.done", { output_index: 0, item: outputMessage });
}
this.event(terminalEvent, { response });
}
failed(response: LocalResponse): void {
this.event("response.failed", { response });
}
error(code: string, message: string, param: string | null = null): void {
this.event("error", { code, message, param });
}
end(): void {
if (!this.response.destroyed && !this.response.writableEnded) this.response.end();
}
private ensureFinalText(finalText: string): void {
if (!this.outputStarted) {
this.addText([finalText]);
return;
}
if (this.outputText === finalText) return;
if (finalText.startsWith(this.outputText)) {
this.addText([finalText.slice(this.outputText.length)]);
return;
}
throw new Error("Command Code finalText does not match the final structured turn text");
}
}
interface ResponseRouteDependencies {
config: BridgeConfig;
installation: InstallationStatus;
coordinator: RequestCoordinator;
}
export async function registerResponseRoutes(
app: FastifyInstance,
dependencies: ResponseRouteDependencies,
): Promise<void> {
const { config, installation, coordinator } = dependencies;
const store = new ResponseStore(config.resolvedResponseStoreDirectory);
await store.initialize();
app.get("/v1/responses/:response_id", async (request, reply) => {
try {
emptyQuerySchema.parse(request.query);
const id = responseIdFromRequest(request);
const stored = await store.load(id);
if (!stored) throw responseNotFound(id);
return reply.send(stored.response);
} catch (error) {
return sendRouteError(reply, error);
}
});
app.delete("/v1/responses/:response_id", async (request, reply) => {
try {
emptyQuerySchema.parse(request.query);
const id = responseIdFromRequest(request);
if (!await store.delete(id)) throw responseNotFound(id);
return reply.send({ id, object: "response", deleted: true });
} catch (error) {
return sendRouteError(reply, error);
}
});
app.get("/v1/responses/:response_id/input_items", async (request, reply) => {
try {
const id = responseIdFromRequest(request);
const stored = await store.load(id);
if (!stored) throw responseNotFound(id);
const query = inputItemsQuerySchema.parse(request.query);
const ordered = query.order === "asc"
? [...stored.input_items]
: [...stored.input_items].reverse();
const afterIndex = query.after ? ordered.findIndex((item) => item.id === query.after) : -1;
const available = afterIndex >= 0 ? ordered.slice(afterIndex + 1) : ordered;
const data = available.slice(0, query.limit);
return reply.send({
object: "list",
data,
first_id: data[0]?.id ?? null,
last_id: data.at(-1)?.id ?? null,
has_more: available.length > data.length,
});
} catch (error) {
return sendRouteError(reply, error);
}
});
app.post("/v1/responses", async (request, reply) => {
if (coordinator.busy) return sendBusy(reply);
if (!installation.installed) {
return reply.status(503).send(openAIError(
"Command Code CLI is not installed",
"command_code_not_installed",
"server_error",
));
}
if (!installation.authenticated) {
return reply.status(503).send(openAIError(
"Command Code is not authenticated; run command-code login",
"command_code_not_authenticated",
"server_error",
));
}
let parsed: ResponseRequest;
let validation: StructuredValidation | undefined;
try {
parsed = parseResponseRequest(request.body);
validation = createStructuredValidation(parsed.text.format);
} catch (error) {
return sendRouteError(reply, error);
}
const model = config.models[parsed.model];
if (!model) {
return reply.status(404).send(openAIError(
`Model '${parsed.model}' is not configured`,
"model_not_found",
"invalid_request_error",
"model",
));
}
const abortController = coordinator.begin();
if (!abortController) return sendBusy(reply);
let responseCompleted = false;
const cancelOnDisconnect = () => {
if (!responseCompleted) abortController.abort("client disconnected");
};
request.raw.once("aborted", cancelOnDisconnect);
reply.raw.once("close", cancelOnDisconnect);
const inputItems = normalizeInput(parsed.input);
const responseId = newResponseId();
const messageId = newMessageId();
const createdAt = Math.floor(Date.now() / 1000);
const effectiveEffort = parsed.reasoning?.effort ?? model.effort;
const initialResponse = createResponse({
id: responseId,
messageId,
createdAt,
request: parsed,
effort: effectiveEffort,
status: "in_progress",
});
let writer: ResponsesSseWriter | undefined;
try {
const chain = parsed.previous_response_id
? await store.loadChain(parsed.previous_response_id)
: [];
const prompt = buildResponsesPrompt(parsed, chain, inputItems);
const deadline = Date.now() + config.timeout_seconds * 1000;
if (parsed.stream) {
writer = startSse(reply, messageId);
writer.begin(initialResponse);
}
const execution = await executeResponse({
config,
cliModel: model.cli_model,
effort: effectiveEffort,
prompt,
format: parsed.text.format,
signal: abortController.signal,
deadline,
...(validation ? { validation } : {}),
...(writer ? { writer } : {}),
});
const terminalResponse = createResponse({
id: responseId,
messageId,
createdAt,
request: parsed,
effort: effectiveEffort,
status: execution.validationErrors ? "incomplete" : "completed",
finalText: execution.finalText,
...(execution.usage ? { usage: execution.usage } : {}),
...(execution.validationErrors ? { incompleteReason: "structured_output_validation_failed" } : {}),
});
if (parsed.store) await store.save({ response: terminalResponse, input_items: inputItems });
responseCompleted = true;
if (writer) {
if (validation) writer.addText(execution.chunks);
writer.finish(
terminalResponse,
terminalResponse.status === "completed" ? "response.completed" : "response.incomplete",
);
writer.end();
return reply;
}
return reply.send(terminalResponse);
} catch (error) {
responseCompleted = true;
const terminal = terminalResponseForError({
error,
id: responseId,
messageId,
createdAt,
request: parsed,
effort: effectiveEffort,
});
try {
if (parsed.store && terminal.response) {
await store.save({ response: terminal.response, input_items: inputItems });
}
} catch (storeError) {
process.stderr.write(`${formatUnknownError(storeError)}\n`);
}
if (writer) {
if (terminal.response?.status === "incomplete") {
writer.finish(terminal.response, "response.incomplete");
} else if (terminal.response?.status === "failed") {
writer.failed(terminal.response);
} else {
writer.error(terminal.code, terminal.message, terminal.param);
}
writer.end();
return reply;
}
if (terminal.response) return reply.status(terminal.statusCode).send(terminal.response);
return reply.status(terminal.statusCode).send(openAIError(
terminal.message,
terminal.code,
terminal.type,
terminal.param,
));
} finally {
request.raw.off("aborted", cancelOnDisconnect);
reply.raw.off("close", cancelOnDisconnect);
coordinator.finish(abortController);
}
});
}
const inputItemsQuerySchema = z.object({
after: z.string().optional(),
limit: z.coerce.number().int().min(1).max(100).optional().default(20),
order: z.enum(["asc", "desc"]).optional().default("desc"),
}).strict();
const emptyQuerySchema = z.object({}).strict();
function parseResponseRequest(body: unknown): ResponseRequest {
const unsupported = findUnsupportedParameter(body);
if (unsupported) {
throw new ResponseApiError(
`Unsupported parameter: '${unsupported}'`,
400,
"unsupported_parameter",
"invalid_request_error",
unsupported,
);
}
try {
return responseRequestSchema.parse(body);
} catch (error) {
if (!(error instanceof ZodError)) throw error;
const unknown = error.issues.find((issue) => issue.code === "unrecognized_keys");
if (unknown?.code === "unrecognized_keys") {
const key = unknown.keys[0] ?? "unknown";
const param = [...unknown.path, key].join(".");
throw new ResponseApiError(
`Unsupported parameter: '${param}'`,
400,
"unsupported_parameter",
"invalid_request_error",
param,
);
}
const message = error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ");
throw new ResponseApiError(message, 400, "invalid_request");
}
}
function findUnsupportedParameter(body: unknown): string | undefined {
if (!isRecord(body)) return undefined;
const supported = new Set([
"model", "input", "instructions", "stream", "store",
"previous_response_id", "metadata", "reasoning", "text",
]);
const unknownTopLevel = Object.keys(body).find((key) => !supported.has(key));
if (unknownTopLevel) return unknownTopLevel;
if (isRecord(body.text) && isRecord(body.text.format)) {
const formatType = body.text.format.type;
if (formatType !== "text" && formatType !== "json_object" && formatType !== "json_schema") {
return "text.format.type";
}
}
if (!Array.isArray(body.input)) return undefined;
for (const [itemIndex, item] of body.input.entries()) {
if (!isRecord(item)) continue;
if (item.type !== undefined && item.type !== "message") return `input.${itemIndex}.type`;
if (!Array.isArray(item.content)) continue;
for (const [partIndex, part] of item.content.entries()) {
if (!isRecord(part)) continue;
if (part.type !== "input_text" && part.type !== "output_text") {
return `input.${itemIndex}.content.${partIndex}.type`;
}
}
}
return undefined;
}
function normalizeInput(input: ResponseRequest["input"]): ResponseInputItem[] {
if (typeof input === "string") {
return [{
id: newMessageId(),
type: "message",
role: "user",
content: [{ type: "input_text", text: input }],
}];
}
return input.map((message) => ({
id: newMessageId(),
type: "message",
role: message.role,
content: typeof message.content === "string"
? [{ type: message.role === "assistant" ? "output_text" : "input_text", text: message.content }]
: message.content.map((part) => ({ type: part.type, text: part.text })),
}));
}
function buildResponsesPrompt(
request: ResponseRequest,
chain: StoredResponse[],
currentInput: ResponseInputItem[],
): string {
const messages: PromptMessage[] = [];
for (const stored of chain) {
messages.push(...stored.input_items.map(inputItemToPromptMessage));
if (stored.response.output_text !== "") {
messages.push({ role: "assistant", content: stored.response.output_text });
}
}
messages.push(...currentInput.map(inputItemToPromptMessage));
const envelope = {
protocol: "openai-responses-text-history-v1",
instructions: request.instructions,
messages,
};
return [
"下面 JSON 对象是外部客户端提交的完整 Responses 文本任务和本地重建的响应链。",
"按 system、developer、user、assistant 的角色与顺序理解上下文;当前 instructions 是本次请求的高优先级开发者指令。",
"严格执行最后一个用户任务。不要复述 JSON,不要输出角色标签,不要暴露内部思考。",
formatDirective(request.text.format),
"JSON 数据开始:",
JSON.stringify(envelope),
].join("\n\n");
}
function buildRepairPrompt(
originalPrompt: string,
format: ResponseTextFormat,
invalidOutput: string,
validationErrors: string[],
): string {
return [
"这是同一 Responses 请求的唯一一次结构化输出修复步骤。",
"保留原始答案的语义,只修复 JSON 语法和 Schema 违规;只返回修复后的 JSON,禁止代码围栏和解释。",
formatDirective(format),
"校验错误:",
JSON.stringify(validationErrors),
"不合格输出:",
JSON.stringify(invalidOutput),
"原始任务:",
originalPrompt,
].join("\n\n");
}
function formatDirective(format: ResponseTextFormat): string {
if (format.type === "text") return "内部输出格式要求:返回纯文本。";
if (format.type === "json_object") {
return "内部输出格式要求:只返回一个合法 JSON 对象;禁止 Markdown 代码围栏、前后说明和非 JSON 文本。";
}
return [
`内部输出格式要求:只返回一个符合 JSON Schema '${format.name}' 的合法 JSON 值;禁止 Markdown 代码围栏、前后说明和非 JSON 文本。`,
`JSON Schema${JSON.stringify(format.schema)}`,
].join("\n");
}
async function executeResponse(options: {
config: BridgeConfig;
cliModel: string;
effort: string;
prompt: string;
format: ResponseTextFormat;
validation?: StructuredValidation;
signal: AbortSignal;
deadline: number;
writer?: ResponsesSseWriter;
}): Promise<ExecutionResult> {
const first = await executeAttempt(options, options.prompt, options.validation ? undefined : options.writer);
if (!options.validation) {
return {
finalText: first.result.finalText,
chunks: first.finalChunks,
...(first.result.usage ? { usage: first.result.usage } : {}),
};
}
const firstValidation = options.validation.validate(first.result.finalText);
if (firstValidation.valid) {
return {
finalText: first.result.finalText,
chunks: first.finalChunks,
...(first.result.usage ? { usage: first.result.usage } : {}),
};
}
const repairPrompt = buildRepairPrompt(
options.prompt,
options.format,
first.result.finalText,
firstValidation.errors,
);
const second = await executeAttempt(options, repairPrompt);
const combinedUsage = addUsage(first.result.usage, second.result.usage);
const secondValidation = options.validation.validate(second.result.finalText);
return {
finalText: second.result.finalText,
chunks: second.finalChunks,
...(combinedUsage ? { usage: combinedUsage } : {}),
...(!secondValidation.valid ? { validationErrors: secondValidation.errors } : {}),
};
}
async function executeAttempt(
options: {
config: BridgeConfig;
cliModel: string;
effort: string;
signal: AbortSignal;
deadline: number;
},
prompt: string,
writer?: ResponsesSseWriter,
): Promise<AttemptResult> {
const accumulator = new FinalTurnAccumulator();
const result = await runCommandCode(
options.config,
options.cliModel,
options.effort,
prompt,
options.signal,
{
timeoutMs: options.deadline - Date.now(),
onEvent: (event) => {
const chunks = accumulator.event(event);
if (chunks && writer) writer.addText(chunks);
},
},
);
return {
result,
finalChunks: accumulator.finalChunks ?? [result.finalText],
};
}
function createStructuredValidation(format: ResponseTextFormat): StructuredValidation | undefined {
if (format.type === "text") return undefined;
if (format.type === "json_object") {
return {
validate: (text) => {
try {
const value: unknown = JSON.parse(text);
const valid = isRecord(value);
return valid
? { valid: true, errors: [] }
: { valid: false, errors: ["Output must be a JSON object"] };
} catch (error) {
return { valid: false, errors: [`Invalid JSON: ${formatUnknownError(error)}`] };
}
},
};
}
let validator: ValidateFunction;
try {
const ajv = new Ajv({ allErrors: true, strict: false });
validator = ajv.compile(format.schema);
} catch (error) {
throw new ResponseApiError(
`Invalid JSON Schema: ${formatUnknownError(error)}`,
400,
"invalid_json_schema",
"invalid_request_error",
"text.format.schema",
);
}
return {
validate: (text) => {
let value: unknown;
try {
value = JSON.parse(text);
} catch (error) {
return { valid: false, errors: [`Invalid JSON: ${formatUnknownError(error)}`] };
}
const valid = validator(value);
return {
valid,
errors: valid ? [] : formatAjvErrors(validator.errors),
};
},
};
}
function createResponse(options: {
id: string;
messageId: string;
createdAt: number;
request: ResponseRequest;
effort: string;
status: ResponseStatus;
finalText?: string;
usage?: CommandUsage;
error?: ResponseError;
incompleteReason?: string;
}): LocalResponse {
const finalText = options.finalText ?? "";
const terminal = options.status !== "in_progress";
const hasOutput = options.finalText !== undefined;
const outputStatus = options.status === "completed" ? "completed" : "incomplete";
return {
id: options.id,
object: "response",
created_at: options.createdAt,
completed_at: terminal ? Math.floor(Date.now() / 1000) : null,
status: options.status,
error: options.error ?? null,
incomplete_details: options.incompleteReason ? { reason: options.incompleteReason } : null,
instructions: options.request.instructions,
model: options.request.model,
output: hasOutput ? [{
id: options.messageId,
type: "message",
role: "assistant",
status: outputStatus,
content: [{ type: "output_text", text: finalText, annotations: [] }],
}] : [],
output_text: finalText,
previous_response_id: options.request.previous_response_id,
reasoning: { effort: options.effort, summary: null },
store: options.request.store,
metadata: options.request.metadata,
text: { format: options.request.text.format },
usage: toResponseUsage(options.usage),
};
}
function terminalResponseForError(options: {
error: unknown;
id: string;
messageId: string;
createdAt: number;
request: ResponseRequest;
effort: string;
}): {
response?: LocalResponse;
statusCode: number;
code: string;
message: string;
type: string;
param: string | null;
} {
const { error } = options;
if (error instanceof ResponseApiError) {
return {
statusCode: error.statusCode,
code: error.code,
message: error.message,
type: error.type,
param: error.param,
};
}
if (error instanceof CommandCodeError) {
if (error.kind === "max_turns" || error.kind === "timeout") {
const reason = error.kind === "max_turns" ? "max_turns" : "timeout";
return {
response: createResponse({
id: options.id,
messageId: options.messageId,
createdAt: options.createdAt,
request: options.request,
effort: options.effort,
status: "incomplete",
incompleteReason: reason,
}),
statusCode: 200,
code: reason,
message: error.message,
type: "server_error",
param: null,
};
}
if (error.kind === "cancelled") {
return {
response: createResponse({
id: options.id,
messageId: options.messageId,
createdAt: options.createdAt,
request: options.request,
effort: options.effort,
status: "cancelled",
error: { code: "cancelled", message: "Command Code request cancelled" },
}),
statusCode: 499,
code: "cancelled",
message: "Command Code request cancelled",
type: "server_error",
param: null,
};
}
const mapped = commandErrorMapping(error.exitCode);
return {
response: createResponse({
id: options.id,
messageId: options.messageId,
createdAt: options.createdAt,
request: options.request,
effort: options.effort,
status: "failed",
error: { code: mapped.code, message: mapped.message },
}),
statusCode: mapped.statusCode,
code: mapped.code,
message: mapped.message,
type: "server_error",
param: null,
};
}
process.stderr.write(`${formatUnknownError(error)}\n`);
return {
statusCode: 500,
code: "bridge_error",
message: "Internal bridge error",
type: "server_error",
param: null,
};
}
function commandErrorMapping(exitCode?: number | null): { statusCode: number; code: string; message: string } {
switch (exitCode) {
case 3:
return { statusCode: 503, code: "command_code_not_authenticated", message: "Command Code is not authenticated" };
case 5:
return { statusCode: 429, code: "rate_limit_exceeded", message: "Command Code rate limit exceeded" };
case 10:
return { statusCode: 402, code: "insufficient_credits", message: "Command Code credits are insufficient" };
default:
return { statusCode: 502, code: "command_code_error", message: "Command Code request failed" };
}
}
function startSse(reply: FastifyReply, messageId: string): ResponsesSseWriter {
reply.hijack();
reply.raw.writeHead(200, {
"Content-Type": "text/event-stream; charset=utf-8",
"Cache-Control": "no-cache, no-transform",
Connection: "keep-alive",
"X-Accel-Buffering": "no",
});
reply.raw.flushHeaders();
return new ResponsesSseWriter(reply.raw, messageId);
}
function sendRouteError(reply: FastifyReply, error: unknown) {
if (error instanceof ResponseApiError) {
return reply.status(error.statusCode).send(openAIError(error.message, error.code, error.type, error.param));
}
if (error instanceof ZodError) {
const unknown = error.issues.find((issue) => issue.code === "unrecognized_keys");
if (unknown?.code === "unrecognized_keys") {
const key = unknown.keys[0] ?? "unknown";
const param = [...unknown.path, key].join(".");
return reply.status(400).send(openAIError(
`Unsupported parameter: '${param}'`,
"unsupported_parameter",
"invalid_request_error",
param,
));
}
const message = error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ");
return reply.status(400).send(openAIError(message, "invalid_request"));
}
process.stderr.write(`${formatUnknownError(error)}\n`);
return reply.status(500).send(openAIError("Internal bridge error", "bridge_error", "server_error"));
}
function sendBusy(reply: FastifyReply) {
return reply.status(429).send(openAIError(
"Another Command Code request is already running",
"busy",
"server_error",
));
}
function responseIdFromRequest(request: FastifyRequest): string {
const params = request.params as { response_id?: unknown };
const result = responseIdSchema.safeParse(params.response_id);
if (!result.success) {
throw new ResponseApiError("Invalid response_id", 400, "invalid_response_id", "invalid_request_error", "response_id");
}
return result.data;
}
function responseNotFound(id: string): ResponseApiError {
return new ResponseApiError(
`Response '${id}' was not found`,
404,
"response_not_found",
"invalid_request_error",
"response_id",
);
}
function inputItemToPromptMessage(item: ResponseInputItem): PromptMessage {
return { role: item.role, content: item.content.map((part) => part.text).join("") };
}
function toResponseUsage(usage?: CommandUsage): ResponseUsage | null {
if (!usage || (usage.inputTokens === undefined && usage.outputTokens === undefined)) return null;
const inputTokens = usage.inputTokens ?? 0;
const outputTokens = usage.outputTokens ?? 0;
return {
input_tokens: inputTokens,
output_tokens: outputTokens,
total_tokens: inputTokens + outputTokens,
};
}
function addUsage(left?: CommandUsage, right?: CommandUsage): CommandUsage | undefined {
if (!left && !right) return undefined;
return {
inputTokens: (left?.inputTokens ?? 0) + (right?.inputTokens ?? 0),
outputTokens: (left?.outputTokens ?? 0) + (right?.outputTokens ?? 0),
};
}
function formatAjvErrors(errors: ErrorObject[] | null | undefined): string[] {
if (!errors || errors.length === 0) return ["JSON Schema validation failed"];
return errors.map((error) => `${error.instancePath || "/"}: ${error.message ?? error.keyword}`);
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function newResponseId(): string {
return `resp_local_${randomHexId()}`;
}
function newMessageId(): string {
return `msg_local_${randomHexId()}`;
}
function randomHexId(): string {
return randomUUID().replaceAll("-", "");
}
function formatUnknownError(error: unknown): string {
return error instanceof Error ? (error.stack ?? error.message) : String(error);
}