Codex 系统手册

App-server 集成套件 — 传输、协议契约和客户端连接行为

stage-23.1.4.25 个文件

这一阶段不是主干活儿,而是 app-server 上线前的联调验收,专盯客户端连进来后的路和规矩。WebSocket 测试检查 JSON-RPC 收发、鉴权、健康检查和重连;Unix 信号测试确认 Ctrl-C 等退出时能安全收尾。证明令牌测试保证握手时把身份/环境凭据带给后端;实验 API 测试拦住未声明支持的新接口;实时对话测试用假后端和假客户端,把语音、文字、WebRTC、代理交接、错误和配置开关一起跑通。

本阶段的文件5

WebSocket 连接生命周期

这些测试建立核心 WebSocket 传输行为,并扩展到 Unix 专属的关闭和重启场景。

app-server/tests/suite/v2/connection_handling_websocket.rs源码 ↗
testintegration test run

这个文件会真的启动一个 codex-app-server 测试进程,再像真实客户端一样连上它。WebSocket 可以理解成一根长期打开的网络管子;JSON-RPC 是一种用 JSON 写“请求、响应、通知”的约定。测试会检查:每个连接初始化后只收到自己的回复;同一个请求编号在不同连接里不会串线;/readyz 和 /healthz 健康接口还在;浏览器来源、Bearer token(放在请求头里的通行证)、短期签名 token 都按规则放行或拒绝;断线后已加载线程在空闲超时前还可找回。文件下半部分是测试工具箱,负责启动服务器、连 WebSocket、发请求、读响应、写临时配置和生成测试 token。

函数细节34
websocket_transport_routes_per_connection_handshake_and_responses63–105 ↗
async fn websocket_transport_routes_per_connection_handshake_and_responses() -> Result<()>

作用:这个测试确认 WebSocket 的消息不会在不同客户端之间串门。尤其是初始化回复、错误回复,以及相同请求编号的响应,都必须回到发起它的那条连接。

数据流:它先建一个假的模型服务和临时配置,再启动 app-server,打开两条 WebSocket。它分别发送初始化和读配置请求,观察每条连接收到什么,最后确认响应编号和内容都正确,并杀掉测试进程。

调用关系:这是顶层测试用例。它把启动、连接、发请求、读响应、确认无消息这些小工具串起来,重点验证 spawn_websocket_server、connect_websocket、send_initialize_request、send_config_read_request、read_response_for_id、read_error_for_id 和 assert_no_message 能组成一条真实的端到端流程。

调用图:调用 8 个内部函数(assert_no_message, connect_websocket, create_config_toml, read_error_for_id, read_response_for_id, send_config_read_request, send_initialize_request, spawn_websocket_server);外部调用 6 个(from_millis, new, new, create_mock_responses_server_sequence_unchecked, assert!, assert_eq!)。

websocket_transport_serves_health_endpoints_on_same_listener108–132 ↗
async fn websocket_transport_serves_health_endpoints_on_same_listener() -> Result<()>

作用:这个测试确认同一个监听端口既能服务 WebSocket,也能服务健康检查 HTTP 接口。这样部署系统可以用 /readyz 和 /healthz 判断服务是否活着。

数据流:它创建配置并启动服务器,然后用 HTTP 客户端访问 /readyz 和 /healthz,要求都返回 200 OK。接着再连 WebSocket 并完成初始化,证明健康接口没有挤掉 WebSocket 功能。

调用关系:这是顶层测试用例。它调用 http_get 检查普通 HTTP 路径,再调用 connect_websocket、send_initialize_request 和 read_response_for_id 检查 WebSocket 路径。

调用图:调用 7 个内部函数(connect_websocket, create_config_toml, http_get, read_response_for_id, send_initialize_request, spawn_websocket_server, new);外部调用 4 个(new, new, create_mock_responses_server_sequence_unchecked, assert_eq!)。

websocket_transport_rejects_browser_origin_without_auth135–161 ↗
async fn websocket_transport_rejects_browser_origin_without_auth() -> Result<()>

作用:这个测试确认没有鉴权时,带可疑浏览器 Origin 的 WebSocket 连接会被拒绝。Origin 可以理解成网页自报的来源地址,用来防止恶意网页偷偷连本地服务。

数据流:它先证明普通本地连接可以初始化成功,然后断开。之后带上 https://evil.example 这个来源头重新握手,期望服务器返回 403 Forbidden,而不是放它进来。

调用关系:这是顶层安全测试。它先用正常连接工具建立基线,再把拒绝场景交给 assert_websocket_connect_rejected_with_headers 去验证 HTTP 握手阶段的状态码。

调用图:调用 6 个内部函数(assert_websocket_connect_rejected_with_headers, connect_websocket, create_config_toml, read_response_for_id, send_initialize_request, spawn_websocket_server);外部调用 4 个(new, new, create_mock_responses_server_sequence_unchecked, assert_eq!)。

websocket_transport_rejects_missing_and_invalid_capability_tokens164–193 ↗
async fn websocket_transport_rejects_missing_and_invalid_capability_tokens() -> Result<()>

作用:这个测试确认 capability-token 模式下,WebSocket 必须带正确的 Bearer token 才能连上。Bearer token 就像门禁卡,少了或错了都不能进。

数据流:它把正确 token 写进临时文件,用额外参数启动服务器。然后分别尝试不带 token、带错误 token,要求都被拒绝;最后带正确 token 连接并初始化成功。

调用关系:这是顶层鉴权测试。它依赖 spawn_websocket_server_with_args 打开 token 鉴权模式,用 assert_websocket_connect_rejected 验证失败路径,用 connect_websocket_with_bearer 验证成功路径。

调用图:调用 6 个内部函数(assert_websocket_connect_rejected, connect_websocket_with_bearer, create_config_toml, read_response_for_id, send_initialize_request, spawn_websocket_server_with_args);外部调用 6 个(new, new, create_mock_responses_server_sequence_unchecked, assert_eq!, write, vec!)。

websocket_transport_verifies_signed_short_lived_bearer_tokens196–290 ↗
async fn websocket_transport_verifies_signed_short_lived_bearer_tokens() -> Result<()>

作用:这个测试确认 signed-bearer-token 模式会认真检查短期签名 token。它不只看 token 像不像,还会看是否过期、是否未生效、签发者和使用对象是否正确、签名是否可信。

数据流:它写入共享密钥并启动服务器,然后用 signed_bearer_token 造出多种 token:过期的、格式坏的、还没到生效时间的、签发者错的、受众错的、签名错的、正确的。前几种都应被拒,最后一种应能连接并初始化。

调用关系:这是顶层鉴权测试里最细的一项。它反复调用 assert_websocket_connect_rejected 检查坏 token,再调用 connect_websocket_with_bearer 走通好 token,signed_bearer_token 负责制造测试用 JWT。

调用图:调用 7 个内部函数(assert_websocket_connect_rejected, connect_websocket_with_bearer, create_config_toml, read_response_for_id, send_initialize_request, signed_bearer_token, spawn_websocket_server_with_args);外部调用 8 个(new, new, create_mock_responses_server_sequence_unchecked, assert_eq!, format!, json!, write, vec!)。

websocket_transport_rejects_short_signed_bearer_secret_configuration293–322 ↗
async fn websocket_transport_rejects_short_signed_bearer_secret_configuration() -> Result<()>

作用:这个测试确认签名密钥太短时,服务器不会带病启动。密钥太短就像锁芯太弱,签名 token 的安全性会打折。

数据流:它写入一个 too-short 的共享密钥文件,然后用签名 token 模式运行服务器并等待它退出。结果必须是启动失败,错误输出里也必须提示密钥至少 32 字节。

调用关系:这是顶层配置安全测试。它不需要建立 WebSocket,而是用 run_websocket_server_to_completion_with_args 检查服务器启动阶段就会拒绝坏配置。

调用图:调用 2 个内部函数(create_config_toml, run_websocket_server_to_completion_with_args);外部调用 6 个(from_utf8, new, new, create_mock_responses_server_sequence_unchecked, assert!, write)。

websocket_transport_rejects_unauthenticated_non_loopback_startup325–344 ↗
async fn websocket_transport_rejects_unauthenticated_non_loopback_startup() -> Result<()>

作用:这个测试确认没有鉴权时,服务器不能监听 0.0.0.0 这种对外开放地址。这样可以避免把无门禁的 WebSocket 服务暴露给局域网或公网。

数据流:它创建普通配置,然后让服务器尝试监听 ws://0.0.0.0:0 且不加鉴权参数。服务器应直接启动失败,错误输出里应说明拒绝启动非本机监听。

调用关系:这是顶层启动保护测试。它通过 run_websocket_server_to_completion_with_args 观察进程完整输出,而不是走正常连接流程。

调用图:调用 2 个内部函数(create_config_toml, run_websocket_server_to_completion_with_args);外部调用 5 个(from_utf8, new, new, create_mock_responses_server_sequence_unchecked, assert!)。

websocket_disconnect_keeps_last_subscribed_thread_loaded_until_idle_timeout347–376 ↗
async fn websocket_disconnect_keeps_last_subscribed_thread_loaded_until_idle_timeout() -> Result<()>

作用:这个测试确认客户端断线后,它最后订阅或加载的线程不会立刻消失。这样短暂掉线再重连时,用户还能看到刚才的会话。

数据流:它启动服务器并连接第一个客户端,初始化后创建一个线程,并确认线程在已加载列表里。然后关闭连接,第二个客户端重连并初始化,持续查询直到看到同一个线程仍在已加载列表中。

调用关系:这是顶层断线重连测试。它用 start_thread 创建测试对象,用 assert_loaded_threads 立即检查,用 wait_for_loaded_threads 处理重连后可能有一点延迟的状态恢复。

调用图:调用 8 个内部函数(assert_loaded_threads, connect_websocket, create_config_toml, read_response_for_id, send_initialize_request, spawn_websocket_server, start_thread, wait_for_loaded_threads);外部调用 3 个(new, new, create_mock_responses_server_sequence_unchecked)。

spawn_websocket_server378–380 ↗
async fn spawn_websocket_server(codex_home: &Path) -> Result<(Child, SocketAddr)>

作用:这个函数用默认的本机地址启动 WebSocket 版 app-server。测试不想关心复杂启动参数时,就用它拿到一个进程和实际监听地址。

数据流:输入是临时的 CODEX_HOME 路径。它把监听地址固定为 ws://127.0.0.1:0,并把实际工作交给 spawn_websocket_server_with_args,输出启动后的子进程和端口地址。

调用关系:它是多项测试和其他测试文件的快捷入口。真正拼命令、读 stderr、等待端口的工作都交给 spawn_websocket_server_with_args。

调用图:调用 1 个内部函数(spawn_websocket_server_with_args);被 8 处调用(command_exec_process_ids_are_connection_scoped_and_disconnect_terminates_process, websocket_disconnect_keeps_last_subscribed_thread_loaded_until_idle_timeout, websocket_transport_rejects_browser_origin_without_auth, websocket_transport_routes_per_connection_handshake_and_responses, websocket_transport_serves_health_endpoints_on_same_listener, start_ctrl_c_restart_fixture, thread_name_updated_broadcasts_for_loaded_threads, thread_name_updated_broadcasts_for_not_loaded_threads)。

spawn_websocket_server_with_args382–455 ↗
async fn spawn_websocket_server_with_args(
    codex_home: &Path,
    listen_url: &str,
    extra_args: &[String],
) -> Result<(Child, SocketAddr)>

作用:这个函数真正负责启动 app-server 测试进程,并等到它报告 WebSocket 监听地址。它让测试可以像操作真实服务一样操作一个独立进程。

数据流:输入是配置目录、监听 URL 和额外命令行参数。它找到 codex-app-server 可执行文件,设置环境变量,启动进程,读取 stderr 中打印的 ws://地址,去掉终端颜色码后解析成 SocketAddr,最后返回进程和地址,并继续后台转发日志。

调用关系:它被默认启动函数和需要特殊鉴权参数的测试调用。上层测试只拿结果连接即可,不用知道进程启动和日志解析细节。

调用图:被 3 处调用(spawn_websocket_server, websocket_transport_rejects_missing_and_invalid_capability_tokens, websocket_transport_verifies_signed_short_lived_bearer_tokens);外部调用 11 个(new, now, null, piped, with_capacity, new, cargo_bin, eprintln!, matches!, spawn (+1 more))。

connect_websocket457–459 ↗
async fn connect_websocket(bind_addr: SocketAddr) -> Result<WsClient>

作用:这个函数用无 token 的方式连接测试服务器。适合本机、无需鉴权的测试场景。

数据流:输入是服务器绑定地址。它把 bearer_token 设为空,然后调用 connect_websocket_with_bearer,输出一条可收发消息的 WebSocket 客户端流。

调用关系:它是许多测试的普通连接入口。真正处理地址转换、握手重试和请求头组装的工作由 connect_websocket_with_bearer 完成。

调用图:调用 1 个内部函数(connect_websocket_with_bearer);被 8 处调用(command_exec_process_ids_are_connection_scoped_and_disconnect_terminates_process, websocket_disconnect_keeps_last_subscribed_thread_loaded_until_idle_timeout, websocket_transport_rejects_browser_origin_without_auth, websocket_transport_routes_per_connection_handshake_and_responses, websocket_transport_serves_health_endpoints_on_same_listener, start_ctrl_c_restart_fixture, thread_name_updated_broadcasts_for_loaded_threads, thread_name_updated_broadcasts_for_not_loaded_threads)。

connect_websocket_with_bearer461–479 ↗
async fn connect_websocket_with_bearer(
    bind_addr: SocketAddr,
    bearer_token: Option<&str>,
) -> Result<WsClient>

作用:这个函数连接 WebSocket,并可选择带上 Bearer token。它还会在服务器刚启动、端口暂时没准备好时短暂重试。

数据流:输入是绑定地址和可选 token。它先把 0.0.0.0 这类地址转成客户端能连的地址,构造握手请求,然后循环调用 connect_async;成功就返回 WebSocket 流,超时仍失败就报错。

调用关系:它被普通连接函数和鉴权测试直接使用。它依赖 websocket_request 生成带请求头的握手请求。

调用图:调用 1 个内部函数(websocket_request);被 3 处调用(connect_websocket, websocket_transport_rejects_missing_and_invalid_capability_tokens, websocket_transport_verifies_signed_short_lived_bearer_tokens);外部调用 6 个(from_millis, now, bail!, format!, sleep, connect_async)。

assert_websocket_connect_rejected481–492 ↗
async fn assert_websocket_connect_rejected(
    bind_addr: SocketAddr,
    bearer_token: Option<&str>,
) -> Result<()>

作用:这个函数断言一次 WebSocket 连接会因为未授权而失败。它是“应该进不去”的通用检查。

数据流:输入是服务器地址和可选 token。它把期望状态固定为 401 Unauthorized,然后交给 assert_websocket_connect_rejected_with_headers 执行真正握手和判断。

调用关系:它被 token 鉴权和签名 token 测试反复调用。带 Origin 或特殊状态码的场景则直接用更底层的 assert_websocket_connect_rejected_with_headers。

调用图:调用 1 个内部函数(assert_websocket_connect_rejected_with_headers);被 2 处调用(websocket_transport_rejects_missing_and_invalid_capability_tokens, websocket_transport_verifies_signed_short_lived_bearer_tokens)。

