☰
逐行读懂deepclaude model-proxy源码:如何拦截并归一化Claude API的SSE流
2026/10/7 3:16:04 网站建设 项目流程

逐行读懂deepclaude model-proxy源码:如何拦截并归一化Claude API的SSE流

【免费下载链接】deepclaudeUse Claude Code's autonomous agent loop with DeepSeek V4 Pro, OpenRouter, or any Anthropic-compatible backend. Same UX, 17x cheaper.项目地址: https://gitcode.com/gh_mirrors/deepc/deepclaude

本文带你逐行读懂deepclaude 的 model-proxy 源码:这个本地模型代理如何拦截 Claude Code 发出的所有模型 API 请求,并把 DeepSeek 返回的SSE 流归一化成 Claude Code 能安全消费的标准格式。deepclaude 让你继续用 Claude Code 的自主智能体循环,只需把"大脑"换成 DeepSeek V4 Pro、OpenRouter 或任意 Anthropic 兼容后端——同样的体验,成本最多便宜 17 倍。

🧠 deepclaude 是什么:保留 Claude Code 的"身体",只换"大脑"

Claude Code 是目前最强的自主编码智能体之一,但 Max 订阅每月 $200 且有用量上限。deepclaude 的思路非常简单:不改 CLI 本身,只把 API 调用的目的地换掉——文件读写、bash 执行、git 操作、子代理全部照常工作,唯一的区别是负责"思考"的模型变成了 DeepSeek V4 Pro(输出价低至 $0.87/M tokens,而 Anthropic 是 $15/M)。

用deepclaude --remote启动后,你可以在浏览器甚至手机上打开一个 Claude Code 会话,deepclaude 会自动在本地拉起代理:模型调用被路由到 DeepSeek,而桥接认证仍走 Anthropic。

🗺️ 整体架构:所有请求先过 localhost:3200

整个方案的枢纽是一个跑在127.0.0.1:3200的轻量 HTTP 代理(入口见 proxy/start-proxy.js,核心实现在 proxy/model-proxy.js):

Claude Code └── 所有模型请求 → http://localhost:3200(model-proxy) ├── /v1/messages → DeepSeek / OpenRouter / Fireworks(便宜大脑) ├── /_proxy/mode → 会话中途热切换后端 ├── /_proxy/status、/_proxy/cost → 状态与成本统计 └── 其他一切 → api.anthropic.com(透明透传)

之所以必须用代理而不是直接改环境变量,是因为 Claude Code 的远程控制有两条独立通道:桥接 WebSocket(硬编码指向 Anthropic,必须用官方 OAuth)和模型 HTTP 调用(可配置)。把 API Key 直接换成 DeepSeek 会弄坏桥接,而代理可以"劈开"这两股流量——详细说明见 proxy/README.md。

源码结构一张表

模块位置职责
MODEL_REMAPproxy/model-proxy.jsClaude 模型名 → 各后端模型名映射
PRICING_PER_Mproxy/model-proxy.js各后端每百万 token 单价表
UsageNormalizerproxy/model-proxy.js⭐ SSE 流归一化核心类
normalizeJsonBodyproxy/model-proxy.js非流式 JSON 响应补齐 usage
stripAllThinkingBlocksproxy/model-proxy.js剥离 thinking 内容块
startModelProxyproxy/model-proxy.js入口:HTTP 服务 + 路由 + 成本统计

🪤 拦截层:请求路由与"身份替换"

