回合执行和模型交互
这是系统最核心的“每一轮聊天怎么跑”。用户发来一句话后,普通任务先排队,处理取消和追加输入;回合执行器把历史、工具、钩子和环境标签装好。若聊天太长,压缩模块先把旧内容缩成摘要。模型传输部分再把请求送到模型服务,并一边收回分段结果一边处理重试。流式展示像字幕组,把碎片排成界面和历史。代码模式还提供 JavaScript 小沙盒,让脚本、定时器和外部工具在回合里协同工作。
子阶段
本阶段涉及的状态20
reg-auth-account用户当前是谁、有没有登录、用的是哪种账号或套餐、令牌是否还能用的身份状态。reg-secret-store本机保存的 API Key、ChatGPT 令牌、个人访问令牌、Bedrock Key 等秘密凭据。reg-account-quota-limits账号的额度、限流、消费控制和剩余可用量信息,用来决定请求能不能继续发。reg-thread-session-store当前有哪些线程和会话、哪个正在用、哪些能恢复或关闭的共享会话账本。reg-conversation-history每条线程里的用户消息、模型回复、工具调用、文件改动和审批记录等对话历史。reg-model-catalog系统当前知道有哪些模型、各模型有什么能力、来自哪个提供方以及能不能选用的模型清单。reg-exec-environment-processesexec-server 和统一执行层里正在运行或已经结束的进程、退出码、输入输出和失败原因。reg-ui-interaction-stateTUI 或前端当前显示哪个线程、输入框内容、弹窗、快捷键、通知和终端接管等界面状态。reg-turn-execution-state一轮对话正在排队、运行、取消、追加输入、压缩上下文或等待工具结果的临时但跨模块共享状态。reg-tool-registry模型这一轮能看到和调用的工具集合,包括内置工具、动态工具、插件工具、MCP 工具和搜索工具。reg-observability-telemetry日志、指标、分析事件、请求耗时、工具结果和错误报告等观测数据的收集与发送状态。reg-network-client-proxyHTTP 客户端、证书、Cookie、重试、流式连接和网络代理放行/拒绝规则的共享联网状态。reg-code-mode-sessions代码模式运行器里的会话、正在执行的脚本、等待结果、定时器和宿主回调状态。reg-context-token-budget每轮组装上下文和调用模型时可用的上下文窗口、剩余 token 预算和截断压缩决策状态。reg-realtime-session-state线程级实时对话的会话参数、音频/文本输入缓冲、实时 WebSocket 状态和流式通知进度。reg-instructions-context-store用户规则、项目 AGENTS.md、扩展贡献指令和其他需持续注入模型的说明片段集合。reg-attestation-state宿主或客户端提供的证明材料、证明请求结果以及附加到上游请求的 attestation 头状态。reg-parallel-tool-scheduler并行工具调用的活跃任务、资源占用、互斥限制、取消和收尾调度状态。reg-active-model-selection当前线程或会话实际选中的模型、提供方和推理参数状态,区别于仅列出可用模型的模型目录。reg-session-usage-accounting每个线程或回合累计的模型 token、请求次数、工具用量和成本/用量统计状态,用于展示、持久化和遥测。
代码模式运行时
这些文件介绍嵌入式 JavaScript 运行时,然后说明模块如何加载,以及计时器回调如何在运行时循环中驱动。
code-mode/src/runtime/mod.rs源码 ↗
这个文件解决的是“怎么安全、可控地执行一段代码”的问题。用户给来一段源码后,不能直接堵住主程序运行,所以这里会先初始化 V8(Chrome 同款 JavaScript 引擎),再开一个单独线程来跑代码。主程序和这个线程之间靠通道传消息,通道可以理解成排队信箱:外面把工具结果、报错、定时器触发、终止命令投进去,运行间按顺序取出来处理。它还会把事件发回外面,比如“已启动”“正在等待”“产生了一段输出”“要调用工具”“执行结束”。文件里的 RuntimeState 像运行时的账本,记着正在等待的工具调用、定时器、可保存的值,以及是否要退出。一个重要细节是 PendingRuntimeMode:运行间可以正常等待下一条命令,也可以暂停到外部明确说“继续”为止,这让上层能控制代码何时往下跑。
spawn_runtime65–112 ↗
fn spawn_runtime(
stored_values: HashMap<String, JsonValue>,
request: ExecuteRequest,
event_tx: mpsc::UnboundedSender<RuntimeEvent>,
pending_mode: PendingRuntimeMode,
) -> Result<
作用:启动一个新的代码运行间。调用者给它初始保存值、执行请求和事件发送口,它会开线程跑 V8,并把以后控制这个运行间需要的几个“遥控器”还给调用者。
数据流:进去的是已有保存值、ExecuteRequest(里面有源码、工具设置、调用编号等)、事件发送通道和等待模式。它先确保 V8 已经初始化,然后创建两条标准消息通道:一条给普通运行命令,一条给暂停/继续/终止控制命令;再整理启用工具的信息,开新线程执行 run_runtime。出来的是普通命令发送器、控制命令发送器和 V8 的终止句柄;如果 V8 没起来,就返回错误文字。
调用关系:它是外部进入这个运行系统的入口。测试里的 terminate_execution_stops_cpu_bound_module 和 pending_mode_freezes_runtime_commands_until_resume 会用它启动运行间,其他上层流程比如 start_cell 也会用它。它先调用 initialize_v8 做全局准备,再通过线程把真正的执行交给 run_runtime。
调用图:调用 1 个内部函数(initialize_v8);被 4 处调用(pending_mode_freezes_runtime_commands_until_resume, terminate_execution_stops_cpu_bound_module, start_cell, terminate_waits_for_runtime_shutdown_before_responding);外部调用 3 个(channel, sync_channel, spawn)。
initialize_v8144–158 ↗
fn initialize_v8() -> Result<(), String>
作用:把 V8 JavaScript 引擎做一次全局初始化。V8 这种引擎必须先准备平台和语言数据,否则后面创建运行环境会失败。
数据流:它不需要外部输入,只读取一个 OnceLock(一次性锁存器,用来保证初始化只做一次)。第一次调用时,它加载 ICU 数据(处理国际化文字所需的数据),创建 V8 平台并初始化 V8;以后再调用就直接复用结果。出来的是成功或一段错误文字。
调用关系:spawn_runtime 在每次启动运行间前都会先叫它。它的作用像开店前先通电、开总闸;只有总闸开好,后面的线程和 JavaScript 环境才能创建。
调用图:被 1 处调用(spawn_runtime);外部调用 1 个(new)。
run_runtime160–269 ↗
fn run_runtime(
config: RuntimeConfig,
event_tx: mpsc::UnboundedSender<RuntimeEvent>,
command_rx: std_mpsc::Receiver<RuntimeCommand>,
control_rx: std_mpsc::Receiver<RuntimeControlComma
作用:这是运行间线程里的主工作循环。它真正创建 V8 环境、安装给用户代码用的全局函数、执行源码,并不断处理外面发来的工具结果、定时器和终止命令。
数据流:进去的是运行配置、事件发送通道、命令接收通道、控制接收通道、等待模式、V8 句柄回传通道和运行命令发送器。它创建 V8 Isolate(可理解成一个独立的 JavaScript 小房间),把 RuntimeState 放进上下文,安装全局能力,然后执行用户主模块。执行过程中,它循环取 next_runtime_command 给出的命令:工具成功就把结果塞回对应的 JavaScript Promise,工具失败就把错误塞回去,定时器触发就调用回调,终止就退出。它会不断检查主任务是否完成,完成后发 RuntimeEvent::Result,里面带保存值改动和可能的错误。
调用关系:它由 spawn_runtime 开出的新线程运行,是整个文件的核心调度器。它会把安装全局函数的活交给 globals::install_globals,把加载和解析模块的活交给 module_loader,把定时器回调交给 timers::invoke_timeout_callback;遇到错误时用 capture_scope_send_error 和 send_result 把结果发回上层。
调用图:调用 8 个内部函数(capture_scope_send_error, install_globals, completion_state, evaluate_main_module, resolve_tool_response, next_runtime_command, send_result, invoke_timeout_callback);外部调用 11 个(default, new, send, clone, send, new, new, default, new, new (+1 more))。
next_runtime_command271–293 ↗
fn next_runtime_command(
event_tx: &mpsc::UnboundedSender<RuntimeEvent>,
command_rx: &std_mpsc::Receiver<RuntimeCommand>,
control_rx: &std_mpsc::Receiver<RuntimeControlCommand>,
pendin
作用:决定运行间下一步该等什么命令。它既负责从命令信箱里取消息,也负责在没有消息时告诉外面“我现在挂起等待了”。
数据流:进去的是事件发送通道、普通命令接收通道、控制命令接收通道和等待模式。它先尝试立刻取普通命令;如果没有,就发送 RuntimeEvent::Pending。若模式是 Continue,它会直接阻塞等待下一条普通命令;若模式是 PauseUntilResumed,它会先等控制命令,收到 Resume 才回头继续取普通命令,收到 Terminate 就生成终止命令。出来的是一条 RuntimeCommand,或者在通道断开时返回空。
调用关系:run_runtime 的主循环每一轮都靠它拿下一条指令。它像门卫:普通模式下有人来就放行;暂停模式下,即使普通命令已经排队,也要等上层先说“继续”。
调用图:调用 1 个内部函数(recv);被 1 处调用(run_runtime);外部调用 2 个(send, try_recv)。
capture_scope_send_error295–306 ↗
fn capture_scope_send_error(
scope: &mut v8::PinScope<'_, '_>,
event_tx: &mpsc::UnboundedSender<RuntimeEvent>,
error_text: Option<String>,
)
作用:在 V8 执行环境里出错时,把已经写下的保存值改动尽量带出来,再把错误作为最终结果发给外面。
数据流:进去的是当前 V8 作用域、事件发送通道和可选错误文字。它从作用域里读取 RuntimeState,拿到 stored_value_writes,也就是这次执行已经准备写回的保存值;如果读不到状态,就用空表。然后调用 send_result 发出最终结果。出来没有直接返回值,但会向事件通道发送 Result。
调用关系:run_runtime 在模块加载失败、工具回填失败、定时器回调失败等场景会调用它。它自己不直接组装事件细节,而是把最后一步交给 send_result,保证错误收尾走同一条出口。
调用图:调用 1 个内部函数(send_result);被 1 处调用(run_runtime)。
send_result308–317 ↗
fn send_result(
event_tx: &mpsc::UnboundedSender<RuntimeEvent>,
stored_value_writes: HashMap<String, JsonValue>,
error_text: Option<String>,
)
作用:把一次代码执行的最终结果发回主程序。结果包括这次要写回的保存值,以及有没有错误。
数据流:进去的是事件发送通道、保存值写入表和可选错误文字。它把这些包成 RuntimeEvent::Result,并通过通道发送出去。它不等待对方处理,也不返回业务数据。
调用关系:run_runtime 在正常完成或初始化后续步骤失败时会直接用它;capture_scope_send_error 在出错收尾时也会用它。它是这个文件里“执行结束通知”的统一出口。
调用图:被 2 处调用(capture_scope_send_error, run_runtime);外部调用 1 个(send)。
tests::execute_request335–343 ↗
fn execute_request(source: &str) -> ExecuteRequest
作用:给测试快速造一个 ExecuteRequest。这样每个测试不用重复填写一大串固定字段。
数据流:进去的是一段源码字符串。它把这段源码放进 ExecuteRequest,并填上固定的 tool_call_id、空工具列表、很短的 yield_time_ms 和空的 max_output_tokens。出来的是可直接传给 spawn_runtime 的请求对象。
调用关系:它只在本文件测试里使用。terminate_execution_stops_cpu_bound_module 和 pending_mode_freezes_runtime_commands_until_resume 都调用它来准备测试输入,让测试重点放在运行间行为,而不是构造请求。
调用图:外部调用 1 个(new)。
tests::terminate_execution_stops_cpu_bound_module346–379 ↗
async fn terminate_execution_stops_cpu_bound_module()
作用:测试一个死循环 JavaScript 能不能被外部强制停掉。这很重要,因为用户代码可能一直占 CPU,如果停不掉,整个系统会被拖死。
数据流:它创建事件通道,用 spawn_runtime 启动一段 while(true){} 的代码,然后等待 Started 事件。接着它通过 V8 的 terminate_execution 句柄强制终止执行,再等待 Result 事件,并检查里面确实有错误。最后它确认事件流结束,没有继续冒出多余消息。
调用关系:这是对 spawn_runtime 和运行间终止能力的验证。它通过 execute_request 构造输入,通过事件通道观察 run_runtime 的输出,用超时保护测试本身,避免测试也卡死。
调用图:调用 1 个内部函数(spawn_runtime);外部调用 7 个(from_secs, new, assert!, execute_request, unbounded_channel, panic!, timeout)。
tests::pending_mode_freezes_runtime_commands_until_resume382–447 ↗
async fn pending_mode_freezes_runtime_commands_until_resume()
作用:测试“暂停等待继续”的模式是否真的会冻结运行命令。也就是说,即使定时器已经触发,运行间也不能擅自往下跑,必须等外部发 Resume。
数据流:它启动一段先等定时器、输出文字、再永远等待的 JavaScript,并把运行间设为 PauseUntilResumed。它先确认收到 Started 和 Pending,然后发送 TimeoutFired,但短时间内应收不到输出。接着发送 Resume,才应该收到文本输出“after”,随后再次进入 Pending。最后发送 Terminate 收尾。
调用关系:这个测试主要验证 next_runtime_command 在暂停模式下的门卫作用。它通过 spawn_runtime 启动真实运行间,通过普通命令通道塞入 TimeoutFired,通过控制通道塞入 Resume 和 Terminate,并用事件通道确认 run_runtime 是否按规则行动。
调用图:调用 1 个内部函数(spawn_runtime);外部调用 8 个(from_secs, new, assert!, assert_eq!, execute_request, unbounded_channel, panic!, timeout)。
code-mode/src/runtime/module_loader.rs源码 ↗
可以把这个文件想成“JavaScript 小剧场的场务”。它先把一段源码包装成一个 V8 模块(V8 是 Chrome 使用的 JavaScript 引擎),给它起一个临时文件名,编译、实例化,再执行。执行后如果得到 Promise(异步任务的占位票据),它会保存起来,后面再检查这张票到底完成没有。代码里还有一个特殊的“退出信号”,用来区分脚本主动退出和真正报错。外部工具调用也在这里接回:当工具有结果时,它找到之前等结果的 Promise,把成功值或错误信息塞回 JavaScript。一个重要限制是:这里不支持 import 导入别的模块;无论静态导入还是动态导入,都会被拒绝并抛出错误,避免执行环境偷偷加载外部代码。
evaluate_main_module9–52 ↗
fn evaluate_main_module(
scope: &mut v8::PinScope<'_, '_>,
source_text: &str,
) -> Result<Option<v8::Global<v8::Promise>>, String>
作用:把主 JavaScript 源码编译并执行。调用者用它来启动用户代码,并知道这段代码是立刻结束了,还是留下了一个异步 Promise 需要继续等。
数据流:输入是一段源码文字和当前 V8 执行环境 → 它创建错误捕捉器,给源码加上脚本来源信息,编译成模块,实例化并执行 → 输出是“没有待等任务”或一个全局保存的 Promise;如果编译或执行失败,就返回错误文字;如果遇到特殊退出信号,则当作正常结束。
调用关系:它由 run_runtime 在开始运行用户代码时调用。它会请 script_origin 生成脚本来源信息,用 resolve_module_callback 处理静态 import,用 is_exit_exception 判断是不是主动退出,还会用 value_to_error_text 把 V8 里的异常变成人能读的错误文字。
调用图:调用 3 个内部函数(is_exit_exception, script_origin, value_to_error_text);被 1 处调用(run_runtime);外部调用 6 个(new, pin!, new, try_from, new, compile_module)。
is_exit_exception54–64 ↗
fn is_exit_exception(
scope: &mut v8::PinScope<'_, '_>,
exception: v8::Local<'_, v8::Value>,
) -> bool
作用:判断一个 V8 异常是不是程序主动退出时抛出的特殊标记,而不是普通错误。这样运行时就不会把“用户要求退出”误报成崩溃。
数据流:输入是当前执行环境和一个异常值 → 它读取 RuntimeState 里的 exit_requested 标记,再检查异常是不是指定的字符串哨兵值 → 输出 true 或 false,不改动外部状态。
调用关系:evaluate_main_module 在执行失败时用它判断是否要安静结束;completion_state 在 Promise 被拒绝时也用它判断这次拒绝是不是正常退出。
调用图:被 2 处调用(completion_state, evaluate_main_module);外部调用 2 个(is_string, to_rust_string_lossy)。
resolve_tool_response66–101 ↗
fn resolve_tool_response(
scope: &mut v8::PinScope<'_, '_>,
id: &str,
response: Result<JsonValue, String>,
) -> Result<(), String>
作用:把某个外部工具调用的结果送回正在等待的 JavaScript Promise。脚本调用工具后会暂停等结果,这个函数就是把结果“还票”的地方。
数据流:输入是工具调用 id,以及成功的 JSON 结果或失败的错误文字 → 它从 RuntimeState 的 pending_tool_calls 里取出对应的 Promise 解析器,成功时把 JSON 转成 V8 值并 resolve,失败时把错误文字 reject → 输出成功或错误;同时会从待处理列表里移除这个工具调用。
调用关系:它由 run_runtime 在外部工具返回后调用。它依赖 json_to_v8 把 Rust/JSON 数据变成 JavaScript 能看懂的值,也会用 value_to_error_text 把回填过程中发生的 V8 异常转成文字。
调用图:调用 1 个内部函数(json_to_v8);被 1 处调用(run_runtime);外部调用 3 个(pin!, new, new)。
completion_state103–139 ↗
fn completion_state(
scope: &mut v8::PinScope<'_, '_>,
pending_promise: Option<&v8::Global<v8::Promise>>,
) -> CompletionState
作用:查看当前 JavaScript 执行是不是已经完成,以及是否带着错误完成。调用者用它来决定运行时还要不要继续等待异步任务。
数据流:输入是当前执行环境和一个可选的待等 Promise → 它先读取已经记录的 stored_value_writes,再看有没有 Promise;没有就算完成;有的话检查 Promise 是等待中、成功、还是失败 → 输出 CompletionState,里面包含是否完成、错误文字,以及已记录的存储写入。
调用关系:它由 run_runtime 在主代码执行后或异步推进时调用。遇到失败的 Promise 时,它会交给 is_exit_exception 判断是否是主动退出,否则用 value_to_error_text 生成错误说明。
调用图:调用 2 个内部函数(is_exit_exception, value_to_error_text);被 1 处调用(run_runtime);外部调用 1 个(new)。
script_origin141–162 ↗
fn script_origin(
scope: &mut v8::PinScope<'s, '_>,
resource_name_: &str,
) -> Result<v8::ScriptOrigin<'s>, String>
作用:给要执行的源码做一张“来源标签”。这能让 V8 在报错、调试或生成位置说明时知道这段代码叫什么名字。
数据流:输入是 V8 执行环境和资源名字符串 → 它创建资源名和 source map 地址这两个 V8 字符串,并组装成 ScriptOrigin → 输出这个来源信息;如果字符串分配失败,就返回错误。
调用关系:它只被 evaluate_main_module 使用,在编译主模块前给源码补上来源信息,比如这里的 exec_main.mjs。
调用图:被 1 处调用(evaluate_main_module);外部调用 2 个(new, new)。
resolve_module_callback164–173 ↗
fn resolve_module_callback(
context: v8::Local<'s, v8::Context>,
specifier: v8::Local<'s, v8::String>,
_import_attributes: v8::Local<'s, v8::FixedArray>,
_referrer: v8::Local<'s, v8::M
作用:处理 JavaScript 静态 import 语句的回调。当前策略很简单:任何导入都不支持。
数据流:输入是 V8 给出的上下文、导入名和引用模块等信息 → 它把导入名从 V8 字符串转成 Rust 字符串,再交给 resolve_module → 输出永远是没有模块,通常同时在 V8 里抛出“不支持导入”的异常。
调用关系:它作为回调传给 V8 的模块实例化流程。evaluate_main_module 和 dynamic_import_callback 在实例化模块时都会让 V8 用这套规则处理静态依赖,最后实际判断交给 resolve_module。
调用图:调用 1 个内部函数(resolve_module);外部调用 2 个(to_rust_string_lossy, callback_scope!)。
dynamic_import_callback175–221 ↗
fn dynamic_import_callback(
scope: &mut v8::PinScope<'s, '_>,
_host_defined_options: v8::Local<'s, v8::Data>,
_resource_name: v8::Local<'s, v8::Value>,
specifier: v8::Local<'s, v8::Str
作用:处理 JavaScript 里的动态 import(...)。它返回一个 Promise,但在这个运行环境里,导入外部模块基本会被拒绝。
数据流:输入是当前执行环境和要导入的模块名 → 它创建一个 Promise 解析器,尝试 resolve_module;如果找不到模块,就 reject 一个“不支持导入”的错误;如果将来有模块,也会确保它已实例化和执行,然后把模块命名空间 resolve 出去 → 输出一个给 JavaScript 的 Promise。
调用关系:它是 V8 遇到 import(...) 时调用的入口。它会把模块查找交给 resolve_module;如果模块存在,还会用 resolve_module_callback 处理该模块内部的静态 import。
调用图:调用 1 个内部函数(resolve_module);外部调用 4 个(to_rust_string_lossy, matches!, new, new)。
resolve_module223–235 ↗
fn resolve_module(
scope: &mut v8::PinScope<'s, '_>,
specifier: &str,
) -> Option<v8::Local<'s, v8::Module>>
作用:统一决定某个模块名能不能被导入。当前实现明确拒绝所有导入,并把原因抛给 JavaScript。
数据流:输入是当前执行环境和导入名 → 它拼出“Unsupported import in exec: 某模块”的错误信息,能创建字符串就抛这个字符串,创建失败就抛 undefined → 输出 None,表示没有可用模块。
调用关系:resolve_module_callback 处理静态 import 时会调用它;dynamic_import_callback 处理动态 import(...) 时也会调用它。它是这个文件里“禁止导入外部模块”规则的集中出口。
调用图:被 2 处调用(dynamic_import_callback, resolve_module_callback);外部调用 4 个(throw_exception, format!, new, undefined)。
code-mode/src/runtime/timers.rs源码 ↗
这份文件是在 Rust 写的运行时里,模拟浏览器里常见的定时器。它面对的是 V8,也就是用来执行 JavaScript 的引擎。脚本调用 setTimeout 时,这里会检查第一个参数是不是函数,把这个函数保存起来,给它发一个编号,然后开一个后台线程睡指定的毫秒数。时间到了,后台线程不会直接碰 V8,而是发一条“某个定时器到点了”的命令给运行时主循环,这样更安全。脚本调用 clearTimeout 时,它会把对应编号的回调从等待列表里删掉,等后台线程醒来后也找不到要执行的函数,就等于取消了。真正执行回调的是运行时主循环调用 invoke_timeout_callback,它会把保存的 JavaScript 函数取出来执行,并把 JavaScript 抛出的异常转成普通错误文字。
schedule_timeout12–45 ↗
fn schedule_timeout(
scope: &mut v8::PinScope<'_, '_>,
args: v8::FunctionCallbackArguments,
) -> Result<u64, String>
作用:这个函数安排一个延迟执行的 JavaScript 回调,也就是 setTimeout 背后的主要工作。它会给这次定时分配一个编号,方便以后取消。
数据流:进去的是 V8 当前执行上下文和 JavaScript 传来的参数:第一个参数应该是回调函数,第二个参数是延迟毫秒数。它先确认回调真的是函数,再把延迟时间整理成安全的非负整数,然后把回调保存到运行时状态里的 pending_timeouts 表中,同时启动一个后台线程等待。出来的是一个 timeout_id;另外它改动了运行时状态,增加了一个待执行定时器,并让后台线程到点后发送 TimeoutFired 命令。
调用关系:它由 set_timeout_callback 在 JavaScript 调用 setTimeout 时使用。它自己会借助 V8 的函数转换和全局引用保存回调,并用 thread::spawn 开后台等待;等时间到了,后续工作不是它直接执行,而是交给运行时命令通道,再由 run_runtime 触发真正回调。
调用图:被 1 处调用(set_timeout_callback);外部调用 4 个(get, spawn, new, try_from)。
clear_timeout47–60 ↗
fn clear_timeout(
scope: &mut v8::PinScope<'_, '_>,
args: v8::FunctionCallbackArguments,
) -> Result<(), String>
作用:这个函数取消一个还没执行的定时器,也就是 clearTimeout 背后的主要工作。它的作用就像把闹钟从待响列表里删掉。
数据流:进去的是 V8 当前执行上下文和 JavaScript 传来的参数。它先调用 timeout_id_from_args 从参数里读出定时器编号;如果参数为空、无效或不需要处理,就直接结束。如果拿到了有效编号,它会从运行时状态的 pending_timeouts 表里删除对应回调。出来没有返回值;它的主要结果是让这个编号对应的回调不再被执行。
调用关系:它由 clear_timeout_callback 在 JavaScript 调用 clearTimeout 时使用。它把“怎么从参数里读编号”的小活交给 timeout_id_from_args,自己只负责根据编号删除等待中的定时器。
调用图:调用 1 个内部函数(timeout_id_from_args);被 1 处调用(clear_timeout_callback)。
invoke_timeout_callback62–89 ↗
fn invoke_timeout_callback(
scope: &mut v8::PinScope<'_, '_>,
timeout_id: u64,
) -> Result<(), String>
作用:这个函数在某个定时器到点后,真正执行之前保存的 JavaScript 回调。它还会捕捉回调里抛出的异常,并把异常变成 Rust 能返回的错误文字。
数据流:进去的是 V8 当前执行上下文和一个 timeout_id。它先从运行时状态里取出并删除这个编号对应的回调;如果已经被 clearTimeout 删除了,就什么也不做。若找到了回调,它会在 V8 的 TryCatch(一个捕捉 JavaScript 异常的保护罩)里调用这个函数。出来是成功或错误;成功时回调已经执行完,失败时返回一段描述异常的文字。
调用关系:它由 run_runtime 在收到 TimeoutFired 命令时调用。它不会负责等待时间,也不会负责分配编号;这些是 schedule_timeout 做的。它只管“到点以后安全地执行”,并用 V8 的 TryCatch、undefined 接收者等机制完成调用。
调用图:被 1 处调用(run_runtime);外部调用 3 个(pin!, new, undefined)。
timeout_id_from_args90–106 ↗
fn timeout_id_from_args(
scope: &mut v8::PinScope<'_, '_>,
args: v8::FunctionCallbackArguments,
) -> Result<Option<u64>, String>
作用:这个小函数专门从 clearTimeout 的参数里读出定时器编号,并把各种不该取消的输入处理掉。这样 clear_timeout 不用自己关心参数细节。
数据流:进去的是 V8 当前执行上下文和 JavaScript 传来的参数。它先看有没有参数,或者第一个参数是不是 null/undefined;这些情况都表示没有有效编号。然后它尝试把第一个参数转成数字;如果转不了,就返回错误。若数字不是有限值或小于等于 0,就当作没有有效编号。最后它把小数截断,并限制在 u64 能表示的范围内,出来是一个可选的 timeout_id。
调用关系:它只被 clear_timeout 调用,是 clearTimeout 参数解析的辅助零件。它通过 V8 参数读取接口拿值,把输入清洗成 clear_timeout 可以直接使用的编号。
调用图:被 1 处调用(clear_timeout);外部调用 2 个(get, length)。
normalize_delay_ms108–114 ↗
fn normalize_delay_ms(delay_ms: f64) -> u64
作用:这个小函数把 setTimeout 传进来的延迟时间整理成安全的毫秒数。它避免负数、无穷大、小数等奇怪输入把定时器搞乱。
数据流:进去的是一个浮点数形式的延迟毫秒数。它检查这个数是不是有限值、是不是大于 0;无效或小于等于 0 的值会变成 0,表示尽快执行。有效值会被去掉小数部分,并限制在 u64 最大值以内。出来的是一个 u64 类型的毫秒数,供后台线程 sleep 使用。
调用关系:它被 schedule_timeout 用来清洗 setTimeout 的第二个参数。它不碰 V8,也不改运行时状态,只是一个纯粹的时间数值转换工具。
压缩路径
这些文件涵盖手动压缩任务,以及本地和提供商支持的转录压缩的两种底层实现。
core/src/tasks/compact.rs源码 ↗
这个文件像一个“调度员”,专门处理用户手动触发的对话压缩。压缩可以理解成把一大摞聊天记录整理成一页会议纪要:保留重要信息,删掉重复和细枝末节。真正怎么压缩不在这里做,这里负责判断该走哪条路:如果当前模型和服务支持远程压缩,就把任务交给远程服务;如果还开了新版远程压缩功能,就走新版;否则就在本地构造一段压缩提示词,让普通的压缩流程来做。它还会记录一次指标,说明这次压缩用的是本地、远程还是远程新版,方便之后统计和排查问题。无论压缩结果如何,这个任务本身不直接返回给用户一段文字,而是把压缩过程交给对应模块完成。
CompactTask::kind16–18 ↗
fn kind(&self) -> TaskKind
作用:告诉系统:这个任务的类型是“压缩任务”。系统用这个标识来区分它和普通聊天、工具调用等其他任务。
数据流:进去的是这个任务对象本身,但它不读取复杂数据;它只返回一个固定的任务种类标记:Compact。调用前系统还不知道这个任务该归哪类,调用后就能把它当作压缩任务处理。
调用关系:这是任务框架识别任务身份时会调用的小函数。它不把工作交给别人,只是给外层调度流程一个清楚的标签。
CompactTask::span_name20–22 ↗
fn span_name(&self) -> &'static str
作用:给这次压缩任务起一个用于追踪和日志的名字。这样系统记录运行过程时,能看出这段耗时和行为属于“session_task.compact”。
数据流:进去的是任务对象本身;它不改任何状态,只返回固定字符串“session_task.compact”。调用前日志系统没有这段任务的标准名字,调用后就能用这个名字做性能追踪或问题排查。
调用关系:这是任务运行前后做观测、打点、追踪时会用到的名字提供者。它不参与压缩内容本身,只帮助外层框架看清任务发生了什么。
CompactTask::run24–65 ↗
async fn run(
self: Arc<Self>,
session: Arc<SessionTaskContext>,
ctx: Arc<TurnContext>,
_input: Vec<TurnInput>,
_cancellation_token: CancellationToken,
) ->
作用:真正启动一次压缩任务,并决定用本地压缩、远程压缩,还是新版远程压缩。它像路口的分流牌,把同一个“压缩会话”的请求送到最合适的执行方式。
数据流:进去的是会话信息、当前轮次上下文、用户输入列表和取消令牌;这里的用户输入和取消令牌没有被实际使用。函数先复制出可用的会话对象,再读取当前模型提供方信息和功能开关。若 should_use_remote_compact_task 判断适合远程压缩,并且 RemoteCompactionV2 功能已开启,就记录“remote_v2”指标,然后调用新版远程压缩;若适合远程但没开新版,就记录“remote”指标,然后调用旧版远程压缩;若不适合远程,就记录“local”指标,生成一条系统合成的压缩提示词 UserInput::Text,再交给本地 run_compact_task。最后它返回 None,表示这个任务本身不额外产出一段直接回复文本。
调用关系:外层会话任务调度器在需要执行压缩时会调用它。它先用 should_use_remote_compact_task 做路线判断,再按情况把活交给 run_remote_compact_task 或 run_compact_task;每条路线开始前都会调用 emit_compact_metric 记录统计信息。本地路线还会用 vec! 组装一份只包含压缩提示词的输入。
调用图:调用 4 个内部函数(run_compact_task, should_use_remote_compact_task, run_remote_compact_task, run_remote_compact_task);外部调用 2 个(emit_compact_metric, vec!)。
core/src/compact_remote.rs源码 ↗
模型一次能看的文字有上限,叫上下文窗口。对话越聊越长,工具输出也可能很大,如果不清理,后面的请求就会放不下,甚至失败。这个文件就是远端压缩流程的总控台:先发出“开始压缩”的事件,跑压缩前的钩子(钩子就是外部可插入的小检查或小动作),再把当前历史整理成适合模型看的提示词,调用远端的 compact 接口生成一份更短的新历史。拿到结果后,它会过滤掉不该信任或容易重复的内容,比如过期的开发者指令;必要时还会把当前初始上下文插回正确位置。最后,它把新历史安装回会话里,重新计算 token(可粗略理解为模型按小块计数的文字量),并记录分析数据和错误。一个重要细节是:如果工具输出太大,它不会硬塞进去,而是先把部分输出替换成“已截断”的提示,保证远端压缩请求本身也能放进窗口。
run_inline_remote_auto_compact_task44–63 ↗
async fn run_inline_remote_auto_compact_task(
sess: Arc<Session>,
turn_context: Arc<TurnContext>,
turn_state: Arc<OnceLock<String>>,
initial_context_injection: InitialContextInjection,
作用:这是自动压缩的入口之一,用在一轮对话进行中发现上下文太长的时候。它把“这是自动触发的压缩”以及压缩原因、阶段等信息传下去。
数据流:进去的是当前会话、当前这一轮的上下文、可能保存中的轮次状态、是否要插入初始上下文、压缩原因和阶段 → 它不自己做压缩细节,而是把这些信息整理后交给 run_remote_compact_task_inner → 出来的是成功或失败;成功表示远端压缩已经完成并安装,新历史替换了旧历史。
调用关系:它由 run_auto_compact 调用,属于自动压缩通道。它的主要工作是把触发方式标成 Auto,然后把真正的流程交给 run_remote_compact_task_inner。
调用图:调用 1 个内部函数(run_remote_compact_task_inner);被 1 处调用(run_auto_compact)。
run_remote_compact_task65–89 ↗
async fn run_remote_compact_task(
sess: Arc<Session>,
turn_context: Arc<TurnContext>,
) -> CodexResult<()>
作用:这是用户手动触发远端压缩时用的入口。它会先通知前端或外部监听者:这一轮压缩开始了。
数据流:进去的是会话和当前轮次上下文 → 它根据当前轮次生成一个 TurnStarted 事件并发送出去,然后调用 run_remote_compact_task_inner,说明这是手动触发、用户请求的独立压缩轮 → 出来的是压缩是否成功;失败会把错误继续传出去。
调用关系:它由 run 调用,代表正常运行流程里的手动压缩分支。它先做“开场通知”,随后把共同的压缩流程交给 run_remote_compact_task_inner。
调用图:调用 1 个内部函数(run_remote_compact_task_inner);被 1 处调用(run);外部调用 1 个(TurnStarted)。
run_remote_compact_task_inner91–167 ↗
async fn run_remote_compact_task_inner(
sess: &Arc<Session>,
turn_context: &Arc<TurnContext>,
turn_state: Option<Arc<OnceLock<String>>>,
initial_context_injection: InitialContextInject
作用:这是远端压缩的外层管家。它负责跑前置/后置钩子、记录分析数据、统一处理错误,而不是直接拼请求细节。
数据流:进去的是会话、轮次上下文、可选的轮次状态、上下文插入方式、触发方式、原因和阶段 → 它先创建压缩元数据和分析记录,执行压缩前钩子;如果钩子叫停,就记录“被中断”并返回错误;否则调用 run_remote_compact_task_inner_impl 做真正压缩;成功后再跑压缩后钩子;最后根据结果记录状态、发送错误事件或返回成功 → 出来的是压缩结果,同时会留下分析埋点、错误事件等副作用。
调用关系:run_inline_remote_auto_compact_task 和 run_remote_compact_task 都会进入这里。它像门卫加记账员:先检查能不能做,调用 run_remote_compact_task_inner_impl 真正干活,结束后再收尾并记录结果。
调用图:调用 6 个内部函数(begin, compaction_status_from_result, run_remote_compact_task_inner_impl, run_post_compact_hooks, run_pre_compact_hooks, new);被 2 处调用(run_inline_remote_auto_compact_task, run_remote_compact_task);外部调用 2 个(default, Error)。
run_remote_compact_task_inner_impl169–294 ↗
async fn run_remote_compact_task_inner_impl(
sess: &Arc<Session>,
turn_context: &Arc<TurnContext>,
turn_state: Option<Arc<OnceLock<String>>>,
initial_context_injection: InitialContextI
作用:这是远端压缩真正干活的核心流程。它准备要发给模型的历史、调用远端压缩接口、处理返回的新历史,并把它装回当前会话。
数据流:进去的是会话、轮次上下文、可选轮次状态、初始上下文插入方式、压缩请求元数据和分析详情 → 它创建一个“正在压缩”的会话条目,复制当前历史,必要时先把过大的工具输出改成截断提示;然后构造 Prompt(给模型看的完整输入,包括历史、工具说明、基础指令等),调用 model_client 的 compact_conversation_history;拿到远端返回的新历史后,调用 process_compacted_history 清洗和补上下文;最后记录检查点,把新历史替换进会话,重新计算 token,并发出完成事件 → 出来的是成功或错误;成功时当前会话历史已经变成压缩后的版本。
调用关系:它只由 run_remote_compact_task_inner 调用,是整个文件的发动机。它会把局部小任务交给 trim_function_call_history_to_fit_context_window、built_tools 和 process_compacted_history,并通过模型客户端联系远端压缩服务。
调用图:调用 4 个内部函数(process_compacted_history, trim_function_call_history_to_fit_context_window, built_tools, new);被 1 处调用(run_remote_compact_task_inner);外部调用 5 个(new, new, ContextCompaction, Compaction, info!)。
process_compacted_history296–316 ↗
async fn process_compacted_history(
sess: &Session,
turn_context: &TurnContext,
mut compacted_history: Vec<ResponseItem>,
initial_context_injection: InitialContextInjection,
) -> Vec<R
作用:这个函数负责清洗远端模型返回的压缩历史。它会丢掉不该保留的条目,并在需要时把当前会话的初始上下文插回去。
数据流:进去的是会话、轮次上下文、远端返回的历史列表,以及是否要插入初始上下文的标记 → 如果要求在最后一条真实用户消息前插入初始上下文,它会先从会话里重新生成这份上下文;然后用 should_keep_compacted_history_item 过滤历史;最后调用 insert_initial_context_before_last_real_user_or_summary 把上下文放到合适位置 → 出来的是一份更干净、更适合继续使用的新历史。
调用关系:它由 run_remote_compact_task_inner_impl 在拿到远端压缩结果后调用,测试代码也会直接调用它验证行为。它的位置像质检员:远端给出的内容不能直接全信,必须先过这一关。
调用图:调用 1 个内部函数(insert_initial_context_before_last_real_user_or_summary);被 4 处调用(run_remote_compact_task_inner_impl, run_remote_compact_task_inner_impl, process_compacted_history_with_test_session, process_compacted_history_preserves_separate_guardian_developer_message);外部调用 3 个(new, build_initial_context, matches!)。
should_keep_compacted_history_item334–360 ↗
fn should_keep_compacted_history_item(item: &ResponseItem) -> bool
作用:这个函数判断远端压缩结果里的某一条历史该不该留下。它的重点是保留真实用户消息、摘要和有价值的助手内容,丢掉容易重复或过期的包装信息。
数据流:进去的是一条 ResponseItem,也就是对话历史里的一个项目 → 它查看项目类型和角色:开发者消息会被丢掉;用户消息只有能解析成真实用户消息或钩子提示时才留下;助手消息、代理消息、压缩条目等会保留;工具调用、推理片段、触发标记等会丢掉 → 出来的是 true 或 false,表示保留还是删除。
调用关系:它被 process_compacted_history 用作过滤规则。远端模型可能返回看起来像历史、但其实不该重新安装的内容,这个函数就是那张筛网。
调用图:外部调用 1 个(matches!)。
trim_function_call_history_to_fit_context_window362–402 ↗
fn trim_function_call_history_to_fit_context_window(
history: &mut ContextManager,
turn_context: &TurnContext,
base_instructions: &BaseInstructions,
) -> (usize, i64)
作用:这个函数在发起远端压缩前,先尽量把过大的工具输出缩短,避免连“压缩请求”本身都超过模型窗口。它不会随便删历史,只挑可以安全改写的工具输出下手。
数据流:进去的是可修改的历史、当前轮次上下文和基础指令 → 它先读取模型的上下文窗口大小,再估算当前历史加基础指令需要多少 token;如果太大,就从后往前找可以改写的工具输出,把它替换成简短的“输出太长已截断”信息,替换后重新估算;循环直到放得下、无法估算、或找不到可改写项 → 出来的是两个数字:改写了多少个输出,以及估计删掉了多少 token;同时 history 本身会被实际改短。
调用关系:它由 run_remote_compact_task_inner_impl 在调用远端模型前使用。它会读取 ContextManager 里的原始历史,必要时调用 rewritten_output_for_context_window 生成替换项,再用 replace 放回历史。
调用图:调用 4 个内部函数(estimate_token_count_with_base_instructions, raw_items, replace, model_context_window);被 2 处调用(run_remote_compact_task_inner_impl, run_remote_compact_task_inner_impl)。
rewritten_output_for_context_window404–441 ↗
fn rewritten_output_for_context_window(item: &ResponseItem) -> Option<ResponseItem>
作用:这个函数尝试把某条很占空间的工具结果改写成一个短版本。它只处理几类工具输出,其他历史条目不动。
数据流:进去的是一条 ResponseItem → 如果它是普通函数调用输出或自定义工具输出,就保留调用编号等外壳,把正文换成 truncated_output_payload 生成的截断提示;如果它是工具搜索输出,就保留状态、执行信息等,但清空工具详情列表;如果不是这些类型,就返回 None 表示不能改写 → 出来的是一个可替换的新条目,或没有结果。
调用关系:它被 trim_function_call_history_to_fit_context_window 在缩短历史时使用。它自己又调用 truncated_output_payload 来生成统一的“已截断”正文。
调用图:调用 1 个内部函数(truncated_output_payload);外部调用 1 个(new)。
truncated_output_payload443–448 ↗
fn truncated_output_payload(output: &FunctionCallOutputPayload) -> FunctionCallOutputPayload
作用:这个函数生成一个统一的短工具输出,内容是“输出超过模型上下文并被截断”。这样后续模型知道这里原本有输出,只是因为太长被省略了。
数据流:进去的是原来的工具输出载荷 → 它保留原来的 success 成功标记,但把正文替换成固定的文字提示 → 出来的是新的 FunctionCallOutputPayload,用来放回被改写的历史里。
调用关系:它由 rewritten_output_for_context_window 调用,是工具输出截断时最小的造件函数。它保证不同工具输出被缩短后使用同一句清楚的说明。
调用图:被 1 处调用(rewritten_output_for_context_window);外部调用 1 个(Text)。
core/src/compact.rs源码 ↗
模型一次能读的内容有限,这个文件做的事就像给厚厚一摞聊天记录写会议纪要:保留最近和重要的用户消息,再让模型生成一段总结,然后用这份更短的新历史替换旧历史。它支持用户手动压缩,也支持系统自动压缩;压缩前后还会跑钩子,钩子就是外部可以插进来的检查或拦截步骤。真正执行时,它会把压缩提示发给模型,边接收模型返回边记录结果;如果网络或服务出错,会按退避策略重试,退避就是每次失败后稍等更久再试。成功后,它会更新会话历史、重新计算 token,也就是模型眼里的文字长度单位,并发出提醒:多次压缩可能让模型准确度下降。文件里还带有埋点统计,用来记录压缩是否成功、用了多久、前后上下文变短了多少。
should_use_remote_compact_task69–71 ↗
fn should_use_remote_compact_task(provider: &ModelProviderInfo) -> bool
作用:判断当前模型服务商是否支持“远程压缩”。远程压缩就是把压缩这件事交给服务端专门处理,而不是本地自己走普通模型请求。
数据流:进去的是模型服务商信息 → 它读取其中的能力标记,问一句这个服务商支不支持远程压缩 → 出来一个 true 或 false,告诉上层该走远程路线还是本地路线。
调用关系:自动压缩流程和运行入口会先问它怎么选路。它自己只调用服务商信息里的 supports_remote_compaction,不做压缩,只负责给后面的流程做分岔判断。
调用图:调用 1 个内部函数(supports_remote_compaction);被 2 处调用(run_auto_compact, run)。
run_inline_auto_compact_task73–98 ↗
async fn run_inline_auto_compact_task(
sess: Arc<Session>,
turn_context: Arc<TurnContext>,
initial_context_injection: InitialContextInjection,
reason: CompactionReason,
phase: Comp
作用:执行一次自动触发的本地压缩。它不是用户点按钮开始的,而是系统发现上下文太长等情况后,直接在当前流程里插入一次压缩。
数据流:进去的是会话、当前回合上下文、初始上下文插入方式、压缩原因和阶段 → 它从当前上下文拿到压缩提示词,包装成一条用户输入 → 交给通用压缩流程执行,成功就返回空结果,失败就把错误传回去。
调用关系:它由自动压缩流程调用。它不自己做模型通信,而是把准备好的输入和“自动触发”的标记交给 run_compact_task_inner,让统一的压缩管线继续处理。
调用图:调用 1 个内部函数(run_compact_task_inner);被 1 处调用(run_auto_compact);外部调用 1 个(vec!)。
run_compact_task100–124 ↗
async fn run_compact_task(
sess: Arc<Session>,
turn_context: Arc<TurnContext>,
input: Vec<UserInput>,
) -> CodexResult<()>
作用:执行一次用户手动要求的压缩。它会先告诉前端“一个新回合开始了”,再进入真正的压缩流程。
数据流:进去的是会话、当前回合上下文和用户输入 → 它生成 TurnStarted 事件发出去,让外界知道压缩回合开始 → 然后以“手动触发、用户请求”为原因调用内部流程,最后返回成功或错误。
调用关系:它由运行入口在用户请求压缩时调用。它负责补上手动压缩才需要的开始事件,然后把具体工作交给 run_compact_task_inner。
调用图:调用 1 个内部函数(run_compact_task_inner);被 1 处调用(run);外部调用 1 个(TurnStarted)。
run_compact_task_inner126–195 ↗
async fn run_compact_task_inner(
sess: Arc<Session>,
turn_context: Arc<TurnContext>,
input: Vec<UserInput>,
initial_context_injection: InitialContextInjection,
trigger: CompactionT
作用:这是本地压缩的总调度员。它负责压缩前检查、真正执行压缩、压缩后检查,以及最后记录埋点。
数据流:进去的是会话、上下文、输入、压缩触发来源、原因和阶段 → 它先创建压缩元数据和统计尝试记录,再运行压缩前钩子;如果被拦截就记为中断并返回错误;否则调用真正实现函数;成功后再跑压缩后钩子;最后根据结果记录成功、失败或中断 → 出来的是压缩是否完成。
调用关系:手动压缩和自动压缩都会走到这里。它把实际重活交给 run_compact_task_inner_impl,把结果交给 compaction_status_from_result 判断状态,并用 CompactionAnalyticsAttempt::begin 和 CompactionAnalyticsAttempt::track 做统计闭环。
调用图:调用 6 个内部函数(begin, compaction_status_from_result, run_compact_task_inner_impl, run_post_compact_hooks, run_pre_compact_hooks, new);被 2 处调用(run_compact_task, run_inline_auto_compact_task);外部调用 2 个(clone, default)。
run_compact_task_inner_impl197–331 ↗
async fn run_compact_task_inner_impl(
sess: Arc<Session>,
turn_context: Arc<TurnContext>,
input: Vec<UserInput>,
initial_context_injection: InitialContextInjection,
compaction_meta
作用:这是本地压缩真正干活的地方。它把旧聊天记录送给模型生成摘要,再用摘要和必要的用户消息拼出新的短历史。
数据流:进去的是会话、当前回合、用户输入、初始上下文插入规则和压缩元数据 → 它把本次输入写入临时历史,构造给模型的 Prompt,也就是模型请求内容;然后调用 drain_to_completed 直到模型返回完整摘要;遇到上下文超限会删掉最旧历史再试,遇到普通错误会重试;成功后从历史中取出最后的助手回复作为摘要,收集旧的用户消息,构建新的压缩历史,必要时把初始上下文插回合适位置,最后替换会话历史、重算 token、发送完成和警告事件 → 出来的是摘要正文。
调用关系:它只由 run_compact_task_inner 调用,是整条本地压缩链的核心工序。它会请 drain_to_completed 负责模型流式返回,请 collect_user_messages 找出用户原话,请 build_compacted_history 组装新历史,请 insert_initial_context_before_last_real_user_or_summary 处理初始上下文的位置。
调用图:调用 8 个内部函数(build_compacted_history, collect_user_messages, drain_to_completed, insert_initial_context_before_last_real_user_or_summary, get_last_assistant_message_from_turn, backoff, new, from);被 1 处调用(run_compact_task_inner);外部调用 10 个(default, new, ContextCompaction, Compaction, error!, format!, matches!, Error, Warning, sleep)。
CompactionAnalyticsAttempt::begin354–374 ↗
async fn begin(
sess: &Session,
turn_context: &TurnContext,
trigger: CompactionTrigger,
reason: CompactionReason,
implementation: CompactionImplementation,
作用:开始记录一次压缩统计。它像给一次压缩按下秒表,并记下开始时对话有多长。
数据流:进去的是会话、回合上下文、触发方式、原因、实现方式和阶段 → 它读取线程 ID、回合 ID、当前 token 用量和当前时间 → 出来一个 CompactionAnalyticsAttempt 对象,里面保存了之后上报统计需要的起点信息。
调用关系:本地压缩流程 run_compact_task_inner 会在真正压缩前调用它,远程压缩流程也会复用它。它的结果稍后会交给 CompactionAnalyticsAttempt::track,用来补上结束状态和耗时。
调用图:被 3 处调用(run_compact_task_inner, run_remote_compact_task_inner, run_remote_compact_task_inner);外部调用 3 个(now, now_unix_seconds, get_total_token_usage)。
CompactionAnalyticsAttempt::track376–417 ↗
async fn track(
self,
sess: &Session,
status: CompactionStatus,
codex_error: Option<&CodexErr>,
details: CompactionAnalyticsDetails,
)
作用:结束并上报一次压缩统计。它告诉分析系统:这次压缩成功了吗、花了多久、压缩前后上下文大概有多大。
数据流:进去的是之前 begin 得到的尝试记录、当前会话、最终状态、可能的错误和额外细节 → 它读取压缩后的 token 用量,计算从开始到现在的耗时,把错误类型和 HTTP 状态码等信息整理进事件 → 输出不是返回给调用者的数据,而是向 analytics_events_client 发送一条压缩埋点事件。
调用关系:压缩流程在成功、失败、被用户或钩子中断时都会用它收尾。它不决定压缩结果,只负责把结果变成可统计的数据。
调用图:外部调用 4 个(elapsed, now_unix_seconds, get_total_token_usage, try_from)。
compaction_status_from_result420–426 ↗
fn compaction_status_from_result(result: &CodexResult<T>) -> CompactionStatus
作用:把程序里的成功或错误结果,翻译成统计系统能懂的压缩状态。这样埋点不用理解所有错误细节。
数据流:进去的是一个 CodexResult,也就是可能成功也可能失败的结果 → 如果成功就标为 Completed;如果是 Interrupted 或 TurnAborted 这类被打断的错误,就标为 Interrupted;其他错误标为 Failed → 出来一个 CompactionStatus。
调用关系:run_compact_task_inner 和远程压缩流程会在拿到压缩结果后调用它。它给 CompactionAnalyticsAttempt::track 提供状态标签。
调用图:被 3 处调用(run_compact_task_inner, run_remote_compact_task_inner, run_remote_compact_task_inner)。
content_items_to_text428–445 ↗
fn content_items_to_text(content: &[ContentItem]) -> Option<String>
作用:把一组消息内容里的文字抽出来,合成一段普通文本。它会忽略图片,因为图片不能直接拼进文本摘要。
数据流:进去的是 ContentItem 列表,里面可能有输入文字、输出文字或图片 → 它收集非空文字项,用换行符连起来,图片直接跳过 → 如果有文字就返回这段文本,没有文字就返回 None。
调用关系:守护检查的 transcript 收集和当前线程区块构建会用它。它是小工具函数,帮助其他模块把结构化消息变成可展示、可拼接的纯文本。
调用图:被 2 处调用(collect_guardian_transcript_entries, build_current_thread_section);外部调用 1 个(new)。
collect_user_messages453–473 ↗
fn collect_user_messages(items: &[ResponseItem]) -> Vec<CompactedUserMessage>
作用:从旧历史里挑出真正的用户消息,供压缩后继续保留。它会跳过已经是压缩摘要的消息,避免摘要套摘要越堆越乱。
数据流:进去的是一串 ResponseItem,也就是模型历史里的原始项目 → 它逐个解析成更好懂的 TurnItem,遇到用户消息就取出文字和元数据;如果这条其实是以摘要前缀开头的压缩摘要,就丢掉 → 出来是 CompactedUserMessage 列表。
调用关系:run_compact_task_inner_impl 在重建短历史前会调用它,远程压缩回放重建历史时也会用。它为 build_compacted_history 提供可保留的用户原话。
调用图:被 2 处调用(run_compact_task_inner_impl, reconstruct_history_from_rollout);外部调用 1 个(iter)。
is_summary_message475–477 ↗
fn is_summary_message(message: &str) -> bool
作用:判断一条用户样式的消息是不是压缩摘要。因为压缩摘要在历史里也会被编码成 user message,所以需要一个明显前缀来认出来。
数据流:进去的是一段消息文字 → 它检查文字是否以 SUMMARY_PREFIX 加换行开头 → 出来 true 或 false。
调用关系:insert_initial_context_before_last_real_user_or_summary 会用它区分真实用户消息和摘要消息。collect_user_messages 里也依赖同样的判断思路来避免保留旧摘要。
调用图:被 1 处调用(insert_initial_context_before_last_real_user_or_summary);外部调用 1 个(format!)。
insert_initial_context_before_last_real_user_or_summary489–534 ↗
fn insert_initial_context_before_last_real_user_or_summary(
mut compacted_history: Vec<ResponseItem>,
initial_context: Vec<ResponseItem>,
) -> Vec<ResponseItem>
作用:把“初始上下文”插回压缩后的历史里,并放到模型最容易理解的位置。初始上下文可以理解为开局说明、环境信息、系统已知资料。
数据流:进去的是已经压缩好的历史和要插入的初始上下文 → 它从后往前找最后一条真实用户消息;找不到就找最后一条摘要样式的用户消息;再找不到就找最后一个压缩项;如果都没有,就追加到末尾 → 出来是一份已经插入初始上下文的新历史。
调用关系:run_compact_task_inner_impl 在某些中途压缩场景会调用它,远程压缩处理后的历史也会用它。它内部通过 parse_turn_item 识别历史项,并用 is_summary_message 判断摘要,目的是保证最后的摘要或压缩项仍然待在模型期望的位置。
调用图:调用 2 个内部函数(is_summary_message, parse_turn_item);被 2 处调用(run_compact_task_inner_impl, process_compacted_history)。
build_compacted_history536–547 ↗
fn build_compacted_history(
initial_context: Vec<ResponseItem>,
user_messages: &[CompactedUserMessage],
summary_text: &str,
) -> Vec<ResponseItem>
作用:按默认限制生成压缩后的新历史。它把初始上下文、保留的用户消息和最终摘要拼成模型后续要看的短版本聊天记录。
数据流:进去的是初始上下文、用户消息列表和摘要文本 → 它使用默认的用户消息 token 上限,调用 build_compacted_history_with_limit 做实际拼装 → 出来是新的 ResponseItem 列表。
调用关系:本地压缩完成后由 run_compact_task_inner_impl 调用,远程压缩重建和测试样例也会调用。它是对 build_compacted_history_with_limit 的简单封装,让大多数地方不用关心具体上限数字。
调用图:调用 1 个内部函数(build_compacted_history_with_limit);被 3 处调用(run_compact_task_inner_impl, reconstruct_history_from_rollout, sample_rollout)。
build_compacted_history_with_limit549–606 ↗
fn build_compacted_history_with_limit(
mut history: Vec<ResponseItem>,
user_messages: &[CompactedUserMessage],
summary_text: &str,
max_tokens: usize,
) -> Vec<ResponseItem>
作用:真正拼装压缩历史,并控制保留的用户原话不要太长。它优先保留最近的用户消息,必要时截短最老的那条入选消息。
数据流:进去的是已有历史、用户消息列表、摘要文本和最大 token 数 → 它从最后一条用户消息往前挑,计算每条大概占多少 token;能放下就保留,放不下就按剩余额度截断;然后把选中的用户消息按原顺序写进历史,最后再追加摘要消息;如果摘要为空,就放入“没有可用摘要”的占位文字 → 出来是一份长度受控的新历史。
调用关系:它由 build_compacted_history 调用,是压缩后历史形状的具体制造者。它会使用 approx_token_count 估算长度,用 truncate_text 截断超长文本。
调用图:被 1 处调用(build_compacted_history);外部调用 6 个(new, approx_token_count, truncate_text, iter, Tokens, vec!)。
drain_to_completed608–657 ↗
async fn drain_to_completed(
sess: &Session,
turn_context: &TurnContext,
client_session: &mut ModelClientSession,
responses_metadata: &CodexResponsesMetadata,
prompt: &Prompt,
) ->
作用:把发给模型的压缩请求一路读到完成。它像一直盯着水管出水:中间的内容、限流信息、token 用量都要及时记录,直到看到完成信号。
数据流:进去的是会话、回合上下文、模型客户端会话、请求元数据和 Prompt → 它打开模型流式响应,循环读取事件;收到输出项就记入会话历史,收到服务端推理标记就更新状态,收到限流快照就更新限流信息,收到完成事件就更新 token 用量并返回成功;如果流提前断开或事件报错,就返回对应错误。
调用关系:run_compact_task_inner_impl 每次尝试向模型要摘要时都会调用它。它直接和模型客户端的 stream 打交道,但不负责重试;重试由外层 run_compact_task_inner_impl 根据它返回的错误来决定。
调用图:调用 2 个内部函数(stream, disabled);被 1 处调用(run_compact_task_inner_impl);外部调用 6 个(record_conversation_items, set_server_reasoning_included, update_rate_limits, update_token_usage_info, Stream, from_ref)。
轮次执行
这些文件描述普通轮次如何启动、补充元数据,并通过中央轮次引擎执行直至完成。
core/src/tasks/regular.rs源码 ↗
这个文件里的 RegularTask 可以理解成一次普通聊天回合的“现场调度员”。用户发来一段话后,它先告诉外部“这一轮已经开始了”,里面带上回合编号、追踪编号、开始时间、模型上下文窗口等信息。然后它会处理一种启动优化:startup prewarm,意思是系统可能提前准备好了一个客户端会话,好让第一轮更快开始;如果准备过程被取消,就直接结束。真正生成回复的活儿不在这里做,而是交给 run_turn。这个文件还会注意一个容易出错的情况:用户在当前回合运行时又追加了输入。它不会立刻新开一轮,而是在当前 run_turn 结束后检查队列,如果还有待处理输入,就用空输入继续跑下一次,让排队内容被消化掉。取消令牌(一种“停工信号”)也会传下去,方便外部中止任务。
RegularTask::new22–24 ↗
fn new() -> Self
作用:创建一个新的普通对话任务。它本身不带复杂数据,只是给外部一个可以启动普通回合的任务对象。
数据流:进去没有额外参数 → 它直接构造一个 RegularTask → 出来一个新的普通任务实例,不修改别的状态。
调用关系:当系统收到用户输入、发现空闲时要启动一轮对话,或测试普通回合行为时,会先调用它拿到任务对象。之后这个对象会通过 SessionTask 这套任务接口继续参与 kind、span_name 和 run 等流程。
调用图:被 5 处调用(user_input_or_turn_inner, try_start_turn_if_idle, interrupting_regular_turn_waiting_on_startup_prewarm_emits_turn_aborted, regular_turn_emits_turn_started_with_trace_id_without_waiting_for_startup_prewarm, maybe_start_turn_for_pending_work_with_sub_id)。
RegularTask::kind28–30 ↗
fn kind(&self) -> TaskKind
作用:告诉系统这个任务属于“普通任务”。这能让调度系统区分它和其他类型的任务,比如特殊后台任务或别的会话任务。
数据流:进去是这个任务对象本身 → 它不看外部数据,只返回固定的 TaskKind::Regular → 出来的是一个任务类型标签,不改任何东西。
调用关系:任务调度或状态记录需要知道当前跑的是什么任务时,会通过 SessionTask 接口问它的 kind。RegularTask 用这个函数把自己标记成普通对话回合。
RegularTask::span_name32–34 ↗
fn span_name(&self) -> &'static str
作用:给追踪日志起一个固定名字。追踪日志可以理解成系统里的“行车记录仪”,用来事后看这一段任务花了多久、卡在哪里。
数据流:进去是这个任务对象本身 → 它返回固定文本 session_task.turn → 出来的是给日志和性能追踪用的名字,不修改状态。
调用关系:外层任务框架在记录这一类任务的运行轨迹时会用到它。这个名字让普通对话回合在日志里更容易被识别。
RegularTask::run36–88 ↗
async fn run(
self: Arc<Self>,
session: Arc<SessionTaskContext>,
ctx: Arc<TurnContext>,
input: Vec<TurnInput>,
cancellation_token: CancellationToken,
) -> O
作用:真正启动并串起一次普通对话回合。它先发出“回合开始”的事件,再处理预热会话,最后反复调用 run_turn,直到当前和排队的输入都处理完。
数据流:进去的是会话上下文、这一轮的上下文、用户输入列表,以及一个取消令牌 → 它复制出可用的会话句柄,发送 TurnStarted 事件,重置“服务端推理内容是否已包含”的标记,然后尝试取出启动预热好的客户端会话;如果被取消就返回空结果 → 接着把输入交给 run_turn,并传入子取消令牌;每跑完一次就检查输入队列,如果没有新输入,就返回最后一条助手消息,如果还有排队输入,就清空本轮显式输入并继续跑下一轮。
调用关系:这是 RegularTask 被调度后最核心的执行入口。它自己不生成模型回复,而是把重活交给 run_turn;它会调用 trace_span! 给准备阶段和 run_turn 阶段加上追踪标记,会调用 CancellationToken 的 child_token 把“可取消”能力继续传下去,也会构造 TurnStarted 事件通知外部这一轮已经开始。
调用图:调用 1 个内部函数(run_turn);外部调用 5 个(clone, child_token, new, TurnStarted, trace_span!)。
core/src/turn_metadata.rs源码 ↗
一次请求发给模型时,光有用户说的话还不够,还要带上很多背景:这是哪个会话、哪个线程、是不是子任务、当前目录是不是 Git 仓库、代码有没有改动、沙箱权限是什么。这个文件就是专门收集和拼装这些“背景标签”的地方。核心是 TurnMetadataState,它保存一个回合的固定信息,也保存运行中才知道的信息,比如回合开始时间、客户端额外传来的 metadata、用户是否中途被要求输入。Git 信息比较慢,所以它会用后台任务异步获取,避免卡住主流程;拿到后再填进 workspace 信息里。它还会过滤客户端传来的额外信息,避免保留不该由客户端覆盖的字段。简单说,它是请求的“随车记录单”:让系统和服务端能准确理解这次请求的身份、来源和工作区状态。
WorkspaceGitMetadata::is_empty48–52 ↗
fn is_empty(&self) -> bool
作用:判断一个工作区的 Git 信息是不是完全没拿到。这样后面就不会把一条空的工作区记录硬塞进 metadata 里。
数据流:输入是一个 WorkspaceGitMetadata,也就是远程仓库地址、最新提交号、是否有改动这三类信息 → 它逐项检查这些字段是不是都为空 → 输出 true 或 false,不改动任何数据。
调用关系:后台 Git 补充任务和 memory_workspaces 都会在准备写入工作区信息前用它把关;如果它说是空的,后续就直接不写这条工作区记录。
TurnMetadataWorkspace::from56–62 ↗
fn from(value: WorkspaceGitMetadata) -> Self
作用:把本文件内部用的 Git 信息格式,转换成 responses metadata 对外使用的工作区格式。它像把内部草稿誊写成正式表格。
数据流:输入是 WorkspaceGitMetadata → 它把远程地址、最新提交号、是否有改动原样搬到 TurnMetadataWorkspace 对应字段里 → 输出一个可以放进最终请求 metadata 的工作区对象。
调用关系:spawn_git_enrichment_task 和 memory_workspaces 拿到 Git 信息后,会通过这个转换把结果放进正式的 workspaces 字段。
detached_memory_responses_metadata66–82 ↗
async fn detached_memory_responses_metadata(
installation_id: String,
session_id: String,
thread_id: String,
window_id: String,
session_source: &SessionSource,
cwd: &AbsolutePa
作用:为“记忆”类请求单独生成一份 metadata。这里的“记忆”可以理解成系统在主对话之外保存或读取长期信息时,也要带上环境标签。
数据流:输入安装 ID、会话 ID、线程 ID、窗口 ID、会话来源、当前目录和沙箱信息 → 它创建基础 CodexResponsesMetadata,标记请求类型为 Memory,加入子任务来源标识,并调用 memory_workspaces 补上当前 Git 工作区信息 → 输出一份可直接随记忆请求发送的 metadata。
调用关系:这是记忆请求专用的入口函数;它把基础 metadata 创建、子任务标识 subagent_header_value、以及 Git 工作区收集 memory_workspaces 串起来。
调用图:调用 3 个内部函数(new, subagent_header_value, memory_workspaces)。
TurnMetadataState::new105–143 ↗
fn new(
session_id: String,
thread_id: String,
forked_from_thread_id: Option<ThreadId>,
parent_thread_id: Option<ThreadId>,
session_source: &SessionSource,
作用:创建一个回合的 metadata 状态对象。每个新回合开始时,系统用它把会话、线程、权限、当前目录等基础信息先装进一个地方。
数据流:输入会话 ID、线程 ID、父子线程关系、会话来源、回合 ID、当前目录、权限配置、Windows 沙箱级别和网络限制设置 → 它尝试找当前目录所在的 Git 根目录,生成沙箱标签,判断子任务相关标识,并初始化一些可共享、可后续更新的字段 → 输出一个 TurnMetadataState,后面整个回合都会围绕它补充和读取 metadata。
调用关系:它通常在创建回合上下文、开启 review 线程或测试场景里被调用;后续 current_meta_value_for_mcp_request、to_responses_metadata、spawn_git_enrichment_task 等函数都依赖它保存的状态。
调用图:调用 3 个内部函数(subagent_header_value, subagent_metadata_kind, permission_profile_sandbox_tag);被 14 处调用(spawn_review_thread, make_turn_context, turn_metadata_state_ignores_client_reserved_metadata_before_start, turn_metadata_state_includes_forked_thread_spawn_subagent_lineage, turn_metadata_state_includes_known_parent_for_non_thread_spawn_subagents_without_fork, turn_metadata_state_includes_model_and_reasoning_effort_only_in_request_meta, turn_metadata_state_includes_root_fork_lineage, turn_metadata_state_includes_thread_spawn_subagent_parent_without_fork, turn_metadata_state_includes_turn_started_at_unix_ms_after_start, turn_metadata_state_marks_user_input_requested_during_turn_only_for_mcp_request_meta (+4 more));外部调用 6 个(new, new, new, new, new, get_git_repo_root)。
TurnMetadataState::current_meta_value_for_mcp_request145–181 ↗
fn current_meta_value_for_mcp_request(
&self,
context: McpTurnMetadataContext<'_>,
) -> Option<serde_json::Value>
作用:生成发给 MCP 请求时使用的当前回合 metadata。MCP 可以理解成外部工具或服务的连接协议,这里要给它一份 JSON 格式的背景说明。
数据流:输入模型名和可选的推理强度 → 它先拿 responses_metadata_template 做底稿,再把模型名写进去;如果有推理强度就写入,没有就移除;如果本回合曾要求用户输入,也会加上这个标记 → 输出一个 serde_json::Value,也就是 JSON 值;如果底稿不是 JSON 对象就返回空。
调用关系:它在需要给 MCP 工具请求附带 metadata 时使用;内部依赖 responses_metadata_template 生成通用底稿,再临时加上只属于这次 MCP 请求的信息。
调用图:调用 1 个内部函数(responses_metadata_template);外部调用 3 个(Bool, Object, String)。
TurnMetadataState::to_responses_metadata183–195 ↗
fn to_responses_metadata(
&self,
installation_id: String,
window_id: String,
request_kind: CodexResponsesRequestKind,
) -> CodexResponsesMetadata
作用:把当前状态打包成发送给 Responses API 的正式 metadata。Responses API 可以理解成和模型服务通信的接口。
数据流:输入安装 ID、窗口 ID 和请求类型 → 它取当前状态生成通用模板,再补上这次请求特有的安装 ID、窗口 ID、请求类型 → 输出完整的 CodexResponsesMetadata。
调用关系:它是把 TurnMetadataState 变成正式请求 metadata 的主要出口;内部把大部分活交给 responses_metadata_template,测试也会用它检查 JSON 结果是否正确。
调用图:调用 1 个内部函数(responses_metadata_template);被 1 处调用(test_responses_metadata_json)。
TurnMetadataState::mark_user_input_requested_during_turn197–200 ↗
fn mark_user_input_requested_during_turn(&self)
作用:记录本回合中系统曾经要求用户补充输入。这个标记之后会写进请求 metadata,方便后端知道这次回合不是完全自动完成的。
数据流:输入只有当前状态对象 → 它把 user_input_requested_during_turn 这个原子布尔值设为 true;原子布尔值可以理解成多任务同时读写也不会撕裂的小开关 → 不返回结果,但状态被永久标记到本回合结束。
调用关系:当流程中某处需要用户再说一句或确认时会调用它;之后 current_meta_value_for_mcp_request 会读取这个标记,并决定是否把它放进 JSON metadata。
TurnMetadataState::set_responsesapi_client_metadata202–211 ↗
fn set_responsesapi_client_metadata(
&self,
responsesapi_client_metadata: HashMap<String, String>,
)
作用:保存客户端传来的额外 metadata,但会先过滤掉不该让客户端随便写的字段。这样既能保留有用补充信息,也能防止客户端覆盖系统保留信息。
数据流:输入一个 HashMap,也就是一组字符串键值对 → 它调用 filter_extra_metadata 做筛选,再把筛选后的结果写进状态里的 responsesapi_client_metadata → 不返回结果,但之后生成 metadata 时会带上这些额外字段。
调用关系:客户端 metadata 到达时会调用它;responses_metadata_template 后面会读取这里保存的内容,workspace_kind 也会从这里取 workspace_kind 字段。
调用图:调用 1 个内部函数(filter_extra_metadata)。
TurnMetadataState::workspace_kind213–219 ↗
fn workspace_kind(&self) -> Option<String>
作用:读取客户端额外 metadata 里声明的工作区类型。比如系统可能想知道当前工作区是普通项目、云端环境,还是别的类型。
数据流:输入是当前状态对象 → 它从 responsesapi_client_metadata 里查找 workspace_kind 这个键 → 找到就返回对应字符串,找不到就返回空;不修改任何状态。
调用关系:它是给其他流程查询工作区类型的小窗口;它依赖 set_responsesapi_client_metadata 之前保存并过滤过的客户端信息。
TurnMetadataState::responses_metadata_template221–243 ↗
fn responses_metadata_template(&self) -> CodexResponsesMetadata
作用:生成一份通用 metadata 底稿。很多不同类型的请求都需要同一批基础信息,所以这里集中拼好,避免到处重复。
数据流:输入是当前状态对象 → 它读取回合 ID、父子线程关系、子任务标识、沙箱标签、当前工作区信息、回合开始时间和额外 metadata,再用 CodexResponsesMetadata::new 建一个基础对象 → 输出一份还没填安装 ID、窗口 ID、具体请求类型的 CodexResponsesMetadata。
调用关系:current_meta_value_for_mcp_request 和 to_responses_metadata 都会先调用它拿底稿;它又会调用 current_workspaces 和 current_turn_started_at_unix_ms 读取最新状态。
调用图:调用 3 个内部函数(new, current_turn_started_at_unix_ms, current_workspaces);被 3 处调用(current_meta_value_for_mcp_request, to_responses_metadata, test_turn_metadata_header);外部调用 1 个(new)。
TurnMetadataState::current_workspaces245–251 ↗
fn current_workspaces(&self) -> BTreeMap<String, TurnMetadataWorkspace>
作用:取出当前已经补充好的工作区信息。这里主要是 Git 仓库地址、提交号、是否有未提交改动等内容。
数据流:输入是当前状态对象 → 它读取 enriched_workspaces 这份共享数据;如果后台任务还没填好,就使用空表 → 输出一个 BTreeMap,也就是按键有序排列的工作区信息表。
调用关系:responses_metadata_template 会调用它,把当前能拿到的工作区信息放进最终 metadata;这些信息通常由 spawn_git_enrichment_task 的后台任务写入。
调用图:被 1 处调用(responses_metadata_template)。
TurnMetadataState::current_turn_started_at_unix_ms253–258 ↗
fn current_turn_started_at_unix_ms(&self) -> Option<i64>
作用:读取本回合开始的 Unix 毫秒时间戳。Unix 时间戳就是从 1970 年 1 月 1 日起算的时间数字,方便机器统一比较时间。
数据流:输入是当前状态对象 → 它读取 turn_started_at_unix_ms 这个共享字段 → 输出一个可选的 i64 数字;如果还没设置,就返回空。
调用关系:responses_metadata_template 会调用它,把回合开始时间写入 metadata;这个值通常先由 set_turn_started_at_unix_ms 设置。
调用图:被 1 处调用(responses_metadata_template)。
TurnMetadataState::set_turn_started_at_unix_ms260–265 ↗
fn set_turn_started_at_unix_ms(&self, turn_started_at_unix_ms: i64)
作用:记录本回合开始的时间。这样之后发送出去的 metadata 能说明这次回合从什么时候开始。
数据流:输入一个毫秒级 Unix 时间戳 → 它把这个数字写进 turn_started_at_unix_ms 字段里,包装成 Some 表示已经有值 → 不返回结果,但状态被更新。
调用关系:回合真正开始时会调用它;之后 responses_metadata_template 通过 current_turn_started_at_unix_ms 读取这个时间并放进请求 metadata。
TurnMetadataState::spawn_git_enrichment_task267–298 ↗
fn spawn_git_enrichment_task(&self)
作用:启动一个后台任务去补充 Git 工作区信息。这样主请求不用等 Git 命令跑完,用户不会因为收集 metadata 被明显拖慢。
数据流:输入是当前状态对象 → 它先确认当前目录确实在 Git 仓库里,再检查是否已经启动过任务;如果没有,就克隆一份状态并用 tokio::spawn 启动异步任务;任务里会获取 Git 信息,非空时写入 enriched_workspaces → 函数本身不返回数据,但会让后台慢慢把工作区信息补上。
调用关系:回合开始后需要补充 Git 信息时会调用它;它启动的任务会调用 fetch_workspace_git_metadata,最后通过 WorkspaceGitMetadata::is_empty 和 TurnMetadataWorkspace::from 决定是否写入正式工作区表。
TurnMetadataState::cancel_git_enrichment_task300–308 ↗
fn cancel_git_enrichment_task(&self)
作用:取消正在后台收集 Git 信息的任务。比如回合结束或状态不再需要时,避免后台任务继续占资源。
数据流:输入是当前状态对象 → 它拿到保存后台任务句柄的锁;如果里面有任务,就取出来并调用 abort 中止 → 不返回结果,但后台任务会被取消,任务句柄也从状态中清掉。
调用关系:在清理阶段或不再需要 Git 补充信息时使用;它和 spawn_git_enrichment_task 配套,一个负责启动,一个负责停掉。
TurnMetadataState::fetch_workspace_git_metadata310–323 ↗
async fn fetch_workspace_git_metadata(&self) -> WorkspaceGitMetadata
作用:实际去读取当前工作区的 Git 信息。它会同时问三个问题:最新提交是什么、远程仓库地址有哪些、工作区有没有改动。
数据流:输入是当前状态对象里的 cwd 当前目录 → 它用 tokio::join! 并发执行三个 Git 查询,意思是三件事一起等而不是排队等;然后把提交哈希、远程地址和改动状态装进 WorkspaceGitMetadata → 输出这份内部 Git 信息对象。
调用关系:它主要被 spawn_git_enrichment_task 启动的后台任务调用;拿到结果后,后台任务会判断是否为空,再转换成 TurnMetadataWorkspace 写入状态。
调用图:外部调用 1 个(join!)。
memory_workspaces326–345 ↗
async fn memory_workspaces(cwd: &AbsolutePathBuf) -> BTreeMap<String, TurnMetadataWorkspace>
作用:为记忆请求收集当前目录对应的工作区 Git 信息。它是 detached_memory_responses_metadata 的辅助函数。
数据流:输入当前目录 cwd → 它先找 Git 仓库根目录,再并发读取最新提交号、远程仓库地址、是否有改动;如果找到了仓库根目录且信息不是空的,就把它放进以仓库根目录为键的工作区表 → 输出这张 workspaces 表,可能为空。
调用关系:detached_memory_responses_metadata 在生成记忆请求 metadata 时调用它;它内部会用 WorkspaceGitMetadata::is_empty 过滤空结果,并用 TurnMetadataWorkspace::from 转成正式格式。
调用图:被 1 处调用(detached_memory_responses_metadata);外部调用 3 个(new, get_git_repo_root, join!)。
core/src/session/turn.rs源码 ↗
可以把这个文件看成一次对话回合的“现场导演”。用户输入进来后,它先检查有没有安全拦截和额外上下文,再决定要给模型塞哪些技能、插件和工具说明。然后它向模型发请求,并一边接收模型像打字一样流出来的内容,一边把内容发给前端显示。如果模型要求调用工具,它会执行工具,把结果记回对话历史,再继续问模型。它还会盯着上下文长度,太长时自动压缩,避免超过模型能读的上限。计划模式下,它会把“计划内容”和普通回复分开显示,避免用户看到空消息或混在一起的文本。没有这个文件,系统就很难把一次对话中这些异步发生的事情按正确顺序收拢起来。
run_turn137–411 ↗
async fn run_turn(
sess: Arc<Session>,
turn_context: Arc<TurnContext>,
turn_extension_data: Arc<codex_extension_api::ExtensionData>,
input: Vec<TurnInput>,
prewarmed_client_session
作用:跑完整个对话回合:从收到用户输入,到模型回复、工具调用、自动压缩、收尾钩子全部结束。别人想让会话真正前进一步时,会从这里开始。
数据流:输入是会话、当前回合上下文、用户输入、可复用的模型连接和取消信号。它先做预压缩和输入记录,再注入技能/插件说明,随后循环向模型采样;模型如果要工具就执行并继续,如果给出最终回复就记录并返回最后一条助手消息。过程中会改动会话历史、连接器选择、token 统计和发给客户端的事件。
调用关系:它由更上层的 run 调用,是本文件的总调度。它会把准备工作交给 run_pre_sampling_compact、build_skills_and_plugins、run_hooks_and_record_inputs,把真正请求模型交给 run_sampling_request,并在需要时调用 run_auto_compact 和各种钩子做收尾。
调用图:调用 15 个内部函数(run_legacy_after_agent_hook, run_pending_session_start_hooks, run_turn_stop_hooks, maybe_record_token_budget_remaining_context, auto_compact_token_status, build_skills_and_plugins, run_auto_compact, run_hooks_and_record_inputs, run_pre_sampling_compact, run_sampling_request (+5 more));被 1 处调用(run);外部调用 12 个(clone, new, child_token, new, error!, info!, Error, Warning, from_ref, new (+2 more))。
turn_diff_display_roots414–430 ↗
async fn turn_diff_display_roots(turn_context: &TurnContext) -> Vec<(String, PathBuf)>
作用:找出本回合里哪些目录应该用来展示代码改动差异。这样用户看到的 diff 会以合适的项目根目录为基准,而不是一堆不好懂的绝对路径。
数据流:输入是回合上下文里的各个运行环境。它读取每个环境的当前目录,尽量向上找到 Git 仓库根目录;找不到就用当前目录。输出是一组“环境 ID → 展示根目录”的列表。
调用关系:run_turn 在创建 TurnDiffTracker 前调用它。它依赖 get_git_repo_root_with_fs 去识别 Git 根目录,结果交给差异追踪器用于后续展示文件变化。
调用图:被 1 处调用(run_turn);外部调用 2 个(new, get_git_repo_root_with_fs)。
run_hooks_and_record_inputs433–459 ↗
async fn run_hooks_and_record_inputs(
sess: &Arc<Session>,
turn_context: &Arc<TurnContext>,
input: &[TurnInput],
) -> bool
作用:在把用户输入写进历史前,先让钩子检查能不能放行。钩子可以理解成“门卫规则”,比如补充上下文或阻止某些输入。
数据流:输入是会话、回合上下文和一批待处理输入。它逐条调用 inspect_pending_input 检查;被拦的只记录额外上下文,没被拦的写入待处理输入队列。输出是一个布尔值,表示是否因为全部有效用户输入都被拦住而应该停止本回合。
调用关系:run_turn 在处理最初输入和运行中追加输入时都会调用它。它把检查交给 inspect_pending_input,把记录交给 record_pending_input 或 record_additional_contexts。
调用图:调用 3 个内部函数(inspect_pending_input, record_additional_contexts, record_pending_input);被 1 处调用(run_turn);外部调用 1 个(matches!)。
build_skills_and_plugins462–622 ↗
async fn build_skills_and_plugins(
sess: &Arc<Session>,
turn_context: &TurnContext,
input: &[TurnInput],
cancellation_token: &CancellationToken,
) -> Option<(Vec<ResponseItem>, HashSet
作用:根据用户明确提到的技能、插件或应用,生成要塞给模型的额外说明。这样模型知道本回合该用哪些专门能力,而不是只靠通用提示词猜。
数据流:输入是会话、回合上下文、用户输入和取消信号。它提取用户文字,查已加载插件、MCP 工具和可用连接器,识别用户提到的技能/插件/应用,必要时提示安装依赖,然后生成一批上下文消息和显式启用的连接器 ID。输出是这些注入消息和连接器集合;失败或被取消时返回空结果。
调用关系:run_turn 在真正请求模型前调用它。它内部会调用 build_extension_turn_input_items 收集扩展提供的上下文,也会调用 collect_explicit_app_ids_from_skill_items 从技能说明里反推出应用启用信息。
调用图:调用 10 个内部函数(merge_plugin_connectors_with_accessible, accessible_connectors_from_mcp_tools, with_app_enabled_state, maybe_prompt_and_install_mcp_dependencies, build_connector_slug_counts, collect_explicit_app_ids, collect_explicit_plugin_mentions, build_extension_turn_input_items, collect_explicit_app_ids_from_skill_items, apps_enabled);被 1 处调用(run_turn);外部调用 10 个(new, new, build_track_events_context, iter, build_skill_injections, collect_explicit_skill_mentions, is_guardian_reviewer_source, build_skill_name_counts, build_plugin_injections, Warning)。
build_extension_turn_input_items624–677 ↗
async fn build_extension_turn_input_items(
sess: &Arc<Session>,
turn_context: &TurnContext,
user_input: &[UserInput],
cancellation_token: &CancellationToken,
) -> Option<Vec<ResponseIt
作用:让扩展在每个回合开始前补充自己的上下文片段。比如某个扩展想告诉模型当前项目、环境或其他外部信息,就通过这里加入。
数据流:输入是会话、回合上下文、用户输入和取消信号。它整理当前环境的 ID、目录和主环境标记,交给每个扩展贡献者;每个贡献者返回的片段会被包装成模型可读的消息。输出是一组 ResponseItem,取消或失败时返回 None。
调用关系:build_skills_and_plugins 调用它,把扩展补充的内容和技能、插件注入内容合并后一起交给 run_turn 记录进历史。
调用图:被 1 处调用(build_skills_and_plugins);外部调用 2 个(new, to_vec)。
track_turn_resolved_config_analytics679–731 ↗
async fn track_turn_resolved_config_analytics(
sess: &Session,
turn_context: &TurnContext,
input: &[TurnInput],
)
作用:记录本回合最终采用的配置,用于统计和排查问题。它会告诉后台:这次用了哪个模型、权限策略、沙盒网络状态、是否首轮等。
数据流:输入是会话、回合上下文和用户输入。它从会话状态读取线程配置,统计输入图片数量,并取出本回合各种已解析配置。输出不是返回值,而是向 analytics 客户端发送一条配置事实记录,同时会消费“下一回合是否首轮”的状态。
调用关系:run_turn 在技能和输入都准备好后调用它。它不参与模型回答本身,但为后续分析一次回合为什么这样运行提供依据。
调用图:调用 2 个内部函数(network_sandbox_policy, permission_profile);被 1 处调用(run_turn);外部调用 1 个(iter)。
auto_compact_token_status746–793 ↗
async fn auto_compact_token_status(
sess: &Session,
turn_context: &TurnContext,
) -> AutoCompactTokenStatus
作用:判断当前对话上下文是不是太长,是否需要自动压缩。token 可以理解成模型读文字时用的“小字块”,超过上限模型就读不下。
数据流:输入是会话和回合上下文。它读取当前总 token 用量、配置的压缩范围和模型窗口大小,计算当前范围内用了多少、上限是多少、是否达到完整窗口限制。输出是 AutoCompactTokenStatus,里面说明是否已经触发压缩条件。
调用关系:run_pre_sampling_compact 在模型采样前用它决定是否预先压缩,run_turn 在每次模型响应后用它判断是否需要中途压缩再继续。
调用图:调用 1 个内部函数(model_context_window);被 2 处调用(run_pre_sampling_compact, run_turn);外部调用 2 个(auto_compact_window_snapshot, get_total_token_usage)。
run_pre_sampling_compact796–816 ↗
async fn run_pre_sampling_compact(
sess: &Arc<Session>,
turn_context: &Arc<TurnContext>,
client_session: &mut ModelClientSession,
) -> CodexResult<()>
作用:在发给模型之前先检查要不要压缩历史。这样可以避免请求刚发出去就因为上下文太长失败。
数据流:输入是会话、回合上下文和本回合模型连接。它先尝试按上一轮模型兼容性做压缩,再检查当前 token 状态;如果已经到上限,就启动自动压缩。输出是成功或错误。
调用关系:run_turn 一开始就调用它。它把“模型切换或兼容性变化”的判断交给 maybe_run_previous_model_inline_compact,把真正压缩交给 run_auto_compact。
调用图:调用 3 个内部函数(auto_compact_token_status, maybe_run_previous_model_inline_compact, run_auto_compact);被 1 处调用(run_turn)。
comp_hash_changed820–824 ↗
fn comp_hash_changed(previous: Option<&str>, current: Option<&str>) -> bool
作用:判断前后两轮的“压缩兼容标记”是否都存在且不同。这个标记不同,说明旧摘要可能不适合新设置,需要重新压缩。
数据流:输入是上一轮和当前轮的可选字符串标记。它只有在两个标记都存在时才比较;如果缺一个,就认为证据不足不触发。输出是是否变化的布尔值。
调用关系:maybe_run_previous_model_inline_compact 用它决定是否因为压缩兼容性变化而先用上一轮模型做一次压缩。
调用图:被 1 处调用(maybe_run_previous_model_inline_compact)。
maybe_run_previous_model_inline_compact830–897 ↗
async fn maybe_run_previous_model_inline_compact(
sess: &Arc<Session>,
turn_context: &Arc<TurnContext>,
client_session: &mut ModelClientSession,
) -> CodexResult<()>
作用:在模型或压缩规则变化时,必要的话先用上一轮模型把历史压缩好。这样从大窗口模型切到小窗口模型时,不会突然塞不下。
数据流:输入是会话、当前回合上下文和模型连接。它读取上一轮设置,比较压缩兼容标记,或比较新旧模型上下文窗口大小;如果发现需要,就构造上一轮模型的上下文并执行压缩。输出是成功或压缩错误。
调用关系:run_pre_sampling_compact 调用它。它先用 comp_hash_changed 做判断,最终把压缩动作交给 run_auto_compact。
调用图:调用 2 个内部函数(comp_hash_changed, run_auto_compact);被 1 处调用(run_pre_sampling_compact);外部调用 1 个(new)。
run_auto_compact904–960 ↗
async fn run_auto_compact(
sess: &Arc<Session>,
turn_context: &Arc<TurnContext>,
client_session: &mut ModelClientSession,
initial_context_injection: InitialContextInjection,
reason
作用:真正执行自动压缩,把很长的对话历史整理成更短但仍有用的上下文。它会根据模型提供方和功能开关选择本地压缩或远程压缩。
数据流:输入是会话、回合上下文、模型连接、初始上下文注入方式、压缩原因和阶段。它先记录压缩指标,再根据 provider 和 RemoteCompactionV2 开关选择远程 v2、远程 v1 或本地压缩任务。输出是成功或错误,压缩会改写会话里的上下文历史。
调用关系:run_turn、run_pre_sampling_compact 和 maybe_run_previous_model_inline_compact 都会在上下文过长或模型变化时调用它。它把具体工作交给 run_inline_auto_compact_task 或远程压缩函数。
调用图:调用 6 个内部函数(turn_state, run_inline_auto_compact_task, should_use_remote_compact_task, run_inline_remote_auto_compact_task, run_inline_remote_auto_compact_task, emit_compact_metric);被 3 处调用(maybe_run_previous_model_inline_compact, run_pre_sampling_compact, run_turn);外部调用 1 个(clone)。
collect_explicit_app_ids_from_skill_items962–1011 ↗
fn collect_explicit_app_ids_from_skill_items(
skill_items: &[ResponseItem],
connectors: &[connectors::AppInfo],
skill_name_counts_lower: &HashMap<String, usize>,
) -> HashSet<String>
作用:从技能注入文本里找出明确提到的应用 ID。这样用户只点名某个技能时,技能里依赖的应用也能被显式启用。
数据流:输入是技能消息、可用连接器列表和技能名计数。它抽出技能消息里的文本,解析工具提及,包括路径式应用提及和不歧义的应用短名;最后输出一组连接器 ID。
调用关系:build_skills_and_plugins 在生成技能注入后调用它。它会利用 collect_tool_mentions_from_messages 和 build_connector_slug_counts 来避免把重名技能和应用搞混。
调用图:调用 3 个内部函数(connector_mention_slug, build_connector_slug_counts, collect_tool_mentions_from_messages);被 1 处调用(build_skills_and_plugins);外部调用 4 个(new, is_empty, is_empty, iter)。
build_prompt1014–1031 ↗
fn build_prompt(
input: Vec<ResponseItem>,
router: &ToolRouter,
turn_context: &TurnContext,
base_instructions: BaseInstructions,
) -> Prompt
作用:把要发给模型的内容组装成 Prompt,也就是模型请求的“包裹”。里面包括历史输入、可见工具、基础指令、人格和输出格式要求。
数据流:输入是 ResponseItem 列表、工具路由器、回合上下文和基础指令。它读取工具对模型可见的规格,并结合模型是否支持并行工具调用、是否需要严格 JSON 输出等设置。输出是 Prompt。
调用关系:run_sampling_request 在每次真正请求模型前调用它;启动预热和从会话构造提示词的流程也会用它,保证不同入口组装出的模型请求格式一致。
调用图:调用 1 个内部函数(model_visible_specs);被 3 处调用(build_prompt_input_from_session, run_sampling_request, schedule_startup_prewarm_inner);外部调用 1 个(is_guardian_reviewer_source)。
run_sampling_request1043–1137 ↗
async fn run_sampling_request(
sess: Arc<Session>,
turn_context: Arc<TurnContext>,
turn_store: Arc<codex_extension_api::ExtensionData>,
turn_diff_tracker: SharedTurnDiffTracker,
cl
作用:负责一次或多次向模型发请求,并处理可重试错误。它像一个“带重试的请求外壳”,真正读流和处理事件交给下一层。
数据流:输入是会话、回合上下文、扩展数据、差异追踪器、模型连接、请求元数据、提示输入和取消信号。它先构建本回合可用工具和基础指令,再组装 Prompt,调用 try_run_sampling_request;如果遇到可重试错误,会按上限重试并复用同一个模型连接。输出是采样结果和最初用于请求的输入。
调用关系:run_turn 在主循环里调用它。它向下调用 built_tools、build_prompt 和 try_run_sampling_request,错误重试交给 handle_retryable_response_stream_error。
调用图:调用 5 个内部函数(handle_retryable_response_stream_error, build_prompt, built_tools, try_run_sampling_request, new);被 1 处调用(run_turn);外部调用 3 个(clone, child_token, UsageLimitReached)。
built_tools1147–1246 ↗
async fn built_tools(
sess: &Session,
turn_context: &TurnContext,
cancellation_token: &CancellationToken,
) -> CodexResult<Arc<ToolRouter>>
作用:为当前回合准备模型能看到、能调用的工具清单。工具包括 MCP 工具、应用连接器、插件工具、动态工具和扩展工具。
数据流:输入是会话、回合上下文和取消信号。它读取 MCP 工具、插件配置、认证信息和应用开关,计算哪些工具直接暴露、哪些延后暴露、哪些可被发现推荐。输出是一个 ToolRouter,也就是模型工具调用时的“分发台”。
调用关系:run_sampling_request 会在请求模型前调用它;远程压缩、启动预热和从会话构造提示词的流程也会调用它。它内部会用 build_mcp_tool_exposure、extension_tool_executors 和 ToolRouter::from_turn_context 拼出最终路由器。
调用图:调用 9 个内部函数(merge_plugin_connectors_with_accessible, list_tool_suggest_discoverable_tools_with_auth, with_app_enabled_state, build_mcp_tool_exposure, apps_enabled, from_turn_context, extension_tool_executors, search_tool_enabled, tool_suggest_enabled);被 5 处调用(run_remote_compact_task_inner_impl, run_remote_compact_task_inner_impl, build_prompt_input_from_session, run_sampling_request, schedule_startup_prewarm_inner);外部调用 3 个(new, trace_span!, warn!)。
PlanModeStreamState::new1279–1286 ↗
fn new(turn_id: &str) -> Self
作用:创建计划模式下的临时流式状态。计划模式会把“计划”与普通助手回复分开显示,所以需要额外记账。
数据流:输入是 turn_id。它创建几个空表,用来暂存尚未展示的助手消息、已开始的消息、开头空白,以及一个计划项状态。输出是新的 PlanModeStreamState。
调用关系:计划模式的流处理开始时会用它准备状态;测试也会覆盖它的行为。它内部创建 ProposedPlanItemState,后续由 handle_plan_segments 等函数逐步更新。
调用图:调用 1 个内部函数(new);被 1 处调用(plan_mode_uses_contributed_turn_item_for_last_agent_message);外部调用 2 个(new, new)。
AssistantMessageStreamParsers::new1298–1303 ↗
fn new(plan_mode: bool) -> Self
作用:创建助手文本流解析器集合。模型文本是一小段一小段来的,这里负责按消息项分别解析,尤其要识别计划模式里的计划标签。
数据流:输入是是否处于计划模式。它初始化一个空的“item_id 到解析器”表,并记录计划模式开关。输出是 AssistantMessageStreamParsers。
调用关系:try_run_sampling_request 在接收模型流时创建它;相关测试也会检查它能正确处理预置文本和增量文本。
调用图:被 4 处调用(assistant_message_stream_parsers_can_be_seeded_from_output_item_added_text, assistant_message_stream_parsers_seed_buffered_prefix_stays_out_of_finish_tail, assistant_message_stream_parsers_seed_plan_parser_across_added_and_delta_boundaries, try_run_sampling_request);外部调用 1 个(new)。
AssistantMessageStreamParsers::parser_mut1305–1310 ↗
fn parser_mut(&mut self, item_id: &str) -> &mut AssistantTextStreamParser
作用:取出某个消息项对应的文本解析器;如果还没有,就现场创建一个。这样每条并行出现的助手消息都能独立解析。
数据流:输入是可变的解析器集合和 item_id。它在内部表里查找对应解析器,没有就按当前计划模式新建。输出是这个解析器的可变引用。
调用关系:seed_item_text 和 parse_delta 都通过它拿到正确的解析器,然后把文本片段喂进去。
调用图:被 2 处调用(parse_delta, seed_item_text)。
AssistantMessageStreamParsers::seed_item_text1312–1317 ↗
fn seed_item_text(&mut self, item_id: &str, text: &str) -> ParsedAssistantTextDelta
作用:在模型刚声明一个输出项时,把其中已经带着的初始文本先喂给解析器。这样不会漏掉 OutputItemAdded 事件里自带的文字。
数据流:输入是 item_id 和初始文本。文本为空就返回空解析结果;否则拿到对应解析器并推入文本。输出是解析出的可见文本、计划片段和引用信息等。
调用关系:try_run_sampling_request 在 OutputItemAdded 里可能调用它,随后把结果交给 emit_streamed_assistant_text_delta 发送给客户端。
调用图:调用 1 个内部函数(parser_mut);外部调用 1 个(default)。
AssistantMessageStreamParsers::parse_delta1319–1321 ↗
fn parse_delta(&mut self, item_id: &str, delta: &str) -> ParsedAssistantTextDelta
作用:处理模型后续流出来的一小段助手文本。它把碎片喂进对应解析器,得到可以安全展示或分流的内容。
数据流:输入是 item_id 和本次增量文本。它找到对应解析器并追加文本。输出是本次解析出的 ParsedAssistantTextDelta。
调用关系:try_run_sampling_request 在收到 OutputTextDelta 时调用它,然后由 emit_streamed_assistant_text_delta 决定是发普通文字还是计划片段。
调用图:调用 1 个内部函数(parser_mut)。
AssistantMessageStreamParsers::finish_item1323–1328 ↗
fn finish_item(&mut self, item_id: &str) -> ParsedAssistantTextDelta
作用:某个助手消息项结束时,清空它还缓存在解析器里的尾巴。这样最后一点因为等待判断而没发出的文字不会丢。
数据流:输入是 item_id。它从表里移除对应解析器并调用 finish;如果根本没有解析器,就返回空结果。输出是最后解析出的文本片段。
调用关系:flush_assistant_text_segments_for_item 调用它,在单个输出项结束时把剩余内容交给 emit_streamed_assistant_text_delta。
调用图:被 1 处调用(flush_assistant_text_segments_for_item);外部调用 1 个(default)。
AssistantMessageStreamParsers::drain_finished1330–1336 ↗
fn drain_finished(&mut self) -> Vec<(String, ParsedAssistantTextDelta)>
作用:在整个模型响应结束时,把所有还没结束的文本解析器都收尾。它是防止残留缓冲文本丢失的兜底。
数据流:输入是解析器集合本身。它取走内部整张表,逐个 finish。输出是 item_id 和对应最终解析结果的列表,同时内部表被清空。
调用关系:flush_assistant_text_segments_all 调用它,在响应完成或异常退出后的清理阶段统一冲刷剩余文本。
调用图:被 1 处调用(flush_assistant_text_segments_all);外部调用 1 个(take)。
ProposedPlanItemState::new1340–1346 ↗
ProposedPlanItemState::start1348–1358 ↗
async fn start(&mut self, sess: &Session, turn_context: &TurnContext)
作用:通知客户端:计划项开始出现了。它保证同一个计划项不会重复开始,也不会在完成后重新开始。
数据流:输入是计划状态、会话和回合上下文。如果还没开始且没完成,就标记 started,并发出一个空文本的 Plan TurnItem started 事件。输出没有返回值,但会向客户端发送开始事件。
调用关系:handle_plan_segments 在看到计划开始或计划内容时会调用它;maybe_complete_plan_item_from_message 在最终消息里发现计划但之前没开始时也会调用。
调用图:外部调用 3 个(new, Plan, emit_turn_item_started)。
ProposedPlanItemState::push_delta1360–1375 ↗
async fn push_delta(&mut self, sess: &Session, turn_context: &TurnContext, delta: &str)
作用:把计划内容的一小段增量发给客户端。这样用户能像看普通回复一样实时看到计划生成。
数据流:输入是计划状态、会话、回合上下文和 delta 文本。若计划已完成或 delta 为空就不做事;否则构造 PlanDeltaEvent 并发送。输出没有返回值,效果是客户端收到计划增量。
调用关系:handle_plan_segments 在解析到 ProposedPlanDelta 时调用它。它只负责发计划增量,不负责最终完成事件。
调用图:外部调用 2 个(send_event, PlanDelta)。
ProposedPlanItemState::complete_with_text1377–1392 ↗
async fn complete_with_text(
&mut self,
sess: &Session,
turn_context: &TurnContext,
text: String,
)
作用:把计划项标记为完成,并发送最终完整计划。最终文本来自完成后的助手消息,比单个流片段更可靠。
数据流:输入是计划状态、会话、回合上下文和完整计划文本。如果已经完成或还没开始,就跳过;否则标记 completed,并发送 Plan TurnItem completed 事件。输出没有返回值。
调用关系:maybe_complete_plan_item_from_message 解析最终助手消息得到计划文本后调用它,完成计划模式里计划项的生命周期。
调用图:外部调用 2 个(Plan, emit_turn_item_completed)。
maybe_emit_pending_agent_message_start1398–1413 ↗
async fn maybe_emit_pending_agent_message_start(
sess: &Session,
turn_context: &TurnContext,
state: &mut PlanModeStreamState,
item_id: &str,
)
作用:计划模式下,只有确认真的有普通助手文字时,才把助手消息开始事件发出去。这样纯计划输出不会在界面上留下一个空助手消息。
数据流:输入是会话、回合上下文、计划模式状态和 item_id。它检查该消息是否已开始;如果没开始且在 pending 表里,就移出并发送 started 事件,同时记录为已开始。输出没有返回值。
调用关系:handle_plan_segments 在普通文字出现时调用它,emit_agent_message_in_plan_mode 在最终发助手消息时也会调用它。
调用图:被 2 处调用(emit_agent_message_in_plan_mode, handle_plan_segments);外部调用 1 个(emit_turn_item_started)。
agent_message_text1416–1423 ↗
fn agent_message_text(item: &codex_protocol::items::AgentMessageItem) -> String
作用:把一个助手消息里的所有文本块拼成完整字符串。现在助手消息内容只有文本,所以这是取最终文本的简单工具。
数据流:输入是 AgentMessageItem。它遍历 content,把每个 Text 条目的 text 依次拼接。输出是完整文本字符串。
调用关系:emit_agent_message_in_plan_mode 用它判断消息是否为空并取文本;realtime_text_for_event 用它从完成事件里提取可转发到实时通道的文字。
调用图:被 2 处调用(emit_agent_message_in_plan_mode, realtime_text_for_event)。
realtime_text_for_event1425–1507 ↗
fn realtime_text_for_event(msg: &EventMsg) -> Option<String>
作用:从事件里挑出适合转发到实时语音/实时界面的文字。不是所有事件都有可读文本,工具事件、警告、diff 等都会被忽略。
数据流:输入是一个 EventMsg。它只对 AgentMessage 和完成的 AgentMessage item 提取文本;其他大量事件类型都返回 None。输出是可选字符串。
调用关系:maybe_mirror_event_text_to_realtime 会调用它,决定某个事件是否需要同步到实时通道;它在完成项场景下会借助 agent_message_text。
调用图:调用 1 个内部函数(agent_message_text);被 1 处调用(maybe_mirror_event_text_to_realtime)。
handle_plan_segments1512–1573 ↗
async fn handle_plan_segments(
sess: &Session,
turn_context: &TurnContext,
state: &mut PlanModeStreamState,
item_id: &str,
segments: Vec<ProposedPlanSegment>,
)
作用:把解析出来的文本片段分成普通助手文字和计划内容,并分别发不同事件。它是计划模式显示不混乱的关键。
数据流:输入是会话、回合上下文、计划模式状态、item_id 和一串 ProposedPlanSegment。普通文本会触发助手文本增量;计划开始会启动计划项;计划内容会发送 PlanDelta;计划结束本身不额外处理。过程中会缓存开头空白,避免空白触发空助手消息。
调用关系:emit_streamed_assistant_text_delta 在计划模式下发现 plan_segments 时调用它。它需要 maybe_emit_pending_agent_message_start 来延迟开启普通助手消息。
调用图:调用 1 个内部函数(maybe_emit_pending_agent_message_start);被 1 处调用(emit_streamed_assistant_text_delta);外部调用 3 个(send_event, format!, AgentMessageContentDelta)。
emit_streamed_assistant_text_delta1575–1607 ↗
async fn emit_streamed_assistant_text_delta(
sess: &Session,
turn_context: &TurnContext,
plan_mode_state: Option<&mut PlanModeStreamState>,
item_id: &str,
parsed: ParsedAssistantTe
作用:把解析后的助手文本真正发给客户端。普通模式直接发文字;计划模式则先拆出计划片段再发。
数据流:输入是会话、回合上下文、可选计划模式状态、item_id 和解析结果。空结果直接跳过;引用信息目前只剥离不对外发;计划模式下交给 handle_plan_segments;普通模式下发送 AgentMessageContentDelta。输出没有返回值,效果是客户端收到流式文本事件。
调用关系:try_run_sampling_request 在收到新文本或预置文本时调用它;flush_assistant_text_segments_for_item 和 flush_assistant_text_segments_all 也会用它发送收尾缓冲。
调用图:调用 1 个内部函数(handle_plan_segments);被 3 处调用(flush_assistant_text_segments_all, flush_assistant_text_segments_for_item, try_run_sampling_request);外部调用 3 个(is_empty, send_event, AgentMessageContentDelta)。
flush_assistant_text_segments_for_item1610–1619 ↗
async fn flush_assistant_text_segments_for_item(
sess: &Session,
turn_context: &TurnContext,
plan_mode_state: Option<&mut PlanModeStreamState>,
parsers: &mut AssistantMessageStreamPars
作用:某个助手消息项结束时,把该项解析器里没发完的文本冲刷出去。它防止最后一小段文字卡在解析器里。
数据流:输入是会话、回合上下文、可选计划状态、解析器集合和 item_id。它先 finish_item 拿到尾部解析结果,再交给 emit_streamed_assistant_text_delta 发送。输出没有返回值。
调用关系:try_run_sampling_request 在 OutputItemDone 且之前正在向客户端流式展示助手消息时调用它。
调用图:调用 2 个内部函数(finish_item, emit_streamed_assistant_text_delta);被 1 处调用(try_run_sampling_request)。
flush_assistant_text_segments_all1622–1638 ↗
async fn flush_assistant_text_segments_all(
sess: &Session,
turn_context: &TurnContext,
mut plan_mode_state: Option<&mut PlanModeStreamState>,
parsers: &mut AssistantMessageStreamParse
作用:整个模型响应结束或退出前,把所有助手文本解析器里的残留内容都发出去。它是全局收尾保险。
数据流:输入是会话、回合上下文、可选计划状态和解析器集合。它 drain_finished 取出所有剩余解析结果,再逐个调用 emit_streamed_assistant_text_delta。输出没有返回值,内部解析器会被清空。
调用关系:try_run_sampling_request 在响应 Completed 后和循环退出后的清理阶段都会调用它。
调用图:调用 2 个内部函数(drain_finished, emit_streamed_assistant_text_delta);被 1 处调用(try_run_sampling_request)。
maybe_complete_plan_item_from_message1641–1667 ↗
async fn maybe_complete_plan_item_from_message(
sess: &Session,
turn_context: &TurnContext,
state: &mut PlanModeStreamState,
item: &ResponseItem,
)
作用:从最终助手消息里提取完整计划,并把计划项标记完成。流式片段适合实时显示,最终消息适合拿来做完整结果。
数据流:输入是会话、回合上下文、计划模式状态和一个 ResponseItem。它只处理 assistant message,拼出其中 OutputText,尝试提取 proposed plan 文本并去掉引用标记;如果找到,就确保计划项开始并完成。输出没有返回值。
调用关系:handle_assistant_item_done_in_plan_mode 在助手消息完成时调用它,然后再处理最终的普通助手消息记录。
调用图:被 1 处调用(handle_assistant_item_done_in_plan_mode);外部调用 3 个(new, extract_proposed_plan_text, strip_citations)。
emit_agent_message_in_plan_mode1670–1710 ↗
async fn emit_agent_message_in_plan_mode(
sess: &Session,
turn_context: &TurnContext,
agent_message: codex_protocol::items::AgentMessageItem,
state: &mut PlanModeStreamState,
)
作用:计划模式下发送完成的助手消息,同时尊重“延迟开始”的规则。空白消息会被丢弃,避免界面出现没内容的气泡。
数据流:输入是会话、回合上下文、助手消息和计划模式状态。它拼出消息文本,若全是空白就清掉 pending/started 状态;否则确保 started 事件已发,再发送 completed 事件,并移除 started 记录。输出没有返回值。
调用关系:emit_turn_item_in_plan_mode 在遇到 AgentMessage 时调用它。它会用 agent_message_text 判断内容,并可能调用 maybe_emit_pending_agent_message_start。
调用图:调用 2 个内部函数(agent_message_text, maybe_emit_pending_agent_message_start);被 1 处调用(emit_turn_item_in_plan_mode);外部调用 3 个(AgentMessage, emit_turn_item_completed, emit_turn_item_started)。
emit_turn_item_in_plan_mode1713–1731 ↗
async fn emit_turn_item_in_plan_mode(
sess: &Session,
turn_context: &TurnContext,
turn_item: TurnItem,
previously_active_item: Option<&TurnItem>,
state: &mut PlanModeStreamState,
)
作用:计划模式下发送一个完成的 TurnItem。助手消息要特殊处理,其他项目则按普通 started/completed 顺序发送。
数据流:输入是会话、回合上下文、TurnItem、之前活跃项和计划模式状态。若是 AgentMessage,就交给 emit_agent_message_in_plan_mode;否则如果之前没有开始过,就先发 started,再发 completed。输出没有返回值。
调用关系:handle_assistant_item_done_in_plan_mode 在把模型响应项转成最终 TurnItem 后调用它。
调用图:调用 1 个内部函数(emit_agent_message_in_plan_mode);被 1 处调用(handle_assistant_item_done_in_plan_mode);外部调用 2 个(emit_turn_item_completed, emit_turn_item_started)。
handle_assistant_item_done_in_plan_mode1734–1785 ↗
async fn handle_assistant_item_done_in_plan_mode(
sess: &Session,
turn_context: &TurnContext,
turn_store: &codex_extension_api::ExtensionData,
item: &ResponseItem,
state: &mut Plan
作用:在计划模式下处理一个完成的助手响应项。它同时完成计划项、生成最终助手消息、记录历史,并更新最后一条助手回复。
数据流:输入是会话、回合上下文、扩展数据、响应项、计划状态、之前活跃项和 last_agent_message 引用。它只处理 assistant message;先尝试从消息里完成计划,再调用 finalize_non_tool_response_item 生成 TurnItem 和事实,发送完成事件,记录历史,必要时更新 last_agent_message。输出是布尔值,表示这个项是否已被它处理。
调用关系:try_run_sampling_request 在 OutputItemDone 时优先调用它;它向下使用 maybe_complete_plan_item_from_message、emit_turn_item_in_plan_mode 和 record_completed_response_item_with_finalized_facts。
调用图:调用 4 个内部函数(emit_turn_item_in_plan_mode, maybe_complete_plan_item_from_message, finalize_non_tool_response_item, record_completed_response_item_with_finalized_facts);被 1 处调用(try_run_sampling_request);外部调用 1 个(Run)。
drain_in_flight1788–1812 ↗
async fn drain_in_flight(
in_flight: &mut FuturesOrdered<BoxFuture<'static, CodexResult<ResponseInputItem>>>,
sess: Arc<Session>,
turn_context: Arc<TurnContext>,
) -> CodexResult<()>
作用:等待正在执行的工具调用全部结束,并把工具结果写回对话历史。否则模型下一步可能看不到工具返回了什么。
数据流:输入是进行中的工具任务队列、会话和回合上下文。它逐个等待任务完成;成功时把 ResponseInputItem 转成 ResponseItem 并记录进历史,同时标记外部上下文是否污染内存模式;失败时记录严重错误。输出是成功或错误。
调用关系:try_run_sampling_request 在模型流结束后调用它,确保所有 in-flight 工具结果都落库,再发 token 统计和回合 diff。
调用图:调用 2 个内部函数(mark_thread_memory_mode_polluted_if_external_context, error_or_panic);被 1 处调用(try_run_sampling_request);外部调用 3 个(next, format!, from_ref)。
try_run_sampling_request1822–2302 ↗
async fn try_run_sampling_request(
tool_runtime: ToolCallRuntime,
sess: Arc<Session>,
turn_context: Arc<TurnContext>,
turn_store: Arc<codex_extension_api::ExtensionData>,
client_se
作用:真正向模型发出一次流式请求,并逐个处理模型返回的事件。它是“读模型流、发前端事件、执行工具、记录历史”的核心循环。
数据流:输入是工具运行时、会话、回合上下文、扩展数据、模型连接、请求元数据、差异追踪器、Prompt 和取消信号。它打开模型 stream,循环读取 Created、OutputItemAdded、OutputTextDelta、ToolCallInputDelta、Completed 等事件;文本会流给客户端,工具调用会排入 in_flight,token 和 rate limit 会记录,完成后等待工具、发送 token 统计和 diff。输出是 SamplingRequestResult,说明是否还要继续追问模型以及最后助手消息是什么。
调用关系:run_sampling_request 调用它,并在外层负责重试。它内部调用 drain_in_flight、handle_output_item_done、handle_non_tool_response_item、emit_streamed_assistant_text_delta、flush_assistant_text_segments_all、handle_assistant_item_done_in_plan_mode 等函数,把模型事件一步步变成会话状态和客户端事件。
调用图:调用 14 个内部函数(stream, new, drain_in_flight, emit_streamed_assistant_text_delta, flush_assistant_text_segments_all, flush_assistant_text_segments_for_item, handle_assistant_item_done_in_plan_mode, handle_non_tool_response_item, handle_output_item_done, raw_assistant_output_text_from_item (+4 more));被 1 处调用(run_sampling_request);外部调用 16 个(clone, child_token, is_cancelled, new, lock, clone, feedback_tags!, matches!, Stream, AgentMessageContentDelta (+6 more))。
get_last_assistant_message_from_turn2304–2311 ↗
fn get_last_assistant_message_from_turn(responses: &[ResponseItem]) -> Option<String>
作用:从一批响应项里倒着找最后一条助手消息。压缩等流程需要知道模型最后说了什么时会用它。
数据流:输入是 ResponseItem 切片。它从后往前检查每个项,调用 last_assistant_message_from_item 提取非计划模式下的助手文本;找到就返回,找不到返回 None。
调用关系:run_compact_task_inner_impl 会调用它,用来从某次回合的响应里取最后助手消息作为后续处理依据。
调用图:调用 1 个内部函数(last_assistant_message_from_item);被 1 处调用(run_compact_task_inner_impl);外部调用 1 个(iter)。