Skip to main content

单轮 vs 多轮:架构层面的差异

  • 单轮(一次 Agentic Loop):query() 函数的一次完整执行——组装上下文 → 调 API → 处理工具调用 → 循环直到结束
  • 多轮(一个 Session):QueryEngine 类管理的一次会话——跨越数十轮 submitMessage() 调用,持续数小时
QueryEnginesrc/QueryEngine.ts,类定义)是单轮 Agentic Loop 之上的会话编排器,它管理的状态远不止消息列表:

QueryEngine 的核心方法:submitMessage()

每次用户输入一条消息,REPL 或 SDK 调用 submitMessage(),它会执行完整的 turn 初始化链路:
关键设计:submitMessage()async *Generator——它逐步 yield SDKMessage,让调用方(REPL/SDK)能实时展示进度,而不是等整个 turn 结束。

会话持久化:JSONL Transcript

每次对话事件都被追加写入 transcript 文件(src/utils/sessionStorage.ts):

存储路径

  • 路径由 getProjectDir(originalCwd) 生成,使用 sanitizePath() 将项目目录路径转换为安全的目录名(非 hash),同一项目目录的会话归入同一子目录
  • 每条记录是一行 JSON(JSONL 格式),支持追加写入而不需要读取-修改-写入整个文件
  • 读取上限为 50MB(MAX_TRANSCRIPT_READ_BYTES 常量,src/utils/sessionStorage.ts),防止超大会话导致 OOM

Transcript 写入器

Project 类(src/utils/sessionStorage.ts,私有类)管理 transcript 的写入。它通过 writeQueues(按文件分组的写队列)和 drainWriteQueue()(定时批量刷写)确保并发消息追加不会互相覆盖:

会话恢复链路

--resume 参数触发的恢复流程(src/main.tsx--resume 分支):

成本追踪:从 API Usage 到美元

成本追踪贯穿三个模块,形成完整的记录→累计→展示链路:

记录层:API 响应中的 Usage

每个 message_delta 事件携带 usage 字段(input_tokensoutput_tokenscache_creation_input_tokenscache_read_input_tokens)。accumulateUsage() 将增量 usage 累加到会话总量。

累计层:cost-tracker.ts

addToTotalSessionCost() 根据模型定价计算每次 API 调用的费用,累计到 totalCostUSD。按模型的 ModelUsage 支持在同一会话中切换模型后分别统计。

持久化:跨重启保留

预算熔断

QueryEngineConfig.maxBudgetUsd 提供了会话级的硬性预算上限。在 REPL 中,当累计费用超过 $5 时(src/screens/REPL.tsx 中费用阈值 useEffect),弹出费用提醒对话框——这不是硬性阻断,而是”软提醒”,且仅在 hasConsoleBillingAccess() 为 true 时显示。

模型热切换

在一个会话中切换模型不会丢失对话历史——因为 mutableMessages 与模型选择是解耦的:
切换模型时,contextWindowTokensmaxOutputTokens 也会根据新模型的规格重新计算——例如从 Sonnet 切换到 Opus 时,上下文窗口可能从 200K 变为 1M。

文件快照与回滚

fileHistoryMakeSnapshot()src/utils/fileHistory.ts)在 AI 每次修改文件前自动保存当前内容。快照绑定到具体的 message.id,使得 --rewind-files <user-message-id> 可以精确恢复到对话中任意时间点的文件状态——这比 git 更细粒度(git 只追踪已提交的内容)。