面向中文开发者的完整技术解析,基于 Claude Code CLI 真实源码
目录
第1章:项目概览
1.1 技术栈一览
Claude Code 是 Anthropic 官方出品的 AI 编程助手 CLI 工具,其源码采用了一套现代化的 TypeScript 全栈技术体系:
| 技术 | 版本/说明 | 用途 |
|---|---|---|
| TypeScript | ~5.x | 主要开发语言,约 512K 行代码 |
| React Ink | ^5.x | 终端 UI 框架(TUI) |
| Bun | runtime | 打包 + 运行时 |
| Anthropic SDK | latest | 与 Claude API 通信 |
| Commander.js | ^12.x | CLI 参数解析 |
| Zustand | ^4.x | 轻量状态管理 |
| Zod | ^3.x | 运行时类型验证 |
| chalk | ^5.x | 终端颜色输出 |
| sqlite3 | ^5.x | 本地持久化存储 |
| Biome | ^1.x | 代码格式化 + Lint |
代码规模统计:
总行数:~512,000 行 TypeScript
核心目录:src/
入口点数量:5 个
内置工具数量:42+
内置命令数量:150+
1.2 核心目录结构
src/
├── entrypoints/ # 五个程序入口点
│ ├── cli.tsx # 主 CLI 入口
│ ├── init.ts # 初始化入口
│ ├── mcp.ts # MCP 服务入口
│ └── agentSdkTypes.ts # Agent SDK 类型入口
├── commands/ # 150+ 斜杠命令实现
│ ├── login/ # 登录命令
│ ├── mcp/ # MCP 管理命令
│ ├── memory/ # 记忆命令
│ └── ...(80+ 命令目录)
├── tools/ # 42+ 内置工具
│ ├── BashTool/ # Bash 执行工具
│ ├── FileReadTool/ # 文件读取工具
│ ├── FileEditTool/ # 文件编辑工具
│ ├── WebSearchTool/ # 网络搜索工具
│ └── AgentTool/ # 子代理工具
├── bridge/ # 远程通信层
│ ├── replBridge.ts # Bridge 主逻辑(2407行)
│ ├── bridgeMain.ts # Bridge 核心(3000行)
│ └── remoteBridgeCore.ts # 远程核心(1009行)
├── services/ # 业务服务层
├── hooks/ # React 自定义 Hooks(83个)
├── components/ # React UI 组件(113个)
├── state/ # 状态管理
├── context/ # React Context 定义
├── main.tsx # 主入口(4684行)
├── query.ts # 核心查询循环
├── QueryEngine.ts # 查询引擎(1296行)
├── Task.ts # 任务抽象
├── Tool.ts # 工具基类(793行)
├── commands.ts # 命令注册(755行)
├── cost-tracker.ts # 成本追踪(324行)
└── setup.ts # 初始化配置(478行)
1.3 整体架构图
┌─────────────────────────────────────────────────────────────┐
│ Claude Code CLI │
├─────────────────────────────────────────────────────────────┤
│ 用户输入层 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ React Ink │ │ Commander │ │ 斜杠命令解析器 │ │
│ │ TUI 界面 │ │ CLI 参数 │ │ parseSlashCmd() │ │
│ └──────┬───────┘ └──────┬───────┘ └────────┬─────────┘ │
│ └─────────────────┴───────────────────┘ │
│ │ │
│ 核心处理层 ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ query.ts (核心查询循环) │ │
│ │ while(true) → Claude API → 工具执行 → 响应输出 │ │
│ └──────────────────────┬──────────────────────────────┘ │
│ │ │
│ ┌──────────┬───────────┼───────────┬──────────────────┐ │
│ │ 工具系统 │ 命令系统 │ Bridge层 │ 认证/会话管理 │ │
│ │ Tool.ts │ commands │ replBridge│ OAuth + JWT │ │
│ └──────────┴───────────┴───────────┴──────────────────┘ │
│ │
│ 基础设施层 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ SQLite │ │ NDJSON │ │ Keychain│ │ Anthropic │ │
│ │ 存储 │ │ 会话 │ │ 令牌 │ │ SDK │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
1.4 关键依赖解析
React Ink — 终端 UI 的 React:
// src/ink.ts 中的典型用法
import { render, Box, Text } from 'ink'
import React from 'react'
// Claude Code 将整个 TUI 界面渲染为 React 组件树
render(<App />, { stdout: process.stdout })
Zod — 运行时类型安全:
// 工具输入 Schema 定义示例
import { z } from 'zod'
const BashInputSchema = z.object({
command: z.string().describe('要执行的 bash 命令'),
timeout: z.number().optional().describe('超时时间(毫秒)'),
})
Zustand — 轻量状态管理:
// src/state/AppStateStore.ts 中的 store 定义模式
import { create } from 'zustand'
const useAppStore = create<AppState>((set, get) => ({
bridgeStatus: 'disconnected',
setBridgeStatus: (status) => set({ bridgeStatus: status }),
}))
第2章:启动流程解析
2.1 主入口 main.tsx
src/main.tsx 是整个程序的核心,共 4684 行,负责串联所有启动逻辑。
启动阶段时序图:
程序启动
│
▼
profileCheckpoint() ← 性能分析检查点
│
▼
startMdmRawRead() ← MDM(移动设备管理)配置预读
│
▼
startKeychainPrefetch() ← 提前异步读取 Keychain 令牌
│
▼
Commander.js 解析 CLI 参数
│
▼
setup() ← src/setup.ts ← 核心初始化(见2.2节)
│
├── 检查 Node.js 版本 ≥ 18
├── 启动 UDS 消息服务
├── 创建代理会话快照
├── 配置 Hook 系统
└── 加载插件
│
▼
render(<App />) ← 进入 React Ink TUI 循环
│
▼
用户交互 ←→ query 循环
2.2 早期初始化三件套
profileCheckpoint — 性能追踪:
// src/main.tsx 顶部调用
function profileCheckpoint(name: string) {
// 记录启动阶段时间戳,用于性能分析
if (process.env.CLAUDE_PROFILE) {
console.error(`[profile] ${name}: ${Date.now()}ms`)
}
}
// 关键检查点:
profileCheckpoint('start')
profileCheckpoint('imports-loaded')
profileCheckpoint('setup-complete')
profileCheckpoint('render-start')
startMdmRawRead — MDM 配置预读:
// 企业部署场景下,通过 MDM 下发配置
// 提前异步读取,避免阻塞主流程
async function startMdmRawRead(): Promise<void> {
// 从系统 MDM 配置文件读取企业策略
// 例如:禁用某些功能、强制使用特定模型等
}
startKeychainPrefetch — 令牌预读:
// 登录凭证存储在 OSX Keychain 或 Linux 密钥环
// 读取操作有延迟,所以提前异步预读
async function startKeychainPrefetch(): Promise<void> {
// 异步触发 keychain 读取,结果会缓存
// 后续 getAccessToken() 直接从缓存取,无需等待
keychainPrefetchPromise = readKeychainToken()
}
2.3 核心 setup.ts
src/setup.ts(478行)执行七项关键初始化任务:
// src/setup.ts 核心流程(简化)
export async function setup(options: SetupOptions): Promise<SetupResult> {
// 1. 运行时版本检查
const nodeVersion = parseInt(process.versions.node.split('.')[0])
if (nodeVersion < 18) {
throw new Error(`需要 Node.js 18+,当前版本: ${process.version}`)
}
// 2. 启动 UDS(Unix Domain Socket)消息服务
// 用于进程间通信,如与 IDE 插件通信
const udsServer = await startUDSMessageService()
// 3. 代理会话快照
// 保存当前环境快照,用于恢复和调试
const agentSnapshot = await createAgentSessionSnapshot()
// 4. Hook 系统初始化
// 加载用户配置的 Hook 脚本(pre/post 工具调用)
const hooks = await loadHookConfig()
// 5. 插件加载
// 三层加载:内置 → 用户 → 项目
const plugins = await loadAllPlugins()
// 6. MCP 服务器连接
// Model Context Protocol 服务器初始化
await initMcpConnections()
// 7. 权限检查
// 验证工作目录访问权限
await checkWorkspacePermissions()
return { udsServer, agentSnapshot, hooks, plugins }
}
2.4 五个入口点
src/entrypoints/ 目录包含五个独立入口:
entrypoints/
├── cli.tsx # 主交互式 CLI(最常用)
├── init.ts # claude init 命令入口
├── mcp.ts # MCP 服务器模式入口
├── agentSdkTypes.ts # Agent SDK 类型导出入口
└── ...
cli.tsx 的职责流程:
// src/entrypoints/cli.tsx(简化)
async function main() {
// 解析命令行参数
const program = new Command('claude')
program
.option('-p, --print', '非交互式输出模式')
.option('--resume <sessionId>', '恢复会话')
.option('--model <model>', '指定模型')
program.action(async (options) => {
if (options.print) {
// 管道/脚本模式:直接输出结果
await runPrintMode(options)
} else {
// 交互式 TUI 模式
await runInteractiveMode(options)
}
})
await program.parseAsync(process.argv)
}
2.5 React Ink TUI 循环
Claude Code 的终端界面完全基于 React Ink,将 React 的声明式 UI 带入终端:
// src/main.tsx(简化)
import { render } from 'ink'
import React from 'react'
import { App } from './components/App'
// 渲染主应用组件到终端
const { waitUntilExit } = render(
<App
initialPrompt={options.prompt}
sessionId={options.resume}
model={options.model}
/>,
{
stdout: process.stdout,
stderr: process.stderr,
exitOnCtrlC: false, // 手动处理 Ctrl+C
}
)
await waitUntilExit()
第3章:核心查询循环
3.1 query.ts 架构总览
src/query.ts 是 Claude Code 的心脏,实现了与 Claude API 通信的完整循环。
核心循环伪代码:
while (true) {
1. 技能发现预取(异步)
2. 消息规范化 & 上下文窗口管理
3. 检查是否需要自动压缩
4. 构建系统提示
5. 调用 Claude API(流式)
6. 处理响应流(累积 tool_use 块)
7. 执行工具调用(并发安全)
8. 检查 stop_reason:
├── tool_use → 继续循环
├── end_turn → 结束,返回结果
└── max_tokens → 触发压缩,继续循环
}
3.2 消息规范化与上下文管理
上下文窗口是有限的(Claude 3.5 Sonnet: 200K tokens),Claude Code 有完整的管理策略:
// src/query.ts 消息规范化(简化)
async function normalizeMessages(
messages: Message[],
maxTokens: number
): Promise<Message[]> {
// 1. 计算当前 token 数
let totalTokens = countTokens(messages)
// 2. 如果超过阈值,触发压缩
if (totalTokens > maxTokens * 0.8) {
messages = await compressHistory(messages)
}
// 3. 合并连续的同角色消息
messages = mergeConsecutiveMessages(messages)
// 4. 截断过长的工具输出
messages = truncateLargeToolOutputs(messages)
return messages
}
3.3 流式 API 调用
Claude Code 使用 Server-Sent Events(SSE)流式接收响应:
// src/query.ts API 调用(简化)
async function* callClaudeAPI(
messages: Message[],
systemPrompt: string,
tools: ToolDefinition[]
): AsyncGenerator<StreamEvent> {
const stream = await anthropic.messages.stream({
model: 'claude-opus-4-5',
max_tokens: 8192,
system: systemPrompt,
messages,
tools,
stream: true,
})
for await (const event of stream) {
yield event
}
}
流事件类型处理:
// 流事件状态机
for await (const event of stream) {
switch (event.type) {
case 'message_start':
// 记录 usage 信息(输入 tokens 数)
inputTokens = event.message.usage.input_tokens
break
case 'content_block_start':
if (event.content_block.type === 'tool_use') {
// 开始接收工具调用块
currentToolUse = {
id: event.content_block.id,
name: event.content_block.name,
inputJson: '',
}
}
break
case 'content_block_delta':
if (event.delta.type === 'input_json_delta') {
// 累积工具调用的 JSON 参数
currentToolUse.inputJson += event.delta.partial_json
} else if (event.delta.type === 'text_delta') {
// 流式输出文本
yield { type: 'text', content: event.delta.text }
}
break
case 'content_block_stop':
if (currentToolUse) {
// 完整的工具调用块接收完毕
const input = JSON.parse(currentToolUse.inputJson)
yield { type: 'tool_use', ...currentToolUse, input }
currentToolUse = null
}
break
case 'message_stop':
// 消息结束,记录 stop_reason
yield { type: 'done', stopReason: event.message.stop_reason }
break
}
}
3.4 QueryEngine.ts
src/QueryEngine.ts(1296行)是更高层的查询管理器:
// src/QueryEngine.ts 核心接口(简化)
export class QueryEngine {
private tokenBudget: TokenBudget
private conversationHistory: Message[]
async *query(userMessage: string): AsyncGenerator<QueryEvent> {
// 1. 添加用户消息到历史
this.conversationHistory.push({
role: 'user',
content: userMessage,
})
// 2. Token 预算追踪
this.tokenBudget.recordInput(userMessage)
// 3. 调用核心查询循环
for await (const event of runQueryLoop(this.conversationHistory)) {
// 4. 更新 token 预算
if (event.type === 'usage') {
this.tokenBudget.update(event.usage)
}
yield event
}
}
// Token 预算解析:支持多种格式
// "+500k" → 当前预算 + 500,000
// "use 2m tokens" → 2,000,000
// "1000" → 1,000
parseTokenBudget(input: string): number {
const match = input.match(/(\+?)(\d+)(k|m)?/)
// ... 解析逻辑
}
}
3.5 stop_reason 状态机
API 返回 stop_reason
│
├── "end_turn"
│ └── 模型正常完成 → 结束循环,返回结果
│
├── "tool_use"
│ └── 模型请求工具调用
│ │
│ ▼
│ 执行工具(并发/串行)
│ │
│ ▼
│ 将工具结果追加到消息历史
│ │
│ └── 继续 while 循环
│
└── "max_tokens"
└── 触发上下文压缩
│
▼
autoCompressHistory()
│
└── 继续 while 循环
第4章:工具系统架构
4.1 Tool.ts 工具接口
src/Tool.ts(793行)定义了所有工具的统一接口:
// src/Tool.ts 工具接口(简化)
export interface Tool<TInput = unknown, TOutput = unknown> {
// 工具标识
name: string
description: string
inputSchema: z.ZodType<TInput>
// 工具执行
call(
input: TInput,
context: ToolContext
): Promise<TOutput> | AsyncGenerator<TOutput>
// 权限相关
checkPermissions(input: TInput, context: ToolPermissionContext): ToolPermission
isReadOnly(): boolean // 只读工具不需要确认
isDestructive(): boolean // 破坏性操作需要警告
// 并发控制
isConcurrencySafe(): boolean // 是否可以并发执行
// 元数据
getProgressDescription?(input: TInput): string // 进度描述
}
// 权限类型
type ToolPermission =
| { type: 'allow' } // 直接允许
| { type: 'ask'; message: string } // 需要用户确认
| { type: 'deny'; reason: string } // 拒绝执行
4.2 buildTool 工厂函数
buildTool 是创建工具的工厂函数,实现了**失闭(Fail-Closed)**安全设计:
// src/Tool.ts buildTool 工厂(简化)
export function buildTool<TInput>(config: {
name: string
description: string
inputSchema: z.ZodType<TInput>
call: (input: TInput, ctx: ToolContext) => Promise<string>
// 安全默认值:未指定时使用最严格的默认值
isReadOnly?: boolean // 默认 false(非只读)
isDestructive?: boolean // 默认 true(破坏性)
isConcurrencySafe?: boolean // 默认 false(非并发安全)
}): Tool<TInput, string> {
return {
name: config.name,
description: config.description,
inputSchema: config.inputSchema,
call: config.call,
// 失闭原则:不明确声明安全,就假设危险
isReadOnly: () => config.isReadOnly ?? false,
isDestructive: () => config.isDestructive ?? true,
isConcurrencySafe: () => config.isConcurrencySafe ?? false,
checkPermissions(input, ctx) {
// 检查工具是否在允许列表
// 检查是否被明确拒绝
// 默认行为:询问用户
return defaultPermissionCheck(this, input, ctx)
},
}
}
4.3 42+ 内置工具列表
src/tools/
├── BashTool/ # bash 命令执行
├── FileReadTool/ # 文件读取
├── FileEditTool/ # 文件编辑(精确替换)
├── FileWriteTool/ # 文件写入
├── FileDeleteTool/ # 文件删除
├── GlobTool/ # 文件模式匹配
├── GrepTool/ # 文本搜索
├── WebSearchTool/ # 网络搜索
├── WebFetchTool/ # 网页内容获取
├── AgentTool/ # 启动子代理
├── MCPTool/ # MCP 工具代理
├── NotebookReadTool/ # Jupyter Notebook 读取
├── NotebookEditTool/ # Jupyter Notebook 编辑
└── ...(共 42+ 个)
BashTool 示例实现:
// src/tools/BashTool/BashTool.ts(简化)
export const BashTool = buildTool({
name: 'Bash',
description: '在 shell 中执行命令',
inputSchema: z.object({
command: z.string().describe('要执行的命令'),
timeout: z.number().optional().default(120000),
description: z.string().optional(),
}),
isReadOnly: false,
isDestructive: true,
isConcurrencySafe: false, // bash 命令不能并发(可能有副作用)
async call({ command, timeout }, ctx) {
// 安全检查
if (isBlocked(command)) {
throw new Error(`命令被安全策略阻止: ${command}`)
}
// 执行命令
const result = await executeWithTimeout(command, timeout)
// 格式化输出(截断超长输出)
return formatOutput(result, MAX_OUTPUT_SIZE)
},
checkPermissions({ command }, ctx) {
// 检查是否在 allowedCommands 列表
if (ctx.mode === 'auto') {
return { type: 'allow' }
}
// 识别危险命令
if (isDangerousCommand(command)) {
return {
type: 'ask',
message: `即将执行: ${command}\n确认继续?`
}
}
return { type: 'allow' }
},
})
4.4 条件加载工具
Claude Code 通过功能开关控制工具加载,在编译时消除未启用的功能:
// src/tools.ts 工具注册(简化)
export function getAvailableTools(flags: FeatureFlags): Tool[] {
const tools: Tool[] = [
// 核心工具(始终加载)
BashTool,
FileReadTool,
FileEditTool,
FileWriteTool,
GlobTool,
GrepTool,
]
// 条件工具(功能开关控制)
if (flags.PROACTIVE_SEARCH) {
tools.push(WebSearchTool)
tools.push(WebFetchTool)
}
if (flags.KAIROS_ENABLED) {
tools.push(KairosTool)
}
if (flags.NOTEBOOKS_ENABLED) {
tools.push(NotebookReadTool)
tools.push(NotebookEditTool)
}
// MCP 工具(动态加载,运行时注入)
const mcpTools = getMcpTools()
tools.push(...mcpTools)
return tools
}
4.5 StreamingToolExecutor
工具执行器负责并发安全调度和权限检查:
// src/tools/StreamingToolExecutor(简化)
export class StreamingToolExecutor {
private runningTools = new Set<string>()
async executeTools(
toolCalls: ToolCall[],
context: ToolPermissionContext
): Promise<ToolResult[]> {
// 1. 并发安全分组
const { concurrent, sequential } = groupByConcurrency(toolCalls)
// 2. 并发安全的工具并行执行
const concurrentResults = await Promise.all(
concurrent.map(call => this.executeSingle(call, context))
)
// 3. 非并发安全的工具串行执行
const sequentialResults = []
for (const call of sequential) {
sequentialResults.push(await this.executeSingle(call, context))
}
return [...concurrentResults, ...sequentialResults]
}
private async executeSingle(
call: ToolCall,
context: ToolPermissionContext
): Promise<ToolResult> {
const tool = this.findTool(call.name)
// 权限检查
const permission = tool.checkPermissions(call.input, context)
if (permission.type === 'deny') {
return { error: `权限拒绝: ${permission.reason}` }
}
if (permission.type === 'ask') {
const confirmed = await askUserPermission(permission.message)
if (!confirmed) {
return { error: '用户拒绝执行' }
}
}
// Hook 前置执行
await runPreToolHooks(call, context)
// 执行工具
const result = await tool.call(call.input, context)
// Hook 后置执行
await runPostToolHooks(call, result, context)
return { result }
}
}
4.6 ToolPermissionContext
// 权限上下文定义
interface ToolPermissionContext {
// 运行模式
mode: 'default' | 'plan' | 'auto'
// default = 交互式,需要确认破坏性操作
// plan = 计划模式,只允许只读操作
// auto = 自动模式,跳过大多数确认
// 显式规则
allowedTools: string[] // 白名单工具
deniedTools: string[] // 黑名单工具
// 会话级权限记忆
sessionPermissions: Map<string, 'allow' | 'deny'>
}
第5章:命令系统设计
5.1 三种命令类型
// src/commands.ts 命令类型定义
// 类型1:PromptCommand(技能命令)
// 将预设提示注入到对话
interface PromptCommand {
type: 'prompt'
name: string
description: string
prompt: string | ((args: string) => string)
aliases?: string[]
}
// 类型2:LocalCommand(本地执行命令)
// 直接执行 JavaScript 函数
interface LocalCommand {
type: 'local'
name: string
description: string
handler: (args: string, ctx: CommandContext) => Promise<void>
aliases?: string[]
}
// 类型3:LocalJSXCommand(UI 组件命令)
// 渲染 React 组件到 TUI
interface LocalJSXCommand {
type: 'local-jsx'
name: string
description: string
component: React.ComponentType<CommandProps>
aliases?: string[]
}
5.2 commands.ts 命令注册
src/commands.ts(755行)通过 memoize 函数懒加载所有命令:
// src/commands.ts(简化)
import { memoize } from 'lodash'
// 避免重复初始化,使用 memoize 缓存
export const COMMANDS = memoize((): Command[] => {
return [
// 对话管理
clearCommand, // /clear
compactCommand, // /compact
contextCommand, // /context
// 模型配置
modelCommand, // /model
effortCommand, // /effort
// 系统管理
loginCommand, // /login
logoutCommand, // /logout
configCommand, // /config
// MCP
mcpCommand, // /mcp
// 插件
pluginCommand, // /plugin
// 记忆
memoryCommand, // /memory
// 技能
skillsCommand, // /skills
// 会话
sessionCommand, // /session
resumeCommand, // /resume
// 调试
doctorCommand, // /doctor
statsCommand, // /stats
costCommand, // /cost
// ... 150+ 命令
]
})
5.3 斜杠命令解析
// src/commands.ts parseSlashCommand(简化)
export function parseSlashCommand(input: string): ParsedCommand | null {
// 必须以 "/" 开头
if (!input.startsWith('/')) return null
// 去掉前缀
const rest = input.slice(1)
// 检查是否是 MCP 命令(格式:/mcp__server__tool)
const mcpMatch = rest.match(/^mcp__(\w+)__(\w+)(.*)$/)
if (mcpMatch) {
return {
commandName: rest.split(' ')[0],
args: rest.slice(rest.indexOf(' ') + 1).trim(),
isMcp: true,
mcpServer: mcpMatch[1],
mcpTool: mcpMatch[2],
}
}
// 普通命令解析
const [commandName, ...argParts] = rest.split(' ')
return {
commandName,
args: argParts.join(' ').trim(),
isMcp: false,
}
}
5.4 命令查找与过滤
// src/commands.ts findCommand(简化)
export function findCommand(
name: string,
context: CommandContext
): Command | undefined {
const commands = COMMANDS()
// 1. 精确名称匹配
let cmd = commands.find(c => c.name === name)
if (cmd) return filterCommand(cmd, context)
// 2. 别名匹配
cmd = commands.find(c => c.aliases?.includes(name))
if (cmd) return filterCommand(cmd, context)
return undefined
}
function filterCommand(
cmd: Command,
context: CommandContext
): Command | undefined {
// 可用性检查
if (cmd.isAvailable && !cmd.isAvailable(context)) return undefined
// Bridge 安全白名单
// 在远程 Bridge 模式下,只允许特定命令
if (context.isBridgeMode && !BRIDGE_SAFE_COMMANDS.includes(cmd.name)) {
return undefined
}
return cmd
}
5.5 命令实现示例
// src/commands/clear/clearCommand.ts(简化)
export const clearCommand: LocalCommand = {
type: 'local',
name: 'clear',
description: '清除对话历史',
aliases: ['reset'],
async handler(args, ctx) {
// 清除对话历史
ctx.clearConversation()
// 重置成本追踪
ctx.costTracker.reset()
// 输出确认消息
ctx.print('✓ 对话已清除')
},
}
第6章:Bridge 通信层
6.1 Bridge 架构概述
Bridge 层使 Claude Code 能够嵌入到远程环境(如 Replit、IDE 插件)中工作。
本地 Claude Code 远程环境(如 Replit)
│ │
│ V1 路径 │
├──── WebSocket ──────────►│
│◄─── HTTP Polling ────────┤
│ │
│ V2 路径 │
├──── SSE ────────────────►│
│◄─── CCRClient ───────────┤
│ │
└──────── OAuth JWT ───────┘
两条通信路径:
- V1(环境层):WebSocket + HTTP 轮询,适用于传统环境
- V2(直接连接):SSE(Server-Sent Events)+ CCRClient,性能更好
6.2 核心文件解析
| 文件 | 行数 | 职责 |
|---|---|---|
src/bridge/replBridge.ts | 2407 | Bridge 主逻辑,消息路由 |
src/bridge/bridgeMain.ts | 3000 | 连接管理,状态机 |
src/bridge/remoteBridgeCore.ts | 1009 | 远程连接核心实现 |
src/bridge/bridgeMessaging.ts | ~600 | 消息序列化/去重 |
src/bridge/jwtUtils.ts | ~200 | JWT 工具函数 |
6.3 初始化流程
// src/bridge/initReplBridge.ts(简化)
export async function initReplBridge(
config: BridgeConfig
): Promise<BridgeHandle> {
// 1. 检查网关可达性
const gatewayOk = await checkGatewayHealth(config.gatewayUrl)
if (!gatewayOk) throw new BridgeError('网关不可达')
// 2. OAuth 认证检查
const token = await getValidOAuthToken()
if (!token) throw new BridgeError('未登录,请先执行 /login')
// 3. 策略检查(企业 MDM 策略)
await checkBridgePolicy()
// 4. 选择通信路径
const version = await negotiateBridgeVersion(config)
if (version === 'v2') {
return initV2Bridge(config, token)
} else {
return initV1Bridge(config, token)
}
}
6.4 消息处理与去重
Bridge 实现了三层消息去重机制,防止重复处理:
// src/bridge/bridgeMessaging.ts(简化)
export class BridgeMessaging {
private seenMessageIds = new Set<string>()
private lastSequenceNumber = -1
private historyMessageIds = new Set<string>()
async handleIngressMessage(rawMessage: unknown): Promise<void> {
const message = validateMessage(rawMessage)
// 第1层:UUID 去重
if (this.seenMessageIds.has(message.id)) {
console.debug(`重复消息,忽略: ${message.id}`)
return
}
this.seenMessageIds.add(message.id)
// 第2层:序列号去重
if (message.sequenceNumber <= this.lastSequenceNumber) {
console.debug(`过期消息,忽略: seq=${message.sequenceNumber}`)
return
}
this.lastSequenceNumber = message.sequenceNumber
// 第3层:历史光标去重(防止重放)
if (this.historyMessageIds.has(message.id)) {
return
}
// 按类型路由消息
await this.routeMessage(message)
}
private async routeMessage(message: BridgeMessage): Promise<void> {
switch (message.type) {
case 'user_message':
await this.handleUserMessage(message)
break
case 'permission_response':
await this.handlePermissionResponse(message)
break
case 'abort':
await this.handleAbort(message)
break
}
}
}
6.5 重连策略
// src/bridge/replBridgeTransport.ts 重连逻辑(简化)
export class BridgeTransport {
private reconnectAttempts = 0
private lastDisconnectTime = 0
private getReconnectDelay(): number {
// 指数退避:2s → 4s → 8s → ... → 120s(2分钟上限)
const baseDelay = 2000 // 2秒
const maxDelay = 120000 // 2分钟
const delay = Math.min(
baseDelay * Math.pow(2, this.reconnectAttempts),
maxDelay
)
// 加入随机抖动,防止雷群效应
return delay + Math.random() * 1000
}
async reconnect(): Promise<void> {
const now = Date.now()
// 睡眠检测:如果断连超过 240 秒(4分钟),认为系统休眠
const timeSinceDisconnect = now - this.lastDisconnectTime
if (timeSinceDisconnect > 240_000) {
console.log('检测到系统唤醒,重置重连计数')
this.reconnectAttempts = 0
}
// 放弃条件:单次断连累计超过 10 分钟
const totalReconnectTime = this.reconnectAttempts * 120_000
if (totalReconnectTime > 600_000) {
throw new BridgeError('重连超时,放弃连接')
}
await sleep(this.getReconnectDelay())
this.reconnectAttempts++
await this.connect()
}
}
第7章:认证与安全
7.1 两层认证体系
┌─────────────────────────────────────────────────┐
│ Claude Code 认证体系 │
├─────────────────────────────────────────────────┤
│ │
│ 第1层:OAuth(用户身份认证) │
│ ┌─────────────────────────────────────────┐ │
│ │ PKCE 授权流程 │ │
│ │ 用户 ←→ 浏览器 ←→ Anthropic OAuth 服务 │ │
│ │ 生命周期:长期(refresh token) │ │
│ └─────────────────────────────────────────┘ │
│ │
│ 第2层:会话 JWT(运行时请求认证) │
│ ┌─────────────────────────────────────────┐ │
│ │ 短期 JWT(约 1 小时有效) │ │
│ │ 自动刷新(到期前 5 分钟) │ │
│ │ 附加在每个 API 请求头 │ │
│ └─────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────┘
7.2 OAuth PKCE 流程
PKCE(Proof Key for Code Exchange)是 OAuth 2.0 的安全扩展,防止授权码被拦截:
// src/bridge/jwtUtils.ts OAuth 实现(简化)
// 步骤1:生成代码验证器
function generateCodeVerifier(): string {
// 生成 43-128 字符的随机字符串
const array = new Uint8Array(32)
crypto.getRandomValues(array)
return base64URLEncode(array) // URL 安全的 base64 编码
}
// 步骤2:构建授权 URL
function buildAuthUrl(codeVerifier: string): string {
// code_challenge = BASE64URL(SHA256(code_verifier))
const codeChallenge = base64URLEncode(
sha256(new TextEncoder().encode(codeVerifier))
)
const params = new URLSearchParams({
client_id: OAUTH_CLIENT_ID,
redirect_uri: 'https://claude.ai/callback',
response_type: 'code',
scope: 'openid email profile',
code_challenge: codeChallenge,
code_challenge_method: 'S256',
state: generateState(), // CSRF 防护
})
return `${OAUTH_AUTH_URL}?${params}`
}
// 步骤3:用授权码换取令牌
async function exchangeCodeForTokens(
code: string,
codeVerifier: string
): Promise<OAuthTokens> {
const response = await fetch(OAUTH_TOKEN_URL, {
method: 'POST',
body: new URLSearchParams({
grant_type: 'authorization_code',
code,
redirect_uri: 'https://claude.ai/callback',
code_verifier: codeVerifier, // 验证器(PKCE 核心)
}),
})
return response.json()
}
7.3 JWT 自动刷新
// JWT 刷新调度器
export function createTokenRefreshScheduler(
getToken: () => Promise<string>,
onRefresh: (newToken: string) => void
): TokenRefreshScheduler {
let refreshTimer: NodeJS.Timeout | null = null
async function scheduleRefresh(token: string): Promise<void> {
const payload = decodeJWT(token)
const expiresAt = payload.exp * 1000 // 转换为毫秒
const now = Date.now()
// 到期前 5 分钟刷新
const refreshAt = expiresAt - 5 * 60 * 1000
const delay = refreshAt - now
if (delay <= 0) {
// 已过期或即将过期,立即刷新
await doRefresh()
return
}
refreshTimer = setTimeout(async () => {
await doRefresh()
}, delay)
}
async function doRefresh(): Promise<void> {
try {
const newToken = await getToken()
onRefresh(newToken)
scheduleRefresh(newToken) // 安排下次刷新
} catch (error) {
// 刷新失败,1分钟后重试
setTimeout(doRefresh, 60_000)
}
}
return { scheduleRefresh, cancel: () => refreshTimer && clearTimeout(refreshTimer) }
}
7.4 可信设备令牌
// src/bridge/trustedDevice.ts(简化)
// 可信设备令牌避免每次都需要 2FA
export class TrustedDeviceManager {
// 令牌有效期:90天
private static TOKEN_TTL = 90 * 24 * 60 * 60 * 1000
async getTrustedDeviceToken(): Promise<string | null> {
const stored = await readFromKeychain('trusted-device-token')
if (!stored) return null
const { token, createdAt } = JSON.parse(stored)
// 滚动过期:每次使用都延长有效期
if (Date.now() - createdAt > TrustedDeviceManager.TOKEN_TTL) {
await this.revokeTrustedDevice()
return null
}
// 更新使用时间(滚动过期)
await this.updateLastUsed()
return token
}
// 在请求头中附加可信设备令牌
getRequestHeaders(): Record<string, string> {
const token = this.cachedToken
if (!token) return {}
return { 'X-Trusted-Device-Token': token }
}
}
7.5 URL 安全验证
// src/bridge/bridgeConfig.ts URL 验证(简化)
export function validateBridgeId(bridgeId: string): boolean {
// 防止路径遍历攻击(../../../etc/passwd 等)
if (bridgeId.includes('..')) return false
if (bridgeId.includes('/')) return false
if (bridgeId.includes('\\')) return false
// 只允许字母数字和连字符
return /^[a-zA-Z0-9-_]+$/.test(bridgeId)
}
第8章:状态管理
8.1 Zustand Store 设计
src/state/AppStateStore.ts(570行)是全局状态中心:
// src/state/AppStateStore.ts(简化)
import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'
interface AppState {
// Bridge 状态域
bridgeStatus: BridgeStatus
bridgeSessionId: string | null
// 应用配置域
currentModel: string
theme: 'light' | 'dark' | 'auto'
effortLevel: 'low' | 'medium' | 'high' | 'max'
// 任务和通知域
activeTasks: Task[]
notifications: Notification[]
// MCP 和插件域
mcpServers: McpServer[]
loadedPlugins: Plugin[]
// 动作
setBridgeStatus: (status: BridgeStatus) => void
addTask: (task: Task) => void
removeTask: (taskId: string) => void
addNotification: (notification: Notification) => void
}
export const useAppStore = create<AppState>()(
immer((set, get) => ({
// 初始状态
bridgeStatus: 'disconnected',
bridgeSessionId: null,
currentModel: 'claude-opus-4-5',
theme: 'auto',
effortLevel: 'medium',
activeTasks: [],
notifications: [],
mcpServers: [],
loadedPlugins: [],
// 动作实现
setBridgeStatus: (status) =>
set(state => { state.bridgeStatus = status }),
addTask: (task) =>
set(state => { state.activeTasks.push(task) }),
removeTask: (taskId) =>
set(state => {
state.activeTasks = state.activeTasks.filter(t => t.id !== taskId)
}),
addNotification: (notification) =>
set(state => { state.notifications.push(notification) }),
}))
)
8.2 与 React 集成
Zustand 通过 useSyncExternalStore 实现与 React 的高效集成:
// 选择器模式:只订阅需要的状态片段,避免不必要的重渲染
function BridgeStatusIndicator() {
// 只有 bridgeStatus 变化时才重渲染
const bridgeStatus = useAppStore(state => state.bridgeStatus)
return (
<Text color={bridgeStatus === 'connected' ? 'green' : 'red'}>
{bridgeStatus === 'connected' ? '● 已连接' : '○ 未连接'}
</Text>
)
}
8.3 批量更新模式
// 使用 immer 实现不可变更新
// 批量修改多个状态,只触发一次重渲染
function initializeBridgeConnection(sessionId: string) {
useAppStore.setState(state => {
// immer 允许直接"修改"(实际产生新对象)
state.bridgeStatus = 'connected'
state.bridgeSessionId = sessionId
state.notifications.push({
id: uuid(),
type: 'success',
message: 'Bridge 连接成功',
})
})
}
8.4 四大状态域
AppStateStore
├── Bridge 状态域
│ ├── bridgeStatus: 'connecting' | 'connected' | 'disconnected' | 'error'
│ ├── bridgeSessionId: string | null
│ └── bridgeError: Error | null
│
├── 应用配置域
│ ├── currentModel: string
│ ├── theme: 'light' | 'dark' | 'auto'
│ ├── effortLevel: 'low' | 'medium' | 'high' | 'max'
│ └── outputStyle: 'default' | 'compact' | 'verbose'
│
├── 任务/通知域
│ ├── activeTasks: Task[]
│ ├── notifications: Notification[]
│ └── pendingPermissions: PermissionRequest[]
│
└── MCP/插件域
├── mcpServers: McpServer[]
├── loadedPlugins: Plugin[]
└── pluginErrors: PluginError[]
第9章:会话管理
9.1 会话生命周期
创建会话
│
▼
POST /v1/sessions → { sessionId: "sess_xxx" }
│
▼
在本地创建会话文件
~/.claude/sessions/{sessionId}.jsonl
│
▼
每条消息追加写入(NDJSON 格式)
│
├── 用户消息 → { "type": "user", "content": "..." }
├── 助手消息 → { "type": "assistant", "content": "..." }
├── 工具调用 → { "type": "tool_use", "name": "Bash", ... }
└── 工具结果 → { "type": "tool_result", "content": "..." }
│
▼
会话结束
│
└── 可通过 --resume-session {id} 恢复
9.2 NDJSON 存储格式
NDJSON(Newline-Delimited JSON)每行一个 JSON 对象,适合追加写入:
// src/assistant/sessionHistory.ts(简化)
export class SessionHistory {
private filePath: string
private writeStream: fs.WriteStream
constructor(sessionId: string) {
this.filePath = path.join(
os.homedir(),
'.claude',
'sessions',
`${sessionId}.jsonl`
)
// 追加模式打开,避免覆盖历史
this.writeStream = fs.createWriteStream(this.filePath, { flags: 'a' })
}
// 追加一条记录
async append(record: SessionRecord): Promise<void> {
const line = JSON.stringify(record) + '\n'
return new Promise((resolve, reject) => {
this.writeStream.write(line, (err) => {
if (err) reject(err)
else resolve()
})
})
}
// 读取完整历史
async readAll(): Promise<SessionRecord[]> {
const content = await fs.promises.readFile(this.filePath, 'utf-8')
return content
.split('\n')
.filter(line => line.trim())
.map(line => JSON.parse(line))
}
}
9.3 会话恢复
// src/commands/resume/resumeCommand.ts(简化)
export async function resumeSession(sessionId: string): Promise<void> {
const history = new SessionHistory(sessionId)
// 读取历史记录
const records = await history.readAll()
// 重建对话历史
const messages: Message[] = records
.filter(r => r.type !== 'metadata')
.map(recordToMessage)
// 恢复成本追踪
const costState = records.find(r => r.type === 'cost_state')
if (costState) {
costTracker.restore(costState.data)
}
// 注入历史消息,继续对话
await startConversation({ messages, sessionId })
}
9.4 多会话模式
Claude Code 支持三种会话模式:
// 模式1:单会话(默认)
// 一个项目目录,一个活跃会话
{
mode: 'single',
sessionId: 'sess_abc123',
workdir: '/projects/my-app',
}
// 模式2:Worktree 模式
// 每个 Git Worktree 独立会话
{
mode: 'worktree',
sessions: [
{ sessionId: 'sess_abc', worktree: '/projects/my-app' },
{ sessionId: 'sess_def', worktree: '/projects/my-app-feature' },
]
}
// 模式3:同目录多会话
// 同一目录可以有多个并发会话
{
mode: 'multi',
sessions: ['sess_abc', 'sess_def'],
sharedWorkdir: '/projects/my-app',
}
第10章:成本追踪
10.1 cost-tracker.ts 架构
src/cost-tracker.ts(324行)追踪每次对话的 API 使用成本:
// src/cost-tracker.ts(简化)
export interface StoredCostState {
// 总成本
totalCostUSD: number
// 按模型分类的使用量
modelUsage: Record<string, ModelUsage>
// 代码变更统计
totalLinesAdded: number
totalLinesRemoved: number
// Token 使用统计
totalInputTokens: number
totalOutputTokens: number
totalCacheReadTokens: number
totalCacheWriteTokens: number
}
interface ModelUsage {
inputTokens: number
outputTokens: number
cacheReadTokens: number
cacheWriteTokens: number
costUSD: number
requestCount: number
}
10.2 计费公式
Claude API 的计费按 Token 类型分别计算:
// src/cost-tracker.ts tokensToUSDCost(简化)
export function tokensToUSDCost(usage: TokenUsage, model: string): number {
const pricing = MODEL_PRICING[model] ?? MODEL_PRICING['default']
// 各类型 Token 独立计费(每百万 Token 的美元价格)
const inputCost = (usage.inputTokens / 1_000_000) * pricing.inputPerMToken
const outputCost = (usage.outputTokens / 1_000_000) * pricing.outputPerMToken
// 缓存读取(最便宜,约为输入的 10%)
const cacheReadCost = (usage.cacheReadTokens / 1_000_000) * pricing.cacheReadPerMToken
// 缓存写入(最贵,约为输入的 125%)
const cacheWriteCost = (usage.cacheWriteTokens / 1_000_000) * pricing.cacheWritePerMToken
// 搜索成本(如果使用了 WebSearch)
const searchCost = (usage.searchRequests ?? 0) * pricing.searchPerRequest
return inputCost + outputCost + cacheReadCost + cacheWriteCost + searchCost
}
// 主流模型定价参考(美元/百万Token,2024年)
const MODEL_PRICING = {
'claude-opus-4-5': {
inputPerMToken: 15,
outputPerMToken: 75,
cacheReadPerMToken: 1.5,
cacheWritePerMToken: 18.75,
},
'claude-sonnet-4-5': {
inputPerMToken: 3,
outputPerMToken: 15,
cacheReadPerMToken: 0.3,
cacheWritePerMToken: 3.75,
},
}
10.3 Token 预算解析
Claude Code 支持人性化的 Token 预算表达式:
// src/QueryEngine.ts parseTokenBudget(简化)
export function parseTokenBudget(input: string): number | null {
const str = input.toLowerCase().trim()
// 格式1:"+500k" → 当前预算 + 500,000
const relativeMatch = str.match(/^\+(\d+)(k|m)?$/)
if (relativeMatch) {
const base = parseInt(relativeMatch[1])
const multiplier = relativeMatch[2] === 'm' ? 1_000_000 : 1_000
return currentBudget + base * multiplier
}
// 格式2:"use 2m tokens" → 2,000,000
const verboseMatch = str.match(/use\s+(\d+)(k|m)?\s+tokens?/)
if (verboseMatch) {
const base = parseInt(verboseMatch[1])
const multiplier = verboseMatch[2] === 'm' ? 1_000_000 : 1_000
return base * multiplier
}
// 格式3:"1000" → 1,000
const numericMatch = str.match(/^(\d+)$/)
if (numericMatch) {
return parseInt(numericMatch[1])
}
return null
}
10.4 成本追踪展示
// src/commands/cost/costCommand.ts(简化)
export const costCommand: LocalJSXCommand = {
type: 'local-jsx',
name: 'cost',
description: '查看本次会话的 API 使用成本',
component: function CostDisplay({ ctx }) {
const state = ctx.costTracker.getState()
return (
<Box flexDirection="column" padding={1}>
<Text bold>💰 会话成本统计</Text>
<Text>总成本: ${state.totalCostUSD.toFixed(4)}</Text>
<Text>输入 Tokens: {state.totalInputTokens.toLocaleString()}</Text>
<Text>输出 Tokens: {state.totalOutputTokens.toLocaleString()}</Text>
<Text>缓存读取: {state.totalCacheReadTokens.toLocaleString()}</Text>
<Text>新增代码行: +{state.totalLinesAdded}</Text>
<Text>删除代码行: -{state.totalLinesRemoved}</Text>
</Box>
)
},
}
第11章:插件系统
11.1 三层插件加载
Claude Code 的插件系统采用分层加载策略:
插件加载优先级(从高到低):
第1层:项目插件(.claude/plugins/)
↓ 覆盖同名插件
第2层:用户插件(~/.claude/plugins/)
↓ 覆盖同名插件
第3层:内置插件(src/plugins/built-in/)
// src/plugins/loadAllPlugins.ts(简化)
export async function loadAllPlugins(): Promise<Plugin[]> {
// 并行加载三层插件(提高启动速度)
const [builtInPlugins, userPlugins, projectPlugins] = await Promise.all([
loadBuiltInPlugins(),
loadUserPlugins(),
loadProjectPlugins(),
])
// 合并,高优先级覆盖低优先级
const pluginMap = new Map<string, Plugin>()
for (const plugin of [...builtInPlugins, ...userPlugins, ...projectPlugins]) {
pluginMap.set(plugin.name, plugin) // 后面的覆盖前面的
}
return Array.from(pluginMap.values())
}
11.2 插件版本管理
插件安装状态存储在 JSON 文件中,支持 V1→V2 格式迁移:
// installed_plugins.json 格式(V2)
{
"version": 2,
"plugins": [
{
"name": "my-plugin",
"version": "1.2.0",
"source": "npm:my-claude-plugin",
"installedAt": "2024-01-15T10:30:00Z",
"checksum": "sha256:abc123...",
"enabled": true
}
]
}
// 插件格式迁移
function migratePluginsV1ToV2(v1Data: PluginsV1): PluginsV2 {
return {
version: 2,
plugins: v1Data.installedPlugins.map(p => ({
name: p.id, // V1 用 id,V2 用 name
version: p.ver, // V1 用 ver,V2 用 version
source: p.registry, // V1 用 registry,V2 用 source
installedAt: new Date(p.ts).toISOString(),
checksum: p.hash,
enabled: !p.disabled,
})),
}
}
11.3 插件安全模型
// 插件安装需要信任对话
async function installPlugin(source: string): Promise<void> {
// 1. 展示插件信息,请求用户确认
const metadata = await fetchPluginMetadata(source)
const confirmed = await askUserConfirmation(
`安装插件: ${metadata.name} v${metadata.version}\n` +
`来源: ${source}\n` +
`作者: ${metadata.author}\n` +
`权限需求: ${metadata.permissions.join(', ')}\n` +
'\n确认安装?'
)
if (!confirmed) {
console.log('已取消安装')
return
}
// 2. 验证插件签名/校验和
const download = await downloadPlugin(source)
if (!verifyChecksum(download.data, metadata.checksum)) {
throw new Error('插件校验和验证失败,可能已被篡改')
}
// 3. 安装插件文件
await extractPlugin(download.data, getPluginInstallPath(metadata.name))
// 4. 记录安装信息
await recordPluginInstallation(metadata)
console.log(`✓ 插件 ${metadata.name} 安装成功`)
}
11.4 MCP 插件集成
MCP(Model Context Protocol)是 Anthropic 推出的开放协议,允许第三方扩展工具:
// src/commands/mcp/mcpCommand.ts MCP 管理(简化)
export async function connectMcpServer(config: McpServerConfig): Promise<void> {
// 支持多种传输方式
let transport: McpTransport
switch (config.transport) {
case 'stdio':
// 启动本地进程,通过 stdio 通信
transport = new StdioMcpTransport(config.command, config.args)
break
case 'sse':
// 通过 HTTP SSE 连接远程服务器
transport = new SseMcpTransport(config.url)
break
case 'websocket':
transport = new WebSocketMcpTransport(config.url)
break
}
const client = new McpClient(transport)
await client.connect()
// 自动发现 MCP 服务器提供的工具
const tools = await client.listTools()
// 将 MCP 工具包装为 Claude Code 工具
const wrappedTools = tools.map(tool => wrapMcpTool(tool, client))
// 注册到工具系统
toolRegistry.registerMcpTools(wrappedTools)
}
第12章:关键设计模式总结
12.1 失闭权限模型(Fail-Closed)
Claude Code 的安全哲学:当不确定时,拒绝;而非当不确定时,允许。
// ✅ 失闭设计:安全默认值
function buildTool(config: ToolConfig): Tool {
return {
isReadOnly: () => config.isReadOnly ?? false, // 默认:非只读(需要确认)
isDestructive: () => config.isDestructive ?? true, // 默认:破坏性(需要警告)
isConcurrencySafe: () => config.isConcurrencySafe ?? false, // 默认:非并发安全
}
}
// ❌ 失开设计(危险,不要这样做)
function buildToolUnsafe(config: ToolConfig): Tool {
return {
isReadOnly: () => config.isReadOnly ?? true, // 危险:默认只读,可能错误跳过确认
isDestructive: () => config.isDestructive ?? false, // 危险:默认非破坏性
}
}
12.2 流式异步处理(AsyncGenerator)
Claude Code 大量使用 AsyncGenerator 实现背压(Backpressure)和延迟计算:
// 流式处理的优势:
// 1. 内存效率:不需要一次性加载所有数据
// 2. 实时性:第一个结果立即显示,无需等待全部完成
// 3. 可取消:中途可以 break 退出
// 4. 可组合:多个 generator 可以链接
// 示例:链接多个异步生成器
async function* processQuery(input: string): AsyncGenerator<UIEvent> {
// 第1层:调用 API
for await (const apiEvent of callClaudeAPI(input)) {
// 第2层:处理流事件
for await (const toolEvent of handleStreamEvent(apiEvent)) {
// 第3层:转换为 UI 事件
yield convertToUIEvent(toolEvent)
}
}
}
12.3 功能开关系统(Feature Flags)
编译时消除未启用功能,减小二进制体积:
// src/constants/featureFlags.ts(示例)
export const FEATURE_FLAGS = {
PROACTIVE_SEARCH: process.env.CLAUDE_PROACTIVE_SEARCH === '1',
KAIROS_ENABLED: process.env.CLAUDE_KAIROS === '1',
NOTEBOOKS_ENABLED: process.env.CLAUDE_NOTEBOOKS === '1',
BETA_FEATURES: process.env.CLAUDE_BETA === '1',
} as const
// 类型安全的功能开关检查
type FeatureFlag = keyof typeof FEATURE_FLAGS
function isFeatureEnabled(flag: FeatureFlag): boolean {
return FEATURE_FLAGS[flag]
}
// 使用示例
if (isFeatureEnabled('PROACTIVE_SEARCH')) {
tools.push(WebSearchTool)
}
12.4 模块化工具架构(buildTool 工厂)
统一工厂函数确保所有工具遵循相同的安全契约:
// 工具开发者只需关注业务逻辑
// 安全、权限、Hook 等横切关注点由工厂统一处理
export const MyCustomTool = buildTool({
name: 'MyTool',
description: '我的自定义工具',
inputSchema: z.object({
input: z.string(),
}),
// 明确声明安全属性
isReadOnly: true,
isDestructive: false,
isConcurrencySafe: true,
// 只需实现核心逻辑
async call({ input }) {
return processInput(input)
},
})
// 工厂自动处理:
// ✓ 输入 Schema 验证(Zod)
// ✓ 权限检查
// ✓ Hook 执行(pre/post)
// ✓ 错误格式化
// ✓ 超时控制
// ✓ 并发安全检查
12.5 多层消息去重
消息到达 Bridge
│
▼
第1层:UUID 去重 ──── 已见过此 UUID?→ 丢弃
│ 未见过
▼
第2层:序列号去重 ─── 序列号 ≤ 上次?→ 丢弃
│ 序列号更新
▼
第3层:历史光标 ────── 在历史快照中?→ 丢弃
│ 新消息
▼
正常处理消息
12.6 指数退避重连
// 退避策略可视化:
//
// 时间轴:
// 0s ──断连── 2s ──重连失败── 4s ──重连失败── 8s ──重连失败──►
// 16s ...
//
// 公式:delay = min(2^attempt × baseDelay + jitter, maxDelay)
// attempt: 0 → delay: ~2s
// attempt: 1 → delay: ~4s
// attempt: 2 → delay: ~8s
// attempt: 3 → delay: ~16s
// attempt: 4 → delay: ~32s
// attempt: 5 → delay: ~64s
// attempt: 6+ → delay: 120s(上限)
//
// 放弃条件:累计时间 > 10分钟
12.7 NDJSON 会话持久化
会话文件:~/.claude/sessions/sess_xxx.jsonl
优点:
✓ 追加写入(O(1),无需读取整个文件)
✓ 断电安全(每行独立,部分写入不影响已有数据)
✓ 流式读取(逐行解析,内存友好)
✓ 易于调试(每行是合法 JSON,可用 jq 处理)
✓ 增量同步(只传输新增行,网络高效)
文件格式示例:
{"type":"metadata","sessionId":"sess_abc","createdAt":"2024-01-15T10:00:00Z"}
{"type":"user","content":"帮我写一个排序算法","timestamp":"2024-01-15T10:00:01Z"}
{"type":"assistant","content":"我来实现一个快速排序...","timestamp":"2024-01-15T10:00:05Z"}
{"type":"tool_use","name":"FileWriteTool","input":{"path":"sort.ts"},"id":"tu_001"}
{"type":"tool_result","tool_use_id":"tu_001","content":"文件已创建"}
{"type":"cost_state","totalCostUSD":0.0023,"inputTokens":1024,"outputTokens":512}
12.8 架构设计亮点总结
| 模式 | 位置 | 解决的问题 |
|---|---|---|
| 失闭权限 | Tool.ts | 防止权限过度放松 |
| AsyncGenerator | query.ts, QueryEngine.ts | 流式处理,实时响应 |
| 功能开关 | constants/featureFlags.ts | 灰度发布,编译时优化 |
| buildTool 工厂 | Tool.ts | 横切关注点统一处理 |
| 三层去重 | bridgeMessaging.ts | 消息幂等性保证 |
| 指数退避 | replBridgeTransport.ts | 避免重连风暴 |
| NDJSON 持久化 | sessionHistory.ts | 高性能会话存储 |
| Zustand 选择器 | AppStateStore.ts | 精准更新,避免过渲染 |
附录:调试技巧
查看调试日志
# 启用详细日志
CLAUDE_LOG_LEVEL=debug claude
# 启用 Bridge 调试
CLAUDE_BRIDGE_DEBUG=1 claude
# 启用性能分析
CLAUDE_PROFILE=1 claude
会话文件分析
# 查看最近的会话
ls -lt ~/.claude/sessions/ | head -5
# 分析会话内容(需要安装 jq)
cat ~/.claude/sessions/sess_xxx.jsonl | jq '. | select(.type == "cost_state")'
# 统计 token 使用
cat ~/.claude/sessions/sess_xxx.jsonl | jq '[.inputTokens // 0] | add'
插件调试
# 查看已安装插件
cat ~/.claude/installed_plugins.json | jq '.plugins[].name'
# 禁用特定插件
cat ~/.claude/installed_plugins.json | jq '.plugins[] |= if .name == "my-plugin" then .enabled = false else . end'
附录:关键源码文件索引
| 文件 | 行数 | 核心职责 |
|---|---|---|
src/main.tsx | 4684 | 程序主入口,启动协调 |
src/bridgeMain.ts 对应 src/bridge/bridgeMain.ts | 3000 | Bridge 连接管理 |
src/bridge/replBridge.ts | 2407 | Bridge 消息路由 |
src/QueryEngine.ts | 1296 | 查询引擎,Token 管理 |
src/bridge/remoteBridgeCore.ts | 1009 | 远程连接核心 |
src/Tool.ts | 793 | 工具系统基础 |
src/commands.ts | 755 | 命令注册中心 |
src/state/AppStateStore.ts | 570 | 全局状态管理 |
src/setup.ts | 478 | 初始化配置 |
src/cost-tracker.ts | 324 | 成本追踪 |
本教程基于 Claude Code 真实源码分析编写,面向希望深入理解 AI 编程助手实现原理的 TypeScript 开发者。
回复