assert_websocket_connect_rejected_with_headers494–516 ↗
async fn assert_websocket_connect_rejected_with_headers(
    bind_addr: SocketAddr,
    bearer_token: Option<&str>,
    origin: Option<&str>,
    expected_status: StatusCode,
) -> Result<()>

作用:这个函数检查 WebSocket 握手会被服务器用指定 HTTP 状态码拒绝。握手还没成功前,本质上就是一次 HTTP 请求。

数据流:输入是地址、可选 token、可选 Origin 和期望状态码。它构造请求并尝试 connect_async;如果竟然连上就报错,如果收到 HTTP 拒绝就比较状态码是否符合预期。

调用关系:它是所有“连接应被拒绝”检查的核心。assert_websocket_connect_rejected 用它的默认 401 版本,浏览器来源测试用它检查 403。

调用图:调用 1 个内部函数(websocket_request);被 2 处调用(assert_websocket_connect_rejected, websocket_transport_rejects_browser_origin_without_auth);外部调用 4 个(assert_eq!, bail!, format!, connect_async)。

run_websocket_server_to_completion_with_args518–539 ↗
async fn run_websocket_server_to_completion_with_args(
    codex_home: &Path,
    listen_url: &str,
    extra_args: &[String],
) -> Result<std::process::Output>

作用:这个函数启动 app-server 并等待它自己退出,用来测试“启动应该失败”的情况。它不连接服务器,只看进程结果。

数据流:输入是配置目录、监听 URL 和额外参数。它组装命令、设置环境变量、收集 stderr,并在超时时间内等待 output,输出进程退出状态和输出内容。

调用关系:它被两个启动失败测试使用:一个检查签名密钥太短,一个检查无鉴权对外监听。和 spawn_websocket_server_with_args 不同,它期待进程结束。

调用图:被 2 处调用(websocket_transport_rejects_short_signed_bearer_secret_configuration, websocket_transport_rejects_unauthenticated_non_loopback_startup);外部调用 5 个(null, piped, new, cargo_bin, timeout)。

http_get541–564 ↗
async fn http_get(
    client: &reqwest::Client,
    bind_addr: SocketAddr,
    path: &str,
) -> Result<reqwest::Response>

作用:这个函数对测试服务器发 HTTP GET 请求,并在服务刚启动时自动重试。主要用于检查健康检查接口。

数据流:输入是 reqwest HTTP 客户端、服务器地址和路径。它把地址转成可连接形式,循环发送 GET;成功返回响应,超过默认时间还失败就报错。

调用关系:它被健康检查测试调用。内部使用 connectable_bind_addr 处理 0.0.0.0 这种只能监听、不能直接连接的地址。

调用图:调用 1 个内部函数(connectable_bind_addr);被 1 处调用(websocket_transport_serves_health_endpoints_on_same_listener);外部调用 6 个(from_millis, now, get, bail!, format!, sleep)。

websocket_request566–588 ↗
fn websocket_request(
    url: &str,
    bearer_token: Option<&str>,
    origin: Option<&str>,
) -> Result<tokio_tungstenite::tungstenite::http::Request<()>>

作用:这个函数创建 WebSocket 握手请求,并按需要加上 Authorization 和 Origin 请求头。它把“怎么敲门”这件事统一起来。

数据流:输入是 URL、可选 Bearer token、可选 Origin。它先把 URL 转成 WebSocket 请求,再把 token 写成 Authorization: Bearer ...,把来源写进 Origin,最后返回请求对象。

调用关系:它被连接函数和拒绝断言函数使用。这样成功连接和失败连接都走同一种请求头构造逻辑。

调用图:被 2 处调用(assert_websocket_connect_rejected_with_headers, connect_websocket_with_bearer);外部调用 2 个(from_str, format!)。

send_initialize_request590–610 ↗
async fn send_initialize_request(
    stream: &mut WsClient,
    id: i64,
    client_name: &str,
) -> Result<()>

作用:这个函数向服务器发送 initialize 请求,也就是告诉服务器“我是哪个客户端,我准备开始了”。很多 JSON-RPC 服务在初始化前会拒绝其他请求。

数据流:输入是 WebSocket 流、请求编号和客户端名。它组装客户端信息,包括名称、标题和版本,把它转成 JSON,然后通过 send_request 发出 initialize 方法。

调用关系:它被几乎所有 WebSocket 测试调用,通常是连接成功后的第一步。真正发送 JSON-RPC 的底层动作由 send_request 和 send_jsonrpc 完成。

调用图:调用 1 个内部函数(send_request);被 9 处调用(command_exec_process_ids_are_connection_scoped_and_disconnect_terminates_process, websocket_disconnect_keeps_last_subscribed_thread_loaded_until_idle_timeout, websocket_transport_rejects_browser_origin_without_auth, websocket_transport_rejects_missing_and_invalid_capability_tokens, websocket_transport_routes_per_connection_handshake_and_responses, websocket_transport_serves_health_endpoints_on_same_listener, websocket_transport_verifies_signed_short_lived_bearer_tokens, start_ctrl_c_restart_fixture, initialize_both_clients);外部调用 1 个(to_value)。

start_thread612–626 ↗
async fn start_thread(stream: &mut WsClient, id: i64) -> Result<String>

作用:这个函数通过 WebSocket 请求服务器创建一个新线程,并返回线程 ID。这里的线程是应用里的会话/任务线索,不是操作系统线程。

数据流:输入是 WebSocket 流和请求编号。它发送 thread/start,请求使用 mock-model,然后读取同编号响应,把响应解析成 ThreadStartResponse,最后取出 thread.id。

调用关系:它被断线重连测试用来制造一个可观察的线程。它依赖 send_request 发请求,依赖 read_response_for_id 等到对应结果。

调用图:调用 2 个内部函数(read_response_for_id, send_request);被 1 处调用(websocket_disconnect_keeps_last_subscribed_thread_loaded_until_idle_timeout);外部调用 2 个(default, to_value)。

assert_loaded_threads628–640 ↗
async fn assert_loaded_threads(stream: &mut WsClient, id: i64, expected: &[&str]) -> Result<()>

作用:这个函数确认服务器当前报告的已加载线程列表正好等于预期。它像点名表,少一个多一个都算失败。

数据流:输入是 WebSocket 流、请求编号和预期线程 ID 列表。它请求已加载线程,把实际和预期都排序后比较,并确认没有下一页游标。

调用关系:它被断线重连测试在断线前使用。实际查询工作交给 request_loaded_threads。

调用图:调用 1 个内部函数(request_loaded_threads);被 1 处调用(websocket_disconnect_keeps_last_subscribed_thread_loaded_until_idle_timeout);外部调用 1 个(assert_eq!)。

wait_for_loaded_threads642–667 ↗
async fn wait_for_loaded_threads(
    stream: &mut WsClient,
    first_id: i64,
    expected: &[&str],
) -> Result<()>

作用:这个函数反复查询已加载线程列表,直到它变成预期结果或超时。它适合状态恢复可能有短暂延迟的场景。

数据流:输入是 WebSocket 流、第一个请求编号和预期线程 ID。它每次用递增编号请求列表,排序后比较;匹配就成功,不匹配就睡 50 毫秒再试,超过默认时间就报错。

调用关系:它被断线重连测试在新连接建立后使用。每次查询都交给 request_loaded_threads。

调用图:调用 1 个内部函数(request_loaded_threads);被 1 处调用(websocket_disconnect_keeps_last_subscribed_thread_loaded_until_idle_timeout);外部调用 3 个(from_millis, sleep, timeout)。

request_loaded_threads669–682 ↗
async fn request_loaded_threads(
    stream: &mut WsClient,
    id: i64,
) -> Result<ThreadLoadedListResponse>

作用:这个函数发送 thread/loaded/list 请求,并把响应解析成已加载线程列表。它把“问服务器现在加载了哪些线程”封装成一步。

数据流:输入是 WebSocket 流和请求编号。它发送带默认参数的 thread/loaded/list 请求,读取同编号响应,再转换成 ThreadLoadedListResponse 输出。

调用关系:它是 assert_loaded_threads 和 wait_for_loaded_threads 的共同底层工具。发送靠 send_request,读取靠 read_response_for_id。

调用图:调用 2 个内部函数(read_response_for_id, send_request);被 2 处调用(assert_loaded_threads, wait_for_loaded_threads);外部调用 2 个(default, to_value)。

send_config_read_request684–692 ↗
async fn send_config_read_request(stream: &mut WsClient, id: i64) -> Result<()>

作用:这个函数发送 config/read 请求,要求服务器返回配置内容。测试用它检查连接初始化前后服务器是否按规矩处理请求。

数据流:输入是 WebSocket 流和请求编号。它构造 includeLayers=false 的参数,通过 send_request 发出 config/read,不直接等待响应。

调用关系:它被多连接路由测试使用。响应由 read_response_for_id 或 read_error_for_id 另行读取。

调用图:调用 1 个内部函数(send_request);被 1 处调用(websocket_transport_routes_per_connection_handshake_and_responses);外部调用 1 个(json!)。

send_request694–707 ↗
async fn send_request(
    stream: &mut WsClient,
    method: &str,
    id: i64,
    params: Option<serde_json::Value>,
) -> Result<()>

作用:这个函数把方法名、编号和参数包装成一条 JSON-RPC 请求。调用者不用自己拼协议格式。

数据流:输入是 WebSocket 流、方法名、整数请求 ID 和可选 JSON 参数。它创建 JSONRPCRequest,放进 JSONRPCMessage::Request,再交给 send_jsonrpc 发到网络里。

调用关系:它是各种具体请求函数的公共出口,比如初始化、读配置、列线程、开线程。真正把文本帧发出去的是 send_jsonrpc。

调用图:调用 1 个内部函数(send_jsonrpc);被 9 处调用(command_exec_process_ids_are_connection_scoped_and_disconnect_terminates_process, request_loaded_threads, send_config_read_request, send_initialize_request, start_thread, send_thread_start_request, send_turn_start_request, thread_name_updated_broadcasts_for_loaded_threads, thread_name_updated_broadcasts_for_not_loaded_threads);外部调用 2 个(Request, Integer)。

send_jsonrpc709–715 ↗
async fn send_jsonrpc(stream: &mut WsClient, message: JSONRPCMessage) -> Result<()>

作用:这个函数把一条 JSON-RPC 消息变成文本并发送到 WebSocket。它是测试里“写入网络管子”的最低层工具。

数据流:输入是 WebSocket 流和 JSONRPCMessage。它把消息序列化成字符串,再作为 WebSocket 文本帧发送;发送失败就带上下文报错。

调用关系:它只被 send_request 调用。上层负责决定发什么方法,它只负责按 WebSocket 文本帧送出去。

调用图:被 1 处调用(send_request);外部调用 3 个(Text, send, to_string)。

read_response_for_id717–730 ↗
async fn read_response_for_id(
    stream: &mut WsClient,
    id: i64,
) -> Result<JSONRPCResponse>

作用:这个函数一直读 WebSocket,直到看到指定请求 ID 的成功响应。这样即使中间夹着通知或别的消息,也不会拿错。

数据流:输入是 WebSocket 流和目标整数 ID。它循环调用 read_jsonrpc_message,筛选 JSONRPCMessage::Response 且 id 匹配的消息,找到后返回该响应。

调用关系:它被大量测试和辅助函数使用,是读取请求结果的标准方式。底层帧处理、Ping/Pong 和 JSON 解析由 read_jsonrpc_message 完成。

调用图:调用 1 个内部函数(read_jsonrpc_message);被 11 处调用(request_loaded_threads, start_thread, websocket_disconnect_keeps_last_subscribed_thread_loaded_until_idle_timeout, websocket_transport_rejects_browser_origin_without_auth, websocket_transport_rejects_missing_and_invalid_capability_tokens, websocket_transport_routes_per_connection_handshake_and_responses, websocket_transport_serves_health_endpoints_on_same_listener, websocket_transport_verifies_signed_short_lived_bearer_tokens, start_ctrl_c_restart_fixture, initialize_both_clients (+1 more));外部调用 1 个(Integer)。

read_notification_for_method732–744 ↗
async fn read_notification_for_method(
    stream: &mut WsClient,
    method: &str,
) -> Result<JSONRPCNotification>

作用:这个函数一直读消息,直到看到指定方法名的通知。通知是不带请求 ID 的服务器主动消息。

数据流:输入是 WebSocket 流和目标方法名。它循环读取 JSON-RPC 消息,忽略无关消息,只在通知方法名匹配时返回。

调用关系:它被其他 WebSocket 测试用来等待广播类事件。底层读取统一交给 read_jsonrpc_message。

调用图:调用 1 个内部函数(read_jsonrpc_message);被 2 处调用(thread_name_updated_broadcasts_for_loaded_threads, thread_name_updated_broadcasts_for_not_loaded_threads)。

read_response_and_notification_for_method746–783 ↗
async fn read_response_and_notification_for_method(
    stream: &mut WsClient,
    id: i64,
    method: &str,
) -> Result<(JSONRPCResponse, JSONRPCNotification)>

作用:这个函数等待一对东西:某个请求 ID 的响应,以及某个方法名的通知。它适合测试一个操作既有直接回复,又会触发广播的场景。

数据流:输入是 WebSocket 流、目标请求 ID 和通知方法名。它循环读消息,分别保存匹配的响应和通知;如果同一通知提前重复出现就报错;两者都拿到后一起返回。

调用关系:它被线程名称广播相关测试使用。它依赖 read_jsonrpc_message 读取所有进来的消息,并在本函数里做配对逻辑。

调用图:调用 1 个内部函数(read_jsonrpc_message);被 2 处调用(thread_name_updated_broadcasts_for_loaded_threads, thread_name_updated_broadcasts_for_not_loaded_threads);外部调用 2 个(Integer, bail!)。

read_error_for_id785–795 ↗
async fn read_error_for_id(stream: &mut WsClient, id: i64) -> Result<JSONRPCError>

作用:这个函数一直读 WebSocket,直到看到指定请求 ID 的错误响应。它用于验证服务器应该拒绝某个请求时,拒绝的是正确那一次请求。

数据流:输入是 WebSocket 流和目标 ID。它循环读取 JSON-RPC 消息,只返回 JSONRPCMessage::Error 且 id 匹配的错误对象。

调用关系:它被多连接路由测试用于检查未初始化连接收到 Not initialized。底层读取仍由 read_jsonrpc_message 负责。

调用图:调用 1 个内部函数(read_jsonrpc_message);被 1 处调用(websocket_transport_routes_per_connection_handshake_and_responses);外部调用 1 个(Integer)。

read_jsonrpc_message797–818 ↗
async fn read_jsonrpc_message(stream: &mut WsClient) -> Result<JSONRPCMessage>

作用:这个函数从 WebSocket 里读出下一条真正的 JSON-RPC 消息。它还会处理 Ping/Pong 心跳,避免测试因为协议心跳卡住。

数据流:输入是 WebSocket 流。它带超时读取下一帧:文本帧就解析成 JSONRPCMessage 返回;Ping 就回 Pong;Pong 忽略;关闭、二进制帧或异常就报错。

调用关系:它是所有读取函数的底座。read_response_for_id、read_error_for_id、read_notification_for_method 等都靠它把 WebSocket 帧变成可理解的协议消息。

