本地翻译代理是 a4api 打通「任意服务商」的关键组件:Claude Code 只说 Anthropic 协议、Codex 说 OpenAI Responses 协议,而国内常用的 OpenAI 兼容上游大多只提供 Chat Completions 接口,三者对不上。代理在本机把一种协议实时翻译成另一种并流式转发,让 Claude Code 能用任意 OpenAI 兼容 API、让 Codex 能接智谱等仅有 Chat Completions 的上游、让 dsh 经由统一透传端点规避上游分片格式缺陷。本文章介绍代理的三条翻译链路、网络与鉴权设计、进程生命周期,以及历次迭代沉淀的上游兼容性加固。

1. 为什么需要翻译代理

三个目标工具各自「说」的协议不同,上游提供的协议也参差不齐:

角色 使用的协议 说明
Claude Code Anthropic /v1/messages 官方仅原生支持 Anthropic 协议
Codex OpenAI /responses Responses 接口较新,多数国内上游未提供
dsh OpenAI /chat/completions 最普及的接口形态
多数上游 仅 Chat Completions DeepSeek、智谱 v4、OpenRouter 等

代理据此提供三条链路,全部收敛到同一个本机端口:

graph LR A[Claude Code
Anthropic messages] -->|翻译| P[本地翻译代理
127.0.0.1] B[Codex
Responses] -->|翻译| P C[dsh
Chat Completions] -->|透传+清洗| P P --> T[翻译为
Chat Completions] T --> U[OpenAI 兼容上游
DeepSeek/智谱/OpenRouter等] D[Codex 直连场景
原生 Responses 上游] -.->|不经代理| U style A fill:#e8f4f8,stroke:#1a6b8a,stroke-width:2px style B fill:#d6eaf8,stroke:#1a6b8a,stroke-width:2px style C fill:#ffecd6,stroke:#e67e22,stroke-width:2px style P fill:#ebdef0,stroke:#8e44ad,stroke-width:2px style T fill:#fdebd0,stroke:#b7950b,stroke-width:2px style U fill:#d5f5e3,stroke:#27ae60,stroke-width:2px style D fill:#fadbd8,stroke:#c0392b,stroke-width:2px

唯一的例外是原生支持 Responses 的上游(当前为 DeepSeek 官方):Codex 直连上游,完全不经代理,见服务商管理与预置模板

2. 网络与鉴权

代理的安全边界刻意收得很紧:

设计点 取值 说明
监听地址 127.0.0.1 不对外网开放,局域网内其他设备无法访问
端口范围 17890 - 17899 固定小范围内自动挑选空闲端口
鉴权 token secrets.token_urlsafe(24) 随机生成 每次启动重新生成,写入目标配置供工具携带
鉴权方式 x-api-key 头或 Authorization: Bearer <token> 二选一,与 token 不一致返回 401

切换时写入配置的是代理鉴权 token 而不是真实 API Key——真实 Key 只保存在加密数据库中、由代理在转发前解密使用,不会落盘到任何明文配置里。

3. 端点一览

端点 方法 服务对象 行为
/v1/messages POST Claude Code 把 Anthropic 请求翻译为 Chat Completions 转发,流式响应回译成 SSE
/responses POST Codex 把 Responses 请求翻译为 Chat Completions 转发,供智谱等非原生上游使用
/chat/completions POST dsh 透传到上游,并对流式分片做 tool_calls null 归一清洗
/v1/api/version GET 内部握手 返回能力版本与特性列表,用于复用前校验
/v1/proxy/refresh POST 内部调用 通知代理立即按数据库当前生效配置刷新上游地址与密钥

4. 进程模型与生命周期

4.1 独立进程

代理以独立子进程运行:安装版通过 a4api.exe --proxy 拉起,开发模式为 python -m backend.app.proxy_standalone。无窗口、输出静默,主程序退出后仍然存活——这正是「关闭 a4api 后 Claude Code 还能继续用」的原因。

4.2 启动与复用流程

切换到需要代理的配置方案时,主程序执行「确保代理就绪」:

graph LR A[切换到需要代理的配置] --> B{状态文件存在?
proxy.json} B -->|是| C{端口存活且有
token?} C -->|是| D{能力握手通过?
version>=2 且支持 Responses} D -->|是| E[复用现有代理
并通知刷新上游] D -->|否| F[结束旧构建进程
清理状态文件] C -->|否| F B -->|否| G[拉起独立代理进程] F --> G G --> H[等待就绪
最多 20 秒] H --> E E --> I[返回 base_url
与 token 供写配置] style A fill:#e8f4f8,stroke:#1a6b8a,stroke-width:2px style B fill:#ebdef0,stroke:#8e44ad,stroke-width:2px style C fill:#ebdef0,stroke:#8e44ad,stroke-width:2px style D fill:#ebdef0,stroke:#8e44ad,stroke-width:2px style E fill:#d5f5e3,stroke:#27ae60,stroke-width:2px style F fill:#fadbd8,stroke:#c0392b,stroke-width:2px style G fill:#ffecd6,stroke:#e67e22,stroke-width:2px style H fill:#fdebd0,stroke:#b7950b,stroke-width:2px style I fill:#d6eaf8,stroke:#1a6b8a,stroke-width:2px

