本文记录一个真实可跑的协议桥实现拦截 Claude Code 的/v1/messages请求转交给 ZCode 的 headless app-server再把流式事件翻译回 Anthropic SSE。全部基于 307 行 Node.js 源码无第三方 HTTP 依赖。文末总结了 5 个实测踩过的坑。为什么需要这座桥先说清楚动机——不是为了用国产模型而是额度经济学。Claude Code 是目前最顺手的终端 AI 编程工具但直接用它只能消耗 Anthropic 官方套餐。而把它的请求桥接到 ZCode 的 Coding Max 套餐通道后请求按 ZCode 的额度扣率计费——以我手上的套餐实测抵扣系数约 0.67同样额度的有效用量约等于放大到 1.5 倍具体扣率以官方当前政策为准。顺带的收益是模型本身也换成了 GLM 系列对日常编码场景完全够用。但两者协议完全不同Anthropic 侧一次性 HTTP POST回来的是message_start → content_block_delta* → message_stop这套 SSE 事件ZCode 侧要先session/create建会话session/send发内容然后从事件流里订阅model.streaming、turn.completed等事件。缺的就是中间的翻译层。先交代两个名词ACPAgent Client ProtocolAgent 客户端协议是一套让编码 Agent 前端与后端模型服务解耦的 JSON-RPC 协议zcode-acp-server是它的 Node 实现ZCode 的 headless 模式叫app-server本文的桥就是Anthropic Messages 协议 → ACP 后端的翻译官。核心思路一句话本地起一个只听 127.0.0.1 的 HTTP 服务把 Anthropic 协议的请求翻给 ZCode把 ZCode 的事件流翻回 Anthropic SSE。架构三段式Claude Code ──HTTP/SSE── 桥接服务(127.0.0.1:8080) ──JSON-RPC── zcode app-server │ ├─ 1. 收 /v1/messages压成 prompt 文本 ├─ 2. session/createmodeyolo subscribe ├─ 3. session/send 送入 prompt └─ 4. 轮询事件流 → 翻译成 Anthropic SSE桥接复用了现成的zcode-acp-server构建产物ZcodeBackend/EventStreamListener/ 凭证加载自己只写协议转换——这是它只有 307 行的原因。难点一app-server 会反向找你要东西最隐蔽的坑在这里session/create过程中app-server 会反过来向桥接发请求server→client 方向其中// zod .strict() 对象nativeSearchEnhancementsEnabled 必填 booleanb.sendReply(req.id,{nativeSearchEnhancementsEnabled:true,memoryEnabled:false,askUserQuestionAutoResolutionEnabled:true,});session/requestRuntimePreferences用的是 zod.strict()校验——少一个必填字段就直接报错而你不回复它session/create就永远挂起表现为一个干巴巴的 timeout日志里毫无线索。解法是一个 100ms 轮询的drainer排水泵后台持续pollServerRequests()把反向请求分类回复——运行时偏好回默认值interaction/requestPermission回allowyolo 模式免得工具调用卡住interaction/requestUserInput回declineheadless 场景没人能答题未知方法一律回空对象兜底。这个模式可以推广任何客户端SDK里嵌着服务端回调的协议对接时第一件事就是把回调通道接住否则主流程必然莫名超时。难点二两套流式协议的对齐Anthropic 的 SSE 是严格的事件序列少一环客户端就报错message_start → content_block_start → content_block_delta* → content_block_stop → message_delta(含 stop_reason 和 usage) → message_stop而 ZCode 侧给的是model.streamingpayload.kindtext_deltaturn.completed/failed。翻译循环的关键细节先发头再发送session/send之前就先把message_start/content_block_start写出去——否则可能丢掉早到的turn.completed轮询 心跳pollEvent(1000)循环里维护lastProgress超过TURN_TIMEOUT_MS默认 180 秒没进展就按超时收尾stop_reason 映射turn.completed → end_turn超时 →max_turn_requeststurn.failed→ 把错误文本塞进 delta 再正常收尾客户端不会因异常断流usage 编造ZCode 不回 token 数用output_chars / 4估算——SSE 里 usage 字段必须有数值不准但协议合法。难点三多轮历史的降维Anthropic 请求里是结构化的messages[]可能还带 tool_use/tool_result 块而 ZCode 每次create都是全新 session。桥接的处理是把历史压平成一段文本functionbuildPrompt(request){// [system]\n... \n\n[user]\n... \n\n[assistant]\n...}文本块类型的 content 直接拼接非文本块图片等丢弃。这是有损压缩但对用 Claude Code 干活这个场景够用——因为真正需要长上下文的是 ZCode 自己的 session 内工具调用历史 prompt 只是开场白。五个实测坑都是拿报错换来的ANTHROPIC_BASE_URL千万别带/v1后缀。Claude Code 会自动拼/v1/messages你写成…:8080/v1就变成/v1/v1/messages→ 404。写http://127.0.0.1:8080就好Node 必须 ≥22底层zcode.cjs依赖内置的node:sqlite低版本直接起不来只监听 127.0.0.1 并校验 remoteAddress桥接是本地代理性质对外开放等于把你的模型配额变成公共 APIZCode 重启后必须重启桥接桥接 fork 的 app-server 子进程会跟着断而且平台刷新后的 provider key 也要重新读取modeyolo的边界工具权限自动放行必须配 workspace 白名单桥接日志里明确打了警告——自动化的代价是把安全决策交给作用域限制别把 workspace 设成整个家目录。收尾做完这个桥的最大体会AI 工具的互操作性问题本质是事件流方向和状态机生命周期两类问题。反向请求要有人接方向问题session 要有人管生命周期问题——这两件事解决了剩下的只是字段名对齐的体力活。完整源码 307 行欢迎在评论区交流你对接过的其他 AI 工具协议。