调用图:被 7 处调用(command_exec_process_ids_are_connection_scoped_and_disconnect_terminates_process, read_command_exec_delta_ws, read_initialize_response, read_error_for_id, read_notification_for_method, read_response_and_notification_for_method, read_response_for_id);外部调用 6 个(Pong, next, send, bail!, from_str, timeout)。

assert_no_message820–827 ↗
async fn assert_no_message(stream: &mut WsClient, wait_for: Duration) -> Result<()>

作用:这个函数确认某段时间内 WebSocket 没有收到任何消息。它用来证明服务器没有把别人的响应错发过来。

数据流:输入是 WebSocket 流和等待时长。它在这段时间里尝试读取下一帧;如果读到帧、读到错误或连接关闭,就失败;如果超时还没消息,就成功。

调用关系:它被多连接路由测试和其他广播测试使用。它和读取响应函数相反,专门验证“安静才是正确”。

调用图:被 4 处调用(command_exec_process_ids_are_connection_scoped_and_disconnect_terminates_process, websocket_transport_routes_per_connection_handshake_and_responses, thread_name_updated_broadcasts_for_loaded_threads, thread_name_updated_broadcasts_for_not_loaded_threads);外部调用 3 个(next, bail!, timeout)。

create_config_toml829–854 ↗
fn create_config_toml(
    codex_home: &Path,
    server_uri: &str,
    approval_policy: &str,
) -> std::io::Result<()>

作用:这个函数在临时 CODEX_HOME 下写一份测试用 config.toml。没有这份配置,app-server 不知道该用哪个模型和哪个假的后端服务。

数据流:输入是配置目录、假的模型服务地址和审批策略。它生成 TOML 文本,写入 model、approval_policy、sandbox_mode、mock_provider 等配置,输出是文件写入结果。

调用关系:它被本文件和很多其他测试反复使用,是启动测试服务器前的准备步骤。它不启动进程,只把磁盘配置摆好。

调用图:被 32 处调用(command_exec_accepts_permission_profile, command_exec_env_overrides_merge_with_server_environment_and_support_unset, command_exec_non_streaming_respects_output_cap, command_exec_permission_profile_does_not_reuse_default_network_proxy, command_exec_permission_profile_project_roots_use_command_cwd, command_exec_permission_profile_starts_selected_network_proxy, command_exec_pipe_streams_output_and_accepts_write, command_exec_process_ids_are_connection_scoped_and_disconnect_terminates_process, command_exec_rejects_disable_output_cap_with_output_bytes_cap, command_exec_rejects_disable_timeout_with_timeout_ms (+15 more));外部调用 3 个(join, format!, write)。

connectable_bind_addr856–866 ↗
fn connectable_bind_addr(bind_addr: SocketAddr) -> SocketAddr

作用:这个函数把监听地址转换成客户端真的能连接的地址。比如 0.0.0.0 表示“监听所有网卡”,但客户端不能直接连这个地址。

数据流:输入是服务器报告的 SocketAddr。如果是 IPv4 的 0.0.0.0,就换成 127.0.0.1;如果是 IPv6 的未指定地址,就换成 ::1;其他地址原样返回。

调用关系:它被 http_get 使用来发健康检查请求。WebSocket 连接路径里也有同类需求,用来避免测试因为监听地址形式而连不上。

调用图:被 1 处调用(http_get);外部调用 1 个(from)。

signed_bearer_token868–876 ↗
fn signed_bearer_token(shared_secret: &[u8], claims: serde_json::Value) -> Result<String>

作用:这个函数生成测试用的签名 Bearer token,也就是一个 HS256 JWT。JWT 可以理解成一张带签名的电子通行证,服务器能检查它有没有被篡改。

数据流:输入是共享密钥和 claims(例如过期时间、签发者、受众)。它把固定头部和 claims 做 URL 安全 base64 编码,用 HMAC-SHA256 计算签名,再拼成 header.claims.signature 字符串返回。

调用关系:它只被签名 token 鉴权测试调用。测试用它制造好 token 和坏 token,服务器端则负责验证这些 token 是否应被接受。

调用图:被 1 处调用(websocket_transport_verifies_signed_short_lived_bearer_tokens);外部调用 3 个(new_from_slice, format!, to_vec)。

app-server/tests/suite/v2/connection_handling_websocket_unix.rs源码 ↗
testintegration test

这个测试文件像是在模拟真实用户关服务器:服务器正通过 WebSocket(浏览器和服务端常用的一条长连接通道)处理一次对话,这时外面发来退出信号。它先搭一个假的后端接口,让一次回答故意慢几秒返回;再启动真实的 WebSocket app-server,连上去,初始化、开线程、开始一次 turn(一次用户提问到助手回答的处理过程)。等确认服务器已经把请求发给后端后,测试才发送 Unix 信号。重点检查三件事:第一次 Ctrl-C 或 SIGTERM 要“有礼貌”,先等当前 turn 做完再退出;第二次 Ctrl-C 或 SIGTERM 要能强制加快退出;SIGHUP 重复发送也不能打断正在跑的 turn。最后还会确认进程真的退出,并且 WebSocket 连接被关掉,避免留下半死不活的连接。

函数细节16
websocket_transport_ctrl_c_waits_for_running_turn_before_exit35–57 ↗
async fn websocket_transport_ctrl_c_waits_for_running_turn_before_exit() -> Result<()>

作用:测试用户按一次 Ctrl-C 时,服务器不会立刻杀掉正在处理的对话,而是等它完成后再退出。这是在保护用户的当前请求不被突然中断。

数据流:它先创建一个会延迟回答的服务器场景,拿到子进程和 WebSocket 连接;然后给进程发 Ctrl-C;接着检查进程在短时间内没有退出;最后等待它在较长时间内正常退出,并确认 WebSocket 断开。

调用关系:它是一个完整测试用例。开头把准备工作交给 start_ctrl_c_restart_fixture,发信号交给 send_sigint,检查“先别退”和“最终退出”分别交给 assert_process_does_not_exit_within 与 wait_for_process_exit_within,最后用 expect_websocket_disconnect 收尾。

调用图:调用 5 个内部函数(assert_process_does_not_exit_within, expect_websocket_disconnect, send_sigint, start_ctrl_c_restart_fixture, wait_for_process_exit_within);外部调用 3 个(from_millis, from_secs, assert!)。

websocket_transport_second_ctrl_c_forces_exit_while_turn_running60–83 ↗
async fn websocket_transport_second_ctrl_c_forces_exit_while_turn_running() -> Result<()>

作用:测试连续按两次 Ctrl-C 时,第二次会让服务器尽快退出。也就是说,第一次是温和提醒,第二次就是用户明确要求赶紧停。

数据流:它准备一个正在慢慢处理请求的服务器;先发一次 Ctrl-C,并确认进程没有马上退出;再发第二次 Ctrl-C;然后要求进程在较短时间内退出,并检查 WebSocket 连接也断开。

调用关系:这个测试复用 start_ctrl_c_restart_fixture 搭环境,复用 send_sigint 发两次信号。它和单次 Ctrl-C 测试形成对照:第一次看 graceful shutdown(优雅关闭),第二次看 forced exit(强制退出)。

调用图:调用 5 个内部函数(assert_process_does_not_exit_within, expect_websocket_disconnect, send_sigint, start_ctrl_c_restart_fixture, wait_for_process_exit_within);外部调用 3 个(from_millis, from_secs, assert!)。

websocket_transport_sigterm_waits_for_running_turn_before_exit86–108 ↗
async fn websocket_transport_sigterm_waits_for_running_turn_before_exit() -> Result<()>

作用:测试收到 SIGTERM 时,服务器也会像收到一次 Ctrl-C 一样,先等正在运行的对话结束再退出。SIGTERM 是系统常用的“请你结束进程”的信号。

数据流:它启动带有慢响应的测试服务器和 WebSocket 连接;给 app-server 子进程发送 SIGTERM;确认它短时间内还活着;再等待它完成当前 turn 后正常退出;最后确认连接关闭。

调用关系:这个测试把场景准备交给 start_ctrl_c_restart_fixture,把发信号交给 send_sigterm。后续的等待和断连检查使用同一组通用辅助函数,保证 SIGTERM 的行为和 Ctrl-C 的温和退出逻辑一致。

调用图:调用 5 个内部函数(assert_process_does_not_exit_within, expect_websocket_disconnect, send_sigterm, start_ctrl_c_restart_fixture, wait_for_process_exit_within);外部调用 3 个(from_millis, from_secs, assert!)。

websocket_transport_second_sigterm_forces_exit_while_turn_running111–134 ↗
async fn websocket_transport_second_sigterm_forces_exit_while_turn_running() -> Result<()>

作用:测试连续两个 SIGTERM 会触发更强硬的退出。这样系统管理工具如果重复要求停止服务,进程不会无限等待。

数据流:它先制造一个正在处理慢请求的进程;发第一次 SIGTERM 后确认进程没有立刻走;再发第二次 SIGTERM;随后检查进程能在短窗口内退出,并检查 WebSocket 断开。

调用关系:它由 start_ctrl_c_restart_fixture 提供统一场景,用 send_sigterm 连续发两次系统信号。它和单次 SIGTERM 测试一起说明:第一次给机会收尾,第二次要求立刻结束。

调用图:调用 5 个内部函数(assert_process_does_not_exit_within, expect_websocket_disconnect, send_sigterm, start_ctrl_c_restart_fixture, wait_for_process_exit_within);外部调用 3 个(from_millis, from_secs, assert!)。

websocket_transport_repeated_sighup_keeps_waiting_for_running_turn137–162 ↗
async fn websocket_transport_repeated_sighup_keeps_waiting_for_running_turn() -> Result<()>

作用:测试重复发送 SIGHUP 时,服务器仍然会等当前对话处理完再退出,不会因为第二次 SIGHUP 就强制停掉。SIGHUP 常被用来表示重新加载或重启一类动作。

数据流:它建立一个正在等待慢响应的 WebSocket 会话;发第一次 SIGHUP 后确认进程没有马上退出;再发第二次 SIGHUP 后再次确认它还在等;最后等当前任务完成后,进程正常退出并断开 WebSocket。

调用关系:这个测试使用 start_ctrl_c_restart_fixture 搭好正在运行的 turn,通过 send_sighup 发信号。它特意和 Ctrl-C、SIGTERM 的“第二次强制退出”行为区分开,说明 SIGHUP 的重复信号仍走耐心等待路线。

调用图:调用 5 个内部函数(assert_process_does_not_exit_within, expect_websocket_disconnect, send_sighup, start_ctrl_c_restart_fixture, wait_for_process_exit_within);外部调用 3 个(from_millis, from_secs, assert!)。

start_ctrl_c_restart_fixture171–207 ↗
async fn start_ctrl_c_restart_fixture(turn_delay: Duration) -> Result<GracefulCtrlCFixture>

作用:搭建所有信号退出测试共同需要的现场:一个真实 app-server 子进程、一条 WebSocket 连接,以及一次已经开始但还没结束的对话。

数据流:输入是一段人为设置的 turn 延迟时间;它启动假的后端服务,配置临时的 codex home,启动 WebSocket app-server,连接上去,发送初始化、thread/start 和 turn/start 请求,并等到假的后端确实收到 /responses 请求;最后返回临时目录、假服务器、子进程和 WebSocket 客户端。

调用关系:五个顶层测试都会先调用它。它内部把具体步骤分给 connect_websocket、create_config_toml、spawn_websocket_server、send_initialize_request、send_thread_start_request、send_turn_start_request、read_response_for_id 和 wait_for_responses_post,等于把复杂开场打包成一个可复用的测试夹具。

调用图:调用 10 个内部函数(connect_websocket, create_config_toml, read_response_for_id, send_initialize_request, spawn_websocket_server, send_thread_start_request, send_turn_start_request, wait_for_responses_post, sse_response, start_mock_server);被 5 处调用(websocket_transport_ctrl_c_waits_for_running_turn_before_exit, websocket_transport_repeated_sighup_keeps_waiting_for_running_turn, websocket_transport_second_ctrl_c_forces_exit_while_turn_running, websocket_transport_second_sigterm_forces_exit_while_turn_running, websocket_transport_sigterm_waits_for_running_turn_before_exit);外部调用 8 个(from_secs, given, new, create_final_assistant_message_sse_response, to_response, assert_eq!, method, path_regex)。

send_thread_start_request209–220 ↗
async fn send_thread_start_request(stream: &mut WsClient, id: i64) -> Result<()>

作用:通过 WebSocket 发出“创建一个新对话线程”的请求。没有线程,后面就没有地方放用户的问题和助手回答。

数据流:它接收 WebSocket 客户端和请求编号;把 ThreadStartParams 转成 JSON 数据,其中指定 mock-model;然后调用通用的 send_request,把 method 写成 thread/start 发出去;成功时不直接返回业务内容,只表示发送完成。

调用关系:它只在 start_ctrl_c_restart_fixture 里使用。它负责夹具流程中的“开线程”一步,真正的 WebSocket 发送动作交给 send_request。

调用图:调用 1 个内部函数(send_request);被 1 处调用(start_ctrl_c_restart_fixture);外部调用 2 个(default, to_value)。

send_turn_start_request222–238 ↗
async fn send_turn_start_request(stream: &mut WsClient, id: i64, thread_id: &str) -> Result<()>

作用:通过 WebSocket 发出“开始处理一次用户输入”的请求,也就是让服务器真的进入正在运行的 turn 状态。

数据流:它拿到 WebSocket 客户端、请求编号和线程 id;把一条文本输入“Hello”放进 TurnStartParams;转成 JSON 后用 send_request 以 turn/start 方法发给服务器;结果是服务器开始处理这次用户消息。

调用关系:它由 start_ctrl_c_restart_fixture 调用,紧跟在线程创建之后。这个函数是整个测试的关键触发点,因为只有它发出后,后面的退出信号才是在“任务运行中”到达的。

调用图:调用 1 个内部函数(send_request);被 1 处调用(start_ctrl_c_restart_fixture);外部调用 3 个(default, to_value, vec!)。

wait_for_responses_post240–258 ↗
async fn wait_for_responses_post(server: &wiremock::MockServer, wait_for: Duration) -> Result<()>

作用:等待假的后端服务收到发往 /responses 的 POST 请求,确认 app-server 已经真正开始向模型后端要回答。

数据流:它接收 mock server 和最长等待时间;循环查看 mock server 已收到的请求;如果发现 POST 且路径以 /responses 结尾,就返回成功;如果超时还没看到,就报错;循环中会短暂睡眠,避免一直空转。

调用关系:start_ctrl_c_restart_fixture 在发送 turn/start 后调用它。它像一个路口观察员,确认请求已经开上路了,顶层测试才开始发送退出信号,这样测试到的确实是“运行中退出”的场景。

调用图:被 1 处调用(start_ctrl_c_restart_fixture);外部调用 5 个(from_millis, now, received_requests, bail!, sleep)。

send_sigint260–262 ↗
fn send_sigint(process: &Child) -> Result<()>

作用:给测试中的 app-server 子进程发送 SIGINT,也就是通常按 Ctrl-C 产生的中断信号。

数据流:它接收一个子进程对象;不自己操作系统命令,而是把具体信号字符串 -INT 交给 send_signal;返回发送成功或失败。

调用关系:两个 Ctrl-C 相关测试调用它。它是更通用的 send_signal 的小包装,让测试代码读起来更像人的动作:发送 Ctrl-C。

