@@ -7,6 +7,322 @@ sourceRef: "3ec5675 (2026-04-08)"
77
88{ /* 本章目标:从源码角度揭示会话编排、持久化存储、成本追踪和模型切换的完整链路 */ }
99
10+ 首先要区分claude code的多种交互方式
11+
12+ REPL关注交互形态,SDK关注接入方式,ACP则关注通信协议。
13+
14+ ### 🆚 核心概念对比
15+
16+ | 维度 | 🖥️ REPL (交互形态) | 🧩 SDK (接入方式) | 🌉 ACP (通信协议) |
17+ | :--- | :--- | :--- | :--- |
18+ | ** 是什么** | 供开发者直接在终端使用的** 交互式对话环境** | 面向开发者的** 程序化调用库** ,供集成到其他应用 | 一种** 开放式的通信标准** ,连接不同AI Agent与编辑器 |
19+ | ** 使用方式** | 1. 直接在终端输入` claude ` 命令<br >2. 进入专用界面(基于React Ink渲染)<br >3. 通过斜杠命令(如` /help ` )交互 | 1. 在自己的Node.js/Python项目中安装SDK包(如` npm install claude-code-sdk ` )<br >2. 通过API发送查询 | 1. 通过ACP适配器(如` claude-code-acp ` )启动Claude Code<br >2. 供编辑器通过ACP协议与其通信 |
20+ | ** 典型场景** | 开发者日常编写代码时,随时向其提问、修改代码或执行任务 | 将Claude Code的核心能力(对话、工具执行等)集成到自动化脚本、CI/CD流程或其他应用的后台中 | 将Claude Code的能力集成到JetBrains IDE、Zed等第三方编辑器中,利用其UI交互功能 |
21+ | ** 主要特点** | - ** 面向人** :交互式、直观<br >- ** 功能完整** :可使用所有内置工具,并支持MCP集成<br >- ** 处理复杂任务** :可自主规划、执行多步操作 | - ** 面向程序** :编程化、可集成<br >- ** 轻量级** :不依赖Claude Code的完整运行时<br >- ** 由你控制** :适合在自有应用中实现自动化 | - ** 标准化** :统一不同Agent与编辑器间的通信<br >- ** 双向通信** :Agent可主动向编辑器请求文件、执行命令等<br >- ** 与编辑器深度整合** :能完全复用Claude Code的能力 |
22+
23+ 其中的 🧩 SDK (接入方式) 与 🌉 ACP (通信协议)采用如下QueryEngine实现会话管理
24+
25+ 作为一个对话终端(🖥️ REPL 交互形态模式),则使用的是 onQueryImpl 在 src/screens/REPL.tsx 中调用 query() 函数
26+
27+ 对于REPL 交互形态模式的调用链路如下
28+ ```
29+ 用户输入
30+ ↓
31+ onSubmit (REPL.tsx)
32+ ↓
33+ handlePromptSubmit (handlePromptSubmit.ts)
34+ ↓
35+ executeUserInput (handlePromptSubmit.ts)
36+ ↓
37+ onQuery (REPL.tsx)
38+ ↓
39+ onQueryImpl (REPL.tsx)
40+ ↓
41+ query (query.ts) ← 在这里调用
42+ ```
43+
44+ 其中
45+
46+ query 函数是 Agentic Loop 的核心实现,包含 while(true) 循环处理对话回合 query.ts:460-522
47+
48+ onQueryImpl 是 REPL(Read-Eval-Print Loop)中与 AI 模型交互的核心控制器,它负责:
49+
50+ 1.环境准备(IDE、诊断、权限)
51+
52+ 2.会话标题的首次生成
53+
54+ 3.构建动态系统提示和用户上下文
55+
56+ 4.执行流式查询并实时更新 UI
57+
58+ 5.收集性能指标和最终清理
59+
60+ ## ` onQueryImpl ` 方法的详细解析
61+ 以下是对 ` onQueryImpl ` 方法的详细解析。该方法是一个 React ` useCallback ` 包装的异步函数,负责处理用户消息到 AI 模型(Claude)的** 完整查询流程** ,包括预处理、系统提示构建、工具上下文准备、流式查询执行、后处理与指标记录。
62+
63+ ---
64+
65+ ### 一、函数签名与参数
66+
67+ ``` typescript
68+ const onQueryImpl = useCallback (
69+ async (
70+ messagesIncludingNewMessages : MessageType [],
71+ newMessages : MessageType [],
72+ abortController : AbortController ,
73+ shouldQuery : boolean ,
74+ additionalAllowedTools : string [],
75+ mainLoopModelParam : string ,
76+ effort ?: EffortValue ,
77+ ) => { ... },
78+ [ ... dependencies ]
79+ )
80+ ```
81+
82+ | 参数 | 说明 |
83+ | -------------------------------- | ---------------------------------------------------------------------------------------- |
84+ | ` messagesIncludingNewMessages ` | 包含新增消息的完整消息列表,用于构建模型输入 |
85+ | ` newMessages ` | 本次新增的消息(例如用户刚输入的文本或附件) |
86+ | ` abortController ` | 用于取消当前查询的控制器 |
87+ | ` shouldQuery ` | 是否真正执行查询;若为 ` false ` 则跳过模型调用(例如处理无效斜杠命令、手动 compact 等) |
88+ | ` additionalAllowedTools ` | 本轮查询额外允许的工具列表(通常来自 Skill 的 frontmatter) |
89+ | ` mainLoopModelParam ` | 指定本次使用的主模型参数(如 ` 'claude-3-opus' ` ) |
90+ | ` effort ` | 可选,覆盖全局的“努力程度”值(用于控制模型推理深度) |
91+
92+ ---
93+
94+ ### 二、总体执行流程
95+
96+ 下图概括了函数的主要分支与关键步骤:
97+
98+ ``` mermaid
99+ graph TD
100+ A["开始"] --> B{shouldQuery?}
101+ B -- true --> C["IDE集成:刷新MCP客户端,诊断追踪,关闭差异视图"]
102+ B -- false --> D["仅处理compact边界/重置状态并返回"]
103+ C --> E["标记项目onboarding完成"]
104+ E --> F["尝试生成会话标题(仅一次)"]
105+ F --> G["将additionalAllowedTools写入全局权限store"]
106+ G --> H["获取ToolUseContext(含最新工具/MCP)"]
107+ H --> I["如有effort,临时覆盖getAppState中的effortValue"]
108+ I --> J["并行执行:系统提示/用户上下文/系统上下文/自动模式检查"]
109+ J --> K["构建有效系统提示"]
110+ K --> L["重置各类耗时计时器"]
111+ L --> M["执行query生成器,流式处理事件"]
112+ M --> N["若BUDDY开启,触发companion观察者"]
113+ N --> O["若UDS_INBOX且中断,记录错误"]
114+ O --> P["ant用户:收集API指标并插入指标消息"]
115+ P --> Q["重置加载状态,输出性能报告,调用onTurnComplete"]
116+ Q --> R["结束"]
117+ D --> R
118+ ```
119+
120+ ---
121+
122+ ### 三、核心逻辑详解
123+
124+ #### 3.1 IDE 集成与诊断(仅 ` shouldQuery = true ` )
125+
126+ ``` typescript
127+ const freshClients = mergeClients (initialMcpClients , store .getState ().mcp .clients );
128+ diagnosticTracker .handleQueryStart (freshClients );
129+ const ideClient = getConnectedIdeClient (freshClients );
130+ if (ideClient ) closeOpenDiffs (ideClient );
131+ ```
132+
133+ - 从 store 中获取最新的 MCP 客户端(因为 ` useManageMCPConnections ` 可能在闭包捕获后更新了状态)。
134+ - 通知诊断追踪器查询开始。
135+ - 若存在已连接的 IDE 客户端,关闭所有打开的差异视图(清理环境)。
136+
137+ #### 3.2 会话标题生成(仅一次)
138+
139+ ``` typescript
140+ if (! titleDisabled && ! sessionTitle && ! agentTitle && ! haikuTitleAttemptedRef .current ) {
141+ const firstUserMessage = newMessages .find (m => m .type === ' user' && ! m .isMeta );
142+ const text = getContentText (firstUserMessage .message .content );
143+ if (text && ! text .startsWith (` <${LOCAL_COMMAND_STDOUT_TAG }> ` ) ... ) {
144+ haikuTitleAttemptedRef .current = true ;
145+ generateSessionTitle (text , ... ).then (title => setHaikuTitle (title ));
146+ }
147+ }
148+ ```
149+
150+ - 仅当全局标题未禁用、当前无任何标题且从未尝试过时执行。
151+ - 从新增消息中提取第一条** 非元用户消息** 的真实文本。
152+ - 跳过合成面包屑(如 slash 命令输出、skill 扩展标记等)。
153+ - 异步调用 ` generateSessionTitle ` ,结果通过 ` setHaikuTitle ` 保存;失败则重置 ref 允许重试。
154+
155+ #### 3.3 权限工具覆盖写入 Store
156+
157+ ``` typescript
158+ store .setState (prev => {
159+ const cur = prev .toolPermissionContext .alwaysAllowRules .command ;
160+ if (cur === additionalAllowedTools || (cur ?.length === ... )) return prev ;
161+ return { ... prev , toolPermissionContext: { ... prev .toolPermissionContext , alwaysAllowRules: { ... prev .toolPermissionContext .alwaysAllowRules , command: additionalAllowedTools } } };
162+ });
163+ ```
164+
165+ - 将本轮 ` additionalAllowedTools ` 写入全局 store 的 ` toolPermissionContext.alwaysAllowRules.command ` 。
166+ - 用于限定本轮查询中可用的工具集(例如 Skill 专属工具)。
167+ - 通过浅比较避免不必要的状态更新。
168+ - 即使在 ` shouldQuery=false ` 时也会执行(例如 forked 命令需要此权限信息),但原代码位置在 ` shouldQuery ` 分支** 之前** ,所以始终会更新。
169+
170+ #### 3.4 ` shouldQuery = false ` 分支
171+
172+ ``` typescript
173+ if (! shouldQuery ) {
174+ if (newMessages .some (isCompactBoundaryMessage )) {
175+ setConversationId (randomUUID ());
176+ if (feature (' PROACTIVE' ) || feature (' KAIROS' )) proactiveModule ?.setContextBlocked (false );
177+ }
178+ resetLoadingState ();
179+ setAbortController (null );
180+ return ;
181+ }
182+ ```
183+
184+ - 处理不需要实际调用模型的情况(如用户输入了无效斜杠命令,或者手动 ` /compact ` 等)。
185+ - 若新消息中包含 ** compact 边界消息** (压缩边界),则:
186+ - 生成新的 ` conversationId ` ,促使 UI 中消息行组件重新挂载。
187+ - 若开启了 PROACTIVE/KAIROS 特性,清除上下文阻塞标志(恢复主动提示)。
188+ - 最后重置加载状态并清空 abortController。
189+
190+ #### 3.5 查询前置准备(` shouldQuery = true ` )
191+
192+ ##### 3.5.1 获取 ToolUseContext
193+
194+ ``` typescript
195+ const toolUseContext = getToolUseContext (messagesIncludingNewMessages , newMessages , abortController , mainLoopModelParam );
196+ const { tools : freshTools, mcpClients : freshMcpClients } = toolUseContext .options ;
197+ ```
198+
199+ - ` getToolUseContext ` 内部会从 store 中读取最新的 tools 和 MCP 客户端配置,确保闭包捕获的旧值不会导致遗漏新连接的工具或 MCP 服务器。
200+
201+ ##### 3.5.2 Effort 覆盖(临时)
202+
203+ ``` typescript
204+ if (effort !== undefined ) {
205+ const previousGetAppState = toolUseContext .getAppState ;
206+ toolUseContext .getAppState = () => ({ ... previousGetAppState (), effortValue: effort });
207+ }
208+ ```
209+
210+ - 如果传入了 ` effort ` 参数,临时覆盖 ` getAppState ` 返回的 ` effortValue ` 。
211+ - 作用域** 仅限于本轮查询** ,不影响全局 store,避免后台 Agent 或 UI 组件误读到该临时值。
212+
213+ ##### 3.5.3 并行获取提示与上下文
214+
215+ ``` typescript
216+ const [, , defaultSystemPrompt, baseUserContext, systemContext] = await Promise .all ([
217+ undefined ,
218+ feature (' TRANSCRIPT_CLASSIFIER' ) ? checkAndDisableAutoModeIfNeeded (... ) : undefined ,
219+ getSystemPrompt (freshTools , mainLoopModelParam , additionalWorkingDirectories , freshMcpClients ),
220+ getUserContext (),
221+ getSystemContext (),
222+ ]);
223+ ```
224+
225+ - 并行执行以下任务以节省时间:
226+ - ** 自动模式断路器** :如果启用了转录分类器,检查并可能禁用快速模式(` fastMode ` )。
227+ - ** 系统提示** :基于最新工具、模型参数、额外工作目录、MCP 客户端生成。
228+ - ** 用户上下文** :如当前工作区、环境变量等。
229+ - ** 系统上下文** :如操作系统、终端信息等。
230+
231+ ##### 3.5.4 增强用户上下文
232+
233+ ``` typescript
234+ const userContext = {
235+ ... baseUserContext ,
236+ ... getCoordinatorUserContext (freshMcpClients , getScratchpadDir ()),
237+ ... ((feature (' PROACTIVE' ) || feature (' KAIROS' )) && proactiveModule ?.isProactiveActive () && ! terminalFocusRef .current
238+ ? { terminalFocus: ' The terminal is unfocused — the user is not actively watching.' }
239+ : {}),
240+ };
241+ ```
242+
243+ - 合并基本用户上下文、协调器上下文(与 MCP 协作相关)、以及可选的终端焦点状态(当 proactive 特性激活且终端未聚焦时,提示模型用户未在观看)。
244+
245+ ##### 3.5.5 构建最终系统提示
246+
247+ ``` typescript
248+ const systemPrompt = buildEffectiveSystemPrompt ({
249+ mainThreadAgentDefinition ,
250+ toolUseContext ,
251+ customSystemPrompt ,
252+ defaultSystemPrompt ,
253+ appendSystemPrompt ,
254+ });
255+ ```
256+
257+ - 整合主线程 Agent 定义、工具上下文、自定义系统提示、默认系统提示以及需要追加的内容。
258+
259+ #### 3.6 执行查询与流式事件处理
260+
261+ ``` typescript
262+ resetTurnHookDuration (); resetTurnToolDuration (); resetTurnClassifierDuration ();
263+ for await (const event of query ({ messages , systemPrompt , userContext , systemContext , canUseTool , toolUseContext , querySource })) {
264+ onQueryEvent (event );
265+ }
266+ ```
267+
268+ - 重置本轮钩子、工具、分类器的耗时计时器。
269+ - 调用 ` query ` 生成器函数(负责与模型 API 通信并返回 SSE 事件流)。
270+ - 遍历每个事件并调用 ` onQueryEvent ` (通常用于更新 UI 消息列表、处理工具调用等)。
271+
272+ #### 3.7 后处理与指标收集
273+
274+ ##### 3.7.1 BUDDY 特性(companion 反应)
275+
276+ ``` typescript
277+ if (feature (' BUDDY' ) && typeof fireCompanionObserver === ' function' ) {
278+ fireCompanionObserver (messagesRef .current , reaction => setAppState (prev => ({ ... prev , companionReaction: reaction })));
279+ }
280+ ```
281+
282+ - 将当前消息列表传递给 companion 观察者,并根据返回的反应更新全局状态。
283+
284+ ##### 3.7.2 UDS_INBOX 中断处理
285+
286+ ``` typescript
287+ if (feature (' UDS_INBOX' ) && abortController .signal .aborted ) {
288+ pipeReturnHadErrorRef .current = true ;
289+ relayPipeMessage ({ type: ' error' , data: ' Slave request was interrupted before completion.' });
290+ }
291+ ```
292+
293+ - 若因中断导致查询未完成,标记错误并通过管道中继消息。
294+
295+ ##### 3.7.3 Ant 内部用户的 API 指标记录
296+
297+ ``` typescript
298+ if (process .env .USER_TYPE === ' ant' && apiMetricsRef .current .length > 0 ) {
299+ const entries = apiMetricsRef .current ;
300+ const ttfts = entries .map (e => e .ttftMs );
301+ const otpsValues = entries .map (e => { /* 计算每请求的 OTPs */ });
302+ const isMultiRequest = entries .length > 1 ;
303+ // 创建 API 指标消息并添加到消息列表
304+ setMessages (prev => [... prev , createApiMetricsMessage ({ ttftMs: isMultiRequest ? median (ttfts ) : ttfts [0 ], ... })]);
305+ }
306+ ```
307+
308+ - 仅当用户类型为 ` 'ant' ` 且存在 API 指标记录时执行。
309+ - 收集每次请求的 ** 首字节时间 (TTFT)** 和 ** 每秒输出 Token 数 (OTPS)** 。
310+ - 若本轮包含多次请求(例如工具调用循环),计算中位数(P50)后存入指标消息。
311+ - 同时记录钩子耗时、工具耗时、分类器耗时、本轮总时长、配置写入次数等。
312+
313+ ##### 3.7.4 重置与清理
314+
315+ ``` typescript
316+ resetLoadingState ();
317+ logQueryProfileReport ();
318+ await onTurnComplete ?.(messagesRef .current );
319+ ```
320+
321+ - 重置加载状态(隐藏 loading 指示器)。
322+ - 输出查询性能报告(如果调试标志启用)。
323+ - 调用外部传入的 ` onTurnComplete ` 回调,并传递完整消息列表(通常用于触发后续行为如自动滚动、保存会话等)。
324+
325+
10326## 单轮 vs 多轮:架构层面的差异
11327
12328- ** 单轮** (一次 Agentic Loop):` query() ` 函数的一次完整执行——组装上下文 → 调 API → 处理工具调用 → 循环直到结束
@@ -28,7 +344,7 @@ QueryEngine 内部状态(src/QueryEngine.ts 构造函数)
28344
29345## QueryEngine 的核心方法:submitMessage()
30346
31- 每次用户输入一条消息,REPL 或 SDK 调用 ` submitMessage() ` ,它会执行完整的 turn 初始化链路:
347+ 每次用户输入一条消息,SDK 调用 ` submitMessage() ` ,它会执行完整的 turn 初始化链路:
32348
33349``` typescript
34350// src/QueryEngine.ts — QueryEngine.submitMessage() 简化流程
0 commit comments