Codex 系统手册

模型请求构造、提示词组装与运行时模型选择套件

stage-23.2.4.317 个文件

这一阶段是在真正调用模型前,给模型“装信封、选收件人”。它检查用户的话、项目说明、AGENTS.md、技能、额外上下文、权限、人格、协作模式等有没有按正确顺序放进请求里,也盯着版面、缓存、token 余量和 JSON 格式要求别出错。另一部分负责按配置选择远程模型、切换模型、打开网页搜索等工具,并确保自动审查用专门模型。整体是幕后把关,避免模型拿错说明或用错能力。

本阶段的文件17

上下文与指令注入

这些测试覆盖补充指令和从仓库派生的上下文如何在各轮次和线程生命周期中被发现、合并并注入模型可见的提示。

core/tests/suite/additional_context.rs源码 ↗
testtest run

这份测试文件检查一个很关键的小规则:用户真正输入的话,和系统额外带给模型看的背景信息,必须分清楚。比如用户说“看看当前标签页”,程序还可能附带“当前浏览器标签页内容”或“自动化运行信息”。这些额外内容要让模型看见,但不能把它们当成用户亲口说的话。文件会启动一个假的服务器,模拟模型接口返回结果,然后向 Codex 提交用户输入和 additional_context。测试再检查实际发出的请求:不可信的外部信息会放到 user 角色里,并用 external 标签包起来;应用自己提供的信息会放到 developer 角色里;相同上下文不会在连续轮次里无意义重复;重新出现的上下文会再次补上;特别长的上下文会先截断,避免把模型输入塞得过大。

函数细节6
additional_context_is_model_visible_but_not_a_user_message_item24–115 ↗
async fn additional_context_is_model_visible_but_not_a_user_message_item() -> Result<()>

作用:这个测试确认额外上下文会进入模型请求,让模型能看到,但不会混进“用户消息记录”里。也就是说,背景资料不是用户亲口输入的内容。

数据流:测试先启动假服务器,并准备一次模拟的模型响应。然后它提交一条用户文本“inspect the active tab”,同时附带两份额外上下文:一个不可信的浏览器信息,一个应用级自动化信息。之后它读取 Codex 发出的事件,确认完成的用户消息只包含用户原文。最后它检查实际请求:自动化信息进了 developer 消息,浏览器信息进了 user 消息但带 external 包装,用户原文仍单独存在。

调用关系:它通过 start_mock_server、mount_sse_once 和 sse 搭好假模型服务,用 test_codex 创建被测的 Codex 实例,再用 wait_for_event_match 等到内部事件出现。最后用快照和断言检查请求内容,证明额外上下文插入到了模型输入里,但没有污染用户消息事件。

调用图:调用 4 个内部函数(mount_sse_once, sse, start_mock_server, test_codex);外部调用 7 个(from, default, assert_eq!, wait_for_event_match, assert_snapshot!, skip_if_no_network!, vec!)。

external_context_like_user_text_remains_a_user_message_item118–164 ↗
async fn external_context_like_user_text_remains_a_user_message_item() -> Result<()>

作用:这个测试防止一个误判:如果用户自己输入了看起来像外部上下文标签的文字,它仍然必须被当成普通用户输入。不能因为文本长得像标签,就把它改成系统附加信息。

数据流:测试准备假服务器和 Codex,然后提交一条用户文本“<external_api>”,但不附带任何 additional_context。它等待用户消息完成事件,检查事件里的内容仍是这条原始文本。之后再查看发给模型的请求,确认 user 消息里也只有“<external_api>”本身,没有被额外包装、移动或过滤。

调用关系:它和其他测试一样先用 start_mock_server、mount_sse_once、sse 搭建模拟接口,再用 test_codex 运行一次用户输入流程。它主要补上边界情况:额外上下文的识别不能只靠文字长相,而要靠 Op::UserInput 里 additional_context 这个明确字段。

调用图:调用 4 个内部函数(mount_sse_once, sse, start_mock_server, test_codex);外部调用 7 个(new, default, new, assert_eq!, wait_for_event_match, skip_if_no_network!, vec!)。

additional_context_trust_controls_message_role167–232 ↗
async fn additional_context_trust_controls_message_role() -> Result<()>

作用:这个测试确认额外上下文的“可信类型”会决定它被放到哪种消息角色里。简单说,应用自己生成的资料和外部不可信资料,给模型看时要分开放。

数据流:测试提交一条“inspect context”的用户输入,并附带两份上下文:browser_info 标成 Untrusted,也就是不可信外部来源;automation_info 标成 Application,也就是应用自己提供。流程跑完后,它检查实际请求:automation_info 被包成 <automation_info> 放在 developer 消息里;browser_info 被包成 <external_browser_info> 放在 user 消息里;用户文本跟在后面。

调用关系:它使用假服务器接住 Codex 发出的模型请求,并等到 TurnComplete 表示这一轮结束。这个测试专门验证 AdditionalContextKind 这个分类在请求组装阶段生效,确保不同信任级别不会混到同一个位置。

调用图:调用 4 个内部函数(mount_sse_once, sse, start_mock_server, test_codex);外部调用 6 个(from, default, assert_eq!, wait_for_event_match, skip_if_no_network!, vec!)。

additional_context_is_deduplicated_between_turns_while_retained235–312 ↗
async fn additional_context_is_deduplicated_between_turns_while_retained() -> Result<()>

作用:这个测试确认同一份额外上下文跨多轮对话时会被保留,但不会每轮都重复插一遍。它避免模型请求里出现一堆重复背景资料。

数据流:测试准备两次模型请求。第一轮提交“first turn”,附带 browser_info 为“same tab”。第二轮提交“second turn”,再次附带完全相同的 browser_info。检查结果时,第一轮请求包含浏览器上下文和第一句话;第二轮请求包含第一轮留下的浏览器上下文、第一句话和第二句话,但没有在第二句话前再次插入同一份浏览器上下文。

调用关系:它连续调用 Codex 的 submit 两次,并分别等待每轮 TurnComplete。mount_sse_once 为两轮各准备一次模拟响应。这个测试说明请求构建器会记住历史上下文,同时做去重,既不丢信息,也不浪费模型输入空间。

调用图:调用 4 个内部函数(mount_sse_once, sse, start_mock_server, test_codex);外部调用 6 个(from, default, assert_eq!, wait_for_event_match, skip_if_no_network!, vec!)。

additional_context_removes_one_value_while_adding_another315–474 ↗
async fn additional_context_removes_one_value_while_adding_another() -> Result<()>

作用:这个测试检查额外上下文在多轮里发生变化时,系统怎么处理“有的消失、有的新增、有的又回来”。它保证上下文历史既稳定,又能反映新变化。

数据流:测试跑三轮。第一轮带 automation_info 和 browser_info;第二轮仍带 automation_info,但不再带 browser_info,新增 terminal_info;第三轮三者都带上。结果显示:第一轮记录两个初始上下文;第二轮不会重复 automation,也不会重新插入已消失的 browser,但会加入新的 terminal;第三轮因为 browser 在上一轮缺席后又出现,所以会再次插入 browser,再接上第三句用户输入。

调用关系:它用三次 mount_sse_once 准备三轮假响应,用三次 submit 推动同一个 Codex 会话前进。这个测试覆盖的是更真实的场景:外部环境会变,系统需要按“上次已经说过什么”和“这次又出现什么”来决定是否把上下文再次发给模型。

调用图:调用 4 个内部函数(mount_sse_once, sse, start_mock_server, test_codex);外部调用 6 个(from, default, assert_eq!, wait_for_event_match, skip_if_no_network!, vec!)。

additional_context_values_are_truncated_before_model_input477–572 ↗
async fn additional_context_values_are_truncated_before_model_input() -> Result<()>

作用:这个测试确认特别长的额外上下文在发给模型前会被截短。这样可以避免浏览器内容或自动化日志太大,挤占模型输入空间,甚至导致请求失败。

数据流:测试构造两段大约四万字符的长文本:一段应用级 automation_info,一段不可信 browser_info。提交后,它检查实际请求里的两段上下文都保留了开头和结尾,中间出现“tokens truncated”提示,长度明显小于原文,并且不超过测试设定的约 5KB 上限。用户自己的“summarize context”仍然完整保留。

调用关系:它同样通过假服务器捕获 Codex 最终发出的模型请求。和其他测试不同,它重点检查大小控制:上下文包装、角色分配之后,还必须经过截断保护,再进入模型输入。assert 和 assert_eq 用来确认截断痕迹、首尾保留和长度上限都符合预期。

调用图:调用 4 个内部函数(mount_sse_once, sse, start_mock_server, test_codex);外部调用 9 个(from, default, assert!, assert_eq!, wait_for_event_match, format!, panic!, skip_if_no_network!, vec!)。

core/tests/suite/agents_md.rs源码 ↗
testtest run

AGENTS.md 可以理解成项目给 AI 助手贴的一张“工作须知”。这个测试文件就是反复模拟各种真实场景,确认这张须知会被正确送进模型请求里。它会造临时目录、写全局说明和项目说明、启动假的模型服务器,然后检查发给模型的请求正文。测试覆盖很多容易出错的地方:AGENTS.override.md 要优先于 AGENTS.md;多个目录里的说明要从项目根目录到当前目录依次拼好;符号链接目录要按用户看到的路径找父目录;没有主环境时只能加载全局说明;多环境时每个环境的项目说明都要出现。它还特别检查“快照”行为:线程创建时读到的说明会被固定下来,之后文件改了,普通对话、冷恢复、fork 和子 agent 仍应复用历史里那份已渲染的说明,而不是偷偷换成新内容。

函数细节22
agents_instructions53–70 ↗
async fn agents_instructions(mut builder: TestCodexBuilder) -> Result<String>

作用:搭一个最小的测试对话,取出模型请求里那段 AGENTS.md 说明文本。其他测试用它来少写重复的假服务器和提交对话代码。

数据流:进去的是一个已经配置好的测试构建器 → 它启动假模型服务器,挂上一段会成功结束的流式响应,创建测试实例并提交一句“hello” → 出来的是请求中以“# AGENTS.md instructions”开头的那段说明文本;如果没找到就报错。

调用关系:它是几个基础场景测试的共用帮手。agents_override_is_preferred_over_agents_md、configured_fallback_is_used_when_agents_candidate_is_directory 和 agents_docs_are_concatenated_from_project_root_to_cwd 先安排好文件布局,再把构建器交给它,由它跑完一次真实请求并把可检查的说明片段带回来。

调用图:调用 4 个内部函数(mount_sse_once, sse, start_mock_server, build_with_remote_env);被 3 处调用(agents_docs_are_concatenated_from_project_root_to_cwd, agents_override_is_preferred_over_agents_md, configured_fallback_is_used_when_agents_candidate_is_directory);外部调用 1 个(vec!)。

write_global_file72–80 ↗
fn write_global_file(
    home: &TempDir,
    filename: &str,
    contents: impl AsRef<[u8]>,
) -> Result<AbsolutePathBuf>

作用:在临时 home 目录里写一个全局说明文件,并返回它的绝对路径。这样测试可以清楚知道系统应该报告哪个说明来源。

数据流:进去的是临时 home 目录、文件名和文件内容 → 它把内容写到 home/文件名 → 出来的是这个文件的绝对路径;磁盘上也真的多了或改了这个文件。

调用关系:很多测试用它准备全局 AGENTS.md 或 AGENTS.override.md。后面的冷恢复、fork、多环境、乱码和子 agent 测试都靠它制造“旧说明”和“新覆盖说明”,再观察系统是否按预期固定或更新来源。

调用图:被 7 处调用(cold_resume_replays_rendered_instructions_but_reports_current_config_sources, fork_replays_rendered_instructions_from_shared_history, fresh_thread_composes_global_before_project_and_reports_sources, invalid_utf8_global_instructions_are_lossy, loads_user_instructions_without_a_primary_environment, multi_environment_thread_loads_every_project_and_keeps_creation_snapshot, run_subagent_global_instruction_case);外部调用 2 个(path, write)。

instruction_fragments82–88 ↗
fn instruction_fragments(request: &responses::ResponsesRequest) -> Vec<String>

作用:从一次假模型请求里筛出所有 AGENTS.md 说明片段。它让测试不用关心请求里别的聊天内容。

数据流:进去的是一条记录下来的模型请求 → 它取出其中 user 角色的所有文本,只保留以“# AGENTS.md instructions”开头的文本 → 出来是这些说明片段组成的列表。

调用关系:它服务于需要检查“有几段说明、内容是什么”的测试。fresh_thread_composes_global_before_project_and_reports_sources 和 loads_user_instructions_without_a_primary_environment 会用它把请求拆干净,再做精确断言。

调用图:调用 1 个内部函数(message_input_texts);被 2 处调用(fresh_thread_composes_global_before_project_and_reports_sources, loads_user_instructions_without_a_primary_environment)。

expected_instruction_fragment90–93 ↗
fn expected_instruction_fragment(cwd: &AbsolutePathBuf, contents: &str) -> String

作用:生成测试期望看到的项目说明格式。它把当前工作目录和说明内容包成模型实际会收到的标准文本。

数据流:进去的是当前工作目录路径和说明内容 → 它按系统约定拼出标题、空行、<INSTRUCTIONS> 包裹内容 → 出来是一整段期望字符串。

调用关系:fresh_thread_composes_global_before_project_and_reports_sources 用它构造“正确答案”,再和假服务器收到的请求片段逐字比较,确保渲染格式没有变。

调用图:调用 1 个内部函数(as_path);被 1 处调用(fresh_thread_composes_global_before_project_and_reports_sources);外部调用 1 个(format!)。

expected_provider_only_instruction_fragment95–97 ↗
fn expected_provider_only_instruction_fragment(contents: &str) -> String

作用:生成只有全局用户说明、没有项目目录信息时的期望格式。它用于那些说明来源只来自用户 home 的场景。

数据流:进去的是说明内容 → 它拼成没有“for 某个目录”的 AGENTS.md 说明块 → 出来是一段可直接拿来比较的期望字符串。

调用关系:冷恢复、fork、无效 UTF-8 和子 agent 测试都会用它确认:模型看到的是全局说明本身,而不是混入了项目路径或额外重复内容。

调用图:被 4 处调用(cold_resume_replays_rendered_instructions_but_reports_current_config_sources, fork_replays_rendered_instructions_from_shared_history, invalid_utf8_global_instructions_are_lossy, run_subagent_global_instruction_case);外部调用 1 个(format!)。

assert_single_instruction_fragment99–101 ↗
fn assert_single_instruction_fragment(request: &responses::ResponsesRequest, expected: &str)

作用:断言一次请求里刚好只有一段 AGENTS.md 说明,而且内容完全等于期望。它把常见检查压成一句,避免测试里到处重复。

数据流:进去的是模型请求和期望字符串 → 它先提取说明片段,再比较是否正好是一个元素且内容一致 → 没有返回值;不一致时测试失败。

调用关系:很多测试把最后的核对交给它。它依赖 instruction_fragments 的筛选思路,但这里直接做强断言,确保说明不会漏、不会重复、也不会格式跑偏。

调用图:被 6 处调用(cold_resume_replays_rendered_instructions_but_reports_current_config_sources, fork_replays_rendered_instructions_from_shared_history, fresh_thread_composes_global_before_project_and_reports_sources, invalid_utf8_global_instructions_are_lossy, multi_environment_thread_loads_every_project_and_keeps_creation_snapshot, run_subagent_global_instruction_case);外部调用 1 个(assert_eq!)。

submit_thread_turn103–118 ↗
async fn submit_thread_turn(thread: &Arc<codex_core::CodexThread>, prompt: &str) -> Result<()>

作用:给指定线程提交一轮用户文本,并等到这一轮结束。它用于测试里手动创建线程后,还想像正常用户一样说一句话。

数据流:进去的是线程对象和提示词 → 它把提示词包装成 UserInput 操作提交给线程,然后等待 TurnComplete 事件 → 出来是成功或错误;线程内部会多一轮对话记录。

调用关系:multi_environment_thread_loads_every_project_and_keeps_creation_snapshot 用它连续提交两轮多环境对话。这样测试重点可以放在说明快照是否稳定,而不用反复写提交和等待事件的样板代码。

调用图:被 1 处调用(multi_environment_thread_loads_every_project_and_keeps_creation_snapshot);外部调用 3 个(default, wait_for_event, vec!)。

request_body_contains120–137 ↗
fn request_body_contains(request: &wiremock::Request, text: &str) -> bool

作用:检查一条 HTTP 请求正文里是否包含某段文字。它还会处理被 zstd 压缩过的请求体,zstd 是一种压缩格式。

数据流:进去的是 wiremock 记录的请求和要查找的文字 → 它先看请求头判断正文是否用 zstd 压缩,如果压缩就解压,再把字节转成 UTF-8 字符串 → 出来是 true 或 false,表示正文里有没有那段文字。

调用关系:它主要给匹配假模型请求用,尤其在子 agent 测试里区分“父线程 seed 请求”“spawn 请求”“子线程请求”。调用图里没有列出直接调用者,是因为它常被放进闭包里当匹配条件使用。

调用图:调用 1 个内部函数(new);外部调用 1 个(decode_all)。

agents_override_is_preferred_over_agents_md140–171 ↗
async fn agents_override_is_preferred_over_agents_md() -> Result<()>

作用:测试同一个目录里同时有 AGENTS.md 和 AGENTS.override.md 时,系统必须选择 override 文件。override 可以理解成“优先级更高的临时说明”。

数据流:进去没有外部输入,测试自己创建工作区 → 它写入 base doc 和 override doc,跑一轮对话取出说明 → 最后断言说明包含 override doc,且不包含 base doc。

调用关系:它调用 test_codex 搭测试实例,再把准备好的构建器交给 agents_instructions。这个测试保护的是文件优先级规则,避免模型同时看到旧说明和覆盖说明。

调用图:调用 2 个内部函数(test_codex, agents_instructions);外部调用 2 个(assert!, skip_if_wine_exec!)。

configured_fallback_is_used_when_agents_candidate_is_directory174–210 ↗
async fn configured_fallback_is_used_when_agents_candidate_is_directory() -> Result<()>

作用:测试当 AGENTS.md 这个名字实际是个目录时,系统会改用配置里的备用说明文件。这样不会因为同名目录就完全丢掉项目说明。

数据流:进去是测试内部设置的配置和文件布局 → 它把备用文件名设为 WORKFLOW.md,创建一个叫 AGENTS.md 的目录,再写 WORKFLOW.md → 出来通过断言确认模型说明里包含 fallback doc。

调用关系:它同样借助 agents_instructions 跑完整请求。它覆盖的是“候选说明文件不可读时如何兜底”的规则,防止目录、特殊文件之类的情况让说明加载中断。

调用图:调用 2 个内部函数(test_codex, agents_instructions);外部调用 2 个(assert!, skip_if_wine_exec!)。

agents_docs_are_concatenated_from_project_root_to_cwd213–274 ↗
async fn agents_docs_are_concatenated_from_project_root_to_cwd() -> Result<()>

作用:测试从项目根目录到当前工作目录的多个 AGENTS.md 会按从上到下的顺序拼接。这样上层通用规则会先出现,下层具体规则再补充。

数据流:进去是测试自己创建的嵌套目录结构 → 它在根目录写 root doc,在当前目录写 child doc,并用 .git 标记项目根 → 取出说明后,检查 root doc 的位置早于 child doc。

调用关系:它通过 agents_instructions 获取模型实际看到的拼接结果。这个测试验证说明发现和排序逻辑,避免子目录规则跑到全局规则前面。

调用图:调用 2 个内部函数(test_codex, agents_instructions);外部调用 1 个(assert!)。

symlinked_cwd_uses_logical_parent_for_agents_discovery277–354 ↗
async fn symlinked_cwd_uses_logical_parent_for_agents_discovery() -> Result<()>

作用:测试当前工作目录是符号链接时,系统按用户配置里看到的“逻辑路径”往上找说明,而不是被链接指向的真实物理路径带跑。符号链接就像一个指向别处文件夹的快捷方式。

数据流:进去没有外部输入 → 它搭出 logical-repo 和 physical-repo 两套目录,让 logical-repo/workspace 指向 physical-repo/workspace,然后分别写说明 → 出来断言来源列表是 logical parent 和 workspace,模型说明包含 logical parent 与 workspace,不包含 physical parent。

调用关系:它自己启动假服务器和测试实例,不走 agents_instructions,因为还要检查 instruction_sources 返回的路径列表。它保护的是路径发现规则,避免用户在链接目录里工作时加载到旁边真实目录的错误说明。

调用图:调用 4 个内部函数(mount_sse_once, sse, start_mock_server, test_codex);外部调用 3 个(assert!, assert_eq!, vec!)。

selected_environment_sources_match_model_visible_instructions357–401 ↗
async fn selected_environment_sources_match_model_visible_instructions() -> Result<()>

作用:测试系统报告的说明来源列表,必须和模型真正看到的全局说明、项目说明一致。来源列表像收据,模型输入像实际送出的货,两者不能对不上。

数据流:进去是测试创建的全局 AGENTS.md 和项目 AGENTS.md → 它启动带远程环境的测试实例,先检查 instruction_sources 是全局路径加项目路径,再提交一轮对话 → 出来断言请求里的说明按“global doc、分隔线、project doc”拼好。

调用关系:它独立使用假服务器和 test_codex。这个测试连接了两个观察面:API 报告给外部的来源,以及真正发给模型的文字,确保二者描述的是同一批文件。

调用图:调用 4 个内部函数(mount_sse_once, sse, start_mock_server, test_codex);外部调用 7 个(new, new, assert!, assert_eq!, skip_if_wine_exec!, write, vec!)。

loads_user_instructions_without_a_primary_environment404–483 ↗
async fn loads_user_instructions_without_a_primary_environment() -> Result<()>

作用:测试即使线程没有主环境,系统也会加载用户 home 里的全局说明,但不会加载项目说明。没有主环境时,系统不知道该进哪个项目目录找项目规则。

数据流:进去是测试准备的 home 目录、全局说明和项目说明 → 它先建正常测试实例,再手动启动一个 environments 为空的新线程 → 出来检查说明来源只有全局文件,请求里包含全局说明且不包含项目说明,同时记录提供器加载次数。

调用关系:它使用 write_global_file 准备全局文件,用 instruction_fragments 检查请求。它测试线程启动流程在“没有环境”这种特殊情况下的降级行为。

调用图:调用 9 个内部函数(new, mount_sse_once, sse, start_mock_server, new, test_codex, instruction_fragments, write_global_file, try_from);外部调用 9 个(clone, new, default, new, new, assert!, assert_eq!, wait_for_event, vec!)。

fresh_thread_composes_global_before_project_and_reports_sources486–595 ↗
async fn fresh_thread_composes_global_before_project_and_reports_sources() -> Result<()>

作用:测试新线程会把全局说明放在项目前面,并且线程创建后会固定这份说明快照。之后文件内容改了,普通下一轮对话也不应该换成新内容。

数据流:进去是测试创建的全局说明和项目说明 → 它启动线程,提交第一轮让说明快照成形,然后把两个源文件都改成新内容,再提交第二轮 → 出来检查两次请求都还是旧的全局加旧的项目说明,来源路径仍是创建时那两个路径,第二轮请求还保留第一轮的结构化前缀。

