一次意外的礼物:Claude Code 源码里藏着什么秘密?

2026年3月31日 ,Anthropic 的 npm 包因一个 .map 文件配置失误,将 Claude Code 完整的 TypeScript 源码暴露在公网上。约 1,900 个文件、超过 51 万行代码,就这样向所有人敞开了大门。

这篇文章不讨论法律与道德边界------那是另一个话题。我们只谈:这份源码里,藏着一个怎样精心设计的 AI Agent 系统。


目录

  1. 系统全貌:它到底是什么?
  2. [入口:一切从 main.tsx 开始](#入口:一切从 main.tsx 开始 "#2-%E5%85%A5%E5%8F%A3%E4%B8%80%E5%88%87%E4%BB%8E-maintsx-%E5%BC%80%E5%A7%8B")
  3. [核心引擎:query.ts 的 Agent 循环](#核心引擎:query.ts 的 Agent 循环 "#3-%E6%A0%B8%E5%BF%83%E5%BC%95%E6%93%8E-queryts-%E7%9A%84-agent-%E5%BE%AA%E7%8E%AF")
  4. 上下文管理:五层过滤的精妙设计
  5. [工具系统:Claude 的"手"](#工具系统:Claude 的"手" "#5-%E5%B7%A5%E5%85%B7%E7%B3%BB%E7%BB%9Fclaude-%E7%9A%84%E6%89%8B")
  6. 容错机制:为生产环境而生的防御
  7. [多智能体:协调器与子 Agent](#多智能体:协调器与子 Agent "#7-%E5%A4%9A%E6%99%BA%E8%83%BD%E4%BD%93%E5%8D%8F%E8%B0%83%E5%99%A8%E4%B8%8E%E5%AD%90-agent")
  8. 工程洞察:值得学习的设计决策
  9. 结语

1. 系统全貌:它到底是什么?

Claude Code 是 Anthropic 官方出品的终端 AI 编程助手 CLI 。不同于简单的补全工具,它是一个完整的 Agentic System,能够:

  • 读写文件、搜索代码库、执行 Shell 命令
  • 管理 Git、创建 PR、审查代码
  • 通过 MCP(Model Context Protocol)连接外部服务
  • 协调多个子 Agent 并行完成复杂任务

技术栈一览:

层次 技术选型
运行时 Bun(取代 Node.js,启动更快)
语言 TypeScript(严格类型,全覆盖)
终端 UI React + Ink(React 渲染到 terminal)
AI SDK @anthropic-ai/sdk(流式 API)
协议层 MCP SDK(工具/资源的统一接入)
Schema Zod(运行时类型校验)
功能开关 GrowthBook + Bun bundle feature flags

代码规模与复杂度超出了大多数人对"一个 CLI 工具"的想象。


2. 入口:一切从 main.tsx 开始

src/main.tsx 的前 20 行就告诉你这个团队极其在意启动性能

typescript 复制代码
// 必须在所有 import 之前执行的副作用:
import { profileCheckpoint } from './utils/startupProfiler.js'
profileCheckpoint('main_tsx_entry')  // ← 第一行就开始计时

import { startMdmRawRead } from './utils/settings/mdm/rawRead.js'
startMdmRawRead()  // ← 并行启动 MDM subprocess(plutil/reg query)

import { startKeychainPrefetch } from './utils/secureStorage/keychainPrefetch.js'
startKeychainPrefetch()  // ← 并行预取 Keychain(OAuth + API Key)

三件事并行触发,都通过注释精确说明了原因:keychain 预取能节省约 65ms 的启动时间。这种对毫秒级延迟的关注,贯穿整个代码库。

启动阶段的主要工作:

  1. 身份认证:OAuth 令牌校验、Bedrock/Vertex 凭证预取
  2. 信任检查checkHasTrustDialogAccepted() 确保用户同意条款
  3. 配置加载:MDM 托管配置、远程策略限制、Feature Flag 初始化
  4. 工具注册getTools() 按 feature flag 动态组装工具集
  5. MCP 初始化:预取官方 MCP Server 列表
  6. REPL 启动launchRepl() 进入交互主循环

3. 核心引擎:query.ts 的 Agent 循环

这是整个系统最精华的部分------src/query.ts,约 1,750 行

它的核心是一个 while(true) 无限循环,每次迭代代表一个"轮次(turn)"。用 ASCII 图描述:

objectivec 复制代码
用户输入
   │
   ▼
┌─────────────────────────────────────────────────┐
│                while(true) 主循环               │
│                                                 │
│  ① 上下文预处理(5层压缩/过滤)                  │
│         │                                       │
│  ② 调用 Claude API(流式)                       │
│         │                                       │
│  ③ 实时处理流消息                                │
│         │                                       │
│   needsFollowUp?                                │
│   ├─ NO → ④ 错误恢复 / Stop Hooks / 结束        │
│   └─ YES → ⑤ 执行工具                           │
│                  │                              │
│             ⑥ 构建下一轮消息 → 继续循环          │
└─────────────────────────────────────────────────┘

3.1 状态机设计

循环的状态通过一个不可变的 State 对象在迭代间传递:

typescript 复制代码
type State = {
  messages: Message[]            // 完整对话历史
  toolUseContext: ToolUseContext  // 工具执行上下文
  turnCount: number              // 轮次计数
  maxOutputTokensRecoveryCount: number  // 截断恢复次数
  hasAttemptedReactiveCompact: boolean  // 防止 compact 死循环
  transition: Continue | undefined      // 调试:上一次为何 continue
  // ...
}

transition 字段尤其值得关注------它记录了每次 continue 的原因,如 'next_turn''max_output_tokens_recovery''reactive_compact_retry' 等。这使得测试可以断言具体的恢复路径触发了,而不必检查消息内容。

3.2 流式处理与消息收集

API 以 async generator 形式流式返回消息,循环实时处理每一块:

typescript 复制代码
for await (const message of deps.callModel({ messages, systemPrompt, ... })) {
  // 1. 处理 fallback(模型降级)
  // 2. 将 tool_use 的 input 字段 backfill 给 SDK 消费者
  // 3. 判断是否要缓留(withheld)这条消息
  // 4. yield 或暂存
  // 5. 提取 tool_use blocks,设置 needsFollowUp = true
}

"缓留"机制 是一个精妙的设计:三类可恢复的错误消息(Prompt-too-long、Media 超限、Max-output-tokens)在流式阶段被暂不传递给调用方 ,等待恢复逻辑判断,成功则悄无声息地重试,失败才将错误暴露出去。这避免了 SDK 消费者(如 desktop 客户端)在看到 error 字段时立即终止会话。


4. 上下文管理:五层过滤的精妙设计

LLM 的致命弱点是有限的上下文窗口。Claude Code 用五层递进的机制来管理这个问题,每层都在 API 调用前依次执行:

lua 复制代码
原始消息列表
     │
     ▼ 层1: Tool Result 大小限制
     │      applyToolResultBudget() --- 截断超大工具返回值
     │      持久化替换记录,/resume 时可恢复
     │
     ▼ 层2: Snip 压缩 [HISTORY_SNIP feature]
     │      snipCompactIfNeeded() --- 按策略删除旧消息
     │      snipTokensFreed 传递给下层,避免误判
     │
     ▼ 层3: Microcompact
     │      微型压缩 --- 合并重复工具调用/结果
     │      CACHED_MICROCOMPACT: 延迟 boundary 消息
     │      以获取真实 cache_deleted_input_tokens
     │
     ▼ 层4: Context Collapse [CONTEXT_COLLAPSE feature]
     │      折叠历史 --- Read-time projection
     │      折叠记录存 store,不修改 REPL 数组
     │      保证 /resume 后折叠状态持久
     │
     ▼ 层5: Auto Compact
           触发阈值时 fork 子 Claude 对历史做摘要
           成功: 用摘要替换历史,继续当前 turn
           失败: 记录 consecutiveFailures,断路器保护
           ↓
     发送给 API 的消息

层次顺序的设计意图:

  • Collapse 在 AutoCompact 之前:如果 collapse 把 token 数降到阈值以下,autocompact 就无需触发,保留细粒度历史
  • Snip 在 Microcompact 之前:snip 释放的 token 数必须传给 autocompact,否则基于旧 usage 数据会误触发
  • Tool Result 限制最先执行:microcompact 只认 tool_use_id,不检查内容,两者相互独立

task_budget 的跨 compact 追踪

这是一个极度细节的设计。Anthropic API 支持 task_budget(任务预算),服务端通过计数 context window 来追踪消耗。但 compact 之后,服务端只看到摘要,会低估历史消耗

解决方案:在每次 compact 触发前,客户端快照当前的 finalContextTokensFromLastResponse,累加到 taskBudgetRemaining,下次请求时通过 remaining 字段告知服务端正确的剩余预算


5. 工具系统:Claude 的"手"

工具系统是 Claude Code 能力边界的直接体现。所有工具实现 Tool 接口,通过 Zod schema 声明输入类型。

5.1 工具全景

arduino 复制代码
核心文件操作
├── FileReadTool      --- 读文件,支持行范围
├── FileEditTool      --- 精确字符串替换(必须唯一匹配)
├── FileWriteTool     --- 全覆盖写入
└── GlobTool          --- 模式匹配文件搜索

代码搜索
├── GrepTool          --- 正则文本搜索
└── LSPTool           --- 基于 LSP 的语义搜索/跳转

Shell 执行
├── BashTool          --- Unix Shell 命令
└── PowerShellTool    --- Windows PowerShell

智能体工具
├── AgentTool         --- 启动子 Agent(多 Agent 协作)
├── SkillTool         --- 执行预定义工作流 Skill
└── TodoWriteTool     --- 进度追踪与任务管理

Web 能力
├── WebFetchTool      --- HTTP 请求/页面抓取
└── WebSearchTool     --- 搜索引擎查询

MCP 集成
├── MCPTool           --- 调用任意 MCP Server 工具
├── ListMcpResourcesTool
└── ReadMcpResourceTool

任务调度(feature-gated)
├── ScheduleCronTool  --- 定时任务(AGENT_TRIGGERS)
├── SleepTool         --- 等待/延迟(PROACTIVE/KAIROS)
└── MonitorTool       --- 后台监控(MONITOR_TOOL)

5.2 StreamingToolExecutor:并发工具执行

传统 Agent Loop 是串行 的:模型流结束 → 执行所有工具 → 下一轮。Claude Code 实现了 StreamingToolExecutor,在模型仍在流式输出时就开始执行已确认的工具调用:

ini 复制代码
模型流式输出:  [text...] [tool_use: BashTool] [text...] [tool_use: GlobTool]
                                    │                              │
StreamingToolExecutor:        addTool() 立即开始执行         addTool() 立即开始执行
                                    │                              │
流结束后:              getRemainingResults() 收割所有完成的结果

这将"等待工具执行"的时间隐藏在了"等待模型输出"的时间里,显著降低延迟

5.3 工具的权限模型

工具执行前必须通过 canUseTool() 检查。权限层级:

  • plan 模式:只读工具,不允许写操作或命令执行
  • default 模式:需要用户确认危险操作
  • auto 模式(bypassPermissions):自动批准,适合 CI/CD 场景

权限系统还内置了 DenialTrackingState------记录用户拒绝了哪些操作,避免重复询问。


6. 容错机制:为生产环境而生的防御

生产级系统的标志是当事情出错时的表现。Claude Code 有完善的错误恢复链:

6.1 模型降级(Fallback)

当主模型因高负载返回 FallbackTriggeredError 时:

typescript 复制代码
if (innerError instanceof FallbackTriggeredError && fallbackModel) {
  currentModel = fallbackModel
  attemptWithFallback = true

  // 1. Tombstone 孤立的 assistant 消息(thinking block 有签名,不能跨模型复用)
  for (const msg of assistantMessages) {
    yield { type: 'tombstone', message: msg }
  }

  // 2. 清空状态,创建新的 StreamingToolExecutor
  // 3. 剥离 signature blocks(thinking block 是模型绑定的)
  // 4. 告知用户:已切换到 ${fallbackModel}(warning 级别)
  // 5. 重试
}

Tombstone 消息是一个有趣的设计:UI 层看到 tombstone 后,会从界面和 transcript 中移除对应消息,保证视觉一致性。

6.2 max_output_tokens 恢复(三层递进)

Claude 有时会在任务未完成时达到输出 token 上限。系统按顺序尝试三种恢复:

第1层:Token 升级

typescript 复制代码
// 如果用户没有显式设置上限,先尝试用 64k 重试同一请求
if (capEnabled && maxOutputTokensOverride === undefined) {
  state = { ..., maxOutputTokensOverride: ESCALATED_MAX_TOKENS }
  continue  // 重试,不告知用户
}

第2层:截断续写(最多 3 次)

typescript 复制代码
const recoveryMessage = createUserMessage({
  content: `Output token limit hit. Resume directly --- no apology, no recap. ` +
           `Pick up mid-thought if that is where the cut happened.`,
  isMeta: true,  // 隐藏消息,不显示给用户
})
state = { messages: [..., recoveryMessage], maxOutputTokensRecoveryCount: count + 1 }
continue

第3层:放弃,暴露错误

"no apology, no recap" 这句续写提示写得极妙------精确地抑制了模型在续写时的常见病:长篇大论地解释"刚才发生了什么"。

6.3 Prompt-too-long 恢复(三层递进)

当 context 过长导致 API 返回 413 时,也有分层恢复:

  1. Context Collapse Drain:提交暂存的折叠,便宜操作,先试
  2. Reactive Compact :被动触发全量摘要(hasAttemptedReactiveCompact 防止死循环)
  3. 暴露错误 ,调用 executeStopFailureHooks

关键细节:这两种错误消息在流式阶段都被缓留(withheld),恢复后悄悄重试。只有恢复失败才向调用方暴露错误。这意味着用户在大多数情况下看不到中间态的错误------系统静默地解决了问题。


7. 多智能体:协调器与子 Agent

Claude Code 支持多 Agent 协作模式,架构设计颇为精妙。

7.1 AgentTool:递归的 Agent

AgentTool 允许 Claude 启动一个完整的子 Agent,子 Agent 又可以继续调用工具甚至再启动孙 Agent:

scss 复制代码
主 Agent (query.ts)
    └─ AgentTool → 子 Agent (独立 query loop)
                        └─ AgentTool → 孙 Agent

每个子 Agent 有独立的 agentId、独立的 abort 控制器、独立的工具使用上下文。消息队列通过 agentId 作用域隔离:

  • 主线程 drain agentId === undefined 的消息
  • 子 Agent drain 自己 agentId 的消息
  • 用户 prompt 只流向主线程

7.2 Team 模式(实验性)

从工具列表可以看到 TeamCreateToolTeamDeleteTool,结合 coordinatorMode.ts,这是一个多 Agent 并行执行的协调器模式

  • Coordinator 负责任务分解和调度
  • 多个 Worker Agent 并行执行子任务
  • 结果汇总回 Coordinator

这是一个仍在 feature flag 保护下的实验性功能,但架构雏形已经清晰可见。

7.3 Buddy 系统(有趣的彩蛋)

src/buddy/ 目录里有一个 CompanionSprite.tsxsprites.ts------这是 Claude Code 的"宠物"功能,一个可以在终端中显示的像素风格伙伴角色,会在某些操作时显示动画。这个功能完全不影响核心功能,却体现了团队对产品体验的用心。


8. 工程洞察:值得学习的设计决策

通读源码后,有几个设计决策值得单独拎出来讨论。

8.1 AsyncGenerator 作为核心抽象

整个 query 函数是一个 async function*(async generator):

typescript 复制代码
export async function* query(params: QueryParams):
  AsyncGenerator<StreamEvent | Message | ..., Terminal>

这个选择非常巧妙:

  • 自然流式传输 :每个 yield 立即推送给调用方,无需额外缓冲
  • 背压(Backpressure):调用方消费速度控制生产速度
  • 资源清理using pendingMemoryPrefetch = ...,无论正常退出、throw 还是 .return() 都能触发 dispose
  • 组合性yield* 自然委托给子 generator,如 queryLoop

返回类型 Terminal 提供了丰富的退出原因枚举:completedaborted_streamingmax_turnsblocking_limitprompt_too_longstop_hook_prevented 等,调用方可以精确分支处理。

8.2 Feature Flag 的双轨制

代码里同时使用两套 feature flag 系统:

typescript 复制代码
// 运行时 A/B 测试(GrowthBook)
const value = getFeatureValue_CACHED_MAY_BE_STALE('tengu_otk_slot_v1', false)

// 构建时消除(Bun bundle)
if (feature('CONTEXT_COLLAPSE')) {
  // 此块在 feature 关闭的 build 中被完全删除
}

_CACHED_MAY_BE_STALE 后缀是一个强制的命名约定------提醒开发者这个值在流式处理的 5-30 秒内可能翻转,如果用在"缓留"和"恢复"两处必须保证一致性。mediaRecoveryEnabled 在循环入口处快照一次,正是基于这个考虑。

8.3 "不可变参数 + 可变 State" 的循环设计

typescript 复制代码
// 一次性解构,循环内永不重赋值
const { systemPrompt, maxTurns, querySource } = params

// 可变 State,每次 continue 整体替换
let state: State = { ... }
while (true) {
  const { messages, turnCount, ... } = state
  // ...
  state = { ...next }  // 整体替换,7 个 continue 点一致
}

这种设计的优点:

  • 意图清晰:看到 const 就知道这个值绝不会变
  • 避免竟态:每次迭代都是从 state 全量读取,不存在"某个字段没更新"的 bug
  • 易于 debug:transition 字段记录了每次 continue 的原因,测试可验证恢复路径

8.4 工具使用摘要的延迟消费

typescript 复制代码
// 工具执行完毕,异步启动摘要生成(Haiku,~1s)
const nextPendingToolUseSummary = generateToolUseSummary(...)

// 在下一轮 --------- 流结束后 --------- 才消费(流 5-30s,摘要 1s)
if (pendingToolUseSummary) {
  const summary = await pendingToolUseSummary  // 此时几乎已完成,无需等待
  yield summary
}

这是经典的"异步计算隐藏延迟"技巧:把慢操作(即使只有 1s)藏在另一个慢操作(5-30s 的模型流)背后,用户几乎感知不到额外等待。

8.5 Memory Prefetch 的 using 析构

typescript 复制代码
using pendingMemoryPrefetch = startRelevantMemoryPrefetch(
  state.messages,
  state.toolUseContext,
)

using 关键字(TC39 显式资源管理提案,TypeScript 5.2 起支持)确保 pendingMemoryPrefetch[Symbol.dispose]() 在所有退出路径(正常返回、throw、generator .return())上都会被调用,即使忘记 try-finally 也没问题。


9. 结语

通读这份源码,最深刻的感受不是"Claude Code 好厉害",而是:

一个真实的、面向生产的 AI Agent 系统,需要解决多少工程问题。

  • 上下文窗口管理不是一个算法,而是 5 层递进的工程系统
  • 错误恢复不是 try-catch,而是精心设计的状态机迁移
  • 流式处理不是 await response.text(),而是涉及背压、缓留、并发执行的复杂管道
  • 工具系统不是函数数组,而是带权限模型、并发执行、结果预算的完整框架

Anthropic 在 Claude Code 上的实践,代表了当前 AI Agent 工程的最高水位线之一。无论你是在构建自己的 Agent 系统,还是只是想理解这类系统的设计边界,这份意外暴露的源码都是难得的一手学习材料。


本文基于 2026-03-31 公开曝光的 Claude Code 源码快照(npm 包 source map 泄露)进行分析,仅用于技术学习与架构研究。文中所有代码引用均来自该公开快照。

如果你对某个具体模块有更深入的问题,欢迎在评论区讨论。

相关推荐
前端冒菜师2 小时前
从 Prompt 到 Token,再到你自己
ai编程
学到头秃的suhian2 小时前
springai Alibaba(中)
ai编程
OpenTiny社区2 小时前
GenUI SDK v1.1.0 正式发布|全端体验革新,能力与稳定性进阶
前端·ai编程
超爱柠檬2 小时前
LangGraph 多智能体协作系统
openai·agent·ai编程
花千树-0103 小时前
5分钟用 Java 构建你的第一个 AI 应用
java·人工智能·spring boot·langchain·aigc·ai编程
与虾牵手3 小时前
OpenClaw vs Cursor vs Claude Code:2026 AI 编程插件实测,哪个真能提效?
ai编程·claude
DanCheOo4 小时前
多模型适配:一套代码接 6 家 AI 厂商
前端·ai编程
前端冒菜师4 小时前
记一次AI全栈开发的过程
前端·ai编程
孟健4 小时前
Claude Code 源码泄露后,我反而更确定:终端 Agent 只该接 3 类活
ai编程