From 7dd16e370fec3559b6afd41e2c747efa9cb1cdda Mon Sep 17 00:00:00 2001 From: Sirius Date: Wed, 5 Aug 2026 17:11:55 +0800 Subject: [PATCH] =?UTF-8?q?chat=E4=B9=9F=E6=83=B3=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +++ src/openai.ts | 20 ++++++++++++++++++++ src/server.ts | 10 ++++++++++ 3 files changed, 33 insertions(+) diff --git a/README.md b/README.md index 8ed42a5..9ab4ec9 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,8 @@ Bearer Token 会被忽略。所有生成接口只接受文本。图片、音频 支持字符串 `content`,也支持由 `{ "type": "text", "text": "..." }` 组成的数组。`stream` 省略或设为 `false` 时返回普通 JSON。`stream: true` 会立即建立 SSE 连接并发送 assistant role 块,随后发送最终回答轮次的原始文本 delta、`finish_reason: "stop"`、可选 usage 块和 `[DONE]`。传入 `stream_options.include_usage: true` 时,结束前返回 usage 块。 +为兼容 Obsidian Copilot 和 LangChain OpenAI-format 客户端,Chat 接口还接受 `temperature`、`max_tokens`、`max_completion_tokens`、`top_p`、`frequency_penalty`、`presence_penalty` 和 `n: 1`。这些采样和输出限制参数会按 OpenAI 取值范围严格校验。Command Code CLI 1.12.0 没有对应的 headless 参数,因此 Bridge 不会把它们伪装成已生效:服务终端会输出警告,HTTP 响应带 `X-Command-Code-Ignored-Parameters`。其他未实现字段仍返回 `400 unsupported_parameter`。 + Chat Completions 和 Responses 共用 Command Code NDJSON 实时事件执行层。服务终端会在事件到达时立即显示状态、文本和工具调用。由于 Command Code 只有在 `turn_end` 才给出 `hadToolCalls`,Bridge 会按 turn 缓冲客户端文本,丢弃工具轮次,在确认 `hadToolCalls: false` 后按原 delta 边界写入 Chat SSE。连接会立即建立,最终文本不会混入工具轮次内容,文本首包仍受最终 turn 完成时间约束。 ### Responses @@ -295,6 +297,7 @@ command-code \ - OpenAI Node.js SDK 的 Chat `stream: true` 检查中,约 31 ms 收到 assistant role 块;最终文本按 10 个原始 delta 返回,随后收到 `stop`、真实 usage 和 `[DONE]`。 - 强制 `read_file` 的 Chat 两 turn 流只向客户端输出第二个无工具 turn 的 6 个文本 delta;终端实时显示工具状态、结果和最终文本,中间轮次没有污染客户端内容。 - Chat 流式连接收到首块后断开会取消 CLI 并恢复空闲;流占用期间第二个生成请求返回 429 `busy`,取消后的普通 Chat 请求正常完成。 +- Obsidian Copilot/LangChain 风格的 `temperature: 0.1`、`max_tokens: 1000`、流式 usage 请求返回 HTTP 200 和正确 SSE;响应头列出两个未映射参数。可选 `top_p`、frequency/presence penalty、`max_completion_tokens` 和 `n: 1` 通过严格校验,`n: 2` 及未知 `tools` 仍返回 400。 - 并发检查中第二个请求返回 HTTP 429 和 `code: busy`,没有启动第二个 CLI。 - 超过 20 MiB 的请求体返回 HTTP 413,服务保持可用。 - 客户端 1 秒超时断开后,当前子进程组被清理,`busy` 恢复为 false,没有发现残留 headless CLI。 diff --git a/src/openai.ts b/src/openai.ts index cbe3116..a8e2124 100644 --- a/src/openai.ts +++ b/src/openai.ts @@ -18,6 +18,13 @@ export const chatCompletionRequestSchema = z.object({ stream_options: z.object({ include_usage: z.boolean().optional().default(false), }).strict().optional(), + temperature: z.number().min(0).max(2).nullable().optional(), + max_tokens: z.number().int().positive().nullable().optional(), + max_completion_tokens: z.number().int().positive().nullable().optional(), + top_p: z.number().min(0).max(1).nullable().optional(), + frequency_penalty: z.number().min(-2).max(2).nullable().optional(), + presence_penalty: z.number().min(-2).max(2).nullable().optional(), + n: z.literal(1).nullable().optional(), }).strict(); export type ChatCompletionRequest = z.infer; @@ -27,6 +34,19 @@ export interface CommandUsage { outputTokens?: number; } +const ignoredCompatibilityParameters = [ + "temperature", + "max_tokens", + "max_completion_tokens", + "top_p", + "frequency_penalty", + "presence_penalty", +] as const; + +export function ignoredChatCompatibilityParameters(request: ChatCompletionRequest): string[] { + return ignoredCompatibilityParameters.filter((key) => request[key] !== undefined); +} + export function normalizeMessages(request: ChatCompletionRequest) { return request.messages.map((message) => ({ role: message.role, diff --git a/src/server.ts b/src/server.ts index 44fb22c..6cbd70e 100644 --- a/src/server.ts +++ b/src/server.ts @@ -12,6 +12,7 @@ import { buildCommandPrompt, chatCompletionRequestSchema, completionResponse, + ignoredChatCompatibilityParameters, openAIError, } from "./openai.js"; import { RequestCoordinator } from "./request-coordinator.js"; @@ -123,6 +124,15 @@ export async function createServer(config: BridgeConfig): Promise )); } + 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 abortController = coordinator.begin(); if (!abortController) { return reply.status(429).send(openAIError(