调用关系:它用 write_global_file 写全局文件,用 expected_instruction_fragment 和 assert_single_instruction_fragment 做精确比对。这个测试是“创建时快照”规则的核心证明。

调用图:调用 8 个内部函数(mount_sse_sequence, start_mock_server, test_codex, assert_single_instruction_fragment, expected_instruction_fragment, instruction_fragments, write_global_file, from_path);外部调用 8 个(clone, new, new, assert!, assert_eq!, format!, skip_if_wine_exec!, vec!)。

multi_environment_thread_loads_every_project_and_keeps_creation_snapshot598–717 ↗
async fn multi_environment_thread_loads_every_project_and_keeps_creation_snapshot() -> Result<()>

作用:测试一个线程同时涉及远程环境和本地环境时,会加载全局说明,以及每个环境各自的项目说明,并在创建后固定下来。

数据流:进去是测试准备的全局说明、远程项目说明、本地项目说明和两个环境选择 → 它启动多环境线程,检查来源顺序是全局、远程、本地,提交一轮后把三个位置都写入新的 override 文件,再提交第二轮 → 出来确认两次请求仍然只含创建时的旧说明,来源列表和加载次数也不变。

调用关系:它调用 submit_thread_turn 来提交多环境线程的两轮输入,调用 assert_single_instruction_fragment 比对渲染结果。它验证多环境组合说明不会漏环境,也不会在运行中被后来的文件变化污染。

调用图:调用 10 个内部函数(new, mount_sse_sequence, start_mock_server, new, test_codex, assert_single_instruction_fragment, submit_thread_turn, write_global_file, try_from, from_path);外部调用 12 个(clone, new, default, new, new, assert_eq!, get_remote_test_env, format!, skip_if_no_network!, skip_if_wine_exec! (+2 more))。

invalid_utf8_global_instructions_are_lossy720–748 ↗
async fn invalid_utf8_global_instructions_are_lossy() -> Result<()>

作用:测试全局说明文件里有非法 UTF-8 字节时,系统不会崩溃,而是用替换字符读出来。UTF-8 是常见文本编码,非法字节就是“这段字节不像正常文字”。

数据流:进去是测试写入的带 0xFF 非法字节的全局文件 → 它启动测试实例并提交一轮 → 出来断言来源是该文件,模型看到的内容里非法字节变成了 U+FFFD 这个替换字符。

调用关系:它用 write_global_file 制造坏编码文件,用 expected_provider_only_instruction_fragment 生成期望。这个测试保证用户文件有小瑕疵时,对话还能继续。

调用图:调用 7 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, assert_single_instruction_fragment, expected_provider_only_instruction_fragment, write_global_file);外部调用 4 个(new, new, assert_eq!, vec!)。

cold_resume_replays_rendered_instructions_but_reports_current_config_sources753–835 ↗
async fn cold_resume_replays_rendered_instructions_but_reports_current_config_sources() -> Result<()>

作用:测试冷恢复会话时,模型历史会重放旧会话里已经渲染好的说明,但 API 报告的来源会按当前配置重新计算。冷恢复就是程序重启后从保存的会话继续。

数据流:进去是旧全局 AGENTS.md 和后来新增的 AGENTS.override.md → 它先创建初始线程并提交一轮,保存 rollout 会话文件,再关闭;随后写入更高优先级的新 override,并从保存文件恢复 → 出来检查恢复后的来源报告是新文件,但两次模型请求里的说明片段都是旧内容,且恢复请求开头复用了原始输入前缀。

调用关系:它用 write_global_file 制造前后两个全局来源,用 assert_single_instruction_fragment 核对模型可见内容。测试里的 TODO 也说明这是一个已知不完全一致点:来源报告和历史重放目前可能不同。

调用图:调用 6 个内部函数(mount_sse_sequence, start_mock_server, test_codex, assert_single_instruction_fragment, expected_provider_only_instruction_fragment, write_global_file);外部调用 7 个(clone, new, new, assert_eq!, assert_ne!, wait_for_event, vec!)。

fork_replays_rendered_instructions_from_shared_history838–941 ↗
async fn fork_replays_rendered_instructions_from_shared_history() -> Result<()>

作用:测试 fork 出来的线程会重放父线程历史里那份已经渲染好的说明,而不是用 fork 时新配置重新渲染。fork 可以理解成从旧会话分出一条新分支。

数据流:进去是父线程创建时的旧全局说明,以及 fork 前新增的新 override 说明 → 它让父线程提交并落盘,再用新配置 fork 一个线程 → 出来检查 fork 线程报告的新来源是 override,但实际发给模型的历史前缀和说明仍与父线程旧内容一致。

调用关系:它通过父线程的 thread_manager 调用 fork_thread,之后手动给 fork 线程提交输入。它和冷恢复测试类似,都验证“历史里已经发给模型的说明不能被改写”。

调用图:调用 6 个内部函数(mount_sse_sequence, start_mock_server, test_codex, assert_single_instruction_fragment, expected_provider_only_instruction_fragment, write_global_file);外部调用 9 个(clone, new, default, new, assert_eq!, assert_ne!, load_default_config_for_test, wait_for_event, vec!)。

forked_subagent_replays_one_creation_time_global_instruction_fragment944–947 ↗
async fn forked_subagent_replays_one_creation_time_global_instruction_fragment() -> Result<()>

作用:测试带父上下文的子 agent 只会重放一份父线程创建时的全局说明,不会因为生成子线程而重复或换成新说明。子 agent 可以理解成主助手叫来帮忙的另一个助手。

数据流:进去没有直接数据,测试只传入 fork_context 为 true → 它先在有网络条件时继续执行公共子 agent 用例 → 出来依赖公共用例确认子线程继承父历史和旧说明。

调用关系:它只是一个薄包装,负责选择“继承父上下文”这条分支,然后把主要工作交给 run_subagent_global_instruction_case。

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

fresh_subagent_uses_creation_time_instructions_without_parent_history950–953 ↗
async fn fresh_subagent_uses_creation_time_instructions_without_parent_history() -> Result<()>

作用:测试不带父上下文的新鲜子 agent 仍使用父线程创建时的说明,但不带父线程之前的用户聊天历史。也就是规则继承,聊天内容不继承。

数据流:进去没有直接数据,测试只传入 fork_context 为 false → 它在有网络条件时执行公共子 agent 用例 → 出来依赖公共用例确认子线程只有自己的提示词,同时说明片段仍是旧全局说明。

调用关系:它是另一个薄包装,选择“新鲜上下文”分支,并把服务器搭建、spawn 调用和断言都交给 run_subagent_global_instruction_case。

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

run_subagent_global_instruction_case955–1114 ↗
async fn run_subagent_global_instruction_case(fork_context: bool) -> Result<()>

作用:跑子 agent 的核心测试流程,同时支持“继承父上下文”和“不继承父上下文”两种模式。它确认子 agent 用的是父线程创建时的说明快照,而且说明只出现一次。

数据流:进去的是 fork_context 布尔值,表示子 agent 是否带父历史 → 它搭假服务器,分别匹配父线程 seed、spawn 调用、子线程请求和父线程 follow-up;创建父线程并写旧全局说明,提交 seed 后再写新 override,然后让父线程调用 spawn_agent → 出来检查父请求、spawn 请求、子请求都只有旧说明;如果是 fork 模式,还检查子请求前缀包含父 seed 历史;如果是 fresh 模式,则检查子请求不含父用户历史、只含自己的提示。

调用关系:forked_subagent_replays_one_creation_time_global_instruction_fragment 和 fresh_subagent_uses_creation_time_instructions_without_parent_history 都调用它。它内部用 request_body_contains 匹配不同请求,用 write_global_file 制造旧说明和新 override,用 assert_single_instruction_fragment 做最终说明检查,是子 agent 说明快照规则的总测试。

调用图:调用 7 个内部函数(mount_sse_once_match, sse, start_mock_server, test_codex, assert_single_instruction_fragment, expected_provider_only_instruction_fragment, write_global_file);被 2 处调用(forked_subagent_replays_one_creation_time_global_instruction_fragment, fresh_subagent_uses_creation_time_instructions_without_parent_history);外部调用 12 个(clone, new, from_millis, from_secs, new, assert_eq!, assert_ne!, json!, to_string, sleep (+2 more))。

core/tests/suite/collaboration_instructions.rs源码 ↗
testtest execution

可以把这个文件看成一套“验货清单”。系统和模型对话时,会把一些隐藏的开发者说明一起发出去,其中可能包含协作模式说明,比如当前是默认模式还是计划模式,以及对应的额外指令。这个测试文件会启动一个假的服务器,模拟模型返回结果,然后让 Codex 发送用户输入,再检查实际发出的请求内容。它重点验证几件事:默认情况下不加协作说明;用户或线程设置里指定后才加;配置关闭后不加;同样的设置不要重复加;内容或模式变了要追加新的说明;恢复旧会话后还要能重放这些说明;空字符串要当作没有说明处理。文件里的小工具函数负责造测试用的协作模式、从 JSON 请求里挑出开发者文本、给说明包上固定标签,以及数某段文字出现了几次。这样测试读起来像是在检查快递包裹:该有的标签必须有,不该有的标签不能混进去,也不能贴两遍。

函数细节17
collab_mode_with_mode_and_instructions22–34 ↗
fn collab_mode_with_mode_and_instructions(
    mode: ModeKind,
    instructions: Option<&str>,
) -> CollaborationMode

作用:这个函数用来快速造一个测试用的“协作模式”对象。测试不想每次都手写一大坨配置,所以用它把模式、模型名和开发者说明打包好。

数据流:输入一个模式,比如默认模式或计划模式,再输入一段可有可无的说明文字 → 它把这些放进 CollaborationMode 结构里,并固定使用测试模型名 gpt-5.4 → 输出一个完整的协作模式配置,供后面的测试提交给系统。

调用关系:它是最底层的造数据小工具。collab_mode_with_instructions 会借它创建默认模式;两个检查“模式变化”和“模式不变”的测试也会直接用它,因为那些测试需要明确指定 ModeKind。

调用图:被 3 处调用(collab_mode_with_instructions, collaboration_mode_update_emits_new_instruction_message_when_mode_changes, collaboration_mode_update_noop_does_not_append_when_mode_is_unchanged)。

collab_mode_with_instructions36–38 ↗
fn collab_mode_with_instructions(instructions: Option<&str>) -> CollaborationMode

作用:这个函数是更省事的版本,用来创建“默认模式”的协作说明。大多数测试只关心说明文字,不关心模式种类,所以用它减少重复代码。

数据流:输入一段可有可无的说明文字 → 它默认选择 ModeKind::Default,再把工作交给 collab_mode_with_mode_and_instructions → 输出一个默认模式下的协作模式配置。

调用关系:它站在很多测试和底层构造函数之间。用户覆盖、单轮覆盖、禁用发送、更新说明、恢复会话等测试都会先用它造出测试配置,然后提交给 Codex。

调用图:调用 1 个内部函数(collab_mode_with_mode_and_instructions);被 8 处调用(collaboration_instructions_added_on_user_turn, collaboration_instructions_omitted_when_disabled, collaboration_mode_update_emits_new_instruction_message, collaboration_mode_update_noop_does_not_append, override_then_next_turn_uses_updated_collaboration_instructions, resume_replays_collaboration_instructions, user_input_includes_collaboration_instructions_after_override, user_turn_overrides_collaboration_instructions_after_override)。

developer_texts40–51 ↗
fn developer_texts(input: &[Value]) -> Vec<String>

作用:这个函数从发给模型的请求里,只挑出“开发者消息”的纯文本内容。测试需要看隐藏说明到底有没有进请求,所以先用它把要检查的文字集中取出来。

数据流:输入一组 JSON 消息 → 它逐条查看 role 是否是 developer,也就是开发者角色;再进入 content 数组,把里面的 text 字段取出来 → 输出一个字符串列表,里面都是开发者消息的文本。

调用关系:它是所有断言前的“筛子”。多个测试先从假服务器记录的请求中拿到 input,再调用 developer_texts 过滤出开发者文本,最后交给 count_messages_containing 或 assert 检查有没有目标说明。

调用图:被 12 处调用(collaboration_instructions_added_on_user_turn, collaboration_instructions_omitted_when_disabled, collaboration_mode_update_emits_new_instruction_message, collaboration_mode_update_emits_new_instruction_message_when_mode_changes, collaboration_mode_update_noop_does_not_append, collaboration_mode_update_noop_does_not_append_when_mode_is_unchanged, empty_collaboration_instructions_are_ignored, no_collaboration_instructions_by_default, override_then_next_turn_uses_updated_collaboration_instructions, resume_replays_collaboration_instructions (+2 more));外部调用 1 个(iter)。

collab_xml53–55 ↗
fn collab_xml(text: &str) -> String

作用:这个函数把一段协作说明包进固定的开始标签和结束标签里。系统发送说明时会用这种标签标记边界,测试也必须按同样格式去找。

数据流:输入一段普通说明文字 → 它用 COLLABORATION_MODE_OPEN_TAG 和 COLLABORATION_MODE_CLOSE_TAG 把文字前后包起来 → 输出一段带标签的完整字符串。

调用关系:它在测试断言前使用。很多测试先用它生成“系统应该发出的样子”,再用 count_messages_containing 去开发者消息里数出现次数。

调用图:被 10 处调用(collaboration_instructions_added_on_user_turn, collaboration_mode_update_emits_new_instruction_message, collaboration_mode_update_emits_new_instruction_message_when_mode_changes, collaboration_mode_update_noop_does_not_append, collaboration_mode_update_noop_does_not_append_when_mode_is_unchanged, empty_collaboration_instructions_are_ignored, override_then_next_turn_uses_updated_collaboration_instructions, resume_replays_collaboration_instructions, user_input_includes_collaboration_instructions_after_override, user_turn_overrides_collaboration_instructions_after_override);外部调用 1 个(format!)。

count_messages_containing57–59 ↗
fn count_messages_containing(texts: &[String], target: &str) -> usize

作用:这个函数数一数某段目标文字在一堆消息里出现了几次。测试用它判断说明是没出现、出现一次,还是被重复加了。

数据流:输入开发者文本列表和要查找的目标字符串 → 它逐条检查文本里是否包含目标字符串 → 输出匹配到的数量,不改动原始消息。

调用关系:它通常接在 developer_texts 后面使用。测试先过滤出开发者文本,再用它统计目标标签或说明的次数,最后用 assert_eq! 判断结果是否符合预期。

no_collaboration_instructions_by_default62–102 ↗
async fn no_collaboration_instructions_by_default() -> Result<()>

作用:这个测试确认:没有任何覆盖设置时,系统默认不会发送协作模式说明。这样可以防止系统无缘无故把额外指令塞给模型。

数据流:它启动假服务器,配置一次模拟的流式返回,然后创建测试用 Codex,提交一句 hello → 等这一轮完成后,取出发给服务器的请求 → 从请求中提取开发者文本,确认权限说明存在,但协作模式标签出现次数是 0。

调用关系:它会调用 start_mock_server、mount_sse_once 和 sse 搭建假模型服务,用 test_codex 创建被测对象,用 developer_texts 抽取开发者消息,再用断言检查默认行为。

调用图:调用 5 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, developer_texts);外部调用 6 个(default, assert!, assert_eq!, wait_for_event, skip_if_no_network!, vec!)。

user_input_includes_collaboration_instructions_after_override105–148 ↗
async fn user_input_includes_collaboration_instructions_after_override() -> Result<()>

作用:这个测试确认:如果先通过线程设置覆盖了协作说明,下一次用户输入会带上这段说明。它检查“先改设置、后发消息”这条常见路径是否生效。

数据流:它先启动假服务器和测试 Codex,再创建一段 collab instructions 的协作模式,通过 submit_thread_settings 提交覆盖设置 → 接着发送 hello → 请求完成后取出开发者文本,检查带 XML 标签的协作说明正好出现一次。

调用关系:它用 collab_mode_with_instructions 造覆盖配置,用 collab_xml 生成预期文本,用 developer_texts 读取实际请求,最后统计出现次数来验证设置覆盖是否影响了下一轮对话。

调用图:调用 7 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, collab_mode_with_instructions, collab_xml, developer_texts);外部调用 6 个(default, assert_eq!, submit_thread_settings, wait_for_event, skip_if_no_network!, vec!)。

collaboration_instructions_added_on_user_turn151–196 ↗
async fn collaboration_instructions_added_on_user_turn() -> Result<()>

作用:这个测试确认:协作说明也可以直接随某一次用户输入一起提交,并在这一轮马上生效。也就是说,不一定要先单独更新线程设置。

数据流:它创建测试环境和协作说明 turn instructions → 在提交用户输入 hello 时,把环境选择、审批策略、沙箱策略、摘要设置和协作模式一起放进 thread_settings → 等模型轮次结束后,检查发出的开发者消息里这段说明出现一次。

调用关系:它调用 local_selections 补齐本轮需要的环境设置,用 collab_mode_with_instructions 造协作模式,再通过 developer_texts 和 collab_xml 检查这一轮请求是否包含正确说明。

调用图:调用 8 个内部函数(mount_sse_once, sse, start_mock_server, local_selections, test_codex, collab_mode_with_instructions, collab_xml, developer_texts);外部调用 5 个(default, assert_eq!, wait_for_event, skip_if_no_network!, vec!)。

collaboration_instructions_omitted_when_disabled199–248 ↗
async fn collaboration_instructions_omitted_when_disabled() -> Result<()>

作用:这个测试确认:即使用户提供了协作说明,只要配置开关关闭,系统也不会把它发给模型。它保护的是“全局禁用开关必须优先”的规则。

数据流:它先创建测试 Codex,但在配置里把 include_collaboration_mode_instructions 设成 false → 然后提交带协作模式的用户输入 → 请求完成后提取开发者文本,确认协作模式开始标签出现次数为 0。

调用关系:它通过 test_codex().with_config 修改测试配置,用 local_selections 和 collab_mode_with_instructions 构造一次完整用户输入,最后用 developer_texts 和统计函数确认禁用开关真的挡住了说明。

调用图:调用 7 个内部函数(mount_sse_once, sse, start_mock_server, local_selections, test_codex, collab_mode_with_instructions, developer_texts);外部调用 5 个(default, assert_eq!, wait_for_event, skip_if_no_network!, vec!)。

override_then_next_turn_uses_updated_collaboration_instructions251–294 ↗
async fn override_then_next_turn_uses_updated_collaboration_instructions() -> Result<()>

作用:这个测试确认:更新线程里的协作说明后,下一轮用户消息会使用更新后的说明。它和前面的覆盖测试类似,重点是证明更新后的状态会被保存到下一轮。

数据流:它启动假服务器和 Codex → 先提交包含 override instructions 的线程设置覆盖 → 再发送 hello → 等完成后读取请求里的开发者文本,确认这段带标签说明出现一次。

调用关系:它依赖 submit_thread_settings 改变线程状态,用 collab_mode_with_instructions 创建新设置,用 collab_xml 和 developer_texts 检查下一轮真正发出的内容。

调用图:调用 7 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, collab_mode_with_instructions, collab_xml, developer_texts);外部调用 6 个(default, assert_eq!, submit_thread_settings, wait_for_event, skip_if_no_network!, vec!)。

user_turn_overrides_collaboration_instructions_after_override297–355 ↗
async fn user_turn_overrides_collaboration_instructions_after_override() -> Result<()>

作用:这个测试确认:如果线程里已经有一套协作说明,但某次用户输入又带了另一套,那么这一轮应该用用户输入里的新说明。它防止旧设置压过本轮显式设置。

数据流:它先把 base instructions 作为线程级协作说明提交进去 → 然后发送 hello,同时在这次用户输入的 thread_settings 里带上 turn override → 最后检查请求,确认旧说明出现 0 次,新说明出现 1 次。

调用关系:它先用 submit_thread_settings 建立基础状态,再在 Op::UserInput 里放入本轮覆盖。collab_mode_with_instructions 负责造两套设置,collab_xml 和 developer_texts 负责验证最终采用的是哪一套。

调用图:调用 8 个内部函数(mount_sse_once, sse, start_mock_server, local_selections, test_codex, collab_mode_with_instructions, collab_xml, developer_texts);外部调用 6 个(default, assert_eq!, submit_thread_settings, wait_for_event, skip_if_no_network!, vec!)。

collaboration_mode_update_emits_new_instruction_message358–431 ↗
async fn collaboration_mode_update_emits_new_instruction_message() -> Result<()>

作用:这个测试确认:协作说明内容变了以后,系统会在后续请求里追加新的说明消息。这样模型能知道说明已经更新,而不是只看到旧版本。

数据流:它准备两次假服务器响应 → 先提交 first instructions 并发送 hello 1,完成第一轮 → 再把线程设置更新成 second instructions 并发送 hello 2 → 检查第二次请求,确认旧说明和新说明各出现一次。

调用关系:它用两个 mount_sse_once 分别接住两轮请求。collab_mode_with_instructions 创建前后两版设置;第二轮请求通过 developer_texts 读取后,用 collab_xml 和统计函数确认“历史说明”和“新说明”都在上下文中。

调用图:调用 7 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, collab_mode_with_instructions, collab_xml, developer_texts);外部调用 6 个(default, assert_eq!, submit_thread_settings, wait_for_event, skip_if_no_network!, vec!)。

collaboration_mode_update_noop_does_not_append434–504 ↗
async fn collaboration_mode_update_noop_does_not_append() -> Result<()>

作用:这个测试确认:如果协作说明没有变,重复提交同样设置不会多加一条一模一样的说明。它防止上下文里堆满重复指令。

数据流:它先提交 same instructions 并完成第一轮 → 再提交完全相同的协作说明并完成第二轮 → 检查第二次请求,确认这段说明只出现一次,而不是两次。

调用关系:它的流程和“内容变更会追加”的测试相反,用同一段 collab_text 做两次 submit_thread_settings。developer_texts 和 collab_xml 帮它确认系统把重复更新当成无操作处理。

调用图:调用 7 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, collab_mode_with_instructions, collab_xml, developer_texts);外部调用 6 个(default, assert_eq!, submit_thread_settings, wait_for_event, skip_if_no_network!, vec!)。

collaboration_mode_update_emits_new_instruction_message_when_mode_changes507–586 ↗
async fn collaboration_mode_update_emits_new_instruction_message_when_mode_changes() -> Result<()>

作用:这个测试确认:即使说明内容和模式绑定在一起,只要协作模式从默认模式变成计划模式,系统也会发出新的说明。它检查的不只是文字变化,也包括模式变化。

数据流:它先提交默认模式下的 default mode instructions 并完成第一轮 → 再提交计划模式下的 plan mode instructions 并完成第二轮 → 读取第二次请求,确认默认模式说明和计划模式说明各出现一次。

调用关系:它直接调用 collab_mode_with_mode_and_instructions,因为需要明确指定 ModeKind::Default 和 ModeKind::Plan。后续仍然通过 collab_xml、developer_texts 和统计断言检查最终请求内容。

