Claude Code 源码解析(一):整体架构与主链路
Claude Code 是 Anthropic 官方的终端 Coding Agent。本文基于 claude-code-main 源码快照,梳理其整体架构、主交互链路与 Message 类型系统,为后续深入 query 循环、Tool 实现、上下文压缩等专题打基础。
仓库概览
仓库约 1900 个文件,核心逻辑集中在 src/:
| 目录/文件 | 职责 |
|---|---|
entrypoints/cli.tsx → main.tsx | CLI 入口 |
screens/REPL.tsx | 主交互界面(Read-Eval-Print-Loop) |
query.ts / QueryEngine.ts | Agent 主循环(API → Tool → 再 API) |
utils/messages.ts | Message 构造、流式事件处理 |
services/api/claude.ts | Anthropic API 流式调用 |
tools/ | 各 Tool 实现(Bash、Edit、Agent 等) |
components/ | Ink 终端 UI 组件 |
types/message.js | Message 联合类型定义(快照中可能缺失,但被全仓库引用) |
启动链路:
1 | cli.tsx → main.tsx → replLauncher → App + REPL |
用户输入经 handlePromptSubmit → processUserInput → onQuery → query() 进入 Agent 循环。
REPL:一切交互的枢纽
REPL.tsx 是 Claude Code 的「大脑皮层」——负责 UI 渲染、用户输入、状态管理,以及把用户动作桥接到无 UI 的 query/tool 层。
关键函数关系:
1 | handlePromptSubmit |
onQuery 与 onQueryImpl
onQuery(约 2855 行):对外入口。先把newMessagesappend 到 state,再通过queryGuard防止并发 query;委托onQueryImpl执行。onQueryImpl(约 2661 行):真正跑 query 前的准备:- 写入 slash-command 的
allowedTools到 store - 调用
getToolUseContext组装上下文 - 拉取
systemPrompt/userContext/systemContext - 调用
query()并用onQueryEvent消费流式事件
- 写入 slash-command 的
onQueryEvent → handleMessageFromStream
onQueryEvent(约 2584 行)是薄封装,核心逻辑在 utils/messages.ts 的 handleMessageFromStream(约 2930 行):
stream_event:更新streamingText、streamingToolUses、spinner 状态,不落库- 完整 message(
assistant/user/system等):onMessage写入 messages 列表 tombstone:删除流式 fallback 产生的半条 assistant messagestream_request_start:切换 spinner 为requesting
Agent 主循环:query() / queryLoop
query.ts 中的 query() 是一个 async generator,实现经典的 Agent 循环:
1 | while (needsFollowUp) { |
每一步通过 yield 向外推送 Message 或 stream_event,REPL 的 for await 消费这些事件更新 UI。
上下文管理(三步 + 兜底)
在每次 API 调用前,query 会对 messages 做阶梯式压缩:
- Snip:删除整条 message(如过长的 tool 结果)
- Microcompact:清空 tool 结果内容 / cache edit 标记
- Context collapse:对 span 做摘要,UI 与 API 各持一份视图
- Autocompact(兜底):整段对话 compact,插入 compact boundary
queryCheckpoint
utils/queryProfiler.ts 中的性能 profiling 标记。设置环境变量 CLAUDE_CODE_PROFILE_QUERY=1 启用,用于分析 query 各阶段耗时。
systemPrompt 与 systemContext 的区别
| 概念 | 来源 | 作用 |
|---|---|---|
systemPrompt | constants/prompts.ts → getSystemPrompt() | 给模型的指令(你是谁、怎么用工具等) |
systemContext | context.ts → getSystemContext() | 环境信息(git 状态、目录结构等),append 到 system |
userContext | context.ts → getUserContext() | CLAUDE.md 等用户规则,走 messages 而非 system |
三者最终在 onQueryImpl 中组装,传入 query()。
Message 类型系统
定义位置
Message 类型在 src/types/message.js 中定义(TypeScript 联合类型)。全仓库大量 import type { Message, UserMessage, AssistantMessage, ... } from '../types/message.js'。
对外 SDK 侧还有平行定义:src/entrypoints/sdk/coreSchemas.ts 的 SDKMessageSchema(type: z.literal('assistant') / 'user' 等)。
核心结论:Message.type 不是 LLM 输出的
LLM/API 给的是 role + content blocks;Claude Code 在代码里推断/构造成带 type 的 Message 联合类型,再写入 REPL state 和 transcript。
Message.type | 来源 | 说明 |
|---|---|---|
assistant | 代码 | 收到 API 响应/流式 block 完成后包装 |
user | 代码 | 真人输入、tool_result、meta 系统消息等 |
attachment | 纯代码 | IDE 上下文、queued command、memory 等 |
progress | 纯代码 | tool 执行进度(Bash/Sleep/Agent) |
system | 纯代码 | 警告、compact 边界、bridge 状态等 |
stream_event | 纯代码 | API SSE 中间事件,不是最终 message |
tombstone | 纯代码 | 流式 fallback 时标记删除半条 assistant |
两层 type 不要混淆
| 层级 | 字段 | 来源 |
|---|---|---|
| 外层 envelope | Message.type = 'user' / 'assistant' / 'system' … | Claude Code 代码 |
| API message | message.message.type = 'message' | Anthropic SDK 的 BetaMessage |
| Content block | content[].type = 'text' / 'tool_use' / 'thinking' … | LLM/API 生成 |
构造示例
assistant — 流式 API 在 content_block_stop 时包装(services/api/claude.ts):
1 | const m: AssistantMessage = { |
user — createUserMessage()(utils/messages.ts):
1 | const m: UserMessage = { |
stream_event — 流式中间态,不落库:
1 | yield { type: 'stream_event', event: part, ttftMs } |
assistant ≠ thinking
一条 type: 'assistant' 的 message 可包含多种 content block:text、thinking、tool_use 等。thinking 是 block 级别类型,不是 message 级别。
UI 渲染在 components/Message.tsx 中按外层 message.type 分支,再按 content block 类型细分组件。
getToolUseContext:上下文工厂
REPL.tsx 约 2392 行的 getToolUseContext 是 REPL 的上下文工厂函数——每次跑 query、执行 slash command、弹权限对话框时,都调用它组装 ProcessUserInputContext,把「当前会话状态 + 可调用的能力 + UI 回调」打包传给下游。
签名
1 | getToolUseContext( |
返回类型 ProcessUserInputContext = ToolUseContext & LocalJSXCommandContext,贯穿 processUserInput → query() → runTools → slash commands / 权限 UI。
核心设计:从 store 读最新状态
不从 useAppState() 闭包拿值,而是 store.getState() 实时读:
1 | const computeTools = () => { |
原因:
- MCP 异步连接:render 之后 MCP server 才连上,闭包里的
tools会过期 - query 中途刷新:
options.refreshTools: computeTools让 query 循环可再拉最新 tools - 为 headless 循环预留:不依赖 React render 周期
返回对象结构
1. options — query / tool 运行配置
commands、tools、MCP clients/resources、thinkingConfig、agentDefinitions、customSystemPrompt 等。
2. 状态读写
getAppState: () => store.getState()— 纯读setAppState— 改全局 app statemessages/setMessages— 当前 transcriptupdateFileHistoryState/updateAttributionState— 带引用相等优化
3. UI / 交互回调
| 字段 | 用途 |
|---|---|
setToolJSX | 本地 JSX command 弹窗 |
setStreamMode | spinner 状态 |
onCompactProgress | compact 时改 spinner |
setInProgressToolUseIDs | 跟踪进行中的 tool |
appendSystemMessage | 追加 UI-only system message |
requestPrompt | hook 交互式 prompt |
在链路中的位置
1 | handlePromptSubmit → processUserInput → onQuery → onQueryImpl |
一句话:getToolUseContext 不是业务逻辑本身,而是 REPL 把 React/store/UI 能力注入到无 UI 的 query/tool 层的适配器。
其他值得知道的细节
Agent Swarms 与排队
setMemberActive 仅标记 teammate active;不会因其他队友 busy 而排队。排队只看本实例的 queryGuard.isActive。
setHaikuTitle
用 Haiku 小模型根据首条用户消息生成终端标题。会跳过 slash command 输出、bash 输入等合成 breadcrumb,等待真实用户 prose。
isMeta 用户消息
isMeta: true 的 user message:UI 隐藏、模型可见。用于系统注入、tool 调用记录等不需要展示给用户的内容。
Haiku 的用途
Haiku 是小模型,用于标题生成等轻量任务,不走主 Agent 循环。
数据流总览
1 | flowchart TB |
Message 构造流:
1 | 用户输入 ──→ createUserMessage() ──→ type: 'user' |
后续阅读建议
handleMessageFromStream全文(messages.ts:2930)— stream_event 如何转成 streamingText / 最终 messagequeryLoop+runTools— 一个具体 Tool 从 tool_use 到 tool_result 的完整路径services/api/claude.ts— SSE 解析与content_block_stop时机- 上下文压缩 — snip / microcompact / context collapse / autocompact 的实现细节
小结
Claude Code 的架构可以概括为:
- REPL 负责交互与状态;query 负责 Agent 循环;messages.ts 负责类型转换与流式处理
Message.type是 Claude Code 自己的 transcript 类型,100% 由代码赋值;LLM 只生成 content blockgetToolUseContext是 REPL 与 query/tool 层之间的适配器,保证 tools/MCP/state 始终 fresh- 主链路就是:
输入 → processUserInput → onQuery → query(API ↔ Tool 循环) → handleMessageFromStream → UI
下一篇可以深入 queryLoop 内部、Tool 注册与执行、以及上下文压缩的具体算法。