import Fastify, { type FastifyInstance } from "fastify"; import { ZodError } from "zod"; import { startChatCompletionSse, type ChatCompletionSseWriter } from "./chat-stream.js"; import type { BridgeConfig } from "./config.js"; import { CommandCodeError, inspectInstallation, runCommandCode, type CommandResult, type InstallationStatus, } from "./command-code.js"; import { buildCommandPrompt, buildToolDecisionRepairPrompt, buildToolStructuredOutputRepairPrompt, chatResponseTextFormat, chatCompletionRequestSchema, completionResponse, ignoredChatCompatibilityParameters, openAIError, toolCallsCompletionResponse, usesChatStructuredOutput, usesToolCalling, type ChatCompletionRequest, } from "./openai.js"; import { RequestCoordinator } from "./request-coordinator.js"; import { addUsage, buildRepairPrompt, createStructuredValidation, registerResponseRoutes, type ResponseTextFormat, type StructuredValidation, } from "./responses.js"; import { parseToolDecision, ToolDecisionError, validateToolSchemas, type ToolDecision, } from "./tool-calling.js"; export interface BridgeServer { app: FastifyInstance; installation: InstallationStatus; abortActive: () => number; } export async function createServer(config: BridgeConfig): Promise { const installation = await inspectInstallation(config.command_code_executable); const app = Fastify({ logger: false, bodyLimit: config.max_request_bytes, requestTimeout: 0, }); const coordinator = new RequestCoordinator( config.max_concurrent_requests, config.max_queue_size, ); app.addHook("onRequest", async (request, reply) => { const requestedHeaders = request.headers["access-control-request-headers"]; reply.raw.setHeader("Access-Control-Allow-Origin", "*"); reply.raw.setHeader( "Access-Control-Allow-Headers", requestedHeaders || "Content-Type, Authorization, dangerously-allow-browser", ); reply.raw.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS"); reply.raw.setHeader("Access-Control-Max-Age", "600"); reply.raw.setHeader("Access-Control-Expose-Headers", "X-Command-Code-Ignored-Parameters"); reply.raw.setHeader( "Vary", "Origin, Access-Control-Request-Method, Access-Control-Request-Headers, Access-Control-Request-Private-Network", ); if (request.headers["access-control-request-private-network"] === "true") { reply.raw.setHeader("Access-Control-Allow-Private-Network", "true"); } }); app.options("/*", async (_request, reply) => reply.status(204).send()); app.setErrorHandler((error, _request, reply) => { const fastifyError = error as Error & { code?: string; statusCode?: number }; if (fastifyError.code === "FST_ERR_CTP_BODY_TOO_LARGE") { reply.status(413).send(openAIError( `Request body exceeds max_request_bytes (${config.max_request_bytes})`, "request_too_large", )); return; } const errorText = error instanceof Error ? (error.stack ?? error.message) : String(error); process.stderr.write(`${errorText}\n`); reply.status(fastifyError.statusCode ?? 500).send(openAIError( "Internal bridge error", "bridge_error", "server_error", )); }); app.get("/health", async () => ({ status: installation.installed && installation.authenticated ? "ok" : "degraded", command_code: installation, busy: coordinator.busy, active_requests: coordinator.activeRequests, max_concurrent_requests: config.max_concurrent_requests, queue_length: coordinator.queueLength, queue_capacity: config.max_queue_size, })); app.get("/v1/models", async () => ({ object: "list", data: Object.keys(config.models).map((id) => ({ id, object: "model", created: 0, owned_by: "command-code-local", })), })); app.post("/v1/chat/completions", 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: ChatCompletionRequest; try { parsed = chatCompletionRequestSchema.parse(request.body); } catch (error) { const unknown = error instanceof ZodError ? error.issues.find((issue) => issue.code === "unrecognized_keys") : undefined; 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 instanceof ZodError ? error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ") : "Invalid request body"; return reply.status(400).send(openAIError(message, "invalid_request")); } const responseFormat: ResponseTextFormat = chatResponseTextFormat(parsed); let structuredValidation: StructuredValidation | undefined; try { structuredValidation = createStructuredValidation(responseFormat); } catch (error) { const message = error instanceof Error ? error.message : "Invalid JSON Schema"; return reply.status(400).send(openAIError( message, "invalid_json_schema", "invalid_request_error", "response_format.json_schema.schema", )); } const model = config.models[parsed.model]; if (!model) { return reply.status(404).send(openAIError( `Model '${parsed.model}' is not configured`, "model_not_found", )); } const toolSchemaErrors = validateToolSchemas(parsed); if (toolSchemaErrors.length > 0) { return reply.status(400).send(openAIError( toolSchemaErrors.join("; "), "invalid_tool_schema", "invalid_request_error", "tools", )); } const ignoredParameters = ignoredChatCompatibilityParameters(parsed); if (ignoredParameters.length > 0) { const parameterList = ignoredParameters.join(","); reply.raw.setHeader("X-Command-Code-Ignored-Parameters", parameterList); process.stderr.write( `警告:Chat Completions 参数 ${parameterList} 已为客户端兼容而接受;Command Code CLI 不支持映射,实际生成不会应用这些参数。\n`, ); } const lease = coordinator.begin(); if (!lease) { return reply.status(429).send(openAIError( "Command Code request queue is full", "queue_full", "server_error", )); } const { controller: abortController } = lease; let responseCompleted = false; let acquired = false; const cancelOnDisconnect = () => { if (!responseCompleted) coordinator.cancel(abortController, "client disconnected"); }; request.raw.once("aborted", cancelOnDisconnect); reply.raw.once("close", cancelOnDisconnect); let writer: ChatCompletionSseWriter | undefined; try { if (parsed.stream) { writer = startChatCompletionSse( reply, parsed.model, config.stream_thinking && !usesChatStructuredOutput(parsed), ); } if (request.raw.aborted || reply.raw.destroyed) cancelOnDisconnect(); acquired = await lease.ready; if (!acquired) { responseCompleted = true; return reply; } const toolMode = usesToolCalling(parsed); const execution = await executeChatTurn({ config, cliModel: model.cli_model, effort: model.effort, request: parsed, signal: abortController.signal, deadline: Date.now() + config.timeout_seconds * 1000, toolMode, format: responseFormat, ...(structuredValidation ? { validation: structuredValidation } : {}), ...(writer ? { writer } : {}), }); const { result, decision } = execution; responseCompleted = true; if (decision?.type === "tool_calls") { if (writer) { writer.finishToolCalls( decision.toolCalls, result.usage, parsed.stream_options?.include_usage, ); return reply; } return reply.send(toolCallsCompletionResponse( parsed.model, decision.toolCalls, result.usage, )); } const finalText = decision?.type === "final" ? decision.content : result.finalText; if (writer) { writer.finish( finalText, result.usage, parsed.stream_options?.include_usage, ); return reply; } return reply.send(completionResponse(parsed.model, finalText, result.usage)); } catch (error) { responseCompleted = true; if (abortController.signal.aborted) { if (writer) { writer.error(openAIError( "Command Code request cancelled", "cancelled", "server_error", )); return reply; } if (!reply.raw.destroyed) { return reply.status(499).send(openAIError( "Command Code request cancelled", "cancelled", "server_error", )); } return; } if (error instanceof ToolDecisionError) { process.stderr.write(`\n工具调用决策连续两次校验失败:${error.errors.join("; ")}\n`); if (writer) { writer.error(openAIError( "Command Code returned an invalid tool decision", "invalid_tool_decision", "server_error", )); return reply; } return reply.status(502).send(openAIError( "Command Code returned an invalid tool decision", "invalid_tool_decision", "server_error", )); } if (error instanceof ChatStructuredOutputError) { process.stderr.write(`\nChat 结构化输出连续两次校验失败:${error.errors.join("; ")}\n`); const payload = openAIError( "Command Code returned invalid structured output after one repair attempt", "invalid_structured_output", "server_error", "response_format", ); if (writer) { writer.error(payload); return reply; } return reply.status(502).send(payload); } const code = error instanceof CommandCodeError ? error.exitCode : undefined; const status = code === 5 ? 429 : code === 10 ? 402 : 502; const errorCode = code === 5 ? "rate_limit_exceeded" : code === 10 ? "insufficient_credits" : "command_code_error"; if (writer) { writer.error(openAIError( "Command Code request failed", errorCode, "server_error", )); return reply; } return reply.status(status).send(openAIError( "Command Code request failed", errorCode, "server_error", )); } finally { request.raw.off("aborted", cancelOnDisconnect); reply.raw.off("close", cancelOnDisconnect); if (!acquired) coordinator.cancel(abortController, "request ended before execution"); coordinator.finish(abortController); } }); await registerResponseRoutes(app, { config, installation, coordinator }); return { app, installation, abortActive: () => coordinator.abortActive(), }; } interface ChatExecution { result: CommandResult; decision?: ToolDecision; } interface ChatTurnOptions { config: BridgeConfig; cliModel: string; effort: string; request: ChatCompletionRequest; signal: AbortSignal; deadline: number; toolMode: boolean; format: ResponseTextFormat; validation?: StructuredValidation; writer?: ChatCompletionSseWriter; } class ChatStructuredOutputError extends Error { constructor(readonly errors: string[]) { super(errors.join("; ")); this.name = "ChatStructuredOutputError"; } } async function executeChatTurn(options: ChatTurnOptions): Promise { const originalPrompt = buildCommandPrompt(options.request); const constrained = options.toolMode || options.validation !== undefined; const first = await runCommandCode( options.config, options.cliModel, options.effort, originalPrompt, options.signal, { timeoutMs: options.deadline - Date.now(), ...(!constrained && options.writer ? { onEvent: (event: Record) => options.writer?.commandEvent(event) } : {}), }, ); if (!options.toolMode) { if (!options.validation) return { result: first }; const firstValidation = options.validation.validate(first.finalText); if (firstValidation.valid) return { result: first }; process.stderr.write(`\nChat 结构化输出校验失败,正在修复:${firstValidation.errors.join("; ")}\n`); const second = await runCommandCode( options.config, options.cliModel, options.effort, buildRepairPrompt( originalPrompt, options.format, first.finalText, firstValidation.errors, ), options.signal, { timeoutMs: options.deadline - Date.now() }, ); const secondValidation = options.validation.validate(second.finalText); if (!secondValidation.valid) throw new ChatStructuredOutputError(secondValidation.errors); return { result: combineCommandResults(first, second) }; } let execution: ChatExecution; try { execution = { result: first, decision: parseToolDecision(first.finalText, options.request), }; } catch (error) { if (!(error instanceof ToolDecisionError)) throw error; process.stderr.write(`\n工具调用决策校验失败,正在修复:${error.errors.join("; ")}\n`); const repairPrompt = buildToolDecisionRepairPrompt( originalPrompt, first.finalText, error.errors, ); const second = await runCommandCode( options.config, options.cliModel, options.effort, repairPrompt, options.signal, { timeoutMs: options.deadline - Date.now() }, ); execution = { result: combineCommandResults(first, second), decision: parseToolDecision(second.finalText, options.request), }; } if (execution.decision?.type !== "final" || !options.validation) return execution; const firstValidation = options.validation.validate(execution.decision.content); if (firstValidation.valid) return execution; process.stderr.write(`\nChat 最终工具正文结构化校验失败,正在修复:${firstValidation.errors.join("; ")}\n`); const repair = await runCommandCode( options.config, options.cliModel, options.effort, buildToolStructuredOutputRepairPrompt( originalPrompt, options.request, execution.decision.content, firstValidation.errors, ), options.signal, { timeoutMs: options.deadline - Date.now() }, ); const result = combineCommandResults(execution.result, repair); let decision: ToolDecision; try { decision = parseToolDecision(repair.finalText, options.request); } catch (error) { if (error instanceof ToolDecisionError) { throw new ChatStructuredOutputError([ "Structured output repair did not return a valid final decision", ...error.errors, ]); } throw error; } if (decision.type !== "final") { throw new ChatStructuredOutputError(["Structured output repair returned tool_calls instead of final"]); } const repairedValidation = options.validation.validate(decision.content); if (!repairedValidation.valid) throw new ChatStructuredOutputError(repairedValidation.errors); return { result, decision }; } function combineCommandResults(left: CommandResult, right: CommandResult): CommandResult { const usage = addUsage(left.usage, right.usage); return { finalText: right.finalText, durationMs: left.durationMs + right.durationMs, ...(usage ? { usage } : {}), }; }