调用图:调用 7 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, collab_mode_with_mode_and_instructions, collab_xml, developer_texts);外部调用 6 个(default, assert_eq!, submit_thread_settings, wait_for_event, skip_if_no_network!, vec!)。

collaboration_mode_update_noop_does_not_append_when_mode_is_unchanged589–665 ↗
async fn collaboration_mode_update_noop_does_not_append_when_mode_is_unchanged() -> Result<()>

作用:这个测试确认:如果模式没变、说明也没变,重复更新不会追加重复消息。它保护的是“真正有变化才写入新说明”的规则。

数据流:它先提交默认模式下的 mode-stable instructions 并完成第一轮 → 再提交同样默认模式、同样文字的设置并完成第二轮 → 检查第二次请求,确认这段说明只出现一次。

调用关系:它和模式变化测试成对出现。它也调用 collab_mode_with_mode_and_instructions,但两次都传 ModeKind::Default,用来证明模式稳定时系统不会误判为更新。

调用图:调用 7 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, collab_mode_with_mode_and_instructions, collab_xml, developer_texts);外部调用 6 个(default, assert_eq!, submit_thread_settings, wait_for_event, skip_if_no_network!, vec!)。

resume_replays_collaboration_instructions668–739 ↗
async fn resume_replays_collaboration_instructions() -> Result<()>

作用:这个测试确认:会话恢复以后,之前的协作说明仍然会被带回请求上下文。它防止用户恢复旧会话后,模型忘记原来的协作规则。

数据流:它先创建一个初始会话,保存会话记录路径和 home 目录 → 提交 resume instructions 并发送 hello,完成第一轮 → 用同一个构建器从旧记录恢复会话 → 恢复后再发送 after resume → 检查第二次请求,确认原来的协作说明出现一次。

调用关系:它用 test_codex 创建初始会话,再调用 builder.resume 恢复会话。collab_mode_with_instructions 负责造初始说明,developer_texts 和 collab_xml 用来确认恢复后的请求重新带上了这段说明。

调用图:调用 7 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, collab_mode_with_instructions, collab_xml, developer_texts);外部调用 6 个(default, assert_eq!, submit_thread_settings, wait_for_event, skip_if_no_network!, vec!)。

empty_collaboration_instructions_are_ignored742–791 ↗
async fn empty_collaboration_instructions_are_ignored() -> Result<()>

作用:这个测试确认:空字符串形式的协作说明会被当作没有说明处理。这样可以避免系统发出一对空标签,让模型收到没有意义的隐藏指令。

数据流:它启动测试环境,拿到当前模型名 → 提交一个 developer_instructions 为 "" 的协作模式 → 发送 hello 并等完成 → 读取开发者文本,检查空内容包上标签后的字符串出现次数为 0。

调用关系:这个测试没有用常用的 collab_mode_with_instructions,而是手动构造 CollaborationMode,因为它要精确放入空字符串。之后仍用 collab_xml 生成“如果错误发送会长什么样”,再用 developer_texts 和断言确认没有发送。

调用图:调用 6 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, collab_xml, developer_texts);外部调用 6 个(default, assert_eq!, submit_thread_settings, wait_for_event, skip_if_no_network!, vec!)。

core/tests/suite/hierarchical_agents.rs源码 ↗
testtest run

AGENTS.md 可以理解成项目给 AI 助手看的“工作守则”。这个测试文件关心的是:系统在真正请求模型前,是否把这些守则正确塞进用户消息里。它先搭一个假的远程模型服务器,这样测试不用真的联网,也能检查系统发出去的请求内容。第一个测试会在临时工作区写入一个 AGENTS.md,内容是“be nice”,然后确认发给模型的指令里既有这句原始守则,也有关于“容器里可能有很多 AGENTS.md”的补充说明,并且补充说明排在原始守则后面。第二个测试不创建 AGENTS.md,只确认系统仍然会发出那段层级 AGENTS.md 的说明。这样可以防止以后改代码时,把项目守则弄丢、顺序放错,或者在没有文件时忘了提示模型。

函数细节2
hierarchical_agents_appends_to_project_doc_in_user_instructions15–66 ↗
async fn hierarchical_agents_appends_to_project_doc_in_user_instructions()

作用:这个测试确认:如果项目根目录里已经有 AGENTS.md,系统会把文件内容放进发给模型的用户指令里,并且再追加层级 AGENTS.md 的说明。它防止“项目自己的规则”和“额外说明”互相覆盖或顺序错乱。

数据流:测试开始时先准备一个假模型服务器,并让它返回一段模拟完成事件;然后创建一个测试版 Codex,把 ChildAgentsMd 这个功能开关打开,再在临时工作区写入 AGENTS.md,内容是“be nice”。接着它提交一轮用户输入“hello”。之后测试从假服务器收到的请求里取出 user 消息,找到以“# AGENTS.md instructions”开头的那段指令,检查里面有“be nice”,也有层级 AGENTS.md 的固定说明,并确认固定说明出现在“be nice”之后。结果不是返回业务数据,而是用断言证明请求内容符合预期。

调用关系:它是测试运行器自动执行的异步测试。它会先用 start_mock_server 建假服务器,再用 sse 和 mount_sse_once 安排服务器只返回一次模拟流式响应;用 test_codex 创建测试用的 Codex 实例;中途还用 skip_if_wine_exec 跳过不适合的 Wine 环境。最后通过 assert! 这些检查点,把真实发出的请求内容和预期规则对上。

调用图:调用 4 个内部函数(mount_sse_once, sse, start_mock_server, test_codex);外部调用 3 个(assert!, skip_if_wine_exec!, vec!)。

hierarchical_agents_emits_when_no_project_doc69–100 ↗
async fn hierarchical_agents_emits_when_no_project_doc()

作用:这个测试确认:即使项目里没有 AGENTS.md,只要开启了 ChildAgentsMd 功能,系统仍然会把层级 AGENTS.md 的说明发给模型。它防止系统因为找不到项目文件,就完全漏掉这段重要提示。

数据流:测试先搭好假模型服务器,并准备一段模拟的流式响应;然后创建测试版 Codex,只打开 ChildAgentsMd 功能,不额外写任何 AGENTS.md 文件。它提交用户输入“hello”后,从假服务器记录到的请求中取出 user 消息,找到 AGENTS.md 指令段,再检查里面包含那段关于“容器里可能有很多 AGENTS.md”的固定说明。最后通过断言表达测试结果:有这段说明就通过,没有就失败。

调用关系:它同样由测试运行器自动执行。它把网络部分交给 start_mock_server、sse 和 mount_sse_once 来伪造,把被测系统的创建交给 test_codex。和前一个测试相比,它故意不设置工作区文件,用来覆盖“没有项目 AGENTS.md”这一条路径。

调用图:调用 4 个内部函数(mount_sse_once, sse, start_mock_server, test_codex);外部调用 2 个(assert!, vec!)。

core/tests/suite/skills.rs源码 ↗
testtest run

这个测试文件专门检查仓库里的技能机制。所谓“技能”,这里可以理解成放在项目目录里的小说明书,比如 .agents/skills/demo/SKILL.md,里面写着技能名字、简介和具体用法。测试先临时搭一个假的项目目录,再写入一个示例技能文件。接着它启动一个模拟服务器,假装自己是远端模型服务,并让 Codex 发送一次用户输入:用户文字里提到 $demo,同时明确附带这个技能。最后测试会检查发给模拟服务器的请求内容,确认里面真的包含技能名称、技能路径和技能正文。这样可以防止一个很隐蔽的问题:界面或调用方以为技能已经给了模型,但实际请求里漏掉了,导致模型只能凭空猜。

函数细节2
write_repo_skill26–47 ↗
async fn write_repo_skill(
    cwd: AbsolutePathBuf,
    fs: Arc<dyn ExecutorFileSystem>,
    name: &str,
    description: &str,
    body: &str,
) -> Result<()>

作用:这个辅助函数用来在测试用的项目目录里造出一个假的技能文件。有人会用它,是因为测试需要一个真实存在的 SKILL.md,才能验证后面系统会不会读取并传递这份技能说明。

数据流:它接收项目目录、文件系统接口、技能名字、技能简介和技能正文。然后它在项目目录下拼出 .agents/skills/<技能名> 这个文件夹,把路径转成文件系统能理解的 URI(一种统一表示文件位置的地址),创建目录,再把名字、简介和正文组合成一个 Markdown 文件内容,写入 SKILL.md。结束后,磁盘上的测试目录里就多了一份完整的技能说明文件;函数本身只返回成功或失败。

调用关系:它是测试里的准备工具,不是产品运行时给用户直接用的功能。user_turn_includes_skill_instructions 在搭建测试工作区时调用它,先把“demo”技能放好;后面的 Codex 流程再基于这份文件构造用户请求。它自己主要把路径拼接、路径转 URI、目录创建和文件写入这些小步骤串起来。

调用图:调用 2 个内部函数(join, from_path);外部调用 1 个(format!)。

user_turn_includes_skill_instructions50–135 ↗
async fn user_turn_includes_skill_instructions() -> Result<()>

作用:这个测试函数验证:当用户输入里选择了某个技能时,发给模型服务的用户消息里会包含这份技能的完整说明。它防的是“用户选了技能,但模型根本没收到技能内容”这种问题。

数据流:它先跳过一些不适合跑这个测试的环境,比如 Windows 之外的兼容执行场景或没有网络的环境。然后启动一个模拟服务器,创建测试版 Codex,并在临时项目里写入 demo 技能。接着它准备一段假的服务器返回内容,让远端看起来会正常回复“done”。之后它向 Codex 提交一次用户输入,输入里既有普通文字 please use $demo,也有一个指向技能文件路径的技能项。Codex 处理完这一轮后,测试取出模拟服务器收到的真实请求,检查用户消息里是否包含技能标签、技能名、技能文件路径和技能正文。检查通过就返回成功;不通过就用断言报错,说明技能说明没有正确送出去。

调用关系:这是整个文件的主测试,由 Tokio 异步测试框架自动运行。它先用 start_mock_server 准备假的远端服务,用 test_codex 创建测试中的 Codex 实例,用 write_repo_skill 准备技能文件,再用 mount_sse_oncesse 安排服务器返回的事件流。提交用户输入时,它还借助 local_selectionsturn_permission_fields 填好本轮会话需要的环境和权限信息。最后它等待 Codex 发出“这一轮完成”的事件,再检查模拟服务器记录到的请求内容。

调用图:调用 6 个内部函数(mount_sse_once, sse, start_mock_server, local_selections, test_codex, turn_permission_fields);外部调用 6 个(default, assert!, wait_for_event, skip_if_no_network!, skip_if_wine_exec!, vec!)。

提示布局与请求组装

这些文件验证组装后的模型输入的精确结构,包括可见布局、权限和人格消息、缓存行为以及 token 预算标注。

core/tests/suite/model_visible_layout.rs源码 ↗
testtest run

可以把这个文件想成给“发给模型的信封内容”拍照留档。测试会启动一个假的服务器,假装模型正常回复,然后让 Codex 连续进行一两轮对话,期间故意改变当前目录、权限、人格或模型设置。每次请求都会被记录下来,再格式化成稳定的快照,和以前保存的结果对比。这样一来,如果以后有人改代码导致模型看到的上下文多了、少了、顺序变了,测试就会提醒。文件里也单独检查环境上下文里的 subagents(子代理,也就是可供模型参考的辅助代理列表)长什么样。一个重要点是:当前实现里,换工作目录后不会重新刷新新的 AGENTS.md 指令,这个行为被测试明确固定下来。

函数细节10
context_snapshot_options33–36 ↗
fn context_snapshot_options() -> ContextSnapshotOptions

作用:给快照格式化器准备一套统一的显示规则。这样不同测试拍出来的“照片”风格一致,不会因为格式细节反复变化。

数据流:它没有外部输入,只从默认配置开始 → 把渲染模式改成“显示内容种类,并带一小段文字预览”,最多显示 96 个字符 → 返回这套快照选项,供后面的格式化函数使用。

调用关系:它是两个格式化辅助函数的共同小工具。format_labeled_requests_snapshot 和 format_environment_context_subagents_snapshot 在生成快照前都会来这里拿同一套显示规则。

调用图:调用 1 个内部函数(default);被 2 处调用(format_environment_context_subagents_snapshot, format_labeled_requests_snapshot)。

format_labeled_requests_snapshot38–47 ↗
fn format_labeled_requests_snapshot(
    scenario: &str,
    sections: &[(&str, &ResponsesRequest)],
) -> String

作用:把一组已记录的模型请求整理成带标题的快照文本。测试用它把“第一次请求”“第二次请求”并排说明清楚,方便人看差异。

数据流:输入是一个场景说明,以及若干个“标签 + 请求记录” → 它先取统一的快照显示选项,再交给 context_snapshot 模块做真正的排版 → 输出一段稳定的文本,用于 insta 快照断言。

调用关系:各个完整对话测试在收集到假服务器收到的请求后,会调用它生成最终快照。它自己不分析请求内容,而是把排版工作交给同名的 context_snapshot::format_labeled_requests_snapshot。

调用图:调用 2 个内部函数(format_labeled_requests_snapshot, context_snapshot_options)。

user_instructions_wrapper_count49–55 ↗
fn user_instructions_wrapper_count(request: &ResponsesRequest) -> usize

作用:数一数某次发给模型的请求里,有多少段是 AGENTS.md 指令包装块。这个测试用它确认换目录后没有偷偷加入新的项目指令。

数据流:输入是一条 ResponsesRequest,也就是一次发给模型的请求记录 → 它取出所有 role 为 user 的输入文字,再筛选出以“# AGENTS.md instructions”开头的文字 → 输出数量,并不改动请求本身。

调用关系:snapshot_model_visible_layout_cwd_change_does_not_refresh_agents 会用它做额外检查。它依赖 ResponsesRequest.message_input_texts 来拿到用户可见文字。

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

format_environment_context_subagents_snapshot57–79 ↗
fn format_environment_context_subagents_snapshot(subagents: &[&str]) -> String

作用:专门拼出一段环境上下文,并把 subagents 子代理列表放进去,然后生成快照。它用来确认模型看到的子代理名单格式正确。

数据流:输入是若干行子代理描述文字 → 如果列表为空就不加子代理块;如果不为空,就缩进后包进 <subagents> 标签里,再和 cwd、shell 一起组成 environment_context → 最后输出格式化后的快照文本。

调用关系:两个 subagent 快照测试会通过它准备要比对的内容。它内部使用 context_snapshot_options 保持显示规则一致,并把构造好的响应条目交给 format_response_items_snapshot 排版。

调用图:调用 2 个内部函数(format_response_items_snapshot, context_snapshot_options);外部调用 3 个(new, format!, vec!)。

snapshot_model_visible_layout_turn_overrides82–201 ↗
async fn snapshot_model_visible_layout_turn_overrides() -> Result<()>

作用:测试同一个会话里,第二轮对话临时改了工作目录、审批策略和人格后,发给模型看的上下文是否正确变化。它也确认模型本身保持不变时,不会出现多余的模型切换信息。

数据流:它先启动假服务器,并预设两次模型回复 → 创建一个开启 Personality(人格特性)的测试 Codex,会话默认人格是 Pragmatic → 第一轮用原目录和 Never 审批,第二轮换目录、换成 OnRequest 审批、人格改成 Friendly → 收集两次发出的请求,生成带标签快照,并断言确实有两条请求。

调用关系:这是一个端到端式的测试:它用 start_mock_server 和 mount_sse_sequence 搭好假模型服务,用 test_codex 创建被测对象,用 turn_permission_fields 配权限字段,用 wait_for_event 等待每轮结束,最后把请求交给 format_labeled_requests_snapshot 做快照。

调用图:调用 6 个内部函数(mount_sse_sequence, start_mock_server, local_selections, test_codex, turn_permission_fields, read_only);外部调用 7 个(default, assert_eq!, wait_for_event, create_dir_all, assert_snapshot!, skip_if_no_network!, vec!)。

snapshot_model_visible_layout_cwd_change_does_not_refresh_agents206–334 ↗
async fn snapshot_model_visible_layout_cwd_change_does_not_refresh_agents() -> Result<()>

作用:测试当前一个很具体的行为:会话开始后,只是换工作目录到另一个带 AGENTS.md 的地方,并不会刷新发给模型的 AGENTS.md 指令。这个测试防止该行为在没人注意时变化。

数据流:它启动假服务器并准备两次回复 → 建两个目录,各写一个不同的 AGENTS.md → 第一轮在第一个目录提问,第二轮切到第二个目录提问 → 收集两次请求,先检查两次请求里的 AGENTS.md 指令包装块数量都是 0,再把两次请求生成快照。

调用关系:它和普通对话流程一样依赖 test_codex、local_selections、turn_permission_fields、wait_for_event 等测试支架。它额外调用 user_instructions_wrapper_count 来做数字断言,再用 format_labeled_requests_snapshot 留下人工可读的请求快照。

调用图:调用 6 个内部函数(mount_sse_sequence, start_mock_server, local_selections, test_codex, turn_permission_fields, read_only);外部调用 8 个(default, assert_eq!, wait_for_event, create_dir_all, write, assert_snapshot!, skip_if_no_network!, vec!)。

snapshot_model_visible_layout_resume_with_personality_change337–449 ↗
async fn snapshot_model_visible_layout_resume_with_personality_change() -> Result<()>

作用:测试“恢复旧会话”后,如果新配置换了模型,又在第一轮恢复后的请求里改了人格,模型可见上下文是否把这些变化讲清楚。它关注的是恢复会话时最容易出错的历史和新设置混合问题。

数据流:它先用 gpt-5.2 开一个会话,并发一轮消息,让系统写下可恢复的历史记录 → 保存 home 和 rollout_path,也就是恢复所需的位置 → 再用 gpt-5.3-codex 和 Pragmatic 人格恢复会话 → 恢复后的第一轮又临时换目录、改审批,并把人格改成 Friendly → 最后把恢复前最后一次请求和恢复后第一次请求一起做快照。

调用关系:它先用 mount_sse_once 记录初始轮请求,再重新创建 test_codex builder 走 resume 流程。恢复后仍通过 submit 发送用户输入,wait_for_event 等完成,然后交给 format_labeled_requests_snapshot 展示恢复前后的差别。

调用图:调用 7 个内部函数(mount_sse_once, sse, start_mock_server, local_selections, test_codex, turn_permission_fields, read_only);外部调用 7 个(clone, default, wait_for_event, create_dir_all, assert_snapshot!, skip_if_no_network!, vec!)。

snapshot_model_visible_layout_resume_override_matches_rollout_model452–549 ↗
async fn snapshot_model_visible_layout_resume_override_matches_rollout_model() -> Result<()>

作用:测试恢复旧会话后,如果临时把模型改回旧会话原本使用的模型,就不应该生成“模型切换”的提示。它是在防止系统把没有实际变化的事情误报给模型。

数据流:它先用 gpt-5.2 创建并记录一轮可恢复会话 → 然后用配置里的 gpt-5.3-codex 去恢复这个会话 → 在正式发恢复后的第一条用户消息前,先提交线程设置,把模型覆盖回 gpt-5.2,并换一个目录 → 发送用户消息后,收集恢复后请求,与恢复前请求一起生成快照。

调用关系:它使用和恢复测试类似的假服务器、test_codex、mount_sse_once 和 wait_for_event。不同之处是它在发用户输入前调用 submit_thread_settings 提交预先的线程设置覆盖,用来验证后续请求的模型上下文不会出现多余更新。

调用图:调用 5 个内部函数(mount_sse_once, sse, start_mock_server, local_selections, test_codex);外部调用 8 个(clone, default, submit_thread_settings, wait_for_event, create_dir_all, assert_snapshot!, skip_if_no_network!, vec!)。

snapshot_model_visible_layout_environment_context_includes_one_subagent552–559 ↗
async fn snapshot_model_visible_layout_environment_context_includes_one_subagent() -> Result<()>

作用:测试环境上下文里只有一个子代理时,发给模型看的格式长什么样。它保证单个子代理不会被漏掉或排版错。

数据流:输入在测试里固定为一行“- agent-1: Atlas” → 这行内容被用来生成环境上下文快照 → insta 的 assert_snapshot 宏把结果和保存的快照对比,函数本身不返回业务数据。

调用关系:这是一个很小的快照测试,重点不是跑完整 Codex 对话,而是检查环境上下文片段。它最终通过 assert_snapshot 完成比对。

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

snapshot_model_visible_layout_environment_context_includes_two_subagents562–569 ↗
async fn snapshot_model_visible_layout_environment_context_includes_two_subagents() -> Result<()>

作用:测试环境上下文里有两个子代理时,列表格式是否稳定。它确认多个子代理会按预期一起出现在模型可见内容中。

数据流:输入在测试里固定为两行子代理描述:“agent-1: Atlas”和“agent-2: Juniper” → 生成包含两行列表的环境上下文快照 → assert_snapshot 把这份文本和已保存版本比较,发现格式变化就让测试失败。

调用关系:它和单子代理测试是一对,用来覆盖列表从一个变成多个时的排版情况。它的最终检查由 insta 的 assert_snapshot 宏完成。

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

core/tests/suite/permissions_messages.rs源码 ↗
testtest run

Codex 在和模型对话时,会把当前权限规则塞进一段给模型看的开发者消息里,比如哪些目录可写、网络是否受限、执行命令要不要先问用户。这个文件就像质检员,搭了一个假的模型服务器,模拟几轮对话,然后检查发出去的请求里有没有正确的“权限说明”。它验证几件关键事:刚开始只发一次;权限策略变了就追加新的说明;没变就不要重复刷屏;配置关掉时完全不发;恢复旧会话或从旧会话分叉时,历史权限说明要保留下来,并在新权限出现时补上。最后一个测试还确认可写目录会真实出现在说明里。这样可以避免模型拿着过期规则做事,或者因为缺少权限提示而提出不该做的操作。

函数细节8
permissions_texts28–34 ↗
fn permissions_texts(request: &ResponsesRequest) -> Vec<String>

作用:这个小工具从一次发给模型的请求里,把包含“权限说明”的开发者消息挑出来。测试不用自己一条条翻消息,只要调用它就能知道本轮到底发了几段权限说明。

数据流:进去的是一次模拟服务器收到的请求。它先读取请求中角色为 developer 的文字消息,再只保留里面含有“<permissions instructions>”标记的文字,最后吐出一个字符串列表;它不改请求本身,只做筛选。

调用关系:它是这些测试里的放大镜。多个测试在完成一轮对话后会调用它查看请求内容;它自己把读取消息的细活交给 message_input_texts,然后把结果过滤成测试真正关心的权限说明。

调用图:调用 1 个内部函数(message_input_texts);被 5 处调用(permissions_message_added_on_override_change, permissions_message_includes_writable_roots, permissions_message_not_added_when_no_change, resume_and_fork_append_permissions_messages, resume_replays_permissions_messages)。

permissions_message_sent_once_on_start37–69 ↗
async fn permissions_message_sent_once_on_start() -> Result<()>

作用:这个测试确认:新会话刚开始时,权限说明会随第一条用户输入一起发给模型,而且只发一份。它防止系统漏发权限规则,也防止一开始就重复发同样内容。

