# Command Code OpenAI Bridge 这是一个只绑定本机地址的 OpenAI 兼容转发服务。它实现 Chat Completions 与 Responses API 的文本对话、受限图片/纯文本文件输入和外部 function calling。每个生成请求启动一次独立的 Command Code headless agent,将完整消息历史通过 stdin 发送给 CLI,把附件安全落为请求专用临时文件并通过真实 `read_file` 路径交给 Command Code,再把结构化事件和最终 `finalText` 转换成 OpenAI 风格的 JSON 或 SSE。 默认地址:`http://127.0.0.1:18000/v1` ## 当前机器上的调查结论 - Node.js:v22.22.2,符合 Command Code 的 Node.js 22+ 要求。 - 已安装 Command Code:v1.18.0;可执行文件是全局 npm 包的 `dist/index.mjs`。 - npm 包没有公开、稳定的可嵌入输入 API。官方 headless 调用仍是 `command-code -p --output-format json`。 - `--help` 没有 `--image`、`--attachment`、文件 URL、二进制 stdin 或 JSON stdin envelope。`-p` 的参数或 piped stdin 都只是查询文本。 - 官方文档同时明确支持在提示中用 `@path/to/file` 引用工作区文件;`read_file` 会把图片真正交给原生视觉模型或 VISION 工具。普通二进制只返回 MIME 提示,PDF 当前只提示使用 `pdftotext`,并不是 PDF 内容输入。 - 真实 headless 检查使用一张四象限 PNG。`moonshotai/kimi-k2.7-code` 与 `gpt-5.6-luna` 都在 NDJSON 中实际调用 `read_file`,并正确返回 `Red, Green, Blue, Yellow`。这证明本地图片路径在 `-p --output-format json` 下会被底层模型理解,不只是把文件名写进 prompt。 - 因此 Bridge 只把经过 MIME、魔数、大小和 UTF-8 校验的数据落为工作区内临时文件,再传入 `@` 路径。它不把 base64 塞进 prompt,也不做 OCR/PDF 摘要降级。 - 远程 HTTP(S)、`file://`、任意本地路径和 `file_id` 均不接受。Bridge 完全不为附件发起网络请求,从策略上消除 SSRF、重定向和 URL token 泄漏。 - JSON 输出是 NDJSON:运行中输出 `AgentEvent`,正常情况下最后输出唯一的 `result` 行;最终回答位于 `finalText`,usage 和耗时也在该行。 - `--no-session` 让每次请求只使用内存会话,不写入或恢复跨请求 Command Code session。 - 官方 headless 模式明确不提供键盘、问题回答或权限批准等交互。原始 TTY/TUI 与可靠的独立 `finalText` 目前不能同时获得。 官方资料: - [Quickstart](https://commandcode.ai/docs/quickstart) - [CLI Reference](https://commandcode.ai/docs/reference/cli) - [Headless Mode](https://commandcode.ai/docs/headless) - [Common Workflows:文件和图片路径](https://commandcode.ai/docs/workflows) - [Vision](https://commandcode.ai/docs/vision) - [The Read Tool](https://commandcode.ai/docs/harness-engineering/read-tool) - [Permissions](https://commandcode.ai/docs/core-concepts/permissions) ## 为什么使用普通子进程与结构化事件 本项目使用官方 headless 子进程和 NDJSON,不使用 PTY,也不解析 TUI 文本。 原因是 v1.18.0 没有公开嵌入 API,交互式 TUI 没有独立的结构化最终结果通道。headless 的最后一行提供稳定 `finalText`,能保证工具参数、工具结果、状态、stderr、思考事件和 ANSI 内容不会混入 API 回复。 终端会显示:请求开始、模型、turn 状态、工具事件、工具参数、工具结果、stderr、最终回答和耗时。每个 turn 结束时还会显示本轮和服务启动以来的累计 input/output/total token;服务退出时再输出一次累计,重启后从零开始。终端不会显示原始 Ink TUI、动画、键盘快捷键、人工权限批准、人工问题回答和详细内部思考文本。 默认 `dangerously_skip_permissions: true`,因此实际传入 `--yolo`,让文件写入和命令工具能继续执行。它会绕过所有权限确认,只应对可信工作目录使用。若要只读,把 `dangerously_skip_permissions` 改为 `false`,同时把 `permission_mode` 改成 `plan`;`read_file` 在只读模式仍可读取 Bridge 创建的附件。 ## 项目结构 ```text command-code-openai-bridge/ ├── config.yaml ├── config.example.yaml ├── package.json ├── tsconfig.json ├── src/ │ ├── command-code.ts │ ├── config.ts │ ├── index.ts │ ├── openai.ts │ ├── request-coordinator.ts │ ├── responses.ts │ ├── renderer.ts │ ├── server.ts │ └── tool-calling.ts ├── scripts/ │ ├── install.sh │ └── start.sh └── examples/ ├── node_client.mjs ├── python_client.py └── opencode.json ``` ## 安装 ```bash cd /Users/zen/Documents/Codex/2026-08-04/files-mentioned-by-the-user-command/outputs/command-code-openai-bridge ./scripts/install.sh ``` 脚本会检查 Node.js 版本、按需安装 Command Code、安装项目依赖并构建。 如未登录: ```bash command-code login command-code status --json ``` `command-code login` 会打开浏览器,也可以粘贴从 Command Code Studio 创建的 API key。 ## 配置 编辑 `config.yaml`: ```yaml host: 127.0.0.1 port: 18000 command_code_executable: command-code command_code_working_directory: . timeout_seconds: 1800 max_request_bytes: 20971520 max_concurrent_requests: 1 max_queue_size: 8 max_turns: 100 stream_thinking: false response_store_directory: .command-code-openai-bridge/responses permission_mode: auto-accept dangerously_skip_permissions: true models: command-default: cli_model: deepseek/deepseek-v4-flash effort: max supports_image_input: false command-vision: cli_model: gpt-5.6-luna effort: max supports_image_input: true ``` `command_code_working_directory` 决定 Command Code 能看到和操作的项目目录。`response_store_directory` 保存 `store: true` 的 Response;两个相对路径都以配置文件所在目录为基准。默认存储目录是隐藏目录 `.command-code-openai-bridge/responses`。`max_concurrent_requests` 是同时运行的 Command Code 请求上限,范围 `1..64`,默认 `1`。`max_queue_size` 是等待执行的生成请求上限,不包含正在运行的请求;设为 `0` 可恢复并发槽满时立即返回 429 的行为。`stream_thinking` 控制 Chat Completions 流是否把 Command Code 思考事件包装成 `` 块;默认关闭。`models..effort` 会传给 Command Code 的 `--effort`,Responses 请求中的 `reasoning.effort` 优先。可用 effort 由对应底层模型决定。 `models..supports_image_input` 默认 `false`。只有对该实际 CLI 模型完成图片路径检查后才能设为 `true`;图片请求选中未声明的模型时返回 400,不会假装模型看到了图片。`GET /v1/models` 同时返回每个映射的 `capabilities.input`。服务强制只监听 `127.0.0.1`,客户端只能选择配置中的模型名,不能注入额外 CLI 参数。 ### Obsidian Copilot 流式输出 在 Copilot 中添加自定义模型时使用: - Provider:`3rd party (openai-format)` - Base URL:`http://127.0.0.1:18000/v1` - API Key:任意非空文本 - CORS:关闭 - Streaming:开启 - Reasoning capability:开启 Bridge 会直接响应浏览器 CORS 预检,包括 Copilot 和 OpenAI SDK 发送的自定义请求头及本机 Private Network Access。关闭 Copilot 的 CORS 绕过后,请求使用原生 `fetch`,可以读取 Bridge 返回的 SSE 流。将 `config.yaml` 中的 `stream_thinking` 设为 `true` 后,Bridge 会把实时思考事件作为 `` 块发送,Copilot 将其显示为可折叠思考内容。思考中的 `Sources` 标记会被等效转义,避免 Copilot 把内部草稿误判为最终引用区并丢弃后续正文。 ### OpenCode 自定义 provider 已在本机验证 **OpenCode 1.18.15** 通过 `@ai-sdk/openai-compatible` 自定义 provider 连接 Bridge,并完成真实工具循环(`glob` 单工具、并行 `glob`、流式 `tool_calls` 与工具结果后的文本轮次)。 1. 启动 Bridge(默认 `http://127.0.0.1:18000/v1`)。 2. 将 `examples/opencode.json` 复制到项目根目录,或通过环境变量指向它: ```bash export OPENCODE_CONFIG="/path/to/command-code-openai-bridge/examples/opencode.json" ``` 3. OpenCode 仍需配置任意非空 API key(Bridge 忽略 Authorization,但客户端要求填写)。示例配置使用 `options.apiKey: "local-bridge"`。 4. 选择模型 `command-bridge/command-default`(与 `config.yaml` 中的 `models.command-default` 映射一致)。 OpenCode 1.18.15 实际发送的 Chat 请求包含 `stream: true`、`stream_options.include_usage: true`、`max_tokens`(默认约 32000)、`store: false`、`tools`(约 24 个内置工具)、`tool_choice: auto`。Bridge 接受 `store: false` 并保持 Chat 无状态;采样和输出限制参数虽然在协议层接受,但未映射参数会通过 `X-Command-Code-Ignored-Parameters` 标出。工具 `parameters` 中的 JSON Schema `$schema`(draft 2020-12)会在校验前剥离元数据字段,避免 AJV 拒绝 OpenCode 工具定义。 OpenCode 还会在会话标题生成、子 agent 和后台任务等场景发起独立请求。这些请求与主 agent 请求共用同一 provider;将 `max_concurrent_requests` 设为大于 `1` 可让它们与多个会话并行执行。 示例把 `command-default` 明确声明为仅文本,把已经通过真实 CLI 检查且在 `config.yaml` 中启用的 `command-luna` 声明为 `modalities.input: ["text", "image"]`。OpenCode 只有看到该字段才会把图片转换成 Chat `image_url`;不要给 `supports_image_input: false` 的映射添加 `image`。OpenCode 会把用户图片规范化为 data URL,Bridge 不接受远程图片 URL。 已知限制:Bridge 不实现 OpenCode hosted tools、音频、视频、PDF attachment、Computer Use 或托管 File Search/Code Interpreter/Hosted Shell。OpenCode 自定义 provider 走 `/v1/chat/completions`;Responses 路径由 OpenAI SDK 等客户端使用。`temperature`、`max_tokens` 等采样参数不会传给 Command Code CLI。 ## 启动、停止与重启 ```bash ./scripts/start.sh ``` `scripts/start.sh` 只运行已经生成的 `dist/index.js`,不会自动编译 TypeScript。修改 `src/` 中的源码后,必须先重新编译,再重启服务,新代码才会生效: ```bash npm run build ./scripts/start.sh ``` 首次执行 `scripts/install.sh` 时会自动安装依赖并完成一次编译。 空闲时按 Ctrl+C 停止。请求运行时第一次 Ctrl+C 取消所有活动 Command Code 子进程组并保留服务;请求结束后再按 Ctrl+C 停止服务。重启就是再次执行启动脚本。 运行中的客户端断开连接会先给整个 Command Code 子进程组发送 SIGINT,随后按需升级为 SIGTERM 和 SIGKILL;排队中的客户端断开连接只会从等待队列移除。总超时由 `timeout_seconds` 控制。 ## API - `GET /health` - `GET /v1/models` - `POST /v1/chat/completions` - `POST /v1/responses` - `GET /v1/responses/:response_id` - `DELETE /v1/responses/:response_id` - `GET /v1/responses/:response_id/input_items` `GET /health` 额外返回 `active_requests`、`max_concurrent_requests`、`queue_length` 和 `queue_capacity`;`busy` 作为兼容字段保留,等价于 `active_requests > 0`。 Bearer Token 会被忽略。服务不会静默忽略会改变行为的参数。 ### 输入能力表 | 输入 | Chat Completions | Responses | 实际传给 Command Code | | --- | --- | --- | --- | | 文本 | `content` 字符串或 `text` part | 字符串、`input_text`、`output_text` | UTF-8 stdin prompt | | 图片 | `image_url.url` 的 base64 data URL | `input_image.image_url` 的 base64 data URL | 校验后写入临时 PNG/JPEG/GIF/WebP,传 `@path`,由 `read_file`/视觉模型读取 | | 纯文本文件 | 不支持 API 附件;OpenCode `read` 工具结果仍是普通文本消息 | `input_file.file_data`,必须带 filename | 校验 UTF-8 后写入临时文本文件,传 `@path`,由 `read_file` 读取 | | HTTP(S)、`file://`、本地路径 | 拒绝 | 拒绝 | 不联网、不读取客户端指定路径 | | PDF、Office、任意二进制 | 拒绝 | 拒绝 | 不做伪 OCR、伪摘要或二进制转文本 | | 音频、视频、Computer Use、托管工具 | 拒绝 | 拒绝 | 未实现 | 附件最多 16 个;单张图片解码后最多 10 MiB,单个 UTF-8 文本文件最多 5 MiB,合计最多 15 MiB,并继续受 `max_request_bytes` 限制。图片 MIME 白名单为 `image/png`、`image/jpeg`、`image/gif`、`image/webp`,声明 MIME 必须与魔数一致。临时文件权限为 `0600`,位于 `.command-code-openai-bridge/inputs` 的请求专用目录,并在成功、错误、取消或超时后从 `finally` 清理。 附件原始 data URL、base64 和 `read_file` 返回的原始文件内容不会进入 Command Code prompt、桥接终端日志或 Responses 本地存储。终端 renderer 会把附件 `read_file` 的完整 tool result 替换为脱敏标记,并递归遮蔽其他事件中的 data URL、base64、凭据和 URL 查询参数;NDJSON 解析错误也不回显原始行。模型按用户要求生成的最终回答仍会照常显示。`store: true` 只保存文本/工具历史;附件 part 被省略,因此后续 `previous_response_id` 不会伪装成仍能读取旧附件。需要再次使用时必须重新提交。 ### Chat Completions 支持 `system`、`developer`、`user`、`assistant` 和 `tool` 消息。文本 `content` 可以是字符串,也可以是由 `{ "type": "text", "text": "..." }` 组成的数组;user 消息还可包含标准 `{ "type": "image_url", "image_url": { "url": "data:image/png;base64,..." } }`。assistant 工具轮次支持 `content: null`、`tool_calls`,tool 消息支持 `tool_call_id` 和可选 `name`。 `image_url` 只接受 base64 data URL;远程 URL、`file://` 和本地路径返回 `400 unsupported_parameter`,`param` 精确指向 `messages..content..image_url.url`。`detail` 只能省略或为 `auto`,因为 CLI 没有 low/high 映射。所选模型必须配置 `supports_image_input: true`。 Chat 接口接受标准 function tools、`tool_choice` 和 `parallel_tool_calls`。Bridge 把工具定义、完整消息历史和工具结果交给 Command Code 决定下一步,校验返回的工具名与 arguments JSON Schema;第一次不合格时在原请求总截止时间内执行一次修复。工具由 API 客户端执行,Bridge 不执行客户端工具,也不直接访问 Obsidian vault。该流程适用于 Obsidian Copilot 的 `localSearch`、`readNote`、`getFileTree`、`writeFile` 和 `editFile`,也适用于其他标准 function tools。协议流程参考 [OpenAI Function calling](https://developers.openai.com/api/docs/guides/function-calling)。 `stream` 省略或设为 `false` 时返回普通 JSON。普通文本请求的 `stream: true` 会立即建立 SSE 连接并发送 assistant role 块,随后在 Command Code 的 `text_delta` 到达时立即发送对应 SSE chunk,最后发送 `finish_reason: "stop"`、可选 usage 块和 `[DONE]`。工具模式需要先解析和校验完整决策:最终文本会在校验后作为 content chunk 发送;工具调用会作为 `delta.tool_calls` 发送,并以 `finish_reason: "tool_calls"` 结束。Chat structured output 会缓存完整正文,只有 JSON 通过校验或单次修复后才发送 content;连续失败会发送 `invalid_structured_output` 错误和 `[DONE]`,不会先发出无法收回的无效 JSON。`stream_thinking: true` 只作用于非结构化普通文本请求;该兼容格式不是原生 OpenAI reasoning item。传入 `stream_options.include_usage: true` 时,结束前返回 usage 块。 为兼容 Obsidian Copilot、OpenCode 和 LangChain OpenAI-format 客户端,Chat 接口还接受 `temperature`、`max_tokens`、`max_completion_tokens`、`top_p`、`frequency_penalty`、`presence_penalty`、`n: 1` 和 `store: false`。`n: 1` 是 Bridge 实际支持的单候选协议行为;`store: false` 保持每次请求使用 `--no-session` 的无状态行为,不创建服务端 Chat session。Command Code 1.18.0 官方 `--help` 没有 temperature、top-p、frequency/presence penalty 或输出 token 上限的 headless 参数,因此前六个采样/输出限制字段只在协议层接受和校验,不会传给 CLI,也不会用提示词伪造。只要请求显式提供其中任一字段,服务终端会输出警告,HTTP 响应的 `X-Command-Code-Ignored-Parameters` 会准确列出未实际应用的字段;浏览器可通过 CORS exposed header 读取。其他未实现字段仍返回 `400 unsupported_parameter`。 Chat Completions 和 Responses 共用 Command Code NDJSON 执行层。服务终端会在事件到达时立即显示状态、文本和 Command Code 自身的工具调用。普通文本 Chat 会把每个 `text_delta` 直接写入 SSE;外部 function calling 和 structured output 模式会缓存 `finalText`,避免内部决策或未校验 JSON 进入客户端正文。`response_format` 只约束最终文本:需要调用工具的轮次先按 tools 协议返回,客户端提交工具结果后的最终文本轮次再执行格式校验。Responses 外部 function calling 同样缓存 `finalText`,工具轮次不向客户端输出 assistant 文本。 ### Responses `POST /v1/responses` 支持这些字段: - `model` - `input` - `instructions` - `stream` - `store` - `previous_response_id` - `metadata` - `reasoning.effort` - `text.format` - `tools` - `tool_choice` - `parallel_tool_calls` `input` 可以是字符串,也可以是 item 数组。message 支持 `system`、`developer`、`user`、`assistant` 角色、字符串 content,以及 `input_text`、`output_text`、`input_image`、`input_file` part。工具轮次还支持 `function_call` 和 `function_call_output` item,分别携带 `call_id`、工具名、`arguments` 和工具执行结果 `output`。 `input_image.image_url` 与 Chat 一样只接受图片 data URL;`file_id`、远程 URL、`file://`、本地路径和 low/high detail 不支持。`input_file` 只接受 `file_data` 加 `filename`,其中 `file_data` 可以是严格 base64 或带受支持文本 MIME 的 base64 data URL;解码结果必须是无 NUL 的 UTF-8 文本。`file_url`、`file_id`、PDF、Office 和其他二进制会返回带准确字段路径的 400。 Responses 接受标准 function tools、`tool_choice` 和 `parallel_tool_calls`。Bridge 把工具定义、完整历史(含先前 response 的完整 output items)和工具结果交给 Command Code 决定下一步,校验返回的工具名与 arguments JSON Schema;第一次不合格时在原请求总截止时间内执行一次修复。工具由 API 客户端执行,Bridge 不执行客户端工具,也不会让 Command Code 用自身文件、终端、网络等工具替代外部 tools。该流程适用于 OpenCode 等使用 `@ai-sdk/openai` 或 OpenAI Node.js SDK 的 Responses 客户端。 普通文本响应包含标准 assistant message output item 和 `output_text`。工具调用轮次返回 `function_call` output item,包含 `id`、`call_id`、`name`、`arguments` 和 `status`,`output_text` 为空。最终文本轮次在有足够 `function_call_output` 后返回 message。响应还包含 `id`、`object`、时间、状态、错误、instructions、model、响应链 ID、effective reasoning effort、store、metadata、text format、tools、tool_choice、parallel_tool_calls 和 usage。usage 只使用 Command Code 实际提供的 input/output token;没有 usage 时返回 `null`,不生成 token 明细。 `stream: true` 会立即建立 SSE 连接并发送 `response.created` 和 `response.in_progress`。文本响应随后发送: 1. `response.output_item.added` 2. `response.content_part.added` 3. 一个或多个 `response.output_text.delta` 4. `response.output_text.done` 5. `response.content_part.done` 6. `response.output_item.done` 7. `response.completed` 或 `response.incomplete` 工具调用轮次发送: 1. `response.output_item.added` 2. `response.function_call_arguments.delta` 3. `response.function_call_arguments.done` 4. `response.output_item.done` 5. `response.completed` 所有事件都包含递增的 `sequence_number`。连接使用 `no-cache, no-transform`,收到客户端断开后会终止 Command Code 子进程组。 Responses API 仍按 turn 缓冲文本,丢弃 `hadToolCalls: true` 的中间轮次,在 `hadToolCalls: false` 时按原 delta 边界发送。文本 delta 会延迟到最终 turn 边界。外部 function calling 和结构化输出还会延迟到 JSON 校验完成,避免把随后需要修复的无效 JSON 或内部决策发给客户端。 ### 本地存储和响应链 `store` 默认是 `true`。完成、incomplete、failed 和 cancelled Response 会写入配置的本地目录;`store: false` 不写入当前 Response,因此它不能在后续作为 `previous_response_id` 使用。 收到 `previous_response_id` 时,Bridge 从本地文件沿链向前读取每个 Response,按原顺序重放历史 input 和完整 assistant output items(含 `function_call` 与 message,不仅重放 `output_text`),再追加当前 input。可以从任意仍存在的旧 Response 创建分支。删除某个祖先后,依赖该祖先的链会返回 404。Response ID 使用固定本地格式并在拼接文件路径前校验。 input items 查询返回标准 `{ object: "list", data, first_id, last_id, has_more }`,支持 `after`、`limit` 和 `order`。删除成功返回 `{ id, object: "response", deleted: true }`。 ### 结构化输出 Chat Completions 支持标准 `response_format`: ```json { "response_format": { "type": "text" } } ``` ```json { "response_format": { "type": "json_object" } } ``` ```json { "response_format": { "type": "json_schema", "json_schema": { "name": "result", "strict": true, "schema": { "type": "object" } } } } ``` Responses 支持对应的 `text.format`: ```json { "text": { "format": { "type": "text" } } } ``` ```json { "text": { "format": { "type": "json_object" } } } ``` ```json { "text": { "format": { "type": "json_schema", "name": "result", "strict": true, "schema": { "type": "object" } } } } ``` Bridge 把格式要求作为独立内部指令发送给 Command Code。最终文本必须能解析为 JSON;`json_object` 要求顶层对象,`json_schema` 使用 Ajv 校验。第一次失败后会在同一 AbortSignal 和原请求总截止时间内顺序执行一次修复,usage 是各次真实用量之和。Chat 第二次仍失败时,非流式请求返回 HTTP 502 `invalid_structured_output`,流式请求发送同 code 的 SSE 错误;Responses 则返回 `status: "incomplete"` 和 `incomplete_details.reason: "structured_output_validation_failed"`。 这是 Bridge 层的提示、JSON 解析、Ajv 校验和单次修复约束,不是 Command Code 或底层模型提供的解码级原生 structured outputs。 官方协议依据: - [Chat Completions streaming events](https://developers.openai.com/api/reference/resources/chat/subresources/completions/streaming-events) - [Responses API reference](https://developers.openai.com/api/reference/resources/responses/methods/create) - [Responses streaming events](https://developers.openai.com/api/reference/resources/responses/streaming-events) - [List input items](https://developers.openai.com/api/reference/resources/responses/subresources/input_items/methods/list) - [Structured Outputs](https://developers.openai.com/api/docs/guides/structured-outputs) ### curl ```bash curl -sS http://127.0.0.1:18000/v1/chat/completions \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ignored' \ -d '{ "model": "command-default", "messages": [ {"role": "user", "content": "用一句话解释递归。"} ], "stream": false }' ``` ### OpenAI Python SDK ```bash python3 -m venv .venv .venv/bin/pip install openai .venv/bin/python examples/python_client.py ``` ### OpenAI Node.js SDK 项目依赖已包含 `openai`: ```bash node examples/node_client.mjs ``` ## 实际 Command Code 命令 每次请求实际执行以下固定参数,完整提示词不进入命令行: ```bash command-code \ -p \ --output-format json \ --no-session \ --skip-onboarding \ --no-auto-update \ --trust \ --max-turns 100 \ --model deepseek/deepseek-v4-flash \ --effort max \ --yolo ``` 随后通过子进程 stdin 写入 UTF-8 的完整文本历史和不含原始数据的附件路径区块。消息序列被放进一个 JSON 对象,角色、顺序、空消息、Unicode、Markdown、代码块和自定义分隔符均不会被简单文本分隔符破坏。附件 data URL/base64 不进入该对象。Chat Completions 不保存会话;Responses 只在 `store: true` 时保存去除附件数据后的协议对象,并在新请求中把响应链重新序列化给一个新的 `--no-session` 进程。 ## 有限队列和错误 同一时间最多运行 `max_concurrent_requests` 个 Command Code 实例,默认值 `1` 保持原来的单并发行为。其余 Chat Completions 和 Responses 生成请求共享同一条 FIFO 等待队列;任一执行槽释放后都会按队首顺序补齐空闲槽。默认最多等待 8 个请求,队列已满时返回 HTTP 429 和 `code: queue_full`。流式请求进入队列后会立即建立 SSE 连接;非流式请求保持等待。客户端在排队期间断开会立即移出队列,活动请求取消并结束后会推进队首请求。`timeout_seconds` 从请求取得执行位置后开始计算。 请求体超过 `max_request_bytes` 返回 413。未知模型、未声明图片能力、无效 base64、MIME/魔数不一致、超限附件和不支持的输入来源返回 4xx,并带准确 `param`。CLI 的详细错误留在服务终端,客户端只收到简洁的 OpenAI 格式错误。 一次 CLI 异常不会结束 HTTP 服务,后续请求仍可继续。 ## 本机实际检查结果 检查日期:Chat Completions 原有检查为 2026-08-04;Responses 文本子集检查为 2026-08-05;Chat function calling 协议检查为 2026-08-06;Responses function calling、OpenCode 1.18.15 Chat Completions E2E、可配置并发和 Command Code 1.18.0 非文本输入检查为 2026-08-12。没有编写测试用例。非文本输入先由真实 CLI 图片路径检查确认,再完成类型检查、生产构建和 Bridge HTTP 冒烟检查。 - TypeScript 严格类型检查和生产构建通过。 - npm 生产依赖审计:0 个已知漏洞。 - `/health` 返回 Command Code v1.18.0、已登录、空闲。 - `max_concurrent_requests: 2` 时实际观察到 2 个 Command Code CLI 子进程并行运行,后续请求保持 FIFO;取消排队请求会移出队列,取消活动请求后队首请求会被推进,最终健康状态恢复为 `active_requests: 0`、`queue_length: 0`。 - `/v1/models` 返回两个本地映射。 - curl 中文请求返回 HTTP 200,客户端只收到 `中文接口成功` 和标准 completion 字段。 - 字符串 content 与 text part 数组均通过;不支持的非文本 part 返回 HTTP 400。 - system、user、assistant、user 完整历史检查返回了历史 assistant 中的 `蓝鲸-42`。 - 约 96 KiB 的中文消息通过 stdin 完整传入并返回 `长文本回退成功`,没有经过命令行参数。 - read_file 工具调用、参数和文件结果显示在服务终端,API 只返回 package name。 - `--yolo` 下 shell_command 实际执行并返回 `shell-tool-ok`;`auto-accept` 下该工具确实被 headless 权限引擎拒绝。 - OpenAI Node.js SDK 示例通过。 - OpenAI Python SDK 示例通过。 - OpenAI Node.js SDK 的 Chat `stream: true` 检查中,约 31 ms 收到 assistant role 块;最终文本按 10 个原始 delta 返回,随后收到 `stop`、真实 usage 和 `[DONE]`。 - LangChain 双轮检查成功把 SSE `delta.tool_calls` 重建为 `AIMessage.tool_calls`,随后发送 assistant tool call 与带 `name` 的 tool result;Bridge Schema 接受实际请求,第二轮文本也被正确重建。 - 真实 `deepseek/deepseek-v4-flash` 双轮检查中,第一轮在 `tool_choice: auto` 下返回 `localSearch`,arguments 通过 JSON Schema 校验并以 `finish_reason: tool_calls` 结束;第二轮消费模拟 vault 结果后返回 `Projects/独立决策记录.md` 并以 `stop` 结束。两次累计 usage 为输入 62,850、输出 394、合计 63,244 tokens。 - 强制 `read_file` 的 Chat 两 turn 流只向客户端输出第二个无工具 turn 的 6 个文本 delta;终端实时显示工具状态、结果和最终文本,中间轮次没有污染客户端内容。 - Chat 流式连接收到首块后断开会取消 CLI 并恢复空闲;取消后的普通 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` 仍返回 400。 - 超过 20 MiB 的请求体返回 HTTP 413,服务保持可用。 - 客户端 1 秒超时断开后,当前子进程组被清理,`busy` 恢复为 false,没有发现残留 headless CLI。 - 运行中按 Ctrl+C 后客户端收到 HTTP 499,服务保持运行;紧接着的真实请求返回 `中断后恢复成功`。 - 原始交互模式的本机 TTY 探测确认 Ink TUI、ANSI、输入框和双 Ctrl+C 退出行为存在;该模式没有独立结构化最终结果通道。 - OpenAI Node.js SDK 的 `responses.create()` 返回标准文本 Response;请求级 `reasoning.effort: "max"` 覆盖模型配置并实际传给 CLI。 - `store: true` 的 Response 可通过 GET 和 input items 查询;`previous_response_id` 成功重建历史并从 `链起点-42` 得到下一轮 `42`。 - `json_schema` 合格输出通过 Ajv;不可满足的合法 Schema 顺序执行两次 CLI 后返回 `incomplete` 和 `structured_output_validation_failed`,usage 为两次实际用量之和。 - OpenAI Node.js SDK 成功消费 Responses SSE;事件顺序、递增 sequence number、多个 text delta 和最终文本均正确。 - 强制 `read_file` 的两 turn 请求只向 SSE 输出第二个无工具 turn 的 6 个文本 delta,中间工具轮次没有污染 `output_text`。 - `store: false` 的流式 Response 随后查询返回 404;删除已保存 Response 返回 deleted,随后查询返回 404。 - 真实 `-p --output-format json` 图片路径检查中,Kimi K2.7 Code 和 GPT-5.6 Luna 都调用 `read_file` 并正确识别四象限颜色。 - 真实 `max_turns: 1` 工具请求返回 200 `incomplete/max_turns`;1 秒总超时返回 200 `incomplete/timeout`;主动取消返回 499 `cancelled`。 - 取消后紧接着的旧 Chat Completions 请求返回 `Chat恢复成功`,证明共享执行状态和错误恢复正常。 - Responses 第一轮 `tool_choice: required` 返回 `function_call`,`output_text` 为空;第二轮提交 `function_call_output` 和 `previous_response_id` 后返回最终 message 文本。 - Responses 流式 function call 发送 `output_item.added`、`function_call_arguments.delta/done`、`output_item.done` 和 `completed`,无 `output_text.delta`。 - OpenAI Node.js SDK 的 Responses 双轮检查:第一轮返回 `multiply` function call,第二轮消费 `function_call_output` 后返回最终文本。 - Responses 无效工具名、无效 arguments JSON、孤立 `function_call_output` 和 hosted tool 类型分别返回带 `param` 的 400 错误。 - OpenCode 1.18.15 自定义 provider:`store: false` 与 `max_tokens: 32000` 返回 HTTP 200;`stream: true` 工具轮次以 `finish_reason: tool_calls` 结束,文本轮次以 `stop` 结束;`glob` 单工具与并行 `glob` 完成完整工具循环;工具 `parameters.$schema`(draft 2020-12)不再触发 400。 ## 已知限制 - 没有原始 Command Code TUI、颜色布局、动画和键盘交互。 - headless 无法在服务终端进行批准、拒绝、选项选择或文字回答;`ask_user_question` 不能由等待中的 HTTP 客户端处理。 - 终端事件渲染由本项目完成,格式接近日志,无法等同原始 TUI。 - 普通文本 Chat Completions 会实时转发 Command Code 文本 delta;外部 function calling 以及 Chat/Responses 结构化输出需要等待 Bridge 校验完成。 - Chat Completions 支持受限图片 data URL;Responses 还支持受限 UTF-8 `input_file.file_data`。不实现远程/本地路径附件、PDF/Office/任意二进制、音频、视频、Computer Use、web search 等 hosted tools 或 MCP hosted tool。非 function 工具类型返回 400 `unsupported_parameter`。Responses 不实现原生 reasoning item、加密 reasoning 或隐藏思维过程。 - Chat 与 Responses 的外部 function calling 都是 Bridge 通过提示协议、JSON 解析、工具参数 Schema 校验和一次修复实现的兼容层;Command Code CLI 没有公开原生 function calling 输出接口,因此连续两次输出不合格时返回 HTTP 502 `invalid_tool_decision`。 - Responses 的本地文件存储只供本 Bridge 使用,没有跨进程锁或多实例一致性保证;并发请求应避免同时更新同一条 Response 链。 - Chat 和 Responses Structured Outputs 都是 Bridge 层约束,底层 Command Code 模型仍可能连续两次输出不合格 JSON;Chat 此时返回 `invalid_structured_output`,Responses 状态为 incomplete。 - `usage` 使用 Command Code 最终结果提供的真实 input/output token;Responses 缺失时返回 `null`,Chat Completions 为兼容旧行为返回 0。 - 长文本受 HTTP 请求体上限和 Command Code 模型上下文上限共同限制,不会由桥接服务自行截断。 - 大输入会让 `run_end.nextState` 重复完整提示词。实测约 96 KiB 中文输入时,CLI 退出前可能截断该大事件并丢掉紧随其后的 compact `result` 行。桥接服务会忽略冗余 `run_end`,优先使用 `result.finalText`;若退出码为 0 且 result 缺失,只使用最后一个已完整结束、无工具调用的结构化 turn 文本和真实 turn usage,不从 TUI 文本解析。