支持responses

This commit is contained in:
Sirius
2026-08-05 16:43:18 +08:00
parent 15176a3238
commit 7ec6914872
15 changed files with 1560 additions and 101 deletions
+57 -27
View File
@@ -1,5 +1,6 @@
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,
@@ -11,13 +12,10 @@ import {
buildCommandPrompt,
chatCompletionRequestSchema,
completionResponse,
completionStreamResponse,
openAIError,
} from "./openai.js";
interface ActiveRequest {
abortController: AbortController;
}
import { RequestCoordinator } from "./request-coordinator.js";
import { registerResponseRoutes } from "./responses.js";
export interface BridgeServer {
app: FastifyInstance;
@@ -32,7 +30,7 @@ export async function createServer(config: BridgeConfig): Promise<BridgeServer>
bodyLimit: config.max_request_bytes,
requestTimeout: 0,
});
let active: ActiveRequest | undefined;
const coordinator = new RequestCoordinator();
app.setErrorHandler((error, _request, reply) => {
const fastifyError = error as Error & { code?: string; statusCode?: number };
@@ -56,7 +54,7 @@ export async function createServer(config: BridgeConfig): Promise<BridgeServer>
app.get("/health", async () => ({
status: installation.installed && installation.authenticated ? "ok" : "degraded",
command_code: installation,
busy: active !== undefined,
busy: coordinator.busy,
}));
app.get("/v1/models", async () => ({
@@ -70,7 +68,7 @@ export async function createServer(config: BridgeConfig): Promise<BridgeServer>
}));
app.post("/v1/chat/completions", async (request, reply) => {
if (active) {
if (coordinator.busy) {
return reply.status(429).send(openAIError(
"Another Command Code request is already running",
"busy",
@@ -98,6 +96,19 @@ export async function createServer(config: BridgeConfig): Promise<BridgeServer>
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";
@@ -112,8 +123,14 @@ export async function createServer(config: BridgeConfig): Promise<BridgeServer>
));
}
const abortController = new AbortController();
active = { abortController };
const abortController = coordinator.begin();
if (!abortController) {
return reply.status(429).send(openAIError(
"Another Command Code request is already running",
"busy",
"server_error",
));
}
let responseCompleted = false;
const cancelOnDisconnect = () => {
@@ -122,31 +139,38 @@ export async function createServer(config: BridgeConfig): Promise<BridgeServer>
request.raw.once("aborted", cancelOnDisconnect);
reply.raw.once("close", cancelOnDisconnect);
let writer: ChatCompletionSseWriter | undefined;
try {
if (parsed.stream) writer = startChatCompletionSse(reply, parsed.model);
const result = await runCommandCode(
config,
model.cli_model,
model.effort,
buildCommandPrompt(parsed),
abortController.signal,
writer ? { onEvent: (event) => writer?.commandEvent(event) } : {},
);
responseCompleted = true;
if (parsed.stream) {
return reply
.type("text/event-stream; charset=utf-8")
.header("Cache-Control", "no-cache")
.header("Connection", "keep-alive")
.send(completionStreamResponse(
parsed.model,
result.finalText,
result.usage,
parsed.stream_options?.include_usage,
));
if (writer) {
writer.finish(
result.finalText,
result.usage,
parsed.stream_options?.include_usage,
);
return reply;
}
return reply.send(completionResponse(parsed.model, result.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",
@@ -164,6 +188,14 @@ export async function createServer(config: BridgeConfig): Promise<BridgeServer>
: 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,
@@ -172,17 +204,15 @@ export async function createServer(config: BridgeConfig): Promise<BridgeServer>
} finally {
request.raw.off("aborted", cancelOnDisconnect);
reply.raw.off("close", cancelOnDisconnect);
active = undefined;
coordinator.finish(abortController);
}
});
await registerResponseRoutes(app, { config, installation, coordinator });
return {
app,
installation,
abortActive: () => {
if (!active) return false;
active.abortController.abort("bridge interrupted");
return true;
},
abortActive: () => coordinator.abortActive(),
};
}