数据流:测试先启动一个假的模型服务器,并准备一段假的流式响应。然后创建一个 Codex 测试实例,把审批策略设成“需要时询问用户”。接着提交一条用户文本“hello”,等这一轮结束后,检查模拟服务器收到的请求,确认里面的权限说明数量正好是 1。

调用关系:它由异步测试运行器启动。测试过程依次借助 start_mock_server 搭服务器、mount_sse_once 和 sse 准备响应、test_codex 创建被测 Codex,再用 wait_for_event 等待回合完成,最后用断言确认结果。

调用图:调用 4 个内部函数(mount_sse_once, sse, start_mock_server, test_codex);外部调用 5 个(default, assert_eq!, wait_for_event, skip_if_no_network!, vec!)。

permissions_message_added_on_override_change72–138 ↗
async fn permissions_message_added_on_override_change() -> Result<()>

作用:这个测试确认:如果用户在同一个会话里改了权限相关设置,下一次请求会带上新的权限说明。它保证模型能看到最新规则,而不是继续按旧权限行动。

数据流:测试先安排两次假的模型响应。第一轮用默认的“按需询问”审批策略发送“hello 1”,并记录第一次请求里的权限说明。随后它提交线程设置覆盖,把审批策略改成“永不询问”。第二轮发送“hello 2”后,再查看第二次请求。结果应该是第一次有 1 段权限说明,第二次有 2 段,而且两段内容不同,表示新规则被追加进去了。

调用关系:它模拟“对话中途改权限”的场景。它用 submit_thread_settings 把新设置送进 Codex,用 wait_for_event 等每轮结束,再调用 permissions_texts 读取两次请求里的权限说明,最后用集合去重确认确实出现了两种说明。

调用图:调用 5 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, permissions_texts);外部调用 6 个(default, assert_eq!, submit_thread_settings, wait_for_event, skip_if_no_network!, vec!)。

permissions_message_not_added_when_no_change141–197 ↗
async fn permissions_message_not_added_when_no_change() -> Result<()>

作用:这个测试确认:如果权限规则没有变化,后续对话不会重复追加同一段权限说明。它防止请求内容越来越臃肿,也避免模型被重复信息干扰。

数据流:测试准备两次假的模型响应,创建同一个配置的 Codex。它先发送“hello 1”,等完成后再发送“hello 2”,中间不改任何权限设置。最后分别取出两次请求里的权限说明,确认两次都只有 1 段,并且内容完全一样。

调用关系:它走的是普通连续对话流程。start_mock_server、mount_sse_once、sse 和 test_codex 负责搭测试环境;每次 submit 后用 wait_for_event 等回合结束;permissions_texts 帮它查看请求内容;断言用来证明“没变化就不追加”。

调用图:调用 5 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, permissions_texts);外部调用 5 个(default, assert_eq!, wait_for_event, skip_if_no_network!, vec!)。

permissions_message_omitted_when_disabled200–268 ↗
async fn permissions_message_omitted_when_disabled() -> Result<()>

作用:这个测试确认:当配置明确关闭“把权限说明发给模型”时,无论权限策略怎么变,请求里都不应该出现权限说明。它保证用户或系统的关闭开关真的有效。

数据流:测试创建 Codex 时把 include_permissions_instructions 设为 false,同时仍然设置一个审批策略。它发送第一条消息,之后又把审批策略改成“永不询问”,再发送第二条消息。最后检查两次请求,期望权限说明列表都是空的。

调用关系:它验证的是总开关优先级。测试仍然会用 submit_thread_settings 制造权限变化,用 wait_for_event 等对话完成,但最后结果必须证明:只要关闭开关关着,权限说明生成流程就不应该进入发给模型的请求。

调用图:调用 4 个内部函数(mount_sse_once, sse, start_mock_server, test_codex);外部调用 6 个(default, assert_eq!, submit_thread_settings, wait_for_event, skip_if_no_network!, vec!)。

resume_replays_permissions_messages271–363 ↗
async fn resume_replays_permissions_messages() -> Result<()>

作用:这个测试确认:恢复一个旧会话后,之前发过的权限说明会被重新带进上下文。它很重要,因为恢复会话时模型需要知道这段历史里曾经适用过哪些权限规则。

数据流:测试先开一个会话,第一轮发送“hello 1”并产生初始权限说明;然后改审批策略,再发送“hello 2”,产生第二种权限说明。它保存会话的 rollout 路径和 home 目录,再用这些信息恢复会话。恢复后发送“after resume”,检查第三次请求里有 3 段权限说明,其中去重后只有 2 种,表示历史两种规则被带回来了,并且当前上下文里按历史顺序重放了相关说明。

调用关系:它把流程分成“原会话”和“恢复后会话”。原会话由 test_codex 创建并通过 submit_thread_settings 改权限;恢复阶段调用 builder.resume。最后它用 permissions_texts 检查恢复后那次请求,证明恢复机制没有丢掉权限说明历史。

调用图:调用 5 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, permissions_texts);外部调用 6 个(default, assert_eq!, submit_thread_settings, wait_for_event, skip_if_no_network!, vec!)。

resume_and_fork_append_permissions_messages366–510 ↗
async fn resume_and_fork_append_permissions_messages() -> Result<()>

作用:这个测试同时检查两条路:恢复旧会话和从旧会话分叉。它确认这两种方式在遇到新的权限策略时,都会在原有权限说明后面追加新说明,而且追加结果一致。

数据流:测试先建立一个基础会话,发送第一条消息,再把审批策略改成另一种,发送第二条消息,得到一组基础权限说明。然后它把配置改成新的审批策略,恢复同一个会话并发送消息,检查请求里是在基础说明后多了 1 段新说明。接着它从原会话 fork,也就是像从旧存档复制出一条新分支,再用同样的新权限发送消息,检查分叉请求得到的权限说明和恢复请求完全一样。

调用关系:它覆盖两个会话延续入口:builder.resume 代表恢复旧会话,thread_manager.fork_thread 代表从旧会话开新分支。它依赖 permissions_texts 读取每次请求,用断言比较“基础部分没变、新增部分相同”,从而证明两条路径共用同一套权限说明追加规则。

调用图:调用 6 个内部函数(allow_any, mount_sse_once, sse, start_mock_server, test_codex, permissions_texts);外部调用 7 个(default, assert!, assert_eq!, submit_thread_settings, wait_for_event, skip_if_no_network!, vec!)。

permissions_message_includes_writable_roots513–581 ↗
async fn permissions_message_includes_writable_roots() -> Result<()>

作用:这个测试确认:权限说明里会准确写出额外允许写入的目录。它防止模型只知道抽象规则,却不知道具体哪些文件夹可以动。

数据流:测试先创建一个临时目录,把它转成绝对路径,并用它构造一个 workspace-write 权限配置,也就是“工作区可写”的权限档案。然后它把这个目录放进 workspace roots,启动 Codex,发送一条消息。收到请求后,它取出实际发给模型的权限说明;同时用同一份权限配置重新生成一份期望文本,并统一换行符。最后比较实际文本和期望文本,确认完全一致。

调用关系:它连接了权限配置和最终发给模型的文字。workspace_write_with 用来造权限档案,load_exec_policy 读取执行命令相关策略,PermissionsInstructions::from_permission_profile 生成应该出现的说明文本,permissions_texts 取出实际请求文本,最后断言两边相同。

调用图:调用 8 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, permissions_texts, from_permission_profile, workspace_write_with, try_from);外部调用 8 个(default, new, assert_eq!, load_exec_policy, wait_for_event, skip_if_no_network!, from_ref, vec!)。

core/tests/suite/personality.rs源码 ↗
testtest

这个测试文件像质检员,围绕 Personality(人格,也就是模型回答时采用的沟通风格)反复做实验。它会启动假的服务器,假装自己是模型接口,然后让 Codex 发起一轮对话,再检查真正发出去的请求里有没有正确的说明文字。它覆盖几类关键情况:配置里选了 Friendly 时,基础指令里要加入友好模板;配置为 None 时,不能留下人格模板或占位符;功能开关关掉时,即使用户设置人格也不能生效;用户在对话中改人格时,要额外发一条开发者消息提醒模型风格变了;如果人格没变,就不能重复提醒。它还测试远程模型下发的人格模板,确保本地模板和远程模板不会混用。整体作用是守住“提示词拼装”这条很容易出错的链路。

函数细节15
read_only_text_turn45–53 ↗
fn read_only_text_turn(
    test: &TestCodex,
    text: &str,
    model: String,
    approval_policy: AskForApproval,
) -> Op

作用:这是测试里常用的“小工具”,用来快速造出一条只读权限的用户输入。它适合那些不关心单轮里额外人格设置的测试。

数据流:输入是一套测试环境、用户文字、模型名和审批策略 → 它把人格参数固定成空值 → 再交给 read_only_text_turn_with_personality 组装完整的用户操作,最后返回一个可以提交给 Codex 的 Op。

调用关系:多个测试函数都会先调用它造一条普通对话请求。它自己不做复杂拼装,而是把活交给 read_only_text_turn_with_personality,这样所有测试用的是同一种请求格式。

调用图:调用 1 个内部函数(read_only_text_turn_with_personality);被 8 处调用(config_personality_none_sends_no_personality, config_personality_some_sets_instructions_template, default_personality_is_pragmatic_without_config_toml, user_turn_personality_none_does_not_add_update_message, user_turn_personality_remote_model_template_includes_update_message, user_turn_personality_same_value_does_not_add_update_message, user_turn_personality_skips_if_feature_disabled, user_turn_personality_some_adds_update_message)。

read_only_text_turn_with_personality55–89 ↗
fn read_only_text_turn_with_personality(
    test: &TestCodex,
    text: &str,
    model: String,
    approval_policy: AskForApproval,
    personality: Option<Personality>,
) -> Op

作用:这个函数负责造出一条“用户说了一句话,并附带本轮线程设置”的测试请求。它比 read_only_text_turn 多一个人格参数,所以能测试用户临时切换沟通风格的情况。

数据流:输入是测试环境、文本、模型名、审批策略,以及可选的人格 → 它读取当前测试目录,生成只读权限和本地环境选择,把用户文字、权限、模型设置、人格设置等塞进 ThreadSettingsOverrides → 输出一个 Op::UserInput,提交后 Codex 会按这些设置发请求。

调用关系:read_only_text_turn 会调用它来处理普通情况;远程模型人格测试会直接调用它,把人格明确放进本轮请求里。它是这些测试和真正 Codex 提交流程之间的桥。

调用图:调用 4 个内部函数(cwd_path, local_selections, turn_permission_fields, read_only);被 2 处调用(read_only_text_turn, remote_model_friendly_personality_instructions_with_feature);外部调用 2 个(default, vec!)。

personality_does_not_mutate_base_instructions_without_template92–106 ↗
async fn personality_does_not_mutate_base_instructions_without_template()

作用:这个测试确认:如果模型本身没有人格模板,开启人格功能也不能乱改基础指令。这样可以避免系统把不该加的风格文字硬塞进去。

数据流:它创建临时配置目录,加载默认测试配置,打开 Personality 功能并设置 Friendly → 离线构造模型信息 → 比较模型最终指令和原始基础指令,要求两者完全一样。

调用关系:它直接检查模型信息的指令生成结果,不走网络请求。它依赖测试配置加载和离线模型构造,用来守住最底层的“基础指令不能被误改”规则。

调用图:调用 1 个内部函数(construct_model_info_offline);外部调用 3 个(new, assert_eq!, load_default_config_for_test)。

base_instructions_override_disables_personality_template109–127 ↗
async fn base_instructions_override_disables_personality_template()

作用:这个测试确认:用户手动覆盖了基础指令时,人格模板应该失效。原因很简单,用户明确写了完整指令,系统就不该再擅自追加风格模板。

数据流:它加载测试配置,开启 Personality,设置 Friendly,并把 base_instructions 改成一段自定义文字 → 构造指定模型信息 → 检查模型保存的基础指令和最终指令都等于这段覆盖文字。

调用关系:它和其他网络测试不同,只验证配置和模型信息组合后的结果。它帮助保证手写基础指令的优先级高于人格模板。

调用图:调用 1 个内部函数(construct_model_info_offline);外部调用 3 个(new, assert_eq!, load_default_config_for_test)。

user_turn_personality_none_does_not_add_update_message130–166 ↗
async fn user_turn_personality_none_does_not_add_update_message() -> anyhow::Result<()>

作用:这个测试确认:用户这一轮没有指定人格时,请求里不应该多出“人格更新”消息。这样可以避免模型误以为用户刚改了沟通风格。

数据流:它启动假服务器,挂上一次假的流式响应,开启 Personality 功能但不设置人格 → 提交一条普通只读文本 → 等待回合完成 → 读取发给假服务器的请求,确认 developer 消息里没有 <personality_spec>。

调用关系:它通过 read_only_text_turn 生成输入,通过 mock 服务器接住请求,再用 wait_for_event 等待 Codex 完成。它验证的是“无人格变更时,不插入额外开发者消息”这条线上行为。

调用图:调用 5 个内部函数(mount_sse_once, sse_completed, start_mock_server, test_codex, read_only_text_turn);外部调用 3 个(assert!, wait_for_event, skip_if_no_network!)。

config_personality_some_sets_instructions_template169–213 ↗
async fn config_personality_some_sets_instructions_template() -> anyhow::Result<()>

作用:这个测试确认:配置文件里设置 Friendly 后,发给模型的主指令里会包含本地友好风格模板。也就是说,默认风格要从一开始就体现在 instructions 里。

数据流:它启动假服务器,开启 Personality,并把配置人格设为 Friendly → 提交一条普通用户输入 → 等待完成 → 从请求中取出 instructions 文本,检查包含友好模板,同时确认 developer 消息里没有人格更新块。

调用关系:它使用 read_only_text_turn 触发正常对话流程。它和后面“用户中途改人格”的测试形成对照:配置里的初始人格进主指令,中途变化才进 developer 更新消息。

调用图:调用 5 个内部函数(mount_sse_once, sse_completed, start_mock_server, test_codex, read_only_text_turn);外部调用 3 个(assert!, wait_for_event, skip_if_no_network!)。

config_personality_none_sends_no_personality216–267 ↗
async fn config_personality_none_sends_no_personality() -> anyhow::Result<()>

作用:这个测试确认:配置中明确写 Personality::None 时,系统真的不发送任何人格风格。它还检查模板占位符不会残留在最终指令里。

数据流:它启动假服务器,开启 Personality 功能,但把人格设为 None → 提交一轮对话 → 等待完成 → 检查 instructions 中既没有 Friendly 模板,也没有 Pragmatic 模板,也没有 {{ personality }} 占位符;developer 消息里也不能有 <personality_spec>。

调用关系:它走完整请求链路,验证“关闭人格”不仅是逻辑上关闭,也体现在最终发出的文本里。它依赖 read_only_text_turn 生成标准输入,依赖 mock 响应让回合结束。

调用图:调用 5 个内部函数(mount_sse_once, sse_completed, start_mock_server, test_codex, read_only_text_turn);外部调用 3 个(assert!, wait_for_event, skip_if_no_network!)。

default_personality_is_pragmatic_without_config_toml270–304 ↗
async fn default_personality_is_pragmatic_without_config_toml() -> anyhow::Result<()>

作用:这个测试确认:没有额外配置人格时,系统默认使用 Pragmatic(务实)风格。这样默认体验稳定,不会因为缺少配置就变成无风格或随机风格。

数据流:它启动假服务器,只开启 Personality 功能,不手动设置人格 → 提交一条普通输入 → 等待完成 → 检查发出的 instructions 文本里包含本地 Pragmatic 模板。

调用关系:它验证默认值规则。它和 config_personality_none_sends_no_personality 形成区别:没写配置会走默认务实,明确写 None 才是不发送人格。

调用图:调用 5 个内部函数(mount_sse_once, sse_completed, start_mock_server, test_codex, read_only_text_turn);外部调用 3 个(assert!, wait_for_event, skip_if_no_network!)。

user_turn_personality_some_adds_update_message307–379 ↗
async fn user_turn_personality_some_adds_update_message() -> anyhow::Result<()>

作用:这个测试确认:用户在对话进行中把人格改成 Friendly 后,下一次请求会带一条“沟通风格已更新”的开发者消息。这样模型能知道风格变化是新的要求,而不是普通聊天内容。

数据流:它启动假服务器并准备两次响应,开启 Personality 功能 → 先提交第一轮普通对话并等完成 → 再提交线程设置,把人格改成 Friendly → 提交第二轮对话 → 检查第二个请求的 developer 消息中有 <personality_spec>,并包含更新说明和 Friendly 模板。

调用关系:它用 read_only_text_turn 发两轮,用 submit_thread_settings 模拟用户改设置。它测试的是动态更新路径:不是初始配置,而是会话中途改变人格时该怎么通知模型。

调用图:调用 4 个内部函数(mount_sse_sequence, start_mock_server, test_codex, read_only_text_turn);外部调用 7 个(default, assert!, assert_eq!, submit_thread_settings, wait_for_event, skip_if_no_network!, vec!)。

user_turn_personality_same_value_does_not_add_update_message382–449 ↗
async fn user_turn_personality_same_value_does_not_add_update_message() -> anyhow::Result<()>

作用:这个测试确认:如果用户提交的人格和当前人格一样,系统不要重复发送人格更新消息。这样可以减少无意义提示,避免干扰模型。

数据流:它启动假服务器准备两次响应,配置初始人格为 Pragmatic → 第一轮对话完成后,再提交同样的 Pragmatic 人格设置 → 第二轮对话完成后,检查第二个请求的 developer 消息里找不到 <personality_spec>。

调用关系:它和 user_turn_personality_some_adds_update_message 是一对:一个测“真的变化要通知”,一个测“没变化不要通知”。两者都通过 submit_thread_settings 模拟设置变化。

调用图:调用 4 个内部函数(mount_sse_sequence, start_mock_server, test_codex, read_only_text_turn);外部调用 7 个(default, assert!, assert_eq!, submit_thread_settings, wait_for_event, skip_if_no_network!, vec!)。

instructions_uses_base_if_feature_disabled452–469 ↗
async fn instructions_uses_base_if_feature_disabled() -> anyhow::Result<()>

作用:这个测试确认:Personality 功能开关被关闭时,即使配置里写了 Friendly,最终指令也只能使用基础指令。功能开关就像总电闸,关了就不能偷偷生效。

数据流:它创建临时配置,加载默认测试配置,关闭 Personality 功能,同时设置人格为 Friendly → 离线构造模型信息 → 检查 get_model_instructions 的结果等于 base_instructions。

调用关系:它不走网络,只验证模型指令生成的核心规则。它为后面的请求级测试提供基础保证:功能关掉时,下游不应该看到任何人格效果。

调用图:调用 1 个内部函数(construct_model_info_offline);外部调用 3 个(new, assert_eq!, load_default_config_for_test)。

user_turn_personality_skips_if_feature_disabled472–537 ↗
async fn user_turn_personality_skips_if_feature_disabled() -> anyhow::Result<()>

作用:这个测试确认:就算用户在会话中提交了人格设置,只要 Personality 功能被关闭,请求里也不能加入人格更新消息。它防止实验功能被关闭后仍然漏出行为。

数据流:它启动假服务器并准备两次响应,构建配置时关闭 Personality 功能 → 第一轮对话完成 → 提交线程设置,把人格设为 Pragmatic → 第二轮对话完成 → 检查第二个请求的 developer 消息里没有 <personality_spec>。

调用关系:它走完整会话流程,验证功能开关在运行时路径里也有效。它使用 read_only_text_turn 发送对话,用 submit_thread_settings 触发设置更新,但最终要求系统忽略这次人格更新。

调用图:调用 4 个内部函数(mount_sse_sequence, start_mock_server, test_codex, read_only_text_turn);外部调用 7 个(default, assert!, assert_eq!, submit_thread_settings, wait_for_event, skip_if_no_network!, vec!)。

remote_model_friendly_personality_instructions_with_feature540–651 ↗
async fn remote_model_friendly_personality_instructions_with_feature() -> anyhow::Result<()>

作用:这个测试确认:当模型信息来自远程接口,并且远程模型提供了自己的人格模板时,Friendly 人格会使用远程给的 Friendly 文案,而不是本地默认文案或远程默认文案。

数据流:它启动一个可打印大请求体的假服务器,手工构造一个远程模型信息,其中包含基础指令模板和人格变量 → 把这个模型挂到假 models 接口,再挂一次模型响应接口 → 构建带测试登录态的 Codex,开启 Personality 并选择远程模型和 Friendly → 等模型列表刷新到该模型后提交一轮对话 → 检查请求 instructions 包含远程 Friendly 文案,并且不包含远程 default 文案。

调用关系:它通过 mount_models_once 模拟远程模型目录,通过 wait_for_model_available 等待模型管理器看到新模型,再用 read_only_text_turn_with_personality 触发请求。它专门覆盖“模型元数据来自服务器”的路径。

调用图:调用 9 个内部函数(mount_models_once, mount_sse_once, sse_completed, test_codex, read_only_text_turn_with_personality, wait_for_model_available, create_dummy_chatgpt_auth_for_testing, bytes, default_input_modalities);外部调用 8 个(Limited, default, builder, new, assert!, wait_for_event, skip_if_no_network!, vec!)。

user_turn_personality_remote_model_template_includes_update_message654–796 ↗
async fn user_turn_personality_remote_model_template_includes_update_message() -> anyhow::Result<()>

作用:这个测试确认:远程模型下,用户中途切换人格时,开发者更新消息里也要使用远程模型提供的人格模板。这样不同模型可以有各自合适的风格说明。

数据流:它启动假服务器,构造一个带 Friendly 和 Pragmatic 远程人格模板的模型 → 挂载远程模型列表和两次流式响应 → 构建带测试登录态的 Codex 并开启 Personality → 等远程模型可用后,先用该远程模型跑一轮对话 → 提交线程设置把人格改成 Friendly → 再跑第二轮 → 检查第二个请求的 developer 消息里包含远程 Friendly 文案和“用户请求新沟通风格”的说明。

调用关系:它和 remote_model_friendly_personality_instructions_with_feature 一起测试远程模板:前者看初始 instructions,当前这个看中途更新消息。它依赖 wait_for_model_available 确保模型管理器已经拿到假服务器提供的模型。

调用图:调用 8 个内部函数(mount_models_once, mount_sse_sequence, test_codex, read_only_text_turn, wait_for_model_available, create_dummy_chatgpt_auth_for_testing, bytes, default_input_modalities);外部调用 10 个(Limited, default, builder, new, assert!, assert_eq!, submit_thread_settings, wait_for_event, skip_if_no_network!, vec!)。

wait_for_model_available798–810 ↗
async fn wait_for_model_available(manager: &SharedModelsManager, slug: &str)

作用:这是测试里的等待小工具,用来等远程模型出现在模型管理器的列表里。因为模型列表刷新是异步的,测试不能马上假设它已经准备好。

数据流:输入是共享模型管理器和模型 slug → 它设置一个 2 秒截止时间,循环请求模型列表;如果看到目标模型就返回 → 如果一直没看到,就每 25 毫秒睡一下再试;超过时间后直接 panic,让测试失败并说明等哪个模型超时。

