篇章二 · 2.3
API 层与看门狗
循环的第 2 步会走进 services/api/claude.ts——整个系统里离 Anthropic API 最近的地方。这里藏着两个反直觉的决定。
不用官方封装,自己处理裸流
Anthropic SDK 明明提供了 BetaMessageStream(官方流式封装),Claude Code 偏偏不用,选择手动处理 raw stream。源码注释给出了理由:
“Use raw stream instead of BetaMessageStream to avoid O(n²) partial JSON parsing. BetaMessageStream calls partialParse() on every input_json_delta, which we don’t need since we handle tool input accumulation ourselves.”
—— 源码注释,services/api/claude.ts翻译:官方封装每收到一个增量就对整块 JSON 做部分解析,数据量越大越慢(平方级);Claude Code 自己攒增量,只在块结束时解析一次——线性。
同一段代码,四种云
| 后端 | 开关(环境变量) | 客户端 |
|---|---|---|
| Anthropic 直连 | 默认 | new Anthropic(…) |
| AWS Bedrock | CLAUDE_CODE_USE_BEDROCK | AnthropicBedrock |
| Google Vertex | CLAUDE_CODE_USE_VERTEX | AnthropicVertex |
| Azure Foundry | CLAUDE_CODE_USE_FOUNDRY | AnthropicFoundry |
企业用什么云,一个环境变量切换——这决定了它能在各大厂内部落地。
手动消费 6 种 SSE 事件
message_start会话开始:初始化 usage,记录首字节时间(TTFB)
content_block_start开一个内容块:text / tool_use / thinking
content_block_delta增量追加(最频繁——性能敏感就在这)
content_block_stop块结束:组装成 AssistantMessage,yield 给上层
message_delta更新 usage 与 stop_reason
message_stop消息结束:记录成本
事件流(左:API 吐出的)
组装区(右:攒给 queryLoop 的)
等 message_start…
idle
streaming…(每个 chunk 重置计时)
真实参数:STREAM_IDLE_TIMEOUT_MS = 90,000(无 chunk 超时 abort);45 秒处(一半)打 warning 日志。超时后置
streamIdleAborted 标记并释放流资源——不是崩溃,是体面退出。tombstone · 流式失败时的「讣告」
流式中途失败会回退到非流式重发(超时 300s / 远程 120s)。但上层已经收到半截消息了——直接重发会导致 API 报错 thinking blocks cannot be modified。解法:重发前先 yield 一条墓碑消息:
{ type: 'tombstone', message } ← 上层见到墓碑,删掉此前那条半截消息 一句话:流式世界里「部分结果」是个脏状态,tombstone 负责把它撤销干净再重来。
本站要点:性能(拒绝 O(n²))、兼容(四云一码)、韧性(看门狗 + tombstone)——API 层三个关键词。下一站:请求失败了怎么办——退避公式、五种错误码对策、Fallback 降级链。