Codex 系统手册

轮次上下文、历史与实时提示词组装

stage-12.412 个文件

这一阶段发生在模型每次开口前,像给演员递台本。turn_context 先装好当前目录、权限、工具等“随身资料包”;history 整理旧聊天,normalize 保证工具调用和图片等内容不乱;additional_context 和 updates 只补新变化,省位置;token_budget 提醒上下文快满了。实时部分会选好系统提示词,再补一张开场小抄。网页搜索也拿一小段近况。prompt_debug 则让开发者检查模型到底看见了什么。

本阶段的文件12

回合上下文快照

这些文件定义不可变的逐回合上下文,并根据变化的会话状态和 token 用量推导增量上下文片段。

core/src/session/turn_context.rs源码 ↗
orchestration每次新回合创建时活跃,也会在工具执行、上下文记录和权限检查时被反复读取

可以把一次对话回合想成一次外卖配送:骑手出发前要拿到地址、权限、路线、注意事项和能用的工具。这个文件做的就是这件事。它先把会话里的长期设置拷贝成“本回合专用设置”,再根据当前选择的运行环境确定工作目录,查模型资料,决定工具模式、推理力度、网络代理、文件沙盒权限和插件技能。TurnEnvironment 表示一个本回合可用的运行环境,比如在哪个目录、用哪个 shell。TurnSkillsContext 保存本回合加载到的技能,并记录哪些隐式技能已经被用过。TurnContext 则是最终的大包,后面的工具执行、模型请求、上下文记录、权限判断都会读它。这个文件重要在于:如果这里拼错了,模型可能在错误目录执行命令、拿错权限、用错模型能力,甚至把安全边界搞乱。

函数细节35
TurnSkillsContext::new37–42 ↗
fn new(outcome: Arc<SkillLoadOutcome>) -> Self

作用:创建本回合的技能上下文。它把已经加载好的技能结果收起来,并准备一个空名单,用来记住哪些“自动触发”的技能已经见过,避免重复处理。

数据流:进去的是技能加载结果 → 函数把它放进可共享的容器里,再新建一个受互斥锁保护的集合,互斥锁就是一把锁,防止多个任务同时改名单 → 出来的是一个新的 TurnSkillsContext。

调用关系:它通常在 Session::make_turn_context 组装完整回合上下文时被调用;一些测试和审查线程也会直接用它来搭建技能相关场景。

调用图:被 4 处调用(spawn_review_thread, build_initial_context_emits_thread_start_skill_warning_on_repeated_builds, build_initial_context_trims_skill_metadata_from_context_window_budget, make_turn_context);外部调用 3 个(new, new, new)。

TurnEnvironment::new57–70 ↗
fn new(
        environment_id: String,
        environment: Arc<Environment>,
        cwd: PathUri,
        shell: Option<shell::Shell>,
    ) -> Self

作用:创建一个本回合可用的运行环境。它记录环境编号、真实环境对象、当前目录和可选的 shell。

数据流:进去的是环境 ID、环境对象、工作目录和 shell → 函数把这些值保存起来,并把 shell 快照先设成“还没有” → 出来的是一个 TurnEnvironment。

调用关系:环境选择逻辑和测试会用它生成环境对象;之后 TurnContext 会把多个 TurnEnvironment 放进本回合环境快照里。

调用图:被 7 处调用(resolve_selection, set_primary_environment_cwd, primary_environment_uses_first_turn_environment, request_permissions_tool_resolves_relative_paths_against_selected_environment, replace_primary_environment_cwd, test_turn_environment, test_turn_environment);外部调用 1 个(ready)。

TurnEnvironment::shell_snapshot72–80 ↗
fn shell_snapshot(&self, cwd: &AbsolutePathBuf) -> Option<AbsolutePathBuf>

作用:在当前目录匹配时,尝试拿到这个环境的 shell 快照文件路径。shell 快照可以理解为“终端当时状态的存档”。

数据流:进去的是一个绝对目录路径 → 函数先看这个目录是否和环境记录的目录一致,不一致就返回空 → 如果一致,再查看异步快照任务是否已经完成,完成且有文件就返回文件路径。

调用关系:它依赖 TurnEnvironment 创建时保存的 cwd 和 shell_snapshot;调用方可以用它判断某个目录下有没有可复用的终端状态。

调用图:调用 1 个内部函数(from_abs_path);外部调用 1 个(peek)。

TurnEnvironment::cwd82–84 ↗
fn cwd(&self) -> &PathUri

作用:返回这个运行环境的工作目录。外部代码用它来决定相对路径应该从哪里开始算。

数据流:没有额外输入 → 函数直接读取 TurnEnvironment 里保存的 cwd → 返回这个目录的引用,不改任何东西。

调用关系:构建回合上下文时会读取它,尤其是在选择主环境后,用它来决定本回合的实际工作目录。

调用图:被 1 处调用(build)。

TurnEnvironment::selection86–91 ↗
fn selection(&self) -> TurnEnvironmentSelection

作用:把当前环境转换成一份“选择结果”。这份结果只包含环境 ID 和工作目录,方便传给协议层或保存状态。

数据流:没有额外输入 → 函数复制环境 ID 和 cwd → 输出 TurnEnvironmentSelection。

调用关系:它位于环境对象和外部协议之间,把内部保存的完整环境,整理成外部只需要知道的选择信息。

调用图:外部调用 1 个(clone)。

TurnEnvironment::fmt95–102 ↗
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result

作用:定义调试打印 TurnEnvironment 时显示哪些字段。这样开发者看日志时能看懂当前环境的大概情况。

数据流:进去的是格式化器 → 函数把 environment_id、environment、cwd、shell 写进调试结构 → 输出格式化结果,不改变环境本身。

调用关系:它由 Rust 的调试打印机制自动调用,主要服务于日志、排错和测试。

调用图:外部调用 1 个(debug_struct)。

TurnContext::permission_profile167–169 ↗
fn permission_profile(&self) -> PermissionProfile

作用:取出本回合的权限配置副本。权限配置说明模型和工具能不能读写文件、能不能联网、是否要用户批准。

数据流:没有额外输入 → 函数复制 TurnContext 里的 permission_profile → 返回副本,调用方可以安全读取而不影响原对象。

调用关系:很多地方会问它要权限信息,比如补丁应用、MCP 工具请求、安装依赖、分析埋点和生成 TurnContextItem。

调用图:被 10 处调用(apply_patch, build_permissions_update_item, should_install_mcp_dependencies, augment_mcp_tool_request_meta_with_sandbox_state, install_host_owned_codex_apps_manager, refresh_mcp_servers_inner, track_turn_resolved_config_analytics, to_turn_context_item, apply_spawn_agent_runtime_overrides, test_exec_request);外部调用 1 个(clone)。

TurnContext::file_system_sandbox_policy171–173 ↗
fn file_system_sandbox_policy(&self) -> FileSystemSandboxPolicy

作用:取得本回合的文件系统沙盒策略。沙盒策略就是一圈安全围栏,规定工具能读写哪些文件。

数据流:没有额外输入 → 函数从权限配置里推导文件系统限制 → 返回 FileSystemSandboxPolicy。

调用关系:它被补丁权限计算、运行命令和非旧版沙盒策略生成使用,是执行文件操作前的重要依据。

调用图:调用 1 个内部函数(file_system_sandbox_policy);被 3 处调用(non_legacy_file_system_sandbox_policy, effective_patch_permissions, run)。

TurnContext::network_sandbox_policy175–177 ↗
fn network_sandbox_policy(&self) -> NetworkSandboxPolicy

作用:取得本回合的网络沙盒策略。它说明工具或模型相关流程能不能访问网络,以及访问范围。

数据流:没有额外输入 → 函数从权限配置里推导网络限制 → 返回 NetworkSandboxPolicy。

调用关系:运行工具和记录配置分析时会调用它,用来保证联网行为符合当前权限。

调用图:调用 1 个内部函数(network_sandbox_policy);被 2 处调用(track_turn_resolved_config_analytics, run)。

TurnContext::sandbox_policy179–185 ↗
fn sandbox_policy(&self) -> SandboxPolicy

作用:生成兼容旧系统的综合沙盒策略。因为新旧权限表达方式并存,这个函数把当前权限翻译成旧接口也能看懂的格式。

数据流:进去的是 TurnContext 自己保存的权限和旧工作目录 → 函数调用兼容转换工具 → 输出 SandboxPolicy。

调用关系:MCP 请求元数据、文件系统规则补充、TurnContextItem 输出和新旧策略比较都会用它。

调用图:被 4 处调用(augment_mcp_tool_request_meta_with_sandbox_state, file_system_policy_with_unreadable_glob, non_legacy_file_system_sandbox_policy, to_turn_context_item);外部调用 1 个(compatibility_sandbox_policy_for_permission_profile)。

TurnContext::effective_reasoning_effort187–195 ↗
fn effective_reasoning_effort(&self) -> Option<ReasoningEffortConfig>

作用:算出本回合实际使用的推理力度。推理力度可以理解为模型“想得多深”,但只有支持这个能力的模型才会设置。

数据流:没有额外输入 → 函数先看模型是否支持推理摘要能力;支持时优先用用户配置,没有配置就用模型默认值;不支持就返回空 → 输出可选的推理力度。

调用关系:MCP 请求、回合元数据和追踪日志会用它,确保对外记录的是实际生效的推理设置。

调用图:被 3 处调用(build_mcp_tool_call_request_meta, mcp_turn_metadata_context, effective_reasoning_effort_for_tracing)。

TurnContext::effective_reasoning_effort_for_tracing197–201 ↗
fn effective_reasoning_effort_for_tracing(&self) -> String

作用:把实际推理力度变成适合写日志的文字。如果没有特殊设置,就写成 default。

数据流:没有额外输入 → 函数先调用 TurnContext::effective_reasoning_effort → 有值就转成字符串,没有就返回 default → 输出日志用文本。

调用关系:它是 TurnContext::effective_reasoning_effort 的日志包装版,主要服务于 tracing,也就是运行追踪记录。

调用图:调用 1 个内部函数(effective_reasoning_effort)。

TurnContext::model_context_window203–210 ↗
fn model_context_window(&self) -> Option<i64>

作用:计算本回合模型能使用的有效上下文长度。上下文长度就是模型一次能看进多少内容。

数据流:没有额外输入 → 函数读取模型原始上下文窗口,再乘以配置里的有效百分比 → 输出一个可选数字,表示实际可用窗口大小。

调用关系:裁剪历史、构建初始上下文、统计 token、自动压缩等流程都会用它,防止塞给模型的内容超过容量。

调用图:调用 1 个内部函数(resolved_context_window);被 7 处调用(trim_function_call_history_to_fit_context_window, build_initial_context, recompute_token_usage, record_token_usage_info, set_total_tokens_full, maybe_record_token_budget_remaining_context, auto_compact_token_status)。

TurnContext::apps_enabled212–218 ↗
fn apps_enabled(&self) -> bool

作用:判断应用类功能这回合能不能启用。它会同时考虑当前登录方式和功能开关。

数据流:没有额外输入 → 函数先看认证管理器是否使用 Codex 后端,再把这个情况交给特性开关判断 → 输出 true 或 false。

调用关系:初始上下文构建、技能插件构建和工具列表生成会调用它,用来决定是否露出应用相关能力。

调用图:被 3 处调用(build_initial_context, build_skills_and_plugins, built_tools);外部调用 1 个(apps_enabled_for_auth)。

TurnContext::tool_environment_mode220–222 ↗
fn tool_environment_mode(&self) -> ToolEnvironmentMode

作用:根据本回合有几个运行环境,判断工具应该处于哪种环境模式。一个环境和多个环境的处理方式可能不同。

数据流:没有额外输入 → 函数读取 turn_environments 的数量 → 转成 ToolEnvironmentMode 输出。

调用关系:它把环境快照里的数量信息翻译成工具层更好理解的模式值,供后续工具执行策略使用。

调用图:调用 1 个内部函数(from_count)。

TurnContext::with_model224–334 ↗
async fn with_model(
        &self,
        model: String,
        models_manager: &SharedModelsManager,
    ) -> Self

作用:基于现有 TurnContext 克隆出一个“换了模型”的新上下文。它用于临时或后续回合切换模型,同时尽量保留其他设置。

数据流:进去的是新模型名和模型管理器 → 函数复制配置,查询新模型资料,重新决定工具模式、截断策略、推理力度、协作模式、可用模型列表和遥测信息 → 输出一个新的 TurnContext,原来的不变。

调用关系:它会把模型相关活儿交给 models_manager,比如 get_model_info 和 list_models;同时复用原上下文的大部分字段,是模型切换流程里的重组器。

调用图:调用 1 个内部函数(with_updates);外部调用 18 个(clone, new, load, new, clone, get_model_info, list_models, clone, clone, clone (+8 more))。

TurnContext::resolve_path337–341 ↗
fn resolve_path(&self, path: Option<String>) -> AbsolutePathBuf

作用:把一个可能是相对路径的字符串变成绝对路径。这个函数已经标记为旧做法,因为新代码应该用当前选择环境的 cwd。

数据流:进去的是可选路径字符串 → 没给路径就返回旧 cwd,给了路径就拼到旧 cwd 后面 → 输出绝对路径。

调用关系:执行参数生成 to_exec_params 还会调用它;注释提醒后续应迁移到按选中环境解析路径。

调用图:被 1 处调用(to_exec_params)。

TurnContext::file_system_sandbox_context343–373 ↗
fn file_system_sandbox_context(
        &self,
        additional_permissions: Option<AdditionalPermissionProfile>,
        cwd: &PathUri,
    ) -> FileSystemSandboxContext

作用:生成真正给文件系统操作使用的沙盒上下文。它会把基础权限和某次工具请求的额外权限合并起来。

数据流:进去的是额外权限和当前目录 → 函数先拆出基础文件、网络权限,再套用额外放行规则,重新组装成运行时权限,并附上 cwd、Windows 沙盒等级和 Landlock 兼容设置 → 输出 FileSystemSandboxContext。

调用关系:它把 TurnContext 中的高层权限转换成文件系统模块能直接执行的格式,是工具访问文件前的重要桥梁。