调用图:调用 1 个内部函数(send_signal);被 2 处调用(websocket_transport_ctrl_c_waits_for_running_turn_before_exit, websocket_transport_second_ctrl_c_forces_exit_while_turn_running)。

send_sigterm264–266 ↗
fn send_sigterm(process: &Child) -> Result<()>

作用:给 app-server 子进程发送 SIGTERM,这是 Unix 系统里常见的“请正常结束进程”信号。

数据流:它接收子进程对象;把 -TERM 这个信号参数交给 send_signal;如果底层 kill 命令成功,就返回成功,否则把错误传上去。

调用关系:两个 SIGTERM 相关测试调用它。它把测试意图表达清楚,实际调用系统 kill 命令的工作由 send_signal 完成。

调用图:调用 1 个内部函数(send_signal);被 2 处调用(websocket_transport_second_sigterm_forces_exit_while_turn_running, websocket_transport_sigterm_waits_for_running_turn_before_exit)。

send_sighup268–270 ↗
fn send_sighup(process: &Child) -> Result<()>

作用:给 app-server 子进程发送 SIGHUP,用来测试这类挂起或重载信号到来时的退出等待行为。

数据流:它接收子进程对象;把 -HUP 传给 send_signal;成功时表示信号已经发给进程,失败时返回错误。

调用关系:重复 SIGHUP 的测试调用它。它和 send_sigint、send_sigterm 是同一类薄包装,都把具体信号名交给 send_signal。

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

send_signal272–285 ↗
fn send_signal(process: &Child, signal: &str) -> Result<()>

作用:真正执行发送 Unix 信号的底层工具函数。它通过系统的 kill 命令,把指定信号发给测试启动的子进程。

数据流:它接收子进程和信号字符串;先取出进程 id;然后运行 kill 命令并传入信号和 pid;如果命令退出码成功,就返回成功;如果没有 pid 或 kill 失败,就返回带说明的错误。

调用关系:send_sigint、send_sigterm、send_sighup 都调用它。这样不同测试不用重复写取 pid 和调用 kill 的细节,只需要选择要发哪一种信号。

调用图:被 3 处调用(send_sighup, send_sigint, send_sigterm);外部调用 3 个(id, new, bail!)。

assert_process_does_not_exit_within287–293 ↗
async fn assert_process_does_not_exit_within(process: &mut Child, window: Duration) -> Result<()>

作用:检查进程在一小段时间内没有退出。它用来证明服务器收到第一次温和退出信号后,确实还在等当前任务收尾。

数据流:它接收子进程和一个时间窗口;在这个窗口里等待进程结束;如果等超时,说明进程还活着,测试通过;如果进程提前退出,就报错;如果等待进程本身失败,也把错误返回。

调用关系:五个顶层测试都会用它来检查“不要太早退出”。它通常跟在 send_sigint、send_sigterm 或 send_sighup 后面,是验证优雅关闭行为的第一道关。

调用图:被 5 处调用(websocket_transport_ctrl_c_waits_for_running_turn_before_exit, websocket_transport_repeated_sighup_keeps_waiting_for_running_turn, websocket_transport_second_ctrl_c_forces_exit_while_turn_running, websocket_transport_second_sigterm_forces_exit_while_turn_running, websocket_transport_sigterm_waits_for_running_turn_before_exit);外部调用 3 个(wait, bail!, timeout)。

wait_for_process_exit_within295–304 ↗
async fn wait_for_process_exit_within(
    process: &mut Child,
    window: Duration,
    timeout_context: &'static str,
) -> Result<std::process::ExitStatus>

作用:等待进程在规定时间内退出,并拿到它的退出状态。它用来确认服务器最终不是卡死,而是完成收尾后结束了。

数据流:它接收子进程、最长等待时间和超时提示文字;在限定时间内等待进程结束;成功时返回退出状态;超时或等待失败时返回带上下文的错误。

调用关系:五个顶层测试都会在后半段调用它。前面的 assert_process_does_not_exit_within 证明“没退太早”,这个函数证明“最后确实退了”。

调用图:被 5 处调用(websocket_transport_ctrl_c_waits_for_running_turn_before_exit, websocket_transport_repeated_sighup_keeps_waiting_for_running_turn, websocket_transport_second_ctrl_c_forces_exit_while_turn_running, websocket_transport_second_sigterm_forces_exit_while_turn_running, websocket_transport_sigterm_waits_for_running_turn_before_exit);外部调用 2 个(wait, timeout)。

expect_websocket_disconnect306–327 ↗
async fn expect_websocket_disconnect(stream: &mut WsClient) -> Result<()>

作用:确认 WebSocket 连接最终断开。进程退出后,客户端这边也应该收到关闭或连接结束,而不是一直挂着。

数据流:它接收 WebSocket 客户端;循环读取服务器发来的帧;如果看到连接关闭、流结束或读取错误,就认为断开成功;如果收到 ping,就回 pong 保持协议礼貌;其他文本、二进制或 pong 帧会忽略并继续等;如果一直等不到断开就超时报错。

调用关系:所有顶层测试在确认进程退出后都会调用它。它负责检查最后的网络收尾,确保服务器退出不仅是进程层面结束,也正确关掉了 WebSocket 连接。

调用图:被 5 处调用(websocket_transport_ctrl_c_waits_for_running_turn_before_exit, websocket_transport_repeated_sighup_keeps_waiting_for_running_turn, websocket_transport_second_ctrl_c_forces_exit_while_turn_running, websocket_transport_second_sigterm_forces_exit_while_turn_running, websocket_transport_sigterm_waits_for_running_turn_before_exit);外部调用 4 个(Pong, next, send, timeout)。

握手和协议门控

这些测试验证连接建立时客户端可见的协议约定,覆盖证明转发和实验性 API 的选择启用要求。

app-server/tests/suite/v2/attestation.rs源码 ↗
testtest execution

这个测试文件像一次完整的彩排:先搭一个假的 WebSocket 后端服务器,再写入一份临时配置,让应用服务器以为自己正在连接 ChatGPT。测试启动应用服务器后,用 JSON-RPC(一种用 JSON 格式来发请求和响应的通信方式)模拟桌面客户端初始化、开线程、发一轮用户消息。关键点在中途:服务器会反过来向客户端发起 attestation/generate 请求,也就是“请给我一个证明令牌”。测试收到后回给它固定令牌。最后,假的 WebSocket 服务器检查真正的握手请求,确认里面多了 x-oai-attestation 请求头,而且内容格式正确。辅助函数 create_chatgpt_websocket_config 只是负责写测试用配置文件,让整场测试指向本地假服务器,不碰真实服务。

函数细节2
attestation_generate_round_trip_adds_header_to_responses_websocket_handshake35–172 ↗
async fn attestation_generate_round_trip_adds_header_to_responses_websocket_handshake() -> Result<()>

作用:这是主测试,验证“客户端生成证明令牌 → 应用服务器拿到令牌 → WebSocket 握手带上证明请求头”这条链路是否真的通了。它不是只测一个小函数,而是把应用服务器、临时配置、假后端和客户端消息流程串起来测一遍。

数据流:进去的是测试临时目录、假 WebSocket 服务器地址、模拟的 ChatGPT 登录信息,以及一段用户输入“Hello”。它先写配置和认证文件,再启动测试版应用服务器,发送初始化、开线程、开始一轮对话等请求;当服务器要求生成 attestation 时,测试回给固定 token。出来的结果是一次断言:假后端收到的 WebSocket 握手里必须有 x-oai-attestation 头,并且值等于测试期待的 JSON 字符串;同时它也会确认至少发生过一次 attestation 请求。

调用关系:它是整个文件的入口测试。它会调用 start_websocket_server_with_headers 搭假后端,调用 create_chatgpt_websocket_config 写本地配置,调用 TestAppServer::new_with_env 启动测试服务器,还会使用请求/响应转换工具来读写 JSON-RPC 消息。测试结束前,它向假后端查询握手记录,用来证明前面客户端回传的 token 确实被应用服务器用于真正的 WebSocket 连接。

调用图:调用 4 个内部函数(new, new_with_env, create_chatgpt_websocket_config, start_websocket_server_with_headers);外部调用 14 个(default, try_from, new, Integer, default, to_response, write_chatgpt_auth, assert!, assert_eq!, bail! (+4 more))。

create_chatgpt_websocket_config174–196 ↗
fn create_chatgpt_websocket_config(codex_home: &Path, server_uri: &str) -> std::io::Result<()>

作用:这个辅助函数负责给临时的测试目录写一份 config.toml 配置文件。它的作用是把应用服务器引到本地假 WebSocket 服务,而不是去连真实的 ChatGPT 后端。

数据流:进去的是一个临时目录路径 codex_home 和假服务器地址 server_uri。它把这些信息拼成一段 TOML 配置文本,里面指定模型、供应商、base_url、使用 responses 协议、开启 WebSocket、要求 OpenAI 认证等测试条件;最后把文本写到 codex_home/config.toml。出来的是写文件的成功或失败结果,如果写失败会把系统 I/O 错误返回给调用者。

调用关系:它只被主测试 attestation_generate_round_trip_adds_header_to_responses_websocket_handshake 调用,位置在启动应用服务器之前。因为应用服务器启动时会读取这个配置,所以这个函数相当于提前摆好路标,确保后续所有网络请求都走本地假服务器,方便测试检查握手请求头。

调用图:被 1 处调用(attestation_generate_round_trip_adds_header_to_responses_websocket_handshake);外部调用 3 个(join, format!, write)。

app-server/tests/suite/v2/experimental_api.rs源码 ↗
testtest run

这个测试文件像一组门禁检查。项目里有些功能还属于实验性能力,不应该随便开放给所有客户端,所以客户端初始化时必须明确说自己支持 experimentalApi。这里每个测试都会启动一个临时的测试服务器,用一个临时目录当作配置家目录,然后让客户端初始化时把 experimental_api 设成 false。接着测试去调用某个实验性方法、实时语音会话、记忆模式设置、线程设置更新,或者在线程启动参数里塞入实验性字段。正确结果不是成功,而是服务器返回一个 JSON-RPC 错误。JSON-RPC 可以理解成“用 JSON 传请求和回复的一套格式”。文件里也有一个反例测试:普通的 thread/start 不带实验性动态工具时,即使没有 experimentalApi 也应该允许通过。这样可以防止两种问题:一是实验功能被误开放,二是普通功能被误伤。

函数细节11
mock_experimental_method_requires_experimental_api_capability31–59 ↗
async fn mock_experimental_method_requires_experimental_api_capability() -> Result<()>

作用:这个测试确认 mock/experimentalMethod 这个实验性方法不能被普通客户端调用。客户端没有打开 experimentalApi 时,服务器必须返回明确的权限错误。

数据流:进入时没有外部输入,测试自己创建临时家目录和测试服务器。它先用 experimental_api 为 false 的能力信息初始化客户端,再发送 mock 实验方法请求,然后等服务器回错误。最后它把错误交给 assert_experimental_capability_error 检查,确认错误码、错误文字和原因都对。

调用关系:这是整组门禁测试中的一个具体用例。它用 default_client_info 造一个标准客户端身份,用 TestAppServer 模拟真实服务器通信,收到错误后交给 assert_experimental_capability_error 做统一验收。

调用图:调用 3 个内部函数(new, assert_experimental_capability_error, default_client_info);外部调用 5 个(new, bail!, Integer, default, timeout)。

realtime_conversation_start_requires_experimental_api_capability62–103 ↗
async fn realtime_conversation_start_requires_experimental_api_capability() -> Result<()>

作用:这个测试确认启动实时对话也属于实验性能力,不能在没有 experimentalApi 的情况下使用。这里测的是普通实时启动参数,不带 WebRTC 传输。

数据流:测试创建临时服务器,初始化时声明不支持 experimentalApi。随后发送 thread/realtime/start 请求,请求里带线程 ID、音频输出方式和一句提示词。服务器应该拒绝它,测试读取对应请求 ID 的错误消息,并检查错误说明是 thread/realtime/start 需要 experimentalApi。

调用关系:它和其他测试走同一条初始化和读回复流程。它依赖 default_client_info 生成客户端信息,最后复用 assert_experimental_capability_error 来确认拒绝理由格式一致。

调用图:调用 3 个内部函数(new, assert_experimental_capability_error, default_client_info);外部调用 4 个(new, bail!, Integer, timeout)。

thread_memory_mode_set_requires_experimental_api_capability106–137 ↗
async fn thread_memory_mode_set_requires_experimental_api_capability() -> Result<()>

作用:这个测试确认设置线程记忆模式是实验性操作,普通客户端不能直接改。这里用 Disabled 模式做例子。

数据流:测试先启动测试服务器,并用不含 experimentalApi 的能力初始化。然后它发送 thread/memoryMode/set 请求,内容是某个线程 ID 和要设置的记忆模式。服务器返回错误后,测试检查这个错误是否准确说明 thread/memoryMode/set 需要 experimentalApi。

调用关系:它是实验 API 拦截规则的一个分支用例。流程由测试函数自己驱动,公共的客户端信息来自 default_client_info,公共的错误断言来自 assert_experimental_capability_error。

调用图:调用 3 个内部函数(new, assert_experimental_capability_error, default_client_info);外部调用 4 个(new, bail!, Integer, timeout)。

thread_settings_update_requires_experimental_api_capability140–171 ↗
async fn thread_settings_update_requires_experimental_api_capability() -> Result<()>

作用:这个测试确认更新线程设置也是实验性接口,不能被未声明实验能力的客户端调用。哪怕请求内容基本是默认值,也应该被挡住。

数据流:测试创建临时服务器并初始化客户端,能力里 experimental_api 是 false。接着它发送 thread/settings/update 请求,指定线程 ID,其他设置使用默认值。然后它等待服务器返回错误,并验证错误内容指向 thread/settings/update 需要 experimentalApi。

调用关系:它覆盖的是线程设置更新这一类请求。它通过 default_client_info 复用标准客户端身份,通过 assert_experimental_capability_error 复用统一的错误检查标准。

调用图:调用 3 个内部函数(new, assert_experimental_capability_error, default_client_info);外部调用 5 个(default, new, bail!, Integer, timeout)。

realtime_webrtc_start_requires_experimental_api_capability174–217 ↗
async fn realtime_webrtc_start_requires_experimental_api_capability() -> Result<()>

作用:这个测试确认用 WebRTC 启动实时会话时,也必须有 experimentalApi。WebRTC 可以简单理解成浏览器和服务之间做实时音视频通信的一套技术。

数据流:测试启动服务器并用不支持 experimentalApi 的客户端初始化。随后发送 thread/realtime/start 请求,和普通实时启动类似,但 transport 里带了 WebRTC 的 SDP offer。SDP offer 可以理解成“我这边支持怎样连接”的协商说明。服务器应拒绝请求,测试再检查错误是否说 thread/realtime/start 需要 experimentalApi。

调用关系:它补充覆盖实时会话的 WebRTC 入口,避免只测普通实时参数而漏掉另一种传输方式。它同样使用 default_client_info 准备身份,并把错误验证交给 assert_experimental_capability_error。

调用图:调用 3 个内部函数(new, assert_experimental_capability_error, default_client_info);外部调用 4 个(new, bail!, Integer, timeout)。

