深度解析 Claude Code 架构:从终端输入到 LLM 响应的完整链路,涵盖沙箱安全、工具系统、流式引擎与状态管理,附详细架构图与时序图。
一、整体架构概览
Claude Code 是一个基于终端的 AI 编程助手,其架构设计遵循分层解耦 和事件驱动的原则。整个系统可以划分为以下几个核心层次:
scss
┌─────────────────────────────────────────────────────────────────────────────┐
│ CLI Entry Layer 入口层 │
│ (cli.tsx → main.tsx → REPL.tsx) │
├─────────────────────────────────────────────────────────────────────────────┤
│ Terminal UI 终端界面层 │
│ (Ink + React 渲染引擎) │
├─────────────────────────────────────────────────────────────────────────────┤
│ Query Engine 查询引擎层 │
│ (query.ts → QueryEngine.ts) │
├─────────────────────────────────────────────────────────────────────────────┤
│ Tool System 工具系统层 │
│ (Tool.ts → StreamingToolExecutor.ts → 30+ Tools) │
├─────────────────────────────────────────────────────────────────────────────┤
│ Services 服务层 │
│ (API Client / MCP / OAuth / Memory / Permission) │
├─────────────────────────────────────────────────────────────────────────────┤
│ State Management 状态管理层 │
│ (AppStateStore.ts → Custom Store) │
└─────────────────────────────────────────────────────────────────────────────┘
为什么这样设计? 每一层都有明确的职责边界,上层通过定义良好的接口调用下层,避免了紧耦合。这种设计使得系统易于测试、扩展和维护。
二、详细架构图
2.1 整体架构图
命令行入口] MAIN[main.tsx
主初始化流程] end subgraph UI["终端界面层 Terminal UI"] REPL[REPL.tsx
交互式循环] INK[Ink 渲染引擎] PROMPT[PromptInput
输入组件] end subgraph Query["查询引擎层 Query Engine"] QE[QueryEngine.ts] QLOOP[query.ts
查询循环] STREAM[流式响应处理] end subgraph ToolSys["工具系统层 Tool System"] TREG[Tool Registry
工具注册中心] STE[StreamingToolExecutor] FILE[File Tools
文件操作] SHELL[Shell Tools
命令执行] AGENT[Agent Tools
智能体] COMM[Comm Tools
通信] end subgraph Sandbox["Sandbox Layer"] SB_ADAPTER[sandbox-adapter.ts] SB_RUNTIME[sandbox-runtime] SB_MACOS[sandbox-exec] SB_LINUX[bwrap] SB_POLICY[Policy Config] end subgraph Services["服务层 Services"] API[API Client
Anthropic API] MCP[MCP Client
模型上下文协议] AUTH[OAuth
认证服务] MEM[Memory
记忆系统] PERM[Permission
权限系统] end subgraph State["状态管理层 State"] STORE[Custom Store
自定义状态存储] APPSTATE[AppState
100+ 字段] REACT[React Context
Providers] end CLI --> MAIN MAIN --> REPL REPL --> INK REPL --> QE QE --> QLOOP QLOOP --> STREAM STREAM --> API STREAM --> TREG TREG --> STE STE --> FILE STE --> SHELL STE --> AGENT STE --> COMM TREG --> PERM API --> MEM REPL --> STORE STORE --> APPSTATE APPSTATE --> REACT REACT --> INK SHELL --> SB_ADAPTER SB_ADAPTER --> SB_RUNTIME SB_RUNTIME --> SB_MACOS SB_RUNTIME --> SB_LINUX SB_RUNTIME --> SB_POLICY
2.2 核心模块关系图
早期输入捕获] B[StructuredIO
结构化IO] C[Slash Commands
斜杠命令] end subgraph Process["请求处理"] D[Context Assembly
上下文组装] E[System Prompt
系统提示词] F[Message History
消息历史] end subgraph LLM["LLM 交互"] G[Streaming API
流式API调用] H[Tool Use Detection
工具使用检测] I[Response Parser
响应解析] end subgraph Output["输出处理"] J[Tool Execution
工具执行] K[Permission Check
权限检查] L[Ink Render
终端渲染] end A --> B B --> C C --> D D --> E D --> F E --> G F --> G G --> H H --> I I --> J J --> K K --> L H -->|Multi-turn| G
三、时序图:从终端输入到 LLM 响应
3.1 完整请求生命周期时序图
3.2 工具执行详细时序图
3.3 状态更新与渲染时序图
四、核心模块功能分析
4.1 入口层 (Entry Layer)
文件 : src/entrypoints/cli.tsx, src/main.tsx
typescript
// cli.tsx - 最早期的入口点
async function main(): Promise<void> {
const args = process.argv.slice(2);
// 快速路径:--version 不需要加载任何模块
if (args.length === 1 && args[0] === '--version') {
console.log(`${MACRO.VERSION} (Claude Code)`);
return;
}
// 早期输入捕获 - 用户可能在 REPL 启动前就开始输入
const { startCapturingEarlyInput } = await import('../utils/earlyInput.js');
startCapturingEarlyInput();
// 加载主 CLI
const { main: cliMain } = await import('../main.js');
await cliMain();
}
为什么这样设计?
- 快速路径优化 :
--version等简单命令零依赖加载,响应时间 < 50ms - 早期输入捕获 : 解决用户输入
claude后立即打字但 REPL 尚未就绪的问题 - 动态导入: 减少启动时的模块加载开销,采用按需加载策略
4.2 终端界面层 (Terminal UI)
文件 : src/screens/REPL.tsx, src/ink.ts
typescript
// REPL.tsx 核心渲染循环
function REPL() {
const [appState, setAppState] = useAppState();
const [streamingText, setStreamingText] = useState<string | null>(null);
const onQuery = useCallback(async (input: string, messages: Message[]) => {
// 重置流式状态
setStreamingText(null);
// 启动查询引擎
const stream = queryEngine.stream({
messages,
systemPrompt,
canUseTool: checkToolPermission,
});
// 实时处理流式响应
for await (const event of stream) {
if (event.type === 'content_block_delta') {
setStreamingText(prev => prev + event.delta.text);
}
}
}, []);
return (
<Box flexDirection="column">
<MessageList messages={appState.messages} />
{streamingText && <StreamingText text={streamingText} />}
<PromptInput onSubmit={onQuery} />
</Box>
);
}
为什么使用 Ink + React?
- 声明式 UI: 用 React 组件模型描述终端界面,代码更易维护
- 增量更新: 只重渲染变化的部分,避免全屏刷新导致的闪烁
- 组件复用: 消息、输入框、加载状态都可以封装为独立组件
4.3 查询引擎层 (Query Engine)
文件 : src/query.ts, src/QueryEngine.ts
typescript
// query.ts - 核心查询循环
export async function* query(params: QueryParams): AsyncGenerator<StreamEvent> {
const { messages, systemPrompt, canUseTool, toolUseContext } = params;
// 组装 API 请求
const request = prepareApiRequest({
messages: formatMessagesForAPI(messages),
system: systemPrompt,
tools: getAvailableTools(),
});
// 流式调用 API
const stream = await streamMessages(request);
for await (const event of stream) {
switch (event.type) {
case 'content_block_start':
if (event.content_block.type === 'tool_use') {
// 检测到工具使用,准备执行
yield* handleToolUse(event.content_block, toolUseContext);
}
break;
case 'content_block_delta':
// 普通文本增量,直接透传
yield event;
break;
case 'message_stop':
// 消息结束,执行 stop hooks
yield* executeStopHooks(messages);
break;
}
}
}
查询引擎的设计亮点:
- Generator 函数 : 使用
AsyncGenerator实现真正的流式处理,内存占用低 - 工具拦截 : 在流中实时检测
tool_useblock,无缝切换到工具执行 - 错误恢复: 内置重试机制和错误处理,API 失败时自动降级
4.4 工具系统层 (Tool System)
文件 : src/Tool.ts, src/services/tools/StreamingToolExecutor.ts
typescript
// Tool.ts - 工具定义接口
export type Tool<Input extends AnyObject = AnyObject, Output = unknown> = {
// 身份标识
name: string;
aliases?: string[];
searchHint?: string;
// 能力声明
isEnabled(): boolean;
isConcurrencySafe(input): boolean;
isReadOnly(input): boolean;
isDestructive(input): boolean;
// 生命周期
validateInput(input, context): Promise<void>;
checkPermissions(input, context): Promise<PermissionResult>;
call(input, context, canUseTool, parentMessage): Promise<ToolResult<Output>>;
// 渲染
renderToolUseMessage(input): React.ReactNode;
renderToolResultMessage(result): React.ReactNode;
mapToolResultToToolResultBlockParam(result, toolUseID): ToolResultBlockParam;
// 类型安全
inputSchema: ZodSchema<Input>;
};
工具系统的核心设计原则:
- 自描述: 每个工具自带描述、验证、渲染逻辑,框架零侵入
- 权限感知 :
checkPermissions钩子支持自动和手动权限模式 - 并发安全 :
isConcurrencySafe标记允许并行执行独立的工具 - 类型安全: Zod Schema 确保输入输出类型在编译期和运行期都安全
4.5 服务层 (Services)
4.5.1 API Client
typescript
// src/services/api/claude.ts
export async function* streamMessages(params: MessageStreamParams) {
const response = await anthropic.messages.create({
model: params.model,
messages: params.messages,
system: params.system,
tools: params.tools,
stream: true, // 关键:启用流式
...getBetaHeaders(),
});
for await (const event of response) {
yield event;
}
}
4.5.2 Permission System
typescript
// src/utils/permissions/PermissionMode.ts
export const PERMISSION_MODES = {
// 自动模式:只读工具自动批准
auto: 'auto',
// 自动模式 + 破坏性操作需确认
auto_with_diff: 'auto_with_diff',
// 手动模式:所有工具都需确认
manual: 'manual',
} as const;
export function canUseTool(tool: Tool, input: unknown, mode: PermissionMode): boolean {
if (mode === 'manual') return false;
if (tool.isReadOnly(input)) return true;
if (mode === 'auto_with_diff' && tool.isDestructive(input)) return false;
return true;
}
4.6 状态管理层 (State Management)
文件 : src/state/AppStateStore.ts, src/state/store.ts
typescript
// store.ts - 自定义 Store 实现(仅34行)
export function createStore<T>(initialState: T, onChange?: OnChangeCallback<T>): Store<T> {
let state = initialState;
const listeners = new Set<() => void>();
return {
getState: () => state,
setState: (updater) => {
const prevState = state;
state = typeof updater === 'function'
? (updater as Function)(state)
: { ...state, ...updater };
if (state !== prevState) {
onChange?.({ newState: state, oldState: prevState });
listeners.forEach(fn => fn());
}
},
subscribe: (fn) => {
listeners.add(fn);
return () => listeners.delete(fn);
},
};
}
为什么选择自定义 Store 而不是 Redux/Zustand?
- 极简实现: 只有 34 行代码,零依赖
- React 集成 : 通过
useSyncExternalStore与 React 无缝集成 - 性能优化: 细粒度订阅,避免不必要的重渲染
- 类型安全: 完整的 TypeScript 类型推断
五、数据流全景图
六、关键设计决策与权衡
6.1 为什么使用自定义 Store 而非 Redux?
| 维度 | Custom Store | Redux |
|---|---|---|
| 代码量 | 34 行 | ~500 行 + 依赖 |
| 学习成本 | 低 | 高 |
| 性能 | 手动优化订阅 | 需配合 reselect |
| DevTools | 无 | 有 |
| 类型安全 | 原生支持 | 需额外配置 |
决策理由: Claude Code 的状态结构相对扁平,不需要 Redux 的复杂特性。自定义 Store 提供了足够的功能,同时保持了代码的简洁性。
6.2 为什么使用 Ink 而非传统终端库?
- React 生态: 复用 React 的组件化思维,降低心智负担
- 声明式渲染: 描述"应该显示什么"而非"如何绘制"
- 增量更新: 自动处理差异更新,避免全屏重绘
6.3 流式处理 vs 批处理
typescript
// 流式处理 - 实际采用
for await (const chunk of stream) {
render(chunk); // 即时渲染
}
// 批处理 - 未采用
const fullResponse = await fetchFullResponse();
render(fullResponse); // 等待全部数据
流式优势: 首字节时间 (TTFB) 更快,用户体验更流畅,内存占用更低。
七、Sandbox 沙箱系统深度解析
7.1 沙箱架构设计
Claude Code 的沙箱系统是其安全架构的核心组件,用于隔离和限制 shell 命令的执行环境,防止恶意或意外操作对宿主系统造成损害。
7.1.1 沙箱整体架构图
7.1.2 沙箱核心组件说明
| 组件 | 文件路径 | 职责 |
|---|---|---|
| SandboxAdapter | src/utils/sandbox/sandbox-adapter.ts |
Claude Code 与沙箱运行时的桥梁,处理配置转换和平台适配 |
| BaseSandboxManager | @anthropic-ai/sandbox-runtime |
底层沙箱运行时,提供跨平台抽象 |
| shouldUseSandbox | src/tools/BashTool/shouldUseSandbox.ts |
决定是否启用沙箱的策略逻辑 |
| BashTool | src/tools/BashTool/BashTool.tsx |
调用沙箱执行 Bash 命令 |
| Shell.ts | src/utils/Shell.ts |
命令执行的核心,集成沙箱包装逻辑 |
7.2 沙箱实现原理
7.2.1 平台差异处理
macOS (sandbox-exec):
typescript
// macOS 使用系统内置的 sandbox-exec 工具
// 通过 seatbelt profile 定义沙箱规则
// 支持文件系统访问控制、网络访问控制、进程创建限制等
Linux/WSL2 (bubblewrap):
typescript
// Linux 使用 bubblewrap (bwrap) 创建用户命名空间
// 结合 seccomp-bpf 进行系统调用过滤
// 使用 socat 代理网络请求实现网络隔离
Windows:
typescript
// Windows 原生不支持沙箱(bwrap/sandbox-exec 都是 POSIX-only)
// 如果企业策略强制启用沙箱且禁止非沙箱命令,则拒绝执行
7.2.2 沙箱配置体系
typescript
// 沙箱配置结构(来自 settings.json)
interface SandboxSettings {
enabled: boolean // 是否启用沙箱
autoAllowBashIfSandboxed: boolean // 沙箱内自动允许 Bash
allowUnsandboxedCommands: boolean // 允许非沙箱命令作为 fallback
failIfUnavailable: boolean // 沙箱不可用时是否失败
enabledPlatforms: Platform[] // 启用沙箱的平台列表
excludedCommands: string[] // 排除的命令模式
// 文件系统限制
filesystem: {
allowRead: string[] // 允许读取的路径
allowWrite: string[] // 允许写入的路径
denyWriteWithin: string[] // 在 allowWrite 内禁止写入的子路径
}
// 网络限制
network: {
allowHosts: Array<{domain: string, ports?: number[]}>
denyHosts: Array<{domain: string, ports?: number[]}>
httpProxyPort?: number // HTTP 代理端口
socksProxyPort?: number // SOCKS 代理端口
}
// seccomp 配置(Linux)
seccomp?: {
bpfPath: string // seccomp BPF 文件路径
applyPath: string // apply 脚本路径
}
}
7.3 沙箱调用时序图
7.3.1 命令执行沙箱化完整流程
7.3.2 沙箱初始化流程
7.3.3 沙箱权限决策流程
7.4 代码调用栈详解
7.4.1 沙箱命令执行调用栈
scss
BashTool.call() [src/tools/BashTool/BashTool.tsx:881]
└── exec() [src/utils/Shell.ts:181]
├── provider.buildExecCommand() [src/utils/shell/bashProvider.ts:77]
│ └── 构建命令字符串 + cwd 追踪
├── SandboxManager.wrapWithSandbox() [src/utils/sandbox/sandbox-adapter.ts:704]
│ └── BaseSandboxManager.wrapWithSandbox()
│ ├── macOS: 生成 sandbox-exec 命令
│ └── Linux: 生成 bwrap 命令
├── mkdir(sandboxTmpDir, 0o700) [src/utils/Shell.ts:268]
└── spawn(spawnBinary, shellArgs) [src/utils/Shell.ts:316]
└── child_process.spawn()
7.4.2 沙箱初始化调用栈
scss
REPL.tsx useEffect [src/screens/REPL.tsx:2316]
└── SandboxManager.initialize() [src/utils/sandbox/sandbox-adapter.ts:730]
└── BaseSandboxManager.initialize()
├── isSupportedPlatform() 检查平台支持
├── checkDependencies() 检查依赖
│ ├── macOS: 检查 sandbox-exec
│ └── Linux: 检查 bwrap, socat, seccomp
└── 加载配置文件
7.4.3 沙箱决策调用栈
scss
BashTool.call() [src/tools/BashTool/BashTool.tsx:896]
└── shouldUseSandbox() [src/tools/BashTool/shouldUseSandbox.ts:130]
├── SandboxManager.isSandboxingEnabled()
│ ├── isSupportedPlatform() [src/utils/sandbox/sandbox-adapter.ts:491]
│ ├── checkDependencies() [src/utils/sandbox/sandbox-adapter.ts:451]
│ └── getSandboxEnabledSetting() [src/utils/sandbox/sandbox-adapter.ts:435]
├── SandboxManager.areUnsandboxedCommandsAllowed()
└── containsExcludedCommand() [src/tools/BashTool/shouldUseSandbox.ts:21]
7.5 关键源码分析
7.5.1 沙箱包装逻辑 (Shell.ts)
typescript
// src/utils/Shell.ts:259-273
if (shouldUseSandbox) {
// 使用 SandboxManager 包装命令
commandString = await SandboxManager.wrapWithSandbox(
commandString,
sandboxBinShell,
undefined,
abortSignal,
)
// 创建沙箱临时目录(仅沙箱进程可写)
try {
const fs = getFsImplementation()
await fs.mkdir(sandboxTmpDir, { mode: 0o700 })
} catch (error) {
logForDebugging(`Failed to create ${sandboxTmpDir} directory: ${error}`)
}
}
7.5.2 沙箱决策逻辑 (shouldUseSandbox.ts)
typescript
// src/tools/BashTool/shouldUseSandbox.ts:130-153
export function shouldUseSandbox(input: Partial<SandboxInput>): boolean {
// 1. 检查沙箱是否启用
if (!SandboxManager.isSandboxingEnabled()) {
return false
}
// 2. 检查是否显式禁用沙箱且允许非沙箱命令
if (
input.dangerouslyDisableSandbox &&
SandboxManager.areUnsandboxedCommandsAllowed()
) {
return false
}
// 3. 检查命令是否在用户排除列表中
if (containsExcludedCommand(input.command)) {
return false
}
return true
}
7.5.3 沙箱适配器核心 (sandbox-adapter.ts)
typescript
// src/utils/sandbox/sandbox-adapter.ts:704-725
async function wrapWithSandbox(
command: string,
binShell?: string,
customConfig?: Partial<SandboxRuntimeConfig>,
abortSignal?: AbortSignal,
): Promise<string> {
// 确保沙箱初始化完成
if (isSandboxingEnabled()) {
if (initializationPromise) {
await initializationPromise
} else {
throw new Error('Sandbox failed to initialize.')
}
}
// 委托给底层沙箱运行时
return BaseSandboxManager.wrapWithSandbox(
command,
binShell,
customConfig,
abortSignal,
)
}
7.6 安全设计要点
- 分层安全策略: 沙箱作为深度防御的一环,与权限系统、命令验证形成多层防护
- 最小权限原则: 沙箱内进程只能访问明确允许的文件路径和网络端点
- 平台原生方案: 使用各平台原生沙箱机制(sandbox-exec/bwrap),而非自定义实现
- 配置驱动: 所有沙箱规则通过配置文件管理,支持企业策略锁定
- 优雅降级 : 支持
allowUnsandboxedCommands配置,在沙箱不可用时允许非沙箱执行
八、总结
Claude Code 的架构设计体现了以下核心原则:
- 分层清晰: 每一层职责单一,接口明确
- 流式优先: 从输入到输出全链路流式处理
- 类型安全: TypeScript + Zod 全链路类型保障
- 性能导向: 早期输入捕获、动态导入、细粒度订阅
- 扩展友好: 工具系统插件化,新功能易于接入
- 安全优先: 多层沙箱防护,企业级安全策略支持
这种架构使得 Claude Code 能够在保持代码可维护性的同时,提供流畅且安全的交互体验。