传输、流式处理与提供方协议套件
这一阶段是幕后质检,专门盯住 Codex 和模型服务“怎么通信”。它像检查电话线和快递单:普通 HTTP、流式 SSE、WebSocket 长连接、实时语音/文字都要能发、能收、能断线重试。各测试分别查请求内容、身份头、压缩规则、模型列表更新、代理标记、单轮状态、远程摘要、额度和安全降级提示。WebSocket 坏了还要能改走 HTTP,出错后下一轮也不能卡死。
HTTP 请求整形
这些测试覆盖标准 Responses API HTTP 请求在 API 边界如何构建、标注、压缩和刷新。
core/tests/suite/client.rs源码 ↗
这个测试文件用假的 HTTP 服务器假装模型服务,然后启动一个测试版 Codex 客户端,让它真的走一遍“用户输入 → 组装请求 → 流式接收回复 → 发出事件”的流程。这样可以检查很多外部用户会直接感受到的事:请求里有没有会话 ID 和线程 ID,API Key 和 ChatGPT 登录 token 谁优先,恢复旧会话时历史消息顺序对不对,图片工具结果能不能重放,推理强度、摘要、啰嗦程度等模型参数有没有带上,以及遇到限额、上下文太长、内容过滤时有没有发出正确错误。文件里还有一些小帮手,用来写假登录文件、抽取请求里的文字、搭建命令式鉴权脚本。整体上,它不是产品功能本身,而是用可重复的假服务把客户端最容易出错的“对外通信契约”钉牢。
test_turn_responses_metadata98–113 ↗
fn test_turn_responses_metadata(
_client: &ModelClient,
thread_id: ThreadId,
) -> codex_core::CodexResponsesMetadata
作用:给测试里的某一次模型请求生成一份固定的 Codex 元数据。这样测试不用关心真实环境,也能检查请求里是否带了安装 ID、线程 ID、窗口 ID 这些识别信息。
数据流:进去的是模型客户端和线程 ID → 它把线程 ID 转成字符串,并配上测试用的安装 ID、窗口 ID、来源等固定值 → 出来是一份可放进 Responses 请求里的元数据对象,不改动外部状态。
调用关系:它是底层测试帮手,被 send_provider_auth_request 和 azure_responses_request_includes_store_and_reasoning_ids 在直接调用 ModelClient 发流式请求前使用,保证这些测试也有和普通会话一样的请求元数据。
调用图:被 2 处调用(azure_responses_request_includes_store_and_reasoning_ids, send_provider_auth_request);外部调用 2 个(responses_metadata, to_string)。
assert_message_role116–118 ↗
fn assert_message_role(request_body: &serde_json::Value, role: &str)
作用:检查某个请求消息的角色是不是预期值,比如 user、developer、assistant。它让测试失败时能明确知道是消息身份放错了。
数据流:进去的是一段 JSON 请求消息和期望角色字符串 → 它读取 JSON 里的 role 字段并比较 → 如果一致就继续,不一致测试直接失败;它不返回业务数据。
调用关系:它被用户说明和开发者说明相关测试调用,用来确认 Codex 把权限说明、AGENTS.md 内容等放到了正确角色的消息里。
调用图:被 2 处调用(includes_developer_instructions_message_in_request, includes_user_instructions_message_in_request);外部调用 1 个(assert_eq!)。
message_input_texts121–128 ↗
fn message_input_texts(item: &serde_json::Value) -> Vec<&str>
作用:从一条请求消息里取出所有文字内容。因为一条消息的内容可能分成多个小块,这个函数帮测试把文字块统一拿出来看。
数据流:进去的是一条 JSON 消息 → 它查看 content 数组,挑出里面带 text 字段的项目 → 出来是一组文字引用,不改变原始 JSON。
调用关系:它是多个断言的基础工具,被恢复会话、用户说明、开发者说明等测试用来检查请求中到底包含了哪些文字。
调用图:被 3 处调用(includes_developer_instructions_message_in_request, includes_user_instructions_message_in_request, resume_includes_initial_messages_and_sends_prior_items)。
message_input_text_contains130–135 ↗
fn message_input_text_contains(request: &ResponsesRequest, role: &str, needle: &str) -> bool
作用:检查某个角色的请求消息里是否包含指定文字片段。它让测试不用手工遍历整份请求。
数据流:进去的是捕获到的 Responses 请求、角色名和要找的文字 → 它先拿出该角色的所有输入文字,再逐个查找片段 → 出来是 true 或 false,不改请求。
调用关系:它调用 message_input_texts 完成文字抽取,主要服务于 Apps 指引、环境上下文等“应该出现或不该出现某段话”的测试场景。
调用图:调用 1 个内部函数(message_input_texts)。
assert_codex_client_metadata137–169 ↗
fn assert_codex_client_metadata(
request_body: &serde_json::Value,
installation_id: &str,
session_id: &str,
thread_id: &str,
)
作用:检查请求体里的 Codex 客户端元数据是否完整且前后一致。它保证服务端能知道这次请求来自哪个安装、哪个会话、哪个线程和哪个回合。
数据流:进去的是请求 JSON、安装 ID、会话 ID、线程 ID → 它读取 client_metadata 和其中嵌套的回合元数据 JSON,并逐项比较 → 如果字段缺失或不一致,测试失败;没有额外输出。
调用关系:它被 API Key 请求测试和 ChatGPT 登录请求测试调用,专门确认不同鉴权方式下都不会漏掉客户端追踪信息。
调用图:被 2 处调用(chatgpt_auth_sends_correct_request, includes_session_id_thread_id_and_model_headers_in_request);外部调用 1 个(assert_eq!)。
non_openai_responses_requests_omit_item_turn_metadata172–220 ↗
async fn non_openai_responses_requests_omit_item_turn_metadata()
作用:确认当模型提供方不是标准 OpenAI 名称时,请求里的每条输入项不会夹带回合 metadata。这样可以避免把 OpenAI 私有格式发给不一定认识它的第三方服务。
数据流:它启动假服务器并配置一个改名的 Responses 提供方 → 提交一条 hello 用户输入,等回合完成 → 读取服务器收到的请求,逐条确认 input 项没有 metadata 字段。
调用关系:这是独立测试,借助 test_codex 搭客户端、mount_sse_once 搭一次性流式回复、wait_for_event 等完成后检查真实发出的 HTTP 请求。
调用图:调用 3 个内部函数(mount_sse_once, sse, test_codex);外部调用 7 个(default, start, assert!, built_in_model_providers, wait_for_event, format!, vec!)。
write_auth_json225–272 ↗
fn write_auth_json(
codex_home: &TempDir,
openai_api_key: Option<&str>,
chatgpt_plan_type: &str,
access_token: &str,
account_id: Option<&str>,
) -> String
作用:在临时 Codex 主目录里写一个假的 auth.json 登录文件。它用来模拟用户同时拥有 API Key 和 ChatGPT token 的情况。
数据流:进去的是临时目录、可选 API Key、套餐类型、访问 token 和账号 ID → 它拼出一个假的 JWT(登录令牌,一段带用户信息的字符串)和 tokens JSON,并写到磁盘 → 出来是写入的假 id_token 字符串,同时目录里多了 auth.json。
调用关系:它被 prefers_apikey_when_config_prefers_apikey_even_with_chatgpt_tokens 使用,为后续从磁盘加载鉴权信息做准备。
调用图:被 1 处调用(prefers_apikey_when_config_prefers_apikey_even_with_chatgpt_tokens);外部调用 6 个(path, format!, json!, to_string_pretty, to_vec, write)。
ProviderAuthCommandFixture::new281–345 ↗
fn new(tokens: &[&str]) -> std::io::Result<Self>
作用:创建一个测试用的“外部命令吐 token”环境。它模拟公司内部工具每次运行输出一个访问令牌的鉴权方式。
数据流:进去的是一串 token → 它把 token 写进临时文件,再按系统生成 shell 或 cmd 脚本;脚本每运行一次输出第一行 token 并把它删掉 → 出来是带临时目录、命令名和参数的 fixture。
调用关系:它被两个 provider auth 测试创建,用来验证客户端能从外部命令拿 Bearer token,以及 401 后能再次运行命令刷新 token。
调用图:被 2 处调用(provider_auth_command_refreshes_after_401, provider_auth_command_supplies_bearer_token);外部调用 7 个(new, new, metadata, set_permissions, write, tempdir, vec!)。
ProviderAuthCommandFixture::auth347–357 ↗
fn auth(&self) -> ModelProviderAuthInfo
作用:把命令式鉴权测试环境转换成模型提供方能读懂的鉴权配置。简单说,就是告诉客户端“去哪里、用什么命令、多久超时”来拿 token。
数据流:进去的是 fixture 自身保存的命令、参数和临时目录 → 它复制这些值,并设置超时、刷新间隔、工作目录 → 出来是 ModelProviderAuthInfo 配置对象。
调用关系:它调用 non_zero_u64 设置非零超时时间;provider auth 两个测试把它的结果交给 send_provider_auth_request 发真实请求。
调用图:调用 2 个内部函数(non_zero_u64, try_from);外部调用 1 个(path)。
non_zero_u64360–362 ↗
resume_includes_initial_messages_and_sends_prior_items365–571 ↗
async fn resume_includes_initial_messages_and_sends_prior_items()
作用:测试恢复旧会话时,旧的用户和助手消息会被继续发给模型,而且顺序正确。它同时确认系统消息不会错误混进 API 历史里。
数据流:它先手写一个旧会话 JSONL 文件,里面有用户、系统、助手消息 → 启动 Codex 从这个文件恢复,再提交新输入 hello → 捕获请求并检查旧用户、旧助手、权限说明、AGENTS 指令、环境上下文、新用户输入的先后顺序。
调用关系:它使用 message_input_texts 抽取请求文字,并依赖假 SSE 服务完成一次回合;这是恢复会话相关测试的主场景之一。
调用图:调用 4 个内部函数(mount_sse_once, sse, test_codex, message_input_texts);外部调用 15 个(new, default, start, new, new_v4, new, assert!, assert_eq!, wait_for_event, json! (+5 more))。
resume_replays_legacy_js_repl_image_rollout_shapes574–714 ↗
async fn resume_replays_legacy_js_repl_image_rollout_shapes()
作用:保证很早版本保存的 js_repl 图片结果,今天恢复会话时仍然能被正确重放。这样老用户的历史会话不会因为内部格式升级而坏掉。
数据流:它构造一份旧格式 rollout:自定义工具调用、文本输出、单独的用户图片消息 → 从这份文件恢复并提交新输入 → 检查请求里旧工具输出和旧图片都出现在新用户消息之前。
调用关系:它是独立兼容性测试,通过 test_codex().resume 走真实恢复逻辑,用假服务器记录最终发给模型的 input。
调用图:调用 3 个内部函数(mount_sse_once, sse, test_codex);外部调用 9 个(new, start, new, assert!, assert_eq!, skip_if_no_network!, create, vec!, writeln!)。
resume_replays_image_tool_outputs_with_detail717–842 ↗
async fn resume_replays_image_tool_outputs_with_detail()
作用:检查图片工具输出在恢复后仍保留图片清晰度 detail 信息。没有这个测试,图片可能还能传过去,但“原图/低清”等重要提示会丢。
数据流:它构造包含 view_image 和 js_repl 两类工具图片输出的旧会话 → 恢复会话并提交新输入 → 从请求中找到对应工具输出,确认 output 里有图片 URL 和 detail: original。
调用关系:它和旧 js_repl 兼容测试一起覆盖恢复图片历史的不同形状,依靠假 Responses 服务接住请求。
调用图:调用 3 个内部函数(mount_sse_once, sse, test_codex);外部调用 8 个(new, start, new, assert_eq!, skip_if_no_network!, create, vec!, writeln!)。
includes_session_id_thread_id_and_model_headers_in_request845–911 ↗
async fn includes_session_id_thread_id_and_model_headers_in_request()
作用:确认普通 API Key 请求会带上会话 ID、线程 ID、来源、授权头和请求体里的客户端元数据。服务端靠这些字段追踪一次对话属于谁、哪个线程。
数据流:它用 API Key 创建测试会话 → 提交 hello 并等完成 → 读取假服务器收到的请求头和请求体,核对路径、authorization、originator、prompt_cache_key 和 metadata。
调用关系:它调用 assert_codex_client_metadata 做深层元数据检查,覆盖最常见的 OpenAI API Key 发请求路径。
调用图:调用 5 个内部函数(mount_sse_once, sse, test_codex, assert_codex_client_metadata, from_api_key);外部调用 7 个(default, start, assert_eq!, wait_for_event, skip_if_no_network!, read_to_string, vec!)。
provider_auth_command_supplies_bearer_token914–927 ↗
async fn provider_auth_command_supplies_bearer_token()
作用:测试模型提供方配置了外部命令鉴权时,客户端会把命令输出当作 Bearer token 发出去。Bearer token 可以理解为 HTTP 请求里的一张通行证。
数据流:它搭一个只接受 Authorization: Bearer command-token 的假服务器 → 创建会输出 command-token 的命令 fixture → 调用通用发送函数,若请求能完成就说明 token 被正确使用。
调用关系:它用 ProviderAuthCommandFixture::new 建环境,再把 auth() 结果交给 send_provider_auth_request;服务器端匹配负责真正断言。
调用图:调用 4 个内部函数(mount_sse_once_match, sse, new, send_provider_auth_request);外部调用 4 个(start, skip_if_no_network!, vec!, header)。
provider_auth_command_refreshes_after_401930–959 ↗
async fn provider_auth_command_refreshes_after_401()
作用:测试命令式鉴权遇到 401 未授权后,会重新取 token 再试一次。这样临时 token 过期时用户不用手动重启。
数据流:它准备两个 token:第一次请求用 first-token 被服务器拒绝,第二次用 second-token 成功 → 发送一次模型请求 → 如果最终完成,说明客户端在 401 后刷新了 token。
调用关系:它和上一条测试共用 ProviderAuthCommandFixture 与 send_provider_auth_request,但额外用 wiremock 设置第一次失败、第二次成功的服务器规则。
调用图:调用 3 个内部函数(sse, new, send_provider_auth_request);外部调用 8 个(given, start, new, skip_if_no_network!, vec!, header_regex, method, path)。
send_provider_auth_request966–1056 ↗
async fn send_provider_auth_request(server: &MockServer, auth: ModelProviderAuthInfo)
作用:用指定的命令式鉴权配置发起一条真实的流式 Responses 请求。它把重复的客户端、模型信息、遥测和 prompt 搭建工作集中起来。
数据流:进去的是假服务器和鉴权配置 → 它构造一个自定义模型提供方、测试配置、ModelClient、会话遥测和包含 hello 的 prompt → 发起 stream 并读取事件,直到看到 Completed;如果中途鉴权或传输失败,测试会失败。
调用关系:它被两个 provider auth 测试调用;内部调用 test_turn_responses_metadata 准备元数据,并把真正 HTTP 交给 ModelClient 的流式接口。
调用图:调用 10 个内部函数(new, default, construct_model_info_offline, get_model_offline, test_turn_responses_metadata, from_auth_for_testing, from_api_key, new, new, disabled);被 2 处调用(provider_auth_command_refreshes_after_401, provider_auth_command_supplies_bearer_token);外部调用 5 个(new, new, load_default_config_for_test, format!, vec!)。
includes_base_instructions_override_in_request1059–1105 ↗
async fn includes_base_instructions_override_in_request()
作用:确认配置里的基础指令覆盖内容会放进请求的 instructions 字段。基础指令就是给模型的底层行为说明。
数据流:它设置 base_instructions 为 test instructions → 提交 hello → 检查请求体 instructions 字符串包含这段自定义指令。
调用关系:这是独立请求组装测试,使用 test_codex 创建会话、假 SSE 完成回合,然后直接检查请求体。
调用图:调用 4 个内部函数(mount_sse_once, sse, test_codex, from_api_key);外部调用 6 个(default, start, assert!, wait_for_event, skip_if_no_network!, vec!)。
chatgpt_auth_sends_correct_request1108–1188 ↗
async fn chatgpt_auth_sends_correct_request()
作用:确认使用 ChatGPT 登录态时,请求路径、访问 token、账号 ID 和流式参数都符合 Codex 后端接口要求。它区别于普通 API Key 请求。
数据流:它创建假的 ChatGPT 鉴权,把 OpenAI 提供方 base_url 指向假 /api/codex → 提交 hello → 检查请求路径、authorization、chatgpt-account-id、session/thread 头、metadata、stream 和 include 字段。
调用关系:它调用 create_dummy_codex_auth 提供测试登录态,并用 assert_codex_client_metadata 复用元数据一致性检查。
调用图:调用 5 个内部函数(mount_sse_once, sse, test_codex, assert_codex_client_metadata, create_dummy_codex_auth);外部调用 10 个(default, start, assert!, assert_eq!, built_in_model_providers, wait_for_event, format!, skip_if_no_network!, read_to_string, vec!)。
prefers_apikey_when_config_prefers_apikey_even_with_chatgpt_tokens1191–1280 ↗
async fn prefers_apikey_when_config_prefers_apikey_even_with_chatgpt_tokens()
作用:测试当本地同时有 API Key 和 ChatGPT token,但配置要求优先 API Key 时,客户端真的会用 API Key。否则可能把请求发到错误账号或错误计费方式下。
数据流:它先写入同时包含 sk-test-key 和 ChatGPT tokens 的 auth.json → 加载配置和鉴权,启动 ThreadManager 创建线程 → 提交 hello,服务器只接受 API Key 授权;请求成功就说明优先级正确。
调用关系:它调用 write_auth_json 准备磁盘登录文件,并比一般测试更接近真实启动流程:手动创建 AuthManager、ThreadManager 和线程。
调用图:调用 7 个内部函数(default, auth_manager_from_auth, new, sse, write_auth_json, default_for_tests, from_auth_storage);外部调用 18 个(new, default, given, start, new, new, resolve_installation_id, thread_store_from_config, empty_extension_registry, built_in_model_providers (+8 more))。
includes_user_instructions_message_in_request1283–1360 ↗
async fn includes_user_instructions_message_in_request()
作用:确认 AGENTS.md 里的用户项目指令不会塞进底层 instructions,而是作为用户上下文消息发给模型。这样模型能区分系统级规则和项目级说明。
数据流:它在临时目录写 AGENTS.md 内容 be nice → 提交 hello → 检查 instructions 不含 be nice,同时 input 第一条是 developer 权限说明,第二条 user 消息包含 AGENTS 指令和环境上下文。
调用关系:它使用 assert_message_role 和 message_input_texts 做细粒度检查,覆盖项目说明进入请求的标准路径。
调用图:调用 6 个内部函数(mount_sse_once, sse, test_codex, assert_message_role, message_input_texts, from_api_key);外部调用 6 个(default, start, assert!, wait_for_event, skip_if_no_network!, vec!)。
includes_apps_guidance_as_developer_message_for_chatgpt_auth1363–1423 ↗
async fn includes_apps_guidance_as_developer_message_for_chatgpt_auth()
作用:测试在 ChatGPT 登录且 Apps 功能开启时,Apps 使用说明会作为 developer 消息发送。developer 消息可以理解为给模型的开发者级操作规则。
数据流:它挂载假的 Apps 服务,开启 Apps 功能并使用 ChatGPT 鉴权 → 提交 hello → 检查 Apps 指引出现在 developer 消息里,而不是 user 消息里。
调用关系:它调用 create_dummy_codex_auth 得到 ChatGPT 测试登录,并用 message_input_text_contains 查找指引文本。
调用图:调用 5 个内部函数(mount, mount_sse_once, sse, test_codex, create_dummy_codex_auth);外部调用 6 个(default, start, assert!, wait_for_event, skip_if_no_network!, vec!)。
omits_apps_guidance_for_api_key_auth_even_when_feature_enabled1426–1481 ↗
async fn omits_apps_guidance_for_api_key_auth_even_when_feature_enabled()
作用:确认即使 Apps 功能开着,只要用户用的是 API Key 鉴权,也不会发送 ChatGPT Apps 指引。因为这类指引只适用于 ChatGPT 连接器环境。
数据流:它开启 Apps 功能但使用 API Key → 提交 hello → 检查 developer 和 user 消息里都没有 Apps 指引片段。
调用关系:它和 ChatGPT Apps 正向测试形成对照,使用同样的假 Apps 服务和请求捕获方式。
调用图:调用 5 个内部函数(mount, mount_sse_once, sse, test_codex, from_api_key);外部调用 6 个(default, start, assert!, wait_for_event, skip_if_no_network!, vec!)。
omits_apps_guidance_when_configured_off1484–1536 ↗
async fn omits_apps_guidance_when_configured_off()
作用:确认配置明确关闭 Apps 指令时,即使 ChatGPT 鉴权和 Apps 功能都可用,也不会把 Apps 指令发给模型。
数据流:它开启 Apps 功能、使用 ChatGPT 鉴权,但把 include_apps_instructions 设为 false → 提交 hello → 检查 developer 消息里没有 <apps_instructions>。
调用关系:它调用 create_dummy_codex_auth,和其他 Apps 测试一起覆盖“何时加入、何时不加入”这条规则。
调用图:调用 5 个内部函数(mount, mount_sse_once, sse, test_codex, create_dummy_codex_auth);外部调用 6 个(default, start, assert!, wait_for_event, skip_if_no_network!, vec!)。
omits_environment_context_when_configured_off1539–1578 ↗
async fn omits_environment_context_when_configured_off()
作用:测试关闭环境上下文配置后,请求里不会自动带 <environment_context>。这让用户可以控制是否把当前环境信息发给模型。
数据流:它把 include_environment_context 设为 false → 提交 hello → 检查 user 消息文本里找不到环境上下文标签。
调用关系:这是一个配置开关测试,使用 message_input_text_contains 间接检查请求内容。
调用图:调用 3 个内部函数(mount_sse_once, sse, test_codex);外部调用 5 个(default, start, assert!, wait_for_event, vec!)。
skills_append_to_developer_message1581–1647 ↗
async fn skills_append_to_developer_message()
作用:确认本地 skills(技能说明文件)会追加到 developer 消息里,让模型知道有哪些可用技能和对应文件位置。
数据流:它在临时 Codex home 下创建 skills/demo/SKILL.md → 提交 hello → 合并所有 developer 文本并检查有 Skills 标题、技能摘要和规范化后的文件路径。
调用关系:它通过真实请求组装流程验证技能发现结果最终进入模型请求,依赖假服务器捕获发送内容。
调用图:调用 4 个内部函数(mount_sse_once, sse, test_codex, from_api_key);外部调用 11 个(new, default, start, new, assert!, wait_for_event, canonicalize, skip_if_no_network!, create_dir_all, write (+1 more))。
skills_use_aliases_in_developer_message_under_budget_pressure1650–1737 ↗
async fn skills_use_aliases_in_developer_message_under_budget_pressure()
作用:测试当技能路径很多、上下文预算紧张时,客户端会用短别名压缩技能根目录路径。就像地图上用 r0 代表一长串地址,省空间但还能找回原路。
数据流:它创建很多技能并设置较小模型上下文窗口 → 提交 hello → 检查 developer 消息里出现 Skill roots、r0 根别名、用别名写的技能文件路径,以及如何展开别名的说明。
调用关系:它覆盖技能提示的压缩分支,和普通 skills 测试一起保证既可读又不太浪费请求长度。
调用图:调用 4 个内部函数(mount_sse_once, sse, test_codex, from_api_key);外部调用 13 个(new, default, start, new, new_in, assert!, wait_for_event, canonicalize, format!, skip_if_no_network! (+3 more))。
includes_configured_effort_in_request1740–1785 ↗
async fn includes_configured_effort_in_request() -> anyhow::Result<()>
作用:确认用户配置的推理强度 effort 会进入请求。推理强度可以理解为让模型花多少力气思考。
数据流:它选择支持推理参数的模型,并把 effort 设为 Medium → 提交 hello → 检查请求体 reasoning.effort 是 medium。
调用关系:这是推理参数系列测试之一,使用标准 test_codex 和假 SSE 服务完成一次回合后验请求。
调用图:调用 3 个内部函数(mount_sse_once, sse, test_codex);外部调用 6 个(default, start, assert_eq!, wait_for_event, skip_if_no_network!, vec!)。
includes_no_effort_in_request1788–1827 ↗
async fn includes_no_effort_in_request() -> anyhow::Result<()>
作用:测试在没有显式配置 effort 时,请求仍按模型信息里的默认值发送。这里期望默认是 medium。
数据流:它使用 gpt-5.4 但不手动设置 effort → 提交 hello → 检查请求体中的 reasoning.effort 最终为 medium。
调用关系:它和显式配置 effort 的测试互补,确认默认值来源没有断。
调用图:调用 3 个内部函数(mount_sse_once, sse, test_codex);外部调用 6 个(default, start, assert_eq!, wait_for_event, skip_if_no_network!, vec!)。
includes_default_reasoning_effort_in_request_when_defined_by_model_info1830–1870 ↗
async fn includes_default_reasoning_effort_in_request_when_defined_by_model_info() -> anyhow::Result<()>
作用:确认当模型资料定义了默认推理强度时,客户端会把这个默认值放进请求。这样不同模型可以有自己的默认行为。
数据流:它使用带默认推理强度的 gpt-5.4 → 提交 hello → 检查请求体 reasoning.effort 是 medium。
调用关系:它属于模型目录默认值测试,和 includes_no_effort_in_request 覆盖的现象相近,但强调默认值来自模型信息。
调用图:调用 3 个内部函数(mount_sse_once, sse, test_codex);外部调用 6 个(default, start, assert_eq!, wait_for_event, skip_if_no_network!, vec!)。
user_turn_collaboration_mode_overrides_model_and_effort1873–1930 ↗
async fn user_turn_collaboration_mode_overrides_model_and_effort() -> anyhow::Result<()>
作用:测试单次用户回合传入的协作模式设置可以覆盖模型和推理强度。也就是说,这一轮可以临时换挡。
数据流:它创建一个回合级 collaboration_mode,里面指定模型和 High effort → 提交 hello,并把这些覆盖项放进 thread_settings → 检查请求体模型和 reasoning.effort 采用本轮设置。
调用关系:它调用 local_selections 准备环境选择,覆盖的是比全局配置更靠近用户输入的一层设置优先级。
调用图:调用 4 个内部函数(mount_sse_once, sse, local_selections, test_codex);外部调用 6 个(default, start, assert_eq!, wait_for_event, skip_if_no_network!, vec!)。
configured_reasoning_summary_is_sent1933–1983 ↗
async fn configured_reasoning_summary_is_sent() -> anyhow::Result<()>
作用:确认配置要求发送推理摘要时,请求里会带上对应 summary 参数。推理摘要是模型对自己思考过程的简短说明。
数据流:它把 model_reasoning_summary 设置为 Concise → 提交 hello → 检查请求体 reasoning.summary 是 concise,同时没有设置 lite 模式用的 reasoning.context。
调用关系:这是 reasoning summary 系列测试的正向配置场景,使用假服务器捕获最终请求体。
调用图:调用 3 个内部函数(mount_sse_once, sse, test_codex);外部调用 6 个(default, start, wait_for_event, assert_eq!, skip_if_no_network!, vec!)。
responses_lite_sets_all_turns_context_and_disables_parallel_tool_calls1986–2031 ↗
async fn responses_lite_sets_all_turns_context_and_disables_parallel_tool_calls() -> anyhow::Result<()>
作用:测试模型使用 Responses Lite 模式时,请求会要求 all_turns 上下文,并关闭并行工具调用。这样适配轻量接口的限制。
数据流:它把模型信息改成 use_responses_lite = true 且支持并行工具 → 提交 hello → 检查请求体 reasoning.context 为 all_turns,parallel_tool_calls 为 false。
调用关系:它通过模型信息覆盖触发特殊请求分支,确认客户端会根据模型能力调整发送参数。
调用图:调用 3 个内部函数(mount_sse_once, sse, test_codex);外部调用 6 个(default, start, wait_for_event, assert_eq!, skip_if_no_network!, vec!)。
user_turn_explicit_reasoning_summary_overrides_model_catalog_default2034–2108 ↗
async fn user_turn_explicit_reasoning_summary_overrides_model_catalog_default() -> anyhow::Result<()>
作用:确认单次用户回合明确指定的推理摘要设置,会覆盖模型目录里的默认摘要设置。离用户最近的选择优先。
数据流:它修改模型目录,让 gpt-5.4 默认 summary 为 Detailed → 提交一轮时在 thread_settings 里指定 Concise → 检查最终请求体 reasoning.summary 是 concise。
调用关系:它调用 bundled_models_response 准备模型目录,并用 local_selections 补齐回合设置;它验证配置优先级链条。
调用图:调用 4 个内部函数(mount_sse_once, sse, local_selections, test_codex);外部调用 7 个(default, start, bundled_models_response, wait_for_event, assert_eq!, skip_if_no_network!, vec!)。
reasoning_summary_is_omitted_when_disabled2111–2154 ↗
async fn reasoning_summary_is_omitted_when_disabled() -> anyhow::Result<()>
作用:测试用户把推理摘要设为 None 时,请求里不会带 summary 字段。None 在这里不是“默认”,而是明确不要。
数据流:它配置 model_reasoning_summary = None → 提交 hello → 检查请求体 reasoning.summary 不存在。
调用关系:它和 summary 正向发送测试互补,确保关闭开关不会被默认值偷偷覆盖。
调用图:调用 3 个内部函数(mount_sse_once, sse, test_codex);外部调用 6 个(default, start, wait_for_event, assert_eq!, skip_if_no_network!, vec!)。
reasoning_summary_none_overrides_model_catalog_default2157–2210 ↗
async fn reasoning_summary_none_overrides_model_catalog_default() -> anyhow::Result<()>
作用:确认即使模型目录有默认推理摘要,用户配置 None 也能关掉它。用户明确关闭的优先级高于模型默认。
数据流:它修改模型目录让默认 summary 为 Detailed,同时配置 summary 为 None → 提交 hello → 检查请求体里没有 reasoning.summary。
调用关系:它使用 bundled_models_response 构造默认值场景,是 summary 优先级测试里的“强制关闭”案例。
调用图:调用 3 个内部函数(mount_sse_once, sse, test_codex);外部调用 7 个(default, start, bundled_models_response, wait_for_event, assert_eq!, skip_if_no_network!, vec!)。
includes_default_verbosity_in_request2213–2252 ↗
async fn includes_default_verbosity_in_request() -> anyhow::Result<()>
作用:确认支持 verbosity 的模型会收到默认啰嗦程度参数。verbosity 可以理解为希望模型回答得简短还是详细。
数据流:它使用 gpt-5.4,不手动设置 verbosity → 提交 hello → 检查请求体 text.verbosity 是 low。
调用关系:这是 verbosity 系列测试的默认值场景,验证模型请求里文本风格参数的组装。
调用图:调用 3 个内部函数(mount_sse_once, sse, test_codex);外部调用 6 个(default, start, assert_eq!, wait_for_event, skip_if_no_network!, vec!)。
configured_verbosity_not_sent_for_models_without_support2255–2299 ↗
async fn configured_verbosity_not_sent_for_models_without_support() -> anyhow::Result<()>
作用:测试如果模型不支持 verbosity,即使用户配置了 High,也不会把这个字段发出去。这样避免服务端因为不认识参数而报错。
数据流:它选择名为 test-no-verbosity 的模型并配置 High → 提交 hello → 检查请求体没有 text.verbosity。
调用关系:它和发送 verbosity 的测试形成对照,重点验证客户端会尊重模型能力。
调用图:调用 3 个内部函数(mount_sse_once, sse, test_codex);外部调用 6 个(default, start, assert!, wait_for_event, skip_if_no_network!, vec!)。
configured_verbosity_is_sent2302–2347 ↗
async fn configured_verbosity_is_sent() -> anyhow::Result<()>
作用:确认当模型支持 verbosity 且用户配置了 High,请求里会发送 high。这样用户的回答详略偏好能传到服务端。
数据流:它使用 gpt-5.4 并设置 model_verbosity = High → 提交 hello → 检查请求体 text.verbosity 是 high。
调用关系:它是 verbosity 系列的显式配置场景,和默认值、不可支持模型两个测试一起覆盖完整规则。
调用图:调用 3 个内部函数(mount_sse_once, sse, test_codex);外部调用 6 个(default, start, assert_eq!, wait_for_event, skip_if_no_network!, vec!)。
includes_developer_instructions_message_in_request2350–2444 ↗
async fn includes_developer_instructions_message_in_request()
作用:确认配置里的开发者指令会作为 developer 消息发送,同时 AGENTS.md 仍作为用户上下文发送。两类指令层级不同,不能混在一起。
数据流:它写入 AGENTS.md,并配置 developer_instructions 为 be useful → 提交 hello → 检查权限说明是 developer 消息、另有 developer 消息包含 be useful,user 消息包含 AGENTS 和环境上下文。
调用关系:它使用 assert_message_role 和 message_input_texts 逐项检查,是开发者指令请求格式的主要测试。
调用图:调用 6 个内部函数(mount_sse_once, sse, test_codex, assert_message_role, message_input_texts, from_api_key);外部调用 6 个(default, start, assert!, wait_for_event, skip_if_no_network!, vec!)。
azure_responses_request_includes_store_and_reasoning_ids2447–2631 ↗
async fn azure_responses_request_includes_store_and_reasoning_ids()
作用:测试 Azure 风格的 Responses 请求会设置 store: true,并保留历史条目的 id 或 call_id。Azure 后端需要这些标识来串起历史和推理项。
数据流:它手动构造一个 Azure 提供方和 ModelClient,再往 prompt 塞入 reasoning、message、web search、function、local shell、自定义工具等多种历史项 → 发起流式请求 → 检查路径、store、stream、input 长度以及每项 ID 是否保留。
调用关系:它直接使用 ModelClient,调用 test_turn_responses_metadata 提供元数据;相比普通 test_codex 测试,它更精确地覆盖底层请求序列化。
调用图:调用 12 个内部函数(new, default, auth_manager_from_auth, construct_model_info_offline, get_model_offline, mount_sse_once, test_turn_responses_metadata, from_api_key, new, from_text (+2 more));外部调用 10 个(new, start, new, assert_eq!, concat!, load_default_config_for_test, format!, Exec, skip_if_no_network!, vec!)。
token_count_includes_rate_limits_snapshot2634–2765 ↗
async fn token_count_includes_rate_limits_snapshot()
作用:确认模型完成后发出的 token 计数事件里,也包含最新的速率限制快照。用户界面可以据此显示用了多少额度、什么时候重置。
数据流:它让假服务器返回带 token 用量的 SSE,并在 HTTP 头里放主/副限额百分比、窗口和重置时间 → 提交 hello → 等到 TokenCount 事件,检查 usage 和 rate_limits 的完整 JSON。
调用关系:它覆盖响应头解析和事件输出的连接点,最后还等 TurnComplete 保证整轮正常收尾。
调用图:调用 3 个内部函数(sse, test_codex, from_api_key);外部调用 15 个(default, given, start, new, assert_eq!, built_in_model_providers, wait_for_event, format!, assert_eq!, to_value (+5 more))。
usage_limit_error_emits_rate_limit_event2768–2856 ↗
async fn usage_limit_error_emits_rate_limit_event() -> anyhow::Result<()>
作用:测试遇到 429 使用额度耗尽错误时,客户端会先发出限额事件,再发错误事件。这样界面能既提示失败,也显示限额状态。
数据流:它配置假服务器返回 429、限额相关响应头和 usage_limit_reached 错误体 → 提交 hello → 等 TokenCount 事件并核对 rate_limits,再等 Error 事件并确认错误信息提到 usage limit。
调用关系:它验证错误响应路径,不靠流式完成事件;和正常 token count 测试一起覆盖成功和失败两种限额信息来源。
调用图:调用 1 个内部函数(test_codex);外部调用 14 个(default, given, start, new, assert!, wait_for_event, json!, assert_eq!, to_value, skip_if_no_network! (+4 more))。
context_window_error_sets_total_tokens_to_model_window2859–2961 ↗
async fn context_window_error_sets_total_tokens_to_model_window() -> anyhow::Result<()>
作用:测试当服务端说上下文太长时,客户端会把 token 总数显示成模型有效上下文窗口大小。这样用户能看出大概超到了上限。
数据流:它先完成一轮 seed turn,再提交会触发 context_length_exceeded 的输入 → 等 TokenCount 事件,确认总 token 等于 95% 的模型窗口 → 再检查错误事件是上下文窗口超限,并等待回合结束。
调用关系:它使用 mount_sse_once_match 和 sse_failed 精准模拟流式失败,是上下文窗口错误处理的端到端测试。
调用图:调用 4 个内部函数(mount_sse_once_match, sse, sse_failed, test_codex);外部调用 9 个(default, start, assert!, assert_eq!, wait_for_event, skip_if_no_network!, unreachable!, vec!, body_string_contains)。
incomplete_response_emits_content_filter_error_message2964–3022 ↗
async fn incomplete_response_emits_content_filter_error_message() -> anyhow::Result<()>
作用:确认流式响应以 content_filter 原因变成 incomplete 时,客户端会给出明确错误。也就是告诉用户内容被过滤导致没有完整回复。
数据流:它让假服务器发送部分消息增量,然后发送 response.incomplete 且 reason 为 content_filter → 提交触发输入 → 等 Error 事件并核对错误文案,同时确认没有重试多次。
调用关系:它覆盖流式 SSE 中途不完整的错误分支,依赖 mount_sse_once 记录请求次数。
调用图:调用 3 个内部函数(mount_sse_once, sse, test_codex);外部调用 7 个(default, start, assert!, assert_eq!, wait_for_event, skip_if_no_network!, vec!)。
azure_overrides_assign_properties_used_for_responses_url3034–3120 ↗
async fn azure_overrides_assign_properties_used_for_responses_url()
作用:测试自定义/Azure 类提供方的 base_url、query 参数、自定义请求头和环境变量 token 都会用于最终 Responses URL。这样企业部署能按自己的网关要求发请求。
数据流:它配置 provider:路径是 /openai、query 有 api-version、头有 Custom-Header、token 来自 PATH 环境变量 → 提交 hello → 假服务器只接受这些条件全部匹配的请求,匹配成功说明组装正确。
调用关系:它调用 create_dummy_codex_auth 但实际授权由 provider 的环境变量覆盖;这是自定义 provider 网络配置的测试。
调用图:调用 3 个内部函数(sse, test_codex, create_dummy_codex_auth);外部调用 14 个(default, given, start, new, wait_for_event, format!, skip_if_no_network!, from, vec!, header (+4 more))。
env_var_overrides_loaded_auth3123–3209 ↗
async fn env_var_overrides_loaded_auth()
作用:确认提供方指定的环境变量密钥会覆盖已加载的登录态。也就是说,如果 provider 明确说从环境变量取 token,就按它说的来。
数据流:它配置和上一项类似的 provider,并同时给测试客户端一个假的 ChatGPT 登录态 → 提交 hello → 服务器要求 Authorization 使用环境变量 PATH 的值,请求成功说明环境变量优先。
调用关系:它和 azure_overrides_assign_properties_used_for_responses_url 场景很接近,重点放在“环境变量覆盖已加载 auth”的优先级。
调用图:调用 3 个内部函数(sse, test_codex, create_dummy_codex_auth);外部调用 14 个(default, given, start, new, wait_for_event, format!, skip_if_no_network!, from, vec!, header (+4 more))。
create_dummy_codex_auth3211–3213 ↗
fn create_dummy_codex_auth() -> CodexAuth
作用:创建一个测试用的假 ChatGPT 登录态。它避免每个测试都重复写登录 token 的细节。
数据流:它不需要输入 → 调用测试专用的 dummy auth 创建函数 → 返回一个可用于 ChatGPT 鉴权路径的 CodexAuth。
调用关系:它被 ChatGPT 请求、Apps 指引、自定义 provider 覆盖等多个测试调用,是这些测试共享的登录态小工厂。
调用图:调用 1 个内部函数(create_dummy_chatgpt_auth_for_testing);被 5 处调用(azure_overrides_assign_properties_used_for_responses_url, chatgpt_auth_sends_correct_request, env_var_overrides_loaded_auth, includes_apps_guidance_as_developer_message_for_chatgpt_auth, omits_apps_guidance_when_configured_off)。
history_dedupes_streamed_and_final_messages_across_turns3222–3348 ↗
async fn history_dedupes_streamed_and_final_messages_across_turns()
作用:测试多轮对话中,模型先流式吐出的文字和最后完整助手消息不会在历史里重复保存。否则第三轮请求会把同一句助手回复发两遍。
数据流:它让假服务器连续三次都返回同样的 SSE:先增量输出 Hey there,再给最终 assistant 消息 → 依次提交 U1、U2、U3 → 检查第三次请求的历史尾部只有 U1/A/U2/A/U3,没有重复的流式片段。
调用关系:它用 mount_sse_sequence 捕获三次请求,是对话历史去重逻辑的端到端回归测试。
调用图:调用 4 个内部函数(mount_sse_sequence, sse, test_codex, from_api_key);外部调用 7 个(default, start, assert_eq!, wait_for_event, json!, skip_if_no_network!, vec!)。
core/tests/responses_headers.rs源码 ↗
可以把这个文件理解成“出门前查证件”的测试。Codex 和模型服务通信时,不只是发送用户说了什么,还要附带一些背景信息:这次请求是不是来自子代理、属于哪个会话窗口、当前项目是不是 Git 仓库、最近提交是什么、配置里是否要求返回推理摘要等。这个文件用假的模型服务器接住请求,然后检查请求头和请求体。这样不用真的依赖线上服务,也能确认客户端发出去的东西是对的。它还会临时创建测试目录,甚至初始化一个 Git 仓库,模拟真实用户在项目里使用 Codex 的情况。最重要的是,它验证“一轮对话里可能有多次请求”时,这些请求应该共享同一个 turn_id 和开始时间,避免后台把同一轮对话误当成多轮。
normalize_git_remote_url28–34 ↗
fn normalize_git_remote_url(url: &str) -> String
作用:把 Git 远程仓库地址整理成更容易比较的样子。它主要是避免同一个地址因为末尾多了斜杠或“.git”后缀,就被测试误判成不一样。
数据流:进去的是一个远程仓库地址字符串 → 它先去掉前后空白,再去掉末尾的斜杠,最后如果结尾是“.git”也去掉 → 出来的是一个标准化后的地址字符串,不会改动外部状态。
调用关系:它服务于 Git 工作区元数据检查。测试拿模型请求里记录的远程地址和本地 Git 命令读到的地址做比较时,会先用它把两边整理一下,避免格式小差异干扰真正要验证的内容。
test_turn_responses_metadata37–53 ↗
fn test_turn_responses_metadata(
_client: &ModelClient,
thread_id: ThreadId,
session_source: &SessionSource,
) -> codex_core::CodexResponsesMetadata
作用:生成一份测试用的 Responses 元数据,也就是请求里要带给模型服务的会话和轮次信息。这样多个测试不用重复手写同一套假数据。
数据流:进去的是模型客户端、线程编号和会话来源 → 它把线程编号转成字符串,并配上固定的测试安装 ID、窗口 ID、请求类型等信息 → 出来是一份 CodexResponsesMetadata,后续会被放进实际发出的模型请求里。
调用关系:它是几个请求头测试的共同小帮手。子代理 review 测试、子代理 other 测试、配置覆盖测试都会先调用它准备元数据,然后再让 ModelClient 发起 stream 请求,由请求记录器检查这些元数据有没有真的进入请求。
调用图:被 3 处调用(responses_respects_model_info_overrides_from_config, responses_stream_includes_subagent_header_on_other, responses_stream_includes_subagent_header_on_review);外部调用 3 个(responses_metadata, format!, to_string)。
responses_stream_includes_subagent_header_on_review56–184 ↗
async fn responses_stream_includes_subagent_header_on_review()
作用:验证当一次会话来自“review”子代理时,发给模型服务的请求头里会明确写上 x-openai-subagent: review。这样服务端就能知道这不是普通聊天,而是代码审查类子任务。
数据流:进去的是测试自己搭出来的假服务器、假配置、线程 ID、会话来源和一条“hello”提示 → 它启动 mock 服务器,配置 ModelClient,构造提示词并打开流式请求,读到完成事件后停止 → 最后取出服务器收到的请求,检查子代理请求头、窗口 ID、安装 ID、父线程 ID和沙箱字段是否符合预期。
调用关系:这是一个端到端味道较强的单元测试。它先让测试工具启动假 Responses 服务,再用 test_turn_responses_metadata 准备元数据,最后把活交给 ModelClient 的 stream 流程;等请求被假服务记录下来后,它负责做断言,确认底层发送逻辑没有漏掉 review 来源。
调用图:调用 11 个内部函数(new, default, construct_model_info_offline, get_model_offline, mount_sse_once_match, sse, start_mock_server, test_turn_responses_metadata, new, new (+1 more));外部调用 10 个(new, new, SubAgent, assert_eq!, load_default_config_for_test, skip_if_no_network!, format!, matches!, vec!, header)。
responses_stream_includes_subagent_header_on_other187–301 ↗
async fn responses_stream_includes_subagent_header_on_other()
作用:验证自定义名字的子代理也会被原样写进请求头。比如子代理叫“my-task”,请求头就应该带 x-openai-subagent: my-task。
数据流:进去的是测试搭建的假服务、模型配置、线程信息和一个自定义子代理来源 → 它发送一条简单的用户消息,等待流式响应完成 → 出来没有业务返回值,但测试会读取假服务器记录的请求,并确认请求头里的子代理名字就是“my-task”。
调用关系:它和 review 子代理测试走的是同一条发送链路,只是把会话来源换成 Other("my-task")。它同样调用 test_turn_responses_metadata 准备请求元数据,再通过 ModelClient 发请求,用 mock 服务验证最终网络请求长什么样。
调用图:调用 11 个内部函数(new, default, construct_model_info_offline, get_model_offline, mount_sse_once_match, sse, start_mock_server, test_turn_responses_metadata, new, new (+1 more));外部调用 11 个(new, new, SubAgent, assert_eq!, load_default_config_for_test, skip_if_no_network!, format!, matches!, Other, vec! (+1 more))。
responses_respects_model_info_overrides_from_config304–432 ↗
async fn responses_respects_model_info_overrides_from_config()
作用:验证模型能力信息可以被配置覆盖,特别是配置说“这个模型支持推理摘要”时,请求体里会带上对应的 reasoning.summary。没有这个测试,配置可能写了但实际请求不生效。
数据流:进去的是一套测试配置,其中模型被设为 gpt-3.5-turbo,并强制开启推理摘要,摘要级别是 Detailed → 它构造模型信息、遥测信息和一条用户消息,然后通过 ModelClient 发流式请求 → 最后从假服务器收到的 JSON 请求体里取出 reasoning 字段,确认 summary 是“detailed”。
调用关系:它位于配置到请求生成这条链路的末端检查点。测试先通过配置影响 construct_model_info_offline 得到的模型信息,再调用 test_turn_responses_metadata 和 ModelClient stream;mock 服务器记录请求后,它验证配置覆盖确实传到了最终的 Responses 请求体。
调用图:调用 12 个内部函数(new, default, auth_manager_from_auth, construct_model_info_offline, mount_sse_once, sse, start_mock_server, test_turn_responses_metadata, from_api_key, new (+2 more));外部调用 11 个(new, new, SubAgent, assert!, assert_eq!, load_default_config_for_test, skip_if_no_network!, format!, matches!, Other (+1 more))。
responses_stream_includes_turn_metadata_header_for_git_workspace_e2e435–651 ↗
async fn responses_stream_includes_turn_metadata_header_for_git_workspace_e2e()
作用:验证真实一点的对话流程里,每次发给模型服务的请求都会带 x-codex-turn-metadata,并且里面包含正确的一轮对话信息和 Git 工作区信息。它防止服务端看不到当前项目状态,比如最新提交、远程仓库、是否有未提交改动。
数据流:进去的是一个测试 Codex 实例、假 Responses 服务器和临时工作目录 → 它先发一轮普通请求,检查 turn_id、开始时间和沙箱信息;然后在目录里初始化 Git 仓库、提交文件、添加远程地址,再发一轮会触发两次模型请求的对话 → 最后取出两次请求的元数据,确认同一轮共享同一个 turn_id 和开始时间,并检查 Git 提交哈希、远程地址和是否有改动等信息。
调用关系:这是本文件里最接近真实使用场景的测试。它用 test_codex 搭起一个可提交对话的 Codex 测试对象,用 mock server 按顺序返回模型事件;当模型事件里包含 shell command call 时,Codex 会继续发后续请求,所以测试能检查“一轮里多请求”的元数据一致性。Git 命令由测试直接调用,用来制造和读取真实仓库状态。
调用图:调用 5 个内部函数(mount_response_sequence, mount_sse_once, sse, start_mock_server, test_codex);外部调用 8 个(from_utf8, assert!, assert_eq!, assert_ne!, skip_if_no_network!, from_str, write, vec!)。
core/tests/suite/request_compression.rs源码 ↗
这个测试文件像一个“快递验货员”:程序把用户输入打包成网络请求发出去,它检查包裹外面有没有写“zstd 压缩”,以及拆开后内容是不是正常的 JSON。zstd 是一种压缩格式,可以让请求体更小。文件里先搭一个假的服务器,让 Codex 以为自己在和真实后端说话;再打开“请求压缩”这个功能开关;然后分别用两种身份发请求。第一种是 ChatGPT/Codex 后端认证,测试要求请求必须带 content-encoding: zstd,并且解压后能看到 Responses API 的 input 字段。第二种是 API key 认证,测试要求请求不能压缩,身体本身就是普通 JSON。这样能防止以后有人改网络请求代码时,把压缩用错地方,导致某些后端读不懂请求。
request_body_is_zstd_compressed_for_codex_backend_when_enabled19–68 ↗
async fn request_body_is_zstd_compressed_for_codex_backend_when_enabled() -> anyhow::Result<()>
作用:这个测试确认:当使用 Codex/ChatGPT 后端认证,并且打开“请求压缩”开关时,发出去的请求体真的会用 zstd 压缩。它还顺手确认压缩包解开后仍然是后端能读懂的 Responses API JSON。
数据流:进去的是一个假的后端服务器、一个测试用的 Codex 实例、一个用户输入“compress me”,以及被打开的 EnableRequestCompression 功能开关。测试把 Codex 的后端地址指向假服务器,并用测试用 ChatGPT 认证提交这段输入;等到回合完成后,它从假服务器记录里取出唯一一次请求。最后它检查请求头里有 content-encoding: zstd,把请求体解压,再解析成 JSON,并确认里面有 input 字段;如果这些都成立,测试通过。
调用关系:它是一次完整的端到端风格测试:先调用 start_mock_server 搭假服务器,再用 sse 和 mount_sse_once 准备一次假的流式返回;然后通过 test_codex 创建测试版 Codex,并用 create_dummy_chatgpt_auth_for_testing 模拟 ChatGPT 登录。提交输入后,它等 wait_for_event 看到 TurnComplete,说明请求已经打到服务器,最后自己检查服务器收到的请求。
调用图:调用 6 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, create_dummy_chatgpt_auth_for_testing, new);外部调用 9 个(default, assert!, assert_eq!, wait_for_event, format!, from_slice, skip_if_no_network!, vec!, decode_all)。
request_body_is_not_compressed_for_api_key_auth_even_when_enabled71–119 ↗
async fn request_body_is_not_compressed_for_api_key_auth_even_when_enabled() -> anyhow::Result<()>
作用:这个测试确认:即使打开了“请求压缩”开关,只要走的是普通 API key 认证,请求体也不能被压缩。它是在防止压缩功能误伤不支持这种压缩方式的认证路径。
数据流:进去的是一个假的服务器、一个默认认证方式的测试 Codex 实例、一个用户输入“do not compress”,以及同样被打开的 EnableRequestCompression 功能开关。测试把请求发到假服务器,等 Codex 报告本轮完成后,取出服务器收到的请求。然后它确认请求头里没有 content-encoding,再直接把请求体按普通 JSON 解析,并检查里面有 input 字段;也就是说,请求应该是明文 JSON,而不是压缩数据。
调用关系:它和前一个测试形成对照:同样调用 start_mock_server、sse、mount_sse_once 搭好假服务器和假响应,也同样用 test_codex 建测试实例、提交用户输入、再等 wait_for_event 看到 TurnComplete。区别是它没有加 ChatGPT 测试认证,所以走 API key 认证路径;最后它检查网络层没有把请求交给 zstd 压缩。
调用图:调用 4 个内部函数(mount_sse_once, sse, start_mock_server, test_codex);外部调用 7 个(default, assert!, wait_for_event, format!, from_slice, skip_if_no_network!, vec!)。
core/tests/suite/models_etag_responses.rs源码 ↗
这个测试只在非 Windows 系统上跑。它用一个假的网络服务器来扮演 OpenAI 接口,先让 Codex 启动时访问一次 /v1/models,并记住服务器给的模型列表版本号,也就是 ETag(可以理解成“这份菜单的版本标签”)。接着测试模拟一次用户请求:服务器在 /responses 回答里带了一个新的 X-Models-Etag,表示“模型菜单已经换新版了”。这时 Codex 应该马上再去拉一次 /v1/models。随后,工具调用结果再次提交给 /responses,服务器继续给同一个新版 ETag;因为 Codex 已经刷新过了,所以不应该再拉第三次模型列表。测试最后检查三件事:启动时确实拉过一次模型列表;ETag 不一致后只刷新一次;刷新请求里还带了 client_version,说明走的是正式的模型列表客户端逻辑,而不是随便拼的请求。
refresh_models_on_models_etag_mismatch_and_avoid_duplicate_models_fetch32–165 ↗
async fn refresh_models_on_models_etag_mismatch_and_avoid_duplicate_models_fetch() -> Result<()>
作用:这个测试函数验证 Codex 对模型列表版本号变化的反应:发现服务器说模型列表变了,就刷新一次;如果后面还是同一个新版本,就不要重复刷新。它用假服务器和假响应,把网络过程控制得很精确。
数据流:进去的是一组测试准备数据:假的认证信息、假的服务器、两个 ETag 版本号、两段模拟的 /responses 流式返回内容,以及一次用户输入。函数先启动假服务器,让 Codex 启动时拿到旧模型 ETag;然后模拟服务器在第一次响应里返回新 ETag 和一个 shell 工具调用;Codex 执行完工具后再提交工具输出,收到同一个新 ETag。出来的结果不是业务数据,而是一组断言:/v1/models 启动时请求了一次,ETag 变化后又请求了一次,之后没有再多请求,并且刷新模型列表时带上了 client_version 查询参数。
调用关系:它是 Tokio 异步测试框架在测试阶段直接运行的入口。它把具体搭台子的工作交给 test_codex 创建测试版 Codex,把假接口交给 mount_models_once_with_etag、mount_response_once、sse 和 sse_response 来准备,把权限和本地环境交给 turn_permission_fields、local_selections 来生成。测试过程中它通过 codex.submit 发起用户请求,再用 wait_for_event_with_timeout 等到这一轮对话结束,最后检查各个假接口收到的请求次数和内容。
调用图:调用 8 个内部函数(mount_models_once_with_etag, mount_response_once, sse, sse_response, local_selections, test_codex, turn_permission_fields, create_dummy_chatgpt_auth_for_testing);外部调用 10 个(clone, default, from_secs, start, new, assert!, assert_eq!, wait_for_event_with_timeout, skip_if_no_network!, vec!)。
WebSocket 与实时传输
这些套件测试 websocket 和实时会话行为,涵盖请求封帧、预热流程以及会话级传输处理。
core/tests/suite/agent_websocket.rs源码 ↗
这份测试文件搭了一个假的 WebSocket 服务器,让 Codex 以为自己正在和真实后端通信。测试会预先安排服务器返回什么事件,比如“创建响应”“要求执行 shell 命令”“回复完成”。然后它启动一个测试版 Codex,提交用户输入,再检查 Codex 发给服务器的 JSON 请求是不是符合预期。这里重点覆盖两类事:一是普通 WebSocket 流程,比如模型要求执行命令后,Codex 要把命令结果放进下一次请求里;二是新版 WebSocket V2 流程,比如启动时会先发一个不生成内容的“预热”请求,还要正确处理 previous_response_id、openai-beta 请求头和 service_tier(服务档位,比如 fast/priority)。这些测试像是在给通信线路做体检:不看模型聪不聪明,只看每一封信有没有按规矩寄出去。
websocket_test_codex_shell_chain20–67 ↗
async fn websocket_test_codex_shell_chain() -> Result<()>
作用:这个测试确认旧版 WebSocket 流程里,模型要求执行 shell 命令后,Codex 会再发第二个请求,把后续输入继续传给服务器。简单说,它检查“模型叫你跑命令 → 你跑完后继续回话”这条链路没有断。
数据流:进去的是一个假的 WebSocket 服务器脚本:第一次返回一个 shell 命令调用,第二次返回 assistant 的 done 消息;测试还给 Codex 提交“run the echo command”。它让 Codex 连接服务器、提交这一轮对话,然后读取服务器收到的两次请求。出来的结果是断言通过:两次请求都是 response.create,并且第二次请求里有 input 内容;最后关闭假服务器。
调用关系:测试开始时先用 skip_if_no_network! 避开无网络环境,再用 start_websocket_server 搭假服务器,用 test_codex 创建测试客户端。真正流程由 submit_turn_with_policy 触发,测试最后用 assert_eq! 和 assert! 检查服务器记录到的请求是否符合预期。
调用图:调用 2 个内部函数(start_websocket_server, test_codex);外部调用 4 个(assert!, assert_eq!, skip_if_no_network!, vec!)。
websocket_first_turn_uses_startup_prewarm_and_create70–124 ↗
async fn websocket_first_turn_uses_startup_prewarm_and_create() -> Result<()>
作用:这个测试确认第一次正式对话前,Codex 会先通过 WebSocket 发一个“预热”请求。预热请求不会让模型生成内容,只是把连接和上下文先准备好,避免真正用户请求来时才临时起步。
数据流:进去的是假服务器安排的两段返回:第一段给预热请求用,第二段给真正的 hello 对话用。测试启动 Codex 后提交 hello,然后读取同一条 WebSocket 连接上的两个请求。它检查第一个请求 generate=false,元数据里标着 request_kind=prewarm;第二个请求带有工具列表,元数据标着 request_kind=turn。出来的结果是证明预热和正式请求都发了,而且只建立了一次握手连接。
调用关系:它通过 start_websocket_server 准备服务器,通过 test_codex 构造客户端,通过 submit_turn_with_policy 触发第一轮对话。检查元数据时用 serde_json::from_str 把字符串形式的 JSON 读出来,再用 assert_eq! 和 assert! 验证字段。
调用图:调用 2 个内部函数(start_websocket_server, test_codex);外部调用 5 个(assert!, assert_eq!, from_str, skip_if_no_network!, vec!)。
websocket_first_turn_handles_handshake_delay_with_startup_prewarm127–171 ↗
async fn websocket_first_turn_handles_handshake_delay_with_startup_prewarm() -> Result<()>
作用:这个测试确认 WebSocket 握手变慢时,Codex 仍然能正确完成启动预热和第一轮正式请求。握手可以理解成通话前的“接通电话”,这里故意让接通慢一点,看系统会不会乱掉。
数据流:进去的是一个带延迟设置的假服务器:握手会晚 150 毫秒接受,之后依次响应预热请求和 hello 请求。测试启动 Codex、提交 hello,再读取服务器收到的两个请求。它检查第一个请求是 generate=false 的预热 response.create,第二个请求也是 response.create 且工具列表不为空。出来的结果是证明即使连接启动慢,Codex 也不会跳过预热或丢掉工具信息。
调用关系:它使用 start_websocket_server_with_headers 创建一个更可控的假服务器,其中 accept_delay 模拟慢握手。随后仍由 test_codex 创建客户端,由 submit_turn_with_policy 触发请求,最后用 assert_eq! 和 assert! 做验证并关闭服务器。
调用图:调用 2 个内部函数(start_websocket_server_with_headers, test_codex);外部调用 4 个(assert!, assert_eq!, skip_if_no_network!, vec!)。
websocket_v2_test_codex_shell_chain174–256 ↗
async fn websocket_v2_test_codex_shell_chain() -> Result<()>
作用:这个测试检查新版 WebSocket V2 的完整工具调用链路:先预热,再执行 shell 命令,再把命令输出作为下一次输入发回去。它还确认 V2 连接会带上正确的 beta 请求头,表示正在使用新版协议。
数据流:进去的是假服务器三段返回:预热完成、要求执行 echo websocket、最后回复 done;测试配置里打开 ResponsesWebsocketsV2 功能,并使用 Windows cmd shell。它提交用户请求后,读取同一连接上的三个请求:预热、第一轮、第二轮。它检查第一轮带 previous_response_id=warm-1,第二轮带 previous_response_id=resp-1,并且第二轮 input 里有 function_call_output,call_id 和服务器要求的命令调用一致;还检查握手请求头 openai-beta 是指定的 V2 值。出来的结果是证明 V2 的连续响应串联和工具结果回传都正确。
调用关系:它用 start_websocket_server 安排服务器事件,用 test_codex 创建并配置测试客户端,还通过配置开关启用 Feature::ResponsesWebsocketsV2。submit_turn_with_policy 触发整条对话链,assert_eq! 和 assert! 负责确认请求内容、previous_response_id 和握手头都符合 V2 规则。
调用图:调用 2 个内部函数(start_websocket_server, test_codex);外部调用 4 个(assert!, assert_eq!, skip_if_no_network!, vec!)。
websocket_v2_first_turn_uses_updated_fast_tier_after_startup_prewarm259–311 ↗
async fn websocket_v2_first_turn_uses_updated_fast_tier_after_startup_prewarm() -> Result<()>
作用:这个测试确认新版 WebSocket V2 在启动预热后,第一轮正式请求可以使用用户后来指定的 fast 服务档位。也就是说,预热时没选快档,不会挡住真正请求改用快档。
数据流:进去的是开启 V2 的测试配置,以及一个先响应预热、再响应 hello 的假服务器。测试先等服务器收到预热请求,确认它没有 service_tier;然后提交 hello,并指定 ServiceTier::Fast。最后读取第一轮正式请求,确认 service_tier 变成 priority,previous_response_id 没有被带上,input 也不是空的。出来的结果是证明正式请求会使用最新传入的服务档位,而不是沿用预热时的空档位。
调用关系:它用 start_websocket_server 启动假服务器,用 test_codex 创建客户端并开启 ResponsesWebsocketsV2。这里先调用服务器的 wait_for_request 等到预热请求出现,再用 submit_turn_with_service_tier 触发正式请求,最后通过 assert_eq! 和 assert! 检查字段。
调用图:调用 2 个内部函数(start_websocket_server, test_codex);外部调用 4 个(assert!, assert_eq!, skip_if_no_network!, vec!)。
websocket_v2_first_turn_drops_fast_tier_after_startup_prewarm314–367 ↗
async fn websocket_v2_first_turn_drops_fast_tier_after_startup_prewarm() -> Result<()>
作用:这个测试确认新版 WebSocket V2 不会把预热时的 fast 服务档位错误地带到后面的正式请求里。换句话说,如果正式请求明确不使用服务档位,就应该把它去掉。
数据流:进去的是开启 V2 的配置,并且全局配置里先把 service_tier 设成 fast;假服务器先处理预热,再处理 hello。测试先确认预热请求带了 service_tier=priority;随后提交 hello 时传入 None,表示这轮不要服务档位。最后检查正式请求里没有 service_tier,也没有 previous_response_id,并且 input 不为空。出来的结果是证明预热请求的档位不会污染后续正式请求。
调用关系:它用 start_websocket_server 搭服务器,用 test_codex 创建客户端并修改配置。wait_for_request 用来先抓到预热请求,submit_turn_with_service_tier 再发正式请求。assert_eq! 和 assert! 用来确认服务档位被正确删除。
调用图:调用 2 个内部函数(start_websocket_server, test_codex);外部调用 4 个(assert!, assert_eq!, skip_if_no_network!, vec!)。
websocket_v2_next_turn_uses_updated_service_tier370–444 ↗
async fn websocket_v2_next_turn_uses_updated_service_tier() -> Result<()>
作用:这个测试确认新版 WebSocket V2 的每一轮对话都能单独更新服务档位。第一轮可以用 fast,第二轮可以不用,系统不能把上一轮的选择偷偷沿用下去。
数据流:进去的是开启 V2 的测试 Codex,以及一个会依次响应预热、第一轮、第二轮的假服务器。测试先确认预热请求没有 service_tier;然后提交第一轮并指定 fast,再提交第二轮并指定 None。它读取连接上的三个请求,确认第一轮有 service_tier=priority,第二轮没有 service_tier,两轮都没有 previous_response_id,且 input 都不为空。出来的结果是证明服务档位是按每轮请求现算的,而不是全局粘住的。
调用关系:它通过 start_websocket_server 设置三段服务器响应,通过 test_codex 开启 V2 功能。流程由两次 submit_turn_with_service_tier 推动,测试最后用 assert_eq! 和 assert! 检查服务器实际收到的请求,确保第一轮和第二轮互不串档。
调用图:调用 2 个内部函数(start_websocket_server, test_codex);外部调用 4 个(assert!, assert_eq!, skip_if_no_network!, vec!)。
core/tests/suite/client_websockets.rs源码 ↗
这个文件像一套“模拟客服热线”的验收测试:先搭一个假的 WebSocket 服务器,让 Codex 客户端以为自己在连真实模型服务,然后看客户端说了什么、带了哪些请求头、收到服务器事件后怎么反应。WebSocket 可以理解成长期开着的一根电话线,不是每问一次就重新拨号,所以这里重点检查同一根线能不能被多轮对话、预连接、预热请求继续使用。文件还验证追踪上下文(用来把一次请求在日志里串起来的编号)、运行指标、限流信息、错误事件、Responses Lite 标记、previous_response_id 增量续聊等行为。后半部分是一批小工具,用来造提示词、造测试模型提供方、启动测试客户端,并把流式结果一直读到完成。
assert_request_trace_matches75–99 ↗
fn assert_request_trace_matches(body: &serde_json::Value, expected_trace: &W3cTraceContext)
作用:检查一次 WebSocket 请求里带的追踪信息是不是预期的那一份。追踪信息就像快递单号,用来把日志里的同一次请求串起来。
数据流:输入是服务器收到的 JSON 请求体和期望的 W3C 追踪上下文 → 它从 client_metadata 里取出 traceparent 和 tracestate,比对是否一致,并确认旧的顶层 trace 字段没有被发送 → 没有返回值,检查失败会让测试直接失败。
调用关系:它被两个追踪相关测试调用,用来确认预连接或连接复用不会把上一轮请求的追踪编号错误地带到下一轮。
调用图:被 2 处调用(responses_websocket_preconnect_does_not_replace_turn_trace_payload, responses_websocket_reuses_connection_with_per_turn_trace_payloads);外部调用 2 个(assert!, assert_eq!)。
responses_metadata112–127 ↗
fn responses_metadata(
harness: &WebsocketTestHarness,
turn_id: Option<&str>,
request_kind: TestCodexResponsesRequestKind,
) -> CodexResponsesMetadata
作用:生成一份测试用的 Responses 请求元数据。元数据就是随请求附带的身份和上下文标签,比如安装 ID、会话 ID、线程 ID、请求类型。
数据流:输入是测试环境、可选 turn_id 和请求种类 → 它把固定测试安装 ID、session/thread/window 信息交给测试辅助函数组装 → 输出 CodexResponsesMetadata,供后续请求放进 client_metadata。
调用关系:它是 turn_metadata、prewarm_metadata、websocket_connection_metadata 的共同底座,避免每个测试重复拼同一套字段。
调用图:被 3 处调用(prewarm_metadata, turn_metadata, websocket_connection_metadata);外部调用 1 个(responses_metadata)。
turn_metadata129–131 ↗
fn turn_metadata(harness: &WebsocketTestHarness, turn_id: Option<&str>) -> CodexResponsesMetadata
作用:生成普通一轮对话请求的元数据。测试用它表示“这是真正向模型要回答的一轮”。
数据流:输入是测试环境和可选 turn_id → 它调用 responses_metadata,并把请求类型固定成 Turn → 输出一份普通对话请求元数据。
调用关系:许多测试在真正调用 stream 前使用它,尤其是检查 turn_id、限流事件、错误转发和增量请求时。
调用图:调用 1 个内部函数(responses_metadata);被 12 处调用(responses_websocket_emits_rate_limit_events, responses_websocket_emits_reasoning_included_event, responses_websocket_forwards_turn_metadata_on_initial_and_incremental_create, responses_websocket_preconnect_is_reused_even_with_header_changes, responses_websocket_request_prewarm_is_reused_even_with_header_changes, responses_websocket_request_prewarm_traces_logical_request, responses_websocket_request_prewarm_uses_caller_supplied_metadata, responses_websocket_sends_canonical_turn_metadata, responses_websocket_v2_after_error_uses_full_create_without_previous_response_id, responses_websocket_v2_surfaces_terminal_error_without_close_handshake (+2 more))。
prewarm_metadata133–138 ↗
fn prewarm_metadata(
harness: &WebsocketTestHarness,
turn_id: Option<&str>,
) -> CodexResponsesMetadata
作用:生成预热请求的元数据。预热可以理解成先把线路和上下文热起来,稍后正式请求更快。
数据流:输入是测试环境和可选 turn_id → 它调用 responses_metadata,并把请求类型固定成 Prewarm → 输出预热请求专用元数据。
调用关系:预热相关测试用它来确认客户端发出的 warmup 请求被标成 prewarm,并且后续正式请求能复用连接和响应 ID。
调用图:调用 1 个内部函数(responses_metadata);被 4 处调用(responses_websocket_prewarm_uses_v2_when_provider_supports_websockets, responses_websocket_request_prewarm_is_reused_even_with_header_changes, responses_websocket_request_prewarm_reuses_connection, responses_websocket_request_prewarm_traces_logical_request)。
websocket_connection_metadata140–146 ↗
fn websocket_connection_metadata(harness: &WebsocketTestHarness) -> CodexResponsesMetadata
作用:生成只用于建立 WebSocket 连接的元数据。它不是一轮对话,而是给“先接上线”这个动作贴标签。
数据流:输入是测试环境 → 它调用 responses_metadata,turn_id 为空,请求类型设为 WebsocketConnection → 输出连接预建立时使用的元数据。
调用关系:预连接测试会先用它调用 preconnect_websocket,再检查正式请求是否仍然使用自己的 turn 元数据和追踪信息。
调用图:调用 1 个内部函数(responses_metadata);被 4 处调用(responses_websocket_preconnect_does_not_replace_turn_trace_payload, responses_websocket_preconnect_is_reused_even_with_header_changes, responses_websocket_preconnect_reuses_connection, responses_websocket_preconnect_runs_when_only_v2_feature_enabled)。
responses_websocket_streams_request149–206 ↗
async fn responses_websocket_streams_request()
作用:测试最基础的 WebSocket 流式请求是否能发出去,并且请求体和握手请求头都正确。握手就是 WebSocket 连接一开始互相确认身份和协议的步骤。
数据流:它启动假服务器,创建客户端和一条“hello”提示词 → 客户端流式请求直到完成 → 它读取服务器记录的请求体和握手头,检查模型名、stream=true、输入数量、beta 头、session/thread ID、user-agent 和安装 ID 等字段。
调用关系:这是本文件的基础场景,使用 websocket_harness 建客户端,用 prompt_with_input 造输入,用 stream_until_complete 驱动请求结束。
调用图:调用 4 个内部函数(start_websocket_server, prompt_with_input, stream_until_complete, websocket_harness);外部调用 4 个(assert!, assert_eq!, skip_if_no_network!, vec!)。
responses_websocket_streams_without_feature_flag_when_provider_supports_websockets209–228 ↗
async fn responses_websocket_streams_without_feature_flag_when_provider_supports_websockets()
作用:确认只要模型提供方声明支持 WebSocket,即使没有额外功能开关,客户端也会走 WebSocket。
数据流:它启动假服务器,并创建未启用运行指标的测试客户端 → 发送一条提示词并读完 → 检查服务器只看到一次握手和一次请求。
调用关系:它验证提供方配置的 supports_websockets 会触发 WebSocket 路径,复用 websocket_harness_with_options 和 stream_until_complete。
调用图:调用 4 个内部函数(start_websocket_server, prompt_with_input, stream_until_complete, websocket_harness_with_options);外部调用 3 个(assert_eq!, skip_if_no_network!, vec!)。
responses_websocket_reuses_connection_with_per_turn_trace_payloads231–300 ↗
async fn responses_websocket_reuses_connection_with_per_turn_trace_payloads()
作用:测试多轮请求复用同一条 WebSocket 连接时,每一轮仍然带自己的追踪编号。也就是说电话线可以复用,但每个工单号不能混。
数据流:它启动一个能处理两次请求的假服务器,分别在两个 tracing span 里发两轮提示词 → 记录每轮当前追踪上下文 → 从服务器收到的两个请求里比对各自 trace 字段,并确认两个 traceparent 不一样。
调用关系:它使用 install_test_tracing 建测试追踪环境,stream_until_complete 发请求,assert_request_trace_matches 做字段核对。
调用图:调用 6 个内部函数(start_websocket_server, install_test_tracing, assert_request_trace_matches, prompt_with_input, stream_until_complete, websocket_harness);外部调用 6 个(assert_eq!, assert_ne!, current_span_w3c_trace_context, skip_if_no_network!, info_span!, vec!)。
responses_websocket_preconnect_does_not_replace_turn_trace_payload303–339 ↗
async fn responses_websocket_preconnect_does_not_replace_turn_trace_payload()
作用:确认先预连接 WebSocket 不会污染后面正式请求的追踪信息。预连接只是先拨通电话,不该决定后面工单的编号。
数据流:它先用连接元数据调用 preconnect_websocket → 再在新的追踪 span 内发送正式提示词 → 从服务器请求体里确认正式请求带的是当前 turn 的追踪上下文,而不是预连接时的上下文。
调用关系:它串起 websocket_connection_metadata、preconnect_websocket、stream_until_complete,并用 assert_request_trace_matches 做最终确认。
调用图:调用 7 个内部函数(start_websocket_server, install_test_tracing, assert_request_trace_matches, prompt_with_input, stream_until_complete, websocket_connection_metadata, websocket_harness);外部调用 5 个(assert_eq!, current_span_w3c_trace_context, skip_if_no_network!, info_span!, vec!)。
responses_websocket_preconnect_reuses_connection342–374 ↗
async fn responses_websocket_preconnect_reuses_connection()
作用:测试预连接后,正式请求会复用已经打开的 WebSocket,而不是重新连一次。
数据流:它启动服务器,客户端先 preconnect_websocket → 然后发送 hello → 检查服务器只有一次握手、一次请求,并确认 user-agent 和窗口 ID 在握手里。
调用关系:它验证预连接功能和实际 stream 流程之间能顺利接上,依赖 websocket_connection_metadata 和 stream_until_complete。
调用图:调用 5 个内部函数(start_websocket_server, prompt_with_input, stream_until_complete, websocket_connection_metadata, websocket_harness);外部调用 3 个(assert_eq!, skip_if_no_network!, vec!)。
responses_websocket_request_prewarm_reuses_connection377–438 ↗
async fn responses_websocket_request_prewarm_reuses_connection()
作用:测试预热请求和后续正式请求能用同一条 WebSocket 连接,并且正式请求能接着预热返回的 response_id 继续。
数据流:它让假服务器先返回 warm-1,再返回 resp-1 → 客户端先 prewarm_websocket,再正式 stream → 检查只有一次握手、两次请求;第一请求 generate=false 且没有工具,第二请求带 previous_response_id=warm-1 且 input 为空。
调用关系:这是预热复用的核心测试,使用 prewarm_metadata 标记第一请求,再用 stream_until_complete 验证后续续接。
调用图:调用 5 个内部函数(start_websocket_server, prewarm_metadata, prompt_with_input, stream_until_complete, websocket_harness_with_options);外部调用 4 个(assert_eq!, from_str, skip_if_no_network!, vec!)。
responses_websocket_request_prewarm_uses_caller_supplied_metadata441–481 ↗
async fn responses_websocket_request_prewarm_uses_caller_supplied_metadata()
作用:确认预热请求会尊重调用方传入的元数据,而不是强行改成 prewarm。这样上层如果明确传了 turn 元数据,客户端不会偷偷替换。
数据流:它用 turn_metadata 调用 prewarm_websocket → 从服务器收到的 warmup 请求里解析 x-codex-turn-metadata → 检查 request_kind 是 turn。
调用关系:它和默认预热元数据测试形成对照,证明 prewarm_websocket 使用的是传进来的 metadata。
调用图:调用 4 个内部函数(start_websocket_server, prompt_with_input, turn_metadata, websocket_harness_with_options);外部调用 4 个(assert_eq!, from_str, skip_if_no_network!, vec!)。
responses_websocket_request_prewarm_traces_logical_request484–589 ↗
async fn responses_websocket_request_prewarm_traces_logical_request()
作用:测试预热之后的正式逻辑请求仍然会被正确写进推理追踪文件。推理追踪是把模型调用和对话内容记录下来,方便回放和排查。
数据流:它先发预热请求,再创建临时 trace writer,记录线程和 turn 开始 → 用启用的 InferenceTraceContext 发正式请求 → 读取回放文件,确认记录的请求内容包含用户文本 hello,而不是因为续接 warm-1 就丢掉输入。
调用关系:它连接 WebSocket 预热流程和 rollout trace 系统,使用 TraceWriter、InferenceTraceContext::enabled、replay_bundle 检查跨模块行为。
调用图:调用 7 个内部函数(start_websocket_server, prewarm_metadata, prompt_with_input, turn_metadata, websocket_harness_with_options, enabled, create);外部调用 7 个(new, new, assert_eq!, replay_bundle, matches!, skip_if_no_network!, vec!)。
responses_websocket_reuses_connection_after_session_drop592–617 ↗
async fn responses_websocket_reuses_connection_after_session_drop()
作用:确认一个 ModelClientSession 被丢弃后,新建 session 仍能复用底层 WebSocket 连接。
数据流:它用第一个 session 发 hello 并结束,然后让这个 session 离开作用域被销毁 → 新建另一个 session 发 again → 检查服务器只有一次握手、同一连接上有两次请求。
调用关系:它验证连接缓存不绑死在单个 session 对象上,而是由更外层的客户端复用。
调用图:调用 4 个内部函数(start_websocket_server, prompt_with_input, stream_until_complete, websocket_harness);外部调用 3 个(assert_eq!, skip_if_no_network!, vec!)。
responses_websocket_sends_responses_lite_metadata_per_request620–696 ↗
async fn responses_websocket_sends_responses_lite_metadata_per_request()
作用:测试 Responses Lite 标记是按每次请求单独发送的,不会从一次 lite 请求泄漏到下一次普通请求。
数据流:它准备普通模型、lite 模型、再普通模型,连续发三次请求 → 从服务器记录的三个请求取 client_metadata、reasoning.context 和 parallel_tool_calls → 检查只有中间那次带 responses_lite=true 和 all_turns。
调用关系:它使用 stream_until_complete_with_model_info 指定每次模型信息,专门防止长连接复用时请求级配置互相污染。
调用图:调用 4 个内部函数(start_websocket_server, prompt_with_input, stream_until_complete_with_model_info, websocket_harness);外部调用 3 个(assert_eq!, skip_if_no_network!, vec!)。
responses_websocket_preconnect_is_reused_even_with_header_changes699–741 ↗
async fn responses_websocket_preconnect_is_reused_even_with_header_changes()
作用:确认预连接后,即使正式请求的头部或元数据有变化,也不会因此重新建立 WebSocket。
数据流:它先用连接元数据预连接 → 再用 turn_metadata 发正式 stream → 读到 Completed 后检查服务器只有一次握手和一次请求。
调用关系:它验证连接复用规则不会因为每轮请求的元数据不同而过度保守地重连。
调用图:调用 6 个内部函数(start_websocket_server, prompt_with_input, turn_metadata, websocket_connection_metadata, websocket_harness, disabled);外部调用 4 个(assert_eq!, matches!, skip_if_no_network!, vec!)。
responses_websocket_request_prewarm_is_reused_even_with_header_changes744–809 ↗
async fn responses_websocket_request_prewarm_is_reused_even_with_header_changes()
作用:确认预热请求之后,即使正式请求头部信息变化,也继续复用同一条连接和预热结果。
数据流:它先发 prewarm 请求拿到 warm-1 → 再用 turn_metadata 发正式请求 → 检查一次握手、两次请求,且第二次带 previous_response_id=warm-1、input 为空。
调用关系:它覆盖“预热 + 请求头变化 + 增量续接”组合场景,防止连接池因为头变化丢掉预热收益。
调用图:调用 6 个内部函数(start_websocket_server, prewarm_metadata, prompt_with_input, turn_metadata, websocket_harness_with_options, disabled);外部调用 4 个(assert_eq!, matches!, skip_if_no_network!, vec!)。
responses_websocket_prewarm_uses_v2_when_provider_supports_websockets812–867 ↗
async fn responses_websocket_prewarm_uses_v2_when_provider_supports_websockets()
作用:测试提供方支持 WebSocket 时,预热会使用新版 WebSocket V2 协议。V2 通过 OpenAI-Beta 头声明。
数据流:它启动服务器并发起 prewarm_websocket → 检查一次握手、一次 WebSocket 请求,握手 OpenAI-Beta 包含 responses_websockets=2026-02-06 → 再正式 stream,确认没有多发请求,预热请求本身包含原始输入。
调用关系:它用 prewarm_metadata 和 websocket_harness_with_options 验证预热路径会进入 V2,而不是旧的预热方式。
调用图:调用 5 个内部函数(start_websocket_server, prewarm_metadata, prompt_with_input, stream_until_complete, websocket_harness_with_options);外部调用 4 个(assert!, assert_eq!, skip_if_no_network!, vec!)。
responses_websocket_preconnect_runs_when_only_v2_feature_enabled870–911 ↗
async fn responses_websocket_preconnect_runs_when_only_v2_feature_enabled()
作用:确认只启用 V2 WebSocket 能力时,预连接仍然会执行,但不会提前发送一轮对话请求。
数据流:它先 preconnect_websocket → 检查服务器只有握手、没有请求,并且握手里没有 turn metadata → 后续发 hello 后,仍只用同一次握手,且 V2 beta 头存在。
调用关系:它区分“预连接”与“预热”:前者只开连接,后者才会发送 response.create。
调用图:调用 5 个内部函数(start_websocket_server, prompt_with_input, stream_until_complete, websocket_connection_metadata, websocket_harness_with_options);外部调用 4 个(assert!, assert_eq!, skip_if_no_network!, vec!)。
responses_websocket_v2_requests_use_v2_when_provider_supports_websockets914–960 ↗
async fn responses_websocket_v2_requests_use_v2_when_provider_supports_websockets()
作用:测试 V2 WebSocket 在第二轮对话中会使用 previous_response_id 发送增量内容,而不是把历史全量重发。
数据流:它第一轮让服务器返回 resp-1 和助手消息 msg-1 → 第二轮提示词包含第一轮历史加新消息 → 检查第二次请求带 previous_response_id=resp-1,input 只包含新增的部分,并且握手有 V2 beta 头。
调用关系:它验证 V2 协议的核心省流量能力:借助上次响应 ID 只提交新增对话。
调用图:调用 4 个内部函数(start_websocket_server, prompt_with_input, stream_until_complete, websocket_harness_with_options);外部调用 4 个(assert!, assert_eq!, skip_if_no_network!, vec!)。
responses_websocket_v2_incremental_requests_are_reused_across_turns963–1004 ↗
async fn responses_websocket_v2_incremental_requests_are_reused_across_turns()
作用:确认 V2 的增量请求能力在不同 session 之间也能继续使用。
数据流:它用一个 session 发第一轮并结束,再新建 session 发带历史的第二轮 → 检查服务器仍只有一次握手,两次请求在同一连接上,第二次用 previous_response_id=resp-1 且只发新增输入。
调用关系:它和 session drop 测试类似,但重点放在 V2 增量状态是否跨 session 保留。
调用图:调用 4 个内部函数(start_websocket_server, prompt_with_input, stream_until_complete, websocket_harness_with_options);外部调用 3 个(assert_eq!, skip_if_no_network!, vec!)。
responses_websocket_v2_wins_when_both_features_enabled1007–1053 ↗
async fn responses_websocket_v2_wins_when_both_features_enabled()
作用:测试当旧功能和 V2 功能都可能可用时,客户端优先走 V2。这样避免两套协议选择不稳定。
数据流:它连续发两轮有公共前缀的对话 → 检查第二次请求使用 previous_response_id,只发送新增输入,并确认握手 beta 头包含 V2 标记。
调用关系:它是协议选择优先级测试,确保客户端在多功能开关环境下行为确定。
调用图:调用 4 个内部函数(start_websocket_server, prompt_with_input, stream_until_complete, websocket_harness_with_options);外部调用 4 个(assert!, assert_eq!, skip_if_no_network!, vec!)。
responses_websocket_emits_websocket_telemetry_events1057–1085 ↗
async fn responses_websocket_emits_websocket_telemetry_events()
作用:测试 WebSocket 请求会记录运行指标事件。运行指标就是计数和耗时等数据,用来观察系统是否健康。
数据流:它重置 session_telemetry 的指标 → 发一次 WebSocket 请求并稍等异步记录完成 → 读取摘要,确认普通 API 调用和普通流事件为 0,WebSocket 调用为 1,WebSocket 事件为 2。
调用关系:它把客户端请求流程和 telemetry 统计系统连起来验证,使用 traced_test 捕获测试日志。
调用图:调用 4 个内部函数(start_websocket_server, prompt_with_input, stream_until_complete, websocket_harness);外部调用 5 个(from_millis, assert_eq!, skip_if_no_network!, sleep, vec!)。
responses_websocket_includes_timing_metrics_header_when_runtime_metrics_enabled1088–1135 ↗
async fn responses_websocket_includes_timing_metrics_header_when_runtime_metrics_enabled()
作用:确认启用运行指标时,WebSocket 握手会要求服务器返回详细耗时指标,并且客户端会吸收这些指标。
数据流:服务器返回一个 websocket_timing 事件 → 客户端带 RuntimeMetrics 功能发请求 → 测试检查握手头 X-ResponsesAPI-Include-Timing-Metrics=true,并确认 telemetry 摘要里的各项耗时等于服务器给的数值。
调用关系:它通过 websocket_harness_with_runtime_metrics 打开功能,验证请求头和事件解析两件事都生效。
调用图:调用 4 个内部函数(start_websocket_server, prompt_with_input, stream_until_complete, websocket_harness_with_runtime_metrics);外部调用 5 个(from_millis, assert_eq!, skip_if_no_network!, sleep, vec!)。
responses_websocket_omits_timing_metrics_header_when_runtime_metrics_disabled1138–1161 ↗
async fn responses_websocket_omits_timing_metrics_header_when_runtime_metrics_disabled()
作用:确认没有启用运行指标时,客户端不会向服务器要求详细耗时指标。
数据流:它创建运行指标关闭的客户端 → 发一次请求 → 检查握手里没有 X-ResponsesAPI-Include-Timing-Metrics 头。
调用关系:它和启用指标的测试成对出现,保证功能开关真的控制请求头。
调用图:调用 4 个内部函数(start_websocket_server, prompt_with_input, stream_until_complete, websocket_harness_with_runtime_metrics);外部调用 3 个(assert_eq!, skip_if_no_network!, vec!)。
responses_websocket_emits_reasoning_included_event1164–1207 ↗
async fn responses_websocket_emits_reasoning_included_event()
作用:测试服务器通过响应头告诉客户端“这次包含 reasoning”时,客户端会把这个消息变成一个流事件。
数据流:假服务器在握手响应头里放 X-Reasoning-Included=true → 客户端 stream 并逐个读取事件 → 遇到 ServerReasoningIncluded(true) 就记录,最后确认确实看到。
调用关系:它验证 WebSocket 响应头会进入 ResponseEvent 流,而不只是被底层连接代码吞掉。
调用图:调用 5 个内部函数(start_websocket_server_with_headers, prompt_with_input, turn_metadata, websocket_harness, disabled);外部调用 3 个(assert!, skip_if_no_network!, vec!)。
responses_websocket_emits_rate_limit_events1210–1303 ↗
async fn responses_websocket_emits_rate_limit_events()
作用:测试 WebSocket 流里收到限流信息时,客户端会转成易用的 RateLimits 事件,同时也能转发模型列表 etag 和 reasoning 标记。
数据流:服务器先发 codex.rate_limits 事件,并在响应头里带 X-Models-Etag 和 X-Reasoning-Included → 客户端读流 → 测试检查限流百分比、窗口、重置时间、套餐类型、余额、etag 和 reasoning 标记都被解析出来。
调用关系:它覆盖 WebSocket 流式事件解析中一类非文本输出:账户限额和服务端状态信息。
调用图:调用 5 个内部函数(start_websocket_server_with_headers, prompt_with_input, turn_metadata, websocket_harness, disabled);外部调用 5 个(assert!, assert_eq!, json!, skip_if_no_network!, vec!)。
responses_websocket_usage_limit_error_emits_rate_limit_event1306–1403 ↗
async fn responses_websocket_usage_limit_error_emits_rate_limit_event()
作用:测试 WebSocket 返回“用量达到上限”的错误时,外层 Codex 会先发出限流计数事件,再发错误事件。
数据流:假服务器先完成预热,再对正式请求返回 429 usage_limit_reached 错误和限流头 → 测试通过完整 Codex 提交流程发送用户输入 → 等待 TokenCount 事件并核对限流 JSON,再等待 Error 事件并确认消息包含 usage limit。
调用关系:它不只测底层 ModelClient,还通过 test_codex 走完整业务通道,确认 UI/调用方能收到限流提示。
调用图:调用 2 个内部函数(start_websocket_server, test_codex);外部调用 9 个(default, assert!, wait_for_event, json!, assert_eq!, to_value, skip_if_no_network!, unreachable!, vec!)。
responses_websocket_invalid_request_error_with_status_is_forwarded1406–1464 ↗
async fn responses_websocket_invalid_request_error_with_status_is_forwarded()
作用:测试 WebSocket 返回带 HTTP 状态的无效请求错误时,错误消息会被原样传到上层。
数据流:服务器在正式请求时返回 400 invalid_request_error,消息说模型不支持图片输入 → Codex 提交用户输入 → 测试等待 Error 事件,并确认错误文本包含 does not support image inputs。
调用关系:它保证模型服务给出的可读错误不会在 WebSocket 层被改坏或丢失。
调用图:调用 2 个内部函数(start_websocket_server, test_codex);外部调用 7 个(default, assert!, wait_for_event, json!, skip_if_no_network!, unreachable!, vec!)。
responses_websocket_connection_limit_error_reconnects_and_completes1467–1514 ↗
async fn responses_websocket_connection_limit_error_reconnects_and_completes()
作用:测试 WebSocket 连接达到服务端时长限制时,客户端会重新连接并完成请求,而不是直接失败。
数据流:第一条连接返回 websocket_connection_limit_reached 错误 → 配置允许一次 stream 重试 → 第二条连接返回正常完成 → 测试确认提交成功、总请求数为 2,且两次握手都有 user-agent。
调用关系:它验证自动恢复逻辑:遇到特定连接限制错误时,客户端应丢掉旧连接并重拨。
调用图:调用 2 个内部函数(start_websocket_server, test_codex);外部调用 4 个(assert_eq!, json!, skip_if_no_network!, vec!)。
responses_websocket_uses_incremental_create_on_prefix1517–1559 ↗
async fn responses_websocket_uses_incremental_create_on_prefix()
作用:测试当第二轮提示词以前一轮完整对话为前缀时,客户端会发送增量 create 请求。
数据流:第一轮发送 hello,服务器返回 resp-1 和助手消息 → 第二轮输入包含 hello、助手消息和 second → 检查第二次请求带 previous_response_id=resp-1,只发送新增的 second 部分。
调用关系:它是 previous_response_id 增量发送的基础测试,证明客户端能识别“已有历史 + 新内容”的模式。
调用图:调用 4 个内部函数(start_websocket_server, prompt_with_input, stream_until_complete, websocket_harness);外部调用 3 个(assert_eq!, skip_if_no_network!, vec!)。
responses_websocket_forwards_turn_metadata_on_initial_and_incremental_create1562–1636 ↗
async fn responses_websocket_forwards_turn_metadata_on_initial_and_incremental_create()
作用:确认无论是第一轮完整 create,还是第二轮增量 create,都会转发对应的 turn 元数据。
数据流:它分别给两轮请求传 turn-123 和 turn-456 → 发完两轮后解析两个请求里的 x-codex-turn-metadata → 检查每个请求都带自己的 turn_id,并且 client_metadata 顶层 turn_id 与规范元数据一致。
调用关系:它验证元数据不是只在第一轮生效,增量续聊时也必须准确传给服务端。
调用图:调用 5 个内部函数(start_websocket_server, prompt_with_input, stream_until_complete_with_metadata, turn_metadata, websocket_harness);外部调用 4 个(assert_eq!, from_str, skip_if_no_network!, vec!)。
responses_websocket_sends_canonical_turn_metadata1639–1682 ↗
async fn responses_websocket_sends_canonical_turn_metadata()
作用:测试客户端发送的是规范格式的 turn metadata,并且顶层 turn_id 与其中的 turn_id 一致。
数据流:它用 turn_id=turn-123 发一次请求 → 从服务器请求体解析 x-codex-turn-metadata JSON → 检查内部 turn_id 和 client_metadata.turn_id 都是 turn-123。
调用关系:它专门盯住元数据格式,给其他依赖 turn_id 的统计、追踪和后端逻辑兜底。
调用图:调用 5 个内部函数(start_websocket_server, prompt_with_input, stream_until_complete_with_metadata, turn_metadata, websocket_harness);外部调用 4 个(assert_eq!, from_str, skip_if_no_network!, vec!)。
responses_websocket_uses_previous_response_id_when_prefix_after_completed1685–1722 ↗
async fn responses_websocket_uses_previous_response_id_when_prefix_after_completed()
作用:确认只有前一轮已经完成后,下一轮匹配前缀时会用 previous_response_id 续接。
数据流:它完成第一轮并得到 resp-1 → 发送包含第一轮历史的新提示词 → 检查第二次请求带 previous_response_id=resp-1,并只发送新增输入。
调用关系:它强调增量续接依赖已完成响应,避免客户端在状态不完整时错误引用 response_id。
调用图:调用 4 个内部函数(start_websocket_server, prompt_with_input, stream_until_complete, websocket_harness);外部调用 3 个(assert_eq!, skip_if_no_network!, vec!)。
responses_websocket_creates_on_non_prefix1725–1755 ↗
async fn responses_websocket_creates_on_non_prefix()
作用:测试如果第二轮提示词不是第一轮历史的延续,就必须发完整 create,而不能错误地增量续接。
数据流:第一轮输入 hello,第二轮输入 different → 检查第二次请求没有依赖 previous_response_id,而是完整带模型名、stream=true 和第二轮全部输入。
调用关系:它防止错误复用上下文:只有真的共享前缀时才可以省略历史。
调用图:调用 4 个内部函数(start_websocket_server, prompt_with_input, stream_until_complete, websocket_harness);外部调用 3 个(assert_eq!, skip_if_no_network!, vec!)。
responses_websocket_creates_when_non_input_request_fields_change1758–1791 ↗
async fn responses_websocket_creates_when_non_input_request_fields_change()
作用:测试即使输入看起来有前缀,只要非输入字段变了,比如基础指令变了,也不能增量续接。
数据流:第一轮用 base instructions one,第二轮用 base instructions two,并带 hello+second → 检查第二次请求没有 previous_response_id,发送完整输入。
调用关系:它说明增量复用不只比较聊天内容,还要确认影响模型行为的其他请求字段没有变化。
调用图:调用 4 个内部函数(start_websocket_server, prompt_with_input_and_instructions, stream_until_complete, websocket_harness);外部调用 3 个(assert_eq!, skip_if_no_network!, vec!)。
responses_websocket_v2_creates_with_previous_response_id_on_prefix1794–1833 ↗
async fn responses_websocket_v2_creates_with_previous_response_id_on_prefix()
作用:测试 V2 模式下,当前后两轮输入有公共前缀时,会用 previous_response_id 创建后续请求。
数据流:它在 V2 测试环境中发两轮对话 → 第一轮得到 resp-1 → 第二轮检查带 previous_response_id=resp-1,并且 input 只包含新增项。
调用关系:它是 V2 版本的增量前缀测试,使用 websocket_harness_with_v2 建立环境。
调用图:调用 4 个内部函数(start_websocket_server, prompt_with_input, stream_until_complete, websocket_harness_with_v2);外部调用 3 个(assert_eq!, skip_if_no_network!, vec!)。
responses_websocket_v2_creates_without_previous_response_id_when_non_input_fields_change1836–1870 ↗
async fn responses_websocket_v2_creates_without_previous_response_id_when_non_input_fields_change()
作用:测试 V2 模式下,如果基础指令等非输入字段改变,也必须重新完整 create。
数据流:它用两套不同 base instructions 发两轮 → 第二轮即使包含第一轮输入,也检查没有 previous_response_id,并发送完整输入。
调用关系:它与非 V2 的同名逻辑相呼应,保证 V2 优化不会忽略请求配置变化。
调用图:调用 4 个内部函数(start_websocket_server, prompt_with_input_and_instructions, stream_until_complete, websocket_harness_with_v2);外部调用 3 个(assert_eq!, skip_if_no_network!, vec!)。
responses_websocket_v2_after_error_uses_full_create_without_previous_response_id1873–1962 ↗
async fn responses_websocket_v2_after_error_uses_full_create_without_previous_response_id()
作用:测试 V2 流程中某次增量请求失败后,下一次请求会重新完整发送,不再引用可能失效的 previous_response_id。
数据流:第一轮成功得到 resp-1 → 第二轮增量请求收到 response.failed 并产生错误 → 第三轮发送更长输入 → 检查客户端重新握手,第三次请求没有 previous_response_id,且 input 是完整提示词。
调用关系:它验证错误恢复时会清空不安全的增量状态,避免拿失败连接上的上下文继续续接。
调用图:调用 6 个内部函数(start_websocket_server, prompt_with_input, stream_until_complete, turn_metadata, websocket_harness_with_v2, disabled);外部调用 4 个(assert!, assert_eq!, skip_if_no_network!, vec!)。
responses_websocket_v2_surfaces_terminal_error_without_close_handshake1965–2023 ↗
async fn responses_websocket_v2_surfaces_terminal_error_without_close_handshake()
作用:测试服务器发出终止错误但不关闭 WebSocket 时,客户端仍能把错误暴露出来,不会一直等。
数据流:服务器第一轮正常完成,第二轮发 response.failed 后保持连接不关闭 → 客户端读取第二轮流,并用 2 秒超时保护 → 如果读到错误事件则测试通过。
调用关系:它防止客户端把“错误事件”误当成普通消息后继续等待关闭握手,导致调用方卡住。
调用图:调用 6 个内部函数(start_websocket_server_with_headers, prompt_with_input, stream_until_complete, turn_metadata, websocket_harness_with_v2, disabled);外部调用 5 个(from_secs, assert!, skip_if_no_network!, timeout, vec!)。
responses_websocket_v2_sets_openai_beta_header2026–2052 ↗
async fn responses_websocket_v2_sets_openai_beta_header()
作用:确认 V2 WebSocket 请求的握手头包含正确的 OpenAI-Beta 标记。
数据流:它在 V2 harness 中发一次请求 → 读取服务器记录的握手 → 检查 OpenAI-Beta 逗号分隔值中包含 responses_websockets=2026-02-06。
调用关系:它是协议开关的最直接检查,确保服务端能识别客户端想用 V2。
调用图:调用 4 个内部函数(start_websocket_server, prompt_with_input, stream_until_complete, websocket_harness_with_v2);外部调用 3 个(assert!, skip_if_no_network!, vec!)。
message_item2054–2062 ↗
fn message_item(text: &str) -> ResponseItem
作用:造一个测试用的用户消息条目,内容是一段输入文本。
数据流:输入是文本字符串 → 它包装成 ResponseItem::Message,role 设为 user,content 设为 InputText → 输出可放进 Prompt.input 的消息项。
调用关系:几乎所有测试都用它快速构造用户输入,再交给 prompt_with_input 变成完整提示词。
调用图:外部调用 1 个(vec!)。
assistant_message_item2064–2072 ↗
fn assistant_message_item(id: &str, text: &str) -> ResponseItem
作用:造一个测试用的助手消息条目,用来模拟上一轮模型已经说过的话。
数据流:输入是消息 ID 和文本 → 它包装成 role=assistant、content=OutputText、带 id 的 ResponseItem → 输出可放进历史对话里的助手消息。
调用关系:增量请求测试用它构造“第一轮已经有助手回复”的历史,从而检查 previous_response_id 续接逻辑。
调用图:外部调用 1 个(vec!)。
prompt_with_input2074–2078 ↗
fn prompt_with_input(input: Vec<ResponseItem>) -> Prompt
作用:把一组消息条目装成一个 Prompt。Prompt 可以理解成发给模型的一整份请求草稿。
数据流:输入是一组 ResponseItem → 它创建默认 Prompt,并把 input 字段替换成这组条目 → 输出可直接传给客户端 stream 的 Prompt。
调用关系:它是多数测试构造提示词的入口,也被 prompt_with_input_and_instructions 复用。
调用图:调用 1 个内部函数(default);被 32 处调用(prompt_with_input_and_instructions, responses_websocket_creates_on_non_prefix, responses_websocket_emits_rate_limit_events, responses_websocket_emits_reasoning_included_event, responses_websocket_emits_websocket_telemetry_events, responses_websocket_forwards_turn_metadata_on_initial_and_incremental_create, responses_websocket_includes_timing_metrics_header_when_runtime_metrics_enabled, responses_websocket_omits_timing_metrics_header_when_runtime_metrics_disabled, responses_websocket_preconnect_does_not_replace_turn_trace_payload, responses_websocket_preconnect_is_reused_even_with_header_changes (+15 more))。
prompt_with_input_and_instructions2080–2086 ↗
fn prompt_with_input_and_instructions(input: Vec<ResponseItem>, instructions: &str) -> Prompt
作用:构造带基础指令的 Prompt,用来测试除了聊天输入外,系统指令变化会不会影响请求复用。
数据流:输入是一组消息条目和 instructions 文本 → 它先用 prompt_with_input 创建 Prompt,再设置 base_instructions.text → 输出带指令的 Prompt。
调用关系:两个“非输入字段变化”测试用它验证基础指令一变,就不能使用 previous_response_id 增量续接。
调用图:调用 1 个内部函数(prompt_with_input);被 2 处调用(responses_websocket_creates_when_non_input_request_fields_change, responses_websocket_v2_creates_without_previous_response_id_when_non_input_fields_change)。
websocket_provider2088–2090 ↗
fn websocket_provider(server: &WebSocketTestServer) -> ModelProviderInfo
作用:生成一个指向测试 WebSocket 服务器的模型提供方配置。
数据流:输入是假 WebSocket 服务器 → 它调用 websocket_provider_with_connect_timeout,并不设置连接超时 → 输出 ModelProviderInfo。
调用关系:websocket_harness_with_options 用它给测试客户端配置服务地址和 WebSocket 支持能力。
调用图:调用 1 个内部函数(websocket_provider_with_connect_timeout);被 1 处调用(websocket_harness_with_options)。
websocket_provider_with_connect_timeout2092–2115 ↗
fn websocket_provider_with_connect_timeout(
server: &WebSocketTestServer,
websocket_connect_timeout_ms: Option<u64>,
) -> ModelProviderInfo
作用:生成可自定义连接超时的测试模型提供方配置。
数据流:输入是假服务器和可选超时时间 → 它把服务器地址拼成 base_url,并设置 wire_api=Responses、重试次数、空闲超时、supports_websockets=true 等字段 → 输出 ModelProviderInfo。
调用关系:websocket_provider 是它的简化版;这份配置最终交给 ModelClient,让客户端连到假服务器而不是真实服务。
调用图:被 1 处调用(websocket_provider);外部调用 1 个(format!)。
websocket_harness2117–2119 ↗
async fn websocket_harness(server: &WebSocketTestServer) -> WebsocketTestHarness
作用:创建默认的 WebSocket 测试环境,运行指标默认关闭。
数据流:输入是假服务器 → 它调用 websocket_harness_with_runtime_metrics(false) → 输出 WebsocketTestHarness,里面有客户端、会话 ID、线程 ID、模型信息和 telemetry。
调用关系:大多数普通 WebSocket 测试用它快速拿到一套可运行的客户端环境。
调用图:调用 1 个内部函数(websocket_harness_with_runtime_metrics);被 16 处调用(responses_websocket_creates_on_non_prefix, responses_websocket_creates_when_non_input_request_fields_change, responses_websocket_emits_rate_limit_events, responses_websocket_emits_reasoning_included_event, responses_websocket_emits_websocket_telemetry_events, responses_websocket_forwards_turn_metadata_on_initial_and_incremental_create, responses_websocket_preconnect_does_not_replace_turn_trace_payload, responses_websocket_preconnect_is_reused_even_with_header_changes, responses_websocket_preconnect_reuses_connection, responses_websocket_reuses_connection_after_session_drop (+6 more))。
websocket_harness_with_runtime_metrics2121–2126 ↗
async fn websocket_harness_with_runtime_metrics(
server: &WebSocketTestServer,
runtime_metrics_enabled: bool,
) -> WebsocketTestHarness
作用:创建可选择是否启用运行指标的测试环境。
数据流:输入是假服务器和 runtime_metrics_enabled 布尔值 → 它直接转给 websocket_harness_with_options → 输出同样的 WebsocketTestHarness。
调用关系:指标相关测试用它打开或关闭 RuntimeMetrics,默认 harness 也通过它实现。
调用图:调用 1 个内部函数(websocket_harness_with_options);被 3 处调用(responses_websocket_includes_timing_metrics_header_when_runtime_metrics_enabled, responses_websocket_omits_timing_metrics_header_when_runtime_metrics_disabled, websocket_harness)。
websocket_harness_with_v22128–2133 ↗
async fn websocket_harness_with_v2(
server: &WebSocketTestServer,
runtime_metrics_enabled: bool,
) -> WebsocketTestHarness
作用:创建用于 V2 WebSocket 测试的环境。
数据流:输入是假服务器和运行指标开关 → 它调用 websocket_harness_with_options → 输出测试 harness。
调用关系:V2 专门测试都通过它表达测试意图;当前具体配置由统一的 options 构建函数完成。
调用图:调用 1 个内部函数(websocket_harness_with_options);被 5 处调用(responses_websocket_v2_after_error_uses_full_create_without_previous_response_id, responses_websocket_v2_creates_with_previous_response_id_on_prefix, responses_websocket_v2_creates_without_previous_response_id_when_non_input_fields_change, responses_websocket_v2_sets_openai_beta_header, responses_websocket_v2_surfaces_terminal_error_without_close_handshake)。
websocket_harness_with_options2135–2141 ↗
async fn websocket_harness_with_options(
server: &WebSocketTestServer,
runtime_metrics_enabled: bool,
) -> WebsocketTestHarness
作用:用默认测试提供方和指定运行指标开关创建 WebSocket 测试环境。
数据流:输入是假服务器和 runtime_metrics_enabled → 它先用 websocket_provider 生成提供方配置,再交给 websocket_harness_with_provider_options → 输出完整 harness。
调用关系:它是多种 harness 简化函数的汇合点,许多测试直接用它控制运行指标。
调用图:调用 2 个内部函数(websocket_harness_with_provider_options, websocket_provider);被 12 处调用(responses_websocket_preconnect_runs_when_only_v2_feature_enabled, responses_websocket_prewarm_uses_v2_when_provider_supports_websockets, responses_websocket_request_prewarm_is_reused_even_with_header_changes, responses_websocket_request_prewarm_reuses_connection, responses_websocket_request_prewarm_traces_logical_request, responses_websocket_request_prewarm_uses_caller_supplied_metadata, responses_websocket_streams_without_feature_flag_when_provider_supports_websockets, responses_websocket_v2_incremental_requests_are_reused_across_turns, responses_websocket_v2_requests_use_v2_when_provider_supports_websockets, responses_websocket_v2_wins_when_both_features_enabled (+2 more))。
websocket_harness_with_provider_options2143–2205 ↗
async fn websocket_harness_with_provider_options(
provider: ModelProviderInfo,
runtime_metrics_enabled: bool,
) -> WebsocketTestHarness
作用:真正搭建完整测试客户端环境,包括临时配置目录、模型信息、认证、指标客户端和 ModelClient。
数据流:输入是模型提供方配置和运行指标开关 → 它创建临时 codex_home,加载测试配置,必要时启用 RuntimeMetrics,构造模型信息、session/thread ID、API key 认证、内存指标导出器、SessionTelemetry 和 ModelClient → 输出 WebsocketTestHarness。
调用关系:所有 harness 辅助函数最终都到这里。它把测试所需的各个零件装成一台可发 WebSocket 请求的客户端机器。
调用图:调用 9 个内部函数(new, auth_manager_from_auth, construct_model_info_offline, from_api_key, new, new, in_memory, new, new);被 1 处调用(websocket_harness_with_options);外部调用 6 个(new, default, new, load_default_config_for_test, env!, clone)。
stream_until_complete2207–2219 ↗
async fn stream_until_complete(
client_session: &mut ModelClientSession,
harness: &WebsocketTestHarness,
prompt: &Prompt,
)
作用:把一次客户端流式请求读到完成为止,使用默认服务层级。
数据流:输入是可变 client_session、测试 harness 和 Prompt → 它调用 stream_until_complete_with_service_tier,service_tier 传 None → 没有返回值,内部会一直读流直到 Completed。
调用关系:绝大多数测试用它隐藏重复的“发请求并等完成”步骤,让测试专注检查服务器收到的内容。
调用图:调用 1 个内部函数(stream_until_complete_with_service_tier);被 24 处调用(responses_websocket_creates_on_non_prefix, responses_websocket_creates_when_non_input_request_fields_change, responses_websocket_emits_websocket_telemetry_events, responses_websocket_includes_timing_metrics_header_when_runtime_metrics_enabled, responses_websocket_omits_timing_metrics_header_when_runtime_metrics_disabled, responses_websocket_preconnect_does_not_replace_turn_trace_payload, responses_websocket_preconnect_reuses_connection, responses_websocket_preconnect_runs_when_only_v2_feature_enabled, responses_websocket_prewarm_uses_v2_when_provider_supports_websockets, responses_websocket_request_prewarm_reuses_connection (+14 more))。
stream_until_complete_with_model_info2221–2252 ↗
async fn stream_until_complete_with_model_info(
client_session: &mut ModelClientSession,
harness: &WebsocketTestHarness,
prompt: &Prompt,
model_info: &ModelInfo,
expected_response_
作用:用指定模型信息发流式请求,并确认完成事件里的 response_id 是预期值。
数据流:输入是 session、harness、Prompt、ModelInfo 和期望 response_id → 它生成 turn_metadata,调用 client_session.stream,然后逐个读事件 → 遇到 Completed 时比对 response_id;如果流结束还没完成就 panic。
调用关系:Responses Lite 元数据测试用它在同一连接上切换不同 ModelInfo,并确认每次收到对应服务器响应。
调用图:调用 3 个内部函数(stream, turn_metadata, disabled);被 1 处调用(responses_websocket_sends_responses_lite_metadata_per_request);外部调用 2 个(assert_eq!, panic!)。
stream_until_complete_with_service_tier2254–2269 ↗
async fn stream_until_complete_with_service_tier(
client_session: &mut ModelClientSession,
harness: &WebsocketTestHarness,
prompt: &Prompt,
service_tier: Option<ServiceTier>,
)
作用:用指定服务层级发请求并等完成。服务层级可以理解成请求给后端的优先级或套餐档位提示。
数据流:输入是 session、harness、Prompt 和可选 ServiceTier → 它生成普通 turn_metadata,再调用 stream_until_complete_with_metadata → 没有直接返回值。
调用关系:stream_until_complete 通过它实现默认路径;它在需要测试 service_tier 时提供扩展点。
调用图:调用 2 个内部函数(stream_until_complete_with_metadata, turn_metadata);被 1 处调用(stream_until_complete)。
stream_until_complete_with_metadata2271–2297 ↗
async fn stream_until_complete_with_metadata(
client_session: &mut ModelClientSession,
harness: &WebsocketTestHarness,
prompt: &Prompt,
service_tier: Option<ServiceTier>,
responses
作用:用调用方指定的 Responses 元数据发流式请求,并一直读到 Completed。
数据流:输入是 session、harness、Prompt、可选服务层级和 metadata → 它调用 client_session.stream,传入模型信息、telemetry、推理配置、服务层级字符串、metadata 和禁用的推理追踪 → 然后循环读取事件,看到 Completed 就停止。
调用关系:需要精确检查 turn metadata 的测试直接调用它;更简单的 stream_until_complete_with_service_tier 也把最终工作交给它。
调用图:调用 2 个内部函数(stream, disabled);被 3 处调用(responses_websocket_forwards_turn_metadata_on_initial_and_incremental_create, responses_websocket_sends_canonical_turn_metadata, stream_until_complete_with_service_tier);外部调用 1 个(matches!)。
core/tests/suite/realtime_conversation.rs源码 ↗
实时对话牵涉很多容易出错的东西:WebSocket(浏览器和服务器之间的长连接)、WebRTC(实时音视频连接)、API 密钥、启动提示词、语音设置、会话关闭、以及把实时语音中的问题“交给”普通模型回答。这个测试文件用假服务器假装 OpenAI 和实时服务,观察 Codex 发出的请求和收到的事件是不是符合预期。它会模拟正常来回通信,也会模拟网络失败、权限不对、连接半路关闭、重复启动等坏情况。文件里的小工具负责抓请求、等待某条 WebSocket 消息、准备历史线程数据。下面大量测试函数则像不同场景的演练,确保实时对话这台机器每个齿轮都按约定转。
RealtimeCallRequestCapture::new76–80 ↗
fn new() -> Self
作用:创建一个“请求记录本”,用来在测试里记下发往实时通话接口的 HTTP 请求。这样测试后面可以翻看 Codex 到底发了什么。
数据流:进去没有参数 → 它准备一个可共享的空列表,并用互斥锁(一把锁,防止两个任务同时改同一份数据)保护起来 → 出来一个 RealtimeCallRequestCapture,之后每个匹配到的请求都会被放进这个列表。
调用关系:WebRTC 相关测试会先创建它,再把它交给 wiremock 的匹配器;真正记录请求的工作由 RealtimeCallRequestCapture::matches 完成。
RealtimeCallRequestCapture::single_request82–89 ↗
fn single_request(&self) -> WiremockRequest
作用:取出刚才记录的唯一一个实时通话请求。它还会顺便确认确实只有一个请求,避免测试误判。
数据流:进去是这个捕获器自身 → 它锁住请求列表,检查长度必须是 1,然后复制那条请求 → 出来这条 WiremockRequest;如果数量不对,测试直接失败。
调用关系:WebRTC 启动测试在 Codex 发起通话后调用它,接着检查 URL、请求头和 multipart 表单内容是否正确。
调用图:外部调用 1 个(assert_eq!)。
RealtimeCallRequestCapture::matches93–99 ↗
fn matches(&self, request: &WiremockRequest) -> bool
作用:这是给 wiremock 用的“匹配器”。它不是真的筛选请求,而是把每个经过的请求存下来,并告诉测试服务器“这个请求算匹配”。
数据流:进去一个 HTTP 请求 → 它复制请求并放进共享列表 → 出来 true,表示请求通过匹配;同时捕获器的内部记录被更新。
调用关系:wiremock 在收到 POST /realtime/calls 时自动调用它;之后测试通过 single_request 查看它留下的记录。
调用图:外部调用 1 个(clone)。
normalized_json_string102–105 ↗
fn normalized_json_string(raw: &str) -> Result<String>
作用:把一段 JSON 字符串重新解析再输出成统一格式。这样测试比较 JSON 时不会被空格、换行这些表面差异干扰。
数据流:进去原始 JSON 文本 → 它先解析成 serde_json::Value,再序列化成紧凑标准字符串 → 出来标准化后的 JSON 字符串;解析失败会返回错误。
调用关系:conversation_webrtc_start_posts_generated_session 用它整理期望的 session JSON,再和 multipart 请求体里的内容做精确比较。
调用图:被 1 处调用(conversation_webrtc_start_posts_generated_session);外部调用 2 个(from_str, to_string)。
websocket_request_text107–113 ↗
fn websocket_request_text(
request: &core_test_support::responses::WebSocketRequest,
) -> Option<String>
作用:从一条 WebSocket 请求里取出用户文本内容。它让测试不用反复写很长的 JSON 路径。
数据流:进去一条 WebSocketRequest → 它读取请求 JSON 的 item.content[0].text 字段 → 出来 Some(文本) 或 None,不改动请求。
调用关系:多个测试用它找某条实时文本请求,比如确认排队的文本后来真的发给了实时服务。
调用图:调用 1 个内部函数(body_json)。
websocket_request_instructions115–121 ↗
fn websocket_request_instructions(
request: &core_test_support::responses::WebSocketRequest,
) -> Option<String>
作用:从 WebSocket 的 session.update 请求里取出 instructions,也就是给实时模型的提示词。测试靠它确认启动上下文、默认提示词和配置覆盖是否生效。
数据流:进去一条 WebSocketRequest → 它读取 session.instructions 字段 → 出来 Some(提示词) 或 None,不改动请求。
调用关系:大量启动上下文和提示词测试会调用它;它是这些测试检查“Codex 给实时模型说了什么开场白”的统一入口。
调用图:调用 1 个内部函数(body_json);被 11 处调用(conversation_disables_realtime_startup_context_with_empty_override, conversation_second_start_replaces_runtime, conversation_start_audio_text_close_round_trip, conversation_start_injects_startup_context_from_thread_history, conversation_startup_context_current_thread_selects_many_turns_by_budget, conversation_startup_context_falls_back_to_workspace_map, conversation_startup_context_is_truncated_and_sent_once_per_start, conversation_uses_default_realtime_backend_prompt, conversation_uses_empty_instructions_for_null_or_empty_prompt, conversation_uses_experimental_realtime_ws_backend_prompt_override (+1 more))。
wait_for_websocket_request123–136 ↗
async fn wait_for_websocket_request(
server: &core_test_support::responses::WebSocketTestServer,
connection_index: usize,
request_index: usize,
) -> Result<core_test_support::responses::We
作用:等待假 WebSocket 服务器收到指定连接上的指定请求。它给等待加了超时,避免测试卡死。
数据流:进去服务器、连接编号和请求编号 → 它最多等 2 秒,让服务器返回那条请求 → 出来 WebSocketRequest;超时会返回带说明的错误。
调用关系:conversation_webrtc_start_posts_generated_session 用它等待 sideband WebSocket 里后续发出的 session.update 和排队文本。
调用图:调用 1 个内部函数(wait_for_request);被 1 处调用(conversation_webrtc_start_posts_generated_session);外部调用 2 个(from_secs, timeout)。
expected_realtime_backend_prompt138–142 ↗
fn expected_realtime_backend_prompt() -> String
作用:生成测试中期望的默认实时后端提示词。它会把提示词模板里的用户名占位符换成当前测试机上的名字。
数据流:进去没有参数 → 它取默认提示词,去掉尾部空白,并调用 test_user_first_name 得到名字 → 出来完整的期望提示词字符串。
调用关系:默认提示词测试用它算出应该发送给实时服务的 instructions。
调用图:调用 1 个内部函数(test_user_first_name)。
test_user_first_name144–150 ↗
fn test_user_first_name() -> String
作用:找一个适合放进提示词里的用户名字。优先从系统真实姓名或用户名里取第一个单词,找不到就用 there。
数据流:进去没有参数 → 它读取 whoami 提供的真实姓名和用户名,挑第一个非空名字片段 → 出来一个字符串。
调用关系:expected_realtime_backend_prompt 调用它,用来替换默认提示词里的用户名字占位符。
调用图:被 1 处调用(expected_realtime_backend_prompt);外部调用 2 个(realname, username)。
wait_for_matching_websocket_request152–178 ↗
async fn wait_for_matching_websocket_request(
server: &core_test_support::responses::WebSocketTestServer,
description: &str,
predicate: F,
) -> core_test_support::responses::WebSocketReque
作用:在假 WebSocket 服务器收到的所有请求里,等到某条符合条件的请求出现。它适合找“不知道具体是第几条”的消息。
数据流:进去服务器、说明文字和一个判断函数 → 它循环查看所有连接里的请求,最多等 10 秒 → 出来第一条符合条件的请求;等不到就让测试失败。
调用关系:启动上下文、noop 工具调用等测试用它等待带 instructions 的请求或确认没有多余 response.create。
调用图:调用 1 个内部函数(connections);被 7 处调用(conversation_disables_realtime_startup_context_with_empty_override, conversation_start_injects_startup_context_from_thread_history, conversation_startup_context_current_thread_selects_many_turns_by_budget, conversation_startup_context_falls_back_to_workspace_map, conversation_startup_context_is_truncated_and_sent_once_per_start, conversation_uses_experimental_realtime_ws_startup_context_override, realtime_v2_noop_tool_call_returns_empty_function_output_without_response);外部调用 5 个(from_millis, from_secs, assert!, now, sleep)。
run_realtime_conversation_test_in_subprocess180–210 ↗
fn run_realtime_conversation_test_in_subprocess(
test_name: &str,
openai_api_key: Option<&str>,
) -> Result<()>
作用:把某个测试放到子进程里再跑一次。这样可以干净地控制环境变量,比如有没有 OPENAI_API_KEY。
数据流:进去测试名和可选 API key → 它启动当前测试二进制,只运行指定测试,并清掉代理环境变量、设置或移除 API key → 出来成功结果;子进程失败会把 stdout/stderr 打出来并让测试失败。
调用关系:涉及环境变量鉴权的两个测试先在父进程调用它,再在子进程里执行真正断言,避免当前进程的环境污染测试。
调用图:被 2 处调用(conversation_start_preflight_failure_emits_realtime_error_only, conversation_start_uses_openai_env_key_fallback_with_chatgpt_auth);外部调用 3 个(assert!, new, current_exe)。
seed_recent_thread211–242 ↗
async fn seed_recent_thread(
test: &TestCodex,
title: &str,
first_user_message: &str,
slug: &str,
) -> Result<()>
作用:给测试数据库塞一条假的最近会话记录。这样实时启动上下文可以像真实环境一样看到“最近做过什么”。
数据流:进去 TestCodex、标题、第一条用户消息和 slug → 它创建线程 ID、写一个空 rollout 文件、组装线程元数据并写入状态数据库 → 出来成功或错误;测试目录和数据库会被改动。
调用关系:启动上下文相关测试用它准备历史记录,然后检查 Codex 是否把这些历史摘要写进实时提示词。
调用图:调用 4 个内部函数(codex_home_path, workspace_path, new, new);被 4 处调用(conversation_disables_realtime_startup_context_with_empty_override, conversation_start_injects_startup_context_from_thread_history, conversation_startup_context_is_truncated_and_sent_once_per_start, conversation_uses_experimental_realtime_ws_startup_context_override);外部调用 3 个(now, format!, write)。
conversation_start_audio_text_close_round_trip245–414 ↗
async fn conversation_start_audio_text_close_round_trip() -> Result<()>
作用:测试一次完整的实时对话闭环:启动、收到会话更新、发送音频和文字、收到音频输出、最后关闭。它确认最基础的实时通道能跑通。
数据流:进去是测试环境 → 它启动假 WebSocket 服务,提交 RealtimeConversationStart、Audio、Text、Close 操作,并检查服务器收到的请求和客户端发出的事件 → 出来测试成功;过程中会创建连接并关闭服务。
调用关系:这是实时对话的主路径测试,依赖假 WebSocket、wait_for_event_match 和 websocket_request_instructions 来验证连接头、模型、语音和消息类型。
调用图:调用 3 个内部函数(start_websocket_server, test_codex, websocket_request_instructions);外部调用 8 个(assert!, assert_eq!, wait_for_event_match, RealtimeConversationAudio, RealtimeConversationStart, RealtimeConversationText, skip_if_no_network!, vec!)。
conversation_start_defaults_to_v2_and_gpt_realtime_1_5417–480 ↗
async fn conversation_start_defaults_to_v2_and_gpt_realtime_1_5() -> Result<()>
作用:确认没有显式指定版本和模型时,实时对话默认使用 V2 和 gpt-realtime-1.5。默认值错了会导致客户端连到错误协议。
数据流:进去是测试环境 → 它配置实时 WebSocket 地址为空启动上下文,启动实时对话,读取握手 URI 和 session.update 请求 → 出来测试断言默认版本、模型、声音和提示词都正确。
调用关系:它覆盖“默认配置”这条路径,和显式版本、显式语音等测试互补。
调用图:调用 3 个内部函数(start_mock_server, start_websocket_server, test_codex);外部调用 6 个(assert!, assert_eq!, wait_for_event_match, RealtimeConversationStart, skip_if_no_network!, vec!)。
conversation_webrtc_start_posts_generated_session483–666 ↗
async fn conversation_webrtc_start_posts_generated_session() -> Result<()>
作用:测试 WebRTC 启动时,Codex 会把浏览器给的 SDP offer 和生成好的实时 session 一起 POST 到通话接口。这样媒体连接不用等 sideband WebSocket 准备好才开始。
数据流:进去是测试环境和假 SDP → 它模拟 /realtime/calls 返回 SDP answer,延迟 sideband WebSocket 接入,提交启动和一条排队文本 → 出来断言 SDP、HTTP 表单、认证头、sideband 握手和排队文本都正确。
调用关系:它用 RealtimeCallRequestCapture 抓 HTTP 请求,用 normalized_json_string 比较 session JSON,用 wait_for_websocket_request 等待 sideband 消息。
调用图:调用 6 个内部函数(new, start_mock_server, start_websocket_server_with_headers, test_codex, normalized_json_string, wait_for_websocket_request);外部调用 13 个(from_millis, given, new, from_utf8, assert!, assert_eq!, wait_for_event_match, RealtimeConversationStart, RealtimeConversationText, skip_if_no_network! (+3 more))。
conversation_webrtc_start_uses_avas_architecture_query669–765 ↗
async fn conversation_webrtc_start_uses_avas_architecture_query() -> Result<()>
作用:确认选择 AVAS 架构时,创建 WebRTC 通话的 HTTP 请求会带上 architecture=avas 查询参数。
数据流:进去是 AVAS 启动参数 → 它模拟通话创建和 sideband WebSocket,提交 WebRTC 启动 → 出来断言 POST URL 查询串、SDP answer、会话更新和 sideband 握手都符合预期。
调用关系:它专门覆盖 architecture 参数到 call API 的传递,使用 RealtimeCallRequestCapture 查看真实请求。
调用图:调用 4 个内部函数(new, start_mock_server, start_websocket_server_with_headers, test_codex);外部调用 9 个(given, new, assert_eq!, wait_for_event_match, RealtimeConversationStart, skip_if_no_network!, vec!, method, path_regex)。
conversation_webrtc_start_uses_configured_call_base_url_for_avas768–866 ↗
async fn conversation_webrtc_start_uses_configured_call_base_url_for_avas() -> Result<()>
作用:确认 AVAS WebRTC 通话会使用配置里的 call base URL,而不是默认地址。这样本地或特殊部署环境能把通话创建请求发到正确地方。
数据流:进去是带自定义 call base URL 的配置 → 它启动假 API 和假 WebSocket,提交 AVAS WebRTC 启动 → 出来检查请求路径、查询串、会话 ID 和 sideband call_id 都正确。
调用关系:它和 AVAS 查询参数测试相近,但重点是验证配置覆盖的通话创建地址。
调用图:调用 4 个内部函数(new, start_mock_server, start_websocket_server_with_headers, test_codex);外部调用 10 个(given, new, assert_eq!, wait_for_event_match, format!, RealtimeConversationStart, skip_if_no_network!, vec!, method, path_regex)。
conversation_webrtc_close_while_sideband_connecting_drops_pending_join869–962 ↗
async fn conversation_webrtc_close_while_sideband_connecting_drops_pending_join() -> Result<()>
作用:测试 WebRTC 已拿到 SDP,但 sideband WebSocket 还在连接时,如果用户关闭会话,后台等待连接的任务会被取消。否则旧任务可能之后冒出来发错误事件。
数据流:进去是延迟接入的 sideband 服务 → 它启动 WebRTC,拿到 SDP 后马上提交关闭 → 出来断言关闭原因是 requested,并且之后没有陈旧错误、没有完成握手。
调用关系:它覆盖关闭和异步连接竞态,用超时等待来确认没有泄漏的后台事件。
调用图:调用 3 个内部函数(start_mock_server, start_websocket_server_with_headers, test_codex);外部调用 12 个(from_millis, given, new, assert!, assert_eq!, wait_for_event_match, RealtimeConversationStart, skip_if_no_network!, timeout, vec! (+2 more))。
conversation_webrtc_sideband_connect_failure_closes_with_error965–1052 ↗
async fn conversation_webrtc_sideband_connect_failure_closes_with_error() -> Result<()>
作用:测试 WebRTC 通话创建成功但 sideband WebSocket 连不上时,实时会话会报错并关闭。之后再发文本应该被拒绝。
数据流:进去是不可连接的 WebSocket 地址 → 它创建 WebRTC 通话、收到 SDP,然后等待连接失败事件和关闭事件,再尝试发送文本 → 出来断言错误非空、关闭原因是 error、后续操作提示会话未运行。
调用关系:它验证 WebRTC 双通道里 sideband 失败的处理,连接失败不会被静默吞掉。
调用图:调用 2 个内部函数(start_mock_server, test_codex);外部调用 10 个(given, new, assert!, assert_eq!, wait_for_event_match, RealtimeConversationStart, RealtimeConversationText, skip_if_no_network!, method, path_regex)。
conversation_start_uses_openai_env_key_fallback_with_chatgpt_auth1055–1134 ↗
async fn conversation_start_uses_openai_env_key_fallback_with_chatgpt_auth() -> Result<()>
作用:确认当当前登录是 ChatGPT 账号但环境变量里有 OpenAI API key 时,实时连接会用环境变量里的 key。实时服务需要 API key,不能只靠 ChatGPT 登录态。
数据流:父进程进去时会启动子进程并设置 env-realtime-key;子进程里启动实时对话 → 它检查会话能开始,并读取 WebSocket 握手 authorization 头 → 出来断言使用 Bearer env-realtime-key。
调用关系:它借助 run_realtime_conversation_test_in_subprocess 控制环境变量,验证鉴权兜底逻辑。
调用图:调用 4 个内部函数(start_websocket_server, test_codex, run_realtime_conversation_test_in_subprocess, create_dummy_chatgpt_auth_for_testing);外部调用 7 个(assert!, assert_eq!, wait_for_event_match, RealtimeConversationStart, skip_if_no_network!, var_os, vec!)。
conversation_transport_close_emits_closed_event1137–1201 ↗
async fn conversation_transport_close_emits_closed_event() -> Result<()>
作用:测试实时传输通道被服务器关闭时,Codex 会发出“会话已关闭”事件。没有这个事件,界面可能不知道连接已经断了。
数据流:进去是假 WebSocket 服务 → 它启动会话,等 session.updated 后让服务端关闭连接 → 出来断言收到 RealtimeConversationClosed,原因是 transport_closed。
调用关系:它覆盖被动断线场景,与用户主动 close 的测试互补。
调用图:调用 2 个内部函数(start_websocket_server, test_codex);外部调用 6 个(assert!, assert_eq!, wait_for_event_match, RealtimeConversationStart, skip_if_no_network!, vec!)。
conversation_audio_before_start_emits_error1204–1233 ↗
async fn conversation_audio_before_start_emits_error() -> Result<()>
作用:确认还没启动实时会话就发送音频时,系统会给出清楚错误,而不是悄悄丢掉或崩溃。
数据流:进去是未运行的 Codex 实例 → 它提交 RealtimeConversationAudio → 出来收到普通 Error,错误类型是 BadRequest,消息是 conversation is not running。
调用关系:它和文本未启动测试一起验证运行状态检查。
调用图:调用 2 个内部函数(start_websocket_server, test_codex);外部调用 5 个(assert_eq!, wait_for_event_match, RealtimeConversationAudio, skip_if_no_network!, vec!)。
conversation_start_preflight_failure_emits_realtime_error_only1236–1287 ↗
async fn conversation_start_preflight_failure_emits_realtime_error_only() -> Result<()>
作用:测试启动前置检查失败时,只发实时错误事件,不额外发关闭事件。这里的失败是没有 API key 却想启动实时对话。
数据流:父进程通过子进程清掉 API key;子进程用 ChatGPT 测试登录启动实时会话 → 它等待实时错误,再短暂确认没有 closed 事件 → 出来断言错误内容是需要 API key。
调用关系:它依赖 run_realtime_conversation_test_in_subprocess 隔离环境,覆盖“还没真正建立会话就失败”的行为。
调用图:调用 4 个内部函数(start_websocket_server, test_codex, run_realtime_conversation_test_in_subprocess, create_dummy_chatgpt_auth_for_testing);外部调用 9 个(from_millis, assert!, assert_eq!, wait_for_event_match, RealtimeConversationStart, skip_if_no_network!, var_os, timeout, vec!)。
conversation_start_connect_failure_emits_realtime_error_only1290–1337 ↗
async fn conversation_start_connect_failure_emits_realtime_error_only() -> Result<()>
作用:测试 WebSocket 连接一开始就失败时,只发实时错误事件,不发关闭事件。因为会话根本没有成功启动。
数据流:进去是不可连接的实时地址 → 它提交启动请求,等待 RealtimeEvent::Error,然后用短超时确认没有 RealtimeConversationClosed → 出来测试成功或失败。
调用关系:它和 preflight 失败测试一起确保“启动失败”和“运行后关闭”在事件语义上分清楚。
调用图:调用 2 个内部函数(start_websocket_server, test_codex);外部调用 7 个(from_millis, assert!, wait_for_event_match, RealtimeConversationStart, skip_if_no_network!, timeout, vec!)。
conversation_text_before_start_emits_error1340–1364 ↗
async fn conversation_text_before_start_emits_error() -> Result<()>
作用:确认还没启动实时会话就发送文本时,会得到明确错误。
数据流:进去是未运行的 Codex 实例 → 它提交 RealtimeConversationText → 出来收到 BadRequest 错误,消息是 conversation is not running。
调用关系:它和音频未启动测试共同覆盖发送入口的状态保护。
调用图:调用 2 个内部函数(start_websocket_server, test_codex);外部调用 5 个(assert_eq!, wait_for_event_match, RealtimeConversationText, skip_if_no_network!, vec!)。
conversation_second_start_replaces_runtime1367–1500 ↗
async fn conversation_second_start_replaces_runtime() -> Result<()>
作用:测试第二次启动实时对话会替换旧的运行实例。后续音频应该发到新会话,而不是旧会话。
数据流:进去是假服务器准备好的旧会话和新会话响应 → 它先启动 old,再启动 new,然后发送音频 → 出来断言旧连接只收到旧 session.update,新连接收到新 session.update 和音频追加。
调用关系:它验证实时运行时的替换逻辑,使用 websocket_request_instructions 检查两次启动的提示词。
调用图:调用 3 个内部函数(start_websocket_server, test_codex, websocket_request_instructions);外部调用 7 个(assert!, assert_eq!, wait_for_event_match, RealtimeConversationAudio, RealtimeConversationStart, skip_if_no_network!, vec!)。
conversation_uses_experimental_realtime_ws_base_url_override1503–1569 ↗
async fn conversation_uses_experimental_realtime_ws_base_url_override() -> Result<()>
作用:确认配置里的 experimental_realtime_ws_base_url 会把实时连接导向指定服务器。这样测试或实验环境可以不用默认地址。
数据流:进去是启动服务器和真正实时服务器两个假服务 → 它配置覆盖地址后启动实时对话 → 出来断言启动用的旧服务器没收到新连接,覆盖的实时服务器收到了 session.update。
调用关系:它专门验证 WebSocket 地址配置,和其他提示词、模型配置测试分开。
调用图:调用 2 个内部函数(start_websocket_server, test_codex);外部调用 6 个(assert!, assert_eq!, wait_for_event_match, RealtimeConversationStart, skip_if_no_network!, vec!)。
conversation_uses_default_realtime_backend_prompt1572–1638 ↗
async fn conversation_uses_default_realtime_backend_prompt() -> Result<()>
作用:测试没有传 prompt 时,系统会使用默认实时后端提示词,并附加启动上下文。
数据流:进去是带受控 startup context 的配置 → 它启动实时会话且 prompt 为 None → 出来检查 session.instructions 等于默认提示词加上 controlled startup context。
调用关系:它用 expected_realtime_backend_prompt 算期望值,用 websocket_request_instructions 读取实际发送内容。
调用图:调用 3 个内部函数(start_websocket_server, test_codex, websocket_request_instructions);外部调用 6 个(assert!, assert_eq!, wait_for_event_match, RealtimeConversationStart, skip_if_no_network!, vec!)。
conversation_uses_empty_instructions_for_null_or_empty_prompt1641–1719 ↗
async fn conversation_uses_empty_instructions_for_null_or_empty_prompt() -> Result<()>
作用:确认显式传入 null prompt 或空字符串 prompt 时,instructions 会是空字符串,而不是回退到默认提示词。
数据流:进去是两个启动参数:Some(None) 和 Some(Some("")) → 它分别启动并关闭实时会话 → 出来断言两次 session.instructions 都为空。
调用关系:它澄清 prompt 字段的语义:不传才用默认,显式传空就是空。
调用图:调用 3 个内部函数(start_websocket_server, test_codex, websocket_request_instructions);外部调用 7 个(new, assert!, assert_eq!, wait_for_event_match, RealtimeConversationStart, skip_if_no_network!, vec!)。
conversation_uses_explicit_start_voice1722–1777 ↗
async fn conversation_uses_explicit_start_voice() -> Result<()>
作用:测试启动参数里的 voice 会控制实时输出声音。这里显式指定 Breeze。
数据流:进去是 voice=Breeze 的启动请求 → 它启动实时会话,等会话更新 → 出来检查 session.audio.output.voice 是 breeze。
调用关系:它覆盖“本次启动参数优先”的声音选择路径。
调用图:调用 2 个内部函数(start_websocket_server, test_codex);外部调用 6 个(assert!, assert_eq!, wait_for_event_match, RealtimeConversationStart, skip_if_no_network!, vec!)。
conversation_uses_configured_realtime_voice1780–1838 ↗
async fn conversation_uses_configured_realtime_voice() -> Result<()>
作用:测试配置文件里的实时声音设置会被使用。这里配置为 Cove。
数据流:进去是 realtime.voice=Cove 的配置,启动请求里不带 voice → 它启动会话并查看 session.update → 出来断言输出声音是 cove。
调用关系:它和显式 voice 测试互补,验证配置默认值路径。
调用图:调用 2 个内部函数(start_websocket_server, test_codex);外部调用 6 个(assert!, assert_eq!, wait_for_event_match, RealtimeConversationStart, skip_if_no_network!, vec!)。
conversation_rejects_voice_for_wrong_realtime_version1841–1875 ↗
async fn conversation_rejects_voice_for_wrong_realtime_version() -> Result<()>
作用:确认某些实时版本不支持的声音参数会被拒绝,而不是发出一个服务器可能无法理解的请求。
数据流:进去是 V2 配置和 voice=Cove 的启动请求 → 它提交启动 → 出来收到实时错误,内容说明 cove 不支持 v2。
调用关系:它覆盖参数校验逻辑,防止版本和声音能力不匹配。
调用图:调用 2 个内部函数(start_mock_server, test_codex);外部调用 4 个(assert!, wait_for_event_match, RealtimeConversationStart, skip_if_no_network!)。
conversation_uses_experimental_realtime_ws_backend_prompt_override1878–1937 ↗
async fn conversation_uses_experimental_realtime_ws_backend_prompt_override() -> Result<()>
作用:测试配置里的 experimental_realtime_ws_backend_prompt 会覆盖启动操作里的 prompt。配置覆盖常用于实验或强制统一提示词。
数据流:进去是配置 prompt from config 和操作 prompt from op → 它启动实时会话 → 出来检查实际 instructions 以 prompt from config 开头。
调用关系:它验证配置覆盖优先级,使用 websocket_request_instructions 读取实际请求。
调用图:调用 3 个内部函数(start_websocket_server, test_codex, websocket_request_instructions);外部调用 6 个(assert!, assert_eq!, wait_for_event_match, RealtimeConversationStart, skip_if_no_network!, vec!)。
conversation_uses_experimental_realtime_ws_startup_context_override1940–2008 ↗
async fn conversation_uses_experimental_realtime_ws_startup_context_override() -> Result<()>
作用:测试配置里的 startup context 文本会替代自动生成的启动上下文。这样测试或实验可以给实时模型固定背景。
数据流:进去是自定义 startup context、近期线程和工作区文件 → 它启动实时对话并等到带 instructions 的请求 → 出来断言 instructions 是配置提示词加自定义上下文,且不包含自动生成的机器/工作区摘要。
调用关系:它调用 seed_recent_thread 准备会被忽略的历史,再用 wait_for_matching_websocket_request 找到实际请求。
调用图:调用 5 个内部函数(start_websocket_server, test_codex, seed_recent_thread, wait_for_matching_websocket_request, websocket_request_instructions);外部调用 7 个(assert!, assert_eq!, create_dir_all, write, RealtimeConversationStart, skip_if_no_network!, vec!)。
conversation_disables_realtime_startup_context_with_empty_override2011–2078 ↗
async fn conversation_disables_realtime_startup_context_with_empty_override() -> Result<()>
作用:确认把 startup context 配成空字符串会关闭自动启动上下文。这样用户可以只发基础提示词。
数据流:进去是空 startup context 覆盖、历史线程和工作区文件 → 它启动实时对话 → 出来断言 instructions 只有 prompt from config,不含 startup context 标记或工作区地图。
调用关系:它和自定义 startup context 测试一起验证配置覆盖:非空表示替换,空表示禁用。
调用图:调用 5 个内部函数(start_websocket_server, test_codex, seed_recent_thread, wait_for_matching_websocket_request, websocket_request_instructions);外部调用 7 个(assert!, assert_eq!, create_dir_all, write, RealtimeConversationStart, skip_if_no_network!, vec!)。
conversation_start_injects_startup_context_from_thread_history2081–2150 ↗
async fn conversation_start_injects_startup_context_from_thread_history() -> Result<()>
作用:测试正常启动时,系统会把最近线程和工作区情况写进实时模型的启动提示词。这样实时模型一开始就知道用户最近在做什么。
数据流:进去是 seeded 最近线程和 README 文件 → 它启动实时会话并读取 instructions → 出来断言里面有 startup_context 标签、最近会话、分支、用户问题和工作区地图,但没有不该出现的 memory 提示。
调用关系:它是自动启动上下文的核心测试,依赖 seed_recent_thread 和 websocket_request_instructions。
调用图:调用 5 个内部函数(start_websocket_server, test_codex, seed_recent_thread, wait_for_matching_websocket_request, websocket_request_instructions);外部调用 6 个(assert!, create_dir_all, write, RealtimeConversationStart, skip_if_no_network!, vec!)。
conversation_startup_context_current_thread_selects_many_turns_by_budget2153–2313 ↗
async fn conversation_startup_context_current_thread_selects_many_turns_by_budget() -> Result<()>
作用:测试当前线程历史很长时,启动上下文会按预算挑选和截断多轮对话。它防止提示词过长,也防止最新重要内容丢失。
数据流:进去是一批伪造的用户/助手历史,其中最后一轮很长 → 它恢复线程、启动实时对话、截取 Current Thread 部分并做快照断言 → 出来确认渲染轮数、顺序和每轮长度都在预算内。
调用关系:它通过 resume_thread_with_history 构造真实线程历史,再用 wait_for_matching_websocket_request 查看发送给实时服务的上下文。
调用图:调用 7 个内部函数(auth_manager_from_auth, start_mock_server, start_websocket_server, test_codex, wait_for_matching_websocket_request, websocket_request_instructions, from_api_key);外部调用 7 个(assert_eq!, format!, assert_snapshot!, Forked, RealtimeConversationStart, skip_if_no_network!, vec!)。
conversation_startup_context_falls_back_to_workspace_map2316–2372 ↗
async fn conversation_startup_context_falls_back_to_workspace_map() -> Result<()>
作用:测试没有最近历史时,启动上下文至少会带上工作区地图。这样实时模型仍然知道当前目录里大概有什么。
数据流:进去是只有工作区文件和目录的环境 → 它启动实时会话 → 出来断言 startup context 中包含 Machine / Workspace Map、notes.txt 和 codex-rs/。
调用关系:它覆盖启动上下文的兜底路径:没有线程摘要,也要给工作区概览。
调用图:调用 4 个内部函数(start_websocket_server, test_codex, wait_for_matching_websocket_request, websocket_request_instructions);外部调用 6 个(assert!, create_dir_all, write, RealtimeConversationStart, skip_if_no_network!, vec!)。
conversation_startup_context_is_truncated_and_sent_once_per_start2375–2450 ↗
async fn conversation_startup_context_is_truncated_and_sent_once_per_start() -> Result<()>
作用:测试启动上下文过大时会被截断,并且只在启动时发送一次。后续普通文本不应该重复带上那大段背景。
数据流:进去是超长最近工作摘要和一个 marker 文件 → 它启动实时会话,检查 instructions 长度不超过约 20500,再发送 hello → 出来确认 hello 作为普通文本单独发送。
调用关系:它使用 seed_recent_thread 制造超长内容,用 wait_for_matching_websocket_request 分别找启动请求和后续文本请求。
调用图:调用 5 个内部函数(start_websocket_server, test_codex, seed_recent_thread, wait_for_matching_websocket_request, websocket_request_instructions);外部调用 7 个(assert!, assert_eq!, write, RealtimeConversationStart, RealtimeConversationText, skip_if_no_network!, vec!)。
conversation_user_text_turn_is_not_sent_to_realtime2453–2547 ↗
async fn conversation_user_text_turn_is_not_sent_to_realtime() -> Result<()>
作用:确认普通 UserInput 文字会走普通模型 API,不会自动发到实时 WebSocket。否则同一句话可能被两个通道重复处理。
数据流:进去是已启动的实时会话和一次普通用户输入 → 它等待普通模型 turn 完成,检查模型请求包含用户文本,同时检查实时服务器只收到 session.update → 出来证明文本没有发到实时通道。
调用关系:它区分普通聊天输入和 RealtimeConversationText,两者走不同入口。
调用图:调用 5 个内部函数(mount_sse_once, sse, start_mock_server, start_websocket_server, test_codex);外部调用 7 个(default, assert!, assert_eq!, wait_for_event_match, RealtimeConversationStart, skip_if_no_network!, vec!)。
realtime_v2_noop_tool_call_returns_empty_function_output_without_response2550–2639 ↗
async fn realtime_v2_noop_tool_call_returns_empty_function_output_without_response() -> Result<()>
作用:测试 V2 实时服务请求 remain_silent 这类“不要说话”的工具调用时,Codex 只回空工具结果,不再要求实时模型生成回复。
数据流:进去是假实时服务发来的 function_call remain_silent → 它启动会话,等待 NoopRequested 事件,检查发回 function_call_output 的 output 为空,再确认没有 response.create → 出来测试成功。
调用关系:它覆盖 V2 特有的 noop 工具调用处理,避免模型在应该沉默时又说话。
调用图:调用 4 个内部函数(start_mock_server, start_websocket_server, test_codex, wait_for_matching_websocket_request);外部调用 8 个(from_millis, assert!, assert_eq!, wait_for_event_match, RealtimeConversationStart, skip_if_no_network!, timeout, vec!)。
conversation_mirrors_assistant_message_text_to_realtime_handoff2642–2759 ↗
async fn conversation_mirrors_assistant_message_text_to_realtime_handoff() -> Result<()>
作用:测试实时服务请求 handoff 后,普通模型生成的助手最终文字会被追加回实时 handoff。这样语音侧能拿到代理模型的回答。
数据流:进去是假实时 handoff 请求和普通模型返回 assistant says hi → 它启动实时会话,触发 handoff,等待普通 turn 完成,再查看 WebSocket 请求 → 出来断言发送了 conversation.handoff.append,内容是 Agent Final Message。
调用关系:它验证实时到普通模型再回实时的桥接路径,是交接功能的核心测试之一。
调用图:调用 5 个内部函数(mount_sse_once, sse, start_mock_server, start_websocket_server, test_codex);外部调用 10 个(from_millis, from_secs, assert_eq!, wait_for_event, wait_for_event_match, RealtimeConversationStart, skip_if_no_network!, now, sleep, vec!)。
conversation_handoff_persists_across_item_done_until_turn_complete2762–2919 ↗
async fn conversation_handoff_persists_across_item_done_until_turn_complete() -> Result<()>
作用:测试 handoff 状态不会因为实时 item.done 事件就提前清掉,而是会持续到普通模型 turn 完成。否则后面的助手消息可能追加不到正确 handoff。
数据流:进去是带闸门的流式 SSE 响应和实时 handoff/item.done 事件 → 它先收到第一条助手消息并检查 append,再放行第二条助手消息 → 出来断言两次 append 都使用同一个 handoff_id。
调用关系:它用 start_streaming_sse_server 控制普通模型分块到达时机,专门覆盖异步事件顺序问题。
调用图:调用 3 个内部函数(start_websocket_server, start_streaming_sse_server, test_codex);外部调用 7 个(assert_eq!, wait_for_event, wait_for_event_match, channel, RealtimeConversationStart, skip_if_no_network!, vec!)。
sse_event2921–2923 ↗
message_input_texts2925–2937 ↗
fn message_input_texts(body: &Value, role: &str) -> Vec<String>
作用:从普通模型请求 JSON 里抽出指定角色的 input_text 内容。测试用它确认请求里到底塞了哪些用户文本。
数据流:进去请求 JSON 和角色名 → 它遍历 input 数组,筛出 message、指定 role、content 里的 input_text → 出来文本列表,不修改原 JSON。
调用关系:handoff steering 和不阻塞音频的测试用它检查普通模型请求是否包含 realtime_delegation 包装文本。
调用图:被 2 处调用(inbound_handoff_request_starts_turn_and_does_not_block_realtime_audio, inbound_handoff_request_steers_active_turn);外部调用 1 个(get)。
inbound_handoff_request_starts_turn2940–3034 ↗
async fn inbound_handoff_request_starts_turn() -> Result<()>
作用:测试实时服务发来 handoff.requested 时,Codex 会启动一次普通模型 turn。实时语音里的问题因此能交给普通模型处理。
数据流:进去是假实时 handoff 和普通模型成功响应 → 它启动实时会话,等待 handoff 事件和 TurnComplete → 出来检查普通模型请求包含 realtime_delegation,其中有输入和转写增量。
调用关系:它是“实时请求普通模型帮忙”的基础测试,依赖模型请求捕获来确认输入格式。
调用图:调用 5 个内部函数(mount_sse_once, sse, start_mock_server, start_websocket_server, test_codex);外部调用 7 个(assert!, assert_eq!, wait_for_event, wait_for_event_match, RealtimeConversationStart, skip_if_no_network!, vec!)。
inbound_handoff_request_uses_active_transcript3037–3126 ↗
async fn inbound_handoff_request_uses_active_transcript() -> Result<()>
作用:测试 handoff 请求会带上当前累积的实时转写上下文,而不只看请求里的 input_transcript 字段。这样模型能看到交接前的语音上下文。
数据流:进去是助手转写、用户转写、助手转写再 handoff 的事件序列 → 它启动会话并等待普通 turn 完成 → 出来断言模型请求里的 transcript_delta 包含完整角色顺序。
调用关系:它验证 transcript 缓冲区的使用,补充基础 handoff 启动测试。
调用图:调用 5 个内部函数(mount_sse_once, sse, start_mock_server, start_websocket_server, test_codex);外部调用 6 个(assert!, wait_for_event, wait_for_event_match, RealtimeConversationStart, skip_if_no_network!, vec!)。
inbound_handoff_request_sends_transcript_delta_after_each_handoff3129–3257 ↗
async fn inbound_handoff_request_sends_transcript_delta_after_each_handoff() -> Result<()>
作用:测试每次 handoff 后,发送给普通模型的转写增量会清掉旧内容。第二次 handoff 不应该重复带上第一次的问题。
数据流:进去是两个连续 handoff 和两段普通模型响应 → 它启动会话,完成第一次 turn,再发送音频触发第二批实时事件 → 出来检查两次模型请求分别只包含各自的问题。
调用关系:它覆盖多次交接的状态重置,防止上下文越积越重复。
调用图:调用 4 个内部函数(mount_sse_sequence, start_mock_server, start_websocket_server, test_codex);外部调用 8 个(assert!, assert_eq!, wait_for_event, wait_for_event_match, RealtimeConversationAudio, RealtimeConversationStart, skip_if_no_network!, vec!)。
inbound_conversation_item_does_not_start_turn_and_still_forwards_audio3260–3349 ↗
async fn inbound_conversation_item_does_not_start_turn_and_still_forwards_audio() -> Result<()>
作用:测试实时服务回显一个 conversation.item.added 用户消息时,不会误触发普通模型 turn,但后面的音频输出仍会正常转发。
数据流:进去是 session.updated、用户 item.added 和音频 delta → 它启动会话,等待音频输出事件,再短暂确认没有 TurnStarted → 出来证明回显消息被忽略但音频没被挡住。
调用关系:它保护 handoff 触发条件,确保不是所有实时用户消息都会开启普通模型请求。
调用图:调用 3 个内部函数(start_mock_server, start_websocket_server, test_codex);外部调用 8 个(from_millis, assert!, assert_eq!, wait_for_event_match, RealtimeConversationStart, skip_if_no_network!, timeout, vec!)。
delegated_turn_user_role_echo_does_not_redelegate_and_still_forwards_audio3352–3526 ↗
async fn delegated_turn_user_role_echo_does_not_redelegate_and_still_forwards_audio() -> Result<()>
作用:测试普通模型回答被实时侧以 user role 回显时,不会再次触发交接形成循环,同时音频仍然会转发。
数据流:进去是一次 handoff、模型助手消息、实时 user 回显同样文本和音频 delta → 它检查 handoff.append 已发送、音频输出收到、普通模型请求只有一次 → 出来证明没有重复委托。
调用关系:它防止“代理回答被当成新用户问题”这种循环问题,并用流式 SSE 闸门控制 turn 完成时机。
调用图:调用 3 个内部函数(start_websocket_server, start_streaming_sse_server, test_codex);外部调用 9 个(assert_eq!, wait_for_event, wait_for_event_match, eprintln!, channel, RealtimeConversationStart, skip_if_no_network!, now, vec!)。
inbound_handoff_request_does_not_block_realtime_event_forwarding3529–3643 ↗
async fn inbound_handoff_request_does_not_block_realtime_event_forwarding() -> Result<()>
作用:测试 handoff 启动普通模型 turn 后,实时事件转发不会被这个 turn 卡住。语音音频应该能继续及时到达界面。
数据流:进去是 handoff 后紧跟音频 delta、而普通模型完成被闸门延迟 → 它等待 handoff,再在模型未完成时等待音频输出 → 出来断言音频在 500ms 内收到,之后再放行 turn 完成。
调用关系:它验证实时事件循环和普通模型请求是并行的,不会互相堵塞。
调用图:调用 3 个内部函数(start_websocket_server, start_streaming_sse_server, test_codex);外部调用 9 个(from_millis, assert_eq!, wait_for_event, wait_for_event_match, channel, RealtimeConversationStart, skip_if_no_network!, timeout, vec!)。
inbound_handoff_request_steers_active_turn3646–3826 ↗
async fn inbound_handoff_request_steers_active_turn() -> Result<()>
作用:测试如果普通模型 turn 正在进行,实时 handoff 会引导下一次请求,而不是塞进已经发出去的当前请求。这样不会改写已经在路上的模型调用。
数据流:进去是一个正在流式输出的第一 turn,以及期间到来的实时 handoff → 它先提交普通用户输入,等模型开始输出,再触发实时音频和 handoff,放行完成 → 出来检查第一个请求没有 delegation,第二个请求包含 delegation。
调用关系:它覆盖实时交接和普通 turn 同时发生时的调度顺序,用 message_input_texts 检查两次模型请求。
调用图:调用 4 个内部函数(start_websocket_server_with_headers, start_streaming_sse_server, test_codex, message_input_texts);外部调用 11 个(default, assert!, assert_eq!, wait_for_event, wait_for_event_match, channel, RealtimeConversationAudio, RealtimeConversationStart, from_slice, skip_if_no_network! (+1 more))。
inbound_handoff_request_starts_turn_and_does_not_block_realtime_audio3829–3954 ↗
async fn inbound_handoff_request_starts_turn_and_does_not_block_realtime_audio() -> Result<()>
作用:测试 handoff 会启动普通模型 turn,同时不会阻塞紧跟着到来的实时音频。它把“能启动”和“不堵音频”放在同一个场景里确认。
数据流:进去是 handoff 请求、音频 delta 和被延迟完成的普通模型响应 → 它启动实时会话,等待 handoff,再确认音频很快转发,最后放行普通模型完成并检查请求文本 → 出来断言 delegation 内容正确且音频正常。
调用关系:它结合 message_input_texts、流式 SSE 服务器和实时 WebSocket,验证交接启动与音频转发可以同时工作。
调用图:调用 4 个内部函数(start_websocket_server, start_streaming_sse_server, test_codex, message_input_texts);外部调用 12 个(from_millis, assert!, assert_eq!, wait_for_event, wait_for_event_match, format!, channel, RealtimeConversationStart, from_slice, skip_if_no_network! (+2 more))。
轮次连续性与压缩
这些测试关注在后续请求中保留和重塑对话状态,包括远程压缩、lite 模式变体以及每轮传输状态。
core/tests/suite/compact_remote.rs源码 ↗
模型能记住的内容有限,聊天一长就可能塞不下。这个文件像一套“压力体检”,用假的 HTTP 服务和 WebSocket 服务模拟远端模型,检查手动压缩、自动压缩、压缩失败、重试、工具调用、实时语音会话、恢复会话等场景。它不实现压缩功能,而是验证真正的系统在发请求时带对了认证、会话号、工具列表、上下文窗口编号、turn state(一段让后端延续同一轮状态的小标记),并确认压缩后的历史会替换旧历史。没有这些测试,系统很容易出现摘要丢失、旧开发者指令混入、工具结果太大、压缩失败后还继续跑等隐蔽问题。
approx_token_count53–55 ↗
fn approx_token_count(text: &str) -> i64
作用:粗略估算一段文字大概会占多少 token。token 可以理解成模型读文字时切出来的小块,这里用“约 4 个字符算 1 个 token”的简化算法。
数据流:输入一段文字 → 取它的长度,加一点余量后除以 4,并转成整数 → 输出一个大概的 token 数;它不改动外部状态。
调用关系:它被压缩请求大小估算相关的辅助函数使用,也被测试长系统指令是否影响裁剪时直接调用。它只是测试里的估算尺子,不参与真实模型计费。
调用图:被 2 处调用(estimate_compact_payload_tokens, remote_compact_trim_estimate_uses_session_base_instructions);外部调用 1 个(try_from)。
estimate_compact_input_tokens57–61 ↗
fn estimate_compact_input_tokens(request: &responses::ResponsesRequest) -> i64
作用:估算一次远程压缩请求里,历史输入部分大概占多少 token。它帮助测试判断压缩前的内容是否接近上下文上限。
数据流:输入一个 ResponsesRequest 测试请求 → 取出其中的 input 项,把每一项转成文字并估算 token → 输出所有输入项的估算总数。
调用关系:它会调用请求对象的 input 读取输入内容,并把逐项估算交给 approx_token_count。更大的估算函数和特定裁剪测试会用它做判断。
调用图:调用 1 个内部函数(input);被 2 处调用(estimate_compact_payload_tokens, remote_compact_trim_estimate_uses_session_base_instructions)。
estimate_compact_payload_tokens63–66 ↗
fn estimate_compact_payload_tokens(request: &responses::ResponsesRequest) -> i64
作用:估算整个远程压缩请求的大致大小,包括历史输入和系统指令。这样测试能覆盖“指令本身也会占上下文空间”的情况。
数据流:输入一个压缩请求 → 先估算 input,再读取 instructions_text 里的指令文字并估算 → 输出两部分相加后的总 token 数。
调用关系:它站在 estimate_compact_input_tokens 和 approx_token_count 之上,用在检查基础指令会影响裁剪决策的测试里。
调用图:调用 3 个内部函数(instructions_text, approx_token_count, estimate_compact_input_tokens);被 1 处调用(remote_compact_trim_estimate_uses_session_base_instructions)。
assert_tools_payload_does_not_defer68–75 ↗
fn assert_tools_payload_does_not_defer(body: &Value)
作用:确认发给模型看的工具列表里,没有还应该隐藏的“延迟加载工具”。延迟加载就是先不把工具完整展示给模型,等搜索到再说。
数据流:输入一段 JSON 请求体 → 如果里面有 tools 字段,就递归检查是否出现 defer_loading=true → 如果发现就让测试失败,否则什么也不返回。
调用关系:它被 remote_compact_filters_deferred_dynamic_tools 调用,用来同时检查普通请求和压缩请求的工具列表都干净。内部依赖 contains_defer_loading 做递归查找。
调用图:被 1 处调用(remote_compact_filters_deferred_dynamic_tools);外部调用 2 个(get, assert!)。
namespace_child_tool_names77–102 ↗
fn namespace_child_tool_names(body: &Value, namespace: &str) -> Vec<String>
作用:从请求 JSON 里找出某个工具命名空间下面实际暴露给模型的子工具名字。命名空间可以理解成一个工具文件夹。
数据流:输入请求 JSON 和命名空间名字 → 在 tools 数组里找到对应 namespace → 收集它下面每个子工具的 name → 输出名字列表;找不到就输出空列表。
调用关系:它服务于动态工具过滤测试,帮助确认隐藏工具没有混进请求,只剩下应该可见的工具。
调用图:外部调用 1 个(get)。
contains_defer_loading104–113 ↗
fn contains_defer_loading(value: &Value) -> bool
作用:递归检查一段 JSON 里是否藏着 defer_loading=true。它像在一棵 JSON 树里逐层翻找某个开关。
数据流:输入任意 JSON 值 → 如果是对象就看自身字段并继续看子值,如果是数组就逐个检查 → 输出 true 或 false;不改动输入。
调用关系:它是 assert_tools_payload_does_not_defer 的底层检查器,专门处理工具声明可能嵌套很深的情况。
canonical_json115–130 ↗
fn canonical_json(value: &Value) -> Value
作用:把 JSON 对象的键按固定顺序排好,方便测试比较。这样同样内容不会因为字段顺序不同而被误判不一样。
数据流:输入任意 JSON 值 → 如果是对象就按键名排序并递归处理子值,如果是数组就逐个递归处理 → 输出一个内容相同但顺序稳定的新 JSON。
调用关系:它被 assert_remote_manual_compact_request_parity 用来比较普通请求和压缩请求的公共字段,避免 JSON 字段顺序影响断言结果。
调用图:被 1 处调用(assert_remote_manual_compact_request_parity);外部调用 3 个(Array, Object, clone)。
summary_with_prefix137–139 ↗
fn summary_with_prefix(summary: &str) -> String
作用:给摘要文字加上系统约定的摘要前缀。这个前缀用来标明“下面这段是压缩摘要”。
数据流:输入一段摘要文本 → 在前面拼上 SUMMARY_PREFIX 和换行 → 输出拼好的摘要字符串。
调用关系:多个快照测试用它生成远端压缩返回内容,确保测试数据长得像真实压缩摘要。
调用图:被 3 处调用(snapshot_request_shape_remote_mid_turn_continuation_compaction, snapshot_request_shape_remote_pre_turn_compaction_including_incoming_user_message, snapshot_request_shape_remote_pre_turn_compaction_strips_incoming_model_switch);外部调用 1 个(format!)。
context_snapshot_options141–145 ↗
fn context_snapshot_options() -> ContextSnapshotOptions
作用:统一设置测试快照的显示方式,让快照更短、更稳定。快照就是把请求形状保存下来,之后对比有没有变。
数据流:不接收输入 → 从默认选项开始,去掉能力说明,并设置只显示类型和短文本前缀 → 输出这套快照选项。
调用关系:format_labeled_requests_snapshot 会调用它,所有相关快照测试因此使用同一套渲染规则。
调用图:调用 1 个内部函数(default);被 1 处调用(format_labeled_requests_snapshot)。
format_labeled_requests_snapshot147–156 ↗
fn format_labeled_requests_snapshot(
scenario: &str,
sections: &[(&str, &responses::ResponsesRequest)],
) -> String
作用:把多个带标签的请求整理成一段适合保存为快照的文字。这样测试失败时,人能直接看出哪个阶段的请求变了。
数据流:输入场景说明和若干“标签加请求” → 套用统一快照选项 → 输出格式化后的快照文本。
调用关系:许多 snapshot_request_shape 开头的测试会用它生成快照。它把具体格式化工作交给 context_snapshot 模块,并使用 context_snapshot_options。
调用图:调用 2 个内部函数(format_labeled_requests_snapshot, context_snapshot_options)。
compacted_summary_only_output158–163 ↗
fn compacted_summary_only_output(summary: &str) -> Vec<ResponseItem>
作用:构造一个只包含压缩摘要项的模型返回结果。它让测试可以快速模拟“远端只返回摘要,没有别的消息”。
数据流:输入摘要文字 → 加上摘要前缀,包装成 ResponseItem::Compaction → 输出只有这一项的列表。
调用关系:多处压缩和实时会话测试用它准备假的远端返回,避免每个测试重复写同样的 JSON 结构。
调用图:外部调用 1 个(vec!)。
test_codex165–169 ↗
fn test_codex() -> TestCodexBuilder
作用:创建本文件常用的 Codex 测试构建器,并默认关掉 RemoteCompactionV2 功能。这样大多数测试先覆盖老版远程压缩路径。
数据流:不接收输入 → 调用基础测试构建器,然后修改配置关闭指定功能开关 → 输出可继续定制的 TestCodexBuilder。
调用关系:几乎所有测试都从它开始搭测试环境;需要新版压缩的测试会在它基础上再打开 RemoteCompactionV2。
调用图:调用 1 个内部函数(test_codex);被 31 处调用(assert_remote_manual_compact_request_parity, auto_remote_compact_failure_stops_agent_loop, auto_remote_compact_trims_function_call_history_to_fit_context_window, remote_compact_and_resume_refresh_stale_developer_instructions, remote_compact_filters_deferred_dynamic_tools, remote_compact_persists_replacement_history_in_rollout, remote_compact_refreshes_stale_developer_instructions_without_resume, remote_compact_replaces_history_for_followups, remote_compact_rewrites_multiple_trailing_function_call_outputs, remote_compact_runs_automatically (+15 more))。
remote_realtime_test_codex_builder171–180 ↗
fn remote_realtime_test_codex_builder(
realtime_server: &responses::WebSocketTestServer,
) -> TestCodexBuilder
作用:创建带实时 WebSocket 地址的测试构建器。它用于测试远程压缩和实时会话同时存在时,请求历史怎么写。
数据流:输入一个假的 WebSocket 测试服务器 → 读取它的地址,创建带 API key 的 Codex 测试构建器,并把实时服务地址写进配置 → 输出构建器。
调用关系:多个 realtime 相关快照测试会先启动 start_remote_realtime_server,再用这个函数配置 Codex。
调用图:调用 3 个内部函数(uri, test_codex, from_api_key);被 6 处调用(remote_request_uses_custom_experimental_realtime_start_instructions, snapshot_request_shape_remote_compact_resume_restates_realtime_end, snapshot_request_shape_remote_manual_compact_restates_realtime_start, snapshot_request_shape_remote_mid_turn_compaction_does_not_restate_realtime_end, snapshot_request_shape_remote_pre_turn_compaction_restates_realtime_end, snapshot_request_shape_remote_pre_turn_compaction_restates_realtime_start)。
start_remote_realtime_server182–200 ↗
async fn start_remote_realtime_server() -> responses::WebSocketTestServer
作用:启动一个假的实时会话服务器,模拟后端发来 session.updated 等消息。这样测试不用连真实网络服务。
数据流:不接收输入 → 准备一串 WebSocket 脚本响应,让连接启动后保持打开 → 输出 WebSocketTestServer。
调用关系:实时相关测试先调用它,再把返回的服务器交给 remote_realtime_test_codex_builder,最后测试结束时关闭服务器。
调用图:调用 1 个内部函数(start_websocket_server);被 6 处调用(remote_request_uses_custom_experimental_realtime_start_instructions, snapshot_request_shape_remote_compact_resume_restates_realtime_end, snapshot_request_shape_remote_manual_compact_restates_realtime_start, snapshot_request_shape_remote_mid_turn_compaction_does_not_restate_realtime_end, snapshot_request_shape_remote_pre_turn_compaction_restates_realtime_end, snapshot_request_shape_remote_pre_turn_compaction_restates_realtime_start);外部调用 1 个(vec!)。
start_realtime_conversation202–240 ↗
async fn start_realtime_conversation(codex: &codex_core::CodexThread) -> Result<()>
作用:在测试里的 Codex 线程上启动一次实时会话,并等到确实启动成功。实时会话这里指通过 WebSocket 维持的音频/交互会话。
数据流:输入 CodexThread → 提交 RealtimeConversationStart 操作,等待“已开始”和 session 更新事件 → 成功返回 Ok,失败会把错误传出来。
调用关系:实时压缩测试在发送普通用户消息前调用它。它把启动请求交给 codex.submit,并用 wait_for_event_match 等待系统事件。
调用图:调用 1 个内部函数(submit);被 6 处调用(remote_request_uses_custom_experimental_realtime_start_instructions, snapshot_request_shape_remote_compact_resume_restates_realtime_end, snapshot_request_shape_remote_manual_compact_restates_realtime_start, snapshot_request_shape_remote_mid_turn_compaction_does_not_restate_realtime_end, snapshot_request_shape_remote_pre_turn_compaction_restates_realtime_end, snapshot_request_shape_remote_pre_turn_compaction_restates_realtime_start);外部调用 2 个(wait_for_event_match, RealtimeConversationStart)。
close_realtime_conversation242–250 ↗
async fn close_realtime_conversation(codex: &codex_core::CodexThread) -> Result<()>
作用:关闭测试中的实时会话,并等系统确认关闭。它保证后续断言是在“实时已结束”的状态下做的。
数据流:输入 CodexThread → 提交 RealtimeConversationClose 操作,等待关闭事件 → 返回 Ok,不直接产生业务数据。
调用关系:实时相关测试在中途或结尾调用它,用来覆盖“实时仍活跃”和“实时已关闭”两种历史重述行为。
调用图:调用 1 个内部函数(submit);被 6 处调用(remote_request_uses_custom_experimental_realtime_start_instructions, snapshot_request_shape_remote_compact_resume_restates_realtime_end, snapshot_request_shape_remote_manual_compact_restates_realtime_start, snapshot_request_shape_remote_mid_turn_compaction_does_not_restate_realtime_end, snapshot_request_shape_remote_pre_turn_compaction_restates_realtime_end, snapshot_request_shape_remote_pre_turn_compaction_restates_realtime_start);外部调用 1 个(wait_for_event_match)。
assert_request_contains_realtime_start252–262 ↗
fn assert_request_contains_realtime_start(request: &responses::ResponsesRequest)
作用:检查某次模型请求里重新说明了实时会话已经开始。它防止压缩后系统忘记把实时会话背景告诉模型。
数据流:输入一个 ResponsesRequest → 转成 JSON 字符串,检查包含 realtime 包裹标记、不包含 inactive 原因 → 不满足就让测试失败。
调用关系:实时仍活跃时的预轮压缩和手动压缩快照测试会调用它,确认后续请求补上了实时开始说明。
调用图:调用 1 个内部函数(body_json);被 2 处调用(snapshot_request_shape_remote_manual_compact_restates_realtime_start, snapshot_request_shape_remote_pre_turn_compaction_restates_realtime_start);外部调用 1 个(assert!)。
assert_request_contains_custom_realtime_start264–281 ↗
fn assert_request_contains_custom_realtime_start(
request: &responses::ResponsesRequest,
instructions: &str,
)
作用:检查请求里使用了用户配置的自定义实时开始说明,而不是默认文案。
数据流:输入请求和期望的说明文字 → 查看请求 JSON 是否含实时包裹、含自定义说明、不含默认开始文案 → 不满足就失败。
调用关系:remote_request_uses_custom_experimental_realtime_start_instructions 专门调用它,验证实验配置生效。
调用图:调用 1 个内部函数(body_json);被 1 处调用(remote_request_uses_custom_experimental_realtime_start_instructions);外部调用 1 个(assert!)。
assert_request_contains_realtime_end283–293 ↗
fn assert_request_contains_realtime_end(request: &responses::ResponsesRequest)
作用:检查某次模型请求里重新说明了实时会话已经结束。这样模型不会误以为实时音频会话还在继续。
数据流:输入一个 ResponsesRequest → 转成 JSON 字符串,检查包含 realtime 包裹和 inactive 原因 → 不符合就让测试失败。
调用关系:实时关闭后的压缩、恢复和中途压缩测试会调用它,确认历史里该补结束说明时确实补了。
调用图:调用 1 个内部函数(body_json);被 3 处调用(snapshot_request_shape_remote_compact_resume_restates_realtime_end, snapshot_request_shape_remote_mid_turn_compaction_does_not_restate_realtime_end, snapshot_request_shape_remote_pre_turn_compaction_restates_realtime_end);外部调用 1 个(assert!)。
wait_for_turn_complete295–302 ↗
async fn wait_for_turn_complete(codex: &codex_core::CodexThread)
作用:等待一次 Codex 回合结束,最多等固定时间。回合就是用户发一句话到系统处理完这一轮。
数据流:输入 CodexThread → 监听事件,直到看到 TurnComplete,或超过 30 秒超时 → 没有显式返回业务数据。
调用关系:大量测试在每次提交用户输入、手动压缩后调用它,保证下一步断言发生在系统已经处理完成之后。
调用图:被 11 处调用(assert_remote_manual_compact_request_parity, remote_compact_filters_deferred_dynamic_tools, remote_compact_replaces_history_for_followups, remote_compact_trims_tool_search_output_to_empty_tools_array, remote_compact_v2_accepts_additional_output_items_before_compaction, remote_compact_v2_retries_failures_with_stream_retry_budget, remote_compact_v2_reuses_compaction_trigger_for_followups, remote_mid_turn_compact_v1_sends_turn_state_over_http, remote_mid_turn_compact_v2_sends_turn_state_over_http, remote_mid_turn_compact_v2_sends_turn_state_over_websocket (+1 more));外部调用 1 个(wait_for_event_with_timeout)。
remote_compact_replaces_history_for_followups305–530 ↗
async fn remote_compact_replaces_history_for_followups() -> Result<()>
作用:测试手动远程压缩后,后续请求会用远端返回的压缩历史替换旧聊天历史。它重点防止旧消息继续泄漏到新请求里。
数据流:搭建假服务和 Codex → 发送一次用户消息,执行 Compact,再发送后续消息 → 检查压缩请求的头、元数据、工具字段,以及后续请求只含压缩项和新用户消息。
调用关系:测试框架直接运行它。它使用 test_codex 建环境,用 mount_sse_sequence 和 mount_compact_json_once 模拟后端,并用 wait_for_turn_complete 串起每一步。
调用图:调用 6 个内部函数(mount_compact_json_once, mount_sse_sequence, with_builder, test_codex, wait_for_turn_complete, create_dummy_chatgpt_auth_for_testing);外部调用 9 个(default, assert!, assert_eq!, assert_ne!, assert_snapshot!, from_str, json!, skip_if_no_network!, vec!)。
assert_remote_manual_compact_request_parity532–765 ↗
async fn assert_remote_manual_compact_request_parity(
auth: CodexAuth,
configured_service_tier: Option<ServiceTier>,
expected_service_tier: Option<&str>,
snapshot_name: &str,
scena
作用:复用一套复杂场景,检查手动远程压缩请求和普通模型请求的公共参数是否一致。比如工具、推理设置、prompt_cache_key,以及不同认证下 service_tier 的处理。
数据流:输入认证方式、服务档位预期、快照名和场景说明 → 跑五轮包含文本、图片、工具、shell 的对话,再手动压缩 → 输出测试结果,主要通过断言和快照确认请求形状。
调用关系:两个服务档位测试调用它。它内部使用 test_codex、假 SSE 响应、假 compact 响应、canonical_json 和 wait_for_turn_complete 来完成对比。
调用图:调用 6 个内部函数(mount_compact_user_history_with_summary_once, mount_sse_sequence, with_builder, canonical_json, test_codex, wait_for_turn_complete);被 2 处调用(remote_manual_compact_api_auth_omits_service_tier_and_reuses_prompt_cache_key, remote_manual_compact_chatgpt_auth_reuses_service_tier_and_prompt_cache_key);外部调用 4 个(default, assert_eq!, assert_snapshot!, vec!)。
remote_manual_compact_api_auth_omits_service_tier_and_reuses_prompt_cache_key768–782 ↗
async fn remote_manual_compact_api_auth_omits_service_tier_and_reuses_prompt_cache_key() -> Result<()>
作用:测试 API key 认证时,手动远程压缩不会带 service_tier,但会复用 prompt_cache_key。prompt_cache_key 可以理解成后端缓存提示词的钥匙。
数据流:无业务输入 → 配置 API key 和 Fast 档位,调用共享校验函数 → 通过断言确认压缩请求省略服务档位且缓存键一致。
调用关系:它是 assert_remote_manual_compact_request_parity 的一个具体用例,测试框架单独运行它。
调用图:调用 2 个内部函数(assert_remote_manual_compact_request_parity, from_api_key);外部调用 1 个(skip_if_no_network!)。
remote_manual_compact_chatgpt_auth_reuses_service_tier_and_prompt_cache_key785–799 ↗
async fn remote_manual_compact_chatgpt_auth_reuses_service_tier_and_prompt_cache_key() -> Result<()>
作用:测试 ChatGPT 认证时,手动远程压缩会带上对应的 service_tier,并复用 prompt_cache_key。
数据流:无业务输入 → 配置 ChatGPT 测试认证和 Fast 档位,调用共享校验函数 → 通过断言确认 service_tier 变成预期的 priority,缓存键也一致。
调用关系:它和 API key 用例形成对照,共用 assert_remote_manual_compact_request_parity 覆盖同一套请求形状。
调用图:调用 2 个内部函数(assert_remote_manual_compact_request_parity, create_dummy_chatgpt_auth_for_testing);外部调用 1 个(skip_if_no_network!)。
remote_compact_v2_reuses_compaction_trigger_for_followups802–937 ↗
async fn remote_compact_v2_reuses_compaction_trigger_for_followups() -> Result<()>
作用:测试新版远程压缩 V2 会通过普通 /v1/responses 发送 compaction_trigger,并在后续请求里保留真正的 compaction 结果。
数据流:开启 RemoteCompactionV2 → 发送用户消息、手动压缩、再发送后续消息 → 检查压缩请求带 beta 特性、元数据正确、触发项不含摘要内容,后续请求含远端返回的摘要。
调用关系:测试框架运行它。它使用 test_codex 开启功能开关,用 mount_sse_sequence 模拟三次响应,并靠 wait_for_turn_complete 等待每轮结束。
调用图:调用 5 个内部函数(mount_sse_sequence, with_builder, test_codex, wait_for_turn_complete, create_dummy_chatgpt_auth_for_testing);外部调用 6 个(default, assert!, assert_eq!, from_str, skip_if_no_network!, vec!)。
remote_compact_v2_retries_failures_with_stream_retry_budget940–1049 ↗
async fn remote_compact_v2_retries_failures_with_stream_retry_budget() -> Result<()>
作用:测试 V2 压缩遇到打开连接失败或流中断时,会按流式重试预算重试,并只采用成功那次的压缩结果。
数据流:配置普通请求不重试、流式请求可重试两次 → 模拟第一次 500、第二次流里没有完成、第三次成功 → 检查请求次数和后续历史只含重试成功的摘要。
调用关系:它覆盖 RemoteCompactionV2 的失败恢复路径,用 mount_response_sequence 精确安排每次后端响应。
调用图:调用 5 个内部函数(mount_response_sequence, with_builder, test_codex, wait_for_turn_complete, create_dummy_chatgpt_auth_for_testing);外部调用 5 个(default, assert!, assert_eq!, skip_if_no_network!, vec!)。
remote_compact_v2_accepts_additional_output_items_before_compaction1052–1139 ↗
async fn remote_compact_v2_accepts_additional_output_items_before_compaction() -> Result<()>
作用:测试 V2 压缩流里如果在 compaction 项之前夹杂普通输出,系统会忽略这些噪音,只采用 compaction 项。
数据流:开启 V2 → 模拟压缩响应先给一条助手消息,再给 compaction → 后续请求检查含摘要、不含那条无关助手消息。
调用关系:它验证压缩流解析的容错性,依赖 test_codex、mount_sse_sequence 和 wait_for_turn_complete。
调用图:调用 5 个内部函数(mount_sse_sequence, with_builder, test_codex, wait_for_turn_complete, create_dummy_chatgpt_auth_for_testing);外部调用 4 个(default, assert!, skip_if_no_network!, vec!)。
remote_compact_filters_deferred_dynamic_tools1142–1232 ↗
async fn remote_compact_filters_deferred_dynamic_tools() -> Result<()>
作用:测试远程压缩请求不会把延迟加载的动态工具暴露给模型。动态工具是运行时才挂上的工具,延迟加载工具应等搜索到后再展示。
数据流:创建一个可见工具和一个隐藏工具 → 启动带工具的线程,跑一次普通请求和一次压缩 → 检查两类请求工具列表相同,且只包含可见工具。
调用关系:它使用 assert_tools_payload_does_not_defer 和 namespace_child_tool_names 做具体检查,防止普通请求和压缩请求在工具可见性上不一致。
调用图:调用 8 个内部函数(mount_compact_json_once, mount_sse_once, sse, start_mock_server, assert_tools_payload_does_not_defer, test_codex, wait_for_turn_complete, create_dummy_chatgpt_auth_for_testing);外部调用 6 个(default, assert_eq!, json!, json!, skip_if_no_network!, vec!)。
remote_compact_runs_automatically1235–1362 ↗
async fn remote_compact_runs_automatically() -> Result<()>
作用:测试当模型返回的 token 用量超过阈值时,系统会自动触发远程压缩,而不是等用户手动输入 /compact。
数据流:模拟第一轮返回超大 token 用量和一个 shell 调用 → 系统自动压缩,再继续下一次模型请求 → 检查压缩事件、请求头、元数据、turn_id 和 window_id 的变化。
调用关系:它覆盖自动压缩主流程,使用 mount_sse_once、mount_compact_user_history_with_summary_once 和事件等待函数观察整个回合。
调用图:调用 6 个内部函数(mount_compact_user_history_with_summary_once, mount_sse_once, sse, with_builder, test_codex, create_dummy_chatgpt_auth_for_testing);外部调用 9 个(default, assert!, assert_eq!, assert_ne!, wait_for_event, wait_for_event_match, from_str, skip_if_no_network!, vec!)。
remote_compact_trims_function_call_history_to_fit_context_window1366–1487 ↗
async fn remote_compact_trims_function_call_history_to_fit_context_window() -> Result<()>
作用:测试手动远程压缩前,如果工具调用输出太长,系统会把尾部过大的工具输出改写成“已截断”提示,以塞进上下文窗口。
数据流:配置很小的上下文窗口 → 跑两轮 shell 调用,其中后一轮输出很长 → 手动压缩后检查旧调用完整保留,尾部调用的输出被替换成截断提示。
调用关系:它验证压缩请求构造时的历史裁剪策略,依赖假 SSE 响应和 compact mock;在 Windows 上被忽略。
调用图:调用 5 个内部函数(mount_compact_user_history_with_summary_once, mount_sse_sequence, with_builder, test_codex, create_dummy_chatgpt_auth_for_testing);外部调用 6 个(default, assert!, assert_eq!, wait_for_event, skip_if_no_network!, vec!)。
remote_compact_rewrites_multiple_trailing_function_call_outputs1491–1607 ↗
async fn remote_compact_rewrites_multiple_trailing_function_call_outputs() -> Result<()>
作用:测试多个并行的尾部工具调用输出都太大时,系统会逐个改写,而不是只处理第一个。
数据流:制造一个旧 shell 调用和两个并行的大输出 shell 调用 → 手动压缩 → 检查三个调用项都存在,两个尾部输出都变成截断提示。
调用关系:它补充单个工具输出裁剪测试,覆盖并行调用场景;同样依靠 test_codex 和模拟响应。
调用图:调用 5 个内部函数(mount_compact_user_history_with_summary_once, mount_sse_sequence, with_builder, test_codex, create_dummy_chatgpt_auth_for_testing);外部调用 6 个(default, assert!, assert_eq!, wait_for_event, skip_if_no_network!, vec!)。
auto_remote_compact_trims_function_call_history_to_fit_context_window1611–1757 ↗
async fn auto_remote_compact_trims_function_call_history_to_fit_context_window() -> Result<()>
作用:测试自动远程压缩时也会用同样的工具输出裁剪规则。也就是说,不管压缩是手动还是自动,过长输出都不能把请求撑爆。
数据流:先积累一段含长 shell 输出的历史,再发送触发自动压缩的用户消息 → 检查压缩请求保留必要用户边界和调用项,并把尾部长输出改成截断提示。
调用关系:它和手动裁剪测试对应,验证自动压缩路径没有漏掉同一套保护逻辑。
调用图:调用 5 个内部函数(mount_compact_user_history_with_summary_once, mount_sse_sequence, with_builder, test_codex, create_dummy_chatgpt_auth_for_testing);外部调用 6 个(default, assert!, assert_eq!, wait_for_event, skip_if_no_network!, vec!)。
remote_compact_trims_tool_search_output_to_empty_tools_array1760–1863 ↗
async fn remote_compact_trims_tool_search_output_to_empty_tools_array() -> Result<()>
作用:测试工具搜索结果太大时,远程压缩请求会保留 tool_search_output 这个事件,但把其中 tools 数组清空。
数据流:创建一个描述超长的延迟动态工具 → 模拟模型发起工具搜索 → 手动压缩后检查搜索输出项还在,但工具列表被改成空数组。
调用关系:它覆盖动态工具搜索和上下文裁剪交叉的情况,防止巨大工具描述拖垮压缩请求。
调用图:调用 7 个内部函数(mount_compact_user_history_with_summary_once, mount_sse_once, sse, start_mock_server, test_codex, wait_for_turn_complete, create_dummy_chatgpt_auth_for_testing);外部调用 7 个(default, assert!, format!, json!, Namespace, skip_if_no_network!, vec!)。
auto_remote_compact_failure_stops_agent_loop1866–1962 ↗
async fn auto_remote_compact_failure_stops_agent_loop() -> Result<()>
作用:测试自动压缩失败后,智能体循环会停下来,不会继续向模型发后续请求。这样可以避免在历史状态不可靠时继续执行。
数据流:先让 token 用量超过自动压缩阈值 → 下一轮触发压缩,但 mock 返回非法结构 → 等到错误事件,检查没有发送压缩后的模型请求。
调用关系:它用 mount_compact_json_once 制造解析失败,并通过快照确认失败时压缩请求不包含新进来的用户消息。
调用图:调用 6 个内部函数(mount_compact_json_once, mount_sse_once, sse, with_builder, test_codex, create_dummy_chatgpt_auth_for_testing);外部调用 9 个(default, assert!, assert_eq!, wait_for_event, wait_for_event_match, assert_snapshot!, json!, skip_if_no_network!, vec!)。
remote_compact_trim_estimate_uses_session_base_instructions1966–2184 ↗
async fn remote_compact_trim_estimate_uses_session_base_instructions() -> Result<()>
作用:测试裁剪压缩请求大小时,会把当前会话的基础指令也算进去。基础指令很长时,即使历史没那么长,也可能超出上下文窗口。
数据流:先跑一组基线请求并估算输入和总负载 token → 再创建带超长 base_instructions 的会话 → 手动压缩后检查请求使用新指令,并因此改写尾部工具输出。
调用关系:它直接使用 approx_token_count、estimate_compact_input_tokens 和 estimate_compact_payload_tokens 来设计测试边界,验证真实裁剪逻辑参考的是会话指令。
调用图:调用 8 个内部函数(mount_compact_user_history_with_summary_once, mount_sse_sequence, with_builder, approx_token_count, estimate_compact_input_tokens, estimate_compact_payload_tokens, test_codex, create_dummy_chatgpt_auth_for_testing);外部调用 7 个(default, assert!, assert_eq!, wait_for_event, format!, skip_if_no_network!, vec!)。
remote_manual_compact_emits_context_compaction_items2187–2265 ↗
async fn remote_manual_compact_emits_context_compaction_items() -> Result<()>
作用:测试手动远程压缩会发出新的上下文压缩 item started/completed 事件,同时还兼容旧的 ContextCompacted 事件。
数据流:跑一次普通对话后提交 Compact → 循环读取事件,直到看到压缩项开始、完成、旧事件和回合完成 → 检查开始和完成的是同一个 item。
调用关系:它面向事件协议,保证前端或上层调用者能用新旧两套事件感知压缩进度。
调用图:调用 6 个内部函数(mount_compact_user_history_with_summary_once, mount_sse_once, sse, with_builder, test_codex, create_dummy_chatgpt_auth_for_testing);外部调用 6 个(default, assert!, assert_eq!, wait_for_event, skip_if_no_network!, vec!)。
remote_manual_compact_failure_emits_task_error_event2268–2327 ↗
async fn remote_manual_compact_failure_emits_task_error_event() -> Result<()>
作用:测试手动远程压缩失败时,会向外发出清楚的错误事件,而不是静默失败。
数据流:先跑一次普通对话 → 压缩接口返回非法 payload → 等待 Error 事件并检查错误文案包含远程压缩任务和具体解析失败信息。
调用关系:它覆盖手动压缩错误上报路径,和自动失败测试一起保证失败不会被吞掉。
调用图:调用 6 个内部函数(mount_compact_json_once, mount_sse_once, sse, with_builder, test_codex, create_dummy_chatgpt_auth_for_testing);外部调用 8 个(default, assert!, assert_eq!, wait_for_event, wait_for_event_match, json!, skip_if_no_network!, vec!)。
remote_compact_persists_replacement_history_in_rollout2333–2465 ↗
async fn remote_compact_persists_replacement_history_in_rollout() -> Result<()>
作用:测试压缩后的替换历史会写入 rollout 文件。rollout 是会话落盘记录,用来之后恢复会话。
数据流:跑对话、手动压缩、关闭线程 → 读取 rollout 文件逐行解析 → 查找压缩记录里的 replacement_history 是否包含远端摘要和助手备注,并确认没有混入权限开发者消息。
调用关系:这个测试目前被忽略,因为注释说明相关行为要等后续改动。它仍记录了期望的持久化行为。
调用图:调用 6 个内部函数(mount_compact_json_once, mount_sse_once, sse, with_builder, test_codex, create_dummy_chatgpt_auth_for_testing);外部调用 8 个(default, assert!, assert_eq!, wait_for_event, read_to_string, json!, skip_if_no_network!, vec!)。
remote_compact_and_resume_refresh_stale_developer_instructions2468–2618 ↗
async fn remote_compact_and_resume_refresh_stale_developer_instructions() -> Result<()>
作用:测试远程压缩返回的旧开发者指令不会在后续请求和恢复会话后继续污染上下文。开发者指令是系统给模型的规则说明。
数据流:压缩返回里故意放入过期开发者消息和摘要 → 同会话继续一轮并关闭,再从 rollout 恢复后继续一轮 → 检查两次请求都没有旧指令,有新权限指令和压缩摘要。
调用关系:它覆盖“压缩后立即继续”和“落盘后恢复”两条路径,确保历史重建会刷新开发者上下文。
调用图:调用 4 个内部函数(mount_compact_json_once, mount_sse_sequence, test_codex, create_dummy_chatgpt_auth_for_testing);外部调用 8 个(default, assert!, assert_eq!, wait_for_event, json!, skip_if_no_network!, vec!, start)。
remote_compact_refreshes_stale_developer_instructions_without_resume2621–2716 ↗
async fn remote_compact_refreshes_stale_developer_instructions_without_resume() -> Result<()>
作用:测试不经过恢复会话,只在同一个运行会话里,压缩返回的旧开发者指令也会被清掉。
数据流:让压缩接口返回旧开发者消息和摘要 → 压缩后马上发下一条用户消息 → 检查下一次模型请求没有旧开发者消息,含新权限说明和摘要。
调用关系:它是恢复测试的简化版本,专门确认当前内存里的历史也会被刷新。
调用图:调用 4 个内部函数(mount_compact_json_once, mount_sse_sequence, test_codex, create_dummy_chatgpt_auth_for_testing);外部调用 8 个(default, assert!, assert_eq!, wait_for_event, json!, skip_if_no_network!, vec!, start)。
snapshot_request_shape_remote_pre_turn_compaction_restates_realtime_start2719–2808 ↗
async fn snapshot_request_shape_remote_pre_turn_compaction_restates_realtime_start() -> Result<()>
作用:测试预轮自动压缩发生在实时会话仍活跃时,压缩后的下一次请求会重新说明实时会话开始状态。
数据流:启动假的实时服务器和会话 → 第一轮使 token 超阈值,第二轮触发预轮压缩 → 检查后续请求含实时开始说明,并保存请求形状快照。
调用关系:它调用 start_remote_realtime_server、remote_realtime_test_codex_builder、start_realtime_conversation 和 assert_request_contains_realtime_start 组成完整实时场景。
调用图:调用 7 个内部函数(mount_compact_json_once, mount_sse_sequence, assert_request_contains_realtime_start, close_realtime_conversation, remote_realtime_test_codex_builder, start_realtime_conversation, start_remote_realtime_server);外部调用 8 个(default, assert_eq!, wait_for_event, assert_snapshot!, json!, skip_if_no_network!, vec!, start)。
remote_request_uses_custom_experimental_realtime_start_instructions2811–2858 ↗
async fn remote_request_uses_custom_experimental_realtime_start_instructions() -> Result<()>
作用:测试实验配置里的自定义实时开始说明会出现在请求里。这样产品可以替换默认文案。
数据流:配置 experimental_realtime_start_instructions → 启动实时会话并发一条用户消息 → 检查请求包含自定义说明,不包含默认开始说明。
调用关系:它使用 assert_request_contains_custom_realtime_start 做断言,并在结束时关闭实时会话和测试服务器。
调用图:调用 7 个内部函数(mount_sse_once, sse, assert_request_contains_custom_realtime_start, close_realtime_conversation, remote_realtime_test_codex_builder, start_realtime_conversation, start_remote_realtime_server);外部调用 5 个(default, wait_for_event, skip_if_no_network!, vec!, start)。
snapshot_request_shape_remote_pre_turn_compaction_restates_realtime_end2861–2951 ↗
async fn snapshot_request_shape_remote_pre_turn_compaction_restates_realtime_end() -> Result<()>
作用:测试实时会话在两轮之间关闭后,预轮自动压缩后的请求会重新说明实时会话已经结束。
数据流:启动实时会话,跑第一轮后关闭实时,再发第二轮触发预轮压缩 → 检查后续请求含 inactive 原因,并保存快照。
调用关系:它和实时开始测试形成对照,调用 assert_request_contains_realtime_end 验证结束说明。
调用图:调用 7 个内部函数(mount_compact_json_once, mount_sse_sequence, assert_request_contains_realtime_end, close_realtime_conversation, remote_realtime_test_codex_builder, start_realtime_conversation, start_remote_realtime_server);外部调用 8 个(default, assert_eq!, wait_for_event, assert_snapshot!, json!, skip_if_no_network!, vec!, start)。
snapshot_request_shape_remote_manual_compact_restates_realtime_start2954–3044 ↗
async fn snapshot_request_shape_remote_manual_compact_restates_realtime_start() -> Result<()>
作用:测试手动压缩发生时实时会话仍活跃,下一轮普通请求会补上实时开始说明。
数据流:启动实时会话,跑一轮,手动 Compact,再跑下一轮 → 检查压缩后的请求包含实时开始信息,并记录快照。
调用关系:它复用实时服务器和会话启动/关闭辅助函数,重点覆盖手动压缩而不是自动压缩。
调用图:调用 7 个内部函数(mount_compact_json_once, mount_sse_sequence, assert_request_contains_realtime_start, close_realtime_conversation, remote_realtime_test_codex_builder, start_realtime_conversation, start_remote_realtime_server);外部调用 8 个(default, assert_eq!, wait_for_event, assert_snapshot!, json!, skip_if_no_network!, vec!, start)。
snapshot_request_shape_remote_mid_turn_compaction_does_not_restate_realtime_end3047–3151 ↗
async fn snapshot_request_shape_remote_mid_turn_compaction_does_not_restate_realtime_end() -> Result<()>
作用:测试如果实时会话在本轮开始前已经关闭,并且本轮初始请求已经说明过结束状态,那么中途压缩后的续请求不应重复说明。
数据流:先启动再关闭实时会话 → 第二轮请求先带实时结束说明并触发工具调用,随后中途压缩 → 检查压缩后的续请求不再含 realtime 包裹。
调用关系:它验证“同一轮内已经建立过基线”这个细节,避免压缩续请求重复塞入实时结束消息。
调用图:调用 7 个内部函数(mount_compact_json_once, mount_sse_sequence, assert_request_contains_realtime_end, close_realtime_conversation, remote_realtime_test_codex_builder, start_realtime_conversation, start_remote_realtime_server);外部调用 9 个(default, assert!, assert_eq!, wait_for_event, assert_snapshot!, json!, skip_if_no_network!, vec!, start)。
snapshot_request_shape_remote_compact_resume_restates_realtime_end3154–3260 ↗
async fn snapshot_request_shape_remote_compact_resume_restates_realtime_end() -> Result<()>
作用:测试手动压缩后关闭程序再恢复会话时,如果实时会话之前已结束,恢复后的第一轮请求会重新说明结束状态。
数据流:启动实时会话、跑一轮、关闭实时、手动压缩、关线程并恢复 rollout → 发新用户消息 → 检查恢复后的请求含实时结束说明和压缩摘要。
调用关系:它串起实时会话、远程压缩、rollout 恢复三件事,使用 assert_request_contains_realtime_end 做关键断言。
调用图:调用 9 个内部函数(mount_compact_json_once, mount_sse_sequence, assert_request_contains_realtime_end, close_realtime_conversation, remote_realtime_test_codex_builder, start_realtime_conversation, start_remote_realtime_server, test_codex, create_dummy_chatgpt_auth_for_testing);外部调用 8 个(default, assert_eq!, wait_for_event, assert_snapshot!, json!, skip_if_no_network!, vec!, start)。
snapshot_request_shape_remote_pre_turn_compaction_including_incoming_user_message3264–3361 ↗
async fn snapshot_request_shape_remote_pre_turn_compaction_including_incoming_user_message() -> Result<()>
作用:记录预轮自动压缩当前的请求形状:压缩请求会带上下文差异,但当前行为排除了刚进来的用户消息。
数据流:跑三轮用户消息,第三轮前提交线程设置中的环境变更 → 第二轮后触发预轮压缩 → 快照检查压缩请求和后续请求,并确认第三条用户消息在后续请求里只出现一次。
调用关系:它调用 summary_with_prefix 和 local_selections 构造带上下文差异的场景;注释说明未来行为可能会调整。
调用图:调用 7 个内部函数(mount_compact_user_history_with_summary_once, mount_sse_sequence, with_builder, local_selections, summary_with_prefix, test_codex, create_dummy_chatgpt_auth_for_testing);外部调用 8 个(default, assert_eq!, submit_thread_settings, test_path_buf, wait_for_event, assert_snapshot!, skip_if_no_network!, vec!)。
snapshot_request_shape_remote_pre_turn_compaction_strips_incoming_model_switch3364–3498 ↗
async fn snapshot_request_shape_remote_pre_turn_compaction_strips_incoming_model_switch() -> Result<()>
作用:测试模型切换触发预轮压缩时,压缩请求会去掉本轮新加入的模型切换标记,但后续真正请求会带回来。
数据流:先用旧模型跑一轮 → 提交切到新模型的线程设置并发用户消息 → 触发预轮压缩 → 检查压缩请求不含新用户和 model_switch,后续请求含两者。
调用关系:它覆盖模型切换和压缩的交叉行为,用 summary_with_prefix 准备压缩摘要,并保存快照。
调用图:调用 7 个内部函数(mount_compact_user_history_with_summary_once, mount_sse_once, sse, with_builder, summary_with_prefix, test_codex, create_dummy_chatgpt_auth_for_testing);外部调用 8 个(default, assert!, assert_eq!, submit_thread_settings, wait_for_event, assert_snapshot!, skip_if_no_network!, vec!)。
snapshot_request_shape_remote_pre_turn_compaction_context_window_exceeded3503–3606 ↗
async fn snapshot_request_shape_remote_pre_turn_compaction_context_window_exceeded() -> Result<()>
作用:测试预轮远程压缩如果被后端判定超过上下文窗口,会把错误暴露出来并停止本轮,不继续采样。
数据流:第一轮让用量超过阈值 → 第二轮触发压缩,但 compact 接口返回 context_length_exceeded → 等待错误事件,确认没有后续模型请求,并保存压缩请求快照。
调用关系:它覆盖后端拒绝压缩请求的情况,和 auto_remote_compact_failure_stops_agent_loop 一起保证失败会阻断后续执行。
调用图:调用 7 个内部函数(mount_compact_response_once, mount_sse_once, mount_sse_sequence, sse, with_builder, test_codex, create_dummy_chatgpt_auth_for_testing);外部调用 10 个(default, new, assert!, assert_eq!, wait_for_event, wait_for_event_match, assert_snapshot!, json!, skip_if_no_network!, vec!)。
remote_pre_turn_compact_response_seeds_turn_state3609–3679 ↗
async fn remote_pre_turn_compact_response_seeds_turn_state() -> Result<()>
作用:测试预轮压缩响应里的 turn state 会传给随后同一轮第一次普通模型请求。turn state 是后端用来延续本轮状态的标记。
数据流:第一轮让用量超过阈值 → 第二轮压缩接口返回 x-codex-turn-state=compact-state → 检查压缩请求自己不带状态,但压缩后的模型请求带上 compact-state。
调用关系:它专门验证 HTTP 头在预轮压缩和后续采样之间的传递,用 TURN_STATE_HEADER 常量做断言。
调用图:调用 6 个内部函数(mount_compact_response_once, mount_response_sequence, with_builder, test_codex, wait_for_turn_complete, create_dummy_chatgpt_auth_for_testing);外部调用 6 个(default, new, assert_eq!, json!, skip_if_no_network!, vec!)。
remote_mid_turn_compact_v1_sends_turn_state_over_http3682–3762 ↗
async fn remote_mid_turn_compact_v1_sends_turn_state_over_http() -> Result<()>
作用:测试旧版中途压缩通过 HTTP 时,会把本轮第一次采样得到的 turn state 传给压缩请求和后续请求。
数据流:第一段模型响应返回 sampling-state 并触发中途压缩 → compact 请求返回 compact-state,但系统继续使用最早的 sampling-state → 检查压缩和后续两次请求都带 sampling-state。
调用关系:它覆盖 V1 /v1/responses/compact 路径,验证状态一旦建立就不被后面响应覆盖。
调用图:调用 6 个内部函数(mount_compact_response_once, mount_response_sequence, with_builder, test_codex, wait_for_turn_complete, create_dummy_chatgpt_auth_for_testing);外部调用 6 个(default, new, assert_eq!, json!, skip_if_no_network!, vec!)。
remote_mid_turn_compact_v2_sends_turn_state_over_http3765–3857 ↗
async fn remote_mid_turn_compact_v2_sends_turn_state_over_http() -> Result<()>
作用:测试新版中途压缩 V2 走 HTTP /v1/responses 时,也会传递同一个 turn state。
数据流:开启 V2 → 第一次采样得到 sampling-state 并触发 compaction_trigger 请求 → 后续请求都检查带 sampling-state,且路径都是 /v1/responses。
调用关系:它和 V1 状态测试对应,但覆盖 V2 内联压缩方式。
调用图:调用 5 个内部函数(mount_response_sequence, with_builder, test_codex, wait_for_turn_complete, create_dummy_chatgpt_auth_for_testing);外部调用 5 个(default, assert!, assert_eq!, skip_if_no_network!, vec!)。
remote_mid_turn_compact_v2_sends_turn_state_over_websocket3860–3955 ↗
async fn remote_mid_turn_compact_v2_sends_turn_state_over_websocket() -> Result<()>
作用:测试新版中途压缩 V2 走 WebSocket 时,turn state 会通过 client_metadata 发回后端。
数据流:启动脚本化 WebSocket 服务 → 先预热,再采样得到 sampling-state,随后触发 V2 压缩和续请求 → 检查每个 WebSocket 请求的 client_metadata 状态序列符合预期。
调用关系:它覆盖 WebSocket 传输路径,不使用 HTTP mock;结束时关闭测试 WebSocket 服务器。
调用图:调用 4 个内部函数(start_websocket_server, test_codex, wait_for_turn_complete, create_dummy_chatgpt_auth_for_testing);外部调用 5 个(default, assert!, assert_eq!, skip_if_no_network!, vec!)。
snapshot_request_shape_remote_mid_turn_continuation_compaction3958–4027 ↗
async fn snapshot_request_shape_remote_mid_turn_continuation_compaction() -> Result<()>
作用:记录中途自动压缩的请求形状:工具调用后触发压缩,压缩请求包含工具痕迹,续请求包含返回的摘要项。
数据流:发送用户消息 → 模型先返回函数调用并报告高 token → 系统远程压缩后继续请求最终回答 → 保存压缩请求和续请求的快照。
调用关系:它用 summary_with_prefix 构造摘要,用快照保护中途压缩时工具历史的布局。
调用图:调用 6 个内部函数(mount_compact_user_history_with_summary_once, mount_sse_sequence, with_builder, summary_with_prefix, test_codex, create_dummy_chatgpt_auth_for_testing);外部调用 6 个(default, assert_eq!, wait_for_event, assert_snapshot!, skip_if_no_network!, vec!)。
snapshot_request_shape_remote_mid_turn_compaction_summary_only_reinjects_context4030–4114 ↗
async fn snapshot_request_shape_remote_mid_turn_compaction_summary_only_reinjects_context() -> Result<()>
作用:测试中途压缩返回只有摘要项时,系统会在续请求里把必要上下文重新插到摘要前面。
数据流:模型先返回工具调用并触发中途压缩 → compact 接口只返回一个 compaction 项 → 检查续请求快照中上下文被重新注入,而不是只剩摘要和后续内容。
调用关系:它覆盖远端返回内容很精简的情况,防止续请求丢掉运行所需的系统上下文。
调用图:调用 6 个内部函数(mount_compact_json_once, mount_sse_once, sse, with_builder, test_codex, create_dummy_chatgpt_auth_for_testing);外部调用 7 个(default, assert_eq!, wait_for_event, assert_snapshot!, json!, skip_if_no_network!, vec!)。
snapshot_request_shape_remote_mid_turn_compaction_multi_summary_reinjects_above_last_summary4117–4227 ↗
async fn snapshot_request_shape_remote_mid_turn_compaction_multi_summary_reinjects_above_last_summary() -> Result<()>
作用:测试已经有旧压缩摘要时,再次中途压缩会保留旧摘要,并把新上下文插在最新摘要之前的正确位置。
数据流:第一轮后手动压缩生成旧摘要 → 第二轮触发自动中途压缩生成新摘要 → 检查第二次压缩请求带旧摘要,后续请求的布局符合快照。
调用关系:它使用 mount_compact_user_history_with_summary_sequence 模拟连续两次压缩,保护多摘要历史的排列规则。
调用图:调用 6 个内部函数(mount_compact_user_history_with_summary_sequence, mount_sse_once, sse, with_builder, test_codex, create_dummy_chatgpt_auth_for_testing);外部调用 7 个(default, assert!, assert_eq!, wait_for_event, assert_snapshot!, skip_if_no_network!, vec!)。
snapshot_request_shape_remote_manual_compact_without_previous_user_messages4230–4285 ↗
async fn snapshot_request_shape_remote_manual_compact_without_previous_user_messages() -> Result<()>
作用:测试没有任何历史用户消息时执行手动压缩,不会真的发远程压缩请求。空聊天没必要压缩。
数据流:新会话直接提交 Compact → 再发送第一条用户消息 → 检查 compact mock 没收到请求,并保存首次正常请求的快照。
调用关系:它覆盖空历史边界条件,确保 /compact 不会制造无意义远程调用。
调用图:调用 6 个内部函数(mount_compact_json_once, mount_sse_once, sse, with_builder, test_codex, create_dummy_chatgpt_auth_for_testing);外部调用 7 个(default, assert_eq!, wait_for_event, assert_snapshot!, json!, skip_if_no_network!, vec!)。
core/tests/suite/responses_lite.rs源码 ↗
这份测试文件像一组出厂质检清单。它不会真的去验证模型聪不聪明,而是启动一个假的服务器,假装自己是 OpenAI 接口,然后让 Codex 往它发请求,再检查请求长什么样。重点是 Responses Lite:这是一种更轻的传输约定,请求里会带一个特殊请求头,并且某些工具不能再用“托管工具”(也就是服务端内置工具),而要改成本地扩展提供的独立工具。文件还检查图片输入的细节字段会不会被正确删掉,避免把 Lite 模式不支持或不该带的内容发出去。几个小辅助函数负责搭好测试用扩展、改配置、让模型声明自己能看图,以及判断请求里有没有某类托管工具。整体上,它保证新旧模式分得清:Lite 模式走 Lite 的规则,非 Lite 模式仍走原来的托管工具规则。
responses_extensions27–33 ↗
fn responses_extensions(auth: &CodexAuth) -> Arc<ExtensionRegistry<Config>>
作用:这个函数给测试临时装上“网页搜索”和“图片生成”两个扩展。可以把它理解成给测试版 Codex 插上两块工具插件,这样后面的测试才能确认 Lite 模式会不会使用这些独立工具。
数据流:进去的是一个测试用登录身份 CodexAuth → 函数先用这个身份做出认证管理器,再创建扩展注册表构建器,把网页搜索扩展和图片生成扩展装进去 → 出来的是一个可共享的扩展注册表 Arc,测试里的 Codex 会拿它来知道自己有哪些工具可用。
调用关系:它被 responses_lite_uses_standalone_web_search_and_image_generation 和 non_lite_uses_hosted_tools_when_standalone_features_are_disabled 调用。前者用它确认 Lite 模式会暴露独立工具;后者也装上扩展,但用来证明非 Lite 模式在特定配置下仍然选择托管工具,而不是这些独立扩展。
调用图:调用 1 个内部函数(auth_manager_from_auth);被 2 处调用(non_lite_uses_hosted_tools_when_standalone_features_are_disabled, responses_lite_uses_standalone_web_search_and_image_generation);外部调用 6 个(clone, new, new, install, install, clone)。
configure_responses_tools35–45 ↗
fn configure_responses_tools(config: &mut Config)
作用:这个函数把测试配置调成一个特定状态:开启真实网页搜索,开启图片生成,同时关掉某些独立或新版开关。这样测试可以稳定地比较 Lite 和非 Lite 模式的行为差异。
数据流:进去的是一份可修改的 Config 配置 → 函数修改网页搜索模式和功能开关,并用 assert 确认这些修改都成功 → 出来没有返回值,但这份配置已经被改好,后续构建测试 Codex 时会按这些开关运行。
调用关系:它通常作为 test_codex 构建器的配置回调使用。多个测试把它交给构建器,让同一套工具开关在不同模型模式下产生不同请求,从而验证 Responses Lite 的特殊规则是否生效。
调用图:外部调用 1 个(assert!)。
configure_image_capable_model47–49 ↗
fn configure_image_capable_model(model_info: &mut codex_protocol::openai_models::ModelInfo)
作用:这个函数把测试模型标记成既能读文字也能看图片。没有这个标记,图片相关测试就可能还没测到请求格式,先因为模型不支持图片而失败。
数据流:进去的是一份可修改的模型信息 ModelInfo → 函数把它的输入能力列表设成 Text 和 Image → 出来没有返回值,但模型信息已经声明自己支持图片输入。
调用关系:它被多个测试通过模型信息覆盖回调使用。有些测试还会额外把 use_responses_lite 打开;这个函数只负责“模型能看图”这一件事,让图片输入和图片生成相关检查有前提条件。
调用图:外部调用 1 个(vec!)。
has_hosted_tool51–55 ↗
fn has_hosted_tool(tools: &[Value], tool_type: &str) -> bool
作用:这个函数检查一份工具列表里有没有某种“托管工具”。托管工具可以理解成接口服务端自带的工具,比如 web_search 或 image_generation,而不是本地扩展注册出来的工具。
数据流:进去的是 JSON 工具数组和要找的工具类型字符串 → 函数逐个看每个工具的 type 字段是否等于目标类型 → 出来是 true 或 false,表示请求里有没有这种托管工具。
调用关系:它被各个测试用在断言里。Lite 模式相关测试用它确认不该出现的托管工具确实没出现;非 Lite 测试用它确认原来的托管工具确实还在。
调用图:外部调用 1 个(iter)。
responses_lite_strips_data_image_detail_without_resize_all_images58–111 ↗
async fn responses_lite_strips_data_image_detail_without_resize_all_images() -> Result<()>
作用:这个测试确认:在 Responses Lite 模式下,用户传入 data: 开头的内嵌图片时,请求里只保留图片地址,不带 image detail 这类额外字段。这样可以避免 Lite 接口收到它不需要或不接受的图片细节设置。
数据流:开始时它启动假服务器,并准备一段假的流式响应 → 然后创建一个开启 Responses Lite、并且声明支持图片的测试 Codex → 接着提交一张 base64 内嵌图片,原始输入里带了 detail: Original → 最后读取假服务器收到的请求,确认里面的 input_image 只有 type 和 image_url,没有 detail 字段。
调用关系:它用 start_mock_server 和 mount_sse_once 搭出假接口,用 test_codex 构建被测对象,用 wait_for_event 等待一轮对话结束。它不调用本文件的其他辅助函数,除了间接通过模型覆盖设置图片能力;核心关系是“提交图片输入 → 捕获发出的请求 → 检查 Lite 格式”。
调用图:调用 4 个内部函数(mount_sse_once, sse, start_mock_server, test_codex);外部调用 5 个(default, assert_eq!, wait_for_event, skip_if_no_network!, vec!)。
responses_lite_uses_standalone_web_search_and_image_generation114–162 ↗
async fn responses_lite_uses_standalone_web_search_and_image_generation() -> Result<()>
作用:这个测试确认:Responses Lite 模式下,如果装了独立的网页搜索和图片生成扩展,请求会暴露这些独立工具,而不是使用服务端托管工具。
数据流:开始时它启动假服务器并准备成功响应 → 创建测试登录身份,再通过 responses_extensions 装好两个扩展 → 构建一个开启 Responses Lite、支持图片、并按 configure_responses_tools 调整过开关的 Codex → 提交一轮普通文字输入 → 最后检查发出的请求:请求头里有 Lite 标记,工具里有 web/run 和 image_gen/imagegen,同时 JSON 的 tools 里没有 web_search 和 image_generation 这两种托管工具。
调用关系:它把 responses_extensions、configure_responses_tools、configure_image_capable_model 组合起来使用。流程上,它是 Lite 工具路线的正向测试:先把独立扩展装好,再证明 Codex 真把活交给独立扩展,而不是交给托管工具。
调用图:调用 6 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, responses_extensions, create_dummy_chatgpt_auth_for_testing);外部调用 4 个(assert!, assert_eq!, skip_if_no_network!, vec!)。
responses_lite_compact_request_uses_lite_transport_contract165–217 ↗
async fn responses_lite_compact_request_uses_lite_transport_contract() -> Result<()>
作用:这个测试确认:对话压缩请求在 Responses Lite 模式下也必须遵守 Lite 的传输约定。对话压缩就是把较长聊天整理成更短上下文,方便后续继续用。
数据流:开始时它启动假服务器,分别挂上普通响应接口和压缩接口 → 构建一个开启 Responses Lite、支持并行工具调用的测试模型,同时关掉 RemoteCompactionV2 功能 → 先提交一轮对话,再提交 Compact 操作 → 等待完成后读取压缩请求,确认它带有 Lite 请求头,reasoning.context 是 all_turns,并且 parallel_tool_calls 被明确设为 false。
调用关系:它通过 test_codex 驱动真实的提交和压缩流程,通过 mount_compact_json_once 捕获压缩接口请求。它不依赖本文件的扩展辅助函数,重点是在压缩这条旁路流程上验证同一个 Lite 请求契约没有被漏掉。
调用图:调用 5 个内部函数(mount_compact_json_once, mount_sse_once, sse, start_mock_server, test_codex);外部调用 5 个(assert_eq!, wait_for_event, json!, skip_if_no_network!, vec!)。
responses_lite_omits_hosted_tools_without_standalone_extensions220–252 ↗
async fn responses_lite_omits_hosted_tools_without_standalone_extensions() -> Result<()>
作用:这个测试确认:Responses Lite 模式下,即使配置打开了网页搜索和图片生成,如果没有安装对应的独立扩展,也不能退回去塞入托管工具。
数据流:开始时它启动假服务器并准备流式响应 → 构建一个带测试登录身份、开启 Responses Lite、支持图片、并用 configure_responses_tools 调过开关的 Codex,但刻意不调用 responses_extensions 安装扩展 → 提交一轮输入 → 最后检查请求里的 tools 数组,确认没有 web_search,也没有 image_generation。
调用关系:它和 responses_lite_uses_standalone_web_search_and_image_generation 形成对照:一个装扩展,所以应出现独立工具;这个不装扩展,所以既不应出现独立工具,也不应偷偷改用托管工具。它用 has_hosted_tool 完成最终检查。
调用图:调用 5 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, create_dummy_chatgpt_auth_for_testing);外部调用 3 个(assert!, skip_if_no_network!, vec!)。
non_lite_uses_hosted_tools_when_standalone_features_are_disabled255–291 ↗
async fn non_lite_uses_hosted_tools_when_standalone_features_are_disabled() -> Result<()>
作用:这个测试确认:没有开启 Responses Lite 时,系统仍然按老规则使用服务端托管的网页搜索和图片生成工具。它防止 Lite 模式的改动误伤普通模式。
数据流:开始时它启动假服务器并准备响应 → 创建测试登录身份,用 responses_extensions 装好独立扩展 → 构建一个不启用 use_responses_lite、但支持图片并经过 configure_responses_tools 调整的 Codex → 提交一轮输入 → 最后检查请求:没有 Lite 请求头,没有独立工具 web/run 和 image_gen/imagegen,但 tools 里有 web_search 和 image_generation。
调用关系:它是整个文件里的反例测试。前几个测试证明 Lite 要走独立工具或省略托管工具;这个测试证明非 Lite 不能被改成同样行为。它会调用 responses_extensions 准备扩展环境,但最终断言 Codex 没有选择这些独立扩展,而是走托管工具。
调用图:调用 6 个内部函数(mount_sse_once, sse, start_mock_server, test_codex, responses_extensions, create_dummy_chatgpt_auth_for_testing);外部调用 4 个(assert!, assert_eq!, skip_if_no_network!, vec!)。
core/tests/suite/turn_state.rs源码 ↗
可以把一轮对话想成一次办事流程,中间可能要让模型先想、再调用 shell 命令、再继续回答。服务器有时会发回一个特殊值,放在 x-codex-turn-state 里,意思是“这轮后续请求请带着这个小纸条回来”。这个文件测试的就是这张“小纸条”有没有按规矩使用:第一,请求一开始不应该自带它;第二,同一轮里如果因为工具调用产生了后续请求,就要把它带回去;第三,进入下一轮新对话时必须清空,不能串场。文件同时覆盖两种通信方式:普通的响应流,也就是 SSE(服务器一条条推送事件),以及 WebSocket(客户端和服务器保持一条长连接互发消息)。最后一个测试还确认:如果同一轮中服务器后来又发了新的状态值,客户端仍然坚持使用本轮最早确定的状态,避免中途变卦造成混乱。
responses_turn_state_persists_within_turn_and_resets_after24–89 ↗
async fn responses_turn_state_persists_within_turn_and_resets_after() -> Result<()>
作用:这个测试检查普通响应流场景下,x-codex-turn-state 是否只在同一轮对话里保留,到了下一轮就清掉。它用来保证工具调用后的跟进请求能接上上下文,但新一轮不会继承旧轮状态。
数据流:测试先启动一个假的服务器,再准备三段服务器响应:第一段返回状态 ts-1 并要求执行 shell 命令,第二段模拟同一轮里工具执行后的跟进回答,第三段模拟下一轮新对话。然后它让测试版 Codex 连上这个假服务器,连续提交两轮用户输入。最后它读取服务器收到的三个请求:第一个请求应该没有状态头,第二个请求应该带上 ts-1,第三个请求又应该没有状态头。同时它还解析每个请求里的轮次编号,确认前两个请求属于同一轮,第三个请求属于新一轮。
调用关系:它先用 start_mock_server 搭一个假的 HTTP/SSE 服务器,用 sse 组装服务器逐条推送的事件,再用 mount_response_sequence 把这些响应按顺序挂到服务器上。之后通过 test_codex 创建被测客户端,触发真实的提交流程。最后用断言检查请求日志,证明状态在“同一轮内部保留、下一轮重置”这个规则没有被破坏。
调用图:调用 4 个内部函数(mount_response_sequence, sse, start_mock_server, test_codex);外部调用 4 个(assert_eq!, assert_ne!, skip_if_no_network!, vec!)。
websocket_turn_state_persists_within_turn_and_resets_after92–145 ↗
async fn websocket_turn_state_persists_within_turn_and_resets_after() -> Result<()>
作用:这个测试检查 WebSocket 长连接场景下,单轮状态也能正确保留和清空。它特别重要,因为 WebSocket 会复用同一条连接,容易让人误以为旧状态会自然残留到下一轮。
数据流:测试先启动一个假的 WebSocket 服务器,并安排同一条连接上收到三次请求:第一次响应里通过元数据给出状态 ts-1,并触发一次 shell 命令;第二次是同一轮的跟进响应;第三次是下一轮对话。测试版 Codex 提交两轮用户输入后,测试读取服务器记录下来的三个请求体,看里面 client_metadata 的 x-codex-turn-state 字段。结果应该是:第一次为空,第二次是 ts-1,第三次又为空。测试还确认只有一次握手,说明这些请求确实共用同一条 WebSocket 连接。
调用关系:它用 start_websocket_server_with_headers 准备假的 WebSocket 服务端,用 test_codex 构造连接到该服务端的客户端。客户端提交对话后,服务端保存所有请求,测试再用断言查看这些请求。这个测试和普通响应流测试互相补充,证明不管底层通信方式是短请求还是长连接,轮次状态规则都一致。
调用图:调用 2 个内部函数(start_websocket_server_with_headers, test_codex);外部调用 3 个(assert_eq!, skip_if_no_network!, vec!)。
websocket_turn_state_is_stable_within_turn148–203 ↗
async fn websocket_turn_state_is_stable_within_turn() -> Result<()>
作用:这个测试检查同一轮对话里的状态一旦确定,就不会被后面新的状态值改掉。它防止同一轮工具调用链中途换“通行证”,导致后续请求和前面的上下文对不上。
数据流:测试先启动一个假的 WebSocket 服务器,安排一轮对话里连续发生三次请求。第一次响应给出状态 ts-1 并触发第一个 shell 命令;第二次请求应该带着 ts-1,但服务器又试图给出新状态 ts-2 并触发第二个 shell 命令;第三次请求仍然应该继续带 ts-1。测试提交一次用户输入后,检查同一条连接上的三个请求,确认它们携带的状态依次是空、ts-1、ts-1,也就是后来的 ts-2 没有覆盖本轮原来的状态。
调用关系:它同样通过 start_websocket_server_with_headers 搭建可控的假 WebSocket 服务器,再用 test_codex 启动被测客户端。和前一个 WebSocket 测试不同,这里重点不是跨轮清空,而是同一轮内部的稳定性:服务器第二次发来的新状态不会被客户端采纳,后续请求仍沿用本轮最初拿到的状态。
调用图:调用 2 个内部函数(start_websocket_server_with_headers, test_codex);外部调用 3 个(assert_eq!, skip_if_no_network!, vec!)。
流式韧性与回退
这些回归套件验证当流式响应不完整或失败时的重试、恢复和传输回退行为。
core/tests/suite/stream_error_allows_next_turn.rs源码 ↗
这个测试像是在模拟一次聊天事故:先搭一个假的模型服务器,让第一次请求返回错误,再让第二次请求正常返回。测试程序把 Codex 配成连接这个假服务器,然后先发一条“first message”。服务器故意回 500 错误,也就是服务端失败。测试接着等系统发出 Error 事件,再等 TurnComplete 事件;TurnComplete 可以理解成“这一轮不管成功失败,都已经收摊了”。之后它再发一条“follow up”,假服务器这次用 SSE(服务器持续推送事件的一种文本流格式)返回成功事件。这个文件重要的地方在于,它不只是检查错误会被报告,还检查错误之后内部状态会被清干净。否则系统可能像前台办事窗口一样,上一位客人的单子出错却没关单,下一位客人就永远排不上。
continue_after_stream_error21–132 ↗
async fn continue_after_stream_error()
作用:这个测试函数验证模型流式请求失败后,Codex 仍然能接受并完成下一轮用户输入。它用假的 HTTP 服务器制造“先失败、后成功”的场景,确保系统不会因为一次错误就把会话锁住。
数据流:进去的是一个测试环境:函数先启动假服务器,并准备两种响应,一种是针对“first message”的失败响应,另一种是针对“follow up”的成功 SSE 事件流。然后它创建一个连接假服务器的 Codex 实例,提交第一条用户消息,观察到 Error 和 TurnComplete;接着提交第二条用户消息,最后观察到 TurnComplete。出来的结果不是返回业务数据,而是测试通过或失败;如果第二轮不能完成,这个测试就会失败,说明错误后状态没有正确释放。
调用关系:它是这个测试文件的核心入口,由 Tokio 异步测试框架自动运行。它会调用 MockServer 和 Mock 相关工具搭建假网络服务,用 test_codex 创建测试用 Codex,用 sse 生成模拟的流式返回内容,再用 wait_for_event 等待 Codex 发出的关键事件。整个流程是在替真实模型服务“演戏”,专门检查 Codex 在错误和下一轮提交之间的衔接是否可靠。
调用图:调用 2 个内部函数(sse, test_codex);外部调用 12 个(default, given, start, new, wait_for_event, format!, json!, skip_if_no_network!, vec!, body_string_contains (+2 more))。
core/tests/suite/stream_no_completed.rs源码 ↗
这个测试模拟了一个很常见的坏情况:服务端开始用 SSE(Server-Sent Events,一种服务器持续往客户端推送消息的流式连接)返回内容,但还没发出 response.completed 这个“本轮回答真的结束了”的事件,连接就结束了。文件先准备一段“不完整的流”,再准备一段“正常完成的流”,然后启动一个假的流式服务器:第一次请求给坏流,第二次请求给好流。接着它把测试用的 Codex 客户端指向这个假服务器,并明确设置:普通请求不重试,但流断了可以重试 1 次。最后它提交一条用户输入,等待系统发出 TurnComplete(表示这一轮对话完成),再检查服务器一共收到了 2 次请求。这样就能证明:第一次流提前结束后,系统确实识别出了“不算完成”,并重新请求了一次。
sse_incomplete17–21 ↗
fn sse_incomplete() -> String
作用:这个小函数专门造一段“不完整的 SSE 响应”。它看起来像服务端发了一点内容,但故意不包含“回答完成”的事件,用来触发重试场景。
数据流:进去没有参数 → 它把一个 JSON 事件包装成 SSE 格式,这个事件只说有一个输出项结束了,但没有说整个回答完成 → 出来是一段字符串,代表服务端第一次返回的残缺流。
调用关系:它是测试的道具制造器,由 retries_on_early_close 调用。主测试拿它生成第一次请求要返回的坏数据,再和一段正常完成的数据一起交给假服务器。
调用图:调用 1 个内部函数(sse);被 1 处调用(retries_on_early_close);外部调用 1 个(vec!)。
retries_on_early_close24–102 ↗
async fn retries_on_early_close()
作用:这是整个测试的主体。它验证当流式响应提前结束、缺少完成信号时,Codex 会重新发起一次流式请求,并最终正常完成这一轮对话。
数据流:开始时它先跳过没有网络条件的环境 → 造出第一次用的残缺 SSE 和第二次用的完成 SSE → 启动一个假服务器,让它按顺序返回这两段数据 → 配置测试客户端,让流式连接最多能重试 1 次 → 提交一条“hello”用户输入 → 等到系统报告本轮完成 → 最后读取假服务器收到的请求数量,确认正好是 2 次,并关闭服务器。
调用关系:它调用 sse_incomplete 准备失败用例,也调用测试支持代码启动假 SSE 服务器、创建测试版 Codex、等待完成事件。它位于测试流程的最外层,像导演一样安排服务器、客户端、输入和断言,最后用请求次数证明重试真的发生了。
调用图:调用 4 个内部函数(sse_completed, start_streaming_sse_server, test_codex, sse_incomplete);外部调用 6 个(default, assert_eq!, wait_for_event, format!, skip_if_no_network!, vec!)。
core/tests/suite/websocket_fallback.rs源码 ↗
这里测试的是“先走 WebSocket,不行就退回 HTTP”的安全网。WebSocket 可以理解成一条一直开着的双向通道;HTTP 则像一次次寄信,请求和响应更传统。文件用一个假的模型服务器来演戏:有时让 WebSocket 返回 426,表示“你得换种方式”;有时让 WebSocket 一直失败,逼系统重试到上限;然后再准备好 HTTP 的 SSE 响应。SSE 是“服务器持续推送事件”的 HTTP 流,像服务员不断把新消息端上来。每个测试都会启动假服务器,配置一个测试版 Codex,让它以为服务端支持 WebSocket,然后提交用户输入,最后数一数实际发出了多少次 GET 和 POST 请求。GET 代表 WebSocket 尝试,POST 代表 HTTP fallback。这个文件还检查一个细节:第一次重连错误是否该对用户隐藏,以及 fallback 一旦启用,后续对话是否继续用 HTTP,不再白白尝试坏掉的 WebSocket。
websocket_fallback_switches_to_http_on_upgrade_required_connect30–79 ↗
async fn websocket_fallback_switches_to_http_on_upgrade_required_connect() -> Result<()>
作用:这个测试确认:如果 WebSocket 连接一开始就收到 426“需要升级/不能这样连”的回应,系统会立刻改用 HTTP,而不是继续傻傻重试 WebSocket。这样用户的第一次请求能尽快走通。
数据流:测试先启动一个假服务器,并安排它对 GET /responses 返回 426;同时准备一个 HTTP SSE 成功响应。接着它创建测试版 Codex,把模型地址指向假服务器,并声明服务端支持 WebSocket。提交“hello”之后,它读取假服务器收到的请求,数出 WebSocket 的 GET 次数和 HTTP 的 POST 次数。结果应该是:WebSocket 只试 1 次,HTTP 试 1 次,并且那次 HTTP 响应确实被用上。
调用关系:这个测试用 start_mock_server 搭一个假服务,用 wiremock 的 given 和 ResponseTemplate.new 制造 426,用 mount_sse_once 和 sse 准备 HTTP 流式回答,再通过 test_codex 建出测试对象。它最后用 assert_eq! 把实际请求次数和预期对上,证明 426 这个特殊错误会触发快速 fallback。
调用图:调用 4 个内部函数(mount_sse_once, sse, start_mock_server, test_codex);外部调用 8 个(given, new, assert_eq!, format!, skip_if_no_network!, vec!, method, path_regex)。
websocket_fallback_switches_to_http_after_retries_exhausted82–124 ↗
async fn websocket_fallback_switches_to_http_after_retries_exhausted() -> Result<()>
作用:这个测试确认:如果 WebSocket 没有明确说“换 HTTP”,但一直连不上,系统会按配置重试几次,然后放弃 WebSocket,改用 HTTP。它防止程序无限重连,让用户请求最终能完成。
数据流:测试先启动假服务器,只挂上一个 HTTP SSE 成功响应,不给 WebSocket 成功通道。然后它配置 Codex:WebSocket 最多重试 2 次,普通请求不额外重试。提交“hello”后,它从假服务器记录里统计 GET /responses 和 POST /responses。预期是启动预热先产生 1 次 WebSocket 尝试,真正请求再产生 1 次初试加 2 次重试,总共 4 次 GET;重试耗尽后,请求被用 POST 重新发到 HTTP,并成功消费 1 次响应。
调用关系:它和前一个测试一样,靠 start_mock_server、mount_sse_once、sse 和 test_codex 搭出测试环境,但这里不制造 426,而是让 WebSocket 尝试自然失败。它检验的是重试上限这条路:WebSocket 多次失败后,系统把同一个用户请求交给 HTTP 通道继续完成。
调用图:调用 4 个内部函数(mount_sse_once, sse, start_mock_server, test_codex);外部调用 4 个(assert_eq!, format!, skip_if_no_network!, vec!)。
websocket_fallback_hides_first_websocket_retry_stream_error127–206 ↗
async fn websocket_fallback_hides_first_websocket_retry_stream_error() -> Result<()>
作用:这个测试检查用户看到的错误提示是否合适:WebSocket 重连时,有些内部失败不该过早吓到用户。它确保界面只展示预期的“正在重连”提示,而不是把底层噪音全抛出来。
数据流:测试启动假服务器,并准备一个 HTTP SSE 成功响应。它创建测试版 Codex 后,手动提交一条用户输入,同时带上当前目录、权限、沙箱策略和线程设置。然后它不断从 Codex 的事件流里读消息:遇到 StreamError 就收集错误文字,遇到 TurnComplete 就停止。最后它比较收集到的重连提示:调试版本会看到“Reconnecting... 1/2”和“Reconnecting... 2/2”,发布版本只看到后一次;同时确认 HTTP 响应只被请求了一次。
调用关系:这个测试比其他几个更贴近真实事件流。它先用 start_mock_server 和 mount_sse_once 准备服务端,再用 test_codex 建会话,用 turn_permission_fields 和 local_selections 生成提交请求需要的权限和环境信息。提交 Op::UserInput 后,它不只是数 HTTP 请求,还直接监听 codex.next_event,检查 fallback 过程中交给用户看的事件是否被正确过滤。
调用图:调用 6 个内部函数(mount_sse_once, sse, start_mock_server, local_selections, test_codex, turn_permission_fields);外部调用 9 个(default, from_secs, new, assert_eq!, cfg!, format!, skip_if_no_network!, timeout, vec!)。
websocket_fallback_is_sticky_across_turns209–256 ↗
async fn websocket_fallback_is_sticky_across_turns() -> Result<()>
作用:这个测试确认:一旦系统判断 WebSocket 不可靠并切到 HTTP,后面的对话轮次会继续用 HTTP,不会每次都重新踩同一个坑。这样可以减少无用连接,也让连续对话更稳定。
数据流:测试启动假服务器,并按顺序准备两次 HTTP SSE 成功响应,分别对应两轮对话。它配置测试版 Codex 支持 WebSocket,并设置最多重试 2 次。接着连续提交“first”和“second”。最后它统计请求记录:第一轮会包含启动预热的 1 次 WebSocket 尝试,再加上真正请求的初试和 2 次重试,总共 4 次 GET;fallback 生效后,两轮都通过 HTTP 完成,所以 POST 是 2 次;准备好的两个 HTTP 响应也都被用掉。
调用关系:这个测试用 mount_sse_sequence 而不是 mount_sse_once,因为它要模拟两轮成功的 HTTP 回答。它通过 test_codex 创建同一个会话,连续调用 submit_turn 两次,然后用 assert_eq! 验证第二轮没有再把活儿交回 WebSocket,而是沿用第一次建立的 HTTP fallback 状态。
调用图:调用 3 个内部函数(mount_sse_sequence, start_mock_server, test_codex);外部调用 4 个(assert_eq!, format!, skip_if_no_network!, vec!)。
提供方协议边界情况
这些测试验证面向提供方的协议细节,包括代理身份、配额失败,以及安全触发的重路由或降级信号。
core/tests/suite/responses_api_proxy_headers.rs源码 ↗
这个测试文件模拟了一次真实对话:用户让主代理启动一个子代理,子代理再单独向接口发请求。这里的 Responses API 可以理解成模型服务的接口;请求头就是每次请求随身携带的“小纸条”,写着它属于哪个窗口、哪个父线程、是不是子代理。测试先搭一个假服务器,假装模型会返回“我要启动子代理”等事件;再启动测试版 Codex,把用户提示词交进去;最后检查主代理和子代理真正发出的请求。重点是:主代理不能带子代理身份头,子代理必须带 x-openai-subagent: collab_spawn,而且要写清自己的父线程是谁。这样系统以后做日志、排查问题、区分并行任务时,才不会认错人。
responses_api_parent_and_subagent_requests_include_identity_headers36–141 ↗
async fn responses_api_parent_and_subagent_requests_include_identity_headers() -> Result<()>
作用:这是整个测试的主入口。它搭好假服务器,模拟主代理启动子代理的全过程,然后检查两边发出的 API 请求头是不是符合预期。
数据流:进去的是测试里写死的父提示词、子提示词和假响应规则;它先启动 mock 服务器(假服务器,用来接住请求并返回预设回答),再让测试版 Codex 提交一次用户输入;之后从假服务器记录里找出主请求和子请求,读取它们的请求头和正文;最后用断言确认主子身份、窗口编号、父线程编号都正确。结果是测试通过或失败,失败时说明请求标记可能有 bug。
调用关系:它是本文件的核心测试函数,会调用 start_mock_server、mount_sse_once_match 和 sse 准备假 Responses API;调用 test_codex 创建测试环境;调用 submit_turn_with_timeout 推动一次完整对话;再调用 wait_for_matching_request 等待并取回请求记录;最后用 split_window_id 拆开窗口编号来比较父子关系。
调用图:调用 7 个内部函数(mount_sse_once_match, sse, start_mock_server, test_codex, split_window_id, submit_turn_with_timeout, wait_for_matching_request);外部调用 7 个(assert!, assert_eq!, json!, from_str, to_string, skip_if_no_network!, vec!)。
submit_turn_with_timeout143–189 ↗
async fn submit_turn_with_timeout(test: &TestCodex, prompt: &str) -> Result<()>
作用:这个函数负责把一段用户提示词提交给测试版 Codex,并等到这一轮对话真正开始又结束。它的作用是让测试不用只“发出请求”,而是等系统完整跑完一轮。
数据流:进去的是测试环境 TestCodex 和用户提示词;它读取测试配置里的模型和当前工作目录,生成权限和沙箱设置(沙箱就是限制程序能碰哪些文件和资源的安全边界),再把用户输入包装成一次 Op::UserInput 提交给 Codex;提交后,它等待收到 TurnStarted 事件,再等待同一个回合的 TurnComplete 事件。出来的是成功完成的结果,或者在提交、等待事件时返回错误。
调用关系:它由主测试 responses_api_parent_and_subagent_requests_include_identity_headers 调用,用来触发主代理和子代理实际发请求。它内部会用 turn_permission_fields、local_selections 和 workspace_write 准备本轮运行环境,并把等待事件的细活交给 wait_for_event_result。
调用图:调用 4 个内部函数(local_selections, turn_permission_fields, wait_for_event_result, workspace_write);被 1 处调用(responses_api_parent_and_subagent_requests_include_identity_headers);外部调用 3 个(default, unreachable!, vec!)。
wait_for_matching_request191–213 ↗
async fn wait_for_matching_request(
mock: &ResponseMock,
label: &str,
mut predicate: F,
) -> Result<ResponsesRequest>
作用:这个函数反复查看假服务器收到的请求,直到找到一条符合条件的请求,或者等太久就报错。它像是在收件箱里不断刷新,等目标信件出现。
数据流:进去的是某个 mock 记录器、一个用于错误提示的名字,以及一段判断请求是否匹配的规则;它在固定超时时间内循环读取 mock.requests(),逐条检查请求是否满足规则;找到了就返回这条 ResponsesRequest,一直找不到就返回“等待超时”的错误。
调用关系:它由主测试调用两次:一次找主代理请求,一次找子代理请求。它不关心业务含义,只负责可靠地等到匹配请求;具体什么算匹配,由主测试传进来的判断函数决定。
调用图:调用 1 个内部函数(requests);被 1 处调用(responses_api_parent_and_subagent_requests_include_identity_headers);外部调用 2 个(sleep, timeout)。
wait_for_event_result215–240 ↗
async fn wait_for_event_result(
test: &TestCodex,
stage: &str,
mut predicate: F,
) -> Result<EventMsg>
作用:这个函数等待 Codex 发出某个符合条件的事件,比如“回合开始”或“回合结束”。如果迟迟等不到,它会把已经看到的事件摘要放进错误信息,方便排查。
数据流:进去的是测试环境、当前等待阶段的名字,以及一个判断事件是否目标事件的规则;它不断调用 test.codex.next_event() 读取下一个事件,把每个事件先压缩成简短文字保存下来;一旦某个事件满足规则,就返回这个事件;如果超时,就返回包含已见事件列表的错误。
调用关系:它由 submit_turn_with_timeout 调用,用来确认一次用户输入确实开始并完成。它会调用 event_summary 把看到的事件变成短摘要,这样超时时错误信息更有用。
调用图:调用 1 个内部函数(event_summary);被 1 处调用(submit_turn_with_timeout);外部调用 2 个(new, timeout)。
event_summary242–246 ↗
fn event_summary(event: &EventMsg) -> String
作用:这个小工具把一个事件转成短文字,主要用于等待超时时告诉开发者“之前都看到了什么”。它避免错误信息太长、太难读。
数据流:进去的是一个 EventMsg 事件;它用调试格式把事件转成字符串,然后截断到 240 个字符;出来的是一段较短的事件摘要,不会改动原事件。
调用关系:它只被 wait_for_event_result 使用。等待目标事件时,每看到一个非目标事件,就通过它记一条简短日志,最后如果超时就把这些摘要拼进错误消息。
调用图:被 1 处调用(wait_for_event_result);外部调用 1 个(format!)。
request_body_contains248–250 ↗
fn request_body_contains(req: &wiremock::Request, text: &str) -> bool
作用:这个函数检查一条 HTTP 请求的正文里是否包含某段文字。测试用它来判断这条请求是不是主提示词或子提示词对应的请求。
数据流:进去的是 wiremock 收到的原始请求和要查找的文字;它先把请求正文的字节内容按 UTF-8 文本解码,UTF-8 可以理解成常见的文字编码;如果能解码,就检查里面是否包含目标文字;出来的是 true 或 false。
调用关系:它被放进 mount_sse_once_match 的匹配规则里使用,帮助假服务器判断当前请求该拿哪套预设响应来回答。虽然调用清单里没有列出直接调用者,但它在主测试创建 mock 匹配条件时发挥作用。
调用图:外部调用 1 个(from_utf8)。
request_header252–254 ↗
fn request_header(req: &'a wiremock::Request, name: &str) -> Option<&'a str>
作用:这个函数从一条 HTTP 请求里读取指定名字的请求头,并把它转成普通字符串。测试用它来检查请求有没有带子代理身份、父线程编号等标记。
数据流:进去的是 wiremock 请求和请求头名字;它到请求头集合里查这个名字,找到后尝试转成文本;成功就返回这段文本,找不到或转不成文本就返回空值 None。它不修改请求。
调用关系:它主要服务于主测试里给 mount_sse_once_match 的匹配规则:假服务器根据请求头判断这是不是父请求或子请求。它也体现了本文件最关心的东西——每次 API 请求随身带的身份信息。
split_window_id256–261 ↗
fn split_window_id(window_id: &str) -> Result<(&str, u64)>
作用:这个函数把窗口编号拆成“线程编号”和“代数”两部分。测试用它确认父代理和子代理不是同一个线程,并且它们都是第 0 代窗口。
数据流:进去的是形如 线程编号:代数 的窗口 ID 字符串;它从最后一个冒号处分开,左边当作线程 ID,右边解析成数字;成功时返回这两部分,格式不对或数字解析失败时返回错误。
调用关系:它由主测试 responses_api_parent_and_subagent_requests_include_identity_headers 调用。主测试先从父请求和子请求头里取出 x-codex-window-id,再交给它拆开,随后比较父子线程是否不同、generation 是否为 0。
调用图:被 1 处调用(responses_api_parent_and_subagent_requests_include_identity_headers)。
core/tests/suite/quota_exceeded.rs源码 ↗
这个测试模拟了一次真实对话:用户问了一句“quota?”,后台服务先说已经创建了一个响应,接着又返回“insufficient_quota”,意思是账户额度不够。测试不会真的去消耗额度,而是启动一个假的服务器,让它按预设顺序吐出这些消息,就像排练一场故障现场。然后 Codex 客户端把用户输入发出去,测试不断监听系统产生的事件。它只关心两件事:第一,系统要把底层又长又技术化的错误,改成更适合用户看的提示“Quota exceeded. Check your plan and billing details.”;第二,这个错误事件只能出现一次。最后等到一轮对话结束,再确认错误计数正好是 1。这样可以保证额度不足时,用户得到清楚、稳定、不重复的反馈。
quota_exceeded_emits_single_error_event16–77 ↗
async fn quota_exceeded_emits_single_error_event() -> Result<()>
作用:这个测试函数验证:当服务端返回“额度不足”错误时,Codex 只发出一个用户可读的错误事件。有人改动错误处理流程时,可以靠它发现是否把同一个错误重复通知了用户。
数据流:进去的是一个测试环境:假的网络服务器、预设的服务端事件,以及一条用户输入“quota?”。函数先在假服务器上挂好一次性响应,让它依次返回“响应已创建”和“额度不足失败”;然后把用户输入交给 Codex;接着循环读取 Codex 发出的事件。每读到一个 Error 事件,就检查错误文字是否被改写成友好的额度提示,并把错误次数加一;读到 TurnComplete 表示这一轮结束。最后输出的是测试结果:如果错误事件不是正好一次,或文字不对,测试就失败;否则返回成功。
调用关系:它是测试框架在运行测试套件时直接调用的入口。函数先用 start_mock_server 创建假服务器,用 sse 和 mount_sse_once 安排服务器要吐出的模拟消息,再用 test_codex 搭好被测试的 Codex 实例。发送用户输入后,它依靠 wait_for_event 一件件取出系统事件,并用断言检查结果。skip_if_no_network 会在没有网络条件时跳过这个测试,避免环境问题造成误报。
调用图:调用 4 个内部函数(mount_sse_once, sse, start_mock_server, test_codex);外部调用 5 个(default, assert_eq!, wait_for_event, skip_if_no_network!, vec!)。
core/tests/suite/safety_check_downgrade.rs源码 ↗
这个文件用假的 OpenAI 服务器来演练几种安全相关场景:服务器实际返回的模型名和用户请求的不一样、服务器直接返回“网络安全风险”错误、响应内容里带有模型验证信息等。可以把它理解成给系统做“消防演习”:不是真的去调用线上服务,而是先搭一个假服务,安排它返回特定消息,然后看 Codex 会不会发出 ModelReroute(模型改道通知)、Warning(警告)、Error(错误)或 ModelVerification(模型验证结果)这些事件。文件里还特别检查一些容易出错的边角情况,比如同一轮对话里不要重复警告,只是大小写不同不要误报,以及模型验证信息不应该被当成危险警告。这样能保证安全提示既不会漏报,也不会乱报。
disabled_text_turn38–65 ↗
fn disabled_text_turn(test: &TestCodex, text: &str) -> Op
作用:这个辅助函数用来造一条“用户输入文字”的测试请求,并把权限设置成很保守的状态。测试用它来保证每个场景的输入格式一致,只把重点放在服务器返回什么、系统怎么反应上。
数据流:进去的是一个测试环境和一段用户文字;它读取测试目录、当前模型推理配置,并生成本地环境选择和权限字段;出来的是一个 Op::UserInput,也就是一条可以提交给 Codex 的用户输入操作,里面带着禁用型权限、永不请求批准、指定请求模型等设置。
调用关系:它是这些测试的共同“备料”步骤。后面的每个测试都会先搭假服务器,再调用 disabled_text_turn 做出同样风格的用户请求,最后把请求交给 test.codex.submit 去触发完整流程。它自己会借助 cwd_path、turn_permission_fields 和 local_selections 来填好路径、权限和本地环境信息。
调用图:调用 3 个内部函数(cwd_path, local_selections, turn_permission_fields);被 7 处调用(cyber_policy_response_emits_typed_error_without_retry, model_verification_emits_structured_event_without_reroute_or_warning, model_verification_only_emits_once_per_turn, openai_model_header_casing_only_mismatch_does_not_warn, openai_model_header_mismatch_emits_warning_event, openai_model_header_mismatch_only_emits_one_warning_per_turn, response_model_field_mismatch_emits_warning_when_header_matches_requested);外部调用 2 个(default, vec!)。
openai_model_header_mismatch_emits_warning_event68–107 ↗
async fn openai_model_header_mismatch_emits_warning_event() -> Result<()>
作用:这个测试确认:如果用户请求的是一个模型,但服务器响应头里说实际用了另一个模型,系统会告诉用户模型被改道,并发出警告。这样用户不会被蒙在鼓里。
数据流:进去的是一个预设好的假服务器响应:响应头里的 OpenAI-Model 是 SERVER_MODEL,而测试配置请求的是 REQUESTED_MODEL;测试提交一条文字请求;之后它等待事件流,检查出来的 ModelReroute 事件是否写明从请求模型变到服务器模型,并检查 Warning 里是否同时提到两个模型,最后等到本轮完成。
调用关系:它先用 start_mock_server 启动假服务器,用 sse_response 和 sse_completed 做一条完成响应,再用 mount_response_once 挂上这条响应。随后通过 test_codex 建测试用 Codex,借 disabled_text_turn 生成输入。它主要验证 Codex 的事件输出是否符合预期。
调用图:调用 6 个内部函数(mount_response_once, sse_completed, sse_response, start_mock_server, test_codex, disabled_text_turn);外部调用 5 个(assert!, assert_eq!, wait_for_event, panic!, skip_if_no_network!)。
cyber_policy_response_emits_typed_error_without_retry110–141 ↗
async fn cyber_policy_response_emits_typed_error_without_retry() -> Result<()>
作用:这个测试确认:当服务端明确返回“网络安全策略拦截”错误时,Codex 会把它识别成专门的 CyberPolicy 错误,而不是普通报错,也不会反复重试。
数据流:进去的是一个假服务器 400 错误响应,里面的错误码是 cyber_policy,错误文字是固定的安全策略提示;测试提交用户请求后等待 Error 事件;出来的检查结果是错误消息必须完全等于策略提示,并且 codex_error_info 必须标成 CyberPolicy,同时假服务器只收到一次请求。
调用关系:它用 ResponseTemplate 构造 HTTP 错误响应,再用 mount_response_once 保证只响应一次。disabled_text_turn 负责生成触发请求。测试最后通过 mock.single_request 证明系统没有把这种安全拦截当成可重试故障。
调用图:调用 4 个内部函数(mount_response_once, start_mock_server, test_codex, disabled_text_turn);外部调用 6 个(new, assert_eq!, wait_for_event, panic!, json!, skip_if_no_network!)。
response_model_field_mismatch_emits_warning_when_header_matches_requested144–203 ↗
async fn response_model_field_mismatch_emits_warning_when_header_matches_requested() -> Result<()>
作用:这个测试确认:即使 HTTP 响应头看起来没问题,只要流式响应内容里透露实际模型不一样,系统也会发出模型改道和警告。它防止系统只看表面信息而漏掉真实模型变化。
数据流:进去的是一段假的流式响应:外层响应头写的是用户请求模型,但 response.created 事件里的 headers 写的是服务器模型;测试提交请求后等待 ModelReroute 和 Warning;结果要证明系统能从响应内容里发现差异,并在警告中说明请求模型和实际模型。
调用关系:它用 sse 组装多段服务器发送事件,包含 response.created 和 completed。mount_response_once 把这串事件交给假服务器。Codex 收到后应在事件流里吐出 ModelReroute 和 Warning,测试用 wait_for_event 逐个等这些信号。
调用图:调用 6 个内部函数(mount_response_once, sse, sse_response, start_mock_server, test_codex, disabled_text_turn);外部调用 6 个(assert!, assert_eq!, wait_for_event, panic!, skip_if_no_network!, vec!)。
openai_model_header_mismatch_only_emits_one_warning_per_turn206–255 ↗
async fn openai_model_header_mismatch_only_emits_one_warning_per_turn() -> Result<()>
作用:这个测试确认:同一轮用户请求里,即使系统因为工具调用产生了两次模型响应,也只应该警告一次模型被换了。这样用户能知道重点,但不会被重复提示刷屏。
数据流:进去的是两段连续的假响应:第一段包含一个 shell_command 工具调用,第二段返回助手消息,两段响应头都显示实际模型与请求模型不同;测试提交请求后一路读取事件;它统计包含请求模型名的 Warning 数量,最后要求数量正好是 1。
调用关系:它用 mount_response_sequence 安排假服务器按顺序返回两次响应,模拟“模型先要求执行工具,工具后又继续回答”的一整轮流程。disabled_text_turn 发起这一轮,wait_for_event 循环读取所有事件,直到 TurnComplete。这个测试重点看去重逻辑是否生效。
调用图:调用 6 个内部函数(mount_response_sequence, sse, sse_response, start_mock_server, test_codex, disabled_text_turn);外部调用 5 个(assert_eq!, wait_for_event, json!, skip_if_no_network!, vec!)。
openai_model_header_casing_only_mismatch_does_not_warn258–296 ↗
async fn openai_model_header_casing_only_mismatch_does_not_warn() -> Result<()>
作用:这个测试确认:如果模型名只是大小写不同,比如 gpt 和 GPT,不应该被当成真正换模型。这样可以避免无意义的误报。
数据流:进去的是一个假响应,OpenAI-Model 响应头使用请求模型的大写版本;测试提交请求后循环读取事件;它统计 ModelReroute 和相关 Warning 的数量,最后要求两者都是 0。
调用关系:它用 sse_completed 做一个正常完成响应,只把响应头里的模型名改成大写。Codex 处理这条响应时,应该用不区分大小写的方式比较模型名。测试通过 wait_for_event 一直看到 TurnComplete,确保整个过程没有错误警告冒出来。
调用图:调用 6 个内部函数(mount_response_once, sse_completed, sse_response, start_mock_server, test_codex, disabled_text_turn);外部调用 3 个(assert_eq!, wait_for_event, skip_if_no_network!)。
model_verification_emits_structured_event_without_reroute_or_warning299–356 ↗
async fn model_verification_emits_structured_event_without_reroute_or_warning() -> Result<()>
作用:这个测试确认:当服务器发来“模型验证信息”时,Codex 应该生成结构化的 ModelVerification 事件,而不是把它误解成模型改道或安全警告。
数据流:进去的是一段流式响应,里面包含 trusted_access_for_cyber 这个验证标记;测试提交请求后读取事件流;它要求恰好收到一次 ModelVerification,内容转换成 TrustedAccessForCyber,同时 ModelReroute、Warning,以及伪装成普通消息的 Warning 文本都不能出现。
调用关系:它用 ev_model_verification_metadata 把验证元数据塞进假响应。Codex 解析响应后应把原始字符串翻译成 ModelVerification::TrustedAccessForCyber。测试还顺手检查 RawResponseItem,确保系统没有把验证信息写成一条面向用户的警告消息。
调用图:调用 6 个内部函数(mount_response_once, sse, sse_response, start_mock_server, test_codex, disabled_text_turn);外部调用 5 个(assert_eq!, wait_for_event, matches!, skip_if_no_network!, vec!)。
model_verification_only_emits_once_per_turn359–412 ↗
async fn model_verification_only_emits_once_per_turn() -> Result<()>
作用:这个测试确认:同一轮对话里,即使前后两次模型响应都带了同样的验证信息,Codex 也只发一次 ModelVerification。它避免重复事件让上层界面或日志误以为发生了多次验证。
数据流:进去的是两段连续假响应:第一段带工具调用和验证信息,第二段也带验证信息并给出最终助手消息;测试提交请求后循环读取所有事件;它统计 ModelVerification 次数,要求最后正好是 1,并且如果出现高风险网络安全警告就直接失败。
调用关系:它和“一轮只警告一次”的测试类似,用 mount_response_sequence 模拟带工具调用的多次响应流程。disabled_text_turn 发起用户输入,wait_for_event 持续接收事件直到 TurnComplete。这个测试专门验证模型验证事件在整轮流程中的去重行为。
调用图:调用 6 个内部函数(mount_response_sequence, sse, sse_response, start_mock_server, test_codex, disabled_text_turn);外部调用 6 个(assert_eq!, wait_for_event, panic!, json!, skip_if_no_network!, vec!)。