代理服务创建后(proxy/model-proxy.js),每个进来的请求先做路径判断:

  1. 控制端点/_proxy/*:走本地处理逻辑(下一节讲),绝不转发。
  2. 模型调用:只有路径精确匹配MODEL_PATHS里的/v1/messages(proxy/model-proxy.js),且当前不在 anthropic 模式时,才转发到便宜后端。
  3. 其余流量:透明透传回 Anthropic,Claude Code 完全无感。

转发前,代理对请求做了三处"手术":

  • 换身份(proxy/model-proxy.js):删掉客户端自带的authorization/x-api-key头,再按后端口味注入新密钥——OpenRouter、Fireworks 用Bearer令牌,DeepSeek 兼容端点用x-api-key。
  • 换模型名(proxy/model-proxy.js):请求体里的claude-opus-4-6会被MODEL_REMAP映射成deepseek-v4-pro,Opus 级任务落到 Pro 模型,Sonnet/Haiku 级任务落到轻量的deepseek-v4-flash。
  • 清理 thinking 块(proxy/model-proxy.js):Claude Code 会在多轮对话中回传上一轮的"思考块",但第三方后端会拒绝生成它的思考块,直接全部剥掉;切回 Anthropic 后若会话里混过其他后端,也要剥掉(否则触发 400 错误)。

还有一个容易忽略的细节:路径前缀去重(proxy/model-proxy.js)。OpenRouter 的基址是/api/v1,拼上客户端的/v1/messages会变成/api/v1/v1/messages,代理会计算两段路径的重叠前缀再拼接,避免这类拼接事故。

🌊 SSE 流归一化:全文最关键的一段

这是本项目的灵魂问题:DeepSeek/OpenRouter 的兼容端点有时会在message_start或message_delta事件里省略usage字段,而 Claude Code 直接按$.input_tokens取值——字段不存在就崩溃。

解决方案是一个继承自 Node.jsTransform的流:UsageNormalizer(proxy/model-proxy.js)。

第一步:按 SSE 事件切块(_transform)

SSE 协议用空行(\n\n)分隔事件,但 TCP 分块(chunk)边界是任意的——一个事件可能被拦腰截断。所以它先做缓冲(proxy/model-proxy.js):

_transform(chunk, _enc, cb) { this._buf += chunk.toString(); const parts = this._buf.split('\n\n'); this._buf = parts.pop(); // 最后一段可能不完整,留到下一轮 for (const part of parts) { this.push(this._fix(part) + '\n\n'); } cb(); }

完整的事件修好后立即push给下游,边收边发,不缓存整个响应——对长对话的流式输出至关重要。

第二步:补齐缺失字段(_fix)

对每个完整事件(proxy/model-proxy.js),先用正则/^data: (.+)$/m取出数据行并解析 JSON,然后做两处"填空":

  • message_start事件缺message.usage→ 注入{ input_tokens: 0, output_tokens: 0 };
  • message_delta事件缺usage→ 注入{ output_tokens: 0 }。

只有真的改过才重新序列化,否则原样放行——零开销透传。同时它顺手记录inputTokens/outputTokens,为成本统计提供数据。

第三步:收尾回调(_flush)

流结束时(proxy/model-proxy.js),处理缓冲区里最后一个不完整事件,并触发onUsage回调把本轮 token 数交给recordUsage记账。

流式与非流式两条路径

代理根据上游响应的content-type分流(proxy/model-proxy.js):

  • text/event-stream→ 管道接上UsageNormalizer再交给客户端;
  • application/json(非流式)→ 整体缓冲后用normalizeJsonBody补齐usage字段再一次性返回(proxy/model-proxy.js);
  • 其他情况 → 原样透传。

🔄 控制端点:/_proxy/mode 实现会话中途热切换

代理还内置了三个控制端点(proxy/model-proxy.js),前缀/_proxy/保证永远不会和模型路径/v1/*冲突:

  • GET /_proxy/status:当前模式、运行时长、请求数;
  • GET /_proxy/cost:token 用量与省钱金额(下一节);
  • POST /_proxy/mode:热切换后端,无需重启。

/_proxy/mode做了三重防护:校验Origin只允许本机来源(防跨站请求篡改)、请求体限 1KB(超尺寸直接销毁连接)、非 POST 一律 405。切换动作由switchMode(proxy/model-proxy.js)完成——只需更新内存里的target/apiKey/useBearer三个状态字段,下一条请求就立刻走新后端。

在 Claude Code 里添加自定义斜杠命令后,输入/deepseek就能完成切换(命令本质是一条curl调用该端点):

Claude Code 终端中 /deepseek 命令调用 model-proxy 的 /_proxy/mode 端点切换后端

同样在 VS Code 扩展里,/openrouter与/deepseek可以来回切,状态栏的模型名实时变化:

VS Code 扩展中 model-proxy 从 OpenRouter 热切换到 DeepSeek 后端

📊 成本追踪:内置的"省钱计算器"

每一次归一化流都会留下 token 记账(proxy/model-proxy.js),getCostSummary(proxy/model-proxy.js)再按PRICING_PER_M单价表算出:实际花费、等价的 Anthropic 花费、以及差值savings。

curl -s http://127.0.0.1:3200/_proxy/cost # → { "total_cost": 0.0941, "anthropic_equivalent": 1.05, "savings": 0.9559, ... }

配合 DeepSeek 的自动上下文缓存(重复轮次命中缓存后输入价降至 $0.004/M),长会话的 agent 循环成本几乎可以忽略——这是"17x cheaper"的直接来源。

🛡️ 健壮性细节:端口自增、超时与降级

最后几个小而实用的设计(proxy/model-proxy.js):

  • 端口自增:3200 被占用时自动尝试 3201~3220,避免和已有服务打架;
  • 5 分钟请求超时:agent 任务可能思考很久,超时给足;超时或上游报错时返回结构化的 502,而不是让客户端干等;
  • 全程可观测:每个请求打印序号、目标主机、TTFB(首字节时间)、耗时和 token 数,排查问题一目了然;
  • 最小依赖:整个代理只用 Node.js 内置的http/https/stream模块,零 npm 依赖。

✅ 总结

deepclaude 的 model-proxy 用约 450 行零依赖的 JavaScript,完成了四件事:按路径拦截模型请求、替换身份与模型名转发给便宜后端、用UsageNormalizer流式归一化 SSE 事件防崩溃、并提供热切换与成本统计端点。理解它的实现,也顺带掌握了"为不兼容的 API 写一层归一化代理"这一通用技巧:缓冲切块 → 逐事件修正 → 边收边发 → 记账回调。想动手改造时,建议从 proxy/model-proxy.js 的UsageNormalizer类读起,再对照 README.md 中的后端配置说明和 deepclaude.sh / deepclaude.ps1 启动脚本,即可在自己的后端上复刻这套架构。

【免费下载链接】deepclaudeUse Claude Code's autonomous agent loop with DeepSeek V4 Pro, OpenRouter, or any Anthropic-compatible backend. Same UX, 17x cheaper.项目地址: https://gitcode.com/gh_mirrors/deepc/deepclaude

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询