thread_start_mock_field_requires_experimental_api_capability220–254 ↗
async fn thread_start_mock_field_requires_experimental_api_capability() -> Result<()>

作用:这个测试确认 thread/start 请求里如果带了 mock_experimental_field 这个实验性字段,就必须有 experimentalApi。也就是说,不只是整个方法,单个字段也可能需要门禁。

数据流:测试先启动一个假的响应服务,再写入 config.toml,让应用知道测试时该连这个假服务。然后它启动测试服务器,初始化时声明不支持 experimentalApi。接着发送 thread/start 请求,并塞入 mock_experimental_field。服务器应返回错误,测试检查错误理由是 thread/start.mockExperimentalField 需要 experimentalApi。

调用关系:这个用例需要 create_config_toml 先准备模型服务配置,否则 thread/start 缺少后端地址。它用 default_client_info 完成初始化,用 assert_experimental_capability_error 验证实验字段确实被拦住。

调用图:调用 4 个内部函数(new, assert_experimental_capability_error, create_config_toml, default_client_info);外部调用 7 个(default, new, new, bail!, Integer, create_mock_responses_server_sequence_unchecked, timeout)。

thread_start_without_dynamic_tools_allows_without_experimental_api_capability257–291 ↗
async fn thread_start_without_dynamic_tools_allows_without_experimental_api_capability() -> Result<()>

作用:这个测试确认普通的 thread/start 不应该被实验 API 门禁误伤。客户端没有 experimentalApi,但只要没用实验性动态工具或字段,就应该正常启动线程。

数据流:测试创建假的响应服务和临时配置文件,再启动测试服务器。客户端初始化时仍然声明 experimental_api 为 false。之后它发送一个普通 thread/start 请求,只指定 mock-model。测试等待成功响应,并把 JSON-RPC 响应转换成 ThreadStartResponse,说明服务器确实放行了。

调用关系:这是整组测试里的“反向样本”。其他测试都期待失败,它期待成功,用来证明门禁规则不是粗暴地禁止整个 thread/start,而是只禁止其中的实验性用法。它依赖 create_config_toml 准备后端配置,也用 default_client_info 准备客户端身份。

调用图:调用 3 个内部函数(new, create_config_toml, default_client_info);外部调用 8 个(default, new, new, bail!, Integer, create_mock_responses_server_sequence_unchecked, to_response, timeout)。

thread_start_granular_approval_policy_requires_experimental_api_capability294–335 ↗
async fn thread_start_granular_approval_policy_requires_experimental_api_capability() -> Result<()>

作用:这个测试确认更细粒度的审批策略 AskForApproval::Granular 属于实验性能力。没有 experimentalApi 的客户端不能在线程启动时启用这种高级审批配置。

数据流:测试先建好假的模型响应服务,并写入配置文件。启动测试服务器后,客户端用 experimental_api 为 false 的能力初始化。随后发送 thread/start 请求,里面带 granular 审批策略,分别设置沙箱审批、权限请求等开关。服务器应拒绝请求,测试检查错误理由是 askForApproval.granular 需要 experimentalApi。

调用关系:它覆盖的是 thread/start 参数里的另一种实验性字段。create_config_toml 负责让 thread/start 有可用的测试模型服务,default_client_info 负责初始化身份,assert_experimental_capability_error 负责最后的统一验错。

调用图:调用 4 个内部函数(new, assert_experimental_capability_error, create_config_toml, default_client_info);外部调用 7 个(default, new, new, bail!, Integer, create_mock_responses_server_sequence_unchecked, timeout)。

default_client_info337–343 ↗
fn default_client_info() -> ClientInfo

作用:这个小工具函数生成一份标准测试客户端信息。这样每个测试不用重复写客户端名字、版本这些固定内容。

数据流:它不接收输入,只读取测试支持库里的 DEFAULT_CLIENT_NAME。然后组装一个 ClientInfo:名字用默认测试客户端名,标题为空,版本写成 0.1.0。最后把这份客户端身份返回给调用者。

调用关系:几乎所有测试在初始化服务器会话时都会调用它。它的作用像统一的“测试身份证模板”,保证各个用例差别只在要测试的能力和请求,而不是客户端基本信息。

调用图:被 8 处调用(mock_experimental_method_requires_experimental_api_capability, realtime_conversation_start_requires_experimental_api_capability, realtime_webrtc_start_requires_experimental_api_capability, thread_memory_mode_set_requires_experimental_api_capability, thread_settings_update_requires_experimental_api_capability, thread_start_granular_approval_policy_requires_experimental_api_capability, thread_start_mock_field_requires_experimental_api_capability, thread_start_without_dynamic_tools_allows_without_experimental_api_capability)。

assert_experimental_capability_error345–352 ↗
fn assert_experimental_capability_error(error: JSONRPCError, reason: &str)

作用:这个断言函数专门检查“缺少 experimentalApi”这种错误是否长得完全正确。它避免每个测试都重复写同样的错误码和错误文字检查。

数据流:它接收服务器返回的 JSONRPCError 和一个 reason 字符串。它检查错误码必须是 -32600,错误消息必须是“某某 requires experimentalApi capability”,并且错误 data 必须为空。如果任何一项不符合,测试就失败。

调用关系:所有期待被实验 API 门禁挡住的测试都会调用它。它把错误格式的标准集中到一个地方,确保不同实验接口返回的错误一致。

调用图:被 7 处调用(mock_experimental_method_requires_experimental_api_capability, realtime_conversation_start_requires_experimental_api_capability, realtime_webrtc_start_requires_experimental_api_capability, thread_memory_mode_set_requires_experimental_api_capability, thread_settings_update_requires_experimental_api_capability, thread_start_granular_approval_policy_requires_experimental_api_capability, thread_start_mock_field_requires_experimental_api_capability);外部调用 1 个(assert_eq!)。

create_config_toml354–375 ↗
fn create_config_toml(codex_home: &Path, server_uri: &str) -> std::io::Result<()>

作用:这个辅助函数给临时测试目录写一份 config.toml 配置文件。需要启动 thread/start 的测试要靠它告诉应用:模型服务地址在哪里、用哪个假 provider。

数据流:它接收临时家目录路径和假的服务器地址。它在这个目录下拼出 config.toml 文件路径,然后写入一段 TOML 配置文本,里面包含模型名、审批策略、沙箱模式、模型 provider 名称、base_url 和重试次数等。写入成功返回空结果,失败则返回文件写入错误。

调用关系:thread_start 相关测试会先调用它,再启动 TestAppServer。它把外部假响应服务接进测试配置里,让后面的 thread/start 请求能走到可控的模拟后端,而不是连真实服务。

调用图:被 3 处调用(thread_start_granular_approval_policy_requires_experimental_api_capability, thread_start_mock_field_requires_experimental_api_capability, thread_start_without_dynamic_tools_allows_without_experimental_api_capability);外部调用 3 个(join, format!, write)。

实时对话流程

此套件测试跨传输、协议版本、事件中介和委托回合的完整端到端实时对话行为。

app-server/tests/suite/v2/realtime_conversation.rs源码 ↗
testtest run / realtime request handling

实时对话像一场三方电话:前端客户端、app-server、上游实时模型和后台代理都要配合。这个测试文件就是搭了一个“摄影棚”,把外部服务都换成可控的假服务,然后模拟真实用户开始线程、登录、启动实时会话、追加音频或文字、让模型发消息、让模型调用后台代理、再检查 app-server 对外发出的通知是否正确。它还覆盖 V1/V2 两种实时协议、WebRTC 的 SDP 握手、声音列表、功能开关、启动上下文、后台任务执行 shell 命令、并发时不堵住音频等细节。没有这些测试,实时会话这种跨网络、跨协议、跨任务的功能很容易在改代码时悄悄坏掉,而且问题通常只会在真实使用时才暴露。

函数细节70
RealtimeCallRequestCapture::new102–106 ↗
fn new() -> Self

作用:创建一个请求记录器,用来记住测试中发到“创建实时通话”接口的 HTTP 请求。这样测试后面可以回看 app-server 到底发了什么。

数据流:进去没有参数 → 它新建一个可共享的空列表,并用互斥锁(一把锁,防止同时写乱)保护起来 → 出来一个 RealtimeCallRequestCapture,后续 mock 服务每收到请求都会往里面塞。

调用关系:搭测试环境时会被 RealtimeE2eHarness::new_with_main_loop_responses_server_and_sandbox 和若干 WebRTC 测试使用;之后 RealtimeCallRequestCapture::matches 负责记录请求,RealtimeCallRequestCapture::single_request 负责取出来检查。

调用图:被 5 处调用(new_with_main_loop_responses_server_and_sandbox, realtime_webrtc_start_emits_sdp_notification, conversation_webrtc_start_posts_generated_session, conversation_webrtc_start_uses_avas_architecture_query, conversation_webrtc_start_uses_configured_call_base_url_for_avas);外部调用 3 个(new, new, new)。

RealtimeCallRequestCapture::single_request108–115 ↗
fn single_request(&self) -> WiremockRequest

作用:取出唯一一次被记录的实时通话创建请求。它顺便确认请求数量正好是一个,防止测试漏发或多发。

数据流:进去是记录器自己 → 它锁住内部请求列表,检查长度必须为 1 → 出来这条请求的副本;如果数量不对,测试直接失败。

调用关系:WebRTC 相关测试在启动实时会话后调用它,用来检查 multipart 表单、SDP 和 session JSON 是否正确。

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

RealtimeCallRequestCapture::matches119–125 ↗
fn matches(&self, request: &WiremockRequest) -> bool

作用:这是 wiremock 的匹配器入口。每次 mock HTTP 服务器收到请求时,它都把请求记下来,并总是说“匹配成功”。

数据流:进去是一条 HTTP 请求 → 它把请求克隆一份放进共享列表 → 出来 true,表示这个 mock 规则可以响应该请求。

调用关系:它被 wiremock 框架自动调用;记录下来的数据之后会被 RealtimeCallRequestCapture::single_request 拿来断言。

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

normalized_json_string128–131 ↗
fn normalized_json_string(raw: &str) -> Result<String>

作用:把一段 JSON 文本变成标准格式的 JSON 字符串,避免因为空格、换行顺序不同导致测试误判。

数据流:进去是一段原始 JSON 字符串 → 它先解析成 JSON 值,再重新序列化成紧凑字符串 → 出来标准化后的字符串;解析失败会返回错误。

调用关系:assert_call_create_multipart 和 realtime_webrtc_start_emits_sdp_notification 用它比较请求体里的 session JSON,重点比较内容而不是排版。

调用图:被 2 处调用(assert_call_create_multipart, realtime_webrtc_start_emits_sdp_notification);外部调用 2 个(from_str, to_string)。

GatedSseResponse::respond139–149 ↗
fn respond(&self, _: &WiremockRequest) -> ResponseTemplate

作用:生成一个可以“卡住”的 SSE 响应。SSE 是服务器持续推送事件的一种 HTTP 流;这里用来模拟后台代理迟迟不结束。

数据流:进去是 mock 收到的请求 → 它先等一个通道信号;收到信号后才把预先准备好的 SSE 文本包装成响应 → 出来 HTTP 响应模板。

调用关系:wiremock 在收到 /responses 请求时调用它;并发测试用它拖住后台代理,验证实时音频和第二次工具调用不会被堵住。

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

RealtimeTestVersion::config_value159–164 ↗
fn config_value(self) -> &'static str

作用:把测试里用的实时协议版本枚举,转换成配置文件里的字符串。

数据流:进去是 V1 或 V2 → 它按版本选择 "v1" 或 "v2" → 出来可写进 config.toml 的文本。

调用关系:create_config_toml_with_realtime_version 写测试配置时调用它,确保 app-server 按指定协议启动实时功能。

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

RealtimeTestSandbox::config_value174–179 ↗
fn config_value(self) -> &'static str

作用:把测试里的沙箱模式转换成配置文件里的字符串。沙箱模式决定测试中的工具能不能访问系统。

数据流:进去是 ReadOnly 或 DangerFullAccess → 它变成 "read-only" 或 "danger-full-access" → 出来配置文本。

调用关系:create_config_toml_with_realtime_version 用它写 sandbox_mode;需要执行 shell 命令的测试会选择更开放的模式。

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

RealtimeE2eHarness::new213–227 ↗
async fn new(
        realtime_version: RealtimeTestVersion,
        main_loop: MainLoopResponsesScript,
        realtime_sideband: RealtimeSidebandScript,
    ) -> Result<Self>

作用:创建一套默认只读沙箱的实时端到端测试环境。它让测试不用每次手写一堆 mock 和初始化步骤。

数据流:进去是实时版本、后台 Responses 脚本、实时 WebSocket 脚本 → 它启动假的 Responses 服务,再转交给更底层的构造函数 → 出来一个已登录、已开线程的测试工具箱。

调用关系:大多数实时 V1/V2 测试都从这里开始;它把具体搭建工作交给 RealtimeE2eHarness::new_with_main_loop_responses_server_and_sandbox。

调用图:被 11 处调用(realtime_automatic_handoff_output_is_item_and_append_speaks, realtime_automatic_standalone_output_is_item_and_append_speaks, webrtc_v1_default_automatic_output_uses_handoff_append, webrtc_v1_handoff_request_delegates_context_and_manual_append_speaks, webrtc_v1_start_posts_offer_returns_sdp_and_joins_sideband, webrtc_v2_assistant_output_without_handoff_reaches_realtime_context, webrtc_v2_background_agent_progress_is_sent_before_function_output, webrtc_v2_background_agent_tool_call_delegates_and_returns_function_output, webrtc_v2_forwards_audio_and_text_between_client_and_sideband, webrtc_v2_text_input_is_append_only_when_response_is_cancelled (+1 more));外部调用 2 个(new_with_main_loop_responses_server_and_sandbox, create_mock_responses_server_sequence_unchecked)。

