支持responses
This commit is contained in:
+57
-27
@@ -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(),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user