调用关系:远程模型相关的两个测试会调用它。它把“等模型刷新完成”这件容易重复、容易写错的事集中起来,确保后续提交对话时目标模型已经可用。

调用图:被 2 处调用(remote_model_friendly_personality_instructions_with_feature, user_turn_personality_remote_model_template_includes_update_message);外部调用 6 个(from_millis, from_secs, now, list_models, panic!, sleep)。

core/tests/suite/prompt_caching.rs源码 ↗
testtest execution

可以把一次模型请求想成给助手递一叠资料:前面是长期不变的说明,后面才是用户这次说的话。这个文件测试的就是:这叠资料的“固定前缀”能不能保持一致,好让服务端缓存起来;当目录、权限、模型等真的变了时,又能不能只追加必要的新说明。测试会启动一个假的服务器,假装是模型接口,然后让 TestCodex 连续发送多轮用户输入,再检查实际发出的 JSON 请求。它重点看几件事:工具列表是否稳定;系统说明是否一致;AGENTS.md 里的用户指令和 environment_context(环境上下文,包含当前目录、shell、日期、时区、权限等)是否只在该出现时出现;prompt_cache_key(提示词缓存钥匙,用来识别可缓存前缀)是否不会因为临时设置变化而乱变。这样能避免缓存失效、上下文重复、权限信息过期等很难发现的问题。

函数细节15
write_global_instructions37–40 ↗
fn write_global_instructions(home: &Path)

作用:给测试用的临时用户目录写入一个 AGENTS.md 文件,模拟用户提前写好的全局指令。这样测试就能确认这些指令会被正确放进发给模型的提示里。

数据流:输入是一个 home 目录路径 → 它在这个目录下拼出 AGENTS.md 文件路径,并写入文本“be consistent and helpful” → 输出没有返回值,但磁盘上多了一个测试用指令文件;写失败会让测试直接报错。

调用关系:它通常作为 test_codex 的预构建钩子被调用,在 Codex 测试实例启动前准备好用户说明。后面的多轮请求测试会通过检查请求内容,间接确认这个文件里的文字被读进了提示词前缀。

调用图:外部调用 2 个(join, write)。

text_user_input42–44 ↗
fn text_user_input(text: String) -> serde_json::Value

作用:把一段普通文字包装成测试里期望的用户消息 JSON。测试用它来少写重复的 JSON 拼装代码。

数据流:输入是一段字符串 → 它把这段字符串放进一个单元素列表,再交给 text_user_input_parts 统一生成 JSON → 输出是形如“用户发了一条文本消息”的 serde_json::Value。

调用关系:它是 text_user_input_parts 的简化入口,被检查请求结构的测试使用,尤其在比较第一轮、第二轮用户消息是否按预期排列时很方便。

调用图:调用 1 个内部函数(text_user_input_parts);被 2 处调用(send_user_turn_with_changes_sends_environment_context, send_user_turn_with_no_changes_does_not_send_environment_context);外部调用 1 个(vec!)。

text_user_input_parts46–55 ↗
fn text_user_input_parts(texts: Vec<String>) -> serde_json::Value

作用:把多段文本包装成一个用户消息 JSON。它用于测试那些一条用户消息里同时包含用户说明和环境上下文的情况。

数据流:输入是一组字符串 → 它把每段字符串变成一个 input_text 内容块,再放进 role 为 user、type 为 message 的 JSON 对象 → 输出是可直接拿来和请求体里的 input 项比较的 JSON 值。

调用关系:text_user_input 会调用它处理单段文本;多个测试也直接调用它,构造“用户说明 + 环境上下文”这种组合消息,用来和真实请求逐字比较。

调用图:被 3 处调用(send_user_turn_with_changes_sends_environment_context, send_user_turn_with_no_changes_does_not_send_environment_context, text_user_input);外部调用 1 个(json!)。

assert_default_env_context57–67 ↗
fn assert_default_env_context(text: &str, cwd: &str)

作用:检查一段环境上下文是不是默认形态,并且包含当前工作目录和用户 shell。环境上下文就是告诉模型“现在在哪个目录、用什么命令行环境、日期时区是什么”的那段说明。

数据流:输入是待检查文本和期望的当前目录字符串 → 它先调用 assert_env_context_fragment 检查基本外壳,再查找 cwd 和 shell 标签 → 没有返回值;如果缺少这些内容,测试失败并打印相关文本。

调用关系:它被多项测试用来确认环境上下文没有丢关键信息。它把通用检查交给 assert_env_context_fragment,自己再补充默认环境下必须出现的目录和 shell 检查。

调用图:调用 1 个内部函数(assert_env_context_fragment);被 4 处调用(per_turn_overrides_keep_cached_prefix_and_key_constant, prefixes_context_and_instructions_once_and_consistently_across_requests, send_user_turn_with_changes_sends_environment_context, send_user_turn_with_no_changes_does_not_send_environment_context);外部调用 1 个(assert!)。

assert_env_context_fragment69–86 ↗
fn assert_env_context_fragment(text: &str)

作用:检查一段文本是不是合法的 environment_context 片段。它不关心具体目录或权限,只确认这段环境说明的基本格式完整。

数据流:输入是一段文本 → 它检查文本是否以环境上下文开头标签开始、是否包含 current_date 和 timezone、是否以结束标签收尾 → 没有返回值;任何一项不对都会让测试失败。

调用关系:它是环境上下文检查的底层小工具。assert_default_env_context 会先用它做基础检查;权限或设置变化相关的测试也会直接用它检查新追加的环境说明。

调用图:被 3 处调用(assert_default_env_context, overrides_turn_context_but_keeps_cached_prefix_and_key_constant, send_user_turn_with_changes_sends_environment_context);外部调用 1 个(assert!)。

assert_tool_names88–104 ↗
fn assert_tool_names(body: &serde_json::Value, expected_names: &[&str])

作用:检查发给模型的工具列表名字是否正好等于测试期望。工具就是模型可以调用的能力,比如执行命令、改文件、搜索等。

数据流:输入是请求体 JSON 和期望的工具名列表 → 它从 body["tools"] 里取出每个工具的 name 或 type 字段,整理成字符串列表 → 再和期望列表逐项比较;不一致就让测试失败。

调用关系:它只被 prompt_tools_are_consistent_across_requests 使用,用来验证连续两次请求的工具清单稳定,避免因为工具顺序或内容变化破坏提示词缓存。

调用图:被 1 处调用(prompt_tools_are_consistent_across_requests);外部调用 1 个(assert_eq!)。

normalize_newlines106–108 ↗
fn normalize_newlines(text: &str) -> String

作用:把 Windows 风格换行转换成普通换行,避免同一段说明因为操作系统换行不同而被误判不相等。

数据流:输入是一段文本 → 它把所有“\r\n”替换成“\n” → 输出标准化后的字符串,不改动原文本。

调用关系:它被 gpt_5_tools_without_apply_patch_append_apply_patch_instructions 用来比较两次 instructions。这样测试关注的是内容是否一致,而不是换行符这种平台差异。

prompt_tools_are_consistent_across_requests111–223 ↗
async fn prompt_tools_are_consistent_across_requests() -> anyhow::Result<()>

作用:测试连续两轮请求里,模型说明和工具列表是否保持一致。这样可以保证可缓存的提示词前缀不会因为无关变化而失效。

数据流:测试先启动假模型服务器,并准备两次流式响应 → 创建带全局指令、指定模型和功能开关的 TestCodex → 连续提交“hello 1”和“hello 2”两轮输入 → 读取两次实际请求体,检查 instructions 和 tools 都符合预期且两次一致。

调用关系:这是提示词缓存稳定性的核心测试之一。它借助 start_mock_server、mount_sse_once 和 test_codex 搭出完整请求流程,再把工具名检查交给 assert_tool_names。

调用图:调用 5 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, assert_tool_names);外部调用 6 个(default, assert_eq!, cfg!, wait_for_event, skip_if_no_network!, vec!)。

gpt_5_tools_without_apply_patch_append_apply_patch_instructions226–302 ↗
async fn gpt_5_tools_without_apply_patch_append_apply_patch_instructions() -> anyhow::Result<()>

作用:测试在某些 GPT-5 工具配置下,即使没有直接提供 apply_patch 工具,相关补丁说明也能稳定地进入 instructions。重点不是检查补丁本身,而是检查两轮说明不会前后不一致。

数据流:测试启动假服务器,创建使用 gpt-5.2 的 Codex 测试实例 → 连续提交两条用户消息 → 捕获两次请求里的 instructions → 确认第一轮说明不是空的,并在统一换行后确认第二轮和第一轮完全一样。

调用关系:它验证的是 instructions 的缓存友好性。它依赖假服务器和 TestCodex 跑完整请求,比较文本时调用 normalize_newlines 处理平台换行差异。

调用图:调用 4 个内部函数(mount_sse_once, sse, start_mock_server, test_codex);外部调用 6 个(default, assert!, assert_eq!, wait_for_event, skip_if_no_network!, vec!)。

prefixes_context_and_instructions_once_and_consistently_across_requests305–399 ↗
async fn prefixes_context_and_instructions_once_and_consistently_across_requests() -> anyhow::Result<()>

作用:测试用户指令和环境上下文是否只在开头放一次,并在后续请求里作为缓存前缀复用。这样能避免每轮都重复塞相同背景资料。

数据流:测试准备 AGENTS.md、启动假服务器、创建 Codex → 发送两轮用户输入 → 检查第一轮 input 里有权限消息、缓存用的上下文用户消息、真实用户消息 → 再检查第二轮请求的开头和第一轮完全相同,后面只追加第二条用户消息。

调用关系:它把环境内容检查交给 assert_default_env_context,也用 text 结构直接比对 JSON。它展示了提示词缓存最理想的状态:固定背景不变,新的用户话语只往后追加。

调用图:调用 5 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, assert_default_env_context);外部调用 6 个(default, assert!, assert_eq!, wait_for_event, skip_if_no_network!, vec!)。

overrides_turn_context_but_keeps_cached_prefix_and_key_constant402–529 ↗
async fn overrides_turn_context_but_keeps_cached_prefix_and_key_constant() -> anyhow::Result<()>

作用:测试线程级设置变化后,系统会追加新的权限和环境说明,但不会改掉已经可缓存的旧前缀和 prompt_cache_key。换句话说,变化要被告诉模型,但不能把缓存基础打乱。

数据流:测试先发第一轮普通消息 → 然后提交新的线程设置,比如永不请求批准、高推理强度、可写目录权限等 → 再发第二轮消息 → 检查两次请求的 prompt_cache_key 相同,第二次请求以前一次 input 为前缀,并在后面追加更新后的权限消息、环境上下文和新用户消息。

调用关系:它模拟“会话进行中改权限”的场景。它调用权限配置相关工具生成可写工作区,再用 assert_env_context_fragment 确认追加的环境说明格式正确并包含新的权限详情。

调用图:调用 6 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, assert_env_context_fragment, workspace_write_with);外部调用 10 个(default, new, assert!, assert_eq!, assert_ne!, submit_thread_settings, wait_for_event, json!, skip_if_no_network!, vec!)。

override_before_first_turn_emits_environment_context532–688 ↗
async fn override_before_first_turn_emits_environment_context() -> anyhow::Result<()>

作用:测试如果在第一条用户消息之前就修改了线程设置,第一次请求也必须带上环境上下文和新的权限说明。否则模型一开始就会拿到过期或不完整的背景。

数据流:测试先创建 Codex,但不马上发用户消息 → 先提交模型、审批策略、推理力度、协作模式等覆盖设置 → 再发送第一条用户输入 → 读取请求体,检查模型和 reasoning 字段符合覆盖后的结果,并确认 input 中能找到环境上下文、权限说明和用户原文。

调用关系:它覆盖的是“开聊前先改设置”的边界情况。它通过 submit_thread_settings 把设置送进 Codex,再用捕获到的第一轮请求确认这些设置已经反映到提示里。

调用图:调用 4 个内部函数(mount_sse_once, sse, start_mock_server, test_codex);外部调用 7 个(default, assert!, assert_eq!, submit_thread_settings, wait_for_event, skip_if_no_network!, vec!)。

per_turn_overrides_keep_cached_prefix_and_key_constant691–816 ↗
async fn per_turn_overrides_keep_cached_prefix_and_key_constant() -> anyhow::Result<()>

作用:测试只对某一轮生效的设置覆盖,不应该破坏已有缓存前缀和 prompt_cache_key。临时换目录、换模型、改权限时,系统应该在那一轮追加说明,而不是重写历史前缀。

数据流:测试先发第一轮普通消息 → 第二轮提交用户输入时同时带上本轮专用设置,比如新工作目录、可写目录、模型 o3、高推理强度等 → 捕获两次请求 → 确认缓存钥匙相同,第二次请求以前一次 input 为开头,并追加开发者设置更新、用户环境上下文和第二条用户消息。

调用关系:它和线程级覆盖测试相似,但设置是随第二条用户输入一起发的。它调用 local_selections 和 turn_permission_fields 生成本轮环境与权限,再用 assert_default_env_context 检查新目录确实进入环境上下文。

调用图:调用 8 个内部函数(mount_sse_once, sse, start_mock_server, local_selections, test_codex, turn_permission_fields, assert_default_env_context, workspace_write_with);外部调用 9 个(default, new, assert!, assert_eq!, assert_ne!, wait_for_event, json!, skip_if_no_network!, vec!)。

send_user_turn_with_no_changes_does_not_send_environment_context819–955 ↗
async fn send_user_turn_with_no_changes_does_not_send_environment_context() -> anyhow::Result<()>

作用:测试当用户每轮都显式传入设置,但这些设置其实和默认值一样时,系统不要误以为环境变了,也不要重复发送新的环境上下文。

数据流:测试读取当前默认目录、权限、模型、推理配置等 → 第一轮和第二轮都提交相同的覆盖设置以及不同用户文本 → 捕获两次请求 → 确认第一轮包含权限消息、上下文用户消息和第一条用户消息;第二轮只是在这些内容后追加第二条用户消息,没有多发环境更新。

调用关系:它防止“无变化也触发更新”的浪费和缓存破坏。它用 text_user_input 与 text_user_input_parts 构造期望 JSON,并用 assert_default_env_context 确认第一轮缓存上下文本身是正确的。

调用图:调用 8 个内部函数(mount_sse_once, sse, start_mock_server, local_selections, test_codex, assert_default_env_context, text_user_input, text_user_input_parts);外部调用 6 个(default, assert_eq!, wait_for_event, Array, skip_if_no_network!, vec!)。

send_user_turn_with_changes_sends_environment_context958–1124 ↗
async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Result<()>

作用:测试当某一轮的设置真的变了,比如权限策略、模型或推理摘要变化,系统必须发送新的环境上下文和设置说明。这样模型才能知道当前这轮和之前不一样。

数据流:测试先用默认等价设置发送第一轮 → 第二轮改成不同的审批策略、禁用权限配置、详细摘要和新模型 → 捕获请求体 → 确认第二轮请求以前一轮内容为前缀,然后追加开发者设置更新、包含新权限状态的环境上下文,最后才是第二条用户消息。

调用关系:它和“无变化不发送环境上下文”的测试形成对照。它调用 turn_permission_fields 生成改变后的权限字段,并用 assert_default_env_context、assert_env_context_fragment 检查旧环境和新环境都符合预期。

调用图:调用 10 个内部函数(mount_sse_once, sse, start_mock_server, local_selections, test_codex, turn_permission_fields, assert_default_env_context, assert_env_context_fragment, text_user_input, text_user_input_parts);外部调用 8 个(default, assert!, assert_eq!, assert_ne!, wait_for_event, Array, skip_if_no_network!, vec!)。

core/tests/suite/prompt_debug_tests.rs源码 ↗
testtest run

这个测试像是在检查一份要交给助手的“任务说明书”有没有装订完整。程序会临时建两个空文件夹,一个假装是 Codex 的主目录,一个假装是当前工作目录;然后在主目录里写入 AGENTS.md,里面放一段全局测试说明。接着它用这些临时路径做出一份配置,并创建一个“用户说明提供者”,也就是专门读取主目录里说明文件的小工具。最后调用 build_prompt_input,把配置、用户输入和说明提供者交进去,让核心代码生成真正要发给模型的消息列表。测试会确认:最后一条消息确实是用户输入的“hello from debug prompt”;同时整份消息列表里某处包含 AGENTS.md 里的全局说明。这样可以防止以后改代码时,不小心把用户问题或全局规则漏掉。

函数细节1
build_prompt_input_includes_context_and_user_message17–70 ↗
async fn build_prompt_input_includes_context_and_user_message() -> Result<()>

作用:这个测试函数确认 build_prompt_input 生成的提示内容既包含用户这次说的话,也包含 Codex 主目录里的全局说明。有人改提示组装逻辑后,可以靠它及时发现“漏装说明”或“用户消息位置不对”的问题。

数据流:进去的是两个临时目录、一段写进 AGENTS.md 的测试说明、以及一条用户文本输入。函数先搭好临时配置,再创建读取用户说明的提供者,然后把这些交给 build_prompt_input。出来的是一组准备发给模型的消息;测试检查这组消息的最后一条是不是用户输入,并检查其中有没有包含全局测试说明。它不会改真实用户文件,只会在临时目录里写测试用文件。

调用关系:它是测试框架在跑测试时直接调用的异步测试。它把准备好的配置和输入交给外部的 build_prompt_input,让核心提示构建流程真正跑一遍;之后用 assert_eq! 和 assert! 做核对,像验收员一样确认结果符合预期。

调用图:调用 1 个内部函数(new);外部调用 10 个(new, new, assert!, assert_eq!, build_prompt_input, default, default, current_exe, write, vec!)。

core/tests/suite/token_budget.rs源码 ↗
testtest execution

可以把模型的上下文窗口想成一个固定大小的背包,token 就是往背包里塞的字数碎片。这个测试文件检查系统有没有在正确的时候提醒模型:背包还剩多少空间。它会启动一个假的服务器,假装模型按顺序返回回答、消耗 token、调用工具,然后检查真正发给模型的请求里有没有插入正确的 <token_budget> 提示。这里还测试了几个关键场景:第一次完整上下文时要说明当前窗口编号和剩余空间;消耗到 25%、50%、75% 这些门槛时才追加提醒;模型调用 get_context_remaining 工具时要返回剩余空间;如果不知道窗口大小,就明确说 unknown;压缩上下文或主动开启 new_context 后,窗口编号要变成新的,旧历史也要被丢掉。它不是产品功能本身,而是一组安全网,保证 token 预算这个功能以后改代码时不被悄悄弄坏。

函数细节8
token_budget_texts29–35 ↗
fn token_budget_texts(request: &ResponsesRequest) -> Vec<String>

作用:从一次发给模型的请求里,挑出所有以 <token_budget> 开头的开发者提示文本。测试用它来确认系统有没有把“还剩多少 token”的说明塞进请求里。

数据流:输入是一份 ResponsesRequest,也就是测试记录下来的模型请求。它先读取这个请求里 developer 角色的文本,再只保留以 <token_budget> 开头的内容,最后返回这些文本列表;它不修改请求本身。

调用关系:它是测试里的小帮手。各个测试先通过 mock 服务器收集请求,然后调用 token_budget_texts 来检查 token 预算提示是否出现、内容是否正确。它内部把读取文本的活交给 message_input_texts。

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

tool_names37–46 ↗
fn tool_names(request: &ResponsesRequest) -> Vec<String>

作用:从一次发给模型的请求里,取出系统暴露给模型的工具名字。测试用它确认 token 预算功能打开后,get_context_remaining 或 new_context 这些工具是否真的可用。

数据流:输入是一份 ResponsesRequest。它读取请求的 JSON 正文,找到 tools 数组,再从每个工具对象里拿 name 字段,最后输出工具名列表;如果没有工具或字段缺失,就只返回能找到的名字。

调用关系:它也是测试辅助函数。需要检查工具是否开放时,测试会把 mock 服务器记录的请求交给它。它内部通过 body_json 读取原始请求内容。

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

token_budget_context_is_only_emitted_with_full_context49–98 ↗
async fn token_budget_context_is_only_emitted_with_full_context() -> Result<()>

作用:测试最基本的情况:完整上下文请求里应该带 token 预算提示,而普通的后续上下文更新不应该让“当前窗口编号”乱跳。它保证系统不会因为换工作目录之类的小变化,就误以为开启了新上下文窗口。

数据流:它先跳过无网络环境,再启动 mock 服务器,准备两次假的模型响应。接着创建一个启用了 TokenBudget 功能、上下文窗口大小为 128000 的测试会话,提交两轮用户输入,其中第二轮带了新的本地环境路径。最后它读取两次请求,检查两次请求中的 token_budget 文本都显示同一个线程、窗口 0、剩余 95% 的可用 token。

调用关系:这是一个端到端测试,自己搭建 mock 服务和测试会话。它调用 mount_sse_sequence 安排假响应,调用 start_mock_server 启动服务器,调用 test_codex 创建被测系统,并用 token_budget_texts 这个本文件的辅助函数检查结果。

调用图:调用 3 个内部函数(mount_sse_sequence, start_mock_server, test_codex);外部调用 4 个(assert_eq!, skip_if_no_network!, create_dir_all, vec!)。

token_budget_remaining_context_emits_on_first_threshold_crossing101–183 ↗
async fn token_budget_remaining_context_emits_on_first_threshold_crossing() -> Result<()>

作用:测试 token 消耗跨过提醒门槛时,系统只在第一次跨过时追加剩余空间提示。这样模型能及时知道快用完了,但不会每一轮都被重复提示刷屏。

数据流:它设置一个 10000 token 的窗口,并准备五次模型响应,其中前四次报告不同的总 token 消耗。然后连续提交五轮输入,收集五次请求。最后它检查:第一轮有完整预算提示;消耗跨过约 25%、50%、75% 时,才分别多出“还剩 7000、4500、1500 token”的提示;没跨新门槛的轮次不会新增提示。

调用关系:这个测试模拟一段逐渐变长的对话。mock 响应由 mount_sse_sequence 安排,测试会话由 test_codex 创建,断言时用 token_budget_texts 抽出预算提示,并和预期文本比较。

调用图:调用 3 个内部函数(mount_sse_sequence, start_mock_server, test_codex);外部调用 4 个(assert_eq!, format!, skip_if_no_network!, vec!)。

get_context_remaining_returns_token_budget_remaining_fragment186–252 ↗
async fn get_context_remaining_returns_token_budget_remaining_fragment() -> Result<()>

作用:测试模型调用 get_context_remaining 工具时,系统会把当前剩余 token 预算作为工具结果返回。这个工具就像模型主动问一句:“我现在还剩多少空间?”

数据流:它先准备三次假响应:第一轮普通回答并消耗 2500 token;第二轮模型调用 get_context_remaining;第三轮模型收到工具结果后继续完成。测试提交两轮用户输入后,读取三次请求,检查第二次请求确实暴露了 get_context_remaining 工具,也检查请求里已有正确预算提示,最后确认第三次请求带回给工具调用的输出正是“还剩 7000 token”的 token_budget 文本。

