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 { attachmentPromptBlock, AttachmentInputError, decodeImageDataUrl, decodeTextFileData, materializeAttachments, validateAttachmentBatch, type AttachmentReference, type MaterializedAttachments, type PendingAttachment, } from "./attachments.js"; 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"; import { parseFunctionToolDecision, parseFunctionToolArguments, ToolDecisionError, validateFunctionToolArguments, validateFunctionToolSchemas, type FunctionToolCall, type FunctionToolChoice, type FunctionToolDecisionRequest, type FunctionToolDefinition, } from "./tool-calling.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 functionNameSchema = z.string().min(1).max(64).regex(/^[A-Za-z0-9_-]+$/); const responseItemIdSchema = z.string().min(1).max(128); 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 inputImagePartSchema = z.object({ type: z.literal("input_image"), image_url: z.string().min(1).optional(), file_id: z.string().min(1).optional(), detail: z.enum(["auto", "low", "high"]).optional(), }).strict().superRefine((part, context) => { if ((part.image_url === undefined) === (part.file_id === undefined)) { context.addIssue({ code: z.ZodIssueCode.custom, message: "input_image requires exactly one of image_url or file_id", }); } }); const inputFilePartSchema = z.object({ type: z.literal("input_file"), file_data: z.string().min(1).optional(), file_url: z.string().min(1).optional(), file_id: z.string().min(1).optional(), filename: z.string().min(1).max(255).optional(), }).strict().superRefine((part, context) => { const sourceCount = [part.file_data, part.file_url, part.file_id] .filter((source) => source !== undefined).length; if (sourceCount !== 1) { context.addIssue({ code: z.ZodIssueCode.custom, message: "input_file requires exactly one of file_data, file_url, or file_id", }); } if (part.file_data !== undefined && part.filename === undefined) { context.addIssue({ code: z.ZodIssueCode.custom, path: ["filename"], message: "filename is required with file_data", }); } }); const inputMessageSchema = z.object({ type: z.literal("message").optional(), role: roleSchema, content: z.union([ z.string(), z.array(z.union([ inputTextPartSchema, outputTextPartSchema, inputImagePartSchema, inputFilePartSchema, ])).min(1), ]), }).strict(); const inputFunctionCallSchema = z.object({ id: responseItemIdSchema.optional(), type: z.literal("function_call"), call_id: responseItemIdSchema, name: functionNameSchema, arguments: z.string(), status: z.enum(["in_progress", "completed", "incomplete"]).optional(), }).strict(); const inputFunctionCallOutputSchema = z.object({ id: responseItemIdSchema.optional(), type: z.literal("function_call_output"), call_id: responseItemIdSchema, output: z.string(), status: z.enum(["in_progress", "completed", "incomplete"]).optional(), }).strict(); const responseInputItemSchema = z.union([ inputMessageSchema, inputFunctionCallSchema, inputFunctionCallOutputSchema, ]); const responseFunctionToolSchema = z.object({ type: z.literal("function"), name: functionNameSchema, description: z.string().nullable().optional(), parameters: z.union([z.record(z.unknown()), z.null()]).optional(), strict: z.boolean().nullable().optional(), }).strict(); const responseToolChoiceSchema = z.union([ z.enum(["none", "auto", "required"]), z.object({ type: z.literal("function"), name: functionNameSchema }).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(responseInputItemSchema).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" } }), tools: z.array(responseFunctionToolSchema).min(1).max(128).optional(), tool_choice: responseToolChoiceSchema.optional(), parallel_tool_calls: z.boolean().optional(), }).strict().superRefine((request, context) => { const toolNames = new Set(); for (const [index, tool] of (request.tools ?? []).entries()) { if (toolNames.has(tool.name)) { context.addIssue({ code: z.ZodIssueCode.custom, path: ["tools", index, "name"], message: `Duplicate tool name '${tool.name}'`, }); } toolNames.add(tool.name); } if (request.tool_choice !== undefined && request.tools === undefined) { context.addIssue({ code: z.ZodIssueCode.custom, path: ["tool_choice"], message: "tool_choice requires tools", }); } if (typeof request.tool_choice === "object" && !toolNames.has(request.tool_choice.name)) { context.addIssue({ code: z.ZodIssueCode.custom, path: ["tool_choice", "name"], message: `Unknown forced tool '${request.tool_choice.name}'`, }); } }); type ResponseRequest = z.infer; export type ResponseTextFormat = z.infer; type ResponseRole = z.infer; type ResponseStatus = "in_progress" | "completed" | "incomplete" | "failed" | "cancelled"; interface ResponseInputPart { type: "input_text" | "output_text"; text: string; } interface ResponseInputMessage { id: string; type: "message"; role: ResponseRole; content: ResponseInputPart[]; } interface ResponseInputFunctionCall { id: string; type: "function_call"; call_id: string; name: string; arguments: string; status: "in_progress" | "completed" | "incomplete"; } interface ResponseInputFunctionCallOutput { id: string; type: "function_call_output"; call_id: string; output: string; status: "in_progress" | "completed" | "incomplete"; } type ResponseInputItem = ResponseInputMessage | ResponseInputFunctionCall | ResponseInputFunctionCallOutput; interface ResponseOutputText { type: "output_text"; text: string; annotations: []; } interface ResponseOutputMessage { id: string; type: "message"; role: "assistant"; status: "completed" | "incomplete"; content: ResponseOutputText[]; } interface ResponseOutputFunctionCall { id: string; type: "function_call"; call_id: string; name: string; arguments: string; status: "completed" | "incomplete"; } interface ResponseReasoningSummaryText { type: "summary_text"; text: string; } interface ResponseOutputReasoning { id: string; type: "reasoning"; summary: ResponseReasoningSummaryText[]; status: "in_progress" | "completed" | "incomplete"; content?: Array<{ type: "reasoning_text"; text: string }>; } type ResponseOutputItem = ResponseOutputMessage | ResponseOutputFunctionCall | ResponseOutputReasoning; 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: ResponseOutputItem[]; output_text: string; previous_response_id: string | null; reasoning: { effort: string | null; summary: null }; store: boolean; metadata: Record; text: { format: ResponseTextFormat }; tools: ResponseFunctionTool[]; tool_choice: ResponseToolChoice; parallel_tool_calls: boolean; usage: ResponseUsage | null; } interface ResponseFunctionTool { type: "function"; name: string; description?: string | null; parameters: Record; strict: boolean | null; } type ResponseToolChoice = "none" | "auto" | "required" | { type: "function"; name: string }; interface StoredResponse { response: LocalResponse; input_items: ResponseInputItem[]; } export interface StructuredValidation { validate: (text: string) => { valid: boolean; errors: string[] }; } interface AttemptResult { result: CommandResult; finalChunks: string[]; reasoningItems: ResponseOutputReasoning[]; } interface ExecutionResult { finalText: string; chunks: string[]; reasoningItems: ResponseOutputReasoning[]; usage?: CommandUsage; validationErrors?: string[]; toolCalls?: FunctionToolCall[]; } 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 { 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 { 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 { const chain: StoredResponse[] = []; const seen = new Set(); 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 { 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 { 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 ResponseReasoningAccumulator { private readonly completed: ResponseOutputReasoning[] = []; private current: { id: string; outputIndex: number; text: string } | undefined; constructor(private readonly writer?: ResponsesSseWriter) {} event(event: Record): void { if (event.type === "thinking_start") { this.close(); return; } if (event.type === "thinking_delta" && typeof event.delta === "string") { this.add(event.delta); return; } if ( event.type === "thinking_end" || event.type === "turn_start" || event.type === "text_delta" || event.type === "message_end" || event.type === "turn_end" ) { this.close(); } } finish(): ResponseOutputReasoning[] { this.close(); return [...this.completed]; } private add(delta: string): void { if (delta === "") return; if (!this.current) { this.current = { id: newReasoningItemId(), outputIndex: this.completed.length, text: "", }; this.writer?.beginReasoning(this.current.id, this.current.outputIndex); } this.current.text += delta; this.writer?.addReasoningDelta(this.current.id, this.current.outputIndex, delta); } private close(): void { if (!this.current) return; const item: ResponseOutputReasoning = { id: this.current.id, type: "reasoning", summary: [{ type: "summary_text", text: this.current.text }], status: "completed", }; this.completed.push(item); this.writer?.finishReasoning(item, this.current.outputIndex); this.current = undefined; } } class ResponsesSseWriter { private sequenceNumber = 0; private outputStarted = false; private outputText = ""; private textOutputIndex: number | undefined; private readonly emittedReasoningIds = new Set(); private readonly completedReasoning = new Map(); constructor( private readonly response: ServerResponse, private readonly messageId: string, ) {} event(type: string, payload: Record): 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 }); } beginReasoning(itemId: string, outputIndex: number): void { if (this.emittedReasoningIds.has(itemId)) return; this.emittedReasoningIds.add(itemId); this.event("response.output_item.added", { output_index: outputIndex, item: { id: itemId, type: "reasoning", summary: [], status: "in_progress", }, }); this.event("response.reasoning_summary_part.added", { item_id: itemId, output_index: outputIndex, summary_index: 0, part: { type: "summary_text", text: "" }, }); } addReasoningDelta(itemId: string, outputIndex: number, delta: string): void { if (delta === "" || !this.emittedReasoningIds.has(itemId)) return; this.event("response.reasoning_summary_text.delta", { item_id: itemId, output_index: outputIndex, summary_index: 0, delta, }); } finishReasoning(item: ResponseOutputReasoning, outputIndex: number): void { if (this.completedReasoning.has(item.id)) return; const part = item.summary[0]; if (!part) return; this.completedReasoning.set(item.id, item); this.event("response.reasoning_summary_text.done", { item_id: item.id, output_index: outputIndex, summary_index: 0, text: part.text, }); this.event("response.reasoning_summary_part.done", { item_id: item.id, output_index: outputIndex, summary_index: 0, part, }); this.event("response.output_item.done", { output_index: outputIndex, item }); } addReasoningItems(items: ResponseOutputReasoning[]): void { for (const [outputIndex, item] of items.entries()) { if (this.emittedReasoningIds.has(item.id)) continue; this.beginReasoning(item.id, outputIndex); const part = item.summary[0]; if (part?.text) this.addReasoningDelta(item.id, outputIndex, part.text); this.finishReasoning(item, outputIndex); } } addText(chunks: string[]): void { if (!this.outputStarted) { this.outputStarted = true; this.textOutputIndex = this.completedReasoning.size; this.event("response.output_item.added", { output_index: this.textOutputIndex, item: { id: this.messageId, type: "message", role: "assistant", status: "in_progress", content: [], }, }); this.event("response.content_part.added", { item_id: this.messageId, output_index: this.textOutputIndex, 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: this.textOutputIndex, content_index: 0, delta, }); } } finish(response: LocalResponse, terminalEvent: "response.completed" | "response.incomplete"): void { if (response.output.some((item) => item.type === "function_call")) { this.addToolCalls(response); this.event(terminalEvent, { response }); return; } const outputIndex = response.output.findIndex((item) => ( item.type === "message" && item.id === this.messageId )); if (outputIndex < 0) { this.event(terminalEvent, { response }); return; } this.ensureFinalText(response.output_text); if (this.outputStarted) { const outputMessage = response.output[outputIndex]; if (!outputMessage || outputMessage.type !== "message") { throw new Error("Response output message is missing"); } if (this.textOutputIndex !== outputIndex) { throw new Error("Response output message index does not match the streamed item index"); } 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: outputIndex, content_index: 0, text: response.output_text, }); this.event("response.content_part.done", { item_id: this.messageId, output_index: outputIndex, content_index: 0, part, }); this.event("response.output_item.done", { output_index: outputIndex, item: outputMessage }); } this.event(terminalEvent, { response }); } private addToolCalls(response: LocalResponse): void { for (const [outputIndex, item] of response.output.entries()) { if (item.type !== "function_call") continue; const addedItem = { ...item, arguments: "", status: "in_progress" as const }; this.event("response.output_item.added", { output_index: outputIndex, item: addedItem }); if (item.arguments !== "") { this.event("response.function_call_arguments.delta", { item_id: item.id, output_index: outputIndex, delta: item.arguments, }); } this.event("response.function_call_arguments.done", { item_id: item.id, output_index: outputIndex, arguments: item.arguments, }); this.event("response.output_item.done", { output_index: outputIndex, item }); } } 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 { 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 (!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; let pendingAttachments: PendingAttachment[]; try { parsed = parseResponseRequest(request.body); validation = createStructuredValidation(parsed.text.format); validateResponseToolRequest(parsed); pendingAttachments = responseInputAttachments(parsed); } 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 firstImage = pendingAttachments.find((attachment) => attachment.kind === "image"); if (firstImage && !model.supports_image_input) { return sendRouteError(reply, new AttachmentInputError( `Model '${parsed.model}' is not configured for image input`, "unsupported_parameter", firstImage.param, )); } const lease = coordinator.begin(); if (!lease) return sendBusy(reply); const { controller: abortController } = lease; let responseCompleted = false; let acquired = false; let materialized: MaterializedAttachments | undefined; const cancelOnDisconnect = () => { if (!responseCompleted) coordinator.cancel(abortController, "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 { if (parsed.stream) { writer = startSse(reply, messageId); writer.begin(initialResponse); } if (request.raw.aborted || reply.raw.destroyed) cancelOnDisconnect(); acquired = await lease.ready; if (!acquired) { responseCompleted = true; return reply; } materialized = await materializeAttachments( config.resolvedWorkingDirectory, pendingAttachments, ); const chain = parsed.previous_response_id ? await store.loadChain(parsed.previous_response_id) : []; validateResponseFunctionHistory(parsed, chain, inputItems); const prompt = buildResponsesPrompt(parsed, chain, inputItems, materialized.references); const deadline = Date.now() + config.timeout_seconds * 1000; const toolRequest = responseFunctionToolDecisionRequest(parsed); const execution = await executeResponse({ config, cliModel: model.cli_model, effort: effectiveEffort, prompt, format: parsed.text.format, ...(toolRequest ? { toolRequest } : {}), 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, reasoningItems: execution.reasoningItems, ...(execution.toolCalls ? { toolCalls: execution.toolCalls } : {}), ...(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) { writer.addReasoningItems(execution.reasoningItems); 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); try { await materialized?.cleanup(); } catch { process.stderr.write("警告:无法清理本次请求的临时附件目录。\n"); } if (!acquired) coordinator.cancel(abortController, "request ended before execution"); 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", "tools", "tool_choice", "parallel_tool_calls", ]); 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.tools)) { for (const [toolIndex, tool] of body.tools.entries()) { if (!isRecord(tool)) continue; if (tool.type !== "function") return `tools.${toolIndex}.type`; } } if (isRecord(body.tool_choice) && body.tool_choice.type !== "function") { return "tool_choice.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" && item.type !== "function_call" && item.type !== "function_call_output" ) return `input.${itemIndex}.type`; if (item.type === "function_call" || item.type === "function_call_output") continue; 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" && part.type !== "input_image" && part.type !== "input_file" ) { return `input.${itemIndex}.content.${partIndex}.type`; } } } return undefined; } function responseInputAttachments(request: ResponseRequest): PendingAttachment[] { if (typeof request.input === "string") return []; const attachments: PendingAttachment[] = []; for (const [itemIndex, item] of request.input.entries()) { if (item.type === "function_call" || item.type === "function_call_output") continue; if (typeof item.content === "string") continue; for (const [partIndex, part] of item.content.entries()) { const baseParam = `input.${itemIndex}.content.${partIndex}`; if (part.type === "input_image") { if (part.file_id !== undefined) { throw new AttachmentInputError( "file_id image inputs require an OpenAI Files service, which this bridge does not implement", "unsupported_parameter", `${baseParam}.file_id`, ); } if (part.detail !== undefined && part.detail !== "auto") { throw new AttachmentInputError( "Command Code does not expose image detail controls; omit detail or use 'auto'", "unsupported_parameter", `${baseParam}.detail`, ); } attachments.push(decodeImageDataUrl( part.image_url!, `${baseParam}.image_url`, )); continue; } if (part.type !== "input_file") continue; if (part.file_url !== undefined) { throw new AttachmentInputError( "Remote file URLs are not fetched; provide UTF-8 text through file_data", "unsupported_parameter", `${baseParam}.file_url`, ); } if (part.file_id !== undefined) { throw new AttachmentInputError( "file_id inputs require an OpenAI Files service, which this bridge does not implement", "unsupported_parameter", `${baseParam}.file_id`, ); } attachments.push(decodeTextFileData( part.file_data!, part.filename!, `${baseParam}.file_data`, )); } } validateAttachmentBatch(attachments); return attachments; } 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((item) => { if (item.type === "function_call") { return { id: item.id ?? newFunctionCallItemId(), type: "function_call", call_id: item.call_id, name: item.name, arguments: item.arguments, status: item.status ?? "completed", }; } if (item.type === "function_call_output") { return { id: item.id ?? newFunctionCallOutputItemId(), type: "function_call_output", call_id: item.call_id, output: item.output, status: item.status ?? "completed", }; } return { id: newMessageId(), type: "message", role: item.role, content: typeof item.content === "string" ? [{ type: item.role === "assistant" ? "output_text" : "input_text", text: item.content }] : item.content.flatMap((part) => ( part.type === "input_text" || part.type === "output_text" ? [{ type: part.type, text: part.text }] : [] )), }; }); } function buildResponsesPrompt( request: ResponseRequest, chain: StoredResponse[], currentInput: ResponseInputItem[], attachmentReferences: ReadonlyMap, ): string { const history: Array = []; for (const stored of chain) { history.push(...stored.input_items, ...stored.response.output); } history.push(...currentInput); const envelope = { protocol: usesResponseToolCalling(request, history) ? "openai-responses-tools-history-v1" : "openai-responses-text-history-v1", instructions: request.instructions, input: history, tools: normalizeResponseTools(request.tools), tool_choice: effectiveResponseToolChoice(request), parallel_tool_calls: request.parallel_tool_calls ?? true, }; const attachments = attachmentPromptBlock(attachmentReferences); if (usesResponseToolCalling(request, history)) { const toolBoundary = attachments ? "除读取下方 API 附件临时路径所必需的 read_file 外,不要使用 Command Code 自身工具替代外部 tools;附件读取只是在消费用户输入。" : "不要使用 Command Code 自身的文件、终端、搜索、网络或其他工具替代外部 tools;只依据 input、tools 和 function_call_output 作出决定。"; return [ "下面 JSON 对象是外部客户端提交的一次 OpenAI Responses 工具调用任务,input 包含完整历史。", "你只负责决定当前这一轮应该调用外部工具还是返回最终回答。外部工具由客户端执行,你不能模拟工具结果。", toolBoundary, "严格执行 input 中的 system、developer、user 指令和当前 instructions,并结合 message、function_call、function_call_output 及其他已有 output item 理解完整历史。", "历史 reasoning item 只包含此前公开的 summary 文本记录,不是可恢复的内部推理状态;仅把 summary 当作普通历史上下文。", "只返回以下两个 JSON 对象之一,禁止 Markdown 代码围栏、前后说明和额外字段:", "最终回答:{\"type\":\"final\",\"content\":\"面向用户的完整回答\"}", "调用工具:{\"type\":\"tool_calls\",\"calls\":[{\"name\":\"工具名称\",\"arguments\":{}}]}", "调用工具时,name 必须与 tools 中的名称完全一致,arguments 必须是符合该工具 parameters JSON Schema 的对象。", "tool_choice 为 none 时必须返回 final;为 required 或指定函数时必须返回 tool_calls。parallel_tool_calls 为 false 时 calls 只能有一项。", "需要工具提供信息或执行动作时返回 tool_calls 并立即结束;已有 function_call_output 且信息足够时返回 final。工具调用轮次禁止生成面向用户的正文。", formatDirective(request.text.format), ...(attachments ? [attachments] : []), "JSON 数据开始:", JSON.stringify(envelope), ].join("\n\n"); } return [ "下面 JSON 对象是外部客户端提交的完整 Responses 文本任务和本地重建的响应链。", "按 input 中的 system、developer、user、assistant 角色与顺序理解上下文;当前 instructions 是本次请求的高优先级开发者指令。", "历史 reasoning item 只包含此前公开的 summary 文本记录,不是可恢复的内部推理状态;仅把 summary 当作普通历史上下文。", "严格执行最后一个用户任务。不要复述 JSON,不要输出角色标签,不要暴露内部思考。", formatDirective(request.text.format), ...(attachments ? [attachments] : []), "JSON 数据开始:", JSON.stringify(envelope), ].join("\n\n"); } export function buildRepairPrompt( originalPrompt: string, format: ResponseTextFormat, invalidOutput: string, validationErrors: string[], ): string { return [ "这是同一请求的唯一一次结构化输出修复步骤。", "保留原始答案的语义,只修复 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; toolRequest?: FunctionToolDecisionRequest; validation?: StructuredValidation; signal: AbortSignal; deadline: number; writer?: ResponsesSseWriter; }): Promise { const constrained = options.validation !== undefined || options.toolRequest !== undefined; const first = await executeAttempt(options, options.prompt, constrained ? undefined : options.writer); if (options.toolRequest) { return executeToolDecision({ ...options, toolRequest: options.toolRequest }, first); } if (!options.validation) { return { finalText: first.result.finalText, chunks: first.finalChunks, reasoningItems: first.reasoningItems, ...(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, reasoningItems: first.reasoningItems, ...(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, reasoningItems: second.reasoningItems, ...(combinedUsage ? { usage: combinedUsage } : {}), ...(!secondValidation.valid ? { validationErrors: secondValidation.errors } : {}), }; } async function executeToolDecision( options: { config: BridgeConfig; cliModel: string; effort: string; prompt: string; format: ResponseTextFormat; toolRequest: FunctionToolDecisionRequest; validation?: StructuredValidation; signal: AbortSignal; deadline: number; }, first: AttemptResult, ): Promise { try { return executionFromToolDecision( parseFunctionToolDecision(first.result.finalText, options.toolRequest), first, ); } catch (error) { if (!(error instanceof ToolDecisionError)) throw error; process.stderr.write(`\nResponses 工具调用决策校验失败,正在修复:${error.errors.join("; ")}\n`); const repairPrompt = buildToolDecisionRepairPrompt( options.prompt, first.result.finalText, error.errors, ); const second = await executeAttempt(options, repairPrompt); let decision; try { decision = parseFunctionToolDecision(second.result.finalText, options.toolRequest); } catch (repairError) { if (repairError instanceof ToolDecisionError) { throw new ResponseApiError( `Invalid tool decision after repair: ${repairError.errors.join("; ")}`, 502, "invalid_tool_decision", "server_error", ); } throw repairError; } const result = executionFromToolDecision(decision, second); const usage = addUsage(first.result.usage, second.result.usage); return { ...result, ...(usage ? { usage } : {}) }; } } function executionFromToolDecision( decision: ReturnType, attempt: AttemptResult, ): ExecutionResult { if (decision.type === "tool_calls") { return { finalText: "", chunks: [], reasoningItems: attempt.reasoningItems, toolCalls: decision.toolCalls, ...(attempt.result.usage ? { usage: attempt.result.usage } : {}), }; } return { finalText: decision.content, chunks: [decision.content], reasoningItems: attempt.reasoningItems, ...(attempt.result.usage ? { usage: attempt.result.usage } : {}), }; } function buildToolDecisionRepairPrompt( originalPrompt: string, invalidOutput: string, errors: string[], ): string { return [ "上一次输出不符合 Responses 外部工具调用传输协议。保持原来的决策意图,只修复 JSON 结构、工具名称或参数。", "只返回原任务要求的 final 或 tool_calls JSON 对象,禁止 Markdown 代码围栏、前后说明和额外字段。", "校验错误:", errors.join("\n"), "上一次输出:", invalidOutput, "原始任务:", originalPrompt, ].join("\n\n"); } async function executeAttempt( options: { config: BridgeConfig; cliModel: string; effort: string; signal: AbortSignal; deadline: number; }, prompt: string, writer?: ResponsesSseWriter, ): Promise { const accumulator = new FinalTurnAccumulator(); const reasoning = new ResponseReasoningAccumulator(writer); let result: CommandResult; try { result = await runCommandCode( options.config, options.cliModel, options.effort, prompt, options.signal, { timeoutMs: options.deadline - Date.now(), onEvent: (event) => { reasoning.event(event); const chunks = accumulator.event(event); if (chunks && writer) writer.addText(chunks); }, }, ); } catch (error) { reasoning.finish(); throw error; } return { result, finalChunks: accumulator.finalChunks ?? [result.finalText], reasoningItems: reasoning.finish(), }; } export 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; reasoningItems?: ResponseOutputReasoning[]; toolCalls?: FunctionToolCall[]; usage?: CommandUsage; error?: ResponseError; incompleteReason?: string; }): LocalResponse { const finalText = options.finalText ?? ""; const terminal = options.status !== "in_progress"; const outputStatus = options.status === "completed" ? "completed" : "incomplete"; const generatedOutput: ResponseOutputItem[] = options.toolCalls ? options.toolCalls.map((call) => ({ id: newFunctionCallItemId(), type: "function_call", call_id: call.callId, name: call.name, arguments: call.arguments, status: outputStatus, })) : options.finalText !== undefined ? [{ id: options.messageId, type: "message", role: "assistant", status: outputStatus, content: [{ type: "output_text", text: finalText, annotations: [] }], }] : []; const output: ResponseOutputItem[] = [ ...(options.reasoningItems ?? []), ...generatedOutput, ]; 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, 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 }, tools: normalizeResponseTools(options.request.tools), tool_choice: effectiveResponseToolChoice(options.request), parallel_tool_calls: options.request.parallel_tool_calls ?? true, 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 AttachmentInputError) { return reply.status(400).send(openAIError( error.message, error.code, "invalid_request_error", error.param, )); } 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( "Command Code request queue is full", "queue_full", "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 validateResponseToolRequest(request: ResponseRequest): void { const errors = validateFunctionToolSchemas(responseFunctionTools(request)); if (errors.length > 0) { throw new ResponseApiError( errors.join("; "), 400, "invalid_tool_schema", "invalid_request_error", "tools", ); } } function validateResponseFunctionHistory( request: ResponseRequest, chain: StoredResponse[], currentInput: ResponseInputItem[], ): void { const tools = new Map(responseFunctionTools(request).map((tool) => [tool.name, tool])); const callIds = new Set(); const outputCallIds = new Set(); const items: Array<{ item: ResponseInputItem | ResponseOutputItem; param: string }> = []; for (const stored of chain) { for (const item of stored.input_items) items.push({ item, param: "previous_response_id" }); for (const item of stored.response.output) items.push({ item, param: "previous_response_id" }); } for (const [index, item] of currentInput.entries()) items.push({ item, param: `input.${index}` }); for (const { item, param } of items) { if (item.type === "function_call") { if (callIds.has(item.call_id)) { throw new ResponseApiError( `Duplicate function call_id '${item.call_id}'`, 400, "invalid_function_call", "invalid_request_error", `${param}.call_id`, ); } callIds.add(item.call_id); const tool = tools.get(item.name); if (!tool) { throw new ResponseApiError( `Function call references unknown tool '${item.name}'`, 400, "invalid_tool_name", "invalid_request_error", `${param}.name`, ); } let args: Record; try { args = parseFunctionToolArguments(item.arguments, `${param}.arguments`); } catch (error) { if (error instanceof ToolDecisionError) { throw new ResponseApiError( error.errors.join("; "), 400, "invalid_tool_arguments", "invalid_request_error", `${param}.arguments`, ); } throw error; } const errors = validateFunctionToolArguments(tool, item.name, args); if (errors.length > 0) { throw new ResponseApiError( errors.join("; "), 400, "invalid_tool_arguments", "invalid_request_error", `${param}.arguments`, ); } continue; } if (item.type === "function_call_output") { if (!callIds.has(item.call_id)) { throw new ResponseApiError( `No preceding function_call found for call_id '${item.call_id}'`, 400, "invalid_function_call_output", "invalid_request_error", `${param}.call_id`, ); } if (outputCallIds.has(item.call_id)) { throw new ResponseApiError( `Duplicate function_call_output for call_id '${item.call_id}'`, 400, "invalid_function_call_output", "invalid_request_error", `${param}.call_id`, ); } outputCallIds.add(item.call_id); } } } function responseFunctionTools(request: ResponseRequest): FunctionToolDefinition[] { return normalizeResponseTools(request.tools).map((tool) => ({ name: tool.name, ...(tool.description !== undefined ? { description: tool.description } : {}), parameters: tool.parameters, strict: tool.strict, })); } function responseFunctionToolDecisionRequest( request: ResponseRequest, ): FunctionToolDecisionRequest | undefined { if (!request.tools) return undefined; const toolChoice = effectiveResponseToolChoice(request); return { tools: responseFunctionTools(request), toolChoice: typeof toolChoice === "string" ? toolChoice : { name: toolChoice.name }, parallelToolCalls: request.parallel_tool_calls ?? true, }; } function normalizeResponseTools(tools: ResponseRequest["tools"]): ResponseFunctionTool[] { return (tools ?? []).map((tool) => ({ type: "function", name: tool.name, ...(tool.description !== undefined ? { description: tool.description } : {}), parameters: tool.parameters ?? { type: "object", properties: {} }, strict: tool.strict ?? null, })); } function effectiveResponseToolChoice(request: ResponseRequest): ResponseToolChoice { return request.tool_choice ?? (request.tools ? "auto" : "none"); } function usesResponseToolCalling( request: ResponseRequest, history: Array, ): boolean { return request.tools !== undefined || history.some((item) => ( item.type === "function_call" || item.type === "function_call_output" )); } 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, }; } export 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 { return typeof value === "object" && value !== null && !Array.isArray(value); } function newResponseId(): string { return `resp_local_${randomHexId()}`; } function newMessageId(): string { return `msg_local_${randomHexId()}`; } function newFunctionCallItemId(): string { return `fc_local_${randomHexId()}`; } function newFunctionCallOutputItemId(): string { return `fco_local_${randomHexId()}`; } function newReasoningItemId(): string { return `rs_local_${randomHexId()}`; } function randomHexId(): string { return randomUUID().replaceAll("-", ""); } function formatUnknownError(error: unknown): string { return error instanceof Error ? (error.stack ?? error.message) : String(error); }