调用图:调用 5 个内部函数(enforcement, from_runtime_permissions_with_enforcement, to_runtime_permissions, effective_file_system_sandbox_policy, effective_network_sandbox_policy);外部调用 2 个(use_legacy_landlock, clone)。

TurnContext::non_legacy_file_system_sandbox_policy375–389 ↗
fn non_legacy_file_system_sandbox_policy(&self) -> Option<FileSystemSandboxPolicy>

作用:只在新文件系统沙盒策略和旧沙盒投影不一样时,才返回新策略。这样可以避免对外 payload 无意义地变化。

数据流:没有额外输入 → 函数先用旧兼容策略推导出等价的文件策略,再拿当前新策略比较 → 不一样就返回新策略,一样就返回空。

调用关系:TurnContext::to_turn_context_item 会调用它,用来决定是否在上下文记录里额外带上新字段。

调用图:调用 3 个内部函数(file_system_sandbox_policy, sandbox_policy, from_legacy_sandbox_policy_for_cwd);被 1 处调用(to_turn_context_item)。

TurnContext::compact_prompt391–395 ↗
fn compact_prompt(&self) -> &str

作用:取出压缩上下文时用的提示词。没有自定义提示词时,就使用系统默认的总结提示词。

数据流:没有额外输入 → 函数查看 compact_prompt 字段 → 有自定义就返回它,没有就返回默认 SUMMARIZATION_PROMPT。

调用关系:它给上下文压缩流程提供提示词,让系统知道该如何把旧对话浓缩成摘要。

TurnContext::to_turn_context_item397–420 ↗
fn to_turn_context_item(&self) -> TurnContextItem

作用:把完整 TurnContext 精简成可记录、可发送的上下文条目。它像是给本回合做一张信息卡。

数据流:没有额外输入 → 函数读取工作目录、工作区根目录、日期、权限、沙盒、网络、模型、人格、协作模式等字段 → 输出 TurnContextItem。

调用关系:新上下文窗口启动和上下文更新记录会调用它;它内部还会调用权限、沙盒和网络相关的小函数来填卡片。

调用图:调用 6 个内部函数(value, non_legacy_file_system_sandbox_policy, permission_profile, sandbox_policy, turn_context_network_item, to_path_buf);被 2 处调用(maybe_start_new_context_window, record_context_updates_and_set_reference_context_item);外部调用 1 个(clone)。

TurnContext::turn_context_network_item422–441 ↗
fn turn_context_network_item(&self) -> Option<TurnContextNetworkItem>

作用:把配置里的网络域名规则整理成上下文条目里的网络信息。它主要记录允许和禁止访问哪些域名。

数据流:没有额外输入 → 函数从配置层需求里查 network 设置;没有网络要求就返回空;有的话提取 allowed_domains 和 denied_domains → 输出 TurnContextNetworkItem。

调用关系:只由 TurnContext::to_turn_context_item 调用,是生成回合信息卡时的网络规则填充器。

调用图:被 1 处调用(to_turn_context_item)。

local_time_context444–452 ↗
fn local_time_context() -> (String, String)

作用:取得本机当前日期和时区。这样本回合可以知道“今天是哪天”和“按哪个时区算”。

数据流:没有输入 → 函数尝试读取系统时区;成功就用本地时间生成日期和时区,失败就退回 UTC 时间和 Etc/UTC → 输出日期字符串和时区字符串。

调用关系:Session::make_turn_context 会调用它,把日期和时区写进 TurnContext,供模型和上下文记录使用。

调用图:被 1 处调用(make_turn_context);外部调用 3 个(now, now, get_timezone)。

Session::build_per_turn_config456–493 ↗
fn build_per_turn_config(
        session_configuration: &SessionConfiguration,
        cwd: AbsolutePathBuf,
    ) -> Config

作用:根据当前会话设置,生成本回合专用的 Config。它避免直接乱改原始配置,而是复制一份再补上本回合需要的值。

数据流:进去的是 SessionConfiguration 和本回合 cwd → 函数复制原始配置,写入工作目录、工作区、推理设置、服务档位、人格、权限配置和 web search 模式 → 输出 per-turn Config。

调用关系:新回合构建时会先调用它;Session::build_effective_session_config 和 Session::new_turn_context_from_configuration 都依赖它。

调用图:调用 2 个内部函数(apply_permission_profile_to_permissions, permission_profile);外部调用 1 个(warn!)。

Session::build_effective_session_config495–507 ↗
fn build_effective_session_config(
        session_configuration: &SessionConfiguration,
    ) -> Config

作用:生成当前会话“最终生效”的配置快照。它比每回合配置多补上当前模型和审批策略等会话级信息。

数据流:进去的是 SessionConfiguration → 函数先调用 Session::build_per_turn_config,再写入协作模式里的模型、审批策略和工作区根目录 → 输出 Config。

调用关系:配置贡献者需要比较前后配置变化时会用它,尤其在 Session::new_turn_with_sub_id 处理设置更新时。

调用图:调用 1 个内部函数(cwd);外部调用 1 个(build_per_turn_config)。