调用关系:它覆盖的是工具调用链路。mock 服务器假装模型发起函数调用,系统执行工具并把结果放进后续请求。测试用 tool_names 检查工具是否暴露,用 token_budget_texts 检查提示文本,用请求里的 function_call_output_content_and_success 检查工具返回内容。

调用图:调用 3 个内部函数(mount_sse_sequence, start_mock_server, test_codex);外部调用 5 个(assert!, assert_eq!, format!, skip_if_no_network!, vec!)。

get_context_remaining_returns_unknown_when_window_is_unavailable255–315 ↗
async fn get_context_remaining_returns_unknown_when_window_is_unavailable() -> Result<()>

作用:测试当系统不知道模型上下文窗口大小时,get_context_remaining 不会瞎编数字,而是返回 unknown。这个行为很重要,因为错误的剩余 token 数比不知道更危险。

数据流:它创建 mock 服务器,并把模型信息里的 context_window 和 max_context_window 都改成 None,也就是没有可用窗口大小。随后启用 TokenBudget,提交一轮让模型调用 get_context_remaining 的输入。最后它确认请求中工具是开放的,但没有自动插入 token_budget 数字提示;工具结果则明确写着“unknown tokens left”。

调用关系:这个测试专门验证异常信息缺失的分支。它通过 test_codex 的模型信息覆盖功能制造“窗口未知”的情况,通过 tool_names 确认工具入口仍存在,再检查后续请求里的函数调用输出。

调用图:调用 3 个内部函数(mount_sse_sequence, start_mock_server, test_codex);外部调用 4 个(assert!, assert_eq!, skip_if_no_network!, vec!)。

token_budget_context_uses_new_window_after_compaction318–374 ↗
async fn token_budget_context_uses_new_window_after_compaction() -> Result<()>

作用:测试执行上下文压缩后,token 预算提示会切到新的上下文窗口编号。上下文压缩可以理解为把旧聊天记录整理成摘要,给后面腾空间。

数据流:它启动 mock 服务器,准备三次响应:压缩前一次、压缩过程一次、压缩后一次。然后配置一个 OpenAI 兼容的测试模型提供方,启用 TokenBudget,并提交一轮输入。接着向系统发送 Compact 操作,等待压缩完成,再提交下一轮输入。最后它检查第三次请求里的 token_budget 显示当前 context window 是 1,而不是之前的 0,并且剩余 token 回到新窗口的 95%。

调用关系:这个测试把普通对话和系统级 Compact 操作串起来。它用 built_in_model_providers 做测试模型提供方配置,用 wait_for_event 等待压缩回合真正结束,再用 token_budget_texts 验证压缩后的请求。

调用图:调用 3 个内部函数(mount_sse_sequence, start_mock_server, test_codex);外部调用 6 个(assert_eq!, built_in_model_providers, wait_for_event, format!, skip_if_no_network!, vec!)。

new_context_tool_starts_new_window_before_follow_up377–458 ↗
async fn new_context_tool_starts_new_window_before_follow_up() -> Result<()>

作用:测试模型调用 new_context 工具后,系统会在继续同一轮任务前开启一个全新的上下文窗口。重点是旧窗口历史要被丢掉,新请求要带新的完整 token 预算提示。

数据流:它准备三段假响应:第一段模型调用 new_context;第二段模型继续调用 update_plan;第三段模型最终回答。测试提交一轮用户输入后,检查第一份请求里确实暴露了 new_context 工具;再检查第三份请求里 token_budget 已经变成窗口 1、剩余空间恢复到新窗口;同时确认第三份请求不再包含最初那句用户输入,说明旧历史已被清掉。最后它还生成一份请求快照,把线程 ID 替换成占位符后用于快照断言。

调用关系:这是 new_context 工具的端到端流程测试。mock 响应推动模型先开新窗口、再继续执行计划;测试用 tool_names、token_budget_texts 和请求内容检查新窗口是否生效,并调用 format_labeled_requests_snapshot 与 assert_snapshot 保存可读快照,方便以后比较请求结构有没有变化。

调用图:调用 5 个内部函数(default, format_labeled_requests_snapshot, mount_sse_sequence, start_mock_server, test_codex);外部调用 6 个(assert!, assert_eq!, assert_snapshot!, json!, skip_if_no_network!, vec!)。

提供方请求整形

这些测试关注组装后的提示和设置如何转换为面向具体提供方的请求载荷和工具定义。

core/tests/suite/json_result.rs源码 ↗
testtest run

这个测试文件只在非 Windows 系统上运行。它先准备一份 JSON Schema(可以理解成“答案格式说明书”),规定回答里必须有 explanation 和 final_answer 两个字符串字段。测试会启动一个假的模型服务器,不真的去调用线上模型,而是检查 Codex 发来的请求里有没有带上这份严格的 JSON 格式要求。只有请求符合要求,假服务器才返回一段模拟的助手消息。然后测试创建一个测试版 Codex,像真实用户一样提交“hello world”,并要求最终输出符合那份 JSON Schema。最后它等待 Codex 发出助手消息,把消息文本当 JSON 解析,确认里面确实有 explanation 和 final_answer。简单说,它像一次彩排:检查“点菜时有没有把菜单要求说清楚”,也检查“端上来的菜是不是按要求的样子”。

函数细节3
codex_returns_json_result_for_gpt534–36 ↗
async fn codex_returns_json_result_for_gpt5() -> anyhow::Result<()>

作用:这是一个针对 gpt-5.4 模型名的测试入口。它不自己写完整测试流程,而是把模型名交给通用测试函数去跑一遍。

数据流:进去的是测试框架启动这个异步测试时给的执行环境;函数内部把字符串 "gpt-5.4" 作为模型名传给 codex_returns_json_result;出来的是测试是否成功的结果,如果通用流程里任何一步失败,这个测试也失败。

调用关系:它是测试框架直接调用的入口之一。真正搭假服务器、提交用户输入、检查 JSON 结果的活儿都交给 codex_returns_json_result,这样同一套检查逻辑可以复用。

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

codex_returns_json_result_for_gpt5_codex39–41 ↗
async fn codex_returns_json_result_for_gpt5_codex() -> anyhow::Result<()>

作用:这是另一个测试入口,用来跑同一套“JSON 结果必须正确”的检查。它同样把模型名传给通用测试函数。

数据流:进去的是测试框架的异步测试运行过程;它创建模型名字符串 "gpt-5.4",交给 codex_returns_json_result;最后返回测试成功或失败的信息。

调用关系:它由测试框架在测试时调用,并把后续流程委托给 codex_returns_json_result。这样文件里可以保留多个面向不同模型场景的测试入口,而核心检查只写一份。

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

codex_returns_json_result43–122 ↗
async fn codex_returns_json_result(model: String) -> anyhow::Result<()>

作用:这是本文件的核心测试流程。它验证 Codex 在收到“请按这个 JSON Schema 输出”的要求后,会把这个要求正确放进请求里,并能把模型返回的 JSON 消息交给调用方。

数据流:进去的是一个模型名字符串;函数先在没有网络条件时跳过测试,然后启动假模型服务器,准备一段假的服务端事件流(SSE,意思是服务器一条条推送事件的响应)。接着它设置一个请求匹配规则:只有请求正文里的 text.format 包含正确的名称、类型、严格模式和 schema,假服务器才响应。之后它创建测试用 Codex,构造用户输入、权限设置和线程设置,把 JSON Schema 一起提交进去。最后它等待 Codex 发出助手消息,把消息内容解析成 JSON,并检查 explanation 和 final_answer 两个字段是否等于预期值;成功则返回 Ok,异常则让测试失败。

调用关系:它被 codex_returns_json_result_for_gpt5 和 codex_returns_json_result_for_gpt5_codex 调用,是两条测试入口共用的主流程。它会调用 start_mock_server 建假服务器,用 sse、ev_assistant_message、ev_completed 准备假响应,用 mount_sse_once_match 挂上“只有请求格式正确才响应”的规则,用 test_codex 创建测试实例,用 turn_permission_fields 和 local_selections 准备运行权限和环境,最后用 wait_for_event 等 Codex 的助手消息;如果等到的不是助手消息,就用 bail! 报错结束测试。

调用图:调用 6 个内部函数(mount_sse_once_match, sse, start_mock_server, local_selections, test_codex, turn_permission_fields);被 2 处调用(codex_returns_json_result_for_gpt5, codex_returns_json_result_for_gpt5_codex);外部调用 7 个(default, bail!, assert_eq!, wait_for_event, from_str, skip_if_no_network!, vec!)。

core/tests/suite/web_search.rs源码 ↗
testtest run

网页搜索很敏感,因为它决定模型是只能用缓存结果,还是可以真的连到外网查新内容。这个测试文件就像一个质检员:它不去真的访问 OpenAI 服务,而是启动一个假的服务器,拦住 Codex 发出的请求,然后检查请求里的 JSON 内容。测试会模拟不同情况,比如配置成 cached(缓存搜索)、旧功能开关同时存在、功能开关被关掉、同一段对话里权限变化,以及用户在 config.toml 里写了搜索范围、地点、上下文大小等设置。核心检查点是 web_search 工具里的 external_web_access 字段:false 表示不能实时访问外网,true 表示可以。这样可以防止以后改代码时,不小心把只读场景变成联网搜索,或者把用户写在配置文件里的限制漏传出去。

函数细节6
find_web_search_tool15–22 ↗
fn find_web_search_tool(body: &Value) -> &Value

作用:这个小工具函数从一次请求的 JSON 正文里找出 type 是 web_search 的工具配置。测试用它来避免每个测试都重复写一遍“从工具列表里翻出网页搜索工具”的代码。

数据流:输入是一份请求正文 JSON。它先进入 body["tools"],确认这里是一个数组;再逐个查看数组里的工具,找到 type 等于 web_search 的那一项;最后把这项作为结果返回。如果请求里没有工具数组,或者没有 web_search,它会直接让测试失败,因为这说明请求结构已经不符合预期。

调用关系:它是这些测试里的共同放大镜。各个测试先通过假服务器拿到 Codex 发出的请求正文,再把正文交给 find_web_search_tool;找到网页搜索工具后,测试才继续检查 external_web_access、filters、user_location 等具体字段。

调用图:被 5 处调用(web_search_mode_cached_sets_external_web_access_false, web_search_mode_defaults_to_cached_when_features_disabled, web_search_mode_takes_precedence_over_legacy_flags, web_search_mode_updates_between_turns_with_permission_profile, web_search_tool_config_from_config_toml_is_forwarded_to_request)。

web_search_mode_cached_sets_external_web_access_false25–60 ↗
async fn web_search_mode_cached_sets_external_web_access_false()

作用:这个测试确认:当网页搜索模式明确设为 cached,也就是只用缓存搜索时,请求里会写明不允许实时访问外网。它防止“缓存搜索”被误发成“联网搜索”。

数据流:测试先跳过没有网络条件的环境,然后启动一个假的服务端,并准备一段假的流式响应。接着创建一个测试用 Codex,对它设置模型和 web_search_mode = Cached。提交一轮只读权限的对话后,假服务端记录下 Codex 发来的请求。测试从请求 JSON 里找出 web_search 工具,检查 external_web_access 是否是 false。结果不是 false 就说明配置传错了,测试失败。

调用关系:它先借助 start_mock_server 和 mount_sse_once 搭好假后端,再用 test_codex 建一个测试对话。对话提交后,它调用 find_web_search_tool 找到被发送的网页搜索工具,最后用断言确认 cached 模式确实压住了外网访问。

调用图:调用 6 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, find_web_search_tool, read_only);外部调用 3 个(assert_eq!, skip_if_no_network!, vec!)。

web_search_mode_takes_precedence_over_legacy_flags63–102 ↗
async fn web_search_mode_takes_precedence_over_legacy_flags()

作用:这个测试确认新的 web_search_mode 配置比旧的功能开关更有决定权。即使旧开关看起来允许网页搜索请求,只要新模式设成 cached,最终还是不能实时访问外网。

数据流:测试启动假服务器并准备一段假响应。随后创建测试 Codex,先打开旧的 WebSearchRequest 功能开关,再把新的 web_search_mode 设为 Cached。提交一轮只读对话后,它读取假服务器收到的请求,从里面找出 web_search 工具,检查 external_web_access 是否为 false。这个结果证明新配置覆盖了旧开关。

调用关系:这个测试模拟“新旧配置同时出现”的容易出错场景。它和其他测试一样通过 test_codex 发起请求,通过假服务器截获请求,再交给 find_web_search_tool 提取网页搜索工具;它验证的是配置优先级,而不是搜索本身。

调用图:调用 6 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, find_web_search_tool, read_only);外部调用 3 个(assert_eq!, skip_if_no_network!, vec!)。

web_search_mode_defaults_to_cached_when_features_disabled105–148 ↗
async fn web_search_mode_defaults_to_cached_when_features_disabled()

作用:这个测试确认:即使相关功能开关都被关掉,只要网页搜索模式没有变成实时搜索,默认仍然应该按 cached 处理。也就是说,安全默认值是“不实时访问外网”。

数据流:测试先准备假服务器和假响应。然后创建测试 Codex,把 web_search_mode 设置为 Cached,同时关闭 WebSearchCached 和 WebSearchRequest 两个功能开关。提交一轮只读对话后,它读取请求正文,找到 web_search 工具,并检查 external_web_access 是不是 false。这样可以确认功能开关组合没有把默认行为弄成联网访问。

调用关系:它覆盖的是功能开关被关闭时的兜底行为。假服务器负责收集请求,test_codex 负责生成一次真实的测试对话,find_web_search_tool 负责定位请求中的网页搜索配置,断言负责确认最终外网权限没有被打开。

调用图:调用 6 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, find_web_search_tool, read_only);外部调用 3 个(assert_eq!, skip_if_no_network!, vec!)。

web_search_mode_updates_between_turns_with_permission_profile151–218 ↗
async fn web_search_mode_updates_between_turns_with_permission_profile()

作用:这个测试确认同一个对话里,不同轮次如果权限不同,网页搜索的外网访问设置也会跟着变。它防止第一轮的安全设置被错误地沿用到第二轮,或者第二轮的宽松权限反过来污染第一轮。

数据流:测试启动假服务器,并安排它连续接收两次请求、返回两段假响应。它创建测试 Codex,设置 cached 模式,同时关闭相关功能开关。第一轮用 read_only 只读权限提交,第二轮用 Disabled 权限提交。之后测试拿到两次请求:第一份请求里的 web_search 应该 external_web_access=false,第二份请求里的 web_search 应该 external_web_access=true。结果说明每一轮都会按当时的权限重新决定搜索方式。

调用关系:这是一个多轮对话测试,所以它用 mount_sse_sequence 准备两次服务端响应。每提交一轮,Codex 都会向假服务器发一次请求;测试最后分别对两份请求调用 find_web_search_tool,确认权限配置是在每轮请求时重新计算的。

调用图:调用 5 个内部函数(mount_sse_sequence, start_mock_server, test_codex, find_web_search_tool, read_only);外部调用 3 个(assert_eq!, skip_if_no_network!, vec!)。

web_search_tool_config_from_config_toml_is_forwarded_to_request221–276 ↗
async fn web_search_tool_config_from_config_toml_is_forwarded_to_request()

作用:这个测试确认用户写在 config.toml 里的网页搜索细节,会被原样带进发给模型服务的请求里。它检查的不只是能不能联网,还包括搜索范围、搜索上下文大小和用户大致位置。

数据流:测试先创建假服务器和假响应,再创建一个临时目录当作 Codex 的 home 目录,并在里面写入 config.toml。配置里把 web_search 设为 live,还写了 context_size=high、allowed_domains=["example.com"],以及国家、城市、时区。随后用这个 home 目录启动测试 Codex,提交一轮对话。假服务器收到请求后,测试找出 web_search 工具,并把整个工具 JSON 和期望值比较:它应包含 external_web_access=true、search_context_size、filters.allowed_domains 和 user_location。

调用关系:这个测试把磁盘上的配置文件也拉进流程里,验证“用户配置 → Codex 读取配置 → 生成请求 JSON”这条链路没有断。它用 tempfile 创建隔离的临时 home,用 std::fs::write 写配置文件,再通过 test_codex 发起请求,最后借助 find_web_search_tool 检查配置是否正确出现在请求中。

调用图:调用 5 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, find_web_search_tool);外部调用 6 个(new, assert_eq!, skip_if_no_network!, write, new, vec!)。

远程元数据与运行时选择

这些套件验证远程模型元数据如何影响运行时能力、选择器行为以及专门的审查模型覆盖。

core/tests/suite/remote_models.rs源码 ↗
testtest execution

程序里有一部分模型信息是本地自带的,另一部分可能从服务器的 /models 接口拿到。这个文件就是给这套机制做“体检”:远程模型来了以后,是否会覆盖同名本地模型;优先级高的模型是否排在前面;隐藏模型是否不会出现在选择器里;网络慢时是否会超时并退回默认模型。它还会模拟一次完整对话,检查上下文窗口、推理强度、工具调用方式、截断策略等信息有没有被正确带进运行时请求。这里大量使用 mock server(假服务器,用来返回预设数据),这样测试不用真的依赖外部服务。文件只在非 Windows 系统上跑,因为里面涉及本地 shell 命令等平台相关行为。

函数细节21
remote_models_get_model_info_uses_longest_matching_prefix57–116 ↗
async fn remote_models_get_model_info_uses_longest_matching_prefix() -> Result<()>

作用:测试当用户请求的模型名比远程目录里的模型名更长时,程序会选“最长匹配前缀”的那条模型信息。这样像 gpt-5.3-codex-test 这种变体,不会误用更泛的 gpt-5.3 配置。

数据流:它先启动假服务器,准备两个远程模型:一个泛用前缀,一个更具体前缀。然后创建测试用认证和模型管理器,让管理器刷新远程模型列表。最后拿 gpt-5.3-codex-test 查询模型信息,确认返回的 slug 保留用户请求值,但基础指令来自更具体的前缀模型。

调用关系:这个测试会调用 test_remote_model_with_policy 造模型数据,用 mount_models_once 把数据挂到假服务器上,再通过 models_manager_with_provider 创建模型管理器。它直接验证模型管理器的 get_model_info 行为。

调用图:调用 6 个内部函数(auth_manager_from_auth, models_manager_with_provider, mount_models_once, test_remote_model_with_policy, create_dummy_chatgpt_auth_for_testing, bytes);外部调用 9 个(start, new, assert_eq!, built_in_model_providers, load_default_config_for_test, format!, skip_if_no_network!, skip_if_sandbox!, vec!)。

remote_models_config_context_window_override_clamps_to_max_context_window122–183 ↗
async fn remote_models_config_context_window_override_clamps_to_max_context_window() -> Result<()>

作用:测试用户把上下文窗口设得特别大时,运行时不会照单全收,而是会压到远程模型声明的最大值。上下文窗口可以理解为模型一次能“看见”的文字量。

数据流:它准备一个远程模型:默认窗口是 273000,最大窗口是 400000。用户配置里故意写 1000000。随后提交一条用户输入,等待 TurnStarted 事件,确认真正开始运行时使用的是 400000,而不是用户写的 1000000。

调用关系:这个测试用 test_remote_model 生成远程模型,用 mount_models_once 提供模型列表,用 mount_sse_once 模拟模型响应,再通过 test_codex 启动一套测试版 Codex。它检查提交用户输入后产生的事件。

调用图:调用 6 个内部函数(mount_models_once, mount_sse_once, sse, test_codex, test_remote_model, create_dummy_chatgpt_auth_for_testing);外部调用 8 个(default, start, assert_eq!, wait_for_event, skip_if_no_network!, skip_if_sandbox!, unreachable!, vec!)。

remote_models_config_override_above_max_uses_max_context_window189–250 ↗
async fn remote_models_config_override_above_max_uses_max_context_window() -> Result<()>

作用:测试用户配置的上下文窗口只要超过模型允许上限,就会被改成上限值。它防止程序把过大的请求发给模型服务,造成失败或浪费。

数据流:它让假服务器返回一个最大上下文窗口为 400000 的模型。测试配置里写 500000。提交用户输入后,它等到回合开始事件,并确认事件里的 model_context_window 是 400000。

调用关系:它和前一个上下文窗口测试走同一条运行路径:先用 mount_models_once 提供远程模型,再用 mount_sse_once 提供一次空响应,最后由 test_codex 驱动一次对话。

调用图:调用 6 个内部函数(mount_models_once, mount_sse_once, sse, test_codex, test_remote_model, create_dummy_chatgpt_auth_for_testing);外部调用 8 个(default, start, assert_eq!, wait_for_event, skip_if_no_network!, skip_if_sandbox!, unreachable!, vec!)。

remote_models_use_context_window_when_config_override_is_absent256–316 ↗
async fn remote_models_use_context_window_when_config_override_is_absent() -> Result<()>

作用:测试用户没有手动改上下文窗口时,程序会使用远程模型自己的默认窗口,而不是最大窗口。这样默认行为更稳,不会突然吃掉更多资源。

数据流:它准备一个远程模型,默认窗口是 273000,最大窗口是 400000,但测试配置里不写覆盖值。提交用户输入后,它检查 TurnStarted 事件,确认运行时窗口是默认的 273000。

调用关系:这个测试同样通过 test_codex 启动测试会话,并依赖 mount_models_once 和 mount_sse_once 模拟服务器。它关注的是“没有用户覆盖配置”这条分支。

调用图:调用 6 个内部函数(mount_models_once, mount_sse_once, sse, test_codex, test_remote_model, create_dummy_chatgpt_auth_for_testing);外部调用 8 个(default, start, assert_eq!, wait_for_event, skip_if_no_network!, skip_if_sandbox!, unreachable!, vec!)。

remote_models_long_model_slug_is_sent_with_custom_reasoning319–398 ↗
async fn remote_models_long_model_slug_is_sent_with_custom_reasoning() -> Result<()>

作用:测试长模型名会原样发给服务,同时远程模型里定义的自定义推理强度也会被带上。推理强度可以理解为让模型思考得更浅或更深的档位。

数据流:它准备一个前缀模型 gpt-5.3-codex,并给它设置自定义推理档位 max 和详细推理摘要。用户实际请求 gpt-5.3-codex-test。提交输入后,它读取发给假服务器的 JSON 请求体,确认 model 字段是完整长名字,reasoning.effort 是 max,reasoning.summary 是 detailed。

调用关系:它调用 test_remote_model_with_policy 造带截断策略的模型,用 mount_models_once 提供模型目录,用 mount_sse_once 接住一次模型请求。最后通过 mock 记录的请求体验证 Codex 发出的网络请求。

调用图:调用 7 个内部函数(mount_models_once, mount_sse_once, sse, test_codex, test_remote_model_with_policy, create_dummy_chatgpt_auth_for_testing, bytes);外部调用 8 个(default, start, assert_eq!, wait_for_event, Custom, skip_if_no_network!, skip_if_sandbox!, vec!)。

namespaced_model_slug_uses_catalog_metadata_without_fallback_warning401–450 ↗
async fn namespaced_model_slug_uses_catalog_metadata_without_fallback_warning() -> Result<()>

