CLI 入口与启动流程
深入理解 cli.tsx 的 Fast Path 优化、动态导入机制、以及零依赖启动策略
cli.tsx 架构概览
理解 CLI 入口文件的整体结构和职责划分
cli.tsx 是整个应用的性能瓶颈点。每次用户运行 claude-code,都会从这里开始。因此,这里的每毫秒延迟都会直接影响用户体验。Fast Path 策略确保 90% 的简单命令在 100ms 内响应。
Fast Path 实现细节
13个零模块命令的快速通道实现
// Fast Path: 零模块加载路径 if (process.argv.includes('--version')) { // ✓ 不加载任何 REPL 模块 // ✓ 不初始化 AI 客户端 // ✓ 不加载工具系统 console.log(getVersion()); process.exit(0); } // 其他 Fast Path 命令: // --help, --auth, config, setup, doctor, // update, compress-context, etc. // Slow Path: 完整 REPL 启动 await import('./repl'); // 动态加载
命令执行决策树
添加新的 Fast Path 命令时需要权衡:每个额外命令都会增加启动时的检查逻辑。建议只将高频使用且无需 REPL 环境的命令加入 Fast Path。
动态导入与懒加载机制
理解 ES Module 动态 import() 在启动优化中的应用
在大型 Node.js 应用中,将启动路径分为 Fast Path(简单命令)和 Slow Path(完整功能)是标准做法。关键是要识别出哪些操作不需要完整的应用环境。
完整启动流程图
从命令行到 REPL 的完整生命周期
代码理解测验
检验你对 CLI 启动流程的理解
以下哪个命令会走 Fast Path(零模块加载)?
为什么 cli.tsx 使用动态 import() 加载 REPL 模块?
分析以下代码,哪个描述是正确的?
Fast Path 策略主要优化了什么指标?
REPL 核心循环
深入理解 REPL.tsx 的 Query 状态机、40+ 自定义 Hooks 体系、错误恢复机制
REPL 架构概览
理解 REPL 的核心组件和职责划分
REPL 不是一个简单的循环,而是一个复杂的状态机。每个用户输入都会触发一系列状态转换,涉及 AI 调用、工具执行、流式响应等多个子系统。
Query 状态机详解
理解查询执行的完整生命周期
// Query 状态机的核心循环 async function* executeQuery(prompt: string) { const state = QueryState.IDLE; while (true) { switch (state) { case QueryState.IDLE: state = QueryState.THINKING; break; case QueryState.THINKING: // 调用 AI API response = await anthropic.messages.create(params); state = QueryState.STREAMING; break; case QueryState.STREAMING: // 流式输出响应 for await (const chunk of response) { yield chunk; } state = QueryState.TOOL_EXECUTION; break; case QueryState.TOOL_EXECUTION: // 执行工具调用 if (stopReason === 'tool_use') { toolResults = await executeTools(toolCalls); state = QueryState.THINKING; } else { state = QueryState.COMPLETE; } break; case QueryState.COMPLETE: return; } } }
自定义 Hooks 体系
深入理解 REPL 的 40+ 自定义 Hooks 及其依赖关系
REPL 使用自定义 Hooks 来封装复杂逻辑,每个 Hook 负责一个特定领域。这种设计使得代码模块化、可测试,并且易于维护。Hooks 之间可以相互依赖,形成复杂的依赖关系图。
三层错误恢复机制
深入理解 REPL 如何处理和恢复各种错误
// 第一层: Tool 重试 async function executeToolWithRetry( tool: Tool, maxRetries = 3 ): Promise<ToolResult> { for (let i = 0; i < maxRetries; i++) { try { return await tool.execute(); } catch (error) { if (i === maxRetries - 1) throw error; await sleep(Math.pow(2, i) * 1000); // 指数退避 } } } // 第二层: Query 重启 async function retryQuery( query: Query, context: Context ): Promise<QueryResult> { // 保存失败前的上下文 const snapshot = context.snapshot(); try { return await executeQuery(query); } catch (error) { // 恢复上下文并重试 context.restore(snapshot); return await executeQuery(query); } } // 第三层: REPL 恢复 function recoverREPL(error: Error) { console.error('致命错误,正在恢复 REPL...'); // 清理状态 // 重新初始化核心组件 // 返回到 IDLE 状态 }
过于激进的重试可能导致资源浪费和用户体验下降。需要根据错误类型智能选择重试策略:临时性错误(网络超时)可以重试,逻辑性错误(语法错误)应该直接返回。
代码理解测验
检验你对 REPL 核心循环的理解
在 Query 状态机中,TOOL_EXECUTION 状态之后会转换到哪个状态?
useToolExecutor() Hook 最可能依赖以下哪些 Hook?
当工具执行失败时,第一层错误恢复会做什么?
为什么 Query 状态机使用 while(true) 循环而不是递归?
Query 执行引擎
掌握 query.ts 的核心算法、流式响应处理、性能优化技巧
Query 引擎架构
理解 query.ts 的核心职责和设计原则
Query 引擎是整个系统的"大脑",它需要协调 AI、工具、用户界面等多个子系统。设计时优先考虑可观测性(可调试性)和可恢复性(错误处理)。
while(true) 循环深度解析
理解核心循环的每个细节
export async function* query( prompt: string, options: QueryOptions = {} ): AsyncGenerator<QueryEvent> { // ========== 初始化阶段 ========== const context = new QueryContext(options); const messages: Message[] = [ { role: 'user', content: prompt } ]; // ========== 核心循环 ========== while (true) { // 检查提前退出条件 if (context.shouldStop) { yield { type: 'stop', reason: context.stopReason }; break; } try { // 阶段 1: 调用 AI yield { type: 'thinking_start' }; const response = await anthropic.messages.create({ model: "claude-sonnet-4-6", messages, stream: true, max_tokens: 8192, tools: context.availableTools }); // 阶段 2: 流式处理响应 let fullContent = ''; let toolCalls: ToolCall[] = []; for await (const chunk of response) { if (chunk.type === 'content_block_delta') { fullContent += chunk.delta.text; yield { type: 'content', text: chunk.delta.text }; } if (chunk.type === 'content_block_stop') { yield { type: 'thinking_end' }; } if (chunk.type === 'tool_use') { toolCalls.push(chunk.toolUse); } } // 阶段 3: 处理工具调用 if (toolCalls.length > 0) { yield { type: 'tool_execution_start', count: toolCalls.length }; const toolResults = await executeTools( toolCalls, context ); // 将工具结果添加到消息历史 messages.push({ role: 'assistant', content: fullContent, tool_calls: toolCalls }); messages.push({ role: 'user', content: toolResults }); // 继续循环,返回 THINKING 状态 continue; } // 阶段 4: 完成 yield { type: 'complete', content: fullContent }; break; } catch (error) { // 错误处理 const recovered = await context.handleError(error); if (!recovered) { yield { type: 'error', error: error.message }; break; } } } }
1. 生成器函数:使用 async function* 实现流式输出
2. 消息累积:每次循环保留完整对话历史
3. 工具循环:工具调用后自动返回 THINKING 状态
4. 错误恢复:错误不立即退出,尝试恢复后继续
流式响应处理机制
深入理解 Server-Sent Events (SSE) 和流式渲染
// 流式响应处理器 class StreamHandler { private renderer: StreamRenderer; private buffer: string[] = []; private flushInterval = 16; // ~60fps async handleStream( stream: AsyncGenerator<QueryEvent> ): Promise<void> { const timer = setInterval(() => { this.flush(); }, this.flushInterval); try { for await (const event of stream) { switch (event.type) { case 'content': // 累积到缓冲区 this.buffer.push(event.text); break; case 'thinking_start': this.renderer.showThinking(); break; case 'tool_execution_start': this.renderer.showToolExecution( event.count ); break; case 'complete': this.flush(); this.renderer.finalize(event.content); return; } } } finally { clearInterval(timer); } } private flush() { if (this.buffer.length === 0) return; const content = this.buffer.join(''); this.renderer.append(content); this.buffer = []; } }
性能优化技巧
Query 引擎中的关键优化策略
// 优化 1: 并行工具执行 async function executeToolsParallel( toolCalls: ToolCall[] ): Promise<ToolResult[]> { // 识别独立工具 const independentTools = findIndependentTools(toolCalls); const dependentTools = findDependentTools(toolCalls); // 并行执行独立工具 const parallelResults = await Promise.all( independentTools.map(tool => tool.execute()) ); // 串行执行依赖工具 const serialResults = []; for (const tool of dependentTools) { const result = await tool.execute(); serialResults.push(result); } return [...parallelResults, ...serialResults]; } // 优化 2: 上下文压缩 function compressContext( messages: Message[], maxTokens: number ): Message[] { const compressed: Message[] = []; let currentTokens = 0; // 从最新消息开始倒序遍历 for (let i = messages.length - 1; i >= 0; i--) { const msg = messages[i]; const tokens = estimateTokens(msg); if (currentTokens + tokens > maxTokens) { // 压缩旧消息 const summary = summarizeMessage(msg); compressed.unshift(summary); } else { compressed.unshift(msg); currentTokens += tokens; } } return compressed; } // 优化 3: 提前退出机制 class QueryContext { private aborted = false; abort() { this.aborted = true; } shouldStop() { return this.aborted || this.stopReason; } }
性能优化需要权衡多个因素:并行工具执行可能增加复杂性,上下文压缩可能丢失信息,缓存可能导致一致性挑战。建议基于实际性能分析数据选择优化策略。
代码理解测验
检验你对 Query 执行引擎的理解
在 while(true) 循环中,什么条件会跳出循环?
当检测到工具调用(toolCalls.length > 0)后,代码会做什么?
为什么使用 async function* 而不是普通函数?
并行工具执行的前提条件是什么?
最佳实践总结
从前三个模块中提炼的关键工程经验
1. 性能是用户体验的基础:从 CLI 启动到 Query 执行,每一毫秒都重要。
2. 错误处理是系统的生命线:三层错误恢复确保系统稳定性。
3. 状态机是复杂系统的最佳抽象:while(true) 循环简洁而强大。
4. 流式优先:用户不需要等待完整响应,增量输出提升体验。
🎉 恭喜完成课程!
你已经深入理解了 Claude Code 的核心架构和实现细节。
权限系统深度解析
深入理解 permissions.ts 的 13 步权限检查管道、7 种权限模式、风险评估逻辑
权限系统架构概览
理解 permissions.ts 的整体设计目标和核心组件
Claude Code 拥有强大的文件系统、网络、Shell 操作能力。权限系统是保护用户安全的最后一道防线,确保 AI 不会执行未经授权的危险操作。
13步权限检查管道
完整解析权限检查的每个步骤
async function checkPermissions( tool: Tool, input: ToolInput, context: PermissionContext ): Promise<PermissionDecision> { // ========== Step 1: 模式检查 ========== const mode = context.getPermissionMode(); if (mode === PermissionMode.BYPASS) { return { allowed: true, reason: 'bypass_mode' }; } // ========== Step 2: 工具白名单 ========== if (isWhitelistedTool(tool)) { return { allowed: true, reason: 'whitelisted' }; } // ========== Step 3: 缓存查询 ========== const cached = await cache.get(tool, input); if (cached) { return cached; } // ========== Step 4: 风险评估 ========== const risk = assessRisk(tool, input); // ========== Step 5: 低风险自动通过 ========== if (risk.level === RiskLevel.LOW) { return { allowed: true, reason: 'low_risk' }; } // ========== Step 6: 高风险必须确认 ========== if (risk.level === RiskLevel.HIGH) { if (mode === PermissionMode.AUTO) { return { allowed: false, reason: 'high_risk_auto_mode' }; } } // ========== Step 7: 中等风险检查模式 ========== if (risk.level === RiskLevel.MEDIUM) { if (mode === PermissionMode.AUTO) { return { allowed: true, reason: 'medium_risk_auto' }; } } // ========== Step 8: 计划模式检查 ========== if (mode === PermissionMode.PLAN) { return { allowed: false, reason: 'plan_mode_readonly' }; } // ========== Step 9: 用户确认检查 ========== if (mode === PermissionMode.CONFIRM) { const confirmed = await promptUser(tool, input, risk); if (!confirmed) { return { allowed: false, reason: 'user_denied' }; } } // ========== Step 10: 不再询问模式 ========== if (mode === PermissionMode.DONT_ASK) { return { allowed: true, reason: 'dont_ask_mode' }; } // ========== Step 11: 接受编辑模式 ========== if (mode === PermissionMode.ACCEPT_EDITS) { if (tool.isEditOperation()) { return { allowed: true, reason: 'accept_edits_mode' }; } } // ========== Step 12: 冒泡模式 ========== if (mode === PermissionMode.BUBBLE) { return { allowed: true, requiresConfirmation: true, reason: 'bubble_mode' }; } // ========== Step 13: 缓存决策 ========== const decision = { allowed: true, reason: 'default_allow' }; await cache.set(tool, input, decision); return decision; }
13步权限检查流程
13步管道提供了细粒度的控制点,每个步骤都可以快速返回,避免不必要的计算。这种设计使得权限检查既安全又高效。
7种权限模式对比
深入理解每种权限模式的使用场景
enum PermissionMode { // 默认:基于风险的智能决策 DEFAULT = 'default', // 只读:拒绝所有修改 PLAN = 'plan', // 自动接受编辑 ACCEPT_EDITS = 'acceptEdits', // 完全跳过权限(危险) BYPASS_PERMISSIONS = 'bypassPermissions', // 不询问,自动允许 DONT_ASK = 'dontAsk', // 自动模式(高风险拒绝) AUTO = 'auto', // 冒泡确认 BUBBLE = 'bubble' } // 使用示例 const decision = await checkPermissions( tool, input, { mode: PermissionMode.PLAN } // 只读模式 );
风险评估系统
理解如何评估工具调用的风险等级
interface RiskAssessment { level: RiskLevel; factors: RiskFactor[]; confidence: number; } function assessRisk( tool: Tool, input: ToolInput ): RiskAssessment { const factors: RiskFactor[] = []; let score = 0; // 因子1: 操作类型 if (tool.isDestructive()) { factors.push({ type: 'destructive_operation', weight: 0.4 }); score += 40; } // 因子2: 文件数量 const fileCount = input.getFileCount(); if (fileCount > 10) { factors.push({ type: 'batch_operation', weight: 0.3 }); score += 30; } // 因子3: 路径敏感性 if (input.affectsSystemPath()) { factors.push({ type: 'system_path', weight: 0.5 }); score += 50; } // 因子4: 网络操作 if (tool.isNetworkOperation()) { factors.push({ type: 'network_access', weight: 0.3 }); score += 30; } // 计算风险等级 let level: RiskLevel; if (score >= 70) level = RiskLevel.HIGH; else if (score >= 40) level = RiskLevel.MEDIUM; else level = RiskLevel.LOW; return { level, factors, confidence: calculateConfidence(factors) }; }
风险评估基于启发式规则,无法覆盖所有场景。对于关键操作(如 rm -rf、git push --force),应该强制要求用户确认,无论风险评估结果如何。
权限缓存机制
理解如何避免重复的权限检查
class PermissionCache { private cache = new Map<string, CachedDecision>(); private ttl = 5 * 60 * 1000; // 5分钟过期 async get( tool: Tool, input: ToolInput ): Promise<CachedDecision | null> { const key = this.generateKey(tool, input); const cached = this.cache.get(key); if (!cached) return null; // 检查是否过期 if (Date.now() - cached.timestamp > this.ttl) { this.cache.delete(key); return null; } // 检查上下文是否变化 if (!this.isContextValid(cached, input)) { this.cache.delete(key); return null; } return cached; } async set( tool: Tool, input: ToolInput, decision: PermissionDecision ): Promise<void> { const key = this.generateKey(tool, input); this.cache.set(key, { decision, timestamp: Date.now(), contextHash: this.hashContext(input) }); // 限制缓存大小 if (this.cache.size > 1000) { this.evictOldest(); } } private generateKey(tool: Tool, input: ToolInput): string { // 生成唯一的缓存键 return hash({ tool: tool.name, operation: input.operation, target: input.getTarget(), user: input.userId }); } private isContextValid( cached: CachedDecision, input: ToolInput ): boolean { // 检查文件是否被修改 if (input.hasFileChanged(cached.contextHash)) { return false; } // 检查权限模式是否变化 if (input.mode !== cached.mode) { return false; } return true; } }
1. TTL 过期:避免永久缓存导致权限决策过时
2. 上下文验证:文件变化后缓存失效
3. 大小限制:防止内存无限增长
4. 键生成:包含工具、操作、目标、用户信息
代码理解测验
检验你对权限系统的理解
在13步权限检查管道中,哪一步最先执行?
哪个权限模式会拒绝所有修改操作?
以下哪个操作会被评估为高风险(HIGH)?
权限缓存在什么情况下会失效?
Settings 配置系统
掌握 settings.ts 的 5层配置级联、三层缓存机制、MDM 集成
配置系统架构概览
理解 settings.ts 的设计目标和核心组件
优先级明确:命令行参数 > 环境变量 > 项目配置 > 全局配置 > MDM
层次清晰:每层配置职责明确,不会相互干扰
即时生效:配置变更后自动重载,无需重启
类型安全:TypeScript Schema 确保配置正确性
5层配置级联详解
理解配置合并和优先级处理
interface SettingsCascade { mdm?: Settings; // Layer 1: 最低优先级 global?: Settings; // Layer 2 project?: Settings; // Layer 3 env?: Settings; // Layer 4 cli?: Settings; // Layer 5: 最高优先级 } class SettingsManager { private cascade: SettingsCascade; function getEffectiveSettings(): Settings { // 从低到高合并配置 const merged: Settings = { // 从 MDM 开始(基础配置) ...this.cascade.mdm, // 全局配置覆盖 MDM ...this.cascade.global, // 项目配置覆盖全局 ...this.cascade.project, // 环境变量覆盖项目配置 ...this.cascade.env, // CLI 参数覆盖一切 ...this.cascade.cli }; // 特殊处理:数组类型配置 return this.mergeArrays(merged); } private mergeArrays(settings: Settings): Settings { // 某些配置需要数组合并而非覆盖 const result = { ...settings }; // 示例:工具白名单需要累加 result.toolWhitelist = [ ...(settings.mdm?.toolWhitelist || []), ...(settings.global?.toolWhitelist || []), ...(settings.project?.toolWhitelist || []), ...(settings.env?.toolWhitelist || []), ...(settings.cli?.toolWhitelist || []) ]; return result; } } // 使用示例 const settings = await Settings.load(); const effectiveSettings = settings.getEffective(); console.log(effectiveSettings.permissionMode); // 最终值
配置级联可视化
覆盖而非替换:高级配置只覆盖指定的字段,保留低级配置的其他值
数组特殊处理:某些配置(如工具白名单)需要累加而非覆盖
类型验证:每层配置都经过 Schema 验证,确保类型正确
三层缓存机制详解
理解配置读取的优化策略
class SettingsCache { // L1: 内存缓存 private memoryCache = new Map<string, any>(); private cacheTTL = 5000; // 5秒 // L2: 文件缓存 private fileCachePath = '~/.claude/settings.cache.json'; // L3: 默认值 private defaults = loadDefaults(); async get(key: string): Promise<any> { // ========== L1: 检查内存缓存 ========== const memCached = this.memoryCache.get(key); if (memCached && !this.isExpired(memCached)) { return memCached.value; } // ========== L2: 检查文件缓存 ========== const fileCached = await this.loadFromFileCache(key); if (fileCached) { // 回填内存缓存 this.memoryCache.set(key, { value: fileCached, timestamp: Date.now() }); return fileCached; } // ========== L3: 返回默认值 ========== return this.defaults[key]; } async set(key: string, value: any): Promise<void> { // 写入所有层 // L1: 更新内存 this.memoryCache.set(key, { value, timestamp: Date.now() }); // L2: 更新文件缓存 await this.saveToFileCache(key, value); // L3: 更新实际配置文件 await this.saveToSettingsFile(key, value); } // 文件监视器 private setupFileWatcher() { const watcher = fs.watch(this.settingsPath, async () => { // 文件变化时清除内存缓存 this.memoryCache.clear(); // 重新加载配置 await this.reload(); // 触发配置变更事件 this.emitter.emit('settings-changed'); }); } }
无缓存:每次读取文件 ~1-5ms,频繁读取严重影响性能
单层缓存:内存缓存 ~0.01ms,但文件变化不会自动更新
三层缓存:结合性能和实时性,文件监视确保配置即时生效
MDM(移动设备管理)集成
企业级配置管理方案
interface MDMConfig { // 企业策略配置 permissionMode?: PermissionMode; allowedDomains?: string[]; dataRetentionDays?: number; auditLoggingEnabled?: boolean; // 安全策略 disableDangerousTools?: boolean; requireEncryption?: boolean; allowedIPRanges?: string[]; // 合规配置 dataResidency?: string; // 'eu', 'us', 'asia' complianceMode?: boolean; } class MDMManager { private configPath = '/Library/Managed Preferences/com.anthropic.claude-code.plist'; async load(): Promise<MDMConfig | null> { try { // macOS MDM 配置路径 const plist = await readPlist(this.configPath); return { permissionMode: plist.permissionMode, allowedDomains: plist.allowedDomains, dataRetentionDays: plist.dataRetentionDays || 30, auditLoggingEnabled: plist.auditLoggingEnabled || false, disableDangerousTools: plist.disableDangerousTools || false, requireEncryption: plist.requireEncryption || false, allowedIPRanges: plist.allowedIPRanges || [], dataResidency: plist.dataResidency || 'us', complianceMode: plist.complianceMode || false }; } catch (error) { // 无 MDM 配置,返回 null return null; } } // 验证 MDM 配置合规性 validateCompliance(config: MDMConfig): ComplianceReport { const violations: string[] = []; // 检查数据驻留 if (config.dataResidency === 'eu') { // 确保 API 调用使用 EU 区域 violations.push(...this.checkDataResidency()); } // 检查加密要求 if (config.requireEncryption) { violations.push(...this.checkEncryption()); } return { compliant: violations.length === 0, violations, timestamp: Date.now() }; } } // 使用示例 const mdm = await MDMManager.load(); if (mdm?.disableDangerousTools) { // 禁用 rm、git push --force 等危险工具 ToolRegistry.disableDangerousTools(); }
MDM 配置的优先级最低,用户可以通过项目配置、环境变量或 CLI 参数覆盖。这是为了确保开发者在特殊情况下可以灵活调整配置,但同时企业策略会在常规使用中生效。
配置热更新机制
理解配置变更如何即时生效
class HotReloadManager { private watchers = new Map<string, FSWatcher>(); private debounceTimers = new Map<string, NodeJS.Timeout>(); watchFile(filePath: string, callback: () => void) { // 防抖:避免频繁触发 const debouncedCallback = debounce(callback, 500); const watcher = fs.watch(filePath, async (eventType) => { if (eventType === 'change') { // 等待文件写入完成 await sleep(100); // 重新加载配置 const newConfig = await this.loadConfig(filePath); // 验证配置 const validation = this.validateConfig(newConfig); if (!validation.valid) { console.error('配置验证失败:', validation.errors); return; } // 应用新配置 await this.applyConfig(newConfig); // 触发回调 debouncedCallback(); // 通知所有订阅者 this.emitter.emit('config-updated', newConfig); } }); this.watchers.set(filePath, watcher); } // 监听多个配置文件 watchAllConfigs() { // 全局配置 this.watchFile( '~/.claude/settings.json', () => console.log('全局配置已更新') ); // 项目配置 this.watchFile( './.claude/settings.json', () => console.log('项目配置已更新') ); // MDM 配置 this.watchFile( '/Library/Managed Preferences/com.anthropic.claude-code.plist', () => console.log('MDM 配置已更新') ); } // 清理监视器 dispose() { this.watchers.forEach(watcher => watcher.close()); this.watchers.clear(); } } // 全局实例 const hotReload = new HotReloadManager(); hotReload.watchAllConfigs();
1. 防抖处理:文件保存可能触发多次 change 事件
2. 配置验证:确保新配置符合 Schema
3. 原子更新:配置更新失败时回滚到旧配置
4. 事件通知:所有订阅者都能收到配置变更通知
代码理解测验
检验你对 Settings 配置系统的理解
以下哪种配置方式的优先级最高?
三层缓存中,哪一层访问速度最快?
MDM 配置的优先级如何?
配置热更新使用什么技术监听文件变化?
Agent 系统架构
理解 AgentTool.tsx 的子代理机制、Fork 模式、Prompt Cache 共享
Agent 系统架构概览
理解 Agent 系统的设计目标和核心组件
单个 AI 模型难以处理所有场景。通过 Agent 系统,我们可以:
1. 任务专业化:代码审查、设计评审、QA 测试各有专门 Agent
2. 并行执行:多个 Agent 可以同时工作
3. 错误隔离:一个 Agent 失败不影响其他 Agent
4. 资源优化:通过 Prompt Cache 减少重复计算
AgentTool 实现详解
理解如何将 Agent 封装为工具
interface AgentToolConfig { name: string; description: string; agentType: AgentType; systemPrompt: string; parameters: ToolParameter[]; } class AgentTool implements Tool { private config: AgentToolConfig; private agent: Agent; private cache: PromptCache; constructor(config: AgentToolConfig) { this.config = config; this.agent = this.createAgent(); this.cache = PromptCache.getInstance(); } // 工具元数据 get name() { return this.config.name; } get description() { return this.config.description; } // 工具执行入口 async execute( input: ToolInput, context: ToolContext ): Promise<ToolResult> { try { // 1. 构建完整的 Prompt const fullPrompt = this.buildPrompt(input); // 2. 检查 Prompt Cache const cached = await this.cache.get(fullPrompt); if (cached) { return { success: true, data: cached, cached: true }; } // 3. 执行 Agent const result = await this.agent.execute( fullPrompt, context ); // 4. 缓存结果 await this.cache.set(fullPrompt, result); // 5. 返回结果 return { success: true, data: result }; } catch (error) { return { success: false, error: error.message }; } } private createAgent(): Agent { switch (this.config.agentType) { case AgentType.FORK: return new ForkAgent(this.config); case AgentType.INLINE: return new InlineAgent(this.config); default: throw new Error(`Unknown agent type: ${this.config.agentType}`); } } private buildPrompt(input: ToolInput): string { return ` ${this.config.systemPrompt} 用户请求: ${input.prompt} 上下文信息: ${JSON.stringify(input.context, null, 2)} `.trim(); } } // 使用示例:注册 Code Review Agent const codeReviewTool = new AgentTool({ name: 'code_review', description: 'Review code changes and provide feedback', agentType: AgentType.FORK, systemPrompt: 'You are a code review expert...', parameters: [ { name: 'diff', type: 'string', required: true }, { name: 'baseBranch', type: 'string', required: false } ] }); ToolRegistry.register(codeReviewTool);
1. 统一接口:Agent 通过 Tool 接口调用,与 Bash、Read 等工具一致
2. 可组合性:可以与其他工具串联使用
3. 缓存友好:Prompt Cache 自动生效
4. 类型安全:强类型的参数定义
Fork Agent 深度解析
理解进程隔离和 context 共享机制
class ForkAgent implements Agent { private childProcess: ChildProcess | null = null; private config: AgentConfig; async execute( prompt: string, parentContext: Context ): Promise<AgentResult> { // ========== 步骤1: 准备共享 Context ========== const sharedContext = this.prepareSharedContext(parentContext); // ========== 步骤2: Fork 子进程 ========== this.childProcess = fork( './agent-worker.js', [this.config.name], { silent: true, // 不共享 stdio env: { ...process.env, CLAUDE_AGENT_MODE: 'fork', CLAUDE_AGENT_NAME: this.config.name } } ); // ========== 步骤3: 发送任务和 Context ========== this.childProcess.send({ type: 'execute', prompt, context: sharedContext, config: this.config }); // ========== 步骤4: 等待结果 ========== return new Promise((resolve, reject) => { let result: AgentResult | null = null; // 接收子进程消息 this.childProcess!.on('message', (msg) => { switch (msg.type) { case 'progress': // 转发进度更新 parentContext.emitProgress(msg.progress); break; case 'result': result = msg.result; break; case 'error': reject(new Error(msg.error)); break; } }); // 监听进程退出 this.childProcess!.on('exit', (code) => { if (code === 0) { resolve(result!); } else { reject(new Error(`Agent exited with code ${code}`)); } }); // 超时保护 setTimeout(() => { this.childProcess!.kill('SIGTERM'); reject(new Error('Agent execution timeout')); }, this.config.timeout || 30000); }); } // 准备共享的 Context private prepareSharedContext(parentContext: Context): SharedContext { return { // 共享文件系统状态 workingDirectory: parentContext.cwd, // 共享环境变量(过滤敏感信息) env: this.filterEnvVars(process.env), // 共享工具注册表(只读) toolRegistry: parentContext.toolRegistry.serialize(), // 共享 Prompt Cache(只读) promptCache: parentContext.promptCache.export(), // 不共享:AI 客户端、状态变量 // 子进程需要创建自己的 AI 实例 }; } } // agent-worker.js (子进程入口) if (process.env.CLAUDE_AGENT_MODE === 'fork') { process.on('message', async (msg) => { if (msg.type === 'execute') { const { prompt, context, config } = msg; // 创建独立的 AI 客户端 const agent = new InlineAgent(config); try { // 执行任务 const result = await agent.execute(prompt, context); // 发送结果回父进程 process.send({ type: 'result', result }); } catch (error) { process.send({ type: 'error', error: error.message }); } // 退出子进程 process.exit(0); } }); }
Fork Agent:进程隔离,适合 CPU 密集型、可能崩溃的任务
Inline Agent:同进程执行,开销小但影响主进程稳定性
选择建议:代码审查、设计评审用 Fork;简单查询用 Inline
Prompt Cache 共享机制
理解如何在 Agent 间共享 Prompt 结果
class PromptCache { private cache = new Map<string, CachedPrompt>(); private redis?: Redis; private static instance: PromptCache; static getInstance(): PromptCache { if (!PromptCache.instance) { PromptCache.instance = new PromptCache(); } return PromptCache.instance; } async get(prompt: string): Promise<Anthropic.Message | null> { const hash = this.hashPrompt(prompt); // L1: 内存缓存 const memCached = this.cache.get(hash); if (memCached && !this.isExpired(memCached)) { return memCached.result; } // L2: Redis 缓存(跨进程共享) if (this.redis) { const redisCached = await this.redis.get(`prompt:${hash}`); if (redisCached) { const result = JSON.parse(redisCached); // 回填内存缓存 this.cache.set(hash, { result, timestamp: Date.now() }); return result; } } return null; } async set(prompt: string, result: Anthropic.Message): Promise<void> { const hash = this.hashPrompt(prompt); const cached: CachedPrompt = { result, timestamp: Date.now() }; // L1: 内存缓存 this.cache.set(hash, cached); // L2: Redis 缓存 if (this.redis) { await this.redis.setex( `prompt:${hash}`, 86400, // 24小时 JSON.stringify(result) ); } } private hashPrompt(prompt: string): string { const normalized = prompt.trim().toLowerCase(); return createHash('sha256') .update(normalized) .digest('hex'); } // 为 Fork Agent 导出缓存 async exportForFork(): Promise<Map<string, CachedPrompt>> { // Fork Agent 可以读取但不能写入主进程缓存 return new Map(this.cache); } // 清理过期缓存 private isExpired(cached: CachedPrompt): boolean { const age = Date.now() - cached.timestamp; return age > 86400000; // 24小时 } } // 在 AgentTool 中使用 const result = await this.cache.get(fullPrompt); if (result) { console.log('✓ Prompt cache hit'); return result; } // Cache miss,调用 AI const aiResult = await anthropic.messages.create(...); await this.cache.set(fullPrompt, aiResult);
缓存命中率:~60-70%(代码审查、设计评审等重复任务)
延迟降低:从 2-5s 降至 ~10ms
API 调用减少:~65%
成本节省:每月可节省数千次 API 调用
内置 Agent 生态
探索 10+ 个专业化的内置 Agent
// 内置 Agent 注册表 const builtinAgents = registerBuiltinAgents() { return [ // 代码质量 new AgentTool({ name: 'review', agentType: AgentType.FORK, systemPrompt: 'You are a code review expert...' }), new AgentTool({ name: 'codex', agentType: AgentType.FORK, systemPrompt: 'You are an independent code reviewer...' }), // 测试 new AgentTool({ name: 'qa', agentType: AgentType.FORK, systemPrompt: 'You are a QA testing expert...' }), new AgentTool({ name: 'benchmark', agentType: AgentType.FORK, systemPrompt: 'You are a performance testing expert...' }), // 安全 new AgentTool({ name: 'cso', agentType: AgentType.FORK, systemPrompt: 'You are a security officer...' }), // 产品和架构 new AgentTool({ name: 'plan-ceo-review', agentType: AgentType.INLINE, systemPrompt: 'You are a CEO reviewing product plans...' }), new AgentTool({ name: 'plan-eng-review', agentType: AgentType.INLINE, systemPrompt: 'You are an engineering manager...' }), // DevOps new AgentTool({ name: 'ship', agentType: AgentType.INLINE, systemPrompt: 'You are a release automation expert...' }) ]; }; // 注册所有内置 Agent builtinAgents().forEach(agent => { ToolRegistry.register(agent); }); console.log(`✓ Registered ${builtinAgents().length} builtin agents`);
代码理解测验
检验你对 Agent 系统的理解
Agent 为什么要封装为 Tool 接口?
Fork Agent 和 Inline Agent 的主要区别是什么?
Prompt Cache 如何实现跨进程共享?
对于代码审查任务,应该选择哪种 Agent 类型和哪个内置 Agent?
课程总结
回顾6个模块的核心知识点
完成这6个模块后,你已经:
1. 掌握了大型 Node.js 应用的架构设计
2. 理解了复杂状态机的实现模式
3. 学会了多层缓存和性能优化策略
4. 深入了解了权限和安全系统的设计
5. 掌握了 Agent 系统和进程间通信
🎉 恭喜完成全部课程!
你已经深入理解了 Claude Code 的完整技术架构。
工具系统架构
深入理解 Tool 接口定义、生命周期管理、并行执行机制和权限检查集成
Tool 接口定义与生命周期
理解 Tool.ts 的核心接口设计和工具生命周期
// ========== Tool 接口核心定义 ========== interface Tool { // 工具元数据 readonly name: string; readonly description: string; readonly category: ToolCategory; readonly permissions: Permission[]; // 输入输出 Schema (JSON Schema) readonly inputSchema: JSONSchema; readonly outputSchema?: JSONSchema; // ========== 生命周期方法 ========== // 1. 验证阶段:检查参数有效性 validate(params: unknown): ValidationResult; // 2. 权限检查:验证用户权限 checkPermissions( user: User, params: unknown ): PermissionCheck; // 3. 执行阶段:核心业务逻辑 execute( params: unknown, context: ExecutionContext ): Promise<ToolResult>; // 4. 清理阶段:释放资源 cleanup?() => Promise<void>; // ========== 可选钩子 ========== // 执行前钩子 beforeExecute?(params: unknown) => Promise<unknown>; // 执行后钩子 afterExecute?(result: ToolResult) => Promise<ToolResult>; // 错误处理钩子 onError?(error: Error) => Promise<ToolResult | null>; } // ========== 工具类别枚举 ========== enum ToolCategory { FILE = 'file', BASH = 'bash', SEARCH = 'search', GIT = 'git', MEMORY = 'memory', AGENT = 'agent', WEB = 'web' } // ========== 权限级别 ========== enum Permission { READ_FILES = 'read:files', WRITE_FILES = 'write:files', EXECUTE_BASH = 'execute:bash', MODIFY_GIT = 'modify:git', NETWORK_ACCESS = 'network:access' }
工具生命周期流程图
工具的生命周期设计借鉴了中间件模式,每个阶段都可以注入自定义逻辑。这种设计使得工具系统既有统一的执行流程,又能保持各个工具的灵活性。
buildTool 工厂模式
理解如何使用工厂模式创建和注册工具
// ========== 工厂模式实现 ========== function buildTool<TInput = unknown, TOutput = unknown>(config: { name: string; description: string; category: ToolCategory; inputSchema: JSONSchema<TInput>; outputSchema?: JSONSchema<TOutput>; permissions?: Permission[]; execute: (params: TInput, context: ExecutionContext) => Promise<TOutput>; beforeExecute?: (params: TInput) => Promise<TInput>; afterExecute?: (result: TOutput) => Promise<TOutput>; onError?: (error: Error) => Promise<TOutput | null>; cleanup?: () => Promise<void>; }): Tool { return { name: config.name, description: config.description, category: config.category, permissions: config.permissions || [], inputSchema: config.inputSchema, outputSchema: config.outputSchema, validate(params: unknown): ValidationResult { return validateJSONSchema(params, config.inputSchema); }, checkPermissions(user: User, params: unknown): PermissionCheck { return config.permissions?.every(perm => user.hasPermission(perm)) ? { granted: true } : { granted: false, reason: 'Missing permissions' }; }, async execute(params: unknown, context: ExecutionContext): Promise<ToolResult> { let processedParams = params; if (config.beforeExecute) { processedParams = await config.beforeExecute(processedParams as TInput); } let result: TOutput; try { result = await config.execute(processedParams as TInput, context); } catch (error) { if (config.onError) { const recovered = await config.onError(error as Error); if (recovered !== null) return { success: true, data: recovered }; } throw error; } if (config.afterExecute) { result = await config.afterExecute(result); } return { success: true, data: result }; }, cleanup: config.cleanup }; }
1. 类型安全:泛型确保输入输出类型正确
2. 统一接口:所有工具遵循相同的生命周期
3. 可扩展:钩子机制允许添加自定义逻辑
4. 声明式:配置清晰,易于理解和维护
StreamingToolExecutor 并行执行机制
深入理解工具并行执行算法和依赖关系解析
// ========== 并行工具执行核心算法 ========== class StreamingToolExecutor { async executeTools( toolCalls: ToolCall[] ): AsyncGenerator<ToolExecutionEvent> { // ========== 阶段 1: 依赖分析 ========== const dependencyGraph = await this.analyzeDependencies(toolCalls); const independentTools = dependencyGraph.getIndependentTools(); const dependentTools = dependencyGraph.getDependentTools(); // ========== 阶段 2: 并行执行独立工具 ========== if (independentTools.length > 0) { const parallelResults = await Promise.all( independentTools.map(tool => this.executeSingleTool(tool)) ); } // ========== 阶段 3: 串行执行依赖工具 ========== for (const toolBatch of dependentTools) { for (const toolCall of toolBatch) { await this.executeSingleTool(toolCall); } } } // ========== 依赖分析:拓扑排序 ========== private async analyzeDependencies( toolCalls: ToolCall[] ): Promise<DependencyGraph> { const graph = new DependencyGraph(); // 检测资源冲突和数据依赖 for (let i = 0; i < toolCalls.length; i++) { for (let j = i + 1; j < toolCalls.length; j++) { if (this.hasResourceConflict(toolCalls[i], toolCalls[j])) { graph.addDependency(toolCalls[i].id, toolCalls[j].id); } } } return graph.topologicalSort(); } }
并行工具执行面临多个挑战:1) 资源冲突检测可能遗漏隐式依赖 2) 拓扑排序可能产生循环依赖 3) 错误处理在并行环境下更复杂。建议在工具注册时明确声明依赖关系。
代码理解测验
检验你对工具系统架构的理解
Tool 生命周期中,权限检查在哪个阶段执行?
上下文系统
掌握 System Context 和 User Context 的双上下文系统、并行 Git 操作优化、Memoization 策略
双上下文系统架构
理解 System Context 和 User Context 的职责划分
// ========== 双上下文系统定义 ========== interface SystemContext { // 系统环境信息 readonly env: { os: string; shell: string; }; readonly workingDirectory: { current: string; gitRoot?: string; }; readonly gitState: GitState; readonly toolRegistry: ToolRegistry; readonly permissions: Permission[]; } interface UserContext { readonly prompt: string; readonly messages: Message[]; readonly memory: SessionMemory; readonly preferences: UserPreferences; readonly currentTask?: Task; }
System Context 是相对静态的、全局的,初始化后变化较少;User Context 是动态的、会话相关的,每次查询都可能更新。分离两者可以优化缓存策略和更新频率。
并行 Git 操作优化
深入理解 Git 命令的并行执行策略
// ========== 并行 Git 状态获取 ========== async function getGitStateParallel(cwd: string): Promise<GitState> { // 并行执行 5 个独立的 Git 命令 const results = await Promise.all([ execGit(['status', '--porcelain'], cwd), execGit(['rev-parse', '--abbrev-ref', 'HEAD'], cwd), execGit(['rev-parse', 'HEAD'], cwd), execGit(['remote', '-v'], cwd), execGit(['config', '--get', 'user.email'], cwd) ]); return { status: parseGitStatus(results[0]), branch: results[1].trim(), commit: results[2].trim(), remote: parseGitRemote(results[3]), userEmail: results[4].trim() }; } // ========== Git Status 超截机制 ========== function parseGitStatus(output: string): GitStatus { const MAX_FILES = 100; const lines = output.split('\n').slice(0, MAX_FILES); const status = { modified: [], added: [], deleted: [], untracked: [] }; for (const line of lines) { const [statusCode, ...pathParts] = line.split(' '); const filePath = pathParts.join(' '); switch (statusCode) { case 'M': status.modified.push(filePath); break; case 'A': status.added.push(filePath); break; case 'D': status.deleted.push(filePath); break; case '??': status.untracked.push(filePath); break; } } return status; }
Git status 在大型仓库中可能返回上千个文件,导致上下文爆炸。超截到 100 个文件可以保护 Token 使用量,但可能丢失重要信息。
代码理解测验
检验你对上下文系统的理解
System Context 和 User Context 的主要区别是什么?
Memory 系统
理解基于阈值的自动记忆提取、四种 Memory 类型、Fork Agent 集成
Memory 系统架构
理解四种 Memory 类型和提取阈值机制
// ========== Memory 类型定义 ========== enum MemoryType { USER = 'user', // 用户偏好 PROJECT = 'project', // 项目信息 SESSION = 'session', // 会话上下文 TOOL = 'tool' // 工具使用数据 } interface Memory { id: string; type: MemoryType; content: string; metadata: { importance: number; // 重要性评分 (0-1) frequency: number; tags: string[]; confidence: number; }; createdAt: number; lastAccessed: number; } // ========== 重要性阈值过滤 ========== class SessionMemory { async addCandidateMemory( content: string, type: MemoryType ): Promise<boolean> { const importance = await this.calculateImportance(content, type); if (importance < this.config.importanceThreshold) { return false; // 不够重要,不存储 } this.memories.set(id, { content, type, metadata: { importance } }); return true; } }
阈值设置:太低会存储太多噪音,太高会丢失重要信息
时间衰减:旧信息逐渐降低权重,但不是立即删除
去重机制:相似内容合并而非重复存储
Fork Agent 集成
理解如何使用 Fork Agent 提取和存储 Memory
// ========== Fork Agent 用于 Memory 提取 ========== class MemoryExtractionAgent { async extractMemoriesFromConversation( messages: Message[] ): Promise<Memory[]> { const prompt = ` 分析以下对话,提取值得记忆的信息。 - 用户偏好(代码风格、工具偏好) - 项目信息(架构决策、代码约定) - 会话上下文(当前任务) - 工具使用模式 对话: ${messages}.map(m => m.content).join('\\n') `; const response = await anthropic.messages.create({ model: "claude-sonnet-4-6", messages: [{ role: 'user', content: prompt }], temperature: 0.3 }); const candidates = parseJSON(response.content[0].text); const memories: Memory[] = []; for (const candidate of candidates) { const added = await memory.addCandidateMemory( candidate.content, candidate.type ); if (added) memories.push(candidate); } return memories; } }
使用独立的 Fork Agent 进行 Memory 提取:1) 不影响主任务性能 2) 可以使用不同温度参数 3) 专注于提取任务,提高准确性 4) 可以批量处理历史对话
代码理解测验
检验你对 Memory 系统的理解
以下哪种信息应该存储为 PROJECT Memory?
课程总结
回顾所有模块的核心知识点
通过这 12 个模块的学习,你已经深入理解了:
1. 高性能启动:Fast Path 策略和动态导入
2. 状态机驱动:REPL 和 Query 的核心循环
3. 工具系统:并行执行和依赖解析
4. 上下文优化:双上下文和智能缓存
5. Memory 系统:阈值提取和 Fork Agent
6. MCP 集成:8种传输类型和动态工具发现
7. Feature Gates:编译时优化和死代码消除
8. Git 优化:零子进程和直接文件读取
🎉 恭喜完成全部课程!
MCP 系统架构
深入理解 Model Context Protocol 集成,掌握8种传输类型、连接管理和动态工具发现机制
MCP 架构概览
理解 Model Context Protocol 的整体架构和核心组件
MCP 提供标准化协议让 AI 应用能够连接外部数据源和工具。Claude Code 通过 MCP 实现:
• 扩展性:动态发现和加载 MCP 服务器的工具/资源
• 隔离性:每个 MCP 服务器运行在独立进程
• 可靠性:健康检查和自动重连机制
8种传输类型详解
深入理解不同传输方式的特点、适用场景和实现细节
📡 MCP 传输类型对比表
| 传输类型 | 连接方式 | 适用场景 | 性能 |
|---|---|---|---|
| STDIO | 标准输入/输出 | 本地 CLI 工具 | ★★★★★ |
| SSE | Server-Sent Events | HTTP 长连接 | ★★★★☆ |
| WebSocket | 双向通信 | 实时数据流 | ★★★★★ |
| HTTP | REST API | 无状态服务 | ★★★☆☆ |
1// 传输类型枚举和接口定义 2export enum TransportType { 3 STDIO = 'stdio', 4 SSE = 'sse', 5 WEBSOCKET = 'websocket', 6 HTTP = 'http', 7 // ... 其他4种类型 8} 9 10interface Transport { 11 connect(): Promise<Connection>; 12 send(message: JSONRPCMessage): Promise<void>; 13 onMessage(handler: (msg: JSONRPCMessage) => void): Disposable; 14 disconnect(): Promise<void>; 15}
连接管理和健康检查
掌握 MCP 服务器的连接生命周期管理和健康监控机制
1class MCPConnectionManager { 2 private connections = new Map<string, MCPConnection>(); 3 private healthCheckInterval: NodeJS.Timeout; 4 5 async connect(serverConfig: MCPServerConfig) { 6 const transport = this.createTransport(serverConfig); 7 const connection = new MCPConnection(transport); 8 9 // 建立连接并握手 10 await connection.connect(); 11 await connection.handshake(); 12 13 // 启动健康检查 14 this.startHealthCheck(connection); 15 16 this.connections.set(serverConfig.name, connection); 17 return connection; 18 } 19 20 startHealthCheck(connection: MCPConnection) { 21 const interval = setInterval(async () => { 22 const healthy = await connection.ping(); 23 if (!healthy) { 24 this.handleUnhealthy(connection); 25 } 26 }, 30000); // 每30秒检查一次 27 28 connection.healthCheckInterval = interval; 29 } 30}
动态工具和资源发现
理解 MCP 服务器工具和资源的动态发现、注册和调用机制
1class MCPToolDiscovery { 2 async discoverTools(connection: MCPConnection) { 3 // 调用 tools/list RPC 方法 4 const response = await connection.sendRequest({ 5 jsonrpc: '2.0', 6 id: generateId(), 7 method: 'tools/list', 8 params: {} 9 }); 10 11 // 转换为 Claude Code 工具格式 12 const tools = response.result.tools.map(mcpTool => ({ 13 name: `mcp_${connection.name}_${mcpTool.name}`, 14 description: mcpTool.description, 15 inputSchema: mcpTool.inputSchema, 16 handler: async (args) => { 17 // 调用 MCP 工具 18 return await connection.callTool(mcpTool.name, args); 19 } 20 })); 21 22 // 注册到工具系统 23 this.toolRegistry.registerTools(tools); 24 } 25}
🔧 MCP 工具调用流程
服务器生命周期管理
掌握 MCP 服务器的启动、监控、重启和清理机制
1class MCPServerLifecycle { 2 private servers = new Map<string, ServerProcess>(); 3 4 async startServer(config: MCPServerConfig) { 5 if (config.transport === TransportType.STDIO) { 6 // 启动子进程 7 const proc = spawn(config.command, config.args, { 8 stdio: ['pipe', 'pipe', 'pipe'], 9 env: { ...process.env, ...config.env } 10 }); 11 12 this.servers.set(config.name, { 13 process: proc, 14 config, 15 startTime: Date.now(), 16 restartCount: 0 17 }); 18 19 // 监听进程退出 20 proc.on('exit', () => { 21 this.handleServerExit(config.name); 22 }); 23 } 24 } 25 26 async handleServerExit(serverName: string) { 27 const server = this.servers.get(serverName); 28 29 // 检查是否需要重启 30 if (server.restartCount < MAX_RESTARTS) { 31 this.logger.info(`Restarting ${serverName}...`); 32 await this.startServer(server.config); 33 server.restartCount++; 34 } else { 35 this.logger.error(`Max restarts reached for ${serverName}`); 36 } 37 } 38}
延迟启动:MCP 服务器按需启动,而非全部在启动时加载
优雅关闭:发送 shutdown 请求后等待进程退出
资源清理:确保关闭时清理所有子进程和连接
知识检测
测试你对 MCP 系统架构的理解
MCP 的 STDIO 传输方式最适合什么场景?
Feature Gates 系统
深入理解编译时特性开关、Bun bundle 机制和死代码消除原理
Feature Gates 概览
理解 68 个编译时特性开关的设计理念和实现机制
Feature Gates 实现渐进式功能发布和编译时优化:
• 死代码消除:未启用的功能不会进入最终产物
• A/B 测试:通过 GrowthBook 运行时控制
• 降级策略:关键功能可独立禁用
• 性能优化:减少约 40% 的产物体积
68个编译时特性开关
探索核心特性开关及其作用
🎛️ 核心特性开关分类
1// 特性开关定义 2export const FEATURES = { 3 // 性能优化 4 fastPath: feature('fast-path', true), 5 lazyImport: feature('lazy-import', true), 6 responseCache: feature('response-cache', true), 7 8 // 工具系统 9 toolBash: feature('tool-bash', true), 10 toolGit: feature('tool-git', true), 11 toolEditor: feature('tool-editor', true), 12 13 // ... 68个特性开关 14} as const; 15 16export type FeatureName = keyof typeof FEATURES;
Bun bundle feature() 机制
深入理解 Bun 的条件编译和死代码消除原理
1import { feature } from 'bun:feature'; 2export function createBashTool() { 3 // 如果 feature 返回 false,整个函数会被消除 4 if (!feature('tool-bash')) { 5 return null; 6 } 7 // 这段代码只会在 tool-bash 启用时编译 8 const BashTool = await import('./tools/bash'); 9 return new BashTool(); 10} 11 12// 编译后的产物(如果 tool-bash = false) 13// export function createBashTool() { return null; }
产物体积:从 15MB 减少到 9MB(减少 40%)
启动时间:减少 25% 的初始化工作
内存占用:降低约 30% 的常驻内存
死代码消除原理
理解编译器如何识别和移除死代码
1// 源代码 2import { HeavyDependency } from './heavy'; 3 4if (feature('experimental')) { 5 const heavy = new HeavyDependency(); 6 heavy.doSomething(); 7} 8 9// 如果 experimental = false,编译后: 10// import 语句被删除(因为 HeavyDependency 未使用) 11// if 分支被删除 12// 整个 heavy 模块不会被打包
🔍 死代码消除示例
import { A, B, C } from './lib';
if (feature('a')) A();
if (feature('b')) B();
if (feature('c')) C();
// 只有 a=true, c=false
import { A } from './lib';
A();
GrowthBook 运行时配置
理解运行时特性开关和 A/B 测试机制
1import { GrowthBook } from '@growthbook/growthbook'; 2const gb = new GrowthBook({ 3 apiHost: 'https://cdn.growthbook.io', 4 clientKey: process.env.GROWTHBOOK_KEY, 5 enableDevMode: process.env.NODE_ENV === 'development' 6}); 7export async function isFeatureEnabled( 8 featureKey: string, 9 defaultValue: boolean = false ): Promise<boolean> { 10 // 等待特性配置加载 11 await gb.init(); 12 13 // 检查特性是否启用 14 const enabled = gb.isOn(featureKey); 15 16 // 记录特性使用情况 17 有关日志。debug(`Feature ${featureKey}: ${enabled}`); 18 19 return enabled ?? defaultValue; 20}
渐进式发布:先向 10% 用户开放,逐步扩大到 100%
A/B 测试:对比不同算法或 UI 的效果
快速回滚:发现问题可立即禁用,无需重新部署
用户分组:按用户属性定向展示功能
知识检测
测试你对 Feature Gates 系统的理解
死代码消除主要发生在哪个阶段?
Git 集成系统
掌握零子进程 Git 优化、直接 .git 文件读取和 GitFileWatcher 实现
Git 集成概览
理解零子进程 Git 优化的设计理念和性能提升
传统 Git 操作需要启动 git 子进程(如 `git status`),每次调用耗时 50-200ms。
Claude Code 通过直接读取 .git 文件,将耗时降低到 5-20ms,性能提升 10-40倍。
对于频繁的 Git 操作(如每次查询都检查状态),累计节省数百毫秒。
gitFilesystem.ts 实现
深入理解直接读取 .git 文件的核心实现
1class GitFilesystem { 2 private gitDir: string; 3 span class="highlight">4 constructor(repoPath: string) { 5 this.gitDir = join(repoPath, '.git'); 6 } 7 8 // 读取 HEAD 文件获取当前分支 9 async getCurrentBranch(): Promise<string> { 10 const headPath = join(this.gitDir, 'HEAD'); 11 const content = await fs.readFile(headPath, 'utf-8'); 12 13 // HEAD 内容格式: "ref: refs/heads/main\n" 14 const match = content.match(/^ref: refs\/heads\/(.+)\n?$/); 15 if (!match) throw new Error('Invalid HEAD format'); 16 return match[1]; 17 } 18 19 // 读取 refs/heads 获取所有分支 20 async getBranches(): Promise<string[]> { 21 const headsDir = join(this.gitDir, 'refs', 'heads'); 22 const entries = await fs.readdir(headsDir, { withFileTypes: true }); 23 const branches: string[] = []; 24 for (const entry of entries) { 25 if (entry.isDirectory()) { 26 // 处理嵌套分支(如 feature/xxx) 27 const nested = await this.getNestedBranches(join(headsDir, entry.name)); 28 branches.push(...nested); 29 } else if (entry.isFile()) { 30 branches.push(entry.name); 31 } 32 } 33 return branches; 34 } 35}
直接读取 .git 文件
掌握 Git 对象存储格式和解析方法
1class GitObjectParser { 2 // 解析 loose object(.git/objects/xx/xxxx...) 3 async parseLooseObject(sha: string): Promise<GitObject> { 4 const objectPath = join( 5 this.gitDir, 6 'objects', 7 sha.slice(0, 2), 8 sha.slice(2) 9 ); 10 const compressed = await fs.readFile(objectPath); 11 const decompressed = zlib.inflate(compressed); 12 // Git 对象格式: "type size\0content" 13 const nullIndex = decompressed.indexOf('\0'); 14 const header = decompressed.slice(0, nullIndex).toString(); 15 const [type, size] = header.split(' '); 16 const content = decompressed.slice(nullIndex + 1); 17 return { type, size, content }; 18 } 19 // 解析 pack file(.git/objects/pack/*.pack) 20 async parsePackFile(packPath: string) { 21 const pack = await fs.readFile(packPath); 22 // 解析 pack file 格式... 23 } 24}
.git/
├── HEAD # 当前分支引用
├── objects/ # Git 对象存储
│ ├── xx/ # loose objects
│ └── pack/ # pack files
├── refs/ # 引用
│ ├── heads/ # 分支引用
│ └── tags/ # 标签引用
└── index # 暂存区索引
GitFileWatcher 实现
理解如何高效监控 Git 状态变化
1class GitFileWatcher extends EventEmitter { 2 private watcher: FSWatcher; 3 private debounceTimer: NodeJS.Timeout; 4 constructor(private gitDir: string) { 5 super(); 6 this.setupWatcher(); 7 } 8 setupWatcher() { 9 // 监控关键 Git 文件 10 const watchPaths = [ 11 join(this.gitDir, 'HEAD'), 12 join(this.gitDir, 'index'), 13 join(this.gitDir, 'refs'), 14 ]; 15 this.watcher = fs.watch(gitDir, { recursive: true }, (eventType, filename) => { 16 if (this.isRelevantGitFile(filename)) { 17 this.debounceNotify(); 18 } 19 }); 20 } 21 debounceNotify() { 22 clearTimeout(this.debounceTimer); 23 this.debounceTimer = setTimeout(() => { 24 this.emit('change'); 25 }, 50); // 50ms 防抖 26 } 27 isRelevantGitFile(filename: string): boolean { 28 return filename === 'HEAD' || 29 filename === 'index' || 30 filename?.startsWith('refs/'); 31 } 32}
Worktree 和 Submodule 支持
理解高级 Git 功能的实现
1class GitWorktree { 2 // 读取 .git/worktrees 获取所有 worktree 3 async getWorktrees(repoPath: string): Promise<Worktree[]> { 4 const worktreesDir = join(repoPath, '.git', 'worktrees'); 5 try { 6 const entries = await fs.readdir(worktreesDir); 7 8 return Promise.all(entries.map(async (name) => { 9 const wtPath = join(worktreesDir, name); 10 const gitdir = join(wtPath, 'gitdir'); 11 const head = join(wtPath, 'HEAD'); 12 const [gitdirContent, headContent] = await Promise.all([ 13 fs.readFile(gitdir, 'utf-8'), 14 fs.readFile(head, 'utf-8') 15 ]); 16 return { 17 name, 18 path: gitdirContent.trim(), 19 head: headContent.trim() 20 }; 21 })); 22 } catch { 23 return []; // 没有 worktrees 24 } 25 } 26}
1class GitSubmodule { 2 // 解析 .gitmodules 文件 3 async getSubmodules(repoPath: string): Promise<Submodule[]> { 4 const modulesPath = join(repoPath, '.gitmodules'); 5 try { 6 const content = await fs.readFile(modulesPath, 'utf-8'); 7 const config = this.parseGitConfig(content); 8 return Object.entries(config).map(([path, info]) => ({ 9 path, 10 url: info.url, 11 branch: info.branch 12 })); 13 } catch { 14 return []; 15 } 16 } 17}
性能对比
零子进程 vs 传统 Git 命令的性能差异
⚡ Git 操作性能对比
| 操作 | 传统 Git | 零子进程 | 性能提升 |
|---|---|---|---|
| 获取当前分支 | 80ms | 8ms | 10x |
| 获取所有分支 | 150ms | 15ms | 10x |
| 读取 commit 信息 | 120ms | 12ms | 10x |
| 检查文件状态 | 200ms | 20ms | 10x |
对于一个典型的 Query 流程:
• 需要检查当前分支、读取 commit、获取文件状态等 5-10 次 Git 操作
• 传统方式:400-1000ms
• 零子进程:40-100ms
• 累计节省 350-900ms,显著提升用户体验
知识检测
测试你对 Git 集成系统的理解
零子进程 Git 优化的核心原理是什么?
Prompt 设计系统
深入理解 Static/Dynamic Prompt 分离和缓存优化机制
Prompt 系统架构
914行 prompts.ts 的完整设计
Static/Dynamic 分离是 Prompt 系统的核心优化策略。
Static Prompt 包含不变的内容:系统指令、工具定义、输出格式等。这些内容可以被预编译和缓存,大幅降低 token 消耗。
Dynamic Prompt 包含每次请求都会变化的内容:用户上下文、当前目录、Memory、启用的工具等。这些内容需要实时构建。
通过边界优化,我们精确控制哪些内容进入缓存,哪些内容实时构建,在性能和灵活性之间找到最佳平衡点。
Token 成本:Static Prompt 可能占据 5000+ tokens,如果每次都重新发送,成本巨大
缓存效率:分离后 Static Prompt 只需发送一次,Dynamic Prompt 按需构建
性能提升:缓存命中率提升 80%+,响应速度提升 3-5 倍
Static/Dynamic Prompt 分离机制
深入理解缓存边界和构建逻辑
// 🎯 关键:定义 Static 和 Dynamic Prompt 的边界 const SYSTEM_PROMPT_DYNAMIC_BOUNDARY = ' ═══════════════════════════════════════════════════════════════ STATIC PROMPT ABOVE - THIS LINE IS THE CACHE BOUNDARY ═══════════════════════════════════════════════════════════════ DYNAMIC PROMPT BELOW - NOT CACHED ═══════════════════════════════════════════════════════════════ ' // Static Prompt (可缓存部分) const STATIC_SECTIONS = [ '# System Instructions', '# Tool Definitions', '# Output Format Requirements', '# Safety Guidelines', // ... 其他不变内容 ] // Dynamic Prompt (实时构建部分) function buildDynamicPrompt(context: RequestContext): string { const sections = [] // 1. 当前工作目录 sections.push(`## Current Directory: ${context.cwd}`) // 2. Git 状态 if (context.isGit) { sections.push(`## Git Repository: ${context.gitBranch}`) } // 3. Memory 上下文 if (context.memories.length > 0) { sections.push('## Relevant Memory:') sections.push(context.memories.join('\n')) } // 4. 启用的工具列表 sections.push('## Available Tools:') sections.push(context.enabledTools.map(t => t.name).join(', ')) return sections.join('\n\n') }
Static 边界内:所有不会影响 LLM 行为的内容,如系统指令、工具描述格式、输出示例
Dynamic 边界外:所有会根据用户请求变化的内容,如文件路径、Git 状态、Memory、环境变量
Prompt 构建流程
Section 管理器
模块化 Prompt 构建系统
// 📦 Section 管理器 - 模块化 Prompt 构建 interface PromptSection { id: string priority: number static: boolean build: (context: RequestContext) => string } class SectionManager { private sections: Map<string, PromptSection> = new Map() // 注册新的 Section register(section: PromptSection) { this.sections.set(section.id, section) } // 构建 Static Prompt buildStatic(): string { const staticSections = Array.from(this.sections.values()) .filter(s => s.static) .sort((a, b) => a.priority - b.priority) .map(s => s.build({})) .join('\n\n') return staticSections + SYSTEM_PROMPT_DYNAMIC_BOUNDARY } // 构建 Dynamic Prompt buildDynamic(context: RequestContext): string { const dynamicSections = Array.from(this.sections.values()) .filter(s => !s.static) .sort((a, b) => a.priority - b.priority) .map(s => s.build(context)) .join('\n\n') return dynamicSections } }
模块化:每个 Prompt 部分独立管理,易于维护和扩展
优先级控制:通过 priority 确保 Prompt 部分的正确顺序
条件渲染:根据上下文动态决定是否包含某个 Section
测试友好:可以独立测试每个 Section 的构建逻辑
三层缓存优化
Global → Section → Context 缓存策略
缓存层次结构
永久缓存
会话内缓存
即时缓存
代码理解测验
检验你对 Prompt 系统的理解
SYSTEM_PROMPT_DYNAMIC_BOUNDARY 的作用是什么?
Global Cache 的命中率为什么能达到 95%+?
综合项目实践
将所学知识应用于真实场景,完成完整的工程实践
真实场景代码走查
分析完整的用户请求处理流程
完整请求流程
模块加载
状态机切换
Memory 加载
依赖解析
状态重置
// 🎯 场景:用户发送 "帮我优化 src/utils/format.ts 中的 formatDate 函数" // === 步骤 1: Query 循环接收 === // repl.ts - state machine async function handleQuery(userQuery: string) { currentState = State.QUERY // 解析意图 const intent = parseIntent(userQuery) } // === 步骤 2: 上下文构建 === // context.ts - dual context const context = await buildContext({ cwd: '/project', files: ['src/utils/format.ts'], memories: await memory.getRelevant(userQuery), gitStatus: await git.getStatus() }) // === 步骤 3: Prompt 构建 === // prompts.ts - section manager const prompt = sectionManager.buildFullPrompt({ static: cachedStaticPrompt, // 从缓存读取 dynamic: [ systemInstructions.build(context), fileContext.build('src/utils/format.ts'), toolDefinitions.build([ReadTool, EditTool]) ] }) // === 步骤 4: 工具并行执行 === // tool.ts - parallel execution const results = await executeToolsInParallel([ { tool: 'read', file: 'src/utils/format.ts' }, { tool: 'grep', pattern: 'formatDate', path: 'src/utils/format.ts' } ]) // === 步骤 5: LLM 调用和结果处理 === const optimized = await claude.optimizeFunction(prompt, results) // === 步骤 6: Memory 更新 === await memory.add({ type: 'PROJECT', content: '用户优化了 formatDate 函数,关注性能和可读性', tags: ['optimization', 'format.ts'] }) // === 步骤 7: 返回结果 === currentState = State.REPL return optimized
状态转换:REPL → QUERY → EXECUTE → REPL 的清晰流程
上下文复用:Static Prompt 从缓存读取,只构建 Dynamic 部分
并行优化:Read 和 Grep 工具并行执行,节省 50% 时间
Memory 学习:自动提取和存储项目信息,为未来请求提供上下文
性能优化实践
真实场景中的优化策略和效果
测量先行:使用 performance API 精确测量每个环节
缓存策略:区分可缓存和不可缓存内容
并行思维:识别可并行化的操作
渐进优化:从大处着手,逐步细化
常见陷阱和最佳实践
避免常见错误,遵循最佳实践
错误:将所有内容都缓存,包括频繁变化的数据
后果:返回过时结果,用户困惑
正确:只缓存不变的 Static Prompt,Dynamic Prompt 实时构建
策略:为不同类型的数据设置不同的 TTL
实现:Global Cache 永久,Section Cache 1 分钟,Context Cache 请求级
效果:平衡性能和准确性
✓ 启动优化:使用 Fast Path,延迟加载非核心模块
✓ 缓存策略:Static/Dynamic 分离,精确控制缓存边界
✓ 并行执行:构建依赖图,最大化并行度
✓ 上下文管理:双上下文策略,平衡灵活性和性能
✓ Memory 优化:设置阈值,智能淘汰低价值 Memory
✓ 错误处理:优雅降级,提供有意义的错误信息
✓ 监控指标:跟踪关键性能指标,持续优化
扩展开发指南
如何为 Claude Code 添加新功能
// 🎯 案例:添加一个新的 "SearchOnline" 工具 // === 步骤 1: 创建工具类 === export class SearchOnlineTool extends Tool { name = 'search_online' description = 'Search the web for current information' async execute(params: { query: string }) { return await searchOnline(params.query) } } // === 步骤 2: 创建 Prompt 定义 === export const SEARCH_ONLINE_SECTION = ` ## Search Online Tool Use this tool when you need to find current information. ` // === 步骤 3: 注册到系统 === // 在 prompts.ts 中注册工具 const ALL_TOOLS = [ { name: SEARCH_ONLINE_TOOL_NAME, section: SEARCH_ONLINE_SECTION } ] // === 步骤 4: 添加权限控制 === export const TOOL_PERMISSIONS = { [SEARCH_ONLINE_TOOL_NAME]: { allowedInModes: ['standard', 'careful'], requiresConfirmation: false } }
模块化:每个工具独立,职责单一
可测试:提供完整的测试覆盖
文档化:清晰的使用说明和示例
性能意识:考虑对整体性能的影响
安全优先:适当的权限控制和验证
课程总结与展望
回顾 14 个模块的核心知识点
完整知识体系
Fast Path + REPL + Query
RBAC + Settings + Agent
Tools + Context + Memory
Design + Optimization
通过这 14 个模块的学习,你已经深入理解了:
1. 高性能启动:Fast Path 策略和动态导入
2. 状态机驱动:REPL 和 Query 的核心循环
3. 工具系统:并行执行和依赖解析
4. 上下文优化:双上下文和智能缓存
5. Memory 系统:阈值提取和 Fork Agent
6. Prompt 设计:Static/Dynamic 分离和三层缓存
7. 性能优化:真实场景的优化策略
8. 工程实践:常见陷阱和最佳实践
✅ 初级工程师 重点学习:模块 1-3(启动和核心循环) 目标:理解 CLI 工具的基本架构 ⭐ 中级工程师 重点学习:模块 4-9(权限到 Memory) 目标:掌握复杂系统的设计和优化 🚀 高级工程师 重点学习:模块 13-14(Prompt 和实践) 目标:深入性能优化和架构设计 💡 进阶资源 • Claude API 文档:https://docs.anthropic.com • 源码仓库:github.com/anthropics/claude-code • 社区讨论:Claude Code Discord • 最佳实践:ARCHITECTURE.md
综合能力测验
检验你对整个 Claude Code 系统的理解
在添加新工具时,以下哪步不是必须的?
Static/Dynamic Prompt 分离的核心优势是什么?
在性能优化时,应该优先关注哪个指标?
三层缓存策略的正确顺序是?
Bash 解析器
纯 TypeScript 实现的 Bash 解析器,Tree-sitter 兼容 AST,UTF-8 字节偏移计算
Bash 解析器架构
文件: src/utils/bash/bashParser.ts (4,436 行)
核心职责:
- 纯 TypeScript 实现的 Bash 解析器
- 生成与 tree-sitter-bash 兼容的 AST
- 完整的词法分析(Lexer)和语法分析(Parser)
- UTF-8 字节偏移计算
解析器架构
🎯 设计目标
- 兼容性:与 tree-sitter-bash AST 兼容
- 性能:50ms 超时限制,50k 节点限制
- 安全性:处理对抗性输入
- 国际化:完整的 UTF-8 支持
词法分析(Lexer)
Lexer 负责将 Bash 源代码分解为 Token 流。
Token 类型(15种)
type TokenType =
| 'WORD' // 单词/标识符
| 'NUMBER' // 数字
| 'OP' // 操作符
| 'NEWLINE' // 换行符
| 'COMMENT' // 注释
| 'DQUOTE' // 双引号
| 'SQUOTE' // 单引号
| 'ANSI_C' // ANSI-C 字符串 ($'...')
| 'DOLLAR' // 美元符号
| 'DOLLAR_PAREN' // $(( 算术扩展
| 'DOLLAR_BRACE' // ${} 参数扩展
| 'BACKTICK' // 反引号
| 'LT_PAREN' // <(
| 'GT_PAREN' // >(
| 'EOF' // 文件结束
💡 中文解释
Bash 词法分析器识别 15 种不同的 Token 类型,覆盖了 Bash 语法的所有基本元素:
- 基本元素:WORD(单词)、NUMBER(数字)、OP(操作符)
- 字符串:DQUOTE(双引号)、SQUOTE(单引号)、ANSI_C(ANSI-C 转义)
- 变量扩展:DOLLAR、DOLLAR_PAREN、DOLLAR_BRACE、BACKTICK
- 其他:NEWLINE(换行)、COMMENT(注释)、EOF(结束)
Lexer 状态
type Lexer = {
src: string // 源代码
len: number // 源代码长度
i: number // JS string index (字符索引)
b: number // UTF-8 byte offset (字节偏移)
heredocs: HeredocPending[] // 待处理的 heredoc
byteTable: Uint32Array | null // 字节偏移表(懒加载)
}
🔍 关键设计
- 双索引系统:i(字符索引)和 b(字节偏移)分开维护
- Heredoc 队列:延迟处理,先收集后扫描
- 懒加载 byteTable:只在遇到非 ASCII 字符时才构建
- 性能优化:纯 ASCII 代码零开销
UTF-8 字节偏移计算
这是 Bash 解析器最复杂的部分之一,需要正确处理多字节 UTF-8 字符。
字符前进函数
function advance(L: Lexer): void {
const c = L.src.charCodeAt(L.i)
L.i++
if (c < 0x80) {
// ASCII: 1 字节 = 1 字符
L.b++
} else if (c < 0x800) {
// 2 字节 UTF-8
L.b += 2
} else if (c >= 0xd800 && c <= 0xdbff) {
// 高代理项:需要下一个字符完成代理对
L.b += 4
L.i++
} else {
// 3 字节 UTF-8
L.b += 3
}
}
📝 UTF-8 编码规则
- ASCII (0x00-0x7F):1 字节
- 2 字节 (0x80-0x7FF):2 字节
- 3 字节 (0x800-0xFFFF):3 字节
- 代理项 (0xD800-0xDFFF):4 字节
关键点:JavaScript 字符串索引(i)和 UTF-8 字节偏移(b)不同,需要分别维护。
字节偏移表(懒加载)
function byteAt(L: Lexer, charIdx: number): number {
// 快速路径:纯 ASCII 前缀
if (!L.byteTable) return charIdx
// 懒构建表:首次遇到非 ASCII 时构建
const t = new Uint32Array(L.len + 1)
let b = 0
let i = 0
while (i < L.len) {
t[i] = b
const c = L.src.charCodeAt(i)
if (c < 0x80) {
b++
i++
} else if (c < 0x800) {
b += 2
i++
} else if (c >= 0xd800 && c <= 0xdbff) {
t[i + 1] = b + 2
b += 4
i += 2
} else {
b += 3
i++
}
}
t[L.len] = b
L.byteTable = t
return t[charIdx]
}
⚡ 性能优化策略
- 快速路径:纯 ASCII 代码直接返回字符索引(零开销)
- 懒加载:只在需要时构建字节表
- O(1) 查找:建立后常数时间查找
- 内存优化:使用 Uint32Array,每个条目 4 字节
🎯 实际例子
// 输入: "echo 你好"
// 字符索引: 0 1 2 3 4 5 6 7
// 字符串: e c h o 你 好
// 字节偏移: 0 1 2 3 4 5 7 9
//
// '你' = U+4F60 (UTF-8: E4 BD A0, 3字节)
// '好' = U+597D (UTF-8: E5 A5 BD, 3字节)
语法分析(Parser)
Parser 负责将 Token 流转换为 AST(抽象语法树)。
ParseState
type ParseState = {
L: Lexer
src: string
srcBytes: number
/** True when byte offsets == char indices (no multi-byte UTF-8) */
isPureAscii: boolean
/** Parser node budget - bail out before OOM on deeply nested input */
nodes: number
inBacktick: number
}
🔍 关键字段
- isPureAscii:优化标志,纯 ASCII 时跳过字节偏移计算
- nodes:节点计数器,防止深度嵌套导致 OOM
- inBacktick:反引号嵌套层级
主解析函数
function parseSource(source: string, timeoutMs?: number): TsNode | null {
const startTime = Date.now()
// 1. 初始化 Lexer
const L = makeLexer(source)
// 2. 初始化 ParseState
const P: ParseState = {
L,
src: source,
srcBytes: L.b,
isPureAscii: true,
nodes: 0,
inBacktick: 0,
}
// 3. 解析程序
const program = parseProgram(P)
// 4. 超时检查
const elapsed = Date.now() - startTime
if (timeoutMs && elapsed > timeoutMs) {
return null
}
return program
}
📝 解析流程
- 初始化 Lexer(词法分析器)
- 初始化 ParseState(解析状态)
- 调用 parseProgram 解析整个程序
- 检查是否超时
- 返回 AST 或 null(超时)
Heredoc 和变量扩展
Heredoc 和变量扩展是 Bash 语法中最复杂的部分之一。
Heredoc 处理
type HeredocPending = {
delim: string // 分隔符
stripTabs: boolean // 是否去除制表符
quoted: boolean // 是否引用
bodyStart: number // body 开始位置
bodyEnd: number // body 结束位置
endStart: number // 结束标记开始
endEnd: number // 结束标记结束
}
// 词法分析:检测 << delimiter
if (c === '<' && c1 === '<') {
const start = L.b
advance(L)
advance(L)
const delim = parseHeredocDelimiter(L)
if (delim) {
L.heredocs.push({
delim: delim.delim,
stripTabs: delim.stripTabs,
quoted: delim.quoted,
bodyStart: -1,
bodyEnd: -1,
endStart: -1,
endEnd: -1,
})
}
}
🔍 Heredoc 工作流程
- 词法分析:检测
<< delimiter - 收集阶段:将 heredoc 信息加入队列
- 扫描阶段:遇到换行时扫描 heredoc body
- AST 构建:将 heredoc 作为节点加入 AST
变量扩展解析
function parseDollarLike(P: ParseState): TsNode | null {
// $(( 算术扩展
if (c1 === '(' && c2 === '(') {
advance(L)
advance(L)
advance(L)
const expansion = parseExpansionBody(P, true, false)
// ...
}
// $( 命令替换
if (c1 === '(') {
advance(L)
advance(L)
const expansion = parseExpansionBody(P, false, false)
// ...
}
// ${} 参数扩展
if (c1 === '{') {
advance(L)
advance(L)
const expansion = parseExpansionBody(P, false, true)
// ...
}
// $'...' ANSI-C 字符串
if (c1 === "'") {
advance(L)
advance(L)
// 扫描到结束的 '
// ...
}
// $ 简单变量
advance(L)
return parseBareWord(P)
}
💡 变量扩展类型
- $((...)):算术扩展
- $(...):命令替换
- ${...}:参数扩展
- $'...':ANSI-C 转义字符串
- $var:简单变量扩展
性能优化
Bash 解析器实现了多层性能优化,确保在各种场景下都能快速响应。
⚡ 优化策略
1. 超时保护
const PARSE_TIMEOUT_MS = 50 // 50ms 墙钟超时
function parseSource(source: string, timeoutMs?: number): TsNode | null {
const startTime = Date.now()
// ... 解析逻辑
const elapsed = Date.now() - startTime
if (timeoutMs && elapsed > timeoutMs) {
return null // 超时保护
}
}
目的:防止恶意输入导致无限循环
2. 节点预算
const MAX_NODES = 50_000 // 50k 节点限制
function parseStatements(P: ParseState): TsNode[] {
const out: TsNode[] = []
while (P.nodes < MAX_NODES) { // 检查节点预算
// ... 解析逻辑
}
return out
}
目的:防止深度嵌套导致 OOM
3. 字节偏移表懒加载
type Lexer = {
byteTable: Uint32Array | null // 懒加载
}
function byteAt(L: Lexer, charIdx: number): number {
// 快速路径:纯 ASCII
if (!L.byteTable) return charIdx
// 懒加载:首次遇到非 ASCII 时构建
// ...
}
目的:纯 ASCII 代码零开销
4. 纯 ASCII 快速路径
type ParseState = {
isPureAscii: boolean // 纯 ASCII 标志
}
// 在 advance 中更新标志
if (c < 0x80) {
// ASCII 路径(快速)
} else {
// 非 ASCII 路径(需要更新字节偏移表)
P.isPureAscii = false
}
目的:跳过不必要的 UTF-8 处理
🎯 性能对比
| 场景 | 优化前 | 优化后 |
|---|---|---|
| 纯 ASCII 脚本 | ~10ms | ~1ms |
| 混合 UTF-8 | ~15ms | ~5ms |
| 深度嵌套 | OOM | 50k 节点限制 |
知识点检验
测试你对 Bash 解析器的理解程度
1. Bash 解析器支持多少种 Token 类型?
2. 字符 '你'(U+4F60)在 UTF-8 中占用几个字节?
3. 解析器的超时限制是多少?
4. 以下哪个不是 Bash 变量扩展的语法?
附件系统
40+ 种附件类型管理、智能相关性检索、增量更新机制
附件系统架构
文件: src/utils/messages.ts (5,512 行)
核心职责:
- 消息生命周期管理、格式化、规范化、索引构建
- 40+ 种附件类型管理
- 智能相关性检索
- 增量更新机制
- Token 预算管理
消息处理系统架构
🎯 设计亮点
- 智能检索:自动选择最相关的 5 个 memories
- 增量更新:只发送变化部分(diff)
- 双重限制:行数 + 字节数双重约束
- 会话累积:60KB 后停止检索
- 并行执行:独立的附件生成同时运行
消息类型系统
附件系统支持多种消息类型,每种类型都有特定的用途和格式。
基础消息类型
type Message =
| UserMessage // 用户消息
| AssistantMessage // 助手消息
| AttachmentMessage // 附件消息
| ProgressMessage // 进度消息
| SystemMessage // 系统消息
| TombstoneMessage // 墓碑消息(已删除)
// 规范化消息类型(每个消息只包含一个 content block)
type NormalizedMessage =
| NormalizedUserMessage // 单块用户消息
| NormalizedAssistantMessage // 单块助手消息
💡 消息类型说明
- UserMessage:用户发送的消息
- AssistantMessage:AI 助手的回复
- AttachmentMessage:附件文件或数据
- ProgressMessage:任务进度更新
- SystemMessage:系统级消息
- TombstoneMessage:已删除的消息标记
助手消息结构
interface AssistantMessage {
type: 'assistant'
uuid: UUID
timestamp: string
message: {
id: UUID
role: 'assistant'
content: ContentBlock[] // 可包含多个 block
model: string
stop_reason: string | null
}
}
interface ContentBlock {
type: 'text' | 'tool_use' | 'tool_result' | 'thinking'
text?: string
tool_use?: {...}
tool_result?: {...}
}
🔍 ContentBlock 类型
- text:普通文本内容
- tool_use:工具调用请求
- tool_result:工具执行结果
- thinking:模型思考过程
关键点:一个消息可以包含多个 ContentBlock,这是规范化的关键。
消息规范化
规范化是将多块消息拆分为单块消息的过程,便于统一处理。
规范化函数
function normalizeMessages(messages: Message[]): NormalizedMessage[] {
const out: NormalizedMessage[] = []
for (const message of messages) {
if (message.type === 'user' || message.type === 'assistant') {
const content = message.message.content
if (content.length === 0) {
continue // 跳过空消息
}
if (content.length === 1) {
// 单块消息,直接使用
out.push(message as NormalizedMessage)
} else {
// 多块消息,拆分为多个单块消息
for (const block of content) {
out.push({
type: message.type,
uuid: deriveUUID(message.uuid, content.indexOf(block)),
timestamp: message.timestamp,
message: {
...message.message,
content: [block]
}
})
}
}
}
}
return out
}
🔍 规范化流程
- 遍历消息:逐个处理输入消息
- 检查块数:判断消息包含多少个 ContentBlock
- 单块直接使用:如果只有一个 block,直接使用
- 多块拆分:如果有多个 block,拆分为多个消息
- UUID 派生:为拆分出的消息派生新的 UUID
UUID 派生
function deriveUUID(parentUuid: UUID, index: number): UUID {
// 使用父 UUID + 索引派生新 UUID
const data = `${parentUuid}-${index}`
return uuid5(DNS_NAMESPACE, data)
}
// 示例:
// 父消息 UUID: "abc123"
// 拆分后:
// block 0 → "abc123-0" 的 hash
// block 1 → "abc123-1" 的 hash
// block 2 → "abc123-2" 的 hash
💡 UUID 派生的优势
- 可追溯:可以从子 UUID 追溯回父消息
- 唯一性:保证每个拆分消息的 UUID 唯一
- 稳定性:相同输入总是产生相同输出
- 去重:避免重复处理相同内容
🎯 规范化示例
// 原始消息(3个 block)
{
type: 'assistant',
uuid: 'msg-001',
message: {
content: [
{ type: 'text', text: '让我帮你...' },
{ type: 'tool_use', name: 'read', ... },
{ type: 'tool_result', ... }
]
}
}
// 规范化后(3个单块消息)
[
{
type: 'assistant',
uuid: 'msg-001-0-hash',
message: { content: [{ type: 'text', text: '让我帮你...' }] }
},
{
type: 'assistant',
uuid: 'msg-001-1-hash',
message: { content: [{ type: 'tool_use', ... }] }
},
{
type: 'assistant',
uuid: 'msg-001-2-hash',
message: { content: [{ type: 'tool_result', ... }] }
}
]
智能附件检索
附件系统的核心功能是根据上下文智能选择最相关的附件。
相关性检索
async function retrieveRelevantAttachments(
query: string,
context: SessionContext,
maxAttachments: number = 5
): Promise {
// 1. 计算 query 和所有 memories 的相似度
const similarities = memories.map(memory => ({
memory,
score: computeSimilarity(query, memory)
}))
// 2. 按相似度排序
similarities.sort((a, b) => b.score - a.score)
// 3. 选择 top K
const topK = similarities.slice(0, maxAttachments)
// 4. 构建附件
return topK.map(item => buildAttachment(item.memory))
}
🔍 检索策略
- 相似度计算:计算查询与每个 memory 的相似度
- 排序:按相似度降序排序
- Top K 选择:选择最相关的 5 个
- 附件构建:将 memory 转换为附件格式
增量更新
function buildIncrementalUpdate(
previousAttachments: Attachment[],
currentAttachments: Attachment[]
): Attachment[] {
// 1. 计算差异
const added = currentAttachments.filter(a =>
!previousAttachments.some(p => p.uuid === a.uuid)
)
const removed = previousAttachments.filter(p =>
!currentAttachments.some(a => a.uuid === p.uuid)
)
const modified = currentAttachments.filter(a => {
const prev = previousAttachments.find(p => p.uuid === a.uuid)
return prev && !deepEqual(prev, a)
})
// 2. 只发送变化部分
return {
added,
removed,
modified
}
}
💡 增量更新优势
- 减少带宽:只传输变化部分
- 降低延迟:更少的数据传输
- 节省 Token:减少 API 调用成本
- 提高响应速度:更快的处理速度
Token 预算管理
附件系统需要严格控制 Token 使用,避免超出预算。
双重限制
interface AttachmentBudget {
// 行数限制
maxLines: number
// 字节数限制
maxBytes: number
// 会话累积限制
sessionMaxBytes: number
}
const DEFAULT_BUDGET: AttachmentBudget = {
maxLines: 1000,
maxBytes: 100_000, // 100KB
sessionMaxBytes: 60_000 // 60KB 后停止检索
}
function checkBudget(
attachment: Attachment,
budget: AttachmentBudget
): boolean {
// 检查行数
if (attachment.lineCount > budget.maxLines) {
return false
}
// 检查字节数
if (attachment.byteCount > budget.maxBytes) {
return false
}
return true
}
🔍 预算检查层次
- 单附件限制:每个附件不能超过 1000 行或 100KB
- 会话累积:整个会话的附件总大小超过 60KB 后停止检索
- 动态调整:根据剩余预算调整检索策略
会话累积跟踪
class AttachmentSessionManager {
private sessionBytes = 0
private readonly limit = 60_000 // 60KB
addAttachment(attachment: Attachment): boolean {
// 检查是否超过会话限制
if (this.sessionBytes >= this.limit) {
return false // 停止检索
}
// 检查添加后是否会超限
if (this.sessionBytes + attachment.byteCount > this.limit) {
// 可以添加,但之后会停止
this.sessionBytes += attachment.byteCount
return true
}
// 正常添加
this.sessionBytes += attachment.byteCount
return true
}
shouldStopRetrieval(): boolean {
return this.sessionBytes >= this.limit
}
}
💡 会话管理策略
- 累积计数:跟踪整个会话的附件总大小
- 软限制:达到 60KB 后停止检索
- 软边界:允许最后一个附件超出限制
- 重置机制:新会话重置计数器
⚡ 性能优化
并行附件生成
async function buildAttachmentsParallel(
sources: AttachmentSource[]
): Promise {
// 并行执行独立的附件生成
const results = await Promise.allSettled(
sources.map(source => buildAttachment(source))
)
// 过滤成功的
return results
.filter(r => r.status === 'fulfilled')
.map(r => r.value)
}
优势:独立的附件生成同时运行,大幅提升速度
缓存优化
const attachmentCache = new Map()
function getCachedAttachment(key: string): Attachment | null {
return attachmentCache.get(key) || null
}
function setCachedAttachment(key: string, attachment: Attachment): void {
attachmentCache.set(key, attachment)
}
优势:避免重复生成相同附件
知识点检验
测试你对附件系统的理解程度
1. 附件系统支持多少种附件类型?
2. 智能检索最多选择多少个最相关的 memories?
3. 会话累积限制是多少?
4. 增量更新的主要优势是什么?
API 调用系统
queryModel 核心查询、智能重试机制、Prompt 缓存优化、Tool Search 动态加载
API 调用系统架构
文件: src/services/api/claude.ts (3,419 行)
核心职责:
- 封装 Claude API 调用
- 处理请求构建、响应解析
- 智能重试机制
- Prompt 缓存优化
- Tool Search 动态加载
- Streaming 处理
queryModel 核心架构
🎯 核心特性
- 智能重试:自动降级 max_tokens 和 fallback model
- 动态工具加载:Tool Search 减少初始工具数量
- Prompt 缓存:Global cache + 1h TTL
- Streaming:增量构建消息,低延迟
- Beta Headers Latching:防止缓存失效
- Thinking 支持:Adaptive 和 Budget 两种模式
请求准备阶段
在调用 API 之前,需要准备各种请求参数和配置。
getExtraBodyParams()
export function getExtraBodyParams(betaHeaders?: string[]): JsonObject {
// 第一步:解析环境变量
const extraBodyStr = process.env.CLAUDE_CODE_EXTRA_BODY
let result: JsonObject = {}
if (extraBodyStr) {
const parsed = safeParseJSON(extraBodyStr)
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
result = { ...(parsed as JsonObject) }
}
}
// 第二步:反蒸馏保护(1P CLI 专用)
if (feature('ANTI_DISTILLATION_CC') &&
process.env.CLAUDE_CODE_ENTRYPOINT === 'cli' &&
shouldIncludeFirstPartyOnlyBetas() &&
getFeatureValue('tengu_anti_distill_fake_tool_injection', false)) {
result.fake_tools = ['reading', 'writing']
}
// 第三步:处理 Beta headers
if (betaHeaders && betaHeaders.length > 0) {
result.beta_headers = betaHeaders
}
return result
}
🔍 参数构建流程
- 环境变量:解析 CLAUDE_CODE_EXTRA_BODY
- 反蒸馏保护:添加 fake_tools 混淆真实工具
- Beta Headers:添加 Beta 功能请求头
模型解析
function parseModel(modelString: string): ParsedModel {
// 支持 "model:beta_header" 格式
const parts = modelString.split(':')
const model = parts[0]
const betaHeaders = parts.slice(1)
return {
model,
betaHeaders: betaHeaders.length > 0 ? betaHeaders : undefined
}
}
// 示例:
// "claude-sonnet-4-6:prompt-caching,pdf-tools"
// → {
// model: "claude-sonnet-4-6",
// betaHeaders: ["prompt-caching", "pdf-tools"]
// }
💡 Beta Headers 作用
- 功能开关:启用实验性功能
- Prompt Caching:减少重复 Token 计费
- PDF Tools:启用 PDF 处理能力
- 格式:
model:beta1,beta2
智能重试机制
API 调用可能因为各种原因失败,智能重试机制确保请求最终成功。
重试策略
async function queryModelWithRetry(
request: ApiRequest,
options: QueryOptions
): Promise {
const MAX_RETRIES = 3
let lastError: Error
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
try {
const response = await queryModelOnce(request, options)
return response // 成功,直接返回
} catch (error) {
lastError = error
// 检查是否可重试
if (!isRetryableError(error)) {
throw error // 不可重试,直接抛出
}
// 智能降级
request = applyFallback(request, error, attempt)
// 等待后重试
if (attempt < MAX_RETRIES - 1) {
await sleep(calculateBackoff(attempt))
}
}
}
throw lastError // 所有重试都失败
}
🔍 重试逻辑
- 尝试调用:执行 API 请求
- 成功返回:立即返回结果
- 失败检查:判断是否可重试
- 智能降级:调整请求参数
- 等待重试:指数退避
- 最终失败:抛出最后一个错误
智能降级
function applyFallback(
request: ApiRequest,
error: Error,
attempt: number
): ApiRequest {
// 1. max_tokens 降级
if (error instanceof MaxTokensError) {
const newMaxTokens = Math.floor(request.max_tokens * 0.8)
return {
...request,
max_tokens: newMaxTokens
}
}
// 2. Model fallback
if (error instanceof ModelUnavailableError && attempt === 1) {
return {
...request,
model: FALLBACK_MODEL // 降级到备用模型
}
}
// 3. 其他调整
return request
}
// 降级策略:
// attempt 0: 原始请求
// attempt 1: max_tokens * 0.8
// attempt 2: max_tokens * 0.6 + fallback model
💡 降级策略
- Max Tokens 降级:逐步减少输出长度(80% → 60%)
- Model Fallback:第2次重试使用备用模型
- 指数退避:等待时间递增(1s → 2s → 4s)
- 上下文保留:保持消息内容不变
⚡ 重试性能
指数退避算法
function calculateBackoff(attempt: number): number {
// 基础延迟 1s,指数增长
const baseDelay = 1000 // 1秒
const maxDelay = 4000 // 最大 4秒
const delay = Math.min(
baseDelay * Math.pow(2, attempt),
maxDelay
)
// 添加随机抖动(±25%)
const jitter = delay * 0.25 * (Math.random() * 2 - 1)
return delay + jitter
}
// attempt 0: 1s ± 250ms
// attempt 1: 2s ± 500ms
// attempt 2: 4s ± 1000ms
目的:避免雷鸣效应(thundering herd)
Prompt 缓存优化
Prompt 缓存可以大幅减少 Token 使用和 API 延迟。
缓存策略
interface PromptCacheConfig {
// Global cache scope(跨会话复用)
enabled: boolean
// TTL(1 小时)
ttl: number
// 最大缓存大小
maxSize: number
}
const DEFAULT_CACHE_CONFIG: PromptCacheConfig = {
enabled: true,
ttl: 60 * 60 * 1000, // 1 小时
maxSize: 1000
}
function shouldUsePromptCache(user: User): boolean {
// 检查用户是否 eligible
if (!user.isEligibleForPromptCache) {
return false
}
// 检查是否启用
if (!getFeatureValue('prompt_cache_enabled', false)) {
return false
}
return true
}
🔍 缓存条件
- 用户资格:特定订阅级别才能使用
- 功能开关:Feature Gate 控制
- Global Scope:跨会话复用缓存
- TTL:1 小时后过期
缓存键计算
function computeCacheKey(request: ApiRequest): string {
// 只缓存 System Prompt 部分
const systemPrompt = request.messages[0]
// 计算 hash
const data = JSON.stringify({
model: request.model,
systemPrompt: systemPrompt,
tools: request.tools.map(t => t.name)
})
return sha256(data)
}
// 示例:
// 相同的 system prompt + 工具列表 → 相同的缓存键
// 不同 user prompt → 不同的缓存键
💡 缓存设计
- 只缓存 System Prompt:这是不变的部分
- 不包括 User Prompt:每个请求都不同
- 包括工具列表:工具变化影响缓存
- SHA256 Hash:快速且冲突概率低
🎯 缓存效果
| 场景 | 无缓存 | 有缓存 |
|---|---|---|
| 首次请求 | 10,000 tokens | 10,000 tokens |
| 相似请求(1h内) | 10,000 tokens | 1,000 tokens(90% off) |
| 延迟 | ~2s | ~0.5s |
Tool Search 动态加载
Tool Search 减少初始工具数量,只在需要时加载工具详情。
Tool Search 机制
interface ToolSearchRequest {
query: string
limit: number
}
interface ToolSearchResponse {
tools: ToolSummary[] // 只包含摘要信息
}
async function searchTools(query: string): Promise {
// 1. 调用 Tool Search API
const response = await apiClient.post('/tools/search', {
query,
limit: 5
})
// 2. 获取工具摘要
const summaries = response.data.tools
// 3. 按需加载完整工具定义
const tools = await Promise.all(
summaries.map(async summary => {
return loadToolDefinition(summary.name)
})
)
return tools
}
🔍 工作流程
- 搜索:根据查询搜索相关工具
- 摘要:返回工具摘要(不含完整定义)
- 按需加载:只加载选中的工具的完整定义
- 减少 Token:大幅减少 Prompt 大小
对比:传统 vs Tool Search
// 传统方式:发送所有工具
const allTools = [
{ name: 'read', inputSchema: {...} },
{ name: 'write', inputSchema: {...} },
{ name: 'edit', inputSchema: {...} },
// ... 50+ 更多工具
]
// Prompt 大小:~50,000 tokens
// Tool Search:按需加载
const toolSummaries = await searchTools('文件操作')
// 返回:['read', 'write', 'edit']
// Prompt 大小:~3,000 tokens
💡 优势
- 减少 Token:从 50k 降到 3k(94% off)
- 提高准确度:只展示相关工具
- 降低延迟:更小的 Prompt = 更快的响应
- 降低成本:更少的 Token 计费
知识点检验
测试你对 API 调用系统的理解程度
1. API 调用系统支持的最大重试次数是?
2. Prompt 缓存的 TTL 是多少?
3. Tool Search 可以减少多少 Prompt 大小?
4. 智能重试使用什么退避算法?
认证系统
多认证源管理、SWR 缓存机制、并发控制与锁机制、订阅信息管理、3P 服务集成
认证系统架构
文件: src/utils/auth.ts (2,002 行)
核心职责:
- 管理所有认证相关的功能
- API Key、OAuth Token、AWS/GCP 凭证管理
- 订阅信息管理
- SWR 缓存机制
- 并发控制与锁机制
- 3P 服务集成(AWS Bedrock、GCP Vertex、Foundry)
认证源层次结构
🎯 认证源优先级
- Bare Mode:
--bare标志 - 环境变量:
ANTHROPIC_API_KEY - File Descriptor:
CLAUDE_CODE_API_KEY_FILE_DESCRIPTOR - apiKeyHelper:自定义脚本
- 配置文件或 Keychain:持久化存储
API Key 管理
API Key 是最基础的认证方式,支持多种来源。
API Key 获取优先级
export function getAnthropicApiKeyWithSource(opts): {
key: null | string
source: ApiKeySource
} {
// 1. Bare Mode(最高优先级)
if (opts.bareMode) {
return { key: null, source: 'bare' }
}
// 2. 环境变量
const envKey = process.env.ANTHROPIC_API_KEY
if (envKey) {
return { key: envKey, source: 'ANTHROPIC_API_KEY' }
}
// 3. File Descriptor
const fdKey = process.env.CLAUDE_CODE_API_KEY_FILE_DESCRIPTOR
if (fdKey) {
return { key: fdKey, source: 'file_descriptor' }
}
// 4. apiKeyHelper
if (!opts.skipRetrievingKeyFromApiKeyHelper) {
const helperKey = await getApiKeyFromApiKeyHelper()
if (helperKey) {
return { key: helperKey, source: 'apiKeyHelper' }
}
}
// 5. 配置文件或 Keychain
const storedKey = await getStoredApiKey()
return { key: storedKey, source: 'stored' }
}
🔍 优先级设计
- Bare Mode:确保隔离环境,不使用任何认证
- 环境变量:最灵活,适合 CI/CD
- File Descriptor:安全传递敏感信息
- apiKeyHelper:动态获取,支持复杂逻辑
- 持久化存储:最后的备选方案
apiKeyHelper 的 SWR 缓存
let _apiKeyHelperCache: { value: string; timestamp: number } | null = null
let _apiKeyHelperInflight: {
promise: Promise
startedAt: number | null
} | null = null
export async function getApiKeyFromApiKeyHelper(
isNonInteractiveSession: boolean,
): Promise {
const ttl = calculateApiKeyHelperTTL() // 5 分钟
// 缓存有效
if (_apiKeyHelperCache) {
if (Date.now() - _apiKeyHelperCache.timestamp < ttl) {
return _apiKeyHelperCache.value
}
// 缓存过期:后台刷新,先返回旧值(SWR)
if (!_apiKeyHelperInflight) {
_apiKeyHelperInflight = {
promise: _runAndCache(...),
startedAt: null,
}
}
return _apiKeyHelperCache.value
}
// 缓存未命中:去重并发调用
if (_apiKeyHelperInflight) {
return _apiKeyHelperInflight.promise
}
// 执行脚本
_apiKeyHelperInflight = {
promise: _runAndCache(...),
startedAt: Date.now(),
}
return _apiKeyHelperInflight.promise
}
💡 SWR 策略
- Stale-While-Revalidate:缓存过期时先返回旧值
- 后台刷新:异步更新缓存,不阻塞请求
- 去重:并发调用共享同一个 Promise
- Epoch 机制:配置变更时增加 epoch,旧请求不更新缓存
OAuth Token 管理
OAuth Token 需要定期刷新,使用文件锁确保多进程安全。
Token 刷新机制
export function checkAndRefreshOAuthTokenIfNeeded(
retryCount = 0,
force = false,
): Promise {
// 去重并发调用
if (retryCount === 0 && !force) {
if (pendingRefreshCheck) {
return pendingRefreshCheck
}
const promise = checkAndRefreshOAuthTokenIfNeededImpl(retryCount, force)
pendingRefreshCheck = promise.finally(() => {
pendingRefreshCheck = null
})
return pendingRefreshCheck
}
return checkAndRefreshOAuthTokenIfNeededImpl(retryCount, force)
}
async function checkAndRefreshOAuthTokenIfNeededImpl(
retryCount: number,
force: boolean,
): Promise {
const MAX_RETRIES = 5
// 1. 清理磁盘变更缓存
await invalidateOAuthCacheIfDiskChanged()
// 2. 检查是否过期
const tokens = getClaudeAIOAuthTokens()
if (!force && !isOAuthTokenExpired(tokens.expiresAt)) {
return false // 未过期
}
// 3. 重新读取(可能已被其他进程刷新)
getClaudeAIOAuthTokens.cache?.clear?.()
clearKeychainCache()
const freshTokens = await getClaudeAIOAuthTokensAsync()
if (!isOAuthTokenExpired(freshTokens.expiresAt)) {
return false // 已被其他进程刷新
}
// 4. 获取锁并刷新
const claudeDir = getClaudeConfigHomeDir()
await mkdir(claudeDir, { recursive: true })
let release
try {
release = await lockfile.lock(claudeDir)
} catch (err) {
if ((err as { code?: string }).code === 'ELOCKED') {
// 其他进程持有锁,重试
if (retryCount < MAX_RETRIES) {
await sleep(1000 + Math.random() * 1000)
return checkAndRefreshOAuthTokenIfNeededImpl(retryCount + 1, force)
}
return false
}
return false
}
try {
// 5. 再次检查(竞争条件)
const lockedTokens = await getClaudeAIOAuthTokensAsync()
if (!isOAuthTokenExpired(lockedTokens.expiresAt)) {
return false
}
// 6. 执行刷新
const refreshedTokens = await refreshOAuthToken(
lockedTokens.refreshToken,
{ scopes: lockedTokens.scopes }
)
saveOAuthTokensIfNeeded(refreshedTokens)
// 7. 清除缓存
getClaudeAIOAuthTokens.cache?.clear?.()
clearKeychainCache()
return true
} finally {
await release()
}
}
🔍 刷新流程
- 去重:防止并发刷新
- 检查过期:判断是否需要刷新
- 重新读取:可能已被其他进程刷新
- 获取锁:确保单进程执行
- 竞争检查:获取锁后再次检查
- 执行刷新:调用 refresh API
- 清除缓存:确保下次读取最新值
🎯 锁机制的重要性
// 场景:两个进程同时检测到 Token 过期
// 进程 A:检查 Token → 过期
// 进程 B:检查 Token → 过期
// 没有 lock:
// 进程 A:调用 refresh API
// 进程 B:调用 refresh API(重复!)
// 结果:浪费 API 调用,可能导致限流
// 有 lock:
// 进程 A:获取锁 → 刷新 Token → 释放锁
// 进程 B:尝试获取锁 → 失败 → 重试 → 检查 Token → 已刷新
// 结果:只调用一次 API,高效且安全
AWS/GCP 认证
支持 AWS Bedrock 和 GCP Vertex AI 作为 3P 提供商。
AWS 认证刷新
async function runAwsAuthRefresh(): Promise {
const awsAuthRefresh = getConfiguredAwsAuthRefresh()
if (!awsAuthRefresh) return false
// 安全检查
if (isAwsAuthRefreshFromProjectSettings()) {
const hasTrust = checkHasTrustDialogAccepted()
if (!hasTrust && !getIsNonInteractiveSession()) {
logAntError('awsAuthRefresh invoked before trust check', error)
logEvent('tengu_awsAuthRefresh_missing_trust', {})
return false
}
}
// 先检查 STS 身份
try {
await checkStsCallerIdentity()
return false // STS 有效,无需刷新
} catch {
// STS 无效,执行刷新
return refreshAwsAuth(awsAuthRefresh)
}
}
export function refreshAwsAuth(awsAuthRefresh: string): Promise {
const authStatusManager = AwsAuthStatusManager.getInstance()
authStatusManager.startAuthentication()
return new Promise(resolve => {
const refreshProc = exec(awsAuthRefresh, {
timeout: AWS_AUTH_REFRESH_TIMEOUT_MS, // 3 分钟
})
// 实时流式输出
refreshProc.stdout!.on('data', data => {
const output = data.toString().trim()
if (output) {
authStatusManager.addOutput(output)
logForDebugging(output)
}
})
refreshProc.on('close', (code, signal) => {
if (code === 0) {
authStatusManager.endAuthentication(true)
resolve(true)
} else {
const timedOut = signal === 'SIGTERM'
const message = timedOut
? 'AWS auth refresh timed out after 3 minutes'
: 'Error running awsAuthRefresh'
console.error(chalk.red(message))
authStatusManager.endAuthentication(false)
resolve(false)
}
})
})
}
🔍 AWS 认证流程
- 安全检查:项目配置需要信任确认
- 智能检查:先验证 STS 身份,避免不必要的刷新
- 超时保护:3 分钟超时
- 实时反馈:流式输出,用户可见进度
- 缓存管理:TTL 缓存,自动刷新
GCP 认证检查
const GCP_CREDENTIALS_CHECK_TIMEOUT_MS = 5_000 // 5 秒
export async function checkGcpCredentialsValid(): Promise {
try {
// 动态导入,避免不必要的加载
const { GoogleAuth } = await import('google-auth-library')
const auth = new GoogleAuth({
scopes: ['https://www.googleapis.com/auth/cloud-platform'],
})
// 超时保护
const probe = (async () => {
const client = await auth.getClient()
await client.getAccessToken()
})()
const timeout = sleep(GCP_CREDENTIALS_CHECK_TIMEOUT_MS).then(() => {
throw new GcpCredentialsTimeoutError('GCP credentials check timed out')
})
await Promise.race([probe, timeout])
return true
} catch {
return false
}
}
💡 GCP 认证设计
- 超时保护:5 秒超时,避免 GCE 元数据服务器延迟
- 动态导入:按需加载 google-auth-library
- 智能检查:先验证凭证,避免不必要的刷新
- 安全检查:项目配置需要信任确认
⚡ 认证性能优化
综合刷新流程
export const refreshAndGetAwsCredentials = memoizeWithTTLAsync(
async (): Promise<{
accessKeyId: string
secretAccessKey: string
sessionToken: string
} | null> => {
// 1. 运行认证刷新
const refreshed = await runAwsAuthRefresh()
// 2. 获取凭证
const credentials = await getAwsCredsFromCredentialExport()
// 3. 清除 AWS INI 缓存
if (refreshed || credentials) {
await clearAwsIniCache()
}
return credentials
},
DEFAULT_AWS_STS_TTL, // 1 小时
)
优势:TTL 缓存,自动刷新,减少 API 调用
订阅信息管理
订阅信息决定了用户可以使用哪些功能和模型。
订阅类型
export type SubscriptionType =
| 'max' // 最高级别
| 'enterprise' // 企业版
| 'team' // 团队版
| 'pro' // 专业版
| null // API 用户或未填充
export function getSubscriptionType(): SubscriptionType | null {
// Mock 订阅(ANT 测试)
if (shouldUseMockSubscription()) {
return getMockSubscriptionType()
}
if (!isAnthropicAuthEnabled()) return null
const oauthTokens = getClaudeAIOAuthTokens()
if (!oauthTokens) return null
return oauthTokens.subscriptionType ?? null
}
export function hasOpusAccess(): boolean {
const subscriptionType = getSubscriptionType()
return (
subscriptionType === 'max' ||
subscriptionType === 'enterprise' ||
subscriptionType === 'team' ||
subscriptionType === 'pro' ||
subscriptionType === null // API 用户或未填充的订阅者
)
}
💡 订阅层级
- max:最高级别,所有功能
- enterprise:企业级功能
- team:团队协作功能
- pro:专业版功能
- null:API 用户或未填充
订阅相关工具函数
// 判断订阅类型
export function isMaxSubscriber(): boolean {
return getSubscriptionType() === 'max'
}
export function isTeamSubscriber(): boolean {
return getSubscriptionType() === 'team'
}
export function isEnterpriseSubscriber(): boolean {
return getSubscriptionType() === 'enterprise'
}
export function isProSubscriber(): boolean {
return getSubscriptionType() === 'pro'
}
// Rate Limit Tier
export function getRateLimitTier(): string {
const oauthTokens = getClaudeAIOAuthTokens()
return oauthTokens?.rateLimitTier ?? 'default'
}
// 订阅名称(用于显示)
export function getSubscriptionName(): string {
const subscriptionType = getSubscriptionType()
switch (subscriptionType) {
case 'enterprise':
return 'Claude Enterprise'
case 'team':
return 'Claude Team'
case 'max':
return 'Claude Max'
case 'pro':
return 'Claude Pro'
default:
return 'Claude API'
}
}
🔍 使用场景
- 功能开关:根据订阅类型启用/禁用功能
- Rate Limit:应用不同的速率限制
- UI 显示:显示订阅信息
- 模型访问:控制哪些模型可用
🎯 订阅与功能对应
| 订阅级别 | Opus 访问 | Prompt Cache | 其他特性 |
|---|---|---|---|
| Max | ✅ | ✅ | 所有功能 |
| Enterprise | ✅ | ✅ | 企业级 |
| Team | ✅ | ✅ | 团队协作 |
| Pro | ✅ | ❌ | 基础功能 |
| API | ✅ | ❌ | 按使用计费 |
安全机制
认证系统实现了多层安全机制,保护用户凭证。
信任检查
// apiKeyHelper、awsAuthRefresh、gcpAuthRefresh、
// awsCredentialExport、otelHeadersHelper
// 都需要信任确认
if (isFromProjectSettings()) {
const hasTrust = checkHasTrustDialogAccepted()
if (!hasTrust && !isNonInteractiveSession) {
logAntError('...', error)
logEvent('tengu_..._missing_trust', {})
return null
}
}
🔍 信任检查目的
- 防止恶意代码:项目配置可能包含恶意脚本
- 用户确认:执行前需要用户明确同意
- 安全追踪:记录所有安全事件
- 非交互模式:CI/CD 环境跳过检查
超时保护
// apiKeyHelper: 10 分钟
const API_KEY_HELPER_TIMEOUT = 10 * 60 * 1000
// awsAuthRefresh: 3 分钟
const AWS_AUTH_REFRESH_TIMEOUT_MS = 3 * 60 * 1000
// awsCredentialExport: 30 秒
const AWS_CREDENTIAL_EXPORT_TIMEOUT = 30 * 1000
// otelHeadersHelper: 30 秒
const OTEL_HEADERS_HELPER_TIMEOUT = 30 * 1000
// GCP credentials check: 5 秒
const GCP_CREDENTIALS_CHECK_TIMEOUT_MS = 5_000
💡 超时策略
- apiKeyHelper:10 分钟,允许复杂脚本
- AWS 刷新:3 分钟,避免长时间阻塞
- AWS 导出:30 秒,快速获取凭证
- GCP 检查:5 秒,快速验证
🔒 安全最佳实践
错误处理
// 详细的错误信息
const why = result.timedOut ? 'timed out' : `exited ${result.exitCode}`
const stderr = result.stderr?.trim()
throw new Error(stderr ? `${why}: ${stderr}` : why)
目的:帮助用户快速定位问题
日志记录
// 安全事件
logEvent('tengu_apiKeyHelper_missing_trust11', {})
logEvent('tengu_awsAuthRefresh_missing_trust', {})
logEvent('tengu_oauth_token_refresh_starting', {})
// 调试日志
logForDebugging('Fetching AWS caller identity...')
logForDebugging('GCP credentials are still valid')
目的:安全审计和问题排查
知识点检验
测试你对认证系统的理解程度
1. API Key 的最高优先级来源是?
2. apiKeyHelper 的 SWR 缓存 TTL 是多少?
3. OAuth Token 刷新最多重试几次?
4. 以下哪个订阅级别无法访问 Opus 模型?
Hooks 系统
24种Hook事件、4种Hook类型、超时控制、错误处理
Hooks 系统架构
文件: src/utils/hooks.ts (5,022 行)
核心职责:
- 24 种 Hook 事件类型
- 4 种 Hook 类型(Command、Prompt、Agent、HTTP)
- Hook 配置管理
- 同步/异步执行
- 超时控制:防止 Hook 阻塞主流程
- 错误处理:Hook 失败不影响主流程
Hook 事件类型(24种)
🎯 Hook 执行方式
- Command Hooks:执行shell命令
- Prompt Hooks:调用AI模型
- Agent Hooks:启动子Agent
- HTTP Hooks:HTTP请求回调
知识点检验
1. Hooks 系统支持多少种 Hook 事件类型?
插件/Marketplace 系统
6种插件组件类型、5种插件来源、版本化缓存、Marketplace集成
插件系统架构
文件: pluginLoader.ts (3,302 行) + marketplaceManager.ts (2,643 行)
核心职责:
- 6 种插件组件类型
- 5 种插件来源(marketplace、git、npm、local、session)
- 版本化缓存:避免重复加载
- Marketplace 集成:自动发现和安装
插件组件类型(6种)
🎯 插件来源(5种)
- Marketplace:从官方或第三方市场安装
- Git:从Git仓库克隆
- NPM:从NPM包安装
- Local:本地文件系统
- Session:会话专用(--plugin-dir)
知识点检验
1. 插件系统支持多少种组件类型?
Bridge/Remote 架构
3种Spawn模式、32个并发会话支持、Git Worktree隔离、SSH隧道
Bridge 架构
文件: bridgeMain.ts (2,999 行) + replBridge.ts (2,406 行)
核心职责:
- 3 种 Spawn 模式(ssh、local-docker、local-process)
- 32 个并发会话支持
- Git Worktree 隔离
- SSH 隧道支持
- WebSocket 传输
3种 Spawn 模式
🎯 核心特性
- 多会话管理:最多32个活跃会话
- 隔离机制:Git worktree确保环境独立
- 自动重连:指数退避重试
- 心跳机制:30秒心跳防止超时
知识点检验
1. Bridge 最多支持多少个并发会话?
Compact 架构
对话压缩策略、PTL重试机制、Token预算管理
Compact 系统
文件: compact.ts (1,705 行) + sessionMemoryCompact.ts (630 行)
核心职责:
- 对话压缩策略
- PTL(Prompt Too Long)重试机制
- Token 预算管理
- 1M 上下文支持
🎯 压缩触发条件
- 轮次触发:128k 轮次
- Token 触发:200k token
- PTL 重试:压缩后重试失败的请求
知识点检验
1. Compact 压缩的Token触发阈值是?
Cron 系统
5字段Cron表达式、分布式锁、Jitter优化
Cron 调度系统
文件: cron.ts (308 行) + cronScheduler.ts (565 行)
核心职责:
- 5 字段 Cron 表达式(分 时 日 月 周)
- 分布式锁:防止多实例冲突
- Jitter 优化:避免惊群效应
🎯 Cron 表达式格式
* * * * *
│ │ │ │ │
│ │ │ │ └─ 星期几 (0-6, 0=周日)
│ │ │ └─── 月份 (1-12)
│ │ └───── 日期 (1-31)
│ └─────── 小时 (0-23)
└───────── 分钟 (0-59)
知识点检验
1. Cron 表达式包含几个字段?
Skills & Discovery 系统
3种Tool Search模式、延迟工具发现、文件监控
Tool Search & Discovery
文件: toolSearch.ts (756 行) + skillChangeDetector.ts (311 行)
核心职责:
- 3 种 Tool Search 模式(tst、tst-auto、standard)
- 延迟工具发现:减少初始 Prompt 大小
- 文件监控:自动重载 Skills
🎯 Tool Search 模式
- tst:Tool Search Talk,按需加载
- tst-auto:自动模式,智能选择
- standard:标准模式,加载所有工具
知识点检验
1. Tool Search 有几种模式?
Insights 系统
三阶段数据处理、AI驱动的Facet提取、6个并行Insights生成
Insights 数据分析
文件: insights.ts (3,200 行)
核心职责:
- 三阶段数据处理(Lite Scan → Load Meta → Facet Extraction)
- AI 驱动的 Facet 提取
- 6 个并行 Insights 生成
- Multi-Clauding 检测
- Homespace 数据收集
三阶段架构
🎯 6个并行 Insights
- 项目区域:识别主要工作领域
- 交互风格:用户使用模式
- 成功案例:典型成功操作
- 摩擦分析:常见障碍
- 建议:优化建议
- 总结:整体概览
知识点检验
1. Insights 系统生成几个并行 Insights?
Bash 安全检查系统
23个安全检查点、Early-Allow机制、Heredoc安全验证
Bash Security 层级
文件: bashSecurity.ts (2,592 行)
核心职责:
- 23 个安全检查点
- Early-Allow 机制:可证明安全的命令跳过验证
- Heredoc 安全验证
- Git Commit 特殊处理
- Zsh 危险命令黑名单
分层验证
🎯 关键安全检查
- INCOMPLETE_COMMANDS:检测不完整命令
- SHELL_METACHARACTERS:特殊字符检查
- 边界验证:防止前缀匹配攻击
- Zsh 黑名单:危险命令(zmodload、emulate等)
知识点检验
1. Bash Security 有多少个安全检查点?
系统 Prompt 管理
有效System Prompt构建、Override机制、Coordinator模式支持
System Prompt 架构
文件: systemPrompt.ts (124 行) + systemPromptType.ts (15 行)
核心职责:
- 有效 System Prompt 构建
- Override 机制
- Coordinator 模式支持
- Proactive 模式集成
🎯 优先级顺序
- Override:最高优先级
- Coordinator:Coordinator 模式专用
- Agent:Agent System Prompt
- Custom:用户自定义
- Default:默认 Prompt