Command Code OpenAI Bridge
这是一个只绑定本机地址的 OpenAI 兼容转发服务。它保留 Chat Completions 文本接口,并实现单并发的 Responses API 文本子集。每个生成请求启动一次独立的 Command Code headless agent,将完整消息历史通过 stdin 发送给 CLI,在当前终端显示运行过程,再把结构化事件和最终 finalText 转换成 OpenAI 风格的 JSON 或 SSE。
默认地址:http://127.0.0.1:18000/v1
当前机器上的调查结论
- 系统:macOS 26.4.1 arm64。
- Node.js:v22.22.2,符合 Command Code 的 Node.js 22+ 要求。
- 安装方式:
npm install --global command-code@latest。 - 已安装 Command Code:v1.10.0,npm
latest也是 v1.10.0。 - 可执行文件:
command-code;macOS/Linux 短名为cmd,Windows 短名为cmdc。 - 登录:
command-code login;检查:command-code status --json;本机已登录。 - npm 包没有
exports字段,main指向会直接启动 CLI 的dist/cli.mjs,包尾直接解析命令行,没有公开、稳定的可嵌入 API。 - 官方 headless 调用:
command-code -p --output-format json。不传查询参数时会从 stdin 读取。 - JSON 输出是 NDJSON:运行中输出
AgentEvent,正常情况下最后输出唯一的result行;最终回答位于finalText,usage 和耗时也在该行。 --no-session让每次请求只使用内存会话,不写入或恢复跨请求 session。- 官方 headless 模式明确不提供键盘、问题回答或权限批准等交互。原始 TTY/TUI 与可靠的独立
finalText目前不能同时获得。 - 官方退出码:0 成功;1 常规错误;3 未登录;4 权限拒绝;5 限流;6 网络错误;7 服务端错误;8 达到 turn 上限;9 无回复;10 余额不足;130 被信号中断。
官方资料:
为什么使用普通子进程与结构化事件
本项目使用官方 headless 子进程和 NDJSON,不使用 PTY,也不解析 TUI 文本。
原因是 v1.10.0 没有公开嵌入 API,交互式 TUI 没有独立的结构化最终结果通道。headless 的最后一行提供稳定 finalText,能保证工具参数、工具结果、状态、stderr、思考事件和 ANSI 内容不会混入 API 回复。
终端会显示:请求开始、模型、turn 状态、工具事件、工具参数、工具结果、stderr、最终回答和耗时。终端不会显示原始 Ink TUI、动画、键盘快捷键、人工权限批准、人工问题回答和详细内部思考文本。
本机 v1.10.0 实测表明,headless 即使设为 auto-accept,shell 仍会被拒绝。默认 dangerously_skip_permissions: true,因此实际传入 --yolo,让文件写入和命令工具能继续执行。它会绕过所有权限确认,只应对可信工作目录使用。若要只读,把 dangerously_skip_permissions 改为 false,同时把 permission_mode 改成 plan。
项目结构
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
├── scripts/
│ ├── install.sh
│ └── start.sh
└── examples/
├── node_client.mjs
└── python_client.py
安装
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、安装项目依赖并构建。
如未登录:
command-code login
command-code status --json
command-code login 会打开浏览器,也可以粘贴从 Command Code Studio 创建的 API key。
配置
编辑 config.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_turns: 100
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
command_code_working_directory 决定 Command Code 能看到和操作的项目目录。response_store_directory 保存 store: true 的 Response;两个相对路径都以配置文件所在目录为基准。默认存储目录是隐藏目录 .command-code-openai-bridge/responses。models.<name>.effort 会传给 Command Code 的 --effort,Responses 请求中的 reasoning.effort 优先。可用 effort 由对应底层模型决定。服务强制只监听 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:开启
Bridge 会直接响应浏览器 CORS 预检,包括 Copilot 和 OpenAI SDK 发送的自定义请求头及本机 Private Network Access。关闭 Copilot 的 CORS 绕过后,请求使用原生 fetch,可以读取 Bridge 返回的 SSE 流。
启动、停止与重启
./scripts/start.sh
scripts/start.sh 只运行已经生成的 dist/index.js,不会自动编译 TypeScript。修改 src/ 中的源码后,必须先重新编译,再重启服务,新代码才会生效:
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 /healthGET /v1/modelsPOST /v1/chat/completionsPOST /v1/responsesGET /v1/responses/:response_idDELETE /v1/responses/:response_idGET /v1/responses/:response_id/input_items
Bearer Token 会被忽略。所有生成接口只接受文本。图片、音频、文件、工具和其他未实现字段会返回 OpenAI 格式的 400,code 为 unsupported_parameter;服务不会静默忽略会改变行为的参数。
Chat Completions
支持字符串 content,也支持由 { "type": "text", "text": "..." } 组成的数组。stream 省略或设为 false 时返回普通 JSON。stream: true 会立即建立 SSE 连接并发送 assistant role 块,随后在 Command Code 的 text_delta 到达时立即发送对应 SSE chunk,最后发送 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 实时事件执行层。服务终端会在事件到达时立即显示状态、文本和工具调用。Chat Completions 会把每个 text_delta 直接写入 SSE,实现生成过程中的打字机效果。Bridge 提示 Command Code 在工具调用 turn 不输出面向用户的文本;Command Code 只有在 turn_end 才给出 hadToolCalls,因此上游若仍在工具 turn 输出文本,该文本已经发送,SSE 无法撤回。
Responses
POST /v1/responses 支持这些字段:
modelinputinstructionsstreamstoreprevious_response_idmetadatareasoning.efforttext.format
input 可以是字符串,也可以是 message item 数组。message 支持 system、developer、user、assistant 角色、字符串 content,以及 input_text、output_text part。
普通响应包含 id、object、时间、状态、错误、instructions、model、标准 assistant message output item、output_text、响应链 ID、effective reasoning effort、store、metadata、text format 和 usage。usage 只使用 Command Code 实际提供的 input/output token;没有 usage 时返回 null,不生成 token 明细。
stream: true 会立即建立 SSE 连接并发送:
response.createdresponse.in_progressresponse.output_item.addedresponse.content_part.added- 一个或多个
response.output_text.delta response.output_text.doneresponse.content_part.doneresponse.output_item.doneresponse.completed或response.incomplete
所有事件都包含递增的 sequence_number。连接使用 no-cache, no-transform,收到客户端断开后会终止 Command Code 子进程组。
Responses API 仍按 turn 缓冲文本,丢弃 hadToolCalls: true 的中间轮次,在 hadToolCalls: false 时按原 delta 边界发送。文本 delta 会延迟到最终 turn 边界。结构化输出还会延迟到 JSON 校验完成,避免把随后需要修复的无效 JSON 发给客户端。
本地存储和响应链
store 默认是 true。完成、incomplete、failed 和 cancelled Response 会写入配置的本地目录;store: false 不写入当前 Response,因此它不能在后续作为 previous_response_id 使用。
收到 previous_response_id 时,Bridge 从本地文件沿链向前读取每个 Response,按原顺序重放历史 input 和 assistant output,再追加当前 input。可以从任意仍存在的旧 Response 创建分支。删除某个祖先后,依赖该祖先的链会返回 404。Response ID 使用固定本地格式并在拼接文件路径前校验。
input items 查询返回标准 { object: "list", data, first_id, last_id, has_more },支持 after、limit 和 order。删除成功返回 { id, object: "response", deleted: true }。
结构化输出
支持:
{ "text": { "format": { "type": "text" } } }
{ "text": { "format": { "type": "json_object" } } }
{
"text": {
"format": {
"type": "json_schema",
"name": "result",
"strict": true,
"schema": { "type": "object" }
}
}
}
Bridge 把格式要求作为独立内部指令发送给 Command Code。最终文本必须能解析为 JSON;json_object 要求顶层对象,json_schema 使用 Ajv 校验。第一次失败后会在同一 AbortSignal 和原请求总截止时间内顺序执行一次修复,usage 是两次真实用量之和。第二次仍失败时返回 status: "incomplete" 和 incomplete_details.reason: "structured_output_validation_failed"。
这是 Bridge 层的提示、解析、校验和单次修复约束,不具备底层模型原生 Responses Structured Outputs 的解码级保证。
官方协议依据:
- Chat Completions streaming events
- Responses API reference
- Responses streaming events
- List input items
- Structured Outputs
curl
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
python3 -m venv .venv
.venv/bin/pip install openai
.venv/bin/python examples/python_client.py
OpenAI Node.js SDK
项目依赖已包含 openai:
node examples/node_client.mjs
实际 Command Code 命令
每次请求实际执行以下固定参数,完整提示词不进入命令行:
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、代码块和自定义分隔符均不会被简单文本分隔符破坏。Bridge 不总结、不删除、不截断消息。Chat Completions 不保存会话;Responses 只在 store: true 时保存协议对象,并在新请求中把响应链重新序列化给一个新的 --no-session 进程。
单并发和错误
同一时间只允许一个 Command Code 实例。第二个请求直接返回 HTTP 429 和 code: busy。请求体超过 max_request_bytes 返回 413。未知模型和非文本内容返回 4xx。CLI 的详细错误留在服务终端,客户端只收到简洁的 OpenAI 格式错误。
一次 CLI 异常不会结束 HTTP 服务,后续请求仍可继续。
本机实际检查结果
检查日期:Chat Completions 原有检查为 2026-08-04;Responses 新增检查为 2026-08-05。没有编写测试用例,以下均为构建后运行真实 CLI 和真实 HTTP/SDK 客户端得到的端到端结果。
- TypeScript 严格类型检查和生产构建通过。
- npm 生产依赖审计:0 个已知漏洞。
/health返回 Command Code v1.10.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]。 - 强制
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。 - 运行中按 Ctrl+C 后客户端收到 HTTP 499,服务保持运行;紧接着的真实请求返回
中断后恢复成功。 - 原始交互模式的本机 TTY 探测确认 Ink TUI、ANSI、输入框和双 Ctrl+C 退出行为存在;该模式没有独立结构化最终结果通道。
- OpenAI Node.js SDK 的
responses.create()返回标准文本 Response;请求级reasoning.effort: "max"覆盖模型配置并实际传给 CLI。 - Responses 运行中第二个生成请求返回 429
busy,第一个请求正常完成。 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。input_image返回 400unsupported_parameter,并包含准确的参数路径。- 真实
max_turns: 1工具请求返回 200incomplete/max_turns;1 秒总超时返回 200incomplete/timeout;主动取消返回 499cancelled。 - 取消后紧接着的旧 Chat Completions 请求返回
Chat恢复成功,证明共享单并发状态和错误恢复正常。
已知限制
- 没有原始 Command Code TUI、颜色布局、动画和键盘交互。
- headless 无法在服务终端进行批准、拒绝、选项选择或文字回答;
ask_user_question不能由等待中的 HTTP 客户端处理。 - 终端事件渲染由本项目完成,格式接近日志,无法等同原始 TUI。
- Chat Completions 会实时转发 Command Code 文本 delta;上游若在工具 turn 输出文本,已经发送的 SSE 内容无法撤回。Responses 的文本 delta 需要等到
turn_end.hadToolCalls: false才发送,结构化输出还需要等待 Bridge 校验完成。 - 只实现 Responses 文本子集,不实现图片、音频、文件、function calling、Computer Use、托管工具、原生 reasoning item、加密 reasoning 或隐藏思维过程。
- Responses 的本地文件存储只供本 Bridge 使用,没有跨进程锁、队列或多实例一致性保证。项目本身仍严格单并发。
- Responses Structured Outputs 是 Bridge 层约束,底层 Command Code 模型仍可能连续两次输出不合格 JSON;此时状态为 incomplete。
usage使用 Command Code 最终结果提供的真实 input/output token;Responses 缺失时返回null,Chat Completions 为兼容旧行为返回 0。- 长文本受 HTTP 请求体上限和 Command Code 模型上下文上限共同限制,不会由桥接服务自行截断。
- v1.10.0 的大输入会让
run_end.nextState重复完整提示词。实测约 96 KiB 中文输入时,CLI 退出前可能截断该大事件并丢掉紧随其后的 compactresult行。桥接服务会忽略冗余run_end,优先使用result.finalText;若退出码为 0 且 result 缺失,只使用最后一个已完整结束、无工具调用的结构化 turn 文本和真实 turn usage,不从 TUI 文本解析。