作用:测试带命名空间的模型名,比如 custom/gpt-5.2-codex,能用已有目录里的元数据,而不会误报“只能用备用信息”。命名空间就是模型名前面多了一段分类前缀。

数据流:它启动假服务器,只模拟一次普通响应,然后让测试版 Codex 使用 custom/gpt-5.2-codex。提交输入后,它不断读取事件,统计是否出现 fallback 警告。回合完成后,它确认请求体里的 model 是原始模型名,并且警告次数为 0。

调用关系:这个测试主要依赖 test_codex 启动会话,mount_sse_once 模拟模型响应,wait_for_event 逐个观察运行事件。它验证模型元数据选择不会走错误的兜底路径。

调用图:调用 3 个内部函数(mount_sse_once, sse, test_codex);外部调用 7 个(default, start, assert_eq!, wait_for_event, skip_if_no_network!, skip_if_sandbox!, vec!)。

remote_models_remote_model_uses_unified_exec453–607 ↗
async fn remote_models_remote_model_uses_unified_exec() -> Result<()>

作用:测试远程模型如果声明要使用 UnifiedExec,程序真的会按这种工具调用方式启动命令。UnifiedExec 可以理解为一种统一的命令执行通道,和普通 shell 命令通道不同。

数据流:它手写一个远程模型,shell_type 设为 UnifiedExec。启动测试会话后,先等这个远程模型出现在模型列表里,再把当前线程切换到该模型。随后模拟模型要求执行 /bin/echo call,测试等待 ExecCommandBegin 事件,确认事件来源是 UnifiedExecStartup。

调用关系:它用 wait_for_model_available 等远程模型刷新完成,用 submit_thread_settings 切换模型,用 mount_sse_sequence 模拟两轮服务端事件。这个测试把模型目录、线程设置、工具调用和事件流串在一起验证。

调用图:调用 9 个内部函数(mount_models_once, mount_sse_sequence, local_selections, test_codex, turn_permission_fields, wait_for_model_available, create_dummy_chatgpt_auth_for_testing, bytes, default_input_modalities);外部调用 12 个(Limited, default, builder, new, assert_eq!, submit_thread_settings, wait_for_event, wait_for_event_match, json!, skip_if_no_network! (+2 more))。

remote_models_truncation_policy_without_override_preserves_remote610–653 ↗
async fn remote_models_truncation_policy_without_override_preserves_remote() -> Result<()>

作用:测试没有用户覆盖配置时,远程模型自己的截断策略会被保留下来。截断策略就是当内容太长时,程序按什么限制裁剪内容。

数据流:它让假服务器返回一个远程模型,截断限制是 12000 字节。启动测试会话并等模型出现后,读取该模型信息,确认 truncation_policy 仍然是 12000 字节。

调用关系:它调用 test_remote_model_with_policy 制造带指定截断策略的模型,用 wait_for_model_available 等刷新完成,然后直接检查模型管理器返回的模型信息。

调用图:调用 6 个内部函数(mount_models_once, test_codex, test_remote_model_with_policy, wait_for_model_available, create_dummy_chatgpt_auth_for_testing, bytes);外部调用 6 个(Limited, builder, assert_eq!, skip_if_no_network!, skip_if_sandbox!, vec!)。

remote_models_truncation_policy_with_tool_output_override656–700 ↗
async fn remote_models_truncation_policy_with_tool_output_override() -> Result<()>

作用:测试用户设置工具输出 token 限制时,这个本地设置会改写远程模型的截断策略。token 可以粗略理解为模型读文字时使用的小片段单位。

数据流:它准备一个远程模型,原本截断限制是 10000 字节。测试配置里设置 tool_output_token_limit 为 50。启动后读取模型信息,确认最终截断策略变成 200 字节,说明用户配置生效了。

调用关系:它和无覆盖版本使用同样的模型刷新流程,但额外通过 test_codex 的配置钩子写入 tool_output_token_limit。它验证配置层会影响模型管理器给出的最终模型信息。

调用图:调用 6 个内部函数(mount_models_once, test_codex, test_remote_model_with_policy, wait_for_model_available, create_dummy_chatgpt_auth_for_testing, bytes);外部调用 6 个(Limited, builder, assert_eq!, skip_if_no_network!, skip_if_sandbox!, vec!)。

remote_models_apply_remote_base_instructions703–834 ↗
async fn remote_models_apply_remote_base_instructions() -> Result<()>

作用:测试切换到远程模型后,发给模型服务的 instructions 字段是否是预期的基础指令。instructions 可以理解为每次对话开头偷偷塞给模型的“行为说明书”。

数据流:它创建一个带自定义 base_instructions 的远程模型,启动会话后等待该模型可用,并把线程模型切到它。提交用户输入后,它读取假服务器收到的请求体,拿 instructions 字段和基准模型信息里的基础指令做比较。

调用关系:它调用 wait_for_model_available 等远程模型进入列表,用 submit_thread_settings 切换线程模型,用 mount_sse_once 捕获一次请求。它处在“远程模型元数据如何影响真实请求”的测试链路上。

调用图:调用 10 个内部函数(mount_models_once, mount_sse_once, sse, local_selections, test_codex, turn_permission_fields, wait_for_model_available, create_dummy_chatgpt_auth_for_testing, bytes, default_input_modalities);外部调用 10 个(Limited, default, builder, new, assert_eq!, submit_thread_settings, wait_for_event, skip_if_no_network!, skip_if_sandbox!, vec!)。

remote_models_do_not_append_removed_builtin_presets837–892 ↗
async fn remote_models_do_not_append_removed_builtin_presets() -> Result<()>

作用:测试远程模型列表合并时,不会把已经移除的内置预设又偷偷追加回来。模型预设就是展示给用户选模型的一份简化配置。

数据流:它让假服务器只返回一个 remote-alpha。模型管理器刷新后,它在可用列表里找到这个远程模型,并确认内容等于远程返回的数据。同时检查只有一个默认模型,且 /models 只请求了一次。

调用关系:它用 test_remote_model 造远程模型,用 models_manager_with_provider 单独测试模型管理器,不启动完整 Codex 会话。重点是远程目录和内置目录的合并结果。

调用图:调用 5 个内部函数(auth_manager_from_auth, models_manager_with_provider, mount_models_once, test_remote_model, create_dummy_chatgpt_auth_for_testing);外部调用 9 个(start, new, assert!, assert_eq!, built_in_model_providers, format!, skip_if_no_network!, skip_if_sandbox!, vec!)。

remote_models_merge_adds_new_high_priority_first895–940 ↗
async fn remote_models_merge_adds_new_high_priority_first() -> Result<()>

作用:测试远程新增模型如果优先级很高,会排在模型列表最前面。优先级数值在这里用来决定展示顺序。

数据流:它准备一个优先级为 -10000 的远程模型 remote-top。刷新模型列表后,它检查列表第一个模型就是 remote-top,并确认只请求了一次 /models。

调用关系:这个测试直接驱动模型管理器的 list_models。它依赖 test_remote_model 生成数据,mount_models_once 提供远程响应,用来验证排序逻辑。

调用图:调用 5 个内部函数(auth_manager_from_auth, models_manager_with_provider, mount_models_once, test_remote_model, create_dummy_chatgpt_auth_for_testing);外部调用 8 个(start, new, assert_eq!, built_in_model_providers, format!, skip_if_no_network!, skip_if_sandbox!, vec!)。

remote_models_merge_replaces_overlapping_model943–994 ↗
async fn remote_models_merge_replaces_overlapping_model() -> Result<()>

作用:测试远程模型和本地自带模型同名时,远程版本会覆盖本地版本。这样服务器可以更新模型展示名、说明等信息。

数据流:它先取一个本地自带模型的 slug,再构造同 slug 的远程模型,并把显示名和描述改成 Overridden。刷新后,它在列表中找到这个 slug,确认显示名和描述来自远程版本。

调用关系:它通过 bundled_model_slug 找到一个真实内置模型名,再用 test_remote_model 构造同名远程模型。模型管理器的 list_models 是被验证的核心。

调用图:调用 6 个内部函数(auth_manager_from_auth, models_manager_with_provider, mount_models_once, bundled_model_slug, test_remote_model, create_dummy_chatgpt_auth_for_testing);外部调用 8 个(start, new, assert_eq!, built_in_model_providers, format!, skip_if_no_network!, skip_if_sandbox!, vec!)。

remote_models_merge_preserves_bundled_models_on_empty_response997–1027 ↗
async fn remote_models_merge_preserves_bundled_models_on_empty_response() -> Result<()>

作用:测试远程服务器返回空模型列表时,本地自带模型仍然可用。这样服务器短暂没给数据,也不会让用户没模型可选。

数据流:它让假服务器的 /models 返回空数组。模型管理器刷新后,它取一个本地自带模型 slug,并确认这个模型仍然出现在可用列表里。

调用关系:它调用 bundled_model_slug 拿内置模型作为检查目标,用 mount_models_once 模拟空远程响应。这个测试覆盖远程数据为空时的兜底合并行为。

调用图:调用 5 个内部函数(auth_manager_from_auth, models_manager_with_provider, mount_models_once, bundled_model_slug, create_dummy_chatgpt_auth_for_testing);外部调用 8 个(start, new, new, assert!, built_in_model_providers, format!, skip_if_no_network!, skip_if_sandbox!)。

remote_models_request_times_out_after_5s1030–1095 ↗
async fn remote_models_request_times_out_after_5s() -> Result<()>

作用:测试远程模型请求太慢时,大约 5 秒后会超时,并退回本地默认模型。这样启动或选模型不会被慢网络一直卡住。

数据流:它让假服务器延迟 6 秒才返回模型列表。随后调用 get_default_model,并用计时器确认它在接近 5 秒时结束,而不是等满 6 秒。返回结果应是本地默认模型,同时确认 /models 只请求了一次。

调用关系:它用 mount_models_once_with_delay 制造慢响应,用 bundled_default_model_slug 找到应该兜底的默认模型。它直接验证模型管理器在网络超时时的表现。

调用图:调用 6 个内部函数(auth_manager_from_auth, models_manager_with_provider, mount_models_once_with_delay, bundled_default_model_slug, test_remote_model, create_dummy_chatgpt_auth_for_testing);外部调用 12 个(from_secs, now, start, new, assert!, assert_eq!, built_in_model_providers, format!, skip_if_no_network!, skip_if_sandbox! (+2 more))。

remote_models_hide_picker_only_models1098–1149 ↗
async fn remote_models_hide_picker_only_models() -> Result<()>

作用:测试远程模型如果标记为隐藏,就不会成为默认选择,也不会显示在模型选择器里。选择器就是用户挑模型的列表。

数据流:它准备一个 visibility 为 Hide 的远程模型 codex-auto-balanced。刷新后,默认模型仍应是本地默认模型;模型列表里虽然能找到隐藏模型,但它的 show_in_picker 是 false。

调用关系:它用 test_remote_model 创建隐藏模型,模型管理器先调用 get_default_model 再调用 list_models。这个测试关注远程可用但不该展示的模型。

调用图:调用 5 个内部函数(auth_manager_from_auth, models_manager_with_provider, mount_models_once, test_remote_model, create_dummy_chatgpt_auth_for_testing);外部调用 9 个(start, new, assert!, assert_eq!, built_in_model_providers, format!, skip_if_no_network!, skip_if_sandbox!, vec!)。

wait_for_model_available1151–1165 ↗
async fn wait_for_model_available(manager: &SharedModelsManager, slug: &str) -> ModelPreset

作用:这是测试用的小帮手:反复查看模型列表,直到某个远程模型出现。因为远程刷新是异步的,不能假设模型马上就能查到。

数据流:它接收一个共享模型管理器和模型 slug。它最多等 2 秒,每隔 25 毫秒刷新并查找一次。找到了就返回对应 ModelPreset;超时还没找到就让测试失败。

调用关系:remote_models_remote_model_uses_unified_exec、remote_models_truncation_policy_without_override_preserves_remote、remote_models_truncation_policy_with_tool_output_override 和 remote_models_apply_remote_base_instructions 都会用它,先等远程模型准备好,再继续后面的断言。

调用图:被 4 处调用(remote_models_apply_remote_base_instructions, remote_models_remote_model_uses_unified_exec, remote_models_truncation_policy_with_tool_output_override, remote_models_truncation_policy_without_override_preserves_remote);外部调用 6 个(from_millis, from_secs, now, list_models, panic!, sleep)。

bundled_model_slug1167–1175 ↗
fn bundled_model_slug() -> String

作用:这是测试用的小帮手:从项目自带的 models.json 里取第一个模型的名字。它给“远程模型覆盖内置模型”这类测试提供一个真实目标。

数据流:它读取内置模型响应,确认能解析且至少有一个模型,然后返回第一个模型的 slug 字符串。

调用关系:remote_models_merge_replaces_overlapping_model 用它制造同名远程模型,remote_models_merge_preserves_bundled_models_on_empty_response 用它确认空远程响应不会删掉内置模型。

调用图:被 2 处调用(remote_models_merge_preserves_bundled_models_on_empty_response, remote_models_merge_replaces_overlapping_model);外部调用 1 个(bundled_models_response)。

bundled_default_model_slug1177–1184 ↗
fn bundled_default_model_slug() -> String

作用:这是测试用的小帮手:找出本地自带模型里被标记为默认的那个模型名。它用于验证网络失败时程序会退回正确默认值。

数据流:它读取全部内置模型预设,找到 is_default 为 true 的那一项,然后返回它的 model 字段。

调用关系:remote_models_request_times_out_after_5s 会调用它,拿到预期的兜底模型名,再和超时后实际返回的模型名比较。

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

test_remote_model1186–1193 ↗
fn test_remote_model(slug: &str, visibility: ModelVisibility, priority: i32) -> ModelInfo

作用:这是造测试模型的快捷函数,适合不关心截断策略细节的测试使用。它用默认的 10000 字节截断限制创建一个远程模型。

数据流:它接收模型 slug、可见性和优先级,然后把这些参数连同默认截断策略交给 test_remote_model_with_policy,最后返回完整的 ModelInfo。

调用关系:多个测试用它快速准备远程模型,比如上下文窗口、合并排序、隐藏模型、超时兜底等场景。它本身把具体构造工作交给 test_remote_model_with_policy。

调用图:调用 2 个内部函数(test_remote_model_with_policy, bytes);被 8 处调用(remote_models_config_context_window_override_clamps_to_max_context_window, remote_models_config_override_above_max_uses_max_context_window, remote_models_do_not_append_removed_builtin_presets, remote_models_hide_picker_only_models, remote_models_merge_adds_new_high_priority_first, remote_models_merge_replaces_overlapping_model, remote_models_request_times_out_after_5s, remote_models_use_context_window_when_config_override_is_absent)。

test_remote_model_with_policy1195–1244 ↗
fn test_remote_model_with_policy(
    slug: &str,
    visibility: ModelVisibility,
    priority: i32,
    truncation_policy: TruncationPolicyConfig,
) -> ModelInfo

作用:这是造完整远程模型信息的底层测试工具。测试可以指定模型名、可见性、优先级和截断策略,其余字段由它填成一套合理默认值。

数据流:它接收 slug、visibility、priority 和 truncation_policy,然后组装一个 ModelInfo:包含显示名、描述、默认推理强度、shell 类型、上下文窗口、基础指令等字段。输出是一份可直接放进假 /models 响应里的模型元数据。

调用关系:remote_models_get_model_info_uses_longest_matching_prefix、remote_models_long_model_slug_is_sent_with_custom_reasoning 和两个截断策略测试会直接调用它;test_remote_model 也会调用它作为快捷包装。

调用图:调用 1 个内部函数(default_input_modalities);被 5 处调用(remote_models_get_model_info_uses_longest_matching_prefix, remote_models_long_model_slug_is_sent_with_custom_reasoning, remote_models_truncation_policy_with_tool_output_override, remote_models_truncation_policy_without_override_preserves_remote, test_remote_model);外部调用 4 个(default, new, format!, vec!)。

core/tests/suite/model_runtime_selectors.rs源码 ↗
testtest run

这个测试文件像是在给模型选择流程做“体检”。系统里既有本地配置的功能开关,也有远端模型返回的说明,比如这个模型该用普通工具模式、代码模式,还是多智能体模式。这里要确认一件事:当远端模型明确说自己要哪种模式时,系统应该听模型的,而不是被本地 feature flag(功能开关,用来临时打开或关闭某个能力)带偏。测试会搭一个假的服务器,假装返回模型列表和一次模型回复;然后启动一个测试版 Codex,选择指定模型,发一条用户消息,再检查真正发给模型的请求里带了哪些工具。它还测试了一个容易踩坑的场景:第一次对话前如果用户切换了模型,多智能体版本应该按新选的模型算,而不是按启动时的默认模型算。

函数细节7
remote_model40–46 ↗
fn remote_model(slug: &str) -> ModelInfo

作用:这个小工具函数用来快速造一个“远端模型资料”。测试里不想手写一大堆模型字段,所以先按模型名字生成默认资料,再补上测试需要的可见性设置。

数据流:输入是一个模型名字,也就是 slug(可以理解成模型的唯一代号)→ 它调用 model_info_from_slug 生成基础的 ModelInfo,再把模型标成会出现在列表里,并说明没有使用备用元数据 → 输出一份可被假服务器返回的模型说明。

调用关系:多个测试都会先用它造测试模型。remote_tool_mode_selector_overrides_feature_flags 用它造工具模式模型,remote_multi_agent_selector_overrides_feature_flags 用它造多智能体模型,remote_multi_agent_selector_uses_model_selected_before_first_turn 用它造启动模型和后来选择的模型。它把底层的 model_info_from_slug 包了一层,让测试读起来更像在描述场景。

调用图:调用 1 个内部函数(model_info_from_slug);被 3 处调用(remote_multi_agent_selector_overrides_feature_flags, remote_multi_agent_selector_uses_model_selected_before_first_turn, remote_tool_mode_selector_overrides_feature_flags)。

tool_names48–63 ↗
fn tool_names(body: &Value) -> Vec<String>

作用:这个函数从一次发给模型的请求正文里,把工具名字摘出来。测试关心的不是整份请求,而是系统最后给模型配了哪些工具。

数据流:输入是一段 JSON 请求内容 → 它找到里面的 tools 数组,再逐个工具读取 name 字段;如果没有 name,就尝试读取 type 字段;最后把这些字符串收集成列表 → 输出工具名列表。如果请求里没有工具数组,就输出空列表。

调用关系:测试函数在拿到模拟服务器收到的请求后,会调用它做检查。remote_tool_mode_selector_overrides_feature_flags 用它判断代码模式工具有没有出现,remote_multi_agent_selector_overrides_feature_flags 用它判断多智能体工具有没有出现或被禁用。它只做“提取名字”这一步,不决定工具应该有哪些。

调用图:被 2 处调用(remote_multi_agent_selector_overrides_feature_flags, remote_tool_mode_selector_overrides_feature_flags);外部调用 1 个(get)。

wait_for_model_available65–82 ↗
async fn wait_for_model_available(manager: &SharedModelsManager, slug: &str) -> ModelPreset

作用:这个函数等待某个远端模型真正出现在模型管理器的列表里。因为模型列表是异步刷新的,测试不能刚启动就立刻假设模型已经可用。

数据流:输入是共享的模型管理器和要等的模型代号 → 它在最多 2 秒内反复在线刷新模型列表,看看有没有匹配的模型;每次没找到就短暂睡 25 毫秒再试 → 找到就返回这份模型预设;超过时间还没找到就让测试失败。

调用关系:response_body_for_remote_model 在提交用户消息前会先调用它,确保要测的远端模型已经被系统看见。它把“等待异步刷新完成”的麻烦封装起来,让上层测试不用关心轮询细节。

调用图:被 1 处调用(response_body_for_remote_model);外部调用 6 个(from_millis, from_secs, now, list_models, panic!, sleep)。

response_body_for_remote_model84–142 ↗
async fn response_body_for_remote_model(
    remote_model: ModelInfo,
    configure: impl FnOnce(&mut Config) + Send + 'static,
) -> Result<Value>

作用:这是测试里的核心搭台函数:给它一个远端模型资料和一段配置修改,它会跑完整个请求流程,并返回系统实际发给模型的请求正文。测试靠它观察“模型选择结果到底变成了什么请求”。

数据流:输入是一份远端模型说明,以及一个可以修改 Config 的闭包(闭包就是传进来的一小段自定义配置代码)→ 它启动假服务器,挂上假的模型列表接口和假的流式回复接口;再启动测试版 Codex,等待模型可用,提交线程设置选择该模型,然后发送一条用户输入“list tools”→ 等到一轮对话完成后,取出假服务器收到的那次模型请求 JSON → 输出这份请求正文。

调用关系:remote_tool_mode_selector_overrides_feature_flags 和 remote_multi_agent_selector_overrides_feature_flags 都用它来避免重复搭假服务器、启动 Codex、发消息这些步骤。它内部会调用 mount_models_once、mount_sse_once、test_codex、wait_for_model_available 等测试辅助函数,像一条小型流水线,把“准备环境→选择模型→发请求→拿结果”串起来。

调用图:调用 7 个内部函数(mount_models_once, mount_sse_once, sse, start_mock_server, test_codex, wait_for_model_available, create_dummy_chatgpt_auth_for_testing);被 2 处调用(remote_multi_agent_selector_overrides_feature_flags, remote_tool_mode_selector_overrides_feature_flags);外部调用 5 个(default, assert_eq!, submit_thread_settings, wait_for_event, vec!)。

remote_tool_mode_selector_overrides_feature_flags145–184 ↗
async fn remote_tool_mode_selector_overrides_feature_flags() -> Result<()>

作用:这个测试确认:远端模型声明的工具模式,比本地功能开关更有决定权。换句话说,模型说自己要直接模式或代码模式时,系统应该按模型说的配工具。

数据流:它先造一个声明 ToolMode::Direct 的模型,同时故意在本地打开 CodeModeOnly 功能开关 → 跑请求后检查工具列表,确认代码模式工具没有被加进去;接着又造一个声明 ToolMode::CodeModeOnly 的模型 → 跑请求后确认代码模式入口工具、等待工具、请求用户输入工具,以及网页搜索和图片生成工具都出现 → 测试通过则说明远端 tool_mode 能正确覆盖本地开关。

调用关系:它调用 remote_model 造模型,调用 response_body_for_remote_model 跑完整测试流程,再用 tool_names 从请求中提取工具名做断言。它验证的是工具模式选择这一支逻辑,而不是多智能体逻辑。

调用图:调用 3 个内部函数(remote_model, response_body_for_remote_model, tool_names);外部调用 4 个(assert!, assert_eq!, skip_if_no_network!, vec!)。

remote_multi_agent_selector_overrides_feature_flags187–222 ↗
async fn remote_multi_agent_selector_overrides_feature_flags() -> Result<()>

作用:这个测试确认:远端模型声明的多智能体版本,会覆盖本地功能开关。多智能体可以理解成让一个主助手派生或协调其他助手一起工作,这里要保证该开就开、该关就关。