RealtimeE2eHarness::new_with_sandbox229–244 ↗
async fn new_with_sandbox(
        realtime_version: RealtimeTestVersion,
        main_loop: MainLoopResponsesScript,
        realtime_sideband: RealtimeSidebandScript,
        sandbox: RealtimeTestSa

作用:创建测试环境,但允许指定沙箱权限。它主要用于需要执行 shell 命令的测试。

数据流:进去是版本、后台脚本、实时脚本和沙箱模式 → 它启动假的 Responses 服务,并把沙箱设置一起传下去 → 出来完整测试工具箱。

调用关系:webrtc_v2_tool_call_delegated_turn_can_execute_shell_tool 用它打开 danger-full-access,验证后台代理能跑命令。

调用图:被 1 处调用(webrtc_v2_tool_call_delegated_turn_can_execute_shell_tool);外部调用 2 个(new_with_main_loop_responses_server_and_sandbox, create_mock_responses_server_sequence_unchecked)。

RealtimeE2eHarness::new_with_main_loop_responses_server246–258 ↗
async fn new_with_main_loop_responses_server(
        realtime_version: RealtimeTestVersion,
        main_loop_responses_server: MockServer,
        realtime_sideband: RealtimeSidebandScript,
    ) ->

作用:创建测试环境,但复用外部已经准备好的 Responses mock 服务器。这样测试可以设置更复杂的响应规则。

数据流:进去是版本、现成的 Responses mock 服务器和实时脚本 → 它补上默认只读沙箱并继续初始化 → 出来完整测试工具箱。

调用关系:需要“卡住 SSE 流”的并发测试会先自己搭服务器,再调用它进入通用初始化流程。

调用图:被 2 处调用(webrtc_v2_background_agent_steering_ack_requests_response_create, webrtc_v2_tool_call_does_not_block_sideband_audio);外部调用 1 个(new_with_main_loop_responses_server_and_sandbox)。

RealtimeE2eHarness::new_with_main_loop_responses_server_and_sandbox260–313 ↗
async fn new_with_main_loop_responses_server_and_sandbox(
        realtime_version: RealtimeTestVersion,
        main_loop_responses_server: MockServer,
        realtime_sideband: RealtimeSidebandScri

作用:这是测试工具箱真正的总装函数。它把假 HTTP、假 WebSocket、临时配置、app-server、登录和线程启动全串起来。

数据流:进去是实时版本、Responses mock、实时 sideband 脚本和沙箱模式 → 它注册 WebRTC call mock、启动 WebSocket 服务器、写 config.toml、启动 TestAppServer、登录、创建线程 → 出来包含所有这些资源和 thread_id 的 RealtimeE2eHarness。

调用关系:其他 new 系列函数最终都调用它;它还调用 create_config_toml_with_realtime_version 和 login_with_api_key,是端到端测试的地基。

调用图:调用 5 个内部函数(new, new, create_config_toml_with_realtime_version, login_with_api_key, start_websocket_server_with_headers);外部调用 11 个(given, uri, new, new, Integer, default, Override, to_response, timeout, method (+1 more))。

RealtimeE2eHarness::start_webrtc_realtime315–320 ↗
async fn start_webrtc_realtime(&mut self, offer_sdp: &str) -> Result<StartedWebrtcRealtime>

作用:用默认设置启动一个 WebRTC 实时会话。WebRTC 可以理解成浏览器常用的实时音视频连接方式。

数据流:进去是一段 offer SDP(通话邀请描述)→ 它把“是否把 Codex 回复作为条目”留空 → 出来 started 通知和 answer SDP。

调用关系:许多 WebRTC 测试用它开始会话;真正发送请求的工作交给 RealtimeE2eHarness::start_webrtc_realtime_with_codex_responses_as_items。

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

RealtimeE2eHarness::start_webrtc_realtime_with_codex_response_items322–331 ↗
async fn start_webrtc_realtime_with_codex_response_items(
        &mut self,
        offer_sdp: &str,
    ) -> Result<StartedWebrtcRealtime>

作用:启动 WebRTC 实时会话,并明确要求把 Codex 后台回复作为实时上下文条目发送给模型。

数据流:进去是 offer SDP → 它把 codex_responses_as_items 设为 true → 出来 started 通知和 answer SDP。

调用关系:测试后台代理输出如何进入实时上下文时会用它;底层仍交给 start_webrtc_realtime_with_codex_responses_as_items。

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

RealtimeE2eHarness::start_webrtc_realtime_with_codex_responses_as_items333–377 ↗
async fn start_webrtc_realtime_with_codex_responses_as_items(
        &mut self,
        offer_sdp: &str,
        codex_responses_as_items: Option<bool>,
    ) -> Result<StartedWebrtcRealtime>

作用:真正向 app-server 发送“启动实时 WebRTC 会话”的 JSON-RPC 请求,并等待客户端应该看到的两个结果。

数据流:进去是 offer SDP 和一个可选开关 → 它组装 ThreadRealtimeStartParams,发请求,等响应,再读 started 通知和 sdp 通知 → 出来 StartedWebrtcRealtime。

调用关系:start_webrtc_realtime 和 start_webrtc_realtime_with_codex_response_items 都依赖它;它模拟桌面客户端启动实时语音会话的完整路径。

调用图:调用 2 个内部函数(read_stream_until_response_message, send_thread_realtime_start_request);被 2 处调用(start_webrtc_realtime, start_webrtc_realtime_with_codex_response_items);外部调用 3 个(Integer, to_response, timeout)。

RealtimeE2eHarness::read_notification379–381 ↗
async fn read_notification(&mut self, method: &str) -> Result<T>

作用:从测试客户端读取某种通知,并把 JSON 参数转成指定类型。

数据流:进去是通知方法名 → 它把内部 mcp 客户端交给通用 read_notification → 出来反序列化后的通知对象。

调用关系:各个测试用它等待 turn/started、realtime/delta、closed 等事件;它只是 RealtimeE2eHarness 上的便捷包装。

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

RealtimeE2eHarness::sideband_outbound_request385–394 ↗
async fn sideband_outbound_request(&self, request_index: usize) -> Value

作用:读取 app-server 发给假实时 WebSocket 服务器的第 N 条消息。sideband 可以理解成 app-server 和实时模型之间的旁路控制连接。

数据流:进去是请求序号 → 它等待假 WebSocket 服务器收到对应消息,并设置超时 → 出来这条消息的 JSON 内容。

调用关系:大量测试用它检查 app-server 是否正确发送 session.update、conversation.item.create、response.create 等上游协议消息。

调用图:调用 1 个内部函数(wait_for_request);外部调用 1 个(timeout)。

RealtimeE2eHarness::append_audio396–418 ↗
async fn append_audio(&mut self, thread_id: String) -> Result<()>

作用:模拟客户端给实时会话追加一小段音频。

数据流:进去是 thread_id → 它发送 append_audio JSON-RPC 请求,音频内容固定为测试用的 Base64 字符串,并等待成功响应 → 出来空结果;app-server 状态会收到这段音频。

调用关系:音频转发和并发测试调用它,再用 sideband_outbound_request 检查上游是否收到 input_audio_buffer.append。

调用图:调用 2 个内部函数(read_stream_until_response_message, send_thread_realtime_append_audio_request);外部调用 3 个(Integer, to_response, timeout)。

RealtimeE2eHarness::append_text420–437 ↗
async fn append_text(&mut self, thread_id: String, text: &str) -> Result<()>

作用:模拟客户端给实时会话追加一段文字输入。

数据流:进去是 thread_id 和文本 → 它发送 append_text 请求,角色设为用户,并等待成功响应 → 出来空结果;实时 sideband 后面应收到用户消息条目。

调用关系:文字转发和“响应进行中也只能追加文本”的回归测试会用它。

调用图:调用 2 个内部函数(read_stream_until_response_message, send_thread_realtime_append_text_request);外部调用 3 个(Integer, to_response, timeout)。

RealtimeE2eHarness::append_speech439–455 ↗
async fn append_speech(&mut self, thread_id: String, text: &str) -> Result<()>

作用:模拟客户端要求把一段文字作为语音进展说给实时模型听。

数据流:进去是 thread_id 和文本 → 它发送 append_speech 请求并等待成功响应 → 出来空结果;上游应收到能触发实时回应的消息。

调用关系:测试手动语音更新时调用它,随后检查 V1 的 handoff.append 或 V2 的 progress update 加 response.create。

调用图:调用 2 个内部函数(read_stream_until_response_message, send_thread_realtime_append_speech_request);外部调用 3 个(Integer, to_response, timeout)。

RealtimeE2eHarness::main_loop_responses_requests457–459 ↗
async fn main_loop_responses_requests(&self) -> Result<Vec<Value>>

作用:取出后台代理主循环发给 Responses 接口的请求,方便测试检查提示词和上下文。

数据流:进去是 harness 自己 → 它把内部 mock server 交给 responses_requests → 出来所有 /responses 请求的 JSON 列表。

调用关系:后台代理交接、转向和 shell 工具测试用它确认 app-server 真的把正确内容送给后台代理。

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

RealtimeE2eHarness::shutdown461–463 ↗
async fn shutdown(self)

作用:关闭测试用的假实时 WebSocket 服务器,清理后台资源。

数据流:进去是整个 harness → 它调用 realtime_server.shutdown → 出来没有返回值,服务器停止。

调用关系:多数 harness 测试在结束前调用它,避免测试进程里留下未关闭的服务器。

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

main_loop_responses466–468 ↗
fn main_loop_responses(responses: Vec<String>) -> MainLoopResponsesScript

作用:把一组预写好的 SSE 响应包装成后台代理脚本。

数据流:进去是字符串列表 → 它放进 MainLoopResponsesScript 结构 → 出来可交给 harness 的脚本对象。

调用关系:很多测试用它描述后台代理会返回什么;no_main_loop_responses 也靠它生成空脚本。

调用图:被 9 处调用(no_main_loop_responses, realtime_automatic_handoff_output_is_item_and_append_speaks, realtime_automatic_standalone_output_is_item_and_append_speaks, webrtc_v1_default_automatic_output_uses_handoff_append, webrtc_v1_handoff_request_delegates_context_and_manual_append_speaks, webrtc_v2_assistant_output_without_handoff_reaches_realtime_context, webrtc_v2_background_agent_progress_is_sent_before_function_output, webrtc_v2_background_agent_tool_call_delegates_and_returns_function_output, webrtc_v2_tool_call_delegated_turn_can_execute_shell_tool)。

no_main_loop_responses470–472 ↗
fn no_main_loop_responses() -> MainLoopResponsesScript

作用:生成一个“后台代理不会返回任何响应”的脚本。

数据流:进去没有参数 → 它创建空列表并调用 main_loop_responses → 出来空的 MainLoopResponsesScript。

调用关系:只测试实时 sideband、不涉及后台代理的场景会用它。

调用图:调用 1 个内部函数(main_loop_responses);被 4 处调用(webrtc_v1_start_posts_offer_returns_sdp_and_joins_sideband, webrtc_v2_forwards_audio_and_text_between_client_and_sideband, webrtc_v2_text_input_is_append_only_when_response_is_cancelled, webrtc_v2_text_input_is_append_only_while_response_is_active);外部调用 1 个(new)。

realtime_sideband474–476 ↗
fn realtime_sideband(connections: Vec<WebSocketConnectionConfig>) -> RealtimeSidebandScript

作用:把一组 WebSocket 连接脚本包装成实时 sideband 脚本。

数据流:进去是多个 WebSocketConnectionConfig → 它放进 RealtimeSidebandScript → 出来可交给 harness 的实时脚本。

调用关系:几乎所有 harness 测试都用它描述假实时服务器每次连接会发哪些事件。

调用图:被 14 处调用(realtime_automatic_handoff_output_is_item_and_append_speaks, realtime_automatic_standalone_output_is_item_and_append_speaks, webrtc_v1_default_automatic_output_uses_handoff_append, webrtc_v1_handoff_request_delegates_context_and_manual_append_speaks, webrtc_v1_start_posts_offer_returns_sdp_and_joins_sideband, webrtc_v2_assistant_output_without_handoff_reaches_realtime_context, webrtc_v2_background_agent_progress_is_sent_before_function_output, webrtc_v2_background_agent_steering_ack_requests_response_create, webrtc_v2_background_agent_tool_call_delegates_and_returns_function_output, webrtc_v2_forwards_audio_and_text_between_client_and_sideband (+4 more))。

realtime_sideband_connection478–487 ↗
fn realtime_sideband_connection(
    realtime_server_events: Vec<Vec<Value>>,
) -> WebSocketConnectionConfig

作用:创建一个标准的假实时 WebSocket 连接配置:按脚本发事件,处理完请求后关闭。

数据流:进去是按请求轮次排列的服务器事件 → 它填上默认响应头、无延迟、请求后关闭 → 出来 WebSocketConnectionConfig。

调用关系:realtime_sideband 和各测试用它快速写一条普通 sideband 连接;open_realtime_sideband_connection 在它基础上改成不自动关闭。

调用图:被 1 处调用(open_realtime_sideband_connection);外部调用 1 个(new)。

open_realtime_sideband_connection489–496 ↗
fn open_realtime_sideband_connection(
    realtime_server_events: Vec<Vec<Value>>,
) -> WebSocketConnectionConfig

作用:创建一条不会自动关闭的假实时 WebSocket 连接。

数据流:进去是服务器事件脚本 → 它先创建普通连接配置,再把 close_after_requests 改为 false → 出来保持打开的连接配置。

调用关系:用于验证 WebRTC 启动后连接应该继续活着,而不是拿到 SDP 后马上断开。

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

session_updated498–503 ↗
fn session_updated(realtime_session_id: &str) -> Value

作用:生成一条假实时服务器发来的 session.updated 事件。

数据流:进去是实时会话 ID → 它拼成 JSON,里面带 session id 和 backend prompt → 出来 JSON 值。

调用关系:大量 sideband 脚本用它作为连接成功后的第一条服务端事件。

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

v2_background_agent_tool_call505–516 ↗
fn v2_background_agent_tool_call(call_id: &str, prompt: &str) -> Value

作用:生成一条 V2 实时协议里的 background_agent 函数调用事件。

数据流:进去是 call_id 和 prompt → 它把 prompt 放进 arguments 字符串,包装成 conversation.item.done 的 function_call → 出来 JSON 事件。

调用关系:V2 后台代理测试用它模拟实时模型要求 app-server 把任务交给后台代理。

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

realtime_conversation_streams_v2_notifications519–831 ↗
async fn realtime_conversation_streams_v2_notifications() -> Result<()>

作用:测试 V2 实时会话能把上游事件正确转成客户端通知。它覆盖音频、文本增量、转写完成、handoff 请求、错误和关闭。

数据流:进去由测试框架启动 → 它搭 mock 服务、启动线程和实时会话、追加音频文字、读取通知并逐项断言 → 出来测试通过或失败;同时验证 sideband 收到的请求。

调用关系:这是一个综合性端到端测试,直接调用 create_config_toml、login_with_api_key 和 read_notification 等基础工具。

调用图:调用 4 个内部函数(new, create_config_toml, login_with_api_key, start_websocket_server);外部调用 10 个(new, Integer, default, create_mock_responses_server_sequence_unchecked, to_response, assert!, assert_eq!, skip_if_no_network!, timeout, vec!)。

realtime_start_can_skip_startup_context834–905 ↗
async fn realtime_start_can_skip_startup_context() -> Result<()>

作用:测试启动实时会话时可以明确跳过启动上下文。启动上下文是 app-server 额外塞给模型的背景说明。

数据流:进去由测试框架启动 → 它把 include_startup_context 设为 false,启动实时会话,然后检查 session.update 的 instructions 只有 backend prompt → 出来断言结果。

调用关系:它验证 create_config_toml 生成的默认上下文不会在用户要求跳过时被加入。

调用图:调用 4 个内部函数(new, create_config_toml, login_with_api_key, start_websocket_server);外部调用 11 个(new, new, Integer, default, create_mock_responses_server_sequence_unchecked, to_response, assert!, assert_eq!, skip_if_no_network!, timeout (+1 more))。

realtime_text_output_modality_requests_text_output_and_final_transcript908–1045 ↗
async fn realtime_text_output_modality_requests_text_output_and_final_transcript() -> Result<()>

作用:测试当输出模式选文字时,app-server 会向实时模型请求文字输出,并只发一次最终文本通知。

数据流:进去由测试框架启动 → 它设置 output_modality 为 Text,假服务器发两段文字增量和完成事件 → 出来检查客户端收到两段 delta 和一个 done,且没有重复 done。

调用关系:它直接搭 mock 服务,验证实时输出模式和去重逻辑。

调用图:调用 4 个内部函数(new, create_config_toml, login_with_api_key, start_websocket_server);外部调用 11 个(new, new, Integer, default, create_mock_responses_server_sequence_unchecked, to_response, assert!, assert_eq!, skip_if_no_network!, timeout (+1 more))。

realtime_list_voices_returns_supported_names1048–1105 ↗
async fn realtime_list_voices_returns_supported_names() -> Result<()>

作用:测试“列出可用声音”接口返回完整且正确的 V1/V2 声音列表和默认声音。

数据流:进去由测试框架启动 → 它写配置、启动 app-server、发送 list_voices 请求 → 出来响应对象,并和预期列表逐项比较。

调用关系:这个测试不需要网络实时服务器真正工作,只验证协议层公开的声音清单。

调用图:调用 2 个内部函数(new, create_config_toml);外部调用 5 个(new, Integer, to_response, assert_eq!, timeout)。

realtime_conversation_stop_emits_closed_notification1108–1194 ↗
async fn realtime_conversation_stop_emits_closed_notification() -> Result<()>

作用:测试客户端主动停止实时会话后,会收到 closed 通知。

数据流:进去由测试框架启动 → 它启动实时会话,再发送 stop 请求 → 出来检查 closed 的 thread_id 和关闭原因。

调用关系:它覆盖正常 teardown 路径,确认停止请求不会只返回响应而忘了通知客户端。

调用图:调用 4 个内部函数(new, create_config_toml, login_with_api_key, start_websocket_server);外部调用 11 个(new, new, Integer, default, create_mock_responses_server_sequence_unchecked, to_response, assert!, assert_eq!, skip_if_no_network!, timeout (+1 more))。

realtime_webrtc_start_emits_sdp_notification1197–1363 ↗
async fn realtime_webrtc_start_emits_sdp_notification() -> Result<()>

作用:测试 WebRTC 启动时,app-server 会向上游创建 call,并把 answer SDP 通知给客户端。

数据流:进去由测试框架启动 → 它 mock /v1/realtime/calls,发送带 offer SDP 的 start 请求,读取 started 和 sdp 通知,检查 sideband URI 和 multipart 请求体 → 出来断言结果。

调用关系:它使用 RealtimeCallRequestCapture 和 normalized_json_string,重点覆盖 WebRTC call-create 这条 HTTP 腿。

调用图:调用 6 个内部函数(new, new, create_config_toml, login_with_api_key, normalized_json_string, start_websocket_server_with_headers);外部调用 17 个(given, new, from_utf8, new, new, Integer, default, Override, create_mock_responses_server_sequence_unchecked, to_response (+7 more))。

webrtc_v1_start_posts_offer_returns_sdp_and_joins_sideband1366–1424 ↗
async fn webrtc_v1_start_posts_offer_returns_sdp_and_joins_sideband() -> Result<()>

作用:测试 V1 WebRTC 会话会提交 offer、返回 answer,并加入实时 sideband 连接。

数据流:进去由测试框架启动 → 它用 harness 创建 V1 环境,启动 WebRTC,检查 started/sdp、call-create 表单、session.update 和握手 URI → 出来确认连接不会立刻关闭。

调用关系:它走 RealtimeE2eHarness::new 和 start_webrtc_realtime,并用 assert_call_create_multipart、assert_v1_session_update 辅助断言。

调用图:调用 6 个内部函数(new, assert_call_create_multipart, assert_v1_session_update, no_main_loop_responses, realtime_sideband, v1_session_create_json);外部调用 6 个(from_millis, assert!, assert_eq!, skip_if_no_network!, timeout, vec!)。

webrtc_v1_default_automatic_output_uses_handoff_append1427–1481 ↗
async fn webrtc_v1_default_automatic_output_uses_handoff_append() -> Result<()>

作用:测试 V1 默认情况下,后台代理自动输出会通过 conversation.handoff.append 送回实时侧。

数据流:进去由测试框架启动 → 它启动 V1 实时会话,发一个普通 turn,后台 mock 返回文本 → 出来检查 sideband 第 2 条请求是 handoff.append。

调用关系:它验证普通后台回复和 V1 实时通道的连接方式。

调用图:调用 4 个内部函数(new, assert_v1_session_update, main_loop_responses, realtime_sideband);外部调用 7 个(default, Integer, to_response, assert_eq!, skip_if_no_network!, timeout, vec!)。

webrtc_v1_handoff_request_delegates_context_and_manual_append_speaks1484–1585 ↗
async fn webrtc_v1_handoff_request_delegates_context_and_manual_append_speaks() -> Result<()>

作用:测试 V1 的 handoff 请求会携带转写上下文交给后台代理,而手动追加语音会真的发给实时侧播报。

数据流:进去由测试框架启动 → 它让 sideband 发转写、助手语音转写和 handoff.requested,等待后台 turn 完成,再检查 Responses 请求和 sideband 更新 → 出来确认上下文和手动语音都正确。

调用关系:它同时用 assert_call_create_multipart、response_request_contains_text 和 append_speech,覆盖 V1 交接全链路。

调用图:调用 6 个内部函数(new, assert_call_create_multipart, assert_v1_session_update, main_loop_responses, realtime_sideband, v1_session_create_json);外部调用 4 个(assert!, assert_eq!, skip_if_no_network!, vec!)。

realtime_automatic_standalone_output_is_item_and_append_speaks1588–1664 ↗
async fn realtime_automatic_standalone_output_is_item_and_append_speaks() -> Result<()>

作用:测试 V2 非 handoff 场景下,后台自动输出只作为上下文条目加入;手动 append_speech 才会触发实时回应。

数据流:进去由测试框架启动 → 它启动 V2 会话并要求 Codex 回复作为 item,发普通 turn,检查自动输出是 developer item 且没有 response.create,再手动追加语音 → 出来检查 progress update 和 response.create。

调用关系:它用 assert_v2_backend_item_update、assert_v2_progress_update 和 assert_v2_response_create 区分自动上下文和主动播报。

调用图:调用 6 个内部函数(new, assert_v2_backend_item_update, assert_v2_progress_update, assert_v2_response_create, main_loop_responses, realtime_sideband);外部调用 9 个(default, from_millis, Integer, to_response, assert!, assert_eq!, skip_if_no_network!, timeout, vec!)。

realtime_automatic_handoff_output_is_item_and_append_speaks1667–1738 ↗
async fn realtime_automatic_handoff_output_is_item_and_append_speaks() -> Result<()>

作用:测试 V2 handoff 后的自动最终输出也是安静地写入上下文,而不是立刻让实时模型说话。

数据流:进去由测试框架启动 → 它让实时侧调用 background_agent,等待后台 turn 完成,检查后台结果 item、function_call_output,以及没有自动 response.create;再手动追加语音 → 出来检查手动路径会触发 response.create。

调用关系:它覆盖 V2 工具调用完成后的安静更新策略。

调用图:调用 7 个内部函数(new, assert_v2_backend_item_update, assert_v2_function_call_output, assert_v2_progress_update, assert_v2_response_create, main_loop_responses, realtime_sideband);外部调用 6 个(from_millis, assert!, assert_eq!, skip_if_no_network!, timeout, vec!)。

webrtc_v2_assistant_output_without_handoff_reaches_realtime_context1741–1818 ↗
async fn webrtc_v2_assistant_output_without_handoff_reaches_realtime_context() -> Result<()>

作用:测试没有 handoff 的普通助手输出也会进入 V2 实时上下文,并且长文本会被截断。

数据流:进去由测试框架启动 → 它让后台返回一段 commentary 和超长最终文本,发普通 turn → 出来检查 sideband 收到后台 item,最终文本带前缀且长度不超过限制。

调用关系:它使用 assert_v2_backend_item_update,验证后台普通输出和实时上下文同步。

调用图:调用 4 个内部函数(new, assert_v2_backend_item_update, main_loop_responses, realtime_sideband);外部调用 8 个(default, Integer, to_response, assert!, assert_eq!, skip_if_no_network!, timeout, vec!)。

webrtc_v2_forwards_audio_and_text_between_client_and_sideband1821–1897 ↗
async fn webrtc_v2_forwards_audio_and_text_between_client_and_sideband() -> Result<()>

作用:测试 V2 WebRTC 会话能双向转发:客户端音频/文字送到 sideband,sideband 音频/转写送回客户端。

数据流:进去由测试框架启动 → 它启动会话,append_audio 和 append_text,假 sideband 发转写和音频 → 出来检查客户端通知和上游请求内容。

调用关系:它用 append_audio、append_text、assert_v2_session_update 和 sideband_outbound_request 验证基础管道。

调用图:调用 4 个内部函数(new, assert_v2_session_update, no_main_loop_responses, realtime_sideband);外部调用 4 个(assert!, assert_eq!, skip_if_no_network!, vec!)。

webrtc_v2_text_input_is_append_only_while_response_is_active1904–1973 ↗
async fn webrtc_v2_text_input_is_append_only_while_response_is_active() -> Result<()>

作用:回归测试:当 V2 实时模型已有响应在进行中时,追加文字只应追加消息,不应再请求新响应。

数据流:进去由测试框架启动 → 它让 sideband 标记 response.created,连续追加两段文字,再追加音频 → 出来检查两段文字都是 user item,音频照常转发,没有多余 response.create。

调用关系:它防止旧 bug 复发:活跃响应期间文字输入导致重复响应请求。

调用图:调用 5 个内部函数(new, assert_v2_session_update, assert_v2_user_text_item, no_main_loop_responses, realtime_sideband);外部调用 3 个(assert_eq!, skip_if_no_network!, vec!)。

webrtc_v2_text_input_is_append_only_when_response_is_cancelled1978–2031 ↗
async fn webrtc_v2_text_input_is_append_only_when_response_is_cancelled() -> Result<()>

作用:回归测试:即使活跃响应最后被取消,期间追加文字也只能是追加消息。

数据流:进去由测试框架启动 → 它让 sideband 发 response.created 后稍后 cancelled,期间追加两段文字和一段音频 → 出来检查文字 item 和音频 append 正确。

调用关系:它和 active 版本互补,覆盖 response.cancelled 这条状态变化路径。

调用图:调用 5 个内部函数(new, assert_v2_session_update, assert_v2_user_text_item, no_main_loop_responses, realtime_sideband);外部调用 3 个(assert_eq!, skip_if_no_network!, vec!)。

webrtc_v2_background_agent_tool_call_delegates_and_returns_function_output2039–2128 ↗
async fn webrtc_v2_background_agent_tool_call_delegates_and_returns_function_output() -> Result<()>

作用:测试 V2 background_agent 工具调用会启动后台代理,并把最终结果作为函数调用输出返回实时侧。

数据流:进去由测试框架启动 → 它让 sideband 发多段转写和 tool call,后台 mock 返回文本 → 出来检查 Responses 请求包含整理后的实时对话上下文,并检查 progress update 和 function_call_output。

调用关系:它是 V2 后台代理交接的核心测试,使用 response_request_contains_text、assert_v2_progress_update 和 assert_v2_function_call_output。

调用图:调用 5 个内部函数(new, assert_v2_function_call_output, assert_v2_progress_update, main_loop_responses, realtime_sideband);外部调用 4 个(assert!, assert_eq!, skip_if_no_network!, vec!)。

webrtc_v2_background_agent_steering_ack_requests_response_create2137–2217 ↗
async fn webrtc_v2_background_agent_steering_ack_requests_response_create() -> Result<()>

作用:测试后台代理任务已经在跑时,第二个 background_agent 调用会被当作“转向指令”,并让实时模型能播报确认。

数据流:进去由测试框架启动 → 它用 GatedSseResponse 卡住第一次后台任务,让 sideband 连发两个 tool call → 出来检查第二个调用先收到 steering acknowledgement,再收到 response.create;释放闸门后检查后续 Responses 请求包含转向文本。

调用关系:它用自建 mock server 和 RealtimeE2eHarness::new_with_main_loop_responses_server,专门验证并发中的后台任务转向。

调用图:调用 7 个内部函数(new_with_main_loop_responses_server, assert_v2_function_call_output, assert_v2_response_create, assert_v2_session_update, realtime_sideband, sse, start_mock_server);外部调用 9 个(given, new, assert!, assert_eq!, channel, skip_if_no_network!, vec!, method, path_regex)。

webrtc_v2_background_agent_progress_is_sent_before_function_output2220–2259 ↗
async fn webrtc_v2_background_agent_progress_is_sent_before_function_output() -> Result<()>

作用:测试 V2 后台代理完成时,进度消息会先发给实时侧,然后才发函数调用完成输出。

数据流:进去由测试框架启动 → 它让 realtime 调用 background_agent,后台返回文本 → 出来按顺序检查第 1 条是 progress update,第 2 条是 function_call_output。

调用关系:它防止顺序错误,因为实时模型需要先看到后台内容,再看到工具调用完成信号。

调用图:调用 5 个内部函数(new, assert_v2_function_call_output, assert_v2_progress_update, main_loop_responses, realtime_sideband);外部调用 3 个(assert_eq!, skip_if_no_network!, vec!)。

webrtc_v2_tool_call_delegated_turn_can_execute_shell_tool2262–2349 ↗
async fn webrtc_v2_tool_call_delegated_turn_can_execute_shell_tool() -> Result<()>

作用:测试由实时 V2 工具调用触发的后台代理任务,仍然可以执行 shell 命令并把结果带回实时侧。

数据流:进去由测试框架启动 → 它打开更高权限沙箱,后台 mock 先要求执行命令再给最终回答 → 出来检查命令 started/completed 通知、Responses 后续请求里的命令输出,以及 sideband 的 progress 和 function_call_output。

调用关系:它使用 new_with_sandbox、wait_for_started_command_execution、wait_for_completed_command_execution 和 realtime_tool_ok_command,覆盖实时交接到工具执行的链路。

调用图:调用 7 个内部函数(new_with_sandbox, assert_v2_function_call_output, assert_v2_progress_update, main_loop_responses, realtime_sideband, wait_for_completed_command_execution, wait_for_started_command_execution);外部调用 5 个(assert!, assert_eq!, skip_if_no_network!, unreachable!, vec!)。

webrtc_v2_tool_call_does_not_block_sideband_audio2352–2429 ↗
async fn webrtc_v2_tool_call_does_not_block_sideband_audio() -> Result<()>

作用:测试后台代理工具调用还没完成时,sideband 发来的音频仍然能立刻转发给客户端。

数据流:进去由测试框架启动 → 它用 GatedSseResponse 卡住后台 Responses 流,同时 sideband 发 output_audio.delta → 出来先收到音频通知,再释放后台任务并检查最终输出。

调用关系:它验证 app-server 没有因为等待后台代理而堵住实时音频通道。

调用图:调用 6 个内部函数(new_with_main_loop_responses_server, assert_v2_function_call_output, assert_v2_progress_update, realtime_sideband, sse, start_mock_server);外部调用 8 个(given, new, assert_eq!, channel, skip_if_no_network!, vec!, method, path_regex)。

realtime_webrtc_start_surfaces_backend_error2432–2502 ↗
async fn realtime_webrtc_start_surfaces_backend_error() -> Result<()>

作用:测试 WebRTC call 创建失败时,错误会以实时错误通知的形式告诉客户端。

数据流:进去由测试框架启动 → 它让 /v1/realtime/calls 返回 500,再启动实时 WebRTC → 出来 start 请求本身返回后,客户端收到 thread/realtime/error。

调用关系:它覆盖上游服务失败路径,确保用户能看到友好的高负载错误,而不是测试挂死。

调用图:调用 4 个内部函数(new, create_config_toml, login_with_api_key, start_websocket_server);外部调用 15 个(given, new, new, new, Integer, default, Override, create_mock_responses_server_sequence_unchecked, to_response, assert! (+5 more))。

realtime_conversation_requires_feature_flag2505–2564 ↗
async fn realtime_conversation_requires_feature_flag() -> Result<()>

作用:测试实时对话功能必须打开 feature flag。feature flag 是配置里的功能开关。

数据流:进去由测试框架启动 → 它写入 realtime_enabled=false,创建线程后尝试启动实时会话 → 出来 JSON-RPC 错误,并检查错误码和消息。

调用关系:它用 assert_invalid_request 验证禁用功能时的拒绝行为。

调用图:调用 4 个内部函数(new, assert_invalid_request, create_config_toml, start_websocket_server);外部调用 10 个(new, new, Integer, default, create_mock_responses_server_sequence_unchecked, to_response, format!, skip_if_no_network!, timeout, vec!)。

read_notification2566–2579 ↗
async fn read_notification(
    mcp: &mut TestAppServer,
    method: &str,
) -> Result<T>

作用:通用通知读取工具:等待某个 JSON-RPC 通知,并把 params 转成指定 Rust 类型。

数据流:进去是 TestAppServer 和方法名 → 它在超时时间内读取匹配通知,取出 params,并用 serde_json 转类型 → 出来类型化通知对象。

调用关系:RealtimeE2eHarness::read_notification 和很多测试辅助函数都靠它等待 app-server 发出的事件。

调用图:调用 1 个内部函数(read_stream_until_notification_message);被 1 处调用(read_notification);外部调用 2 个(from_value, timeout)。

login_with_api_key2581–2592 ↗
async fn login_with_api_key(mcp: &mut TestAppServer, api_key: &str) -> Result<()>

作用:用测试 API key 登录 app-server,模拟真实客户端先认证再使用实时功能。

数据流:进去是 TestAppServer 和 API key 字符串 → 它发送登录请求,等待响应,解析成 LoginAccountResponse,并确认是 ApiKey 登录成功 → 出来空结果。

调用关系:harness 初始化和多个手写测试都会先调用它;没有登录,后续访问上游模型相关接口就不完整。

调用图:调用 2 个内部函数(read_stream_until_response_message, send_login_account_api_key_request);被 7 处调用(new_with_main_loop_responses_server_and_sandbox, realtime_conversation_stop_emits_closed_notification, realtime_conversation_streams_v2_notifications, realtime_start_can_skip_startup_context, realtime_text_output_modality_requests_text_output_and_final_transcript, realtime_webrtc_start_emits_sdp_notification, realtime_webrtc_start_surfaces_backend_error);外部调用 4 个(Integer, to_response, assert_eq!, timeout)。

wait_for_started_command_execution2594–2603 ↗
async fn wait_for_started_command_execution(
    mcp: &mut TestAppServer,
) -> Result<ItemStartedNotification>

作用:一直等到客户端通知里出现“命令开始执行”的条目。

数据流:进去是 TestAppServer → 它循环读取 item/started 通知,跳过非命令条目 → 出来第一个命令执行开始通知。

调用关系:shell 工具测试用它确认后台代理真的启动了命令,而不是只生成了最终文本。

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

wait_for_completed_command_execution2605–2615 ↗
async fn wait_for_completed_command_execution(
    mcp: &mut TestAppServer,
) -> Result<ItemCompletedNotification>

作用:一直等到客户端通知里出现“命令执行完成”的条目。

数据流:进去是 TestAppServer → 它循环读取 item/completed 通知,直到找到命令执行条目 → 出来命令完成通知。

调用关系:shell 工具测试在 started 后调用它,检查命令状态和聚合输出。

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

responses_requests2617–2630 ↗
async fn responses_requests(server: &MockServer) -> Result<Vec<Value>>

作用:从 mock Responses 服务器里取出所有发往 /responses 的请求体。

数据流:进去是 MockServer → 它读取已收到的请求,筛选路径以 /responses 结尾的请求,并把请求体解析成 JSON → 出来 JSON 列表。

调用关系:RealtimeE2eHarness::main_loop_responses_requests 调用它;后台代理相关测试用这些请求检查提示词是否正确。

调用图:被 1 处调用(main_loop_responses_requests);外部调用 1 个(received_requests)。

response_request_contains_text2632–2643 ↗
fn response_request_contains_text(request: &Value, text: &str) -> bool

作用:递归检查一个 JSON 请求里是否包含某段文字。

数据流:进去是 JSON 值和目标文本 → 它如果遇到字符串就查包含,遇到数组或对象就继续往里找,其他类型返回 false → 出来布尔值。

调用关系:后台代理、转向和 shell 工具测试用它确认复杂 Responses 请求中出现了预期上下文。

realtime_tool_ok_command2645–2660 ↗
fn realtime_tool_ok_command() -> Vec<String>

作用:生成一个跨平台的小命令,用来输出固定文本 realtime-tool-ok。

数据流:进去没有参数 → 它按操作系统选择 Windows PowerShell 命令或类 Unix 的 printf 命令 → 出来命令参数列表。

调用关系:shell 工具端到端测试用它避免不同系统命令不兼容。

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

assert_v2_function_call_output2662–2674 ↗
fn assert_v2_function_call_output(request: &Value, call_id: &str, expected_output: &str)

作用:断言一条 V2 sideband 请求正是指定 call_id 的函数调用输出。

数据流:进去是实际 JSON、call_id 和期望输出文本 → 它构造标准 function_call_output JSON 并比较 → 出来无返回;不一致则测试失败。

调用关系:多个 V2 后台代理测试用它检查 app-server 对 realtime 工具调用的答复格式。

调用图:被 6 处调用(realtime_automatic_handoff_output_is_item_and_append_speaks, webrtc_v2_background_agent_progress_is_sent_before_function_output, webrtc_v2_background_agent_steering_ack_requests_response_create, webrtc_v2_background_agent_tool_call_delegates_and_returns_function_output, webrtc_v2_tool_call_delegated_turn_can_execute_shell_tool, webrtc_v2_tool_call_does_not_block_sideband_audio);外部调用 1 个(assert_eq!)。

assert_v2_progress_update2676–2691 ↗
fn assert_v2_progress_update(request: &Value, expected_text: &str)

作用:断言一条 V2 sideband 请求是发给实时模型的后台进度更新。

数据流:进去是实际 JSON 和期望文本 → 它期望消息角色为 user,文本带 [BACKEND] 前缀 → 出来无返回;不一致则失败。

调用关系:手动语音更新和后台代理进度测试用它确认实时模型会看到后台进展。

调用图:被 6 处调用(realtime_automatic_handoff_output_is_item_and_append_speaks, realtime_automatic_standalone_output_is_item_and_append_speaks, webrtc_v2_background_agent_progress_is_sent_before_function_output, webrtc_v2_background_agent_tool_call_delegates_and_returns_function_output, webrtc_v2_tool_call_delegated_turn_can_execute_shell_tool, webrtc_v2_tool_call_does_not_block_sideband_audio);外部调用 1 个(assert_eq!)。

assert_v2_backend_item_update2693–2695 ↗
fn assert_v2_backend_item_update(request: &Value, expected_text: &str)

作用:断言一条 V2 后台输出被作为“只供上下文使用”的 item 发送。

数据流:进去是实际 JSON 和期望文本 → 它给文本加 [BACKEND] 前缀,再交给 assert_v2_items_update 检查完整结构 → 出来无返回。

调用关系:自动后台输出相关测试用它,确保这些内容不会直接触发实时模型发声。

调用图:调用 1 个内部函数(assert_v2_items_update);被 3 处调用(realtime_automatic_handoff_output_is_item_and_append_speaks, realtime_automatic_standalone_output_is_item_and_append_speaks, webrtc_v2_assistant_output_without_handoff_reaches_realtime_context);外部调用 1 个(format!)。

assert_v2_items_update2697–2712 ↗
fn assert_v2_items_update(request: &Value, expected_text: &str)

作用:检查 V2 上下文 item 的底层 JSON 格式。

数据流:进去是实际 JSON 和完整期望文本 → 它要求 type 为 conversation.item.create,role 为 developer,内容带 RESPONSE_ITEM_PREFIX → 出来无返回;不一致则失败。

调用关系:assert_v2_backend_item_update 把文本整理好后调用它。

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

assert_v2_user_text_item2714–2729 ↗
fn assert_v2_user_text_item(request: &Value, expected_text: &str)

作用:断言一条 V2 sideband 请求是用户文字消息 item。

数据流:进去是实际 JSON 和用户文本 → 它期望 role 为 user,内容是 [USER] 加原文 → 出来无返回;不一致则失败。

调用关系:两个“文字输入只追加”回归测试用它确认没有错发 response.create。

调用图:被 2 处调用(webrtc_v2_text_input_is_append_only_when_response_is_cancelled, webrtc_v2_text_input_is_append_only_while_response_is_active);外部调用 1 个(assert_eq!)。

assert_v2_response_create2731–2738 ↗
fn assert_v2_response_create(request: &Value)

作用:断言一条 V2 sideband 请求是 response.create,也就是要求实时模型开始生成回应。

数据流:进去是实际 JSON → 它和 {"type":"response.create"} 比较 → 出来无返回;不一致则失败。

调用关系:手动语音更新和转向确认测试用它区分“只是写上下文”和“真的请求模型回应”。

调用图:被 3 处调用(realtime_automatic_handoff_output_is_item_and_append_speaks, realtime_automatic_standalone_output_is_item_and_append_speaks, webrtc_v2_background_agent_steering_ack_requests_response_create);外部调用 1 个(assert_eq!)。

assert_v1_session_update2740–2755 ↗
fn assert_v1_session_update(request: &Value) -> Result<()>

作用:检查 V1 实时协议的 session.update 请求是否符合预期。

数据流:进去是实际 JSON → 它检查 type、session.type、instructions 是否含 startup context、默认声音 cove、tools 为空 → 出来 Result,失败会给出错误或断言失败。

调用关系:V1 WebRTC 启动和 handoff 测试用它验证 app-server 发给上游的初始化配置。

调用图:被 3 处调用(webrtc_v1_default_automatic_output_uses_handoff_append, webrtc_v1_handoff_request_delegates_context_and_manual_append_speaks, webrtc_v1_start_posts_offer_returns_sdp_and_joins_sideband);外部调用 2 个(assert!, assert_eq!)。

assert_v2_session_update2757–2779 ↗
fn assert_v2_session_update(request: &Value) -> Result<()>

作用:检查 V2 实时协议的 session.update 请求是否符合预期。

数据流:进去是实际 JSON → 它检查 session.type、启动上下文、background_agent 和 remain_silent 工具、输入转写模型 → 出来 Result。

调用关系:V2 基础转发、文字追加和转向测试用它验证实时会话启动配置。

调用图:被 4 处调用(webrtc_v2_background_agent_steering_ack_requests_response_create, webrtc_v2_forwards_audio_and_text_between_client_and_sideband, webrtc_v2_text_input_is_append_only_when_response_is_cancelled, webrtc_v2_text_input_is_append_only_while_response_is_active);外部调用 2 个(assert!, assert_eq!)。

assert_call_create_multipart2781–2814 ↗
fn assert_call_create_multipart(
    request: WiremockRequest,
    offer_sdp: &str,
    session: &str,
) -> Result<()>

作用:检查 WebRTC call-create HTTP 请求是否是正确的 multipart 表单。multipart 是一个请求里分多块上传数据的格式。

数据流:进去是 HTTP 请求、offer SDP 和期望 session JSON → 它检查路径、content-type、把 body 转成 UTF-8、标准化 session JSON 后比较完整表单文本 → 出来 Result。

调用关系:V1 WebRTC 测试用它确认 app-server 发给 /v1/realtime/calls 的 SDP 和会话配置完全正确。

调用图:调用 1 个内部函数(normalized_json_string);被 2 处调用(webrtc_v1_handoff_request_delegates_context_and_manual_append_speaks, webrtc_v1_start_posts_offer_returns_sdp_and_joins_sideband);外部调用 2 个(from_utf8, assert_eq!)。

v1_session_create_json2816–2818 ↗
fn v1_session_create_json() -> &'static str

作用:提供 V1 WebRTC call-create 时应发送的 session JSON 模板。

数据流:进去没有参数 → 它返回一段静态 JSON 字符串 → 出来期望 session 内容。

调用关系:assert_call_create_multipart 的调用方用它作为 V1 请求体的标准答案。

调用图:被 2 处调用(webrtc_v1_handoff_request_delegates_context_and_manual_append_speaks, webrtc_v1_start_posts_offer_returns_sdp_and_joins_sideband)。

create_config_toml2820–2836 ↗
fn create_config_toml(
    codex_home: &Path,
    responses_server_uri: &str,
    realtime_server_uri: &str,
    realtime_enabled: bool,
    startup_context: StartupContextConfig<'_>,
) -> std::io::Re

作用:为测试写一个默认 V2、只读沙箱的 config.toml 配置文件。

数据流:进去是临时 Codex home 路径、Responses 地址、实时 WebSocket 地址、功能开关和启动上下文设置 → 它补上默认版本和沙箱,调用更通用的配置写入函数 → 出来 I/O 结果。

调用关系:多数手写测试用它快速准备 app-server 配置;复杂 harness 会直接调用更底层版本。

调用图:调用 1 个内部函数(create_config_toml_with_realtime_version);被 8 处调用(realtime_conversation_requires_feature_flag, realtime_conversation_stop_emits_closed_notification, realtime_conversation_streams_v2_notifications, realtime_list_voices_returns_supported_names, realtime_start_can_skip_startup_context, realtime_text_output_modality_requests_text_output_and_final_transcript, realtime_webrtc_start_emits_sdp_notification, realtime_webrtc_start_surfaces_backend_error)。

create_config_toml_with_realtime_version2838–2889 ↗
fn create_config_toml_with_realtime_version(
    codex_home: &Path,
    responses_server_uri: &str,
    realtime_server_uri: &str,
    realtime_enabled: bool,
    startup_context: StartupContextConfig

作用:真正写测试用 config.toml,包含模型提供方、实时地址、实时版本、沙箱和功能开关。

数据流:进去是路径、两个服务地址、是否开启实时、启动上下文、协议版本和沙箱 → 它查找 feature key,转换版本/沙箱字符串,拼出 TOML 文本并写到磁盘 → 出来 I/O 结果。

调用关系:RealtimeE2eHarness 初始化和 create_config_toml 都依赖它;它决定 app-server 在测试里会连接哪些假服务。

调用图:调用 2 个内部函数(config_value, config_value);被 2 处调用(new_with_main_loop_responses_server_and_sandbox, create_config_toml);外部调用 4 个(join, new, format!, write)。

assert_invalid_request2891–2895 ↗
fn assert_invalid_request(error: JSONRPCError, message: String)

作用:断言 JSON-RPC 错误是“无效请求”,且错误消息正好符合预期。

数据流:进去是 JSONRPCError 和期望消息 → 它检查错误码 -32600、消息文本和 data 为空 → 出来无返回;不一致则测试失败。

调用关系:realtime_conversation_requires_feature_flag 用它确认功能开关关闭时 app-server 返回标准错误。

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