第八篇:QueryEngine查询引擎,Claude Code的核心对话循环

第八篇:QueryEngine查询引擎,Claude Code的核心对话循环 第八篇QueryEngine查询引擎Claude Code的核心对话循环源码位置src/QueryEngine.ts1295行难度高级一、引言为什么QueryEngine是心脏在前七篇中我们依次拆解了Claude Code的架构全景、CLI入口、Handler处理器链。但有一个核心问题始终没有回答用户输入一段提示词后Claude Code究竟做了什么答案就在QueryEngine.ts里。这个1295行的TypeScript文件是整个系统的心脏——它掌管着从用户输入到AI回复、从工具调用到结果回传的完整生命周期。每一次你按下回车都是一次submitMessage()的调用。设计亮点QueryEngine 被刻意设计成一个无头headless友好的独立类既可以驱动终端REPL也可以支撑SDK远程调用。这种解耦使得Claude Code能同时服务多种前端形态。二、QueryEngine架构总览核心职责对话状态管理维护mutableMessages数组所有对话历史都在这里上下文构建动态组装系统提示词、用户上下文CLAUDE.md、git状态等API调用编排调用Anthropic Messages API处理流式响应工具执行循环解析AI返回的工具调用执行将结果注入下一轮对话权限与取消通过AbortController支持中断通过canUseTool控制权限用量追踪实时累积token用量和成本QueryEngineConfig配置接口核心字段字段说明cwd当前工作目录决定CLAUDE.md搜索范围tools可用工具集合Bash、Read、Write、Edit等commands斜杠命令列表/help、/clear等mcpClients已连接的MCP服务器列表canUseTool权限检查函数决定是否允许某工具执行maxTurns最大对话轮次防止无限循环maxBudgetUsd预算上限美元超支自动停止thinkingConfig思维链Thinking配置abortController取消控制器用户按CtrlC时触发核心私有状态privatemutableMessages:Message[]// 可变对话历史privateabortController:AbortController// 取消控制privatepermissionDenials:SDKPermissionDenial[]// 权限拒绝记录privatetotalUsage:NonNullableUsage// 累积用量privatereadFileState:FileStateCache// 文件状态缓存优化重复读取privatediscoveredSkillNames:Setstring// 本turn发现的技能名三、submitMessage一次对话请求的完整生命周期submitMessage()是一个AsyncGeneratorSDKMessage通过yield逐步向外推送事件文本、工具调用、用量等而不是等整个处理完成后一次性返回。处理管道8步清理与初始化— 清除discoveredSkillNames重置cwd记录开始时间处理用户输入processUserInput— 解析prompt字符串或ContentBlockParam[]展开斜杠命令、技能调用、文件引用构建上下文fetchSystemPromptParts— 加载系统提示词片段、用户上下文CLAUDE.md、git状态调用Anthropic APIquery— 将messages system tools发送给Anthropic Messages API开启流式响应处理流式响应— 逐块解析SSE事件文本内容→输出tool_use→收集输入thinking→存储思维链执行工具调用— 对AI返回的每一个tool_use调用对应Tool的execute方法注入工具结果继续下一轮— 将tool_result消息追加到mutableMessages如果未达到maxTurns则回到步骤4收尾记录用量、保存session— 调用flushSessionStorage()持久化对话四、上下文管理系统Claude Code的上下文不仅仅是对话历史还包括环境状态、用户偏好、项目配置。这些由context.ts统一管理。SystemContext环境快照通过getSystemContext()memoize缓存获取包含{gitStatus:Current branch: main\nStatus:\n (clean)\nRecent commits:...,cacheBreaker:[CACHE_BREAKER: ...]}⚡性能优化getSystemContext和getUserContext都用了lodash-es/memoize在一次对话会话内只计算一次后续直接返回缓存值。git status这种耗时操作不会每次都执行。UserContext项目与个人偏好通过getUserContext()获取核心内容是CLAUDE.md的聚合{claudeMd:# 项目规范\n- 使用TypeScript...\n- 测试框架vitest...,currentDate:Todays date is 2026-06-30.}CLAUDE.md的加载逻辑支持项目级./CLAUDE.md、用户级~/.claude/CLAUDE.md、插件级三个层级通过getMemoryFiles()递归搜索所有父目录。五、流式响应处理QueryEngine通过导入的query()函数来自src/query.ts与Anthropic API通信。query()返回的是一个异步生成器逐块产出SSE事件。核心处理循环伪代码forawait(consteventofquery({messages,system,tools,...})){if(event.typecontent_block_delta){// 文本增量 → 累积到输出缓冲区textBufferevent.delta.text;}if(event.typecontent_block_startevent.content_block.typetool_use){// 工具调用开始 → 初始化工具输入收集器currentToolUse{name:event.content_block.name,input:};}if(event.typecontent_block_deltaevent.delta.typeinput_json_delta){// 工具输入JSON流式传输 → 逐步拼接currentToolUse.inputevent.delta.partial_json;}if(event.typemessage_stop){// 消息结束 → 处理收集到的所有tool_usesbreak;}}Thinking 思维链支持当thinkingConfig启用时API会返回thinking类型的content block。QueryEngine将其原样保留在消息历史中供下一轮对话使用Anthropic API支持thinking块作为上下文传入。六、工具执行与权限控制AI返回tool_use后QueryEngine需要执行这些工具。权限包装器wrappedCanUseToolcanUseTool是注入的权限检查函数QueryEngine对其进行了包装constwrappedCanUseTool:CanUseToolFnasync(tool,input,toolUseContext,assistantMessage,toolUseID){constresultawaitcanUseTool(tool,input,...);if(result.typedeny){this.permissionDenials.push({toolName:tool.name,...});}returnresult;};工具执行结果注入每个工具的执行结果被封装成tool_result消息追加到mutableMessagesmutableMessages.push({role:user,content:[{type:tool_result,tool_use_id:toolUseID,content:toolOutput}]});七、错误处理与重试API调用可能失败速率限制、网络抖动、服务不可用。QueryEngine通过categorizeRetryableAPIError()对错误分类可重试错误429速率限制、500/502/503服务端临时错误、网络超时不可重试错误401认证失败、400请求格式错误、内容策略拦截可重试错误会触发指数退避重试最多重试3次。AbortController可在任意时刻中断等待中的重试。八、用量追踪与成本计算每次API调用返回Usage信息input_tokens、output_tokensQueryEngine通过accumulateUsage()实时累积this.totalUsageaccumulateUsage(this.totalUsage,usage);同时CostTracker根据模型定价将token用量转换成美元成本在达到maxBudgetUsd时自动中止对话。遥测事件每次turn结束时QueryEngine会通过tengu_turn_complete事件上报用量统计包括input/output token数、耗时、工具调用次数等。九、高级特性Snip Compaction长对话的历史裁剪当对话变得非常长数万tokenAPI上下文窗口可能溢出。snipReplay机制会在适当时机裁剪早期消息但保留关键决策点使得后续对话可以投影回被裁剪掉的内容。Session Persistence对话持久化flushSessionStorage()在每次turn结束后调用将mutableMessages写入磁盘~/.claude/projects/...使得claude --resume可以精确恢复对话状态。Memory Prompt动态记忆加载loadMemoryPrompt()从项目.claude/memory/目录加载记忆文件注入到系统提示词中。这让Claude能记住跨会话的项目上下文。十、总结QueryEngine的设计哲学读完QueryEngine.ts的1295行代码有三个设计决策令人印象深刻AsyncGenerator而非回调通过异步生成器逐步yield事件使得REPL UI可以实时渲染流式输出而SDK消费者也能灵活处理中间事件。上下文延迟计算 缓存git status、CLAUDE.md等内容通过memoize延迟计算在单次会话内零重复开销。无头优先Headless-FirstQueryEngine不依赖任何UI框架所有状态通过AsyncGenerator yield出去由调用方决定如何渲染。下一篇预告我们将深入src/query.ts——真正与Anthropic API对话的那一层看看流式通信、工具Schema注入、以及200ms首字延迟是怎么做到的。Claude Code 源码分析系列 · 第八篇