数据流:它先造一个声明 MultiAgentVersion::V2 的模型,同时本地把 MultiAgentV2 功能关掉、打开另一个协作开关,并设置最多线程数 → 跑请求后检查工具列表里有 send_message,说明 V2 多智能体工具被启用;然后造一个声明 MultiAgentVersion::Disabled 的模型,同时本地打开 MultiAgentV2 → 跑请求后确认 spawn_agent、send_message、wait_agent 等多智能体工具都没有出现 → 输出是测试成功或失败。

调用关系:它和工具模式测试一样,依靠 remote_model 准备模型,依靠 response_body_for_remote_model 完成一次真实的测试请求,最后用 tool_names 看结果。它专门守住“模型声明优先于 feature flag”这条规则。

调用图:调用 3 个内部函数(remote_model, response_body_for_remote_model, tool_names);外部调用 2 个(assert!, skip_if_no_network!)。

remote_multi_agent_selector_uses_model_selected_before_first_turn225–307 ↗
async fn remote_multi_agent_selector_uses_model_selected_before_first_turn() -> Result<()>

作用:这个测试检查一个时间点问题:如果程序启动时有默认模型,但用户在第一次发消息前换了模型,多智能体版本必须按新模型来算。否则系统可能在第一轮对话就给错工具。

数据流:它启动假服务器,提供两个模型:默认的 ROOT_MODEL 声明多智能体 V1,后来选择的 CHILD_MODEL 声明多智能体 V2 → 启动 Codex 时先把默认模型设成 ROOT_MODEL,并确认此时还没有确定 multi_agent_version → 在第一次用户输入前提交线程设置,把模型改成 CHILD_MODEL,也确认仍未过早确定版本 → 发送用户消息并等一轮完成 → 最后检查模型列表只请求了一次、运行时 multi_agent_version 变成 V2,并且实际请求工具里包含 send_message。

调用关系:这个测试比前两个更手工一些,直接调用 mount_models_once、mount_sse_once、test_codex 等搭建完整场景,而不是使用 response_body_for_remote_model,因为它要精确检查“第一次对话前切换模型”这个过程中的中间状态。它也用 remote_model 造模型,用 tool_names 检查最终请求里的工具。

调用图:调用 6 个内部函数(mount_models_once, mount_sse_once, sse, test_codex, remote_model, create_dummy_chatgpt_auth_for_testing);外部调用 7 个(default, assert_eq!, submit_thread_settings, wait_for_event, skip_if_no_network!, vec!, start)。

core/tests/suite/auto_review.rs源码 ↗
testtest execution

这份测试像一次彩排:先搭一个假的远程服务器,告诉系统“某个主模型有一个专门的自动审查模型”。然后测试让 Codex 开始一轮对话,模型先请求权限,再尝试改文件。用户同意权限时特别标记 strict_auto_review,也就是“这次必须严格自动审查”。接着系统应该把这次改文件动作交给 Guardian 审查模型判断风险。测试最后检查发给服务器的审查请求,确认里面用的 model 确实是模型目录给出的 reviewer,而不是用户当前选的 parent model。这里的 SSE 是服务器一段段推送事件的格式;MockServer 则是假服务器,专门用来稳定复现网络返回。

函数细节2
remote_model_override_uses_catalog_model_for_strict_auto_review44–215 ↗
async fn remote_model_override_uses_catalog_model_for_strict_auto_review() -> Result<()>

作用:这是主测试。它验证当模型目录声明“自动审查请用另一个模型”时,严格自动审查真的会用那个指定模型。

数据流:进去的是一套测试脚本:假的模型目录、假的服务器推送事件、测试用登录信息、一次用户输入和一次权限回复。函数先跳过不适合跑的环境,再启动假服务器,挂上模型目录和多段 SSE 返回;随后创建测试版 Codex,加载模型信息,提交用户请求,等系统发出权限申请;测试再回传“本轮允许,并且要严格自动审查”。最后它从假服务器收到的请求里找出 Guardian 审查请求,检查请求正文里的 model 是否等于目录里指定的 review_model。出来的结果是测试通过或断言失败,不会产出业务数据,但会验证系统行为是否正确。

调用关系:测试运行器会直接执行这个异步测试。它把准备工作交给 mount_models_once 和 mount_sse_sequence 来布置假接口,用 test_codex 创建测试环境,用 turn_permission_fields 和 local_selections 准备权限与本地工作目录,再通过 codex.submit 推动一轮对话。中间它调用 remote_model_with_auto_review_override 造出带审查模型覆盖配置的模型信息;最后通过 wait_for_event 等系统事件,并检查 MockServer 记录的请求。

调用图:调用 7 个内部函数(mount_models_once, mount_sse_sequence, local_selections, test_codex, turn_permission_fields, create_dummy_chatgpt_auth_for_testing, read_only);外部调用 10 个(default, start, assert_eq!, submit_thread_settings, wait_for_event, json!, panic!, skip_if_no_network!, skip_if_sandbox!, vec!)。

remote_model_with_auto_review_override217–261 ↗
fn remote_model_with_auto_review_override(slug: &str, review_model: &str) -> ModelInfo

作用:这个辅助函数用来造一个假的远程模型资料,并且在里面写明“自动审查要改用哪个模型”。测试需要它来模拟真实模型目录的配置。

数据流:进去的是两个字符串:主模型的名字 slug,以及审查模型的名字 review_model。函数把它们填进一个 ModelInfo 结构里,同时补齐模型显示名、推理等级、工具支持、上下文长度、截断策略等必要字段。最关键的输出是一个 auto_review_model_override 已设置好的 ModelInfo,供假模型目录返回给系统。

调用关系:它被 remote_model_override_uses_catalog_model_for_strict_auto_review 用在测试开头,用来构造 mount_models_once 要返回的模型目录内容。它本身不发网络请求、不运行 Codex,只是像填表一样生成一份模型档案;内部会调用 default_input_modalities、TruncationPolicyConfig::bytes 等小工具来补默认值。

调用图:调用 2 个内部函数(bytes, default_input_modalities);外部调用 4 个(default, new, format!, vec!)。

模型切换行为

此套件测试当活动模型或服务层级在执行期间变化时,请求历史和外发字段如何被重写。

core/tests/suite/model_switching.rs源码 ↗
testtest run

这个文件像一套“换车验车清单”:用户从一个模型换到另一个模型时,系统要确认很多细节。比如换模型后,要不要给模型加一段说明;同时换人格时,哪些提示该加、哪些不该加;请求接口时服务档位该不该带上;从支持图片的模型换到只支持文字的模型时,旧图片内容要安全省略;模型生成过图片后,历史里该保留图片生成记录,但不能让不支持图片的模型收到图片数据。测试里会启动假的服务器,模拟模型接口返回,再检查 Codex 实际发出去的请求内容。这样不用真的依赖线上服务,也能确认核心行为稳定。文件里的辅助函数负责拼测试用的用户回合、生成测试模型信息、定位生成图片保存路径;其余函数都是具体测试场景。

函数细节15
read_only_user_turn44–68 ↗
fn read_only_user_turn(test: &TestCodex, items: Vec<UserInput>, model: String) -> Op

作用:这个辅助函数用来造一个“用户发话”的测试操作,而且权限是只读的。测试里反复用它,避免每个测试都手写一大段相同的权限和模型设置。

数据流:输入是测试环境、用户输入内容和要使用的模型名;它读取测试里的当前工作目录和推理强度配置;然后组装出一个 Op::UserInput 操作,里面带上只读权限、本地环境选择、永不请求审批、以及本回合要用的模型;输出就是可以提交给 Codex 的一次用户回合。

调用关系:多个换模型、图片和上下文窗口测试都会调用它来发起对话。它把权限细节交给 turn_permission_fields、PermissionProfile::read_only 和 local_selections,测试本身就能专注检查模型切换后的请求内容。

调用图:调用 4 个内部函数(cwd_path, local_selections, turn_permission_fields, read_only);被 7 处调用(generated_image_is_replayed_for_image_capable_models, model_and_personality_change_only_appends_model_instructions, model_change_appends_model_instructions_developer_message, model_change_from_generated_image_to_text_preserves_prior_generated_image_call, model_change_from_image_to_text_strips_prior_image_content, model_switch_to_smaller_model_updates_token_context_window, thread_rollback_after_generated_image_drops_entire_image_turn_history);外部调用 1 个(default)。

image_generation_artifact_path70–92 ↗
fn image_generation_artifact_path(codex_home: &Path, session_id: &str, call_id: &str) -> PathBuf

作用:这个辅助函数算出“模型生成的图片”在测试目录里应该保存到哪里。它还会把会话 ID 和调用 ID 里的奇怪字符变安全,避免变成非法文件名。

数据流:输入是 Codex 的主目录、会话 ID 和图片生成调用 ID;它先把 ID 里不是字母、数字、横线或下划线的字符替换成下划线;再拼出 generated_images/会话目录/调用ID.png 这样的路径;输出是一个 PathBuf 文件路径,不会实际写文件。

调用关系:涉及生成图片的测试会用它提前定位图片文件,方便测试前后删除残留文件。它只负责算路径,真正的图片保存和回放由 Codex 运行流程完成。

调用图:被 3 处调用(generated_image_is_replayed_for_image_capable_models, model_change_from_generated_image_to_text_preserves_prior_generated_image_call, thread_rollback_after_generated_image_drops_entire_image_turn_history);外部调用 2 个(join, format!)。

test_model_info94–143 ↗
fn test_model_info(
    slug: &str,
    display_name: &str,
    description: &str,
    input_modalities: Vec<InputModality>,
) -> ModelInfo

作用:这个辅助函数快速造一个测试用的模型说明。测试可以用它声明某个假模型支持文字、图片、服务档位、上下文长度等能力。

数据流:输入是模型代号、显示名、描述和输入能力列表;它把这些填进 ModelInfo,并给其他字段放入稳定的默认值,比如默认推理强度、基础指令、截断策略和上下文窗口;输出是一份完整的模型元数据。

调用关系:服务档位测试、图片能力测试和生成图片历史测试都会先用它造模型,再通过 mount_models_once 或配置注入给测试环境。这样每个测试只改自己关心的字段,不用重复写完整模型结构。

调用图:调用 1 个内部函数(bytes);被 8 处调用(default_service_tier_override_is_omitted_from_http_turn, flex_service_tier_is_applied_to_http_turn, generated_image_is_replayed_for_image_capable_models, model_change_from_generated_image_to_text_preserves_prior_generated_image_call, model_change_from_image_to_text_strips_prior_image_content, null_service_tier_override_is_omitted_from_http_turn_with_catalog_default, thread_rollback_after_generated_image_drops_entire_image_turn_history, unsupported_service_tier_is_omitted_from_http_turn);外部调用 3 个(default, new, vec!)。

model_change_appends_model_instructions_developer_message146–208 ↗
async fn model_change_appends_model_instructions_developer_message() -> Result<()>

作用:这个测试确认:用户换模型后,下一次请求里会多带一段给模型看的“你刚刚换过模型”的开发者说明。这样新模型知道之前对话来自另一个模型,不会误以为一直是自己在处理。

数据流:测试先启动假服务器,让它准备接收两次模型请求;第一次用原模型发一句话并等回合结束;然后提交线程设置,把模型改成新模型;第二次再发话;最后读取第二个 HTTP 请求,检查 developer 消息里是否包含 <model_switch> 和说明文字。

调用关系:它使用 read_only_user_turn 生成两次用户回合,用 submit_thread_settings 模拟切换模型,用 mount_sse_sequence 模拟两次模型响应。它验证的是模型切换流程在真正发请求前是否正确补充提示。

调用图:调用 3 个内部函数(mount_sse_sequence, test_codex, read_only_user_turn);外部调用 8 个(default, start, assert!, assert_eq!, submit_thread_settings, wait_for_event, skip_if_no_network!, vec!)。

model_and_personality_change_only_appends_model_instructions211–285 ↗
async fn model_and_personality_change_only_appends_model_instructions() -> Result<()>

作用:这个测试确认:如果同一回合里既换模型又换“人格”,系统只加模型切换说明,不额外加人格更新说明。这样可以避免给新模型重复或冲突的提示。

数据流:测试开启 Personality 功能,先用旧模型完成一回合;随后提交线程设置,同时改模型和人格;再用新模型发一回合;最后检查第二次请求的 developer 消息:必须有 <model_switch>,但不能有 <personality_spec>。

调用关系:它和前一个模型切换测试类似,也依赖 read_only_user_turn、submit_thread_settings 和假 SSE 响应。区别是它覆盖“模型和人格同时变化”的优先级规则。

调用图:调用 4 个内部函数(mount_sse_sequence, start_mock_server, test_codex, read_only_user_turn);外部调用 7 个(default, assert!, assert_eq!, submit_thread_settings, wait_for_event, skip_if_no_network!, vec!)。

service_tier_change_is_applied_on_next_http_turn288–315 ↗
async fn service_tier_change_is_applied_on_next_http_turn() -> Result<()>

作用:这个测试确认服务档位只影响对应的下一次 HTTP 请求。比如用户选择快速档,本次请求应带上;下一次没有选择时,就不该继续沿用。

数据流:测试启动假服务器并准备两次响应;先提交一个带 fast 档位的回合,再提交一个不带档位的普通回合;最后读取两个请求体,确认第一个有 service_tier 为 priority,第二个没有 service_tier 字段。

调用关系:它通过 TestCodex 的 submit_turn_with_service_tier 走完整请求流程,用 mount_sse_sequence 收集请求。它验证的是服务档位不会被错误地“记住”到后续回合。

调用图:调用 3 个内部函数(mount_sse_sequence, start_mock_server, test_codex);外部调用 3 个(assert_eq!, skip_if_no_network!, vec!)。

flex_service_tier_is_applied_to_http_turn318–353 ↗
async fn flex_service_tier_is_applied_to_http_turn() -> Result<()>

作用:这个测试确认:如果模型目录明确说某个模型支持 flex 档位,那么用户选择 flex 时,请求里会真的带上 flex。

数据流:测试先造一个支持 flex 服务档位的假模型,并把它放进配置里的模型目录;启动一次假响应;构建 Codex 后提交一个选择 flex 的回合;最后检查发出的请求体里 service_tier 是否等于 flex。

调用关系:它用 test_model_info 创建基础模型,再手动补上 service_tiers。测试通过 mount_sse_once 捕获单次请求,验证“模型能力表”和“用户选择”一起决定请求字段。

调用图:调用 6 个内部函数(mount_sse_once, sse_completed, start_mock_server, test_codex, test_model_info, default_input_modalities);外部调用 3 个(assert_eq!, skip_if_no_network!, vec!)。

unsupported_service_tier_is_omitted_from_http_turn356–386 ↗
async fn unsupported_service_tier_is_omitted_from_http_turn() -> Result<()>

作用:这个测试确认:如果模型没有声明支持某个服务档位,即使用户请求了 fast,系统也不会把这个档位发给接口。这样可以避免接口因为不认识的参数而报错。

数据流:测试造一个没有任何服务档位的假模型;提交一个带 fast 档位的回合;然后读取请求 JSON,确认里面没有 service_tier 字段。

调用关系:它用 test_model_info 给模型一个最小能力说明,再通过 test_codex 运行完整发送流程。它和 flex 档位测试形成对照:支持才发送,不支持就省略。

调用图:调用 6 个内部函数(mount_sse_once, sse_completed, start_mock_server, test_codex, test_model_info, default_input_modalities);外部调用 2 个(assert_eq!, skip_if_no_network!)。

default_service_tier_override_is_omitted_from_http_turn389–425 ↗
async fn default_service_tier_override_is_omitted_from_http_turn() -> Result<()>

作用:这个测试确认:当用户选择的是“默认档位”时,请求里不需要显式写 service_tier。默认就交给服务端按模型目录的默认值处理。

数据流:测试造一个默认服务档位为 fast 的假模型,并声明它支持 fast;提交一个服务档位值为默认标记的回合;最后检查请求体,确认 service_tier 字段不存在。

调用关系:它依赖 test_model_info 创建模型,再改 service_tiers 和 default_service_tier。它覆盖的是“选择默认值不等于要把默认值写进请求”的规则。

调用图:调用 6 个内部函数(mount_sse_once, sse_completed, start_mock_server, test_codex, test_model_info, default_input_modalities);外部调用 3 个(assert_eq!, skip_if_no_network!, vec!)。

null_service_tier_override_is_omitted_from_http_turn_with_catalog_default428–464 ↗
async fn null_service_tier_override_is_omitted_from_http_turn_with_catalog_default() -> Result<()>

作用:这个测试确认:即使模型目录里有默认服务档位,只要用户本回合没有指定档位,请求里也不要额外带 service_tier。这样默认行为仍由服务端自然决定。

数据流:测试造一个有 fast 默认档位的假模型;提交一个 service_tier 为 None 的普通回合;读取请求体后确认没有 service_tier 字段。

调用关系:它和 default_service_tier_override_is_omitted_from_http_turn 很接近,但测试的是“没有选择档位”的情况。两者一起保证默认档位不会被客户端多此一举地写入请求。

调用图:调用 6 个内部函数(mount_sse_once, sse_completed, start_mock_server, test_codex, test_model_info, default_input_modalities);外部调用 3 个(assert_eq!, skip_if_no_network!, vec!)。

model_change_from_image_to_text_strips_prior_image_content467–564 ↗
async fn model_change_from_image_to_text_strips_prior_image_content() -> Result<()>

作用:这个测试确认:从支持图片输入的模型切到只支持文字的模型时,历史里的用户图片不会继续发给新模型。系统会用一句文字占位说明“图片内容已省略”,避免新模型收到不支持的图片数据。

数据流:测试创建一个支持图片的模型和一个只支持文字的模型;第一回合用图片模型提交图片和文字;第二回合改用文字模型提交文字;最后检查两次请求:第一次应包含图片 URL,第二次不应包含图片 URL,但应包含图片被省略的提示文本。

调用关系:它用 mount_models_once 提供模型能力表,用 read_only_user_turn 发两次回合,并让模型管理器先加载远程模型信息。它验证的是历史回放时会根据新模型能力过滤旧内容。

调用图:调用 7 个内部函数(mount_models_once, mount_sse_sequence, test_codex, read_only_user_turn, test_model_info, create_dummy_chatgpt_auth_for_testing, default_input_modalities);外部调用 6 个(start, assert!, assert_eq!, wait_for_event, skip_if_no_network!, vec!)。

generated_image_is_replayed_for_image_capable_models567–670 ↗
async fn generated_image_is_replayed_for_image_capable_models() -> Result<()>

作用:这个测试确认:如果模型生成过图片,并且后续仍使用支持图片的模型,那么生成图片的历史会作为图片生成调用记录回放给模型。这样模型能知道自己之前生成了什么。

数据流:测试创建一个支持图片的模型;第一次响应模拟产生一个 image_generation_call,里面有调用 ID 和图片数据;第二次用户要求描述生成图;最后检查第二次请求,确认历史里有原来的 image_generation_call,ID 和结果数据都保留,并且 developer 消息里有图片保存路径说明。

调用关系:它用 image_generation_artifact_path 找到测试图片保存位置,用 mount_sse_sequence 模拟先生成图再普通回复。它检查的是生成图片历史如何在支持图片的模型面前完整保留。

调用图:调用 8 个内部函数(mount_models_once, mount_sse_sequence, test_codex, image_generation_artifact_path, read_only_user_turn, test_model_info, create_dummy_chatgpt_auth_for_testing, default_input_modalities);外部调用 7 个(start, assert!, assert_eq!, wait_for_event, skip_if_no_network!, remove_file, vec!)。

model_change_from_generated_image_to_text_preserves_prior_generated_image_call673–794 ↗
async fn model_change_from_generated_image_to_text_preserves_prior_generated_image_call() -> Result<()>

作用:这个测试确认:从会生成/理解图片的模型切到只支持文字的模型后,生成图片这件事仍会保留在历史里,但图片字节数据会被清空。这样新模型知道有过图片生成,不会收到它处理不了的大段图片内容。

数据流:测试先用图片模型模拟生成一张图;第二回合切到只支持文字的模型;随后检查第二次请求:不应把生成图改写成用户图片输入,应保留 image_generation_call 的 ID,但 result 变成空字符串,同时仍有图片保存路径说明,也不插入普通用户图片的省略占位文本。

调用关系:它和 generated_image_is_replayed_for_image_capable_models 使用相同的生成图片模拟方式,但目标模型能力不同。它验证生成图片历史和用户上传图片历史不是同一种处理规则。

调用图:调用 8 个内部函数(mount_models_once, mount_sse_sequence, test_codex, image_generation_artifact_path, read_only_user_turn, test_model_info, create_dummy_chatgpt_auth_for_testing, default_input_modalities);外部调用 7 个(start, assert!, assert_eq!, wait_for_event, skip_if_no_network!, remove_file, vec!)。

thread_rollback_after_generated_image_drops_entire_image_turn_history797–905 ↗
async fn thread_rollback_after_generated_image_drops_entire_image_turn_history() -> Result<()>

作用:这个测试确认:如果用户把包含生成图片的上一回合回滚掉,那么这回合的用户文字、生成图片记录和保存路径说明都应从后续历史里消失。

数据流:测试先模拟一回合生成图片;然后提交 ThreadRollback 回滚一个回合,并等待回滚完成事件;再提交新回合;最后检查第二次模型请求,确认看不到被回滚的用户文字、图片保存说明,也没有 image_generation_call。

调用关系:它使用 image_generation_artifact_path 管理测试文件路径,用 Op::ThreadRollback 触发回滚。它验证的是对话历史删除必须连同图片生成相关的附属信息一起删除。

调用图:调用 8 个内部函数(mount_models_once, mount_sse_sequence, test_codex, image_generation_artifact_path, read_only_user_turn, test_model_info, create_dummy_chatgpt_auth_for_testing, default_input_modalities);外部调用 7 个(start, assert!, assert_eq!, wait_for_event, skip_if_no_network!, remove_file, vec!)。

model_switch_to_smaller_model_updates_token_context_window908–1114 ↗
async fn model_switch_to_smaller_model_updates_token_context_window() -> Result<()>

作用:这个测试确认:从大上下文模型切到小上下文模型后,系统上报和使用的上下文窗口大小会立刻变成小模型的值。上下文窗口可以理解为模型一次能“看见”的最长对话容量。

数据流:测试创建两个假模型:一个上下文窗口 272000,一个 128000,并设置有效窗口比例为 95%;先用大模型发一回合并检查 TokenCount 事件里的窗口值;然后提交设置切到小模型;第二回合开始时和收到 token 统计时,都检查窗口值已经变成小模型的 95%,且不再是大模型的值。

调用关系:它先通过 mount_models_once 提供远程模型目录,再用模型管理器确认两个模型可用。它调用 read_only_user_turn 发起两次回合,重点验证模型切换不只是换名字,还会更新计数和压缩判断所依赖的容量信息。

调用图:调用 8 个内部函数(mount_models_once, mount_sse_sequence, start_mock_server, test_codex, read_only_user_turn, create_dummy_chatgpt_auth_for_testing, bytes, default_input_modalities);外部调用 10 个(default, new, assert!, assert_eq!, assert_ne!, submit_thread_settings, wait_for_event, skip_if_no_network!, unreachable!, vec!)。