Files
command-code-openai-bridge/README.md
T
2026-08-05 15:36:36 +08:00

221 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Command Code OpenAI Bridge
这是一个只绑定本机地址的 OpenAI Chat Completions 兼容转发服务。它为每个 API 请求启动一次独立的 Command Code headless agent,将完整消息历史通过 stdin 发送给 CLI,在当前终端显示运行过程,只把官方结构化结果中的 `finalText` 返回给客户端。
默认地址:`http://127.0.0.1:18000/v1`
## 当前机器上的调查结论
- 系统:macOS 26.4.1 arm64。
- Node.jsv22.22.2,符合 Command Code 的 Node.js 22+ 要求。
- 安装方式:`npm install --global command-code@latest`
- 已安装 Command Codev1.10.0npm `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 被信号中断。
官方资料:
- [Quickstart](https://commandcode.ai/docs/quickstart)
- [CLI Reference](https://commandcode.ai/docs/reference/cli)
- [Headless Mode](https://commandcode.ai/docs/headless)
- [Permissions](https://commandcode.ai/docs/core-concepts/permissions)
## 为什么使用普通子进程与结构化事件
本项目使用官方 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`
## 项目结构
```text
command-code-openai-bridge/
├── config.yaml
├── config.example.yaml
├── package.json
├── tsconfig.json
├── src/
│ ├── command-code.ts
│ ├── config.ts
│ ├── index.ts
│ ├── openai.ts
│ ├── renderer.ts
│ └── server.ts
├── scripts/
│ ├── install.sh
│ └── start.sh
└── examples/
├── node_client.mjs
└── python_client.py
```
## 安装
```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_turns: 100
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 能看到和操作的项目目录。相对路径以 `config.yaml` 所在目录为基准。`models.<name>.effort` 会传给 Command Code 的 `--effort`,可用值取决于对应模型。服务强制只监听 `127.0.0.1`。客户端只能选择配置中的模型名,不能注入额外 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`
Bearer Token 会被忽略。只接受文本消息。支持字符串 `content`,也支持由 `{ "type": "text", "text": "..." }` 组成的数组。图片、音频和其他 part 会返回 400。
`stream` 省略或设为 `false` 时返回普通 JSON。`stream: true` 时,服务仍会等待 Command Code 完整执行结束,再将最终文本一次性封装为 Chat Completions SSE 事件返回;它不提供实时生成过程。传入 `stream_options.include_usage: true` 时,结束前还会返回 usage 块。
### 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、代码块和自定义分隔符均不会被简单文本分隔符破坏。服务不总结、不删除、不截断消息,也不保存会话。
## 单并发和错误
同一时间只允许一个 Command Code 实例。第二个请求直接返回 HTTP 429 和 `code: busy`。请求体超过 `max_request_bytes` 返回 413。未知模型和非文本内容返回 4xx。CLI 的详细错误留在服务终端,客户端只收到简洁的 OpenAI 格式错误。
一次 CLI 异常不会结束 HTTP 服务,后续请求仍可继续。
## 本机实际检查结果
检查日期:2026-08-04。没有编写测试用例,以下均为构建后运行真实 CLI 和真实 HTTP 客户端得到的端到端结果。
- 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 示例通过。
- `stream: false` 返回普通 JSON`stream: true` 在完整结果生成后一次性返回兼容 SSE。
- 并发检查中第二个请求返回 HTTP 429 和 `code: busy`,没有启动第二个 CLI。
- 超过 20 MiB 的请求体返回 HTTP 413,服务保持可用。
- 客户端 1 秒超时断开后,当前子进程组被清理,`busy` 恢复为 false,没有发现残留 headless CLI。
- 运行中按 Ctrl+C 后客户端收到 HTTP 499,服务保持运行;紧接着的真实请求返回 `中断后恢复成功`
- 原始交互模式的本机 TTY 探测确认 Ink TUI、ANSI、输入框和双 Ctrl+C 退出行为存在;该模式没有独立结构化最终结果通道。
## 已知限制
- 没有原始 Command Code TUI、颜色布局、动画和键盘交互。
- headless 无法在服务终端进行批准、拒绝、选项选择或文字回答;`ask_user_question` 不能由等待中的 HTTP 客户端处理。
- 终端事件渲染由本项目完成,格式接近日志,无法等同原始 TUI。
- API 只实现 Chat Completions 文本范围;`stream: true` 是最终结果的 SSE 兼容封装,不是实时生成。不实现 Responses API、图片、音频、function calling 或服务端会话。
- `usage` 使用 Command Code 最终结果提供的真实 input/output tokenCLI 未提供时返回 0。
- 长文本受 HTTP 请求体上限和 Command Code 模型上下文上限共同限制,不会由桥接服务自行截断。
- v1.10.0 的大输入会让 `run_end.nextState` 重复完整提示词。实测约 96 KiB 中文输入时,CLI 退出前可能截断该大事件并丢掉紧随其后的 compact `result` 行。桥接服务会忽略冗余 `run_end`,优先使用 `result.finalText`;若退出码为 0 且 result 缺失,只使用最后一个已完整结束、无工具调用的结构化 turn 文本和真实 turn usage,不从 TUI 文本解析。