几个值得展开的细节:

  • 状态文件:代理启动后把端口、PID 与 token 写入数据目录的 proxy.json;主程序凭它与代理建立联系。
  • 能力握手:端口活着不代表能用——早期构建只有 Anthropic 翻译、没有 Responses 端点,Codex 会拿到 404。复用前必须查询 /v1/api/version,要求能力版本不低于 2 且特性列表包含 Responses 支持,否则视为旧构建,强制结束后重新拉起。
  • 陈旧状态清理:状态文件可能因崩溃而残留过期信息,此时按监听端口反查实际占用进程并处理,避免误判。

4.3 上游信息的更新

代理自己持有真实上游地址与解密后的 API Key:每次切换后主程序调用内部刷新端点,让代理立即从数据库读取「当前生效配置」并更新上游,避免短暂使用旧上游。代理同时每 3 秒轮询一次数据库,一旦发现当前生效配置不再需要代理,便自动退出。

4.4 何时需要代理

当前生效配置 是否运行代理
Anthropic 类型服务商 + Claude 目标 否,直连上游
OpenAI 类型 + 目标含 Claude
OpenAI 类型 + 目标含 Codex(上游非原生 Responses)
OpenAI 原生 Responses + Codex 目标 否,直连上游
OpenAI 类型 + 目标含 dsh 是,始终经代理透传

手动观测与干预走后端接口:查询运行状态的 /api/v1/switch/proxy/status、停止代理的 /api/v1/switch/proxy/stop

5. 上游兼容性加固

真实世界的上游实现五花八门,代理在 0.1.x 的迭代中沉淀了一批针对性兼容处理:

加固项 解决的问题 引入版本
developer 角色映射为 system 部分 Codex 请求的角色字段被上游拒绝(400) 0.1.1
description 字段类型修正 工具描述被误转字符串导致上游拒绝 0.1.0 期间
残缺 tool_call 对话过滤 中断留下的残缺工具调用历史引发上游 400 0.1.2
tool 响应消息重排 工具结果顺序错乱导致 Codex 重连上游 400 0.1.3
上游 429 退避透传 限流响应连同 Retry-After 头原样回传,客户端正确退避 0.1.3
工具 schema 清洗 LLama 系推理服务不认识的 schema 键被裁剪 0.1.0 起
tool_calls null 归一 opencode zen 等上游用 null 填充后续分片,dsh 适配器把工具名覆盖为空报 unknown tool 0.1.5

这些处理全部内建于代理内部,对三个工具透明;对应回归测试覆盖在项目的测试体系中,详见项目总览与快速开始的源码仓库说明。

6. 安全与隐私

代理处理的是完整对话流量,隐私设计上做了三层约束:

  • 密钥不落盘:真实 API Key 只存在于 DPAPI 加密的数据库中,代理转发前才解密;写入各工具配置文件的只有随机鉴权 token。
  • 日志默认静默:正常日志只记时间、级别与摘要,不记录任何请求或响应内容。
  • 调试日志显式开关:仅在设置环境变量 A4API_PROXY_DEBUG 时才记录完整请求头、请求体与上游响应,用于排障后应关闭。

更多安全机制(DPAPI、备份原子写、CORS 白名单等)汇总于安全设计;日志文件位置见数据目录与日志说明

7. 常见问题

问:关闭 a4api 主程序后,Claude Code 还能继续用吗?

答:能。代理是独立进程,主程序退出不影响它;只要数据库里的生效配置仍是需要代理的方案,它会一直驻留并在后台完成翻译转发。

问:界面提示无法访问本地代理地址怎么办?

答:多为残留了旧版本代理进程——能力握手会识别这种情况并自动清理重启,若仍失败,可调用停止接口结束代理后重新切换一次配置;再不行请附上 ~/.a4api/logs/a4api.log 排查。

问:怎么彻底停掉代理?

答:两种方式:把生效配置切换到不需要代理的方案(如 Anthropic 类型直连 Claude Code),代理会在数秒内自行退出;或直接调用停止接口强杀进程并清理状态文件。

问:端口范围只有 10 个,被占满了怎么办?

答:正常场景极难发生——代理只在需要时运行一个实例。若确实出现,先排查是否有异常进程占用 17890 至 17899 端口段;停止代理接口会顺带清理陈旧的占用者。