Session::make_turn_context510–634 ↗
fn make_turn_context(
        thread_id: ThreadId,
        session_id: SessionId,
        auth_manager: Option<Arc<AuthManager>>,
        session_telemetry: &SessionTelemetry,
        provider: ModelP

作用:把所有已经算好的材料正式装配成 TurnContext。它是本文件里最核心的“组装工位”。

数据流:进去的是线程 ID、会话 ID、认证、遥测、提供商、会话设置、模型资料、环境快照、网络代理、cwd、技能结果等 → 函数计算推理摘要、工具模式、服务档位、可用模型、shell 执行模式、元数据、日期时区和扩展数据 → 输出完整 TurnContext。

调用关系:Session::new_turn_context_from_configuration 会在准备好模型、插件、技能和环境之后调用它;它又调用 local_time_context 和 TurnSkillsContext::new 等函数完成最后装配。

调用图:调用 8 个内部函数(new, permission_profile, new, local_time_context, tool_user_shell_type, new, new, for_session);外部调用 14 个(clone, new, new, new, try_list_models, clone, create_model_provider, unified_exec_feature_mode_for_features, default, clone (+4 more))。

Session::new_turn_with_sub_id636–703 ↗
async fn new_turn_with_sub_id(
        &self,
        sub_id: String,
        updates: SessionSettingsUpdate,
    ) -> CodexResult<Arc<TurnContext>>

作用:用指定的 sub_id 创建一个新回合,并先应用用户传来的设置更新。sub_id 可以理解为本回合的编号。

数据流:进去的是 sub_id 和设置更新 → 函数锁住会话状态,尝试应用更新,必要时更新环境选择、通知配置变化、刷新网络代理;如果更新非法就发送错误事件并返回错误 → 成功时输出新的 TurnContext。

调用关系:这是外部请求发起新回合时常走的入口;它验证并保存设置后,把真正创建上下文的工作交给 Session::new_turn_from_configuration。

调用图:调用 1 个内部函数(new_turn_from_configuration);外部调用 2 个(InvalidRequest, Error)。

Session::new_turn_from_configuration705–718 ↗
async fn new_turn_from_configuration(
        &self,
        sub_id: String,
        session_configuration: SessionConfiguration,
        final_output_json_schema: Option<Option<Value>>,
    ) -> Arc<

作用:从一份确定的会话配置创建普通新回合。这里使用会解析并保存的多智能体版本模式。

数据流:进去的是 sub_id、SessionConfiguration 和可选最终 JSON schema → 函数把这些参数连同 ResolveAndStore 模式交给通用构建函数 → 输出 Arc 包裹的 TurnContext。

调用关系:Session::new_turn_with_sub_id 和 Session::new_default_turn_with_sub_id 会调用它;它只是薄薄一层,真正工作在 Session::new_turn_context_from_configuration。

调用图:调用 1 个内部函数(new_turn_context_from_configuration);被 2 处调用(new_default_turn_with_sub_id, new_turn_with_sub_id)。

Session::new_startup_prewarm_turn_from_configuration720–732 ↗
async fn new_startup_prewarm_turn_from_configuration(
        &self,
        sub_id: String,
        session_configuration: SessionConfiguration,
    ) -> Arc<TurnContext>

作用:创建启动预热用的回合上下文。预热就是程序刚启动时先准备一些东西,让后面正式使用更快。

数据流:进去的是 sub_id 和 SessionConfiguration → 函数不设置最终 JSON schema,并使用 Preview 模式交给通用构建函数 → 输出 TurnContext。

调用关系:Session::new_startup_prewarm_turn_with_sub_id 会调用它;它和普通新回合共用 Session::new_turn_context_from_configuration,只是多智能体版本处理方式不同。

调用图:调用 1 个内部函数(new_turn_context_from_configuration);被 1 处调用(new_startup_prewarm_turn_with_sub_id)。

Session::new_turn_context_from_configuration735–834 ↗
async fn new_turn_context_from_configuration(
        &self,
        sub_id: String,
        session_configuration: SessionConfiguration,
        final_output_json_schema: Option<Option<Value>>,

作用:这是创建 TurnContext 的完整流水线。它从会话配置出发,依次准备环境、配置、模型、插件、技能、网络代理,最后装配上下文。

数据流:进去的是 sub_id、会话配置、可选最终 JSON schema 和多智能体运行模式 → 函数读取当前环境快照,确定 cwd,构建 per-turn 配置,更新 MCP 权限,查询模型信息,解析多智能体版本,加载插件和技能,再调用 Session::make_turn_context → 输出共享的 TurnContext,并可能启动 Git 信息补充任务。

调用关系:普通回合和启动预热回合都会走这里;它把前置准备交给各个服务,最后把装配交给 Session::make_turn_context。

调用图:调用 1 个内部函数(permission_profile);被 2 处调用(new_startup_prewarm_turn_from_configuration, new_turn_from_configuration);外部调用 4 个(clone, new, build_per_turn_config, make_turn_context)。

Session::maybe_emit_unknown_model_warning_for_turn836–849 ↗
async fn maybe_emit_unknown_model_warning_for_turn(&self, tc: &TurnContext)

作用:如果本回合模型资料是用兜底信息凑出来的,就发出警告。这样用户知道性能或行为可能不准。

数据流:进去的是 TurnContext → 函数检查 model_info.used_fallback_model_metadata;如果为真,就拼出警告文字并发送事件;否则什么也不做 → 输出为空,只可能产生一条警告事件。

调用关系:新回合创建后或开始使用模型前可以调用它;它通过会话事件通道把问题告诉前端或调用方。

调用图:外部调用 2 个(format!, Warning)。

Session::new_default_turn851–854 ↗
async fn new_default_turn(&self) -> Arc<TurnContext>

作用:创建一个使用默认设置的新回合,并自动生成内部 sub_id。适合系统自己需要一个普通回合时使用。

数据流:没有额外输入 → 函数生成下一个内部 sub_id,再调用 Session::new_default_turn_with_sub_id → 输出 TurnContext。

调用关系:它是默认新回合的便捷入口,实际工作交给带 sub_id 的版本。

调用图:调用 1 个内部函数(new_default_turn_with_sub_id)。

Session::new_default_turn_with_sub_id856–864 ↗
async fn new_default_turn_with_sub_id(&self, sub_id: String) -> Arc<TurnContext>

作用:用指定 sub_id 和当前默认会话配置创建新回合。

数据流:进去的是 sub_id → 函数先读取默认回合配置,再调用 Session::new_turn_from_configuration,且不设置最终 JSON schema → 输出 TurnContext。

调用关系:Session::new_default_turn 会调用它;它把配置读取交给 Session::default_turn_configuration,把上下文创建交给 Session::new_turn_from_configuration。

调用图:调用 2 个内部函数(default_turn_configuration, new_turn_from_configuration);被 1 处调用(new_default_turn)。

Session::new_startup_prewarm_turn_with_sub_id866–873 ↗
async fn new_startup_prewarm_turn_with_sub_id(
        &self,
        sub_id: String,
    ) -> Arc<TurnContext>

作用:用指定 sub_id 创建启动预热回合。它不代表用户正式发起的一轮,而是提前准备运行材料。

数据流:进去的是 sub_id → 函数读取默认会话配置,再调用 Session::new_startup_prewarm_turn_from_configuration → 输出 TurnContext。

调用关系:启动预热流程会调用它;它复用默认配置读取逻辑,并把创建细节交给预热专用函数。

调用图:调用 2 个内部函数(default_turn_configuration, new_startup_prewarm_turn_from_configuration)。

Session::default_turn_configuration875–878 ↗
async fn default_turn_configuration(&self) -> SessionConfiguration

作用:读取当前会话状态里的默认回合配置。它会克隆一份,避免调用方直接改到共享状态。

数据流:没有额外输入 → 函数锁住会话状态,读取 session_configuration 并复制 → 输出 SessionConfiguration。

调用关系:默认新回合和启动预热回合都会先调用它,拿到配置后再继续创建 TurnContext。

调用图:被 2 处调用(new_default_turn_with_sub_id, new_startup_prewarm_turn_with_sub_id)。

core/src/session/token_budget.rs源码 ↗
domain_logicrequest handling

大模型一次能看的文字不是无限的,这个上限通常叫“上下文窗口”,可以理解成一张固定大小的纸。token 是模型眼里的文字小单位,越多就越占纸面。这个文件只做一件事:在每轮对话处理时,看看本轮处理前后用掉的 token 数有没有跨过 25%、50%、75% 这些关键刻度。如果没开启 TokenBudget 功能、模型没有告诉我们上下文窗口大小、数字不合理,都会直接跳过。只有当用量刚好跨过某个刻度时,它才计算还剩多少空间,并把这个信息包装成一条会话内容记录进去。这样后面的对话组织逻辑就能“知道纸还剩多少”,像油表到四分之一、半箱、四分之三时提醒驾驶员一样。

函数细节1
maybe_record_token_budget_remaining_context8–44 ↗
async fn maybe_record_token_budget_remaining_context(
    sess: &Session,
    turn_context: &TurnContext,
    tokens_before_sampling: i64,
    tokens_after_sampling: i64,
)

作用:这个函数检查本轮对话使用的 token 数有没有跨过重要比例线,如果跨过了,就把“还剩多少上下文空间”写进会话记录。它避免每次都记录,只在 25%、50%、75% 这几个有提醒意义的点记录。

数据流:输入是一段会话 sess、本轮上下文 turn_context、采样前 token 数和采样后 token 数。它先读取功能开关,确认 TokenBudget 功能已经打开;再从 turn_context 读取模型上下文窗口大小。然后它比较采样前后用量,看是否从某个阈值以下跨到了阈值以上。如果没有跨过,就什么也不改;如果跨过了,就算出剩余 token,把这个数字做成 TokenBudgetRemainingContext,再转成一条可写入会话的内容,最后异步写入会话记录。

调用关系:它由 run_turn 在处理一轮对话时调用,像是在每轮结束附近做一次“油表检查”。它自己不负责跑完整轮对话,只负责判断是否需要提醒;需要记录时,它会调用 model_context_window 拿容量上限,用 new 创建剩余预算信息,用 into 包装成会话片段,再通过 record_conversation_items 把这条片段交给会话保存。

调用图:调用 3 个内部函数(into, new, model_context_window);被 1 处调用(run_turn);外部调用 2 个(record_conversation_items, from_ref)。

core/src/state/additional_context.rs源码 ↗
domain_logiccross-cutting / context update

可以把 AdditionalContextStore 想成一个小账本,账本里按名字记录每条额外上下文的内容。外部每次给它一整份最新上下文时,它会和自己旧账本对比:没变的就不管,新加的或内容变了的才拿出来,包装成模型能读的 ResponseInputItem。这里还会区分上下文来源:Untrusted 表示不完全可信的用户侧内容,会做成用户片段;Application 表示应用或开发者提供的内容,会做成开发者片段。最后,它会用这次传进来的完整上下文替换旧账本。一个需要注意的点是:如果某个旧条目这次被删掉了,它不会生成一条“删除通知”,只是从账本里消失。

函数细节1
AdditionalContextStore::merge16–36 ↗
fn merge(
        &mut self,
        values: BTreeMap<String, AdditionalContextEntry>,
    ) -> Vec<ResponseInputItem>

作用:把一批最新的额外上下文合并进内部账本,并找出哪些内容是新加的或已经改变了。调用者会用它返回的结果,把这些变化发送给模型。

数据流:进去的是一张按字符串名字索引的上下文表,以及函数自己保存的旧上下文表。它逐条比较新表和旧表:如果某个名字对应的内容没变,就跳过;如果是新的或变了,就根据它的种类包装成用户片段或开发者片段,再转成 ResponseInputItem。出来的是这些需要补发给模型的消息列表;同时,内部保存的旧表会被整张替换成这次传入的新表。

调用关系:它是 AdditionalContextStore 这个上下文账本的核心动作。外部在收到或整理出一份新的额外上下文后会调用它;它不会再调用复杂的业务流程,只是借助 AdditionalContextUserFragment 和 AdditionalContextDeveloperFragment 把变化内容包装成模型输入项,交回给上层继续发送。

core/src/context_manager/updates.rs源码 ↗
orchestration每轮对话开始前或恢复会话时,用来生成上下文更新

可以把这个文件想成一个“变更通知员”。每次新一轮对话开始前,它会看上一轮记录的上下文,再看这一轮的上下文,判断哪些东西变了:运行环境变没变、权限规则变没变、协作模式变没变、实时模式开没开、模型有没有切换、人格设置有没有变化。只有真的变了,它才生成一小段给模型看的说明。这里的 ResponseItem 可以理解成“要放进对话历史里的消息块”。有些消息是 developer 角色,像系统内部给模型的工作说明;有些是 user 角色,像补充给模型看的环境信息。这个文件重要的地方在于:它不是重建全部提示词,而是做“差量更新”,像手机系统只推送改动过的设置,不把整本说明书重新发一遍。

函数细节12
build_environment_update_item21–40 ↗
fn build_environment_update_item(
    previous: Option<&TurnContextItem>,
    next: &TurnContext,
    shell: &Shell,
) -> Option<ResponseItem>

作用:检查工作环境有没有变化,比如当前目录、Shell 类型等,然后只在有变化时生成一条给模型看的环境更新消息。这样模型能知道环境换了,但不会被重复信息刷屏。

数据流:输入是上一轮的上下文记录、下一轮的 TurnContext(一轮对话要用的完整设置)和 Shell(命令行外壳信息)。它先看配置是否允许把环境上下文发给模型;如果不允许、没有上一轮记录,或者除了 Shell 名字外环境没变,就什么都不输出。否则它把上一轮和下一轮环境做对比,生成差异内容,再包装成 ResponseItem 返回。

调用关系:它被 build_settings_update_items 调用,是整套“设置变化汇总”里的环境检查零件。它会借助 EnvironmentContext 的 from_turn_context_item、from_turn_context 和 diff_from_turn_context_item 来读旧环境、读新环境、算差异,最后通过 ContextualUserFragment::into 变成可放进对话历史的消息。

调用图:调用 5 个内部函数(into, diff_from_turn_context_item, from_turn_context, from_turn_context_item, name);被 1 处调用(build_settings_update_items)。

build_permissions_update_item42–71 ↗
fn build_permissions_update_item(
    previous: Option<&TurnContextItem>,
    next: &TurnContext,
    exec_policy: &Policy,
) -> Option<String>

作用:检查权限规则有没有变化,比如模型能不能执行命令、执行前需不需要审批。权限变了就生成一段新说明,避免模型按旧规则做事。

数据流:输入是上一轮上下文、下一轮上下文和执行策略 Policy(执行命令时遵守的安全规则)。它先看配置是否要求把权限说明发给模型;如果没有上一轮记录,或者权限档案和审批策略都没变,就返回 None。若发生变化,它会结合新权限、审批人、当前目录、功能开关等信息,生成一段文本说明并返回。

调用关系:它由 build_settings_update_items 在准备 developer 更新内容时调用。它会读取上一轮的 permission_profile,并把新权限交给 PermissionsInstructions::from_permission_profile 来写成模型能读懂的规则文本。

调用图:调用 2 个内部函数(permission_profile, from_permission_profile);被 1 处调用(build_settings_update_items)。

build_collaboration_mode_update_item73–92 ↗
fn build_collaboration_mode_update_item(
    previous: Option<&TurnContextItem>,
    next: &TurnContext,
) -> Option<String>

作用:检查协作模式有没有变,比如模型应该更主动还是更保守。如果模式变了,就给模型一段新的协作说明。

数据流:输入是上一轮上下文和下一轮上下文。它先看配置是否允许发送协作模式说明;如果没有上一轮记录,或者模式没变,就不输出。若模式变了,它尝试把新模式转成一段 developer 说明文本;如果这个新模式本身没有额外说明,也可能不生成任何更新。

调用关系:它被 build_settings_update_items 调用,属于 developer 更新消息的一部分。它把具体文字生成工作交给 CollaborationModeInstructions::from_collaboration_mode;如果生成不到内容,旧的协作说明可能仍然留在提示历史里。

调用图:调用 1 个内部函数(from_collaboration_mode);被 1 处调用(build_settings_update_items)。

build_realtime_update_item94–121 ↗
fn build_realtime_update_item(
    previous: Option<&TurnContextItem>,
    previous_turn_settings: Option<&PreviousTurnSettings>,
    next: &TurnContext,
) -> Option<String>

作用:判断实时模式是否需要开始或结束,并生成对应说明。实时模式可以理解成一种特殊对话状态,模型需要知道现在是否处在这种状态里。

数据流:输入是上一轮上下文、上一轮保存的设置,以及下一轮上下文。它比较旧的 realtime_active 和新的 realtime_active:从开到关就输出“实时模式结束”;从关到开或首次开启就输出“实时模式开始”;一直不变就不输出。如果上一轮上下文里没有记录,它会用 previous_turn_settings 作为兜底,判断是否需要补一条结束说明。

调用关系:它既被 build_settings_update_items 用在普通设置更新里,也被 build_initial_realtime_item 用在初始上下文构建里。它会根据情况创建 RealtimeEndInstructions、RealtimeStartInstructions 或 RealtimeStartWithInstructions,让模型收到正确的开始/结束提示。

调用图:调用 2 个内部函数(new, new);被 2 处调用(build_initial_realtime_item, build_settings_update_items)。

build_initial_realtime_item123–129 ↗
fn build_initial_realtime_item(
    previous: Option<&TurnContextItem>,
    previous_turn_settings: Option<&PreviousTurnSettings>,
    next: &TurnContext,
) -> Option<String>

作用:在初始上下文阶段复用实时模式更新逻辑,判断一开始是否需要告诉模型实时模式的状态变化。它本身不另写规则,只是入口包装。

数据流:输入是上一轮上下文、上一轮保存的设置和下一轮上下文。它直接把这些交给 build_realtime_update_item,然后原样返回那边生成的实时模式说明,可能是开始、结束,也可能没有内容。

调用关系:它被 build_initial_context 调用,用在构建初始提示内容时。它把真正的判断工作交给 build_realtime_update_item,保证初始阶段和后续更新阶段用的是同一套规则。

调用图:调用 1 个内部函数(build_realtime_update_item);被 1 处调用(build_initial_context)。

build_personality_update_item131–153 ↗
fn build_personality_update_item(
    previous: Option<&TurnContextItem>,
    next: &TurnContext,
    personality_feature_enabled: bool,
) -> Option<String>

作用:检查模型的“人格”设置有没有变化,并在需要时生成新的说明。这里的人格不是人类性格,而是模型回答时应采用的风格或行为设定。

数据流:输入是上一轮上下文、下一轮上下文,以及人格功能是否开启。功能没开、没有上一轮记录、模型已经换了,都会直接不输出。只有同一个模型下人格真的变了,它才去查这个人格对应的提示文字,并包装成 PersonalitySpecInstructions 返回。

调用关系:它被 build_settings_update_items 调用,是 developer 更新内容的一部分。它会调用 personality_message_for 查找具体的人格说明文本,再把文本包成模型可读的指令。

调用图:调用 1 个内部函数(personality_message_for);被 1 处调用(build_settings_update_items)。

personality_message_for155–164 ↗
fn personality_message_for(
    model_info: &ModelInfo,
    personality: Personality,
) -> Option<String>

作用:根据模型信息和指定人格,找出应该给模型看的那段人格说明。它还会过滤掉空字符串,避免发一条没内容的说明。

数据流:输入是 ModelInfo(模型的能力和附加说明)以及 Personality(人格枚举值)。它从模型自带的 model_messages 里查找对应人格的说明;如果没有配置、找不到,或者找到的是空文本,就返回 None;否则返回这段说明文字。

调用关系:它被 build_personality_update_item 和 build_initial_context 使用。前者用它处理人格变更,后者用它处理初始提示里的人格说明,因此它是人格说明文本的统一查找点。

调用图:被 2 处调用(build_personality_update_item, build_initial_context)。

build_model_instructions_update_item166–181 ↗
fn build_model_instructions_update_item(
    previous_turn_settings: Option<&PreviousTurnSettings>,
    next: &TurnContext,
) -> Option<String>

作用:检查这一轮是否切换了模型。如果模型换了,它会生成一段新模型的专属说明,告诉模型现在应该按新模型的规则工作。

数据流:输入是上一轮保存的设置和下一轮上下文。没有上一轮设置时,它无法比较,就不输出;如果模型标识没变,也不输出。若模型变了,它从新模型信息里取出适合当前人格的模型说明;说明为空就不发,不为空就包装成 ModelSwitchInstructions 返回。

调用关系:它被 build_settings_update_items 和 build_initial_context 调用。build_settings_update_items 会把它放在 developer 更新内容的最前面,因为模型切换说明应该先被模型读到,再读其他变化。

调用图:调用 1 个内部函数(new);被 2 处调用(build_settings_update_items, build_initial_context)。

build_developer_update_item183–185 ↗
fn build_developer_update_item(text_sections: Vec<String>) -> Option<ResponseItem>

作用:把多段内部说明合成一条 developer 角色的消息。developer 可以理解成“开发者给模型的工作要求”,优先级高于普通用户补充信息。

数据流:输入是一组文本段落。它把角色固定为 developer,然后交给 build_text_message 组装;如果文本段落为空,就不会生成消息;否则输出一个 ResponseItem。

调用关系:它被 spawn_forked_thread、build_settings_update_items 和 build_initial_context 使用。它自己不关心文本内容来自哪里,只负责把这些内部说明装进统一的消息格式。

调用图:调用 1 个内部函数(build_text_message);被 3 处调用(spawn_forked_thread, build_settings_update_items, build_initial_context)。

build_contextual_user_message187–189 ↗
fn build_contextual_user_message(text_sections: Vec<String>) -> Option<ResponseItem>

作用:把多段上下文说明合成一条 user 角色的消息。这里的 user 消息不是用户手打的问题,而是系统补充给模型看的上下文信息。

数据流:输入是一组文本段落。它把角色固定为 user,然后调用 build_text_message 生成消息;如果没有任何段落,就返回 None;有内容时返回一个 ResponseItem。

调用关系:它被 build_initial_context 调用,用于初始上下文里的用户侧补充信息。它和 build_developer_update_item 共用 build_text_message,只是角色不同。

调用图:调用 1 个内部函数(build_text_message);被 1 处调用(build_initial_context)。

build_text_message191–208 ↗
fn build_text_message(role: &str, text_sections: Vec<String>) -> Option<ResponseItem>

作用:把若干段纯文本装成标准消息格式。它是一个小工具,避免 developer 消息和 user 消息各自重复写一遍包装代码。

数据流:输入是角色名 role 和文本段落列表。列表为空时,它直接返回 None。列表不为空时,它把每段文字变成 ContentItem::InputText,再组成 ResponseItem::Message;消息里会带上角色,但没有 id、阶段和额外 metadata。

调用关系:它被 build_developer_update_item 和 build_contextual_user_message 调用,是这两个包装函数的底层共用零件。上层决定消息身份,它负责把文本变成协议里统一的消息形状。

调用图:被 2 处调用(build_contextual_user_message, build_developer_update_item)。

build_settings_update_items210–244 ↗
fn build_settings_update_items(
    previous: Option<&TurnContextItem>,
    previous_turn_settings: Option<&PreviousTurnSettings>,
    next: &TurnContext,
    shell: &Shell,
    exec_policy: &Policy,

作用:这是本文件的总装配函数:它把环境、权限、协作模式、实时模式、人格、模型切换这些变化统一检查一遍,然后产出本轮真正需要追加给模型的消息列表。

数据流:输入是上一轮上下文、上一轮设置、下一轮上下文、Shell、执行策略,以及人格功能开关。它先单独生成环境变化的 user 消息;再按顺序收集 developer 说明,模型切换说明会排在最前。然后它最多放入两条消息:一条 developer 更新,一条环境相关的 user 更新。没有变化的部分会被跳过,最后返回 ResponseItem 列表。

调用关系:它是这个文件里的汇总入口,会依次调用 build_model_instructions_update_item、build_permissions_update_item、build_collaboration_mode_update_item、build_realtime_update_item、build_personality_update_item、build_environment_update_item 和 build_developer_update_item。调用图也把它标在 build_settings_update_items 这个更新流程中,意思是它承担整套设置差量更新的组织工作。

调用图:调用 7 个内部函数(build_collaboration_mode_update_item, build_developer_update_item, build_environment_update_item, build_model_instructions_update_item, build_permissions_update_item, build_personality_update_item, build_realtime_update_item);被 1 处调用(build_settings_update_items);外部调用 1 个(with_capacity)。

转录规范化和历史

这些文件组织上下文管理器表面,以及从规范化帮助器到提示词就绪历史组装的核心转录处理流程。

core/src/context_manager/mod.rs源码 ↗
orchestrationcross-cutting

在聊天式系统里,“上下文”就是模型回答问题时能看到的历史消息、工具调用结果等信息。上下文太长会塞不下,边界切错又可能让模型误解对话。所以项目把这些工作单独放在 context_manager 里。这个文件本身不做复杂计算,更像一个目录索引:它声明了 history、normalize、updates 三个子模块;然后把 history 里几个重要能力重新公开出来,比如 ContextManager(用来整理和裁剪上下文的核心工具)、is_user_turn_boundary(判断哪里算一个用户回合的边界)、truncate_function_output_payload(把过长的工具输出缩短)。这样别的代码只要认准 context_manager 这个入口,就能拿到需要的上下文管理能力。

core/src/context_manager/normalize.rs源码 ↗
domain_logichistory normalization before sending context to model

模型对话里不只有普通文字,还可能有“调用工具”和“工具返回结果”。这些东西必须像快递单和包裹一样一一对应:有调用,就应该有结果;有结果,也必须能找到对应的调用。这个文件就是对这份历史记录做清理和补洞。它会给缺失的工具结果补一个“已中止”的假结果,避免后续流程因为记录断裂而出错;也会删掉找不到来源的孤立结果,避免模型看到莫名其妙的信息。另一个重要功能是处理图片:如果当前模型不支持图片输入,它不会硬把图片塞过去,而是把图片内容替换成一句说明,或者清空图片生成结果。这样做的目的不是改变对话意思,而是让上下文保持安全、完整、可发送。

函数细节5
ensure_call_outputs_present14–118 ↗
fn ensure_call_outputs_present(items: &mut Vec<ResponseItem>)

作用:检查对话记录里每一次工具调用后面是不是都有对应的工具输出。没有的话,它会补一个占位输出,通常表示这次调用已经“aborted”,也就是中止了。

数据流:进去的是一组可修改的 ResponseItem,也就是对话历史里的条目。它先记下已有的函数输出、搜索工具输出、自定义工具输出的 call_id;再逐条找调用项,看有没有匹配的输出;如果缺,就生成一个假的输出条目并插到调用后面。出来时,原来的列表被原地改好,每个需要配对的调用都尽量有了结果。

调用关系:它在 normalize_history 做整体整理时被调用,属于“补缺口”的步骤。遇到自定义工具或本地 shell 调用缺结果时,它会通过 error_or_panic 报告严重问题;生成普通函数输出时会用 from_text 把“aborted”文字包装成标准输出格式,并用 info 记录发生了什么。

调用图:调用 2 个内部函数(error_or_panic, from_text);被 1 处调用(normalize_history);外部调用 4 个(new, new, format!, info!)。

remove_orphan_outputs120–193 ↗
fn remove_orphan_outputs(items: &mut Vec<ResponseItem>)

作用:删除那些只有工具输出、却找不到对应工具调用的记录。简单说,就是清掉“没有订单号的包裹”,避免历史记录里出现来路不明的结果。

数据流:进去的是一组可修改的 ResponseItem。它先收集所有函数调用、搜索调用、本地 shell 调用、自定义工具调用的 call_id;然后过滤整个列表:每个输出项都要能找到对应的调用,找不到就报告问题并删除。出来时,列表里只保留能解释清楚来源的输出;服务器端搜索输出和没有 call_id 的某些搜索输出会被允许保留。

调用关系:它由 normalize_history 调用,通常和补齐缺失输出的步骤配合使用:一个负责补“有调用没结果”,一个负责删“有结果没调用”。发现孤立输出时,它会通过 error_or_panic 提醒这是不正常的历史状态。

调用图:被 1 处调用(normalize_history)。

remove_corresponding_for195–280 ↗
fn remove_corresponding_for(items: &mut Vec<ResponseItem>, item: &ResponseItem)

作用:当要删除某个调用或输出时,它顺手删除与它配对的另一半。这样不会留下半截记录,比如删了工具调用却还剩工具结果。

数据流:进去的是一个可修改的 ResponseItem 列表,以及一个作为参照的 ResponseItem。它根据这个参照项的类型和 call_id,去列表里找对应的另一项:函数调用找函数输出,函数输出找函数调用或本地 shell 调用,搜索调用找搜索输出,自定义工具调用找自定义工具输出。找到后删除第一个匹配项;如果参照项不是这些成对类型,就什么也不做。

调用关系:它被 remove_first_item 调用,场景通常是上下文太长或需要丢掉某个条目时,顺便保持历史记录成对完整。真正执行“找第一个匹配项并删掉”的小动作,会交给 remove_first_matching;少数分支会直接查找位置并删除。

调用图:调用 1 个内部函数(remove_first_matching);被 1 处调用(remove_first_item)。

remove_first_matching282–289 ↗
fn remove_first_matching(items: &mut Vec<ResponseItem>, predicate: F)

作用:这是一个小工具函数:在列表里找到第一个符合条件的条目,然后把它删掉。它避免上层函数反复写同样的查找删除代码。

数据流:进去的是一个可修改的 ResponseItem 列表,以及一个判断函数,也就是“怎样算匹配”的规则。它从前往后找第一个符合规则的位置;找到了就删除这个条目,找不到就不改动列表。出来时,列表可能少了一个元素,也可能保持原样。

调用关系:它只被 remove_corresponding_for 使用,是后者的底层小帮手。remove_corresponding_for 负责决定要找哪一类配对项,而它负责把“找到并删除第一个”这件事干净地完成。

调用图:被 1 处调用(remove_corresponding_for)。

strip_images_when_unsupported293–343 ↗
fn strip_images_when_unsupported(
    input_modalities: &[InputModality],
    items: &mut [ResponseItem],
)

作用:当模型不支持图片输入时,把对话历史里的图片内容去掉或替换成文字提示。这样可以防止系统把模型看不懂的图片数据发过去。

数据流:进去的是模型支持的输入类型列表 input_modalities,以及一组可修改的 ResponseItem。它先检查列表里是否包含 Image,也就是是否支持图片;支持就直接不动。不支持时,它会遍历消息和工具输出,把图片项替换成“image content omitted because you do not support image input”这句提示;如果是图片生成调用的结果,就清空结果内容。出来时,历史记录仍保留“这里曾经有图片”的提示,但不再包含真实图片内容。

调用关系:它由 normalize_history 调用,通常发生在把上下文交给模型之前。它不负责判断哪家模型能看图,只读取传进来的 input_modalities;如果发现不支持图片,就在本地把内容改成安全的文本占位,避免后续发送阶段出错。

调用图:被 1 处调用(normalize_history);外部调用 3 个(with_capacity, iter_mut, contains)。

core/src/context_manager/history.rs源码 ↗
domain_logiccross-cutting:整段会话中持续活跃,记录消息、准备提示词、估算令牌、回滚历史时都会用到

可以把这个文件里的 ContextManager 想成聊天记录管理员。它按从旧到新的顺序保存用户消息、助手消息、工具调用、工具返回结果、推理内容等,但不会保存系统消息这类不该进普通历史的东西。每次新内容进来时,它会先检查是不是该记录的消息,并把工具输出按限制截短,防止一次工具结果太大把模型窗口挤爆。真正发给模型前,它还会做一次“整理”:工具调用必须配上对应结果,孤零零的结果会被删掉;如果模型不支持图片,就把图片内容去掉。它也维护令牌使用信息,令牌可以理解成模型读写文字时的计费和容量单位。另一个重要功能是回滚最近几轮用户对话,同时清理紧贴在这些轮次前面的临时上下文更新,避免以后拿过期的上下文做差异更新。

函数细节39
ContextManager::new54–63 ↗
fn new() -> Self

作用:创建一个空的聊天历史管理员。它准备好空消息列表、初始历史版本号、初始令牌统计,以及还没有参考上下文的状态。

数据流:进去没有外部输入 → 它新建空的 items,把 history_version 设为 0,并调用令牌统计的初始化逻辑 → 出来一个可以开始记录对话的 ContextManager。

调用关系:这是所有历史记录工作的起点。测试、会话重建和上层的新建流程会先调用它,后续 record_items、for_prompt、replace 等方法都在这个对象上继续工作。

调用图:调用 1 个内部函数(new_or_append);被 7 处调用(create_history_with_items, record_items_respects_custom_token_limit, record_items_truncates_custom_tool_call_output_content, record_items_truncates_function_call_output_content, reconstruct_history_from_rollout, sample_rollout, new);外部调用 1 个(new)。

ContextManager::token_info65–67 ↗
fn token_info(&self) -> Option<TokenUsageInfo>

作用:取出当前保存的令牌使用信息。调用者用它了解模型上下文大概已经用了多少容量。

数据流:进去是当前 ContextManager → 它读取内部 token_info 并复制一份 → 出来一个可选的 TokenUsageInfo,不改动历史本身。

调用关系:它是一个查询口。外层的 token_info 包装接口会调用它,把内部统计结果交给需要展示或判断容量的地方。

调用图:被 1 处调用(token_info)。

ContextManager::set_token_info69–71 ↗
fn set_token_info(&mut self, info: Option<TokenUsageInfo>)

作用:直接替换当前的令牌使用信息。适合外部已经拿到更可信的统计结果时,把这里的旧统计覆盖掉。

数据流:进去一个可选的 TokenUsageInfo → 它把内部 token_info 改成这个值 → 出来没有返回值,但 ContextManager 的令牌状态变了。

调用关系:它被上层 set_token_info 调用,是外部同步令牌统计到历史管理员里的入口。

调用图:被 1 处调用(set_token_info)。

ContextManager::set_reference_context_item73–75 ↗
fn set_reference_context_item(&mut self, item: Option<TurnContextItem>)

作用:设置用于以后比较上下文变化的“参考快照”。有了它,下次可以只告诉模型上下文哪里变了,而不是整包重发。

数据流:进去一个可选的 TurnContextItem → 它把 reference_context_item 替换成这个快照或清空 → 出来没有返回值,但之后的上下文差异判断会受影响。

调用关系:它被 replace_history 和外层 set_reference_context_item 调用。回滚逻辑发现旧快照不可靠时,也可能在 trim_pre_turn_context_updates 里清空同一份状态。

调用图:被 2 处调用(replace_history, set_reference_context_item)。

ContextManager::reference_context_item77–79 ↗
fn reference_context_item(&self) -> Option<TurnContextItem>

作用:读取当前保存的参考上下文快照。调用者可以用它判断下次是否能做上下文差异更新。

数据流:进去是当前 ContextManager → 它复制内部 reference_context_item → 出来一个可选的 TurnContextItem,不改变任何历史。

调用关系:它是查询参考上下文的出口,被外层 reference_context_item 调用。

调用图:被 1 处调用(reference_context_item)。

ContextManager::set_token_usage_full81–88 ↗
fn set_token_usage_full(&mut self, context_window: i64)

作用:把令牌使用状态标记为“上下文窗口已满”。这通常用于告诉系统:模型可用容量已经被占满,需要压缩或裁剪。

数据流:进去一个 context_window,也就是模型上下文窗口大小 → 如果已有 token_info,就把它填满到这个窗口;如果没有,就新建一个满窗口统计 → 出来没有返回值,但 token_info 变成满容量状态。

调用关系:它被外层 set_token_usage_full 调用,内部依赖 full_context_window 创建满窗口统计。

调用图:调用 1 个内部函数(full_context_window);被 1 处调用(set_token_usage_full)。

ContextManager::record_items91–105 ↗
fn record_items(&mut self, items: I, policy: TruncationPolicy)

作用:把一批新产生的对话项目记入历史。它会跳过不该进入 API 历史的项目,并把工具输出先截短。

数据流:进去一批按时间从旧到新的 ResponseItem,以及截断策略 → 它逐个判断 is_api_message,合格的再交给 process_item 处理 → 出来没有返回值,但内部 items 追加了处理后的历史项。

调用关系:这是写入历史的主要入口,被外层 record_items 调用。它把筛选工作交给 is_api_message,把内容缩短工作交给 process_item。

调用图:调用 2 个内部函数(process_item, is_api_message);被 1 处调用(record_items)。

ContextManager::for_prompt111–114 ↗
fn for_prompt(mut self, input_modalities: &[InputModality]) -> Vec<ResponseItem>

作用:把当前历史整理成可以发送给模型的版本。它会修复工具调用和结果的配对问题,并按模型能力处理图片。

数据流:进去一个 ContextManager 本身和模型支持的输入类型列表 → 它先 normalize_history 清理和补齐历史 → 出来一个 Vec<ResponseItem>,就是准备放进提示词的历史。

调用关系:这是读出“模型可见历史”的出口。它消费当前对象,并把最终整理动作交给 normalize_history。

调用图:调用 1 个内部函数(normalize_history)。

ContextManager::raw_items117–119 ↗
fn raw_items(&self) -> &[ResponseItem]

作用:查看未经整理的原始历史。适合需要自己检查或裁剪历史的内部流程使用。

数据流:进去是当前 ContextManager → 它返回内部 items 的只读切片 → 出来的是原始 ResponseItem 列表视图,不会修改内容。

调用关系:trim_function_call_history_to_fit_context_window 会调用它,先看原始历史再决定怎么裁剪。

调用图:被 1 处调用(trim_function_call_history_to_fit_context_window)。

ContextManager::into_raw_items122–124 ↗
fn into_raw_items(self) -> Vec<ResponseItem>

作用:取走未经整理的原始历史,并消耗掉这个历史管理员对象。适合调用者已经不需要 ContextManager,只想拿走里面的列表。

数据流:进去是整个 ContextManager → 它把内部 items 移出来 → 出来一个 Vec<ResponseItem>,原对象随之被消费。

调用关系:这是一个低层取数出口。它不调用别的整理逻辑,也不被给定调用图中的其他函数调用。

ContextManager::history_version126–128 ↗
fn history_version(&self) -> u64

作用:读取历史版本号。版本号会在历史被整体改写时增加,方便外部知道历史是否变过。

数据流:进去是当前 ContextManager → 它读取 history_version → 出来一个数字,不修改历史。

调用关系:它是状态查询函数。replace、replace_last_turn_images 等会改变版本号,而这个函数负责把版本号暴露出去。

ContextManager::estimate_token_count132–139 ↗
fn estimate_token_count(&self, turn_context: &TurnContext) -> Option<i64>

作用:粗略估算当前历史加上基础指令后会占多少令牌。它不是精确分词器,而是用字节数做近似,帮助系统提前判断会不会太长。

数据流:进去一个 TurnContext,里面有模型信息、配置和人格设置 → 它取出模型基础指令,包装成 BaseInstructions → 再交给 estimate_token_count_with_base_instructions → 出来一个可选的令牌估算值。

调用关系:它是更方便的估算入口,调用者只要有 TurnContext 就能用。真正累加历史项目的工作由 estimate_token_count_with_base_instructions 完成。

调用图:调用 1 个内部函数(estimate_token_count_with_base_instructions)。

ContextManager::estimate_token_count_with_base_instructions141–155 ↗
fn estimate_token_count_with_base_instructions(
        &self,
        base_instructions: &BaseInstructions,
    ) -> Option<i64>

作用:在已经给定基础指令文本的情况下,估算整段输入大概多少令牌。它把基础指令和历史项的估算加在一起。

数据流:进去 BaseInstructions → 它先用 approx_token_count 估算指令文本,再逐条用 estimate_item_token_count 估算历史项并安全相加 → 出来一个总令牌数估算。

调用关系:它被 estimate_token_count 调用,也被 trim_function_call_history_to_fit_context_window 用来判断历史是否需要裁剪。

调用图:被 2 处调用(trim_function_call_history_to_fit_context_window, estimate_token_count);外部调用 2 个(approx_token_count, try_from)。

ContextManager::remove_first_item157–167 ↗
fn remove_first_item(&mut self)

作用:删除最老的一条历史。为了不留下半截工具调用,它还会顺手删掉这条记录对应的调用或输出伙伴。

数据流:进去是当前历史 → 如果 items 不空,就移除第 0 个最老项目,并调用 remove_corresponding_for 清掉对应配对项 → 出来没有返回值,历史变短。

调用关系:它是简单的前端裁剪工具。配对关系维护交给 normalize 模块的 remove_corresponding_for,避免后面发送给模型时出现只有调用没有结果的怪状态。

调用图:调用 1 个内部函数(remove_corresponding_for)。

ContextManager::replace169–172 ↗
fn replace(&mut self, items: Vec<ResponseItem>)

作用:用一整套新历史替换旧历史,并把历史版本号加一。它表示历史被重写过,不只是追加了一条消息。

数据流:进去一个新的 ResponseItem 列表 → 它覆盖内部 items,并让 history_version 饱和递增 → 出来没有返回值,但历史内容和版本号都变了。

调用关系:它被裁剪流程 trim_function_call_history_to_fit_context_window、回滚流程 drop_last_n_user_turns 和 replace_history 调用,是统一改写历史的出口。

调用图:被 3 处调用(trim_function_call_history_to_fit_context_window, drop_last_n_user_turns, replace_history)。

ContextManager::replace_last_turn_images176–206 ↗
fn replace_last_turn_images(&mut self, placeholder: &str) -> bool

作用:把最近一轮里来自工具输出的图片替换成占位文字。这样在不能继续保留图片或需要省空间时,历史仍然能说明“这里原来有图片”。

数据流:进去一个 placeholder 文本 → 它从后往前找最近的工具输出或用户轮次边界;如果找到的是工具输出,就把其中的图片项改成文本占位 → 出来 true 或 false,表示是否真的替换了图片;替换成功会增加历史版本号。

调用关系:它独立完成最近工具图片的替换,不走 normalize_history。它只改工具输出中的图片,不会改普通用户消息里的图片。

调用图:外部调用 1 个(matches!)。

ContextManager::drop_last_n_user_turns224–247 ↗
fn drop_last_n_user_turns(&mut self, num_turns: u32)

作用:回滚最近若干个用户轮次。它用于撤销对话后半段,让历史回到更早的状态。

数据流:进去要删除的轮次数 num_turns → 如果是 0 就不动;否则复制当前历史,找出用户轮次边界,算出应该切到哪里,再用 trim_pre_turn_context_updates 清理边界前的临时上下文更新 → 最后 replace 成切短后的历史。

调用关系:这是回滚的主流程。它靠 user_message_positions 找边界,靠 trim_pre_turn_context_updates 处理上下文更新,最后用 replace 正式提交新历史。

调用图:调用 3 个内部函数(replace, trim_pre_turn_context_updates, user_message_positions);外部调用 1 个(try_from)。

ContextManager::update_token_info249–259 ↗
fn update_token_info(
        &mut self,
        usage: &TokenUsage,
        model_context_window: Option<i64>,
    )

作用:把模型刚返回的令牌用量合并进当前统计。这样系统能持续知道最近一次和累计上下文的用量情况。

数据流:进去 TokenUsage 和可选的模型上下文窗口大小 → 它复制 usage,并调用 TokenUsageInfo::new_or_append 与旧 token_info 合并 → 出来没有返回值,但内部 token_info 更新了。

调用关系:它被 update_token_info_from_usage 调用,是模型响应结束后刷新令牌统计的入口。

调用图:调用 1 个内部函数(new_or_append);被 1 处调用(update_token_info_from_usage);外部调用 1 个(clone)。

ContextManager::get_non_last_reasoning_items_tokens261–281 ↗
fn get_non_last_reasoning_items_tokens(&self) -> i64

作用:估算最近一个用户轮次之前那些加密推理内容占了多少令牌。这里的“推理内容”是模型内部思考过程的记录,有时服务器没有把它算进用量,需要客户端补估。

数据流:进去是当前历史 → 它先找到最后一个用户轮次边界;如果没有边界就返回 0;否则只看这个边界之前的加密 Reasoning 项,并逐个估算令牌后相加 → 出来一个令牌估算值。

调用关系:它只被 get_total_token_usage 调用。当服务器没有包含历史推理令牌时,总用量计算会用它补上这部分。

调用图:被 1 处调用(get_total_token_usage)。

ContextManager::items_after_last_model_generated_item285–292 ↗
fn items_after_last_model_generated_item(&self) -> &[ResponseItem]

作用:找出最近一次模型生成内容之后,本地又追加的那些项目。这些项目通常还没有被服务器的 last_token_usage 统计进去。

数据流:进去是当前历史 → 它从后往前找最后一个 is_model_generated_item,取它之后的切片;如果找不到,就返回空尾部位置之后的范围 → 出来一个只读的 ResponseItem 切片。

调用关系:get_total_token_usage 和 estimated_tokens_after_last_model_generated_item 都调用它,用来补算服务器统计之后新增的本地内容。

调用图:被 2 处调用(estimated_tokens_after_last_model_generated_item, get_total_token_usage)。

ContextManager::get_total_token_usage296–314 ↗
fn get_total_token_usage(&self, server_reasoning_included: bool) -> i64

作用:给出当前历史总共大概用了多少令牌。它会把服务器报告的用量、本地新增内容、必要时还有旧推理内容合起来算。

数据流:进去一个 server_reasoning_included,表示服务器是否已经把过去的推理令牌算进去了 → 它读取 token_info 的 last_token_usage,再估算最后一次模型生成后新增项目;如果服务器没包含推理,还会加上 get_non_last_reasoning_items_tokens → 出来一个总令牌数。

调用关系:它被外层 get_total_token_usage 调用。内部会调用 items_after_last_model_generated_item,也可能调用 get_non_last_reasoning_items_tokens。

调用图:调用 2 个内部函数(get_non_last_reasoning_items_tokens, items_after_last_model_generated_item);被 1 处调用(get_total_token_usage)。

ContextManager::estimated_tokens_after_last_model_generated_item316–321 ↗
fn estimated_tokens_after_last_model_generated_item(&self) -> i64

作用:只估算最近一次模型生成之后新增项目的令牌数。它用于判断本地追加内容又占了多少上下文空间。

数据流:进去是当前历史 → 它取 items_after_last_model_generated_item 返回的项目,再逐条估算令牌并相加 → 出来一个估算值。

调用关系:这是一个专门的查询函数,内部复用 items_after_last_model_generated_item 的切片定位逻辑。

调用图:调用 1 个内部函数(items_after_last_model_generated_item)。

ContextManager::normalize_history327–336 ↗
fn normalize_history(&mut self, input_modalities: &[InputModality])

作用:把内存里的历史整理成满足发送要求的形态。它保证工具调用和工具结果成对出现,并按模型是否支持图片来处理图片。

数据流:进去模型支持的输入类型列表 → 它先调用 ensure_call_outputs_present 补齐缺失输出,再调用 remove_orphan_outputs 删除孤儿输出,最后调用 strip_images_when_unsupported 去掉不支持的图片 → 出来没有返回值,但 items 被清理过。

调用关系:它由 for_prompt 调用,是历史真正发给模型前的最后清洗步骤。具体规则放在 normalize 模块中执行。

调用图:调用 3 个内部函数(ensure_call_outputs_present, remove_orphan_outputs, strip_images_when_unsupported);被 1 处调用(for_prompt)。

ContextManager::process_item338–376 ↗
fn process_item(&self, item: &ResponseItem, policy: TruncationPolicy) -> ResponseItem

作用:在历史项入库前做必要加工。主要是把函数或自定义工具的输出按策略截短,其他项目则原样复制。

数据流:进去一个 ResponseItem 和截断策略 → 它给策略留出一点序列化预算余量;如果是工具输出,就调用 truncate_function_output_payload 缩短内容;如果是普通消息、调用、推理等,就 clone 一份 → 出来一个可安全保存的 ResponseItem。

调用关系:它只被 record_items 调用,是记录历史时的预处理工位。真正的文本和内容项截断交给 truncate_function_output_payload。

调用图:调用 1 个内部函数(truncate_function_output_payload);被 1 处调用(record_items);外部调用 1 个(clone)。

ContextManager::trim_pre_turn_context_updates395–423 ↗
fn trim_pre_turn_context_updates(
        &mut self,
        snapshot: &[ResponseItem],
        first_instruction_turn_idx: usize,
        mut cut_idx: usize,
    ) -> usize

作用:回滚时,顺便删掉紧贴在被回滚用户轮次前面的临时上下文更新。否则历史会留下“已经没有对应对话”的环境说明。

数据流:进去一份历史快照、最早可回滚轮次位置、初步切断位置 → 它从切断位置往前看,连续遇到上下文型 developer 或 user 消息就继续往前删;如果删到混合了持久开发者文本的上下文包,就清空 reference_context_item → 出来调整后的切断位置。

调用关系:它被 drop_last_n_user_turns 调用,是回滚流程中的清尾巴步骤。它依赖 event_mapping 里的判断函数区分哪些消息只是上下文更新。

调用图:调用 3 个内部函数(has_non_contextual_dev_message_content, is_contextual_dev_message_content, is_contextual_user_message_content);被 1 处调用(drop_last_n_user_turns)。

truncate_function_output_payload426–443 ↗
fn truncate_function_output_payload(
    output: &FunctionCallOutputPayload,
    policy: TruncationPolicy,
) -> FunctionCallOutputPayload

作用:按给定策略缩短工具输出内容。这样工具返回一大段日志、文件内容或图片列表时,不会把模型上下文撑爆。

数据流:进去一个 FunctionCallOutputPayload 和 TruncationPolicy → 如果输出主体是文本,就调用 truncate_text;如果是内容项列表,就调用 truncate_function_output_items_with_policy → 出来一个新的 FunctionCallOutputPayload,success 标记保持不变。

调用关系:它被 ContextManager::process_item 调用,是工具输出入历史前的实际截断工具。

调用图:被 1 处调用(process_item);外部调用 4 个(truncate_function_output_items_with_policy, truncate_text, ContentItems, Text)。

is_api_message448–467 ↗
fn is_api_message(message: &ResponseItem) -> bool

作用:判断一条 ResponseItem 是否应该进入 API 对话历史。简单说,系统消息、压缩触发器和未知项不收,普通对话、工具调用、工具结果等会收。

数据流:进去一条 ResponseItem → 它按类型和消息角色做判断,system 角色消息返回 false,其它允许的项目返回 true → 出来一个布尔值。

调用关系:它被 record_items 调用,站在历史入口处当过滤器,防止不该给模型看的项目被记录进去。

调用图:被 1 处调用(record_items)。

estimate_reasoning_length469–475 ↗
fn estimate_reasoning_length(encoded_len: usize) -> usize

作用:根据加密推理内容的编码长度,粗略估算它解码后对模型可见的长度。这里用的是经验公式,不追求精确。

数据流:进去一个已编码字符串长度 → 它按 base64 大约 4 字符变 3 字节的比例折算,并减去固定开销 → 出来一个估算字节数。

调用关系:它被 estimate_response_item_model_visible_bytes 调用,用于处理 Reasoning、Compaction 这类加密内容的大小估算。

调用图:被 1 处调用(estimate_response_item_model_visible_bytes)。

estimate_encrypted_function_output_length477–479 ↗
fn estimate_encrypted_function_output_length(encoded_len: usize) -> usize

作用:估算加密工具输出真正代表的内容长度。加密文本本身可能比原内容膨胀,所以这里把它换算成更接近实际内容的大小。

数据流:进去加密内容字符串长度 → 它用固定比例做向上取整换算 → 出来一个估算字节数。

调用关系:它在加密工具输出大小调整中使用,具体由 encrypted_function_output_estimate_adjustment 调用。

estimate_item_token_count481–484 ↗
fn estimate_item_token_count(item: &ResponseItem) -> i64

作用:估算单个历史项大概占多少令牌。它先估算模型真正会看到多少字节,再把字节数换成令牌数。

数据流:进去一条 ResponseItem → 它调用 estimate_response_item_model_visible_bytes 得到字节估算,再用 approx_tokens_from_byte_count_i64 转成令牌 → 出来一个令牌估算值。

调用关系:很多统计流程都会通过它累加历史大小,例如 estimate_token_count_with_base_instructions、get_total_token_usage 和相关私有估算函数。

调用图:调用 1 个内部函数(estimate_response_item_model_visible_bytes);外部调用 1 个(approx_tokens_from_byte_count_i64)。

estimate_response_item_model_visible_bytes508–540 ↗
fn estimate_response_item_model_visible_bytes(item: &ResponseItem) -> i64

作用:估算一条历史项对模型来说大概有多少字节。它会特别处理加密推理、图片 data URL 和加密工具输出,避免简单按 JSON 长度算得太离谱。

数据流:进去一条 ResponseItem → 对加密推理或压缩项,它用 estimate_reasoning_length;对普通项,它先序列化成 JSON 算原始长度,再用 image_data_url_estimate_adjustment 和 encrypted_function_output_estimate_adjustment 把大块 base64 或加密载荷替换成更合理的估算 → 出来一个模型可见字节数。

调用关系:它被 estimate_item_token_count 调用,是单项令牌估算的核心。图片和加密输出的细节分别交给两个 adjustment 函数。

调用图:调用 3 个内部函数(encrypted_function_output_estimate_adjustment, estimate_reasoning_length, image_data_url_estimate_adjustment);被 1 处调用(estimate_item_token_count);外部调用 2 个(try_from, to_string)。

parse_base64_image_data_url547–574 ↗
fn parse_base64_image_data_url(url: &str) -> Option<&str>

作用:识别一个字符串是不是内嵌的 base64 图片 data URL,并取出真正的 base64 数据部分。data URL 可以理解成把图片直接塞进文本里的格式。

数据流:进去一个 URL 字符串 → 它检查是否以 data: 开头、逗号前是否是 image/* 类型、参数里是否有 base64 标记 → 如果都满足,返回逗号后的 payload;否则返回 None。

调用关系:它是图片估算的基础工具。estimate_original_image_bytes 和 image_data_url_estimate_adjustment 都依赖它判断哪些图片可以按图片成本估算,而不是按原始字符串长度估算。

estimate_original_image_bytes576–610 ↗
fn estimate_original_image_bytes(image_url: &str) -> Option<i64>

作用:对 detail 为 original 的内嵌图片,按图片原始尺寸估算它会花多少模型容量。它会缓存结果,避免同一张图反复解码。

数据流:进去一个图片 data URL → 它先用 sha1_digest 做缓存键;缓存没有时,解析 base64、解码图片、读取宽高,按 32 像素小块计算 patch 数,并限制最大 patch 数 → 出来一个估算字节数;如果解析或解码失败,返回 None。

调用关系:它被 image_data_url_estimate_adjustment 在遇到 Original 图片细节时调用。缓存 ORIGINAL_IMAGE_ESTIMATE_CACHE 让重复估算更便宜。

调用图:外部调用 1 个(sha1_digest)。

image_data_url_estimate_adjustment616–657 ↗
fn image_data_url_estimate_adjustment(item: &ResponseItem) -> (i64, i64)

作用:扫描一条历史项里的内嵌图片,并算出该从原始 JSON 长度里减掉多少 base64 字符、再补上多少图片估算成本。

数据流:进去一条 ResponseItem → 它检查普通消息和工具输出里的图片内容;每遇到一个合法 base64 图片 data URL,就累计 payload 长度,并按 detail 决定使用固定图片估算或 estimate_original_image_bytes → 出来两个数字:要减掉的原始 payload 字节数和要加回的替代估算字节数。

调用关系:它被 estimate_response_item_model_visible_bytes 调用,专门修正图片导致的大小估算偏差。

调用图:被 1 处调用(estimate_response_item_model_visible_bytes)。

encrypted_function_output_estimate_adjustment659–682 ↗
fn encrypted_function_output_estimate_adjustment(item: &ResponseItem) -> (i64, i64)

作用:修正函数工具输出中加密内容的大小估算。它把加密字符串长度替换成更接近真实内容的估算长度。

数据流:进去一条 ResponseItem → 如果不是 FunctionCallOutput,或主体不是内容项列表,就返回 0 和 0;否则遍历 EncryptedContent 项,累计加密 payload 长度,并用 estimate_encrypted_function_output_length 算替代长度 → 出来两个数字:要减掉的加密字符串长度和要加回的估算长度。

调用关系:它被 estimate_response_item_model_visible_bytes 调用,和图片调整一起让令牌估算更接近实际。

调用图:被 1 处调用(estimate_response_item_model_visible_bytes)。

is_model_generated_item684–703 ↗
fn is_model_generated_item(item: &ResponseItem) -> bool

作用:判断一条历史项是不是模型生成的内容。它用来划分“服务器已经统计过的内容”和“后来本地追加的内容”。

数据流:进去一条 ResponseItem → 它把 assistant 消息、推理、工具调用、搜索调用、压缩项等视为模型生成,把工具输出、代理消息和其它项视为非模型生成 → 出来一个布尔值。

调用关系:items_after_last_model_generated_item 用它从后往前找最后一个模型生成项,从而决定哪些后续项目需要额外估算。

is_user_turn_boundary705–715 ↗
fn is_user_turn_boundary(item: &ResponseItem) -> bool

作用:判断一条历史项是不是一个新指令轮次的边界。普通用户消息算边界,代理消息也算;某些助手消息如果其实是代理间指令,也算边界。

数据流:进去一条 ResponseItem → 如果是 AgentMessage,直接返回 true;如果是 Message,就检查角色和内容:非上下文型 user 消息算边界,assistant 的代理间指令内容也算边界 → 出来一个布尔值。

调用关系:它被 user_message_positions 调用,也间接影响 drop_last_n_user_turns 的回滚切点。它会调用 is_inter_agent_instruction_content 和 is_contextual_user_message_content 来区分普通对话和上下文更新。

调用图:调用 2 个内部函数(is_inter_agent_instruction_content, is_contextual_user_message_content);被 1 处调用(user_message_positions);外部调用 1 个(matches!)。

is_inter_agent_instruction_content717–719 ↗
fn is_inter_agent_instruction_content(content: &[ContentItem]) -> bool

作用:判断一段消息内容是不是代理之间传递的指令。代理可以理解成系统里的另一个自动助手。

数据流:进去一组 ContentItem → 它调用 InterAgentCommunication::is_message_content 做格式判断 → 出来 true 或 false。

调用关系:它被 is_user_turn_boundary 调用,用来把某些 assistant 消息识别成“新指令轮次”的边界。

调用图:调用 1 个内部函数(is_message_content);被 1 处调用(is_user_turn_boundary)。

user_message_positions721–729 ↗
fn user_message_positions(items: &[ResponseItem]) -> Vec<usize>

作用:找出历史里所有用户轮次边界的位置。回滚最近几轮对话时,需要先知道这些切点在哪里。

数据流:进去一段 ResponseItem 切片 → 它从头到尾遍历,每遇到 is_user_turn_boundary 为 true 的项目,就把下标放进 positions → 出来一个位置列表。

调用关系:它被 drop_last_n_user_turns 调用,是回滚算法寻找切断点的第一步。

调用图:调用 1 个内部函数(is_user_turn_boundary);被 1 处调用(drop_last_n_user_turns);外部调用 2 个(new, iter)。

上下文消息过滤

此文件在提示词组装使用内容之前,将结构化内部上下文片段与普通用户可见内容分离。

core/src/context/contextual_user_message.rs源码 ↗
domain_logic对话消息整理和发送模型前

在对话里,表面上很多内容都像“用户消息”,但其中一部分其实是程序为了让模型更懂当前情况而附加的上下文。比如用户的固定指令、当前运行环境、某个命令的提醒,或者钩子提示(hook prompt,一种由外部机制插入的提示片段)。这个文件就像一个门卫:看到一段输入文本时,先判断它是不是这些已登记的标准上下文片段;如果不是,再看看它是不是钩子提示片段。它还可以把一整条由多个文本块组成的消息检查一遍:只要里面混进了普通用户文本,就拒绝把它当成钩子提示;只有全部都是允许的上下文或钩子片段,并且至少有一个钩子片段时,才组装成一个 HookPromptItem。这样可以保证内部提示不会和用户真实发言混在一起,后续显示、解析和发送给模型时都更安全、更清楚。

函数细节3
is_standard_contextual_user_text60–64 ↗
fn is_standard_contextual_user_text(text: &str) -> bool

作用:判断一段文字是不是系统认识的“标准上下文片段”。这些片段虽然放在用户消息的位置上,但本质上是程序给模型看的背景说明,不是用户临时输入的话。

数据流:进去的是一段文本。它会拿这段文本依次去问已经登记好的各种上下文类型,比如用户指令、环境信息、技能说明、命令提示等,看有没有一种能认出它。只要有一种认出,就返回 true;都认不出,就返回 false;它不改动任何数据。

调用关系:它是本文件里的基础判断工具。is_contextual_user_fragment 用它来判断单个内容块是不是上下文;parse_visible_hook_prompt_message 用它来在解析钩子提示时跳过那些允许出现的标准上下文片段。

调用图:被 2 处调用(is_contextual_user_fragment, parse_visible_hook_prompt_message)。

is_contextual_user_fragment66–71 ↗
fn is_contextual_user_fragment(content_item: &ContentItem) -> bool

作用:判断一个内容块是不是“上下文化的用户片段”。简单说,就是看它是不是内部提示、环境说明、钩子提示这类特殊内容,而不是普通用户发言。

数据流:进去的是一个 ContentItem,也就是一块消息内容。它先要求这块内容必须是输入文本;如果不是文本,直接返回 false。是文本的话,它先尝试按钩子提示片段解析;解析成功就说明是特殊片段。否则再交给 is_standard_contextual_user_text,看是不是标准上下文文本。最后输出 true 或 false,不修改原内容。

调用关系:它通常会被上层消息处理流程用来快速筛选单个消息块。它把具体识别工作交给 parse_hook_prompt_fragment 和 is_standard_contextual_user_text,相当于把“钩子提示”和“标准上下文”两条识别路线合并成一个统一入口。

调用图:调用 2 个内部函数(is_standard_contextual_user_text, parse_hook_prompt_fragment)。

parse_visible_hook_prompt_message73–98 ↗
fn parse_visible_hook_prompt_message(
    id: Option<&String>,
    content: &[ContentItem],
) -> Option<HookPromptItem>

作用:尝试把一整条可见消息解析成 HookPromptItem。它只接受由钩子提示片段和允许的上下文片段组成的消息,防止普通用户文字被误包装成钩子提示。

数据流:进去的是一个可选的消息 id,以及一组 ContentItem 内容块。函数会从头到尾检查每一块:如果不是文本,立刻失败返回 None;如果能解析成钩子提示片段,就收集起来;如果只是标准上下文文本,就允许它存在但不收集;如果是普通文本,也立刻失败。检查完后,如果一个钩子片段都没有,也返回 None;否则用收集到的片段和 id 生成一个 HookPromptItem 返回。

调用关系:它用于处理整条消息,而不是单个文本块。过程中它调用 parse_hook_prompt_fragment 识别钩子片段,调用 is_standard_contextual_user_text 放行标准上下文,最后把真正的钩子片段交给 HookPromptItem::from_fragments 组装成后续流程能使用的对象。

调用图:调用 3 个内部函数(is_standard_contextual_user_text, from_fragments, parse_hook_prompt_fragment);外部调用 1 个(new)。

Realtime 提示词输入

这些文件组装用于初始化 realtime 会话的启动上下文和后端提示词文本。

core/src/realtime_context.rs源码 ↗
orchestrationstartup

这个文件做的事很像给新来的同事递一张简短交接单。它会从三处收集信息:当前聊天线程里的最近问答、本地保存的最近会话记录、当前目录和相关目录的简略文件树。然后它把这些内容分成几个章节,比如“Current Thread”“Recent Work”“Machine / Workspace Map”,每章都有字数预算,太长就截短,避免把模型的输入空间塞满。它还会跳过一些没用或很吵的目录,比如 .git、node_modules、target。最后,所有内容会被包在 <startup_context> 标签里,作为后台上下文注入给实时会话。一个重要细节是:传进来的总预算主要用于日志记录,真正控制长度的是各章节自己的预算。

函数细节15
build_realtime_startup_context59–126 ↗
async fn build_realtime_startup_context(
    sess: &Session,
    budget_tokens: usize,
) -> Option<String>

作用:这是主入口,用来组装整份实时启动上下文。实时会话配置生成时会调用它,给模型准备一份不会太长的背景说明。

数据流:进去的是当前 Session 和一个期望的 token 预算;它读取配置里的当前目录、复制当前历史记录、加载最近线程,再分别生成当前线程、最近工作、工作区地图几个部分;最后把可用部分加上说明和标签,出来一个完整字符串。如果什么信息都没有,就返回 None,不注入上下文。

调用关系:它由 build_realtime_session_config 在准备实时会话时调用。它自己像总装工一样,把活儿分给 build_current_thread_section、load_recent_threads、build_recent_work_section、build_workspace_section_with_user_root、format_section 和 format_startup_context_blob。

调用图:调用 6 个内部函数(build_current_thread_section, build_recent_work_section, build_workspace_section_with_user_root, format_section, format_startup_context_blob, load_recent_threads);被 1 处调用(build_realtime_session_config);外部调用 6 个(clone_history, get_config, debug!, home_dir, info!, vec!)。

load_recent_threads128–153 ↗
async fn load_recent_threads(sess: &Session) -> Vec<StoredThread>

作用:这个函数从本地线程仓库里取最近的会话记录。这样模型不只知道当前聊天,还能知道用户最近在别的项目里问过什么。

数据流:进去的是 Session;它访问 Session 里的 thread_store,按更新时间倒序请求最多 40 条未归档线程;成功就拿出线程列表,失败就写一条警告日志并返回空列表。

调用关系:它只在 build_realtime_startup_context 里被调用,是“最近工作”章节的数据来源。它不负责排版,只负责把原始会话记录拿回来。

调用图:被 1 处调用(build_realtime_startup_context);外部调用 2 个(new, warn!)。

build_recent_work_section155–207 ↗
async fn build_recent_work_section(
    cwd: &AbsolutePathBuf,
    recent_threads: &[StoredThread],
) -> Option<String>

作用:这个函数把最近会话整理成“最近工作”章节。它会按项目或目录分组,让读者和模型看出用户最近主要在哪些地方忙。

数据流:进去的是当前工作目录和最近线程列表;它尝试把每条线程的目录归到 Git 项目根目录,找不到就用原目录;然后按“当前项目优先、最近更新时间靠前”的规则排序;最后让 format_thread_group 把每组变成文字。出来的是一段章节文本,或者没有内容时返回 None。

调用关系:它由 build_realtime_startup_context 调用。它自己负责分组和排序,具体每个项目组怎么写,交给 format_thread_group。

调用图:调用 2 个内部函数(format_thread_group, from_absolute_path);被 1 处调用(build_realtime_startup_context);外部调用 3 个(new, new, resolve_root_git_project_for_trust)。

build_current_thread_section209–310 ↗
fn build_current_thread_section(items: &[ResponseItem]) -> Option<String>

作用:这个函数从当前聊天历史里提取最近几轮用户和助手的对话,做成“当前线程”章节。它的作用是帮助模型接上刚才的话,不要前后不连贯。

数据流:进去的是一串 ResponseItem,也就是聊天历史里的消息项;它跳过系统塞进去的上下文类用户消息,只保留真正的用户发言、助手回复和代理消息;再按轮次整理,从最新一轮往前放,并按每轮预算截短。出来是一段可读的最近对话摘要,或者没有有效对话时返回 None。

调用关系:它由 build_realtime_startup_context 调用。它会用 content_items_to_text 和 plaintext_agent_message_content 把消息内容转成普通文字,用 is_contextual_user_message_content 排除后台上下文,用 truncate_realtime_text_to_token_budget 控制长度。

调用图:调用 5 个内部函数(content_items_to_text, is_contextual_user_message_content, approx_token_count, truncate_realtime_text_to_token_budget, plaintext_agent_message_content);被 1 处调用(build_realtime_startup_context);外部调用 5 个(new, new, format!, take, vec!)。

truncate_realtime_text_to_token_budget312–335 ↗
fn truncate_realtime_text_to_token_budget(text: &str, budget_tokens: usize) -> String

作用:这个函数把一段文字压到指定 token 预算以内。token 可以简单理解为模型阅读文字时用的“小块”,预算就是最多允许塞多少块。

数据流:进去的是原文和 token 上限;它先调用通用截断工具 truncate_text,再用 approx_token_count 粗略估算结果长度;如果截断工具加上的省略标记导致还是超了,它就继续收紧预算;出来的是不超过预算的文字,极端情况下可能返回空字符串。

调用关系:它被 build_current_thread_section 和 format_section 用来控制启动上下文长度,也被 realtime_backend_item、realtime_backend_output 用在实时后端输出上。它是这个文件里控制“别说太多”的关键小工具。

调用图:调用 1 个内部函数(approx_token_count);被 4 处调用(build_current_thread_section, format_section, realtime_backend_item, realtime_backend_output);外部调用 3 个(new, truncate_text, Tokens)。

build_workspace_section_with_user_root337–394 ↗
async fn build_workspace_section_with_user_root(
    cwd: &AbsolutePathBuf,
    user_root: Option<PathBuf>,
) -> Option<String>

作用:这个函数生成“机器 / 工作区地图”章节。它告诉模型当前在哪个目录、Git 项目根目录在哪、用户主目录在哪,以及这些目录的浅层文件结构。

数据流:进去的是当前工作目录和可选的用户根目录;它查找 Git 根目录,分别尝试渲染当前目录、Git 根目录、用户根目录的简短目录树;然后把路径信息和目录树拼成文字。出来是一段工作区说明,或者没有任何可展示内容时返回 None。

调用关系:它由 build_realtime_startup_context 调用。它把扫描目录的细活交给 render_tree,并用 resolve_root_git_project_for_trust 判断当前目录属于哪个 Git 项目。

调用图:调用 2 个内部函数(render_tree, as_path);被 1 处调用(build_realtime_startup_context);外部调用 4 个(new, resolve_root_git_project_for_trust, format!, vec!)。

render_tree396–404 ↗
fn render_tree(root: &Path) -> Option<Vec<String>>

作用:这个函数把一个目录变成几行简短的树状列表。它只在目标确实是目录时工作,避免把普通文件当目录扫描。

数据流:进去的是一个路径;它先检查路径是不是目录,是的话创建一个空列表,让 collect_tree_lines 往里填目录树行;最后如果列表不空就返回这些行,否则返回 None。

调用关系:它被 build_workspace_section_with_user_root 调用。它本身只是入口,真正递归读取子目录的是 collect_tree_lines。

调用图:调用 1 个内部函数(collect_tree_lines);被 1 处调用(build_workspace_section_with_user_root);外部调用 2 个(is_dir, new)。

collect_tree_lines406–437 ↗
fn collect_tree_lines(dir: &Path, depth: usize, lines: &mut Vec<String>)

作用:这个函数递归收集目录树的每一行,比如“- src/”“- Cargo.toml”。它限制深度和每层数量,防止扫描太多文件。

数据流:进去的是目录路径、当前深度和一个可追加的行列表;它先检查深度是否超过上限,再读取并排序目录项;每看到一个文件或目录,就追加一行,目录还会继续往下一层扫;如果某层文件太多,就加一行“还有多少项”。它直接修改传入的列表。

调用关系:它由 render_tree 启动,并在遇到子目录时调用自己。它依赖 read_sorted_entries 拿到干净、有顺序的目录项,依赖 file_name_string 取得适合显示的名字。

调用图:调用 2 个内部函数(file_name_string, read_sorted_entries);被 1 处调用(render_tree);外部调用 1 个(format!)。

read_sorted_entries439–457 ↗
fn read_sorted_entries(dir: &Path) -> io::Result<Vec<DirEntry>>

作用:这个函数读取一个目录下值得展示的条目,并排好顺序。它会过滤掉缓存、构建产物、隐藏目录这类容易干扰视线的东西。

数据流:进去的是目录路径;它调用系统读取目录,丢掉读取失败的单个条目,再用 is_noisy_name 过滤噪音名字;之后排序,让目录排在文件前面,同类再按名字排;出来是排序后的 DirEntry 列表,读取目录本身失败则返回错误。

调用关系:它被 collect_tree_lines 调用,是目录树扫描的“取货员”。它不决定怎么显示,只提供已经过滤和排好序的条目。

调用图:被 1 处调用(collect_tree_lines);外部调用 1 个(read_dir)。

is_noisy_name459–462 ↗
fn is_noisy_name(name: &OsStr) -> bool

作用:这个函数判断一个文件名是不是“噪音”。噪音指的是通常不该塞进上下文里的目录或文件名,比如隐藏文件、node_modules、target。

数据流:进去的是系统文件名 OsStr;它把名字转成可比较的文字,检查是否以点开头,或是否在预设噪音名单里;出来是 true 或 false。

调用关系:它在读取目录项时被用来过滤内容,配合 read_sorted_entries 让工作区地图更清爽。

调用图:外部调用 2 个(starts_with, to_string_lossy)。

format_section464–483 ↗
fn format_section(title: &str, body: Option<String>, budget_tokens: usize) -> Option<String>

作用:这个函数把一段内容包装成带标题的章节,并确保章节正文不会超过给定预算。它让整份启动上下文结构清楚、长度可控。

数据流:进去的是标题、可选正文和 token 预算;如果正文没有内容就直接返回 None;它先生成“## 标题”,扣掉标题占用的预算,再截短正文;最后出来一段完整章节文本。

调用关系:它由 build_realtime_startup_context 多次调用,分别包装当前线程、最近工作、工作区地图和备注。它把截短工作交给 truncate_realtime_text_to_token_budget。

调用图:调用 2 个内部函数(approx_token_count, truncate_realtime_text_to_token_budget);被 1 处调用(build_realtime_startup_context);外部调用 1 个(format!)。

format_startup_context_blob485–487 ↗
fn format_startup_context_blob(body: &str) -> String

作用:这个函数给整份启动上下文加上固定的开始和结束标签。标签像信封,告诉后面的处理流程:这里面是一块启动上下文。

数据流:进去的是已经拼好的正文;它在前面加 <startup_context>,后面加 </startup_context>;出来的是最终可注入的字符串。

调用关系:它由 build_realtime_startup_context 在最后一步调用。前面各章节都准备好后,它负责把它们封装成统一格式。

调用图:被 1 处调用(build_realtime_startup_context);外部调用 1 个(format!)。

format_thread_group489–562 ↗
async fn format_thread_group(
    current_group: &Path,
    group: &Path,
    entries: Vec<&StoredThread>,
) -> Option<String>

作用:这个函数把同一个项目或目录下的最近会话写成一小段。它会列出最近活动时间、会话数量、分支名,以及用户最近问过的问题。

数据流:进去的是当前项目组路径、某个组路径和这个组里的线程列表;它用第一条也就是最新线程确定标题和时间,尽量判断这是 Git 仓库还是普通目录;然后收集每条线程的第一条用户问题,去重、压缩空白、过长截短,并限制数量。出来是一段项目组文本;如果没有足够问题内容,就返回 None。

调用关系:它由 build_recent_work_section 调用。build_recent_work_section 负责决定哪些组要展示,它负责把单个组讲清楚。它也会调用 resolve_root_git_project_for_trust 来辅助判断标题该写 Git repo 还是 Directory。

调用图:调用 1 个内部函数(from_absolute_path);被 1 处调用(build_recent_work_section);外部调用 5 个(new, new, resolve_root_git_project_for_trust, format!, vec!)。

file_name_string564–569 ↗
fn file_name_string(path: &Path) -> String

作用:这个函数把路径最后一段变成适合显示的文字。比如 /home/me/project 会显示成 project;如果取不到名字,就退回显示完整路径。

数据流:进去的是一个路径;它尝试取文件名并转成普通字符串;成功就返回文件名,失败就返回路径的显示形式。

调用关系:它被 collect_tree_lines 用来生成目录树里的名字,也被工作区说明用来显示当前目录名和 Git 项目名。它是一个小的显示辅助函数。

调用图:被 1 处调用(collect_tree_lines);外部调用 1 个(file_name)。

approx_token_count571–573 ↗
fn approx_token_count(text: &str) -> usize

作用:这个函数粗略估算一段文字会占多少 token。它不追求完全精确,只用来快速控制上下文别太长。

数据流:进去的是文字;它按“大约 4 个字节算 1 个 token”的规则,用文本长度向上取整;出来是一个估算数量。

调用关系:它被 build_current_thread_section、format_section 和 truncate_realtime_text_to_token_budget 调用。它是各处预算判断的尺子。

调用图:被 3 处调用(build_current_thread_section, format_section, truncate_realtime_text_to_token_budget)。

core/src/realtime_prompt.rs源码 ↗
domain_logicrealtime session setup

可以把这个文件想成“开场白选择器”。每次要建立实时会话时,后端需要一段提示词来告诉 AI 应该扮演什么角色、怎么和用户互动。这个文件先看配置里有没有明确写好的提示词;如果有,而且不是空白,就优先用它。没有配置时,再看本次请求有没有带提示词;请求明确给了空字符串,也会被尊重,意思是“不要提示词”。如果前面都没有,它就使用内置的 BACKEND_PROMPT 默认模板,并把里面的“用户名占位符”换成当前系统用户的名字。取名字时,它会先试真实姓名,再试登录用户名,最后实在没有就用 there。文件里还带了几组测试,确保这些优先级和默认替换行为不会被以后改坏。

函数细节6
prepare_realtime_backend_prompt5–24 ↗
fn prepare_realtime_backend_prompt(
    prompt: Option<Option<String>>,
    config_prompt: Option<String>,
) -> String

作用:这个函数负责产出最终要发给实时后端的提示词。它解决的是“多个地方都可能提供提示词,到底该听谁的”这个问题。

数据流:进去的是两个可选信息:请求里的 prompt,以及配置里的 config_prompt。它先检查配置提示词,只要配置存在且不是纯空白,就直接返回;否则再检查请求提示词,请求给了文字就返回文字,请求明确表示没有内容就返回空字符串;如果两边都没给,它拿内置默认模板,去掉末尾多余空白,再把用户名占位符替换成 current_user_first_name 算出的名字,最后返回这段完整提示词。

调用关系:它会在 build_realtime_session_config 准备实时会话配置时被调用,是生成会话配置前的一个关键小步骤。当需要默认提示词时,它会把“当前用户叫什么”这件事交给 current_user_first_name;测试 prepare_realtime_backend_prompt_renders_default 也会调用它来确认默认模板真的被正确渲染。

调用图:调用 1 个内部函数(current_user_first_name);被 2 处调用(build_realtime_session_config, prepare_realtime_backend_prompt_renders_default);外部调用 1 个(new)。

current_user_first_name26–32 ↗
fn current_user_first_name() -> String

作用:这个函数尽量找出当前电脑用户的名字里的第一个词,用来让默认提示词听起来更自然。比如真实姓名是“Jane Doe”,它会取“Jane”。

数据流:它读取系统提供的真实姓名和用户名,依次拆开每个名字,只拿第一个非空的词。只要找到一个可用名字,就返回它;如果真实姓名和用户名都拿不到合适内容,就返回默认的 there。

调用关系:它只在 prepare_realtime_backend_prompt 需要渲染默认提示词时出场。prepare_realtime_backend_prompt 负责决定要不要用默认模板,而它负责提供模板里要填进去的用户名字。

调用图:被 1 处调用(prepare_realtime_backend_prompt);外部调用 2 个(realname, username)。

tests::prepare_realtime_backend_prompt_prefers_config_override39–47 ↗
fn prepare_realtime_backend_prompt_prefers_config_override()

作用:这个测试确认:如果配置里写了提示词,就算请求里也带了另一段提示词,最终也必须用配置里的那段。

数据流:它构造一份请求提示词和一份配置提示词,调用 prepare_realtime_backend_prompt,然后用断言检查返回结果是不是“prompt from config”。它不改动外部状态,只验证返回值。

调用关系:这是给 prepare_realtime_backend_prompt 的优先级规则加保险。它不参与正式运行,只在测试时运行,防止以后有人误把请求提示词排到配置提示词前面。

调用图:外部调用 1 个(assert_eq!)。

tests::prepare_realtime_backend_prompt_uses_request_prompt50–58 ↗
fn prepare_realtime_backend_prompt_uses_request_prompt()

作用:这个测试确认:没有配置提示词时,请求里带来的提示词会被使用。

数据流:它传入一段请求提示词,并把配置提示词设为没有。函数返回后,它检查结果是否等于请求里的“prompt from request”。

调用关系:它测试的是 prepare_realtime_backend_prompt 的第二层选择逻辑:配置缺席时才轮到请求内容。这个测试只在测试流程中运行。

调用图:外部调用 1 个(assert_eq!)。

tests::prepare_realtime_backend_prompt_preserves_empty_request_prompt61–70 ↗
fn prepare_realtime_backend_prompt_preserves_empty_request_prompt()

作用:这个测试确认:请求如果明确给了空提示词,系统会尊重这个决定,而不是偷偷换成默认提示词。

数据流:它分别传入空字符串请求提示词,以及请求明确为 None 的情况,同时没有配置提示词。每次调用 prepare_realtime_backend_prompt 后,都检查返回结果是不是空字符串。

调用关系:它保护一个容易被误改的细节:空提示词不是“没提供”,而是“明确要空”。这个测试确保 prepare_realtime_backend_prompt 能区分这两种意思。

调用图:外部调用 1 个(assert_eq!)。

tests::prepare_realtime_backend_prompt_renders_default73–81 ↗
fn prepare_realtime_backend_prompt_renders_default()

作用:这个测试确认:配置和请求都没有提示词时,系统会生成内置默认提示词,并且会把用户名占位符替换掉。

数据流:它在不传请求提示词、也不传配置提示词的情况下调用 prepare_realtime_backend_prompt,拿到默认提示词。然后检查这段文字是否以预期标题开头、是否包含 Codex 身份说明、是否包含用户名字说明,并确认原始占位符已经不存在。

调用关系:它覆盖 prepare_realtime_backend_prompt 的最后兜底路径,也会间接触发 current_user_first_name。它在测试阶段运行,用来保证默认提示词模板不是原样漏给后端。

调用图:调用 1 个内部函数(prepare_realtime_backend_prompt);外部调用 1 个(assert!)。

提示词检查和专用历史

这些文件构建提示词可见输入,用于调试以及精简的独立网页搜索对话路径。

core/src/prompt_debug.rs源码 ↗
orchestrationdebug request handling

平时系统和模型对话前,会先把用户的话、历史记录、系统指令、可用工具等内容整理成一包“模型输入”。这个文件就是为了调试这一步。它不会真的跑完整个对话,而是临时创建一个会话,把配置改成一次性的 ephemeral(临时模式,不当成长期会话保存),准备认证、运行环境、线程管理器等必要零件,再把用户输入塞进会话历史。随后它按正常流程生成工具列表、基础指令和历史上下文,调用构建提示词的逻辑,最后只返回 prompt.input,也就是模型可见的输入清单。用生活类比,它像餐厅出餐前的“验菜单窗口”:不负责吃饭,只让你看到厨房最终递给服务员的那张单子。重要的是,它最后会关闭并移除临时线程,避免调试留下后台资源。

函数细节2
build_prompt_input26–72 ↗
async fn build_prompt_input(
    mut config: Config,
    input: Vec<UserInput>,
    state_db: Option<StateDbHandle>,
    user_instructions_provider: Arc<dyn UserInstructionsProvider>,
) -> CodexResult

作用:这个函数是调试入口。调用者给它配置、用户输入和一些外部依赖,它会临时启动一条会话线程,然后返回这次输入最终会变成哪些模型可见内容。

数据流:进去的是一份配置、用户输入列表、可选的状态数据库句柄,以及用户指令提供器。它先把配置标记成临时模式,再准备认证管理器、执行服务器路径、线程存储、安装编号和环境管理器,然后创建 ThreadManager(一套开关和管理会话线程的机器)。接着它启动临时线程,把真正生成 prompt input 的工作交给 build_prompt_input_from_session。拿到结果后,它关闭线程、等待收尾、从管理器里移除线程,最后把模型输入列表返回;如果关闭或准备过程出错,就返回错误。

调用关系:它是外部调试代码最可能直接调用的函数。它自己不细抠提示词内容,而是先搭好完整但临时的运行现场:调用 thread_store_from_config 准备存储,调用 AuthManager 和 EnvironmentManager 相关函数准备认证和执行环境,调用 ThreadManager::new 创建线程管理器。等会话可用后,它把核心工作交给 build_prompt_input_from_session,最后负责清理现场,像临时摄影棚用完后拆灯、关电、退场。

调用图:调用 6 个内部函数(build_prompt_input_from_session, new, thread_store_from_config, from_codex_home, from_optional_paths, shared_from_config);外部调用 4 个(clone, new, empty_extension_registry, resolve_installation_id)。

build_prompt_input_from_session74–102 ↗
async fn build_prompt_input_from_session(
    sess: &Session,
    input: Vec<UserInput>,
) -> CodexResult<Vec<ResponseItem>>

作用:这个函数拿一个已经存在的会话,按正常对话流程整理出模型会看到的 input。它适合在会话环境已经搭好后,单独检查“这一轮会喂给模型什么”。

数据流:进去的是一个 Session(一次对话会话的上下文)和用户输入列表。它先创建默认的新一轮对话上下文,记录上下文更新,并设置当前参考上下文;如果用户输入不为空,就把用户输入转成一条对话记录并写入历史。然后它复制会话历史,并按当前模型支持的输入形式筛选成 prompt 输入。接着它生成本轮可用工具,读取基础指令,调用 build_prompt 把历史、工具、轮次上下文和基础指令合成完整提示词。最后只取出 prompt.input 返回,不直接发送给模型。

调用关系:它由 build_prompt_input 在临时线程启动后调用,是这个文件里真正“拆开提示词看看”的核心步骤。它会调用会话上的 new_default_turn、record_context_updates_and_set_reference_context_item、record_conversation_items、clone_history、get_base_instructions 等方法来准备上下文;再调用 built_tools 准备工具说明,最后交给 build_prompt 统一拼装。它位于调试入口和正式提示词构建逻辑之间,像一段安全的旁路检查通道。

调用图:调用 2 个内部函数(build_prompt, built_tools);被 1 处调用(build_prompt_input);外部调用 8 个(new, clone_history, get_base_instructions, new_default_turn, record_context_updates_and_set_reference_context_item, record_conversation_items, response_item_from_user_input, from_ref)。

ext/web-search/src/history.rs源码 ↗
domain_logicrequest handling

网页搜索需要一点上下文,才知道用户现在问的“它”“刚才那个”指什么。但如果把整段对话都塞进去,又会太长、太乱,还可能把系统提示、工具调用、图片等搜索用不上的东西带进去。这个文件做的事就像给搜索员递一张小纸条:只保留最近两个真正的用户文字消息,以及夹在它们附近的助手文字回复;助手内容最多留约 1000 个 token(可以粗略理解成模型眼里的“字数预算”)。它会跳过系统消息、开发者消息、工具调用等不可见内容;用户消息里也只拿文字,不拿图片。还有一种“AgentMessage”会被转成普通助手文字消息,方便搜索模块统一读取。最后,如果筛完还有内容,就包装成 SearchInput 交给网页搜索使用。

函数细节6
recent_input18–27 ↗
fn recent_input(items: &[ResponseItem]) -> Option<SearchInput>

作用:为一次网页搜索整理最近的对话上下文。调用者给它一串历史消息,它会挑出搜索真正需要看的那一小段,而不是把整段聊天都带上。

数据流:进去的是一组 ResponseItem,也就是系统已经记录下来的各种对话项目。它先逐条交给 push_visible_message 过滤成“搜索能看懂的可见文字消息”,再只保留最近两个用户消息相关的尾部内容,并把助手回复截到 1000 个 token 以内。出来的是 Some(SearchInput::Items(...)),里面装着筛好的消息;如果筛完一条都没有,就返回 None,不制造空搜索上下文。

调用关系:它是 handle_call 发起网页搜索前会用到的准备步骤。它自己不判断每种消息细节,而是把单条消息的清洗工作交给 push_visible_message;随后再调用通用工具 retain_tail_from_last_n_user_messages 和 truncate_assistant_output_text_to_token_budget,分别负责“只留最近几轮”和“别让助手文字太长”。

调用图:调用 1 个内部函数(push_visible_message);被 1 处调用(handle_call);外部调用 4 个(new, Items, retain_tail_from_last_n_user_messages, truncate_assistant_output_text_to_token_budget)。

push_visible_message29–78 ↗
fn push_visible_message(messages: &mut Vec<ResponseItem>, item: &ResponseItem)

作用:判断一条历史项目能不能放进搜索上下文,并把它整理成统一的文字消息格式。它的目标是:只留下用户和助手真正说出来的文字,过滤掉搜索不该看的杂项。

数据流:进去的是当前已经整理好的 messages 列表,以及一条新的 ResponseItem。它会看这条项目是什么:普通助手消息直接复制进去;AgentMessage 如果能提取出纯文字,就改写成一条助手消息,并标明来自哪个 agent;普通用户消息只有在确实是用户正文时才保留,而且只保留 InputText 文字内容,图片等会被丢掉。出来没有单独返回值,而是可能把一条清洗后的消息追加到 messages 里;不合适的项目就什么也不改。

调用关系:它只被 recent_input 使用,是 recent_input 的“筛子”。recent_input 负责整体流程,这个函数负责单条消息怎么取舍;其中 plaintext_agent_message_content 用来从 agent 消息里拿纯文本,parse_turn_item 和匹配判断用来避免把环境上下文之类伪装成用户消息的内容算作真正用户发言。

调用图:调用 1 个内部函数(plaintext_agent_message_content);被 1 处调用(recent_input);外部调用 3 个(matches!, clone, vec!)。

tests::message91–107 ↗
fn message(role: &str, text: &str) -> ResponseItem

作用:这是测试里的小帮手,用来快速造一条用户或助手消息。它让测试代码不用每次手写一大坨 ResponseItem 结构。

数据流:进去的是角色名和文字内容。它根据角色是不是 assistant,决定把文字放成 OutputText(助手输出文字)还是 InputText(用户输入文字),再包成一条 ResponseItem::Message 返回。它不会改动外部状态。

调用关系:它服务于本文件下面的测试用例。几个测试会用它拼出一段假聊天记录,然后调用 recent_input,最后用 assert_eq! 检查筛出来的上下文是不是符合预期。

调用图:外部调用 1 个(vec!)。

tests::keeps_current_user_and_previous_visible_turn110–138 ↗
fn keeps_current_user_and_previous_visible_turn()

作用:这个测试确认:搜索上下文会保留当前用户问题,以及上一轮真正可见的用户和助手对话,同时丢掉系统消息、工具调用、开发者消息等杂项。

数据流:进去的是测试自己构造的一长串假历史,里面混有 system、user、assistant、function call、developer 等不同项目。测试把这串历史交给 recent_input,然后拿结果和期望的三条消息比较:previous user、previous assistant、current user。结果如果不一致,测试就失败。

调用关系:它由测试运行器执行,用来守住 recent_input 的核心行为。它间接覆盖 push_visible_message 的过滤规则,也验证 retain_tail_from_last_n_user_messages 之后只留下最近两条用户消息相关的尾部内容。

调用图:外部调用 2 个(assert_eq!, vec!)。

tests::keeps_only_text_from_recent_user_messages141–171 ↗
fn keeps_only_text_from_recent_user_messages()

作用:这个测试确认:用户消息里如果同时有文字和图片,搜索上下文只拿文字,不会把图片内容塞进去。

数据流:进去的是一段假历史,其中上一条用户消息包含 InputText 和 InputImage。测试调用 recent_input 后,期望结果里只剩用户文字、助手文字和当前用户文字,图片被去掉。它通过 assert_eq! 判断实际结果是否正好如此。

调用关系:它由测试运行器执行,重点检查 push_visible_message 对用户内容的清洗。这个行为很重要,因为网页搜索通常需要文本查询,图片数据放进去既没用又会让请求变复杂。

调用图:外部调用 2 个(assert_eq!, vec!)。

tests::ignores_contextual_user_messages_when_selecting_recent_turns174–193 ↗
fn ignores_contextual_user_messages_when_selecting_recent_turns()

作用:这个测试确认:像环境信息这样的上下文文本,即使表面上是 user 角色,也不会被当成真正的用户提问来计算“最近两条用户消息”。

数据流:进去的是一段假历史:先有正常用户和助手对话,中间夹了一条 environment_context 形式的用户消息,最后是当前用户消息。测试把它交给 recent_input,期望结果仍然保留 previous user、previous assistant、current user,而不是被环境上下文挤掉。比较不一致就失败。

调用关系:它由测试运行器执行,验证 push_visible_message 里借助 parse_turn_item 做的识别是否生效。这个测试防止搜索上下文被运行环境信息误导,保证 recent_input 选的是人真正说的话。

调用图:外部调用 2 个(assert_eq!, vec!)。