Codex 系统手册

模型传输执行

stage-13.128 个文件

这一阶段是系统和模型服务真正“通话”的地方,属于主干活儿时的网络出口。它先把用户对话、工具、图片、搜索、记忆、压缩请求整理成服务端看得懂的格式,并带上会话身份信息;再由总客户端选择 HTTPS、WebSocket、WebRTC 等通道发出去。返回时,流式解码器把一段段回答、工具调用、用量和错误翻译成内部事件。若断线或限流,重试和错误映射会决定重连、换通道或报错。实时语音部分则像电话线,负责接麦克风、收字幕和转交任务。

本阶段的文件28

共享请求与错误基础

这些文件定义跨出站模型传输使用的通用请求载荷辅助工具、元数据、流包装器、工具序列化,以及公共错误转换。

core/src/client_common.rs源码 ↗
io_transportrequest handling

可以把这个文件理解成“和模型对话的公共包装盒”。Prompt 是发给模型的一次请求,里面装着聊天上下文、可用工具、基础指令、人格设置、输出格式要求等。它还会在需要使用轻量版 Responses API 时,把图片里的 detail 字段去掉;这个 detail 可以理解成“图片解析精度说明”,有些接口不接受或不需要它,所以发送前要清理掉,避免请求不兼容。ResponseStream 则是模型返回结果的“水管”:结果不是一次性回来,而是通过通道一条条流出来。外部代码轮询它时,它就从内部消息通道取下一条事件;如果使用者提前不读了,它会触发取消信号,告诉后台任务别再继续白干。这能避免资源泄漏,也让请求结束得更干净。

函数细节5
Prompt::default43–53 ↗
fn default() -> Self

作用:创建一个空白但安全可用的 Prompt,也就是一次模型请求的默认模板。别人可以先拿这个模板,再按需要填入输入、工具、指令或输出格式。

数据流:进去没有额外输入 → 它生成一个 Prompt:输入列表为空、工具列表为空、默认不允许并行调用工具、使用默认基础指令、没有人格设置、没有输出格式模板,并且默认严格检查输出格式 → 出来的是一个可以继续补充内容的 Prompt。

调用关系:很多测试和请求准备代码会从这个默认 Prompt 开始,像先拿一张空表格再填写内容。它内部会用到一些类型自己的默认值和空列表创建能力,保证新建出来的请求不会带着脏数据或意外设置。

调用图:调用 1 个内部函数(default);被 8 处调用(responses_respects_model_info_overrides_from_config, responses_stream_includes_subagent_header_on_other, responses_stream_includes_subagent_header_on_review, azure_responses_request_includes_store_and_reasoning_ids, send_provider_auth_request, prompt_with_input, sample, memories_startup_phase1_uses_live_thread_service_tier_and_detached_metadata);外部调用 1 个(new)。

Prompt::get_formatted_input_for_request57–66 ↗
fn get_formatted_input_for_request(
        &self,
        use_responses_lite: bool,
    ) -> Vec<ResponseItem>

作用:根据要调用的接口版本,拿出真正要发给模型的输入内容。它不会直接改原始 Prompt,而是先复制一份,再按需要清理不兼容的图片细节。

数据流:进去的是当前 Prompt 和一个 use_responses_lite 开关 → 它先复制 Prompt 里的 input;如果开关为真,就把复制出来的输入交给 strip_image_details,删除图片的 detail 信息 → 出来的是适合本次请求发送的 ResponseItem 列表,原来的 Prompt 不被改动。

调用关系:构建 Responses API 请求时会调用它。它处在“把内部对话内容整理成网络请求内容”的步骤里;如果发现要走轻量接口,就把清理图片细节这件事交给 strip_image_details。

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

strip_image_details69–106 ↗
fn strip_image_details(items: &mut [ResponseItem])

作用:从一批对话内容里删掉图片的 detail 字段,让这些内容适合发送给不支持该字段的接口。它专门处理普通消息里的图片,以及工具调用输出里夹带的图片。

数据流:进去的是一组可修改的 ResponseItem → 它逐项查看:如果是用户或模型消息,就检查里面的内容项;如果是函数或自定义工具的输出,就检查输出里的内容项;凡是遇到图片内容,就把 detail 设为空 → 出来没有新返回值,但传入的那批内容已经被就地清理。

调用关系:它只由 Prompt::get_formatted_input_for_request 调用,是一个隐藏在请求格式化步骤里的清洁工。调用者不用知道各种消息类型里图片藏在哪里,只要交给它,它会把该删的图片细节删掉。

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

ResponseStream::poll_next118–120 ↗
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>>

作用:让外部代码从 ResponseStream 里取下一条模型返回事件。因为模型结果可能一段段到达,所以这个函数的作用就像问水管:“现在有没有下一滴水?”

数据流:进去的是当前 ResponseStream 和异步运行时给的等待上下文 Context;这个上下文可以理解成“如果现在没数据,之后有数据时叫醒我” → 它从内部的消息接收端 rx_event 尝试取一条 Result<ResponseEvent> → 出来可能是下一条事件、结束信号,或者表示现在还要等。

调用关系:它实现了 Stream,也就是 Rust 里“异步一条条产出数据”的接口。任何消费模型流式响应的代码在读取下一条事件时,最终都会走到这里;真正取数据的动作交给底层消息通道的 poll_recv。

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

ResponseStream::drop124–126 ↗
fn drop(&mut self)

作用:当 ResponseStream 被丢弃时,通知后台任务:使用者已经不再读取结果了,可以停止后续工作。这样能避免后台还在继续处理一个没人要的响应。

数据流:进去的是即将被销毁的 ResponseStream → 它触发 consumer_dropped 这个取消令牌;取消令牌可以理解成一个“停止信号灯” → 出来没有返回值,但等待这个信号的后台映射任务会知道消费者提前离开了。

调用关系:它是自动发生的清理动作,不需要调用者手动调用。当读取方提前结束、不再持有 ResponseStream 时,Rust 会自动触发 drop;它再把停止消息交给 CancellationToken,帮助整个流式响应流程干净收尾。

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

core/src/responses_metadata.rs源码 ↗
io_transportrequest building

这个文件解决的是“请求从哪里来、属于哪段对话、该按什么用途看待”的标记问题。没有这些元数据,后端就很难追踪请求、区分普通对话和预热、压缩、记忆整理,也可能让调用方塞进来的自定义字段覆盖系统自己的关键字段。文件里最核心的是 CodexResponsesMetadata,它保存安装 ID、会话 ID、线程 ID、窗口 ID、子代理信息、工作区状态等。它还能把这些信息变成两种形式:一种是 client_metadata,也就是放进请求体里的键值表;另一种是 HTTP 头,用来兼容旧的读取方式。CodexResponsesRequestKind 用来说明请求类型,CompactionTurnMetadata 专门记录“对话压缩”这类请求的触发原因、阶段和策略。文件还会过滤外部传入的额外元数据,防止别人改掉系统保留字段。

函数细节15
CompactionTurnMetadata::new79–92 ↗
fn new(
        trigger: CompactionTrigger,
        reason: CompactionReason,
        implementation: CompactionImplementation,
        phase: CompactionPhase,
    ) -> Self

作用:创建一份“对话压缩请求”的说明卡片。它记录这次压缩为什么发生、用什么实现、处在哪个阶段,并固定使用 Memento 这种压缩策略。

数据流:进去的是压缩触发方式、压缩原因、具体实现方式和压缩阶段 → 函数把它们装进 CompactionTurnMetadata 结构里,并补上默认策略 → 出来的是一份可随请求发送的压缩元数据。

调用关系:当系统真正准备发起本地或远程压缩任务时,run_compact_task_inner 和 run_remote_compact_task_inner 会用它生成说明卡片。测试 turn_metadata_state_overlays_compaction_only_on_compaction_requests 也会用它确认只有压缩请求才带这类信息。

调用图:被 4 处调用(run_compact_task_inner, run_remote_compact_task_inner, run_remote_compact_task_inner, turn_metadata_state_overlays_compaction_only_on_compaction_requests)。

CodexResponsesRequestKind::metadata104–111 ↗
fn metadata(self) -> (&'static str, Option<CompactionTurnMetadata>)

作用:把内部的请求类型,翻译成要发给服务端的简单文字。比如普通对话变成 "turn",预热变成 "prewarm",压缩还会额外带上压缩详情。

数据流:进去的是一个请求类型枚举 → 函数按类型匹配,决定公开给后端看的字符串,以及是否附带压缩元数据 → 出来的是“请求类型文字”和“可选的压缩说明”。

调用关系:它主要被 CodexResponsesMetadata::turn_metadata_payload 间接使用,用来把内部类型变成最终 JSON 元数据里能读懂的字段。

CodexResponsesRequestKind::has_turn_identity113–115 ↗
fn has_turn_identity(self) -> bool

作用:判断这种请求要不要带“属于哪一轮对话”的身份信息。记忆类请求比较特殊,不算普通对话轮次,所以不带这类身份。

数据流:进去的是请求类型 → 函数检查它是不是 Memory → 出来的是 true 或 false,表示是否应该带 session_id、thread_id、turn_id 这些轮次身份字段。

调用关系:CodexResponsesMetadata::turn_metadata_payload 会用它决定最终元数据里哪些身份字段该出现、哪些该省略,避免把不该绑定到某轮对话的请求错误绑定上。

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

CodexResponsesMetadata::new153–175 ↗
fn new(
        installation_id: String,
        session_id: String,
        thread_id: String,
        window_id: String,
    ) -> Self

作用:创建一份基础的 Responses 请求元数据。调用者只要先提供最基本的安装、会话、线程和窗口 ID,其他可选信息之后再慢慢补。

数据流:进去的是 installation_id、session_id、thread_id、window_id 四个基础身份 → 函数把它们放进 CodexResponsesMetadata,并把 turn_id、请求类型、子代理、工作区、额外字段等设为空或空表 → 出来的是一份干净的元数据初始模板。

调用关系:responses_metadata、responses_metadata_template、detached_memory_responses_metadata 会在准备不同类型请求时调用它,先搭好元数据骨架,再按具体场景往里面填更多内容。

调用图:被 3 处调用(responses_metadata, responses_metadata_template, detached_memory_responses_metadata);外部调用 1 个(new)。

CodexResponsesMetadata::has_turn_metadata177–179 ↗
fn has_turn_metadata(&self) -> bool

作用:判断这份元数据是否已经说明了请求类型。只有说明了请求类型,才需要生成完整的“turn metadata”大字段。

数据流:进去的是当前 CodexResponsesMetadata → 函数查看 request_kind 有没有值 → 出来的是布尔值,表示是否应该附带完整轮次元数据。

调用关系:CodexResponsesMetadata::client_metadata 和 CodexResponsesMetadata::compatibility_headers 都会先问它一句:有没有必要把 x-codex-turn-metadata 加进去。

调用图:被 2 处调用(client_metadata, compatibility_headers)。

CodexResponsesMetadata::turn_metadata_json181–183 ↗
fn turn_metadata_json(&self) -> Option<String>

作用:把完整的轮次元数据变成 JSON 字符串。JSON 可以理解成一种通用文本格式,方便塞进请求里的一个字段或 HTTP 头里。

数据流:进去的是当前元数据对象 → 函数先调用 turn_metadata_payload 组装真正要公开的内容,再用 to_ascii_json_string 转成 ASCII JSON 字符串 → 成功就出来字符串,失败就出来空值。

调用关系:CodexResponsesMetadata::client_metadata 和 CodexResponsesMetadata::compatibility_headers 会调用它,把同一份元数据分别放进请求体的 client_metadata 或兼容用的 HTTP 头。

调用图:调用 1 个内部函数(turn_metadata_payload);被 2 处调用(client_metadata, compatibility_headers);外部调用 1 个(to_ascii_json_string)。

CodexResponsesMetadata::turn_metadata_value185–187 ↗
fn turn_metadata_value(&self) -> Option<Value>

作用:把完整的轮次元数据变成 serde_json::Value,也就是程序里可继续处理的 JSON 值,而不是字符串。

数据流:进去的是当前元数据对象 → 函数调用 turn_metadata_payload 取出要序列化的内容,再交给 serde_json 转成 JSON 值 → 出来的是可选 JSON 值,转换失败就为空。

调用关系:它和 turn_metadata_json 使用同一个 payload 来源,只是输出形态不同;适合那些想直接拿结构化 JSON,而不是拿文本字符串的代码。

调用图:调用 1 个内部函数(turn_metadata_payload);外部调用 1 个(to_value)。

CodexResponsesMetadata::client_metadata189–220 ↗
fn client_metadata(&self) -> HashMap<String, String>

作用:生成发给 Responses API 的 client_metadata 键值表。它把安装、会话、线程、窗口、子代理、父线程和完整轮次元数据整理成后端能读取的字段。

数据流:进去的是一份 CodexResponsesMetadata → 函数先放入固定的基础字段,再按需加入 turn_id、subagent、parent_thread_id,以及 x-codex-turn-metadata JSON 字符串 → 出来的是 HashMap<String, String>,也就是请求体里要带的元数据表。

调用关系:build_responses_request 在构造普通 API 请求时会用它,build_ws_client_metadata 在构造 WebSocket 元数据时也会用它。它内部会先调用 has_turn_metadata 判断要不要带完整元数据,再调用 turn_metadata_json 生成那段 JSON。

调用图:调用 2 个内部函数(has_turn_metadata, turn_metadata_json);被 2 处调用(build_responses_request, build_ws_client_metadata);外部调用 1 个(from)。

CodexResponsesMetadata::compatibility_headers222–247 ↗
fn compatibility_headers(&self) -> ApiHeaderMap

作用:生成一组兼容旧接口或旧消费者的 HTTP 头。新代码更推荐读 client_metadata,但有些地方还会从请求头里找这些信息。

数据流:进去的是当前元数据 → 函数新建一个 HTTP 头表,放入窗口 ID,并按需放入完整轮次元数据、父线程 ID、子代理标记;每个值都会先检查能不能合法放进头里 → 出来的是 ApiHeaderMap,也就是可直接随 HTTP 请求发送的头集合。

调用关系:build_responses_compatibility_headers 会调用它。它把具体插入动作交给 insert_header,自己负责决定哪些头应该出现。

调用图:调用 3 个内部函数(has_turn_metadata, turn_metadata_json, insert_header);被 1 处调用(build_responses_compatibility_headers);外部调用 1 个(new)。

CodexResponsesMetadata::turn_metadata_payload249–280 ↗
fn turn_metadata_payload(&self) -> CodexTurnMetadataPayload<'_>

作用:组装真正要写进 x-codex-turn-metadata 的内容。它会根据请求类型,聪明地决定哪些身份信息该带、哪些不该带。

数据流:进去的是完整的 CodexResponsesMetadata → 函数读取请求类型,把它翻译成字符串和可选压缩信息;再判断是否要带会话、线程、轮次、窗口等身份;最后把子代理、沙箱、工作区、开始时间和额外字段一起打包 → 出来的是一个临时的 CodexTurnMetadataPayload,用于转 JSON。

调用关系:turn_metadata_json 和 turn_metadata_value 都依赖它。它还会调用 non_empty_workspaces,确保没有工作区信息时就不在 JSON 里塞一个空对象,让传出去的元数据更干净。

调用图:调用 1 个内部函数(non_empty_workspaces);被 2 处调用(turn_metadata_json, turn_metadata_value)。

subagent_header_value283–302 ↗
fn subagent_header_value(session_source: &SessionSource) -> Option<String>

作用:根据会话来源,决定要不要给请求打上“子代理”头。子代理可以理解成主程序派出去做特定任务的小助手,比如审查、压缩、记忆整理。

数据流:进去的是 SessionSource,也就是这次会话从哪里来 → 函数判断它是不是子代理或内部记忆整理任务,并把不同来源翻译成固定字符串,如 review、compact、memory_consolidation → 出来的是可选字符串;普通 CLI、VSCode、Exec 等来源则不输出。

调用关系:build_subagent_headers、responses_metadata、CodexResponsesMetadata::new 的相关构建流程、detached_memory_responses_metadata 会用它来决定请求头里是否需要 x-openai-subagent 这类标记。

调用图:被 4 处调用(build_subagent_headers, responses_metadata, new, detached_memory_responses_metadata)。

subagent_metadata_kind304–315 ↗
fn subagent_metadata_kind(session_source: &SessionSource) -> Option<String>

作用:把子代理来源转换成要写入轮次元数据的“子代理种类”。它比请求头值更偏向结构化记录,方便后端或分析系统理解来源。

数据流:进去的是 SessionSource → 如果它是 SubAgent,就取出这个子代理自己的 kind 并转成字符串;如果是普通入口或内部来源,就返回空 → 出来的是可选的子代理种类字符串。

调用关系:它在元数据创建流程里被调用,用来填 CodexResponsesMetadata 里的 subagent_kind,之后会通过 turn_metadata_payload 进入完整的 turn metadata JSON。

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

insert_header317–321 ↗
fn insert_header(headers: &mut ApiHeaderMap, name: &'static str, value: &str)

作用:安全地往 HTTP 头表里放一个值。HTTP 头对字符有要求,这个函数会先确认值合法,避免把坏值硬塞进去。

数据流:进去的是可修改的头表、头名字和值字符串 → 函数尝试把字符串转换成 HeaderValue;如果成功,就插入头表;如果失败,就什么也不插入 → 出来没有单独返回值,但头表可能被更新。

调用关系:CodexResponsesMetadata::compatibility_headers 会多次调用它。它像一个小门卫,确保只有能作为 HTTP 头发送的值才会进入最终请求。

调用图:被 1 处调用(compatibility_headers);外部调用 2 个(insert, from_str)。

filter_extra_metadata323–328 ↗
fn filter_extra_metadata(extra: HashMap<String, String>) -> BTreeMap<String, String>

作用:过滤调用方额外传入的元数据,防止它们覆盖系统自己的关键字段。比如 installation_id、thread_id、x-codex-turn-metadata 这些都不允许外部随便改。

数据流:进去的是一个 HashMap<String, String>,里面是外部提供的额外键值 → 函数逐个检查 key 是否在保留名单 RESERVED_METADATA_KEYS 里;保留字段会被丢掉,其他字段留下 → 出来的是按 key 排序的 BTreeMap<String, String>。

调用关系:set_responsesapi_client_metadata 会调用它,把用户或上层客户端给的附加信息清洗后再放进 turn state。这样后面的 turn_metadata_payload 可以安全地把 extra 展开进最终 JSON。

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

non_empty_workspaces330–334 ↗
fn non_empty_workspaces(
    workspaces: &BTreeMap<String, TurnMetadataWorkspace>,
) -> Option<&BTreeMap<String, TurnMetadataWorkspace>>

作用:只有在确实有工作区信息时,才把工作区字段放进元数据。这样可以避免发出无意义的空 workspaces 字段。

数据流:进去的是工作区信息表 → 函数检查它是不是空的;非空就返回这张表的引用,空就返回 None → 出来的是可选的工作区信息。

调用关系:CodexResponsesMetadata::turn_metadata_payload 会调用它。它负责一个小但重要的整理动作:让最终 JSON 只包含有内容的工作区信息。

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

codex-api/src/requests/responses.rs源码 ↗
io_transportrequest handling

这个文件主要解决一个很实际的问题:程序把一批历史内容重新打包成 JSON 发给接口时,有些条目的 id 可能不会自动出现在 JSON 里。没有这些 id,服务端就像看到一堆没有编号的快递盒,可能不知道哪一盒对应之前哪次消息或工具调用。这里的 attach_item_ids 会查看 JSON 里的 input 数组,再对照原始的 ResponseItem 列表,把原始条目里的非空 id 填回对应的 JSON 对象中。它只处理确实有 id 的类型,比如消息、推理、网页搜索调用、函数调用、本地 shell 调用等;没有 id 或 id 为空就跳过。文件里还定义了 Compression,表示请求内容是否压缩,目前有不压缩和 Zstd 压缩两种选择。

函数细节1
attach_item_ids11–37 ↗
fn attach_item_ids(payload_json: &mut Value, original_items: &[ResponseItem])

作用:这个函数把原始响应条目里的 id 补回即将发送的 JSON 请求里。有人会在准备调用接口前使用它,避免重要的“身份编号”在转换成 JSON 时丢掉。

数据流:进去的是一个可修改的 JSON 对象 payload_json,以及一组原始 ResponseItem。它先找 JSON 里的 input 字段,确认它是数组;然后把这个数组和原始条目按顺序一一配对。遇到带有非空 id 的原始条目,就把这个 id 写进对应 JSON 对象的 id 字段。出来时没有单独返回值,但传入的 JSON 可能已经被补上了 id;如果 input 不存在、不是数组,或某项没有可用 id,它就什么也不改。

调用关系:它处在“请求发出去之前”的整理步骤里,上游代码准备好 JSON 和原始条目后会调用它。它自己不发送网络请求,也不创建新条目,只借助 JSON 的查找和遍历能力,把缺失的 id 填好,然后把后续发送请求的工作留给外层流程。

调用图:外部调用 3 个(String, get_mut, iter)。

tools/src/responses_api.rs源码 ↗
io_transport工具注册和发送给 Responses API 前

模型要调用工具时,不能只知道“有个工具”,还得知道工具叫什么、干什么、需要哪些输入、输出大概长什么样。这个文件定义了 Responses API 需要的工具格式,比如单个函数工具、命名空间里的工具,以及可以延后加载的工具。它还提供几条转换通道:动态工具先解析成项目内部的 ToolDefinition,再转成 ResponsesApiTool;MCP 工具也是同样先解析、改名,再转成统一格式。这里的“命名空间”可以理解成工具分组,比如把同一类工具放进同一个抽屉。coalesce_loadable_tool_specs 会把同名抽屉合并,避免最后出现多个名字一样的分组。一个重要细节是 strict 目前固定为 false,说明这里还没有强制校验 JSON schema(用来描述输入格式的规则表)是否足够严格。

函数细节6
default_namespace_description58–60 ↗
fn default_namespace_description(namespace_name: &str) -> String

作用:给一个工具命名空间生成默认说明文字。有人只提供了命名空间名字、没写描述时,就可以用它补一句简单说明。

数据流:进去的是一个命名空间名字,比如“browser” → 它把这个名字塞进固定句式里 → 出来的是一段英文描述,比如“Tools in the browser namespace.”,不改动外部数据。

调用关系:它是一个很小的文字生成帮手,只调用格式化字符串功能。通常会在创建命名空间说明时被用到,让工具分组即使没有手写描述,也有一句可读的解释。

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

dynamic_tool_to_responses_api_tool69–75 ↗
fn dynamic_tool_to_responses_api_tool(
    tool: &DynamicToolFunctionSpec,
) -> Result<ResponsesApiTool, serde_json::Error>

作用:把“动态工具”的定义转换成 Responses API 使用的工具定义。动态工具可以理解成运行时才拿到的工具说明,这个函数负责把它翻译成统一格式。

数据流:进去的是 DynamicToolFunctionSpec,也就是动态工具的原始说明 → 它先交给 parse_dynamic_tool 解析成项目内部统一的 ToolDefinition → 再交给 tool_definition_to_responses_api_tool 转成 ResponsesApiTool → 出来要么是可发给 Responses API 的工具说明,要么是 JSON 解析错误。

调用关系:它站在“动态工具来源”和“Responses API 格式”之间。它自己不做最终字段搬运,而是先请 parse_dynamic_tool 读懂原始工具,再请 tool_definition_to_responses_api_tool 做统一转换。

调用图:调用 1 个内部函数(tool_definition_to_responses_api_tool);外部调用 1 个(parse_dynamic_tool)。

coalesce_loadable_tool_specs77–105 ↗
fn coalesce_loadable_tool_specs(
    specs: impl IntoIterator<Item = LoadableToolSpec>,
) -> Vec<LoadableToolSpec>

作用:把一批可加载工具说明整理一下,尤其是把名字相同的命名空间合并到一起。这样最终列表更干净,不会有多个同名工具分组散落在外面。

数据流:进去的是一串 LoadableToolSpec,里面可能是单个函数工具,也可能是命名空间 → 它从头到尾看一遍:函数工具直接保留;命名空间如果已经出现过同名的,就把工具追加进去,否则新建一项 → 出来的是整理后的列表,原来同名命名空间里的工具被合到同一个分组里。

调用关系:它通常用在准备把工具列表交给外部 API 之前,相当于最后做一次收纳。它不解析工具内容,只关心外层是 Function 还是 Namespace,并把同名 Namespace 的工具数组拼起来。

调用图:外部调用 3 个(new, Function, Namespace)。

mcp_tool_to_responses_api_tool107–114 ↗
fn mcp_tool_to_responses_api_tool(
    tool_name: &ToolName,
    tool: &rmcp::model::Tool,
) -> Result<ResponsesApiTool, serde_json::Error>

作用:把 MCP 工具转换成 Responses API 能使用的普通工具。MCP 是一种让模型连接外部工具或服务的协议,这里负责把 MCP 的工具说明翻译成本项目统一的 API 工具说明。

数据流:进去的是 ToolName 和 rmcp::model::Tool,也就是工具名字和 MCP 原始工具对象 → 它先用 parse_mcp_tool 解析 MCP 工具 → 再把解析后的工具改成指定名字 → 然后交给 tool_definition_to_responses_api_tool 转成 ResponsesApiTool → 出来是转换好的工具说明,或者 JSON 解析错误。

调用关系:它连接 MCP 工具世界和 Responses API 工具世界。解析工作交给 parse_mcp_tool,统一格式生成交给 tool_definition_to_responses_api_tool;它中间额外做了改名,保证最终暴露给模型的名字符合项目里的 ToolName。

调用图:调用 1 个内部函数(tool_definition_to_responses_api_tool);外部调用 1 个(parse_mcp_tool)。

mcp_tool_to_deferred_responses_api_tool116–125 ↗
fn mcp_tool_to_deferred_responses_api_tool(
    tool_name: &ToolName,
    tool: &rmcp::model::Tool,
) -> Result<ResponsesApiTool, serde_json::Error>

作用:把 MCP 工具转换成 Responses API 工具,但标记为“延后加载”。延后加载可以理解成先告诉模型有这个工具,真正详细加载或使用时再补齐,适合工具很多或加载成本高的情况。

数据流:进去的是 ToolName 和 MCP 工具对象 → 它先解析 MCP 工具,再改成指定名字,然后调用 into_deferred 给工具打上延后加载标记 → 最后转成 ResponsesApiTool → 出来的是带 defer_loading 标记的工具说明,或者解析失败的错误。

调用关系:它和 mcp_tool_to_responses_api_tool 很像,但多了一步 into_deferred。最后仍然交给 tool_definition_to_responses_api_tool 做字段转换,所以延后加载这个状态会通过 defer_loading 字段体现在最终结果里。

调用图:调用 1 个内部函数(tool_definition_to_responses_api_tool);外部调用 1 个(parse_mcp_tool)。

tool_definition_to_responses_api_tool127–136 ↗
fn tool_definition_to_responses_api_tool(tool_definition: ToolDefinition) -> ResponsesApiTool

作用:把项目内部统一的 ToolDefinition 搬成 Responses API 的 ResponsesApiTool。它是几个转换入口共用的最后一步,保证不同来源的工具最后长得一样。

数据流:进去的是 ToolDefinition,里面有名字、描述、输入 JSON schema、输出 schema、是否延后加载等信息 → 它把这些字段放进 ResponsesApiTool 对应位置,并把 strict 固定设为 false,把 defer_loading 只在需要时写成 true → 出来的是 Responses API 工具对象,不会返回错误,也不读取外部状态。

调用关系:它是转换流水线的汇合点,被 dynamic_tool_to_responses_api_tool、mcp_tool_to_responses_api_tool 和 mcp_tool_to_deferred_responses_api_tool 调用。前面的函数负责读懂不同来源的工具,它负责生成最终统一格式。

调用图:被 3 处调用(dynamic_tool_to_responses_api_tool, mcp_tool_to_deferred_responses_api_tool, mcp_tool_to_responses_api_tool)。

codex-api/src/api_bridge.rs源码 ↗
io_transportrequest handling / error handling

一次调用远程 API 失败时,失败原因可能藏在很多地方:HTTP 状态码里、返回的 JSON 里、响应头里,甚至是一段普通文字里。这个文件的作用就是把这些零散信息整理成更清楚的 CodexErr。比如,服务过载会变成“服务器太忙”,429 会进一步判断是“用量到顶”、还是“需要重试”,400 会判断是不是网络安全策略拦截,或者图片格式不对。它还会从响应头里取出请求编号、Cloudflare 跟踪号、身份验证错误码等,方便之后排查问题。没有它,上层界面或调用者就只能看到模糊的失败信息,很难告诉用户到底该等一等、改请求、升级套餐,还是联系排障。

函数细节5
map_api_error18–137 ↗
fn map_api_error(err: ApiError) -> CodexErr

作用:把内部 API 层报出来的错误,转换成协议层统一使用的 CodexErr。别人调用它,是为了不用关心错误原本来自网络、HTTP、服务端 JSON,还是本地构造请求失败。

数据流:进去的是一个 ApiError,里面可能带着状态码、网址、响应头、响应正文、重试等待时间等信息。它会按错误种类逐个判断:简单错误直接换名字,HTTP 错误会继续读正文和响应头,识别服务器过载、请求无效、用量限制、图片无效、网络安全拦截等情况。出来的是一个 CodexErr;它不发新请求,但会把原始错误里的关键信息整理进结果里,比如 request_id、cf_ray、用量重置时间和套餐类型。

调用关系:这是本文件的主入口,通常在一次 API 调用失败后被上层错误处理流程使用。它自己负责大判断,但会把“从响应头取某个值”“找请求编号”“解析隐藏在响应头里的错误码”这些小活交给 extract_header、extract_request_id、extract_request_tracking_id 和 extract_x_error_json_code。这样主流程能专心决定错误该归到哪一类。

调用图:调用 4 个内部函数(extract_header, extract_request_id, extract_request_tracking_id, extract_x_error_json_code);外部调用 7 个(matches!, InvalidImageRequest, InvalidRequest, RetryLimit, Stream, UnexpectedStatus, UsageLimitReached)。

extract_request_tracking_id153–155 ↗
fn extract_request_tracking_id(headers: Option<&HeaderMap>) -> Option<String>

作用:找一个能用来追踪这次失败请求的编号。它优先找正式请求 ID,找不到时退而求其次用 cf-ray 这种网络边缘节点给的追踪号。

数据流:进去的是可选的响应头。它先让 extract_request_id 去找常见的请求编号;如果没有,再从 cf-ray 头里取值。出来的是一个可选字符串:找到了就是追踪编号,找不到就是空。

调用关系:它只在 map_api_error 处理重试次数用尽这类错误时用到,因为这类错误需要给排障留下一个线索。它内部依赖 extract_request_id 和 extract_header,相当于把两个查找步骤包装成一个更符合业务含义的“小工具”。

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

extract_request_id157–160 ↗
fn extract_request_id(headers: Option<&HeaderMap>) -> Option<String>

作用:从响应头里找这次请求的 ID。这个 ID 常用于日志排查,相当于快递单号,方便服务端和客户端对上同一次请求。

数据流:进去的是可选的响应头。它先查 x-request-id,如果没有,再查 x-oai-request-id。出来的是找到的 ID 字符串,或者什么也没有。

调用关系:map_api_error 在生成“未知状态错误”等结果时会用它保存请求编号;extract_request_tracking_id 也会先调用它。它把具体的头名选择藏起来,让调用者不用记有哪几种请求 ID 头。

调用图:调用 1 个内部函数(extract_header);被 2 处调用(extract_request_tracking_id, map_api_error)。

extract_header162–168 ↗
fn extract_header(headers: Option<&HeaderMap>, name: &str) -> Option<String>

作用:从 HTTP 响应头里安全地取出一个指定名字的值,并转成普通字符串。它是本文件里最基础的“读响应头”小工具。

数据流:进去的是可选的响应头和一个头名称。它先确认响应头存在,再按名字取值,然后确认这个值能当作正常文本读取,最后转成 String。出来的是这个头的文本值;如果没有头、没有这个字段,或字段不是合法文本,就返回空。

调用关系:map_api_error、extract_request_id 和 extract_x_error_json_code 都靠它取响应头。它把“可能没有、可能读不了”的麻烦统一处理掉,避免每个地方重复写一遍防错代码。

调用图:被 3 处调用(extract_request_id, extract_x_error_json_code, map_api_error)。

extract_x_error_json_code170–181 ↗
fn extract_x_error_json_code(headers: Option<&HeaderMap>) -> Option<String>

作用:从一个特殊响应头 x-error-json 里取出错误码。这个头里的内容不是直接可读文本,而是先用 Base64 编码过的 JSON,所以需要一步步解开。

数据流:进去的是可选的响应头。它先用 extract_header 取到 x-error-json,再做 Base64 解码(Base64 是一种把二进制内容变成普通字符的编码方式),然后把解出来的内容当 JSON 解析,最后读取 error.code。出来的是错误码字符串;中间任何一步失败都会返回空。

调用关系:map_api_error 在构造“意外 HTTP 状态”错误时调用它,用来补充身份或服务端返回的隐藏错误码。它依赖 extract_header 先拿到原始头值,再自己完成解码和解析。

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

Responses 与端点客户端

这些文件实现 Responses 及相邻提供商端点的具体 HTTP、SSE 和 WebSocket 客户端,供核心客户端调用。

codex-api/src/endpoint/responses.rs源码 ↗
io_transportrequest handling

这个文件像一个会填单、寄信、再收回执的办事窗口。上层把要问模型的内容交给 ResponsesClient,它先把内容编码成 JSON,请求体就是发给服务器的“表格”。如果是 Azure 的特殊 responses 端点,并且请求要求保存内容,它还会给输入项补上 item id,避免服务器端保存时认不清每一项。接着它会补齐会话相关的请求头,比如 session_id、thread_id,以及表示子代理来源的头。真正发送时,它固定用 POST 打到 responses 路径,并声明自己要接收 text/event-stream,也就是服务器会一段一段推消息回来。最后它把底层 HTTP 返回的流包装成 ResponseStream,让调用者可以像看直播字幕一样逐步读取模型输出。文件里还支持压缩和遥测,压缩是为了少传数据,遥测是为了记录请求和流式接收的表现。

函数细节6
ResponsesClient::new43–48 ↗
fn new(transport: T, provider: Provider, auth: SharedAuthProvider) -> Self

作用:创建一个新的 ResponsesClient,也就是准备好一个能访问 responses 接口的客户端。调用者需要给它底层传输工具、服务提供方信息和鉴权提供者,这样之后发请求时才知道往哪发、怎么证明身份。

数据流:进去的是 transport(真正发 HTTP 的工具)、provider(告诉它服务端类型和地址等信息)和 auth(提供访问凭证的东西)→ 它把这些交给 EndpointSession 组装成一个会话对象,并把流式遥测先设为空 → 出来的是一个可用的 ResponsesClient,之后可以拿它发流式 responses 请求。

调用关系:它是使用这个客户端的第一步。测试和上层流程会先调用它创建客户端,然后再去调用 stream_request 或 stream;它内部把初始化工作交给 EndpointSession::new,后续所有真正发请求的动作都依赖这个 session。

调用图:调用 1 个内部函数(new);被 8 处调用(azure_default_store_attaches_ids_and_headers, responses_client_stream_request_preserves_exact_json_body, responses_client_uses_responses_path, streaming_client_adds_auth_headers, streaming_client_does_not_retry_auth_build_error, streaming_client_retries_on_transient_auth_error, streaming_client_retries_on_transport_error, responses_stream_parses_items_and_completed_end_to_end)。

ResponsesClient::with_telemetry50–59 ↗
fn with_telemetry(
        self,
        request: Option<Arc<dyn RequestTelemetry>>,
        sse: Option<Arc<dyn SseTelemetry>>,
    ) -> Self

作用:给已经创建好的客户端加上遥测能力。遥测可以理解成“运行记录仪”,用来记录请求和服务端流式返回过程中的信息,方便观察和排查问题。

数据流:进去的是一个已有客户端,以及可选的请求遥测和 SSE 遥测;SSE 是 Server-Sent Events,意思是服务器持续往客户端推送小段消息 → 它把请求遥测装进内部 session,把流式遥测保存到客户端里 → 出来的是一个新的 ResponsesClient,功能和原来一样,但多了记录运行情况的能力。

调用关系:它通常在创建客户端之后、真正发请求之前调用。请求层面的记录交给 EndpointSession::with_request_telemetry,流式接收时用到的记录会留到 stream_encoded 里,再传给 spawn_response_stream。

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

ResponsesClient::stream_request71–107 ↗
async fn stream_request(
        &self,
        request: ResponsesApiRequest,
        options: ResponsesOptions,
    ) -> Result<ResponseStream, ApiError>

作用:这是发送标准 ResponsesApiRequest 的主要入口。它负责把业务请求变成服务器认识的 JSON,加好会话请求头,然后发起流式请求并返回可读取的响应流。

数据流:进去的是 ResponsesApiRequest 和 ResponsesOptions;options 里可能带 session_id、thread_id、来源信息、额外请求头、压缩方式和 turn_state → 它先把请求编码成 JSON;如果遇到 Azure responses 端点并且 request.store 为真,就先把请求转成普通 JSON 值,再给输入项补 item id,然后再编码。接着它把额外请求头、客户端请求 id、会话头、子代理头合在一起 → 最后把编码好的请求体、请求头、压缩设置和 turn_state 交给 stream_encoded,出来的是 ResponseStream,或者失败时返回 ApiError。

调用关系:它是上层最常用的“发 responses 请求”入口。它自己不直接碰底层 HTTP,而是先做请求体和请求头准备;准备好后把真正发送和流式包装交给 stream_encoded。它会调用 provider 判断是否是 Azure 特殊端点,也会调用 build_session_headers、insert_header、subagent_header、attach_item_ids 和 JSON 编码相关函数完成准备工作。

调用图:调用 6 个内部函数(stream_encoded, provider, build_session_headers, insert_header, subagent_header, encode);外部调用 2 个(attach_item_ids, to_value)。

ResponsesClient::path109–111 ↗
fn path() -> &'static str

作用:返回 responses 接口的固定路径。这样代码里不用到处手写字符串,减少写错接口路径的机会。

数据流:进去没有参数 → 它直接返回静态字符串 "responses" → 不改动任何状态,只提供一个统一的路径值。

调用关系:它是一个小工具函数,主要在 stream_encoded 里被用来告诉 EndpointSession 请求应该发到哪个 API 路径。这样以后路径如果要改,只需要改这一处。

ResponsesClient::stream124–135 ↗
async fn stream(
        &self,
        body: Value,
        extra_headers: HeaderMap,
        compression: Compression,
        turn_state: Option<Arc<OnceLock<String>>>,
    ) -> Result<ResponseStre

作用:发送一个已经准备好的原始 JSON 请求体,并获取流式响应。它适合调用者已经自己组好了 JSON,不想使用 ResponsesApiRequest 这个结构的场景。

数据流:进去的是 serde_json::Value 形式的 JSON、额外请求头、压缩方式和可选的 turn_state → 它把这个 JSON 编码成 HTTP 请求体;如果编码失败,就包装成 ApiError → 编码成功后交给 stream_encoded 发送,最后出来 ResponseStream 或错误。

调用关系:它是比 stream_request 更底层一点的入口:不负责补 session header,也不做 Azure store 的 item id 特殊处理,只负责编码原始 JSON,然后把后续发送工作交给 stream_encoded。stream_encoded 是它和 stream_request 共同使用的底层发送步骤。

调用图:调用 2 个内部函数(stream_encoded, encode)。

ResponsesClient::stream_encoded137–172 ↗
async fn stream_encoded(
        &self,
        body: EncodedJsonBody,
        extra_headers: HeaderMap,
        compression: Compression,
        turn_state: Option<Arc<OnceLock<String>>>,
    ) -> R

作用:这是实际把请求发出去并把服务器响应包装成流的核心步骤。前面的函数负责准备材料,它负责把材料交给 HTTP 层,并声明“我要流式事件返回”。

数据流:进去的是已经编码好的 JSON 请求体、请求头、压缩选项和可选 turn_state → 它先把本项目的 Compression 转成底层客户端认识的 RequestCompression;然后通过 session.stream_encoded_json_with 用 POST 发到 responses 路径,并在请求上加 Accept: text/event-stream,表示希望服务器用事件流方式返回;如果 HTTP 请求成功,它把底层 stream_response 交给 spawn_response_stream,并附上空闲超时、SSE 遥测和 turn_state → 出来的是上层可读取的 ResponseStream。

调用关系:它位于整个流程的最后发送环节,由 stream_request 和 stream 调用。它把真正的 HTTP 工作交给 EndpointSession::stream_encoded_json_with,把服务端事件流的解析和后台读取交给 spawn_response_stream;provider 提供流式空闲超时时间,用来避免连接一直挂着没有动静。

调用图:调用 2 个内部函数(provider, stream_encoded_json_with);被 2 处调用(stream, stream_request);外部调用 2 个(path, spawn_response_stream)。

codex-api/src/sse/responses.rs源码 ↗
io_transportrequest handling / streaming

可以把这个文件想成“直播字幕翻译员”。上游服务器通过 SSE(Server-Sent Events,一种服务器不断往客户端推小消息的连接)边生成边发送结果。这里先读取响应头,比如模型名、请求号、限流信息;再开一个后台任务持续读字节流,把每条 JSON 消息解析出来;然后按消息类型转成统一的 ResponseEvent。它还会识别特殊错误,比如上下文太长、额度不够、安全策略拦截、服务器过载,并转成更明确的 ApiError。另一个重点是收尾:只有收到 response.completed 才算正常结束;如果流断了却没完成,就会报错,避免把半截回答当成完整结果。

函数细节55
spawn_response_stream31–88 ↗
fn spawn_response_stream(
    stream_response: StreamResponse,
    idle_timeout: Duration,
    telemetry: Option<Arc<dyn SseTelemetry>>,
    turn_state: Option<Arc<OnceLock<String>>>,
) -> ResponseStr

作用:启动一次响应流的读取工作。调用者给它上游返回的流和超时时间,它会返回一个内部事件通道,后面可以从这个通道里一条条拿到模型输出、限流信息或错误。

数据流:进去的是上游响应、空闲超时时间、可选遥测记录器和可选的回合状态存放处。它先从响应头里读出限流、模型名、请求 ID、模型列表标签等信息,马上发成内部事件;然后把真正的字节流交给后台任务继续解析。出来的是 ResponseStream,里面有接收事件的通道,也保留了上游请求 ID。

调用关系:它是外部代码进入本文件流处理能力的门口。它先调用 parse_all_rate_limits 取头部限流信息,再用 tokio::spawn 开后台任务,后台任务最终把流交给 process_sse 逐条处理。测试里的 spawn_response_stream_emits_header_events 和 spawn_response_stream_ignores_model_verification_header 会直接检查它的头部事件行为。

调用图:调用 2 个内部函数(parse_all_rate_limits, process_sse);被 2 处调用(spawn_response_stream_emits_header_events, spawn_response_stream_ignores_model_verification_header);外部调用 5 个(ModelsEtag, RateLimits, ServerModel, ServerReasoningIncluded, spawn)。

TokenUsage::from120–134 ↗
fn from(val: ResponseCompletedUsage) -> Self

作用:把服务器 response.completed 里的用量格式,转换成项目内部统一使用的 TokenUsage。token 可以理解成模型计费和计算时用的小文本单位。

数据流:进去的是服务器给的输入 token、输出 token、总 token,以及可选的缓存输入和推理输出明细。它把字段搬到内部结构里,缺失的明细按 0 处理。出来的是内部 TokenUsage,不改别的东西。

调用关系:process_responses_event 在处理 response.completed 时会用到这个转换,让完成事件里带上统一格式的用量统计。

ResponsesStreamEvent::kind163–165 ↗
fn kind(&self) -> &str

作用:拿到一条原始流事件的类型名。类型名就是判断这条消息是“输出文字”“完成”“失败”还是“元数据”的标签。

数据流:进去的是一个 ResponsesStreamEvent。它只读取里面的 kind 字段,原样返回字符串引用,不做修改。

调用关系:turn_state、model_verifications、turn_moderation_metadata 都先用它确认事件是不是 response.metadata,再决定要不要继续读元数据。

调用图:被 3 处调用(model_verifications, turn_moderation_metadata, turn_state)。

ResponsesStreamEvent::response_model172–186 ↗
fn response_model(&self) -> Option<String>

作用:从一条事件里找服务器实际使用的模型名。它只认头部里的模型名,不把普通 payload 里的 model 字段当准确信息。

数据流:进去的是一条流事件。它先看 response.headers 里的 openai-model 或 x-openai-model;如果没有,再看事件顶层 headers。出来是可选的模型名字符串。

调用关系:process_sse 每收到一条事件都会问它有没有新的模型名;如果模型名变了,就发送 ServerModel 事件给下游。

ResponsesStreamEvent::turn_state188–196 ↗
fn turn_state(&self) -> Option<String>

作用:从元数据事件里取出本回合状态。这个状态通常来自服务器头部,用来让调用方知道当前对话回合的服务端状态。

数据流:进去的是一条流事件。它先用 kind 判断必须是 response.metadata;然后从 headers 里找 x-codex-turn-state。找到就返回字符串,找不到就返回空。

调用关系:它依赖 kind 和 header_turn_state_value_from_json。当前文件里主要提供这个提取能力,供需要读取回合状态的流程使用。

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

ResponsesStreamEvent::model_verifications198–207 ↗
fn model_verifications(&self) -> Option<Vec<ModelVerification>>

作用:从元数据事件里提取服务器给出的模型验证建议。比如某些安全相关模型可能要求“可信访问”能力。

数据流:进去的是一条流事件。它先确认类型是 response.metadata,再从 metadata.openai_verification_recommendation 里读取数组,过滤掉不认识的值和重复值。出来是可选的 ModelVerification 列表。

调用关系:process_sse 会调用它;如果拿到结果,就发出 ModelVerifications 事件,让后续流程知道服务器给了哪些验证要求。

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

ResponsesStreamEvent::turn_moderation_metadata209–219 ↗
fn turn_moderation_metadata(&self) -> Option<TurnModerationMetadataEvent>

作用:从元数据事件里取出本回合的审核提示信息。审核信息可以告诉前端或上层逻辑,这次内容该如何展示或处理。

数据流:进去的是一条流事件。它先确认类型是 response.metadata,再复制 metadata.openai_chatgpt_moderation_metadata 这段 JSON。出来是可选的 TurnModerationMetadataEvent。

调用关系:process_sse 会调用它;如果有审核元数据,就通过 TurnModerationMetadata 事件发给下游。

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

header_openai_model_value_from_json222–232 ↗
fn header_openai_model_value_from_json(value: &Value) -> Option<String>

作用:在一堆 JSON 形式的响应头里找模型名。它兼容 openai-model 和 x-openai-model,并且大小写不敏感。

数据流:进去的是一个 JSON 值。它要求这个值是对象,然后逐个检查键名;命中模型头后,把对应值转成字符串。出来是可选模型名。

调用关系:ResponsesStreamEvent::response_model 用它读取 response.headers 或顶层 headers 里的模型名。

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

header_turn_state_value_from_json234–243 ↗
fn header_turn_state_value_from_json(value: &Value) -> Option<String>

作用:在 JSON 形式的响应头里找 x-codex-turn-state。这个头表示服务器记录的对话回合状态。

数据流:进去的是一个 JSON 值。它先确认是对象,再大小写不敏感地查找指定头名,并把值转成字符串。出来是可选的回合状态字符串。

调用关系:ResponsesStreamEvent::turn_state 用它完成真正的头部查找。

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

model_verifications_from_json_value245–268 ↗
fn model_verifications_from_json_value(value: &Value) -> Option<Vec<ModelVerification>>

作用:把服务器给的验证建议 JSON 转成内部枚举列表。它会自动忽略不认识的建议,避免陌生字段打断整个流。

数据流:进去的是一个 JSON 值。它只接受数组,逐项取字符串,交给 parse_model_verification 翻译,并去掉重复项。没有有效项就返回空,有有效项就返回列表。

调用关系:ResponsesStreamEvent::model_verifications 调用它来解释 metadata.openai_verification_recommendation 字段。

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

parse_model_verification270–275 ↗
fn parse_model_verification(value: &str) -> Option<ModelVerification>

作用:把一个验证建议的文本名字翻译成内部的 ModelVerification。现在它认识 trusted_access_for_cyber 这一种。

数据流:进去的是字符串。它和已知常量比较,匹配就返回对应枚举,不匹配就返回空。

调用关系:model_verifications_from_json_value 在遍历验证建议数组时会逐个调用它。

json_value_as_string277–283 ↗
fn json_value_as_string(value: &Value) -> Option<String>

作用:尽量从 JSON 值里取出字符串。它还兼容“头部值被包装成数组”的情况,会取数组第一个元素继续解析。

数据流:进去的是 JSON 值。若是字符串就复制出来;若是数组就看第一个元素;其他类型返回空。不会改动原值。

调用关系:header_openai_model_value_from_json 和 header_turn_state_value_from_json 都靠它把头部值安全地变成字符串。

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

ResponsesEventError::into_api_error291–295 ↗
fn into_api_error(self) -> ApiError

作用:把本文件内部的事件处理错误,转换成统一的 ApiError。这样外层不用关心内部包了一层什么错误类型。

数据流:进去的是 ResponsesEventError。它拆开枚举,把里面的 ApiError 拿出来返回,不产生新副作用。

调用关系:process_sse 在 process_responses_event 返回错误时会调用它,然后决定先记住这个错误,等流结束时发给下游。

process_responses_event298–432 ↗
fn process_responses_event(
    event: ResponsesStreamEvent,
) -> std::result::Result<Option<ResponseEvent>, ResponsesEventError>

作用:把一条已经解析好的服务器事件,翻译成项目内部的一条 ResponseEvent,或者翻译成明确的错误。它是本文件最核心的“事件分类器”。

数据流:进去的是 ResponsesStreamEvent。它按 type 字段分支处理:文字增量变成 OutputTextDelta,工具输入变成 ToolCallInputDelta,完成消息带上 response_id 和 token 用量,失败消息根据错误码变成不同 ApiError。出来可能是一条内部事件、没有事件,或一个错误。

调用关系:process_sse 每读到一条 SSE JSON 都会把它交给这里。这里又会调用一组小判断函数,比如 is_context_window_error、is_quota_exceeded_error、cyber_policy_message、try_parse_retry_after,来把服务器错误变成更懂人话的内部错误。

调用图:调用 8 个内部函数(cyber_policy_message, is_context_window_error, is_cyber_policy_error, is_invalid_prompt_error, is_quota_exceeded_error, is_server_overloaded_error, is_usage_not_included, try_parse_retry_after);被 1 处调用(process_sse);外部调用 8 个(OutputItemAdded, OutputItemDone, OutputTextDelta, Stream, Api, debug!, format!, trace!)。

process_sse434–529 ↗
async fn process_sse(
    stream: ByteStream,
    tx_event: mpsc::Sender<Result<ResponseEvent, ApiError>>,
    idle_timeout: Duration,
    telemetry: Option<Arc<dyn SseTelemetry>>,
)

作用:持续读取 SSE 字节流,直到收到完成事件、出错、超时或连接提前关闭。它把底层网络流变成上层可以消费的事件通道。

数据流:进去的是字节流、发送事件的通道、空闲超时时间和可选遥测记录器。它不断等下一条 SSE,解析 JSON,先发模型名、验证建议、审核元数据等旁路信息,再把主事件交给 process_responses_event。出来不是返回值,而是通过通道发送事件或错误;收到 completed 后正常结束。

调用关系:spawn_response_stream 会在后台任务里调用它;测试辅助函数 collect_events、run_sse 也直接调用它来模拟流。它是整个流式响应读取期间最活跃的循环。

调用图:调用 1 个内部函数(process_responses_event);被 4 处调用(spawn_response_stream, collect_events, emits_completed_without_stream_end, run_sse);外部调用 13 个(eventsource, next, now, send, ModelVerifications, ServerModel, TurnModerationMetadata, Stream, debug!, matches! (+3 more))。

try_parse_retry_after531–555 ↗
fn try_parse_retry_after(err: &Error) -> Option<Duration>

作用:从限流错误消息里猜出“多久后再试”。这让上层可以告诉用户或自动重试,而不是只说失败。

数据流:进去的是服务器错误结构。它先确认错误码是 rate_limit_exceeded,再用正则表达式从消息里找“try again in 多少 s/ms/seconds”。找到就转成 Duration,找不到返回空。

调用关系:process_responses_event 在处理未知的 response.failed 限流类错误时会调用它。相关测试 test_try_parse_retry_after、test_try_parse_retry_after_no_delay、test_try_parse_retry_after_azure 覆盖了毫秒、秒和 Azure 风格文案。

调用图:调用 1 个内部函数(rate_limit_regex);被 4 处调用(process_responses_event, test_try_parse_retry_after, test_try_parse_retry_after_azure, test_try_parse_retry_after_no_delay);外部调用 2 个(from_millis, from_secs_f64)。

is_context_window_error557–559 ↗
fn is_context_window_error(error: &Error) -> bool

作用:判断错误是不是“上下文窗口太长”。上下文窗口就是模型一次能看进去的输入长度上限。

数据流:进去的是 Error。它只检查 code 是否等于 context_length_exceeded,返回真假。

调用关系:process_responses_event 用它把这种失败转成 ApiError::ContextWindowExceeded。

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

is_quota_exceeded_error561–563 ↗
fn is_quota_exceeded_error(error: &Error) -> bool

作用:判断错误是不是账号额度不够。额度不够通常不是重试能马上解决的问题。

数据流:进去的是 Error。它检查 code 是否等于 insufficient_quota,返回真假。

调用关系:process_responses_event 用它把服务器失败归类成 ApiError::QuotaExceeded。

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

is_usage_not_included565–567 ↗
fn is_usage_not_included(error: &Error) -> bool

作用:判断错误是不是服务器没有包含用量信息。用量信息缺失会影响计费或统计展示。

数据流:进去的是 Error。它检查 code 是否等于 usage_not_included,返回真假。

调用关系:process_responses_event 用它把对应失败转成 ApiError::UsageNotIncluded。

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

is_invalid_prompt_error569–571 ↗
fn is_invalid_prompt_error(error: &Error) -> bool

作用:判断错误是不是提示词无效。提示词就是发给模型的用户输入和系统说明。

数据流:进去的是 Error。它检查 code 是否等于 invalid_prompt,返回真假。

调用关系:process_responses_event 用它生成 ApiError::InvalidRequest,并尽量保留服务器给的说明文字。

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

is_cyber_policy_error573–575 ↗
fn is_cyber_policy_error(error: &Error) -> bool

作用:判断错误是不是网络安全策略拦截。也就是服务器认为请求可能有网络安全风险。

数据流:进去的是 Error。它检查 code 是否等于 cyber_policy,返回真假。

调用关系:process_responses_event 用它识别安全策略错误,并通过 cyber_policy_message 生成展示给用户的消息。

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

is_server_overloaded_error577–580 ↗
fn is_server_overloaded_error(error: &Error) -> bool

作用:判断错误是不是服务器太忙或要求客户端放慢。这样上层可以用“服务繁忙”这类更准确的提示。

数据流:进去的是 Error。它检查 code 是否为 server_is_overloaded 或 slow_down,返回真假。

调用关系:process_responses_event 用它把这类失败转成 ApiError::ServerOverloaded。

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

cyber_policy_fallback_message582–584 ↗
fn cyber_policy_fallback_message() -> String

作用:提供网络安全策略拦截时的默认提示语。当服务器没有给可用消息时,就用这句兜底。

数据流:没有输入。它创建并返回一段固定英文说明,不改任何状态。

调用关系:cyber_policy_message 在发现服务器消息为空白时会调用它。

cyber_policy_message586–590 ↗
fn cyber_policy_message(message: Option<String>) -> String

作用:为网络安全策略错误挑一条可展示的消息。优先用服务器给的内容,但不会接受空白字符串。

数据流:进去的是可选消息字符串。它过滤掉空白内容;如果还有有效文字就返回它,否则返回 cyber_policy_fallback_message 的默认句子。

调用关系:process_responses_event 在识别到 cyber_policy 错误时调用它,生成 ApiError::CyberPolicy 里的 message。

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

rate_limit_regex592–598 ↗
fn rate_limit_regex() -> &'static regex_lite::Regex

作用:提供一个专门识别“try again in 多少时间”的正则表达式。正则表达式可以理解成按模式找文字的工具。

数据流:没有业务输入。第一次调用时编译正则并缓存起来,之后复用同一个对象。出来的是静态可用的 Regex 引用。

调用关系:try_parse_retry_after 用它从限流错误文案里提取等待时间。OnceLock 保证正则只初始化一次,避免每次解析都重新编译。

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

tests::collect_events620–642 ↗
async fn collect_events(chunks: &[&[u8]]) -> Vec<Result<ResponseEvent, ApiError>>

作用:测试用的小工具:把几段模拟的 SSE 字节喂给 process_sse,然后收集它发出的结果。

数据流:进去的是字节片段数组。它搭一个假的异步读取器,把片段按顺序读出来,启动 process_sse,并从通道里收集所有 Ok 或 Err。出来是事件结果列表。

调用关系:多个测试用它验证正常输出、缺少 completed、错误事件等场景。它把测试数据和真实的 process_sse 流程接起来。

调用图:调用 1 个内部函数(process_sse);外部调用 6 个(pin, new, new, new, idle_timeout, spawn)。

tests::run_sse644–673 ↗
async fn run_sse(events: Vec<serde_json::Value>) -> Vec<ResponseEvent>

作用:测试用的小工具:把一组 JSON 事件包装成标准 SSE 文本,再跑完整解析流程。

数据流:进去的是 JSON 事件列表。它根据 type 字段生成 event/data 格式的文本,启动 process_sse,收集成功事件并直接拆出 ResponseEvent。出来是内部事件列表。

调用关系:许多测试用它快速搭建流式事件,不用手写 SSE 文本。它直接调用 process_sse。

调用图:调用 2 个内部函数(process_sse, new);外部调用 7 个(pin, new, new, new, idle_timeout, format!, spawn)。

tests::idle_timeout675–677 ↗
fn idle_timeout() -> Duration

作用:给测试提供统一的空闲超时时间。这样测试不会因为等待流数据而卡住太久。

数据流:没有输入。它返回 1000 毫秒的 Duration。

调用关系:collect_events、run_sse 和部分直接启动 process_sse 的测试都会调用它。

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

tests::parses_items_and_completed680–743 ↗
async fn parses_items_and_completed()

作用:验证普通输出条目和完成事件能被正确解析。它覆盖了 assistant 消息和可选 phase 字段。

数据流:它构造两条 output_item.done 和一条 completed SSE,送进 collect_events。然后检查前两条是输出项,最后一条是 completed,且 response_id 正确、用量和 end_turn 为空。

调用关系:它通过 collect_events 间接测试 process_sse 和 process_responses_event 的主路径。

调用图:外部调用 7 个(assert!, assert_eq!, assert_matches!, collect_events, format!, json!, panic!)。

tests::error_when_missing_completed746–771 ↗
async fn error_when_missing_completed()

作用:验证流提前结束但没有 response.completed 时会报错。这样可以防止半截回答被当成完整回答。

数据流:它只构造一条输出项,没有完成事件。collect_events 收到输出项后,最终还应收到 Stream 错误,消息是 stream closed before response.completed。

调用关系:它测试 process_sse 在 Ok(None) 即流结束时的收尾判断。

调用图:外部调用 6 个(assert_eq!, assert_matches!, collect_events, format!, json!, panic!)。

tests::parses_tool_search_call_items774–807 ↗
async fn parses_tool_search_call_items()

作用:验证工具搜索调用条目能被解析出来。工具搜索调用是模型要求客户端执行搜索类工具的一种输出。

数据流:它构造一个 tool_search_call 输出项和一个 completed。run_sse 后检查第一条事件里的 call_id、execution 和 arguments 都保留下来。

调用关系:它间接验证 process_responses_event 对 ResponseItem::ToolSearchCall 的 JSON 反序列化能力。

调用图:外部调用 4 个(assert_eq!, assert_matches!, run_sse, vec!)。

tests::parses_tool_call_input_deltas810–839 ↗
async fn parses_tool_call_input_deltas()

作用:验证自定义工具调用输入的增量片段会被转成内部事件。增量片段就是工具参数一点点流出来的内容。

数据流:它构造 custom_tool_call_input.delta、一个当前未处理的 function_call_arguments.delta,以及 completed。run_sse 后检查第一条是 ToolCallInputDelta,第二条是 Completed。

调用关系:它测试 process_responses_event 只处理支持的工具输入增量,并忽略不认识的事件类型。

调用图:外部调用 3 个(assert_matches!, run_sse, vec!)。

tests::emits_completed_without_stream_end842–884 ↗
async fn emits_completed_without_stream_end()

作用:验证一旦收到 response.completed,即使底层连接还没关闭,也会立刻结束处理。

数据流:它构造一条 completed 后接一个永远 pending 的流。process_sse 应该发出 Completed 后返回,而不是一直等连接关闭。

调用关系:它直接调用 process_sse,检查 completed 分支里的提前返回逻辑。

调用图:调用 1 个内部函数(process_sse);外部调用 14 个(pin, from_millis, new, assert!, assert_eq!, idle_timeout, format!, json!, panic!, iter (+4 more))。

tests::error_when_error_event887–906 ↗
async fn error_when_error_event()

作用:验证限流失败事件会变成可重试错误,并且能解析出建议等待时间。

数据流:它构造 response.failed,错误码是 rate_limit_exceeded,消息里含 11.054s。collect_events 后检查结果是 ApiError::Retryable,且 delay 是 11.054 秒。

调用关系:它覆盖 process_responses_event 的失败分支,也覆盖 try_parse_retry_after 的实际调用。

调用图:外部调用 4 个(assert_eq!, collect_events, format!, panic!)。

tests::context_window_error_is_fatal909–919 ↗
async fn context_window_error_is_fatal()

作用:验证上下文过长错误会被识别成专门的致命错误。

数据流:它构造 code 为 context_length_exceeded 的 failed 事件。collect_events 后应该得到 ApiError::ContextWindowExceeded。

调用关系:它间接测试 process_responses_event 调用 is_context_window_error 的分支。

调用图:外部调用 4 个(assert_eq!, assert_matches!, collect_events, format!)。

tests::context_window_error_with_newline_is_fatal922–932 ↗
async fn context_window_error_with_newline_is_fatal()

作用:验证错误消息里有换行时,上下文过长仍然能正确识别。

数据流:它构造带换行 message 的 context_length_exceeded 失败事件。输出仍应是 ApiError::ContextWindowExceeded。

调用关系:它强调判断主要靠错误 code,而不是脆弱地匹配整段 message。

调用图:外部调用 4 个(assert_eq!, assert_matches!, collect_events, format!)。

tests::quota_exceeded_error_is_fatal935–945 ↗
async fn quota_exceeded_error_is_fatal()

作用:验证额度不足错误会被识别为 QuotaExceeded。

数据流:它构造 code 为 insufficient_quota 的 failed 事件。collect_events 后检查只收到 ApiError::QuotaExceeded。

调用关系:它间接测试 process_responses_event 调用 is_quota_exceeded_error 的分支。

调用图:外部调用 4 个(assert_eq!, assert_matches!, collect_events, format!)。

tests::cyber_policy_error_is_fatal948–963 ↗
async fn cyber_policy_error_is_fatal()

作用:验证网络安全策略拦截会保留服务器给出的提示文字。

数据流:它构造 code 为 cyber_policy 且 message 有内容的 failed 事件。结果应是 ApiError::CyberPolicy,并且 message 与服务器文本一致。

调用关系:它覆盖 process_responses_event、is_cyber_policy_error 和 cyber_policy_message 的组合行为。

调用图:外部调用 4 个(assert_eq!, collect_events, format!, panic!)。

tests::cyber_policy_error_uses_fallback_for_empty_message966–984 ↗
async fn cyber_policy_error_uses_fallback_for_empty_message()

作用:验证网络安全策略错误的 message 为空白时,会使用默认提示。

数据流:它构造 cyber_policy 错误,但 message 只有空格。结果应是 ApiError::CyberPolicy,消息来自 cyber_policy_fallback_message。

调用关系:它重点测试 cyber_policy_message 对空白消息的兜底逻辑。

调用图:外部调用 4 个(assert_eq!, collect_events, format!, panic!)。

tests::invalid_prompt_without_type_is_invalid_request987–1005 ↗
async fn invalid_prompt_without_type_is_invalid_request()

作用:验证 invalid_prompt 错误会变成 InvalidRequest。即使错误里没有 type 字段,也不能影响识别。

数据流:它构造 code 为 invalid_prompt 的 failed 事件。collect_events 后检查 ApiError::InvalidRequest 中保留了服务器 message。

调用关系:它覆盖 process_responses_event 调用 is_invalid_prompt_error 的分支。

调用图:外部调用 4 个(assert_eq!, collect_events, format!, panic!)。

tests::table_driven_event_kinds1008–1083 ↗
async fn table_driven_event_kinds()

作用:用表格方式一次测试多种事件类型。这样新增或修改事件分支时,更容易看出哪些类型受影响。

数据流:它准备 created、output_item.done、unknown 三个案例,每个后面都补 completed。run_sse 后检查第一条事件和事件数量是否符合预期。

调用关系:它综合测试 process_responses_event 对已知事件的转换,以及对未知事件的忽略。

调用图:外部调用 5 个(assert!, assert_eq!, run_sse, json!, vec!)。

tests::spawn_response_stream_emits_header_events1086–1119 ↗
async fn spawn_response_stream_emits_header_events()

作用:验证 spawn_response_stream 会把响应头里的请求 ID 和模型名正确暴露出来。

数据流:它构造带 x-request-id 和 openai-model 的空流响应。调用 spawn_response_stream 后,检查 upstream_request_id,并从事件通道收到 ServerModel。

调用关系:它直接测试 spawn_response_stream 的头部预处理逻辑。

调用图:调用 1 个内部函数(spawn_response_stream);外部调用 8 个(pin, new, from_static, new, assert_eq!, idle_timeout, panic!, iter)。

tests::spawn_response_stream_ignores_model_verification_header1122–1156 ↗
async fn spawn_response_stream_ignores_model_verification_header()

作用:验证模型验证建议不能从普通响应头里读,只能从流里的 metadata 事件读。

数据流:它构造带 openai-verification-recommendation 头的响应,并发送 completed。收集所有事件后确认没有 ModelVerifications。

调用关系:它保护 spawn_response_stream 的设计边界:头部会发模型、限流等信息,但不把验证建议头当成有效来源。

调用图:调用 1 个内部函数(spawn_response_stream);外部调用 10 个(pin, new, from_static, new, assert!, idle_timeout, format!, json!, iter, vec!)。

tests::process_sse_ignores_response_model_field_in_payload1159–1188 ↗
async fn process_sse_ignores_response_model_field_in_payload()

作用:验证 response 里的普通 model 字段不会触发 ServerModel 事件。

数据流:它构造 created 和 completed,两者 response 内都有 model 字段但没有 headers。run_sse 后只应得到 Created 和 Completed。

调用关系:它测试 ResponsesStreamEvent::response_model 的规则:只信 headers,不信 payload 里的 model 字段。

调用图:外部调用 4 个(assert_eq!, assert_matches!, run_sse, vec!)。

tests::process_sse_emits_server_model_from_response_headers_payload1191–1225 ↗
async fn process_sse_emits_server_model_from_response_headers_payload()

作用:验证事件里的 response.headers.openai-model 会被识别并发出 ServerModel。

数据流:它构造 created 事件,response.headers 中放模型名,然后补 completed。run_sse 后第一条应是 ServerModel,后面是 Created 和 Completed。

调用关系:它覆盖 process_sse 调用 ResponsesStreamEvent::response_model 并发送模型变化事件的流程。

调用图:外部调用 4 个(assert_eq!, assert_matches!, run_sse, vec!)。

tests::process_sse_emits_model_verification_field1228–1260 ↗
async fn process_sse_emits_model_verification_field()

作用:验证 metadata 里的模型验证建议会被发成内部事件。

数据流:它构造 response.metadata,metadata.openai_verification_recommendation 是可信网络安全访问建议数组。run_sse 后第一条应是 ModelVerifications。

调用关系:它测试 process_sse、ResponsesStreamEvent::model_verifications 和 model_verifications_from_json_value 的连接。

调用图:外部调用 3 个(assert_matches!, run_sse, vec!)。

tests::process_sse_emits_turn_moderation_metadata_field1263–1295 ↗
async fn process_sse_emits_turn_moderation_metadata_field()

作用:验证回合审核元数据会从 metadata 事件里透传出来。

数据流:它构造 response.metadata,里面有 openai_chatgpt_moderation_metadata。run_sse 后第一条应是 TurnModerationMetadata,内容保持原 JSON。

调用关系:它覆盖 process_sse 调用 ResponsesStreamEvent::turn_moderation_metadata 的路径。

调用图:外部调用 3 个(assert_matches!, run_sse, vec!)。

tests::responses_stream_event_response_model_reads_top_level_headers1298–1311 ↗
fn responses_stream_event_response_model_reads_top_level_headers()

作用:验证 response_model 能从事件顶层 headers 读取模型名。

数据流:它把一段 JSON 反序列化成 ResponsesStreamEvent,headers 里放 openai-model。调用 response_model 后应得到该模型名。

调用关系:它直接测试 ResponsesStreamEvent::response_model 的第二优先级来源。

调用图:外部调用 3 个(assert_eq!, json!, from_value)。

tests::responses_stream_event_response_model_prefers_response_headers1314–1333 ↗
fn responses_stream_event_response_model_prefers_response_headers()

作用:验证 response.headers 里的模型名优先级高于顶层 headers。

数据流:它构造一个事件,顶层 headers 和 response.headers 都有 openai-model,但值不同。response_model 应返回 response.headers 的值。

调用关系:它直接保护 ResponsesStreamEvent::response_model 的优先级规则。

调用图:外部调用 3 个(assert_eq!, json!, from_value)。

tests::responses_stream_event_model_verification_reads_metadata_field1336–1352 ↗
fn responses_stream_event_model_verification_reads_metadata_field()

作用:验证模型验证建议能从 metadata 字段中读取出来。

数据流:它构造 response.metadata 事件,metadata 里放可信网络安全访问建议数组。model_verifications 应返回对应枚举列表。

调用关系:它直接测试 ResponsesStreamEvent::model_verifications 和 parse_model_verification 的正常路径。

调用图:外部调用 3 个(assert_eq!, json!, from_value)。

tests::responses_stream_event_model_verification_ignores_unknown_field1355–1366 ↗
fn responses_stream_event_model_verification_ignores_unknown_field()

作用:验证不认识的验证建议会被忽略,而不是报错。

数据流:它构造 metadata 里只有 unknown 的数组。model_verifications 返回空。

调用关系:它测试 model_verifications_from_json_value 对未知字符串的容错行为。

调用图:外部调用 3 个(assert_eq!, json!, from_value)。

tests::responses_stream_event_model_verification_ignores_non_array_field1369–1380 ↗
fn responses_stream_event_model_verification_ignores_non_array_field()

作用:验证验证建议字段不是数组时会被忽略。

数据流:它把 openai_verification_recommendation 写成单个字符串,而不是数组。model_verifications 返回空。

调用关系:它测试 model_verifications_from_json_value 对错误 JSON 形状的容错行为。

调用图:外部调用 3 个(assert_eq!, json!, from_value)。

tests::test_try_parse_retry_after1383–1394 ↗
fn test_try_parse_retry_after()

作用:验证 try_parse_retry_after 能解析毫秒单位的等待时间。

数据流:它构造 rate_limit_exceeded 错误,消息里写 try again in 28ms。函数应返回 28 毫秒。

调用关系:它直接测试 try_parse_retry_after 和 rate_limit_regex。

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

tests::test_try_parse_retry_after_no_delay1397–1407 ↗
fn test_try_parse_retry_after_no_delay()

作用:验证 try_parse_retry_after 能解析小数秒等待时间。

数据流:它构造 rate_limit_exceeded 错误,消息里写 1.898s。函数应返回 1.898 秒。

调用关系:它直接测试 try_parse_retry_after 对 s 单位和小数的支持。

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

tests::test_try_parse_retry_after_azure1410–1420 ↗
fn test_try_parse_retry_after_azure()

作用:验证 Azure 风格的限流提示也能解析。Azure 的文案可能是 Try again in 35 seconds。

数据流:它构造 rate_limit_exceeded 错误,消息里写 35 seconds。函数应返回 35 秒。

调用关系:它确保 try_parse_retry_after 的正则表达式兼容不同服务端文案。

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

codex-api/src/endpoint/responses_websocket.rs源码 ↗
io_transportrequest handling

这个文件像一根“电话线”和一个“翻译员”的组合。它先按 provider 配置拼出 WebSocket 地址,合并请求头,加上认证信息,再建立安全连接。连接成功后,它会记住服务器在握手时带回的额外信息,比如实际使用的模型、模型目录标记、是否包含推理内容。真正发请求时,它把请求序列化成 JSON 文本发出去,然后循环读取服务器消息:普通回答会被翻译成 ResponseEvent;限流消息会单独识别;服务器包在 WebSocket 里的错误会被还原成 HTTP 风格错误;连接超时、提前关闭、二进制消息等异常会变成 ApiError。底层的 WsStream 还专门开了一个后台任务,负责收发消息和自动回复 ping,避免上层直接碰复杂的 WebSocket 细节。文件末尾的测试确保请求格式、压缩配置、错误映射和请求头优先级都符合预期。

函数细节33
WsStream::new64–126 ↗
fn new(inner: WebSocketStream<MaybeTlsStream<TcpStream>>) -> Self

作用:创建一个安全好用的 WebSocket 包装器。它把原始 WebSocket 连接包起来,并启动一个后台任务统一处理收消息、发消息和自动回复心跳。

数据流:进去的是底层 WebSocket 连接 → 它建立两条内部通道:一条接收“要发送什么”的命令,一条把服务器消息送回上层;后台任务在发送命令和接收网络消息之间来回等待 → 出来的是 WsStream,上层以后只需要调用 send 和 next。

调用关系:这是 WebSocket 通道的地基。connect_websocket 建好网络连接后会调用它;它内部用 spawn 启动后台泵,并用 select 在“有发送命令”和“服务器有新消息”之间选择处理。

调用图:外部调用 2 个(select!, spawn)。

WsStream::request128–137 ↗
async fn request(
        &self,
        make_command: impl FnOnce(oneshot::Sender<Result<(), WsError>>) -> WsCommand,
    ) -> Result<(), WsError>

作用:向后台 WebSocket 任务提交一个操作,并等它告诉自己成功还是失败。这里目前主要用来发送消息。

数据流:进去的是一个能生成命令的函数 → 它创建一次性回信通道,把命令发给后台任务,然后等待后台任务返回发送结果 → 出来的是成功,或者连接已关闭等 WebSocket 错误。

调用关系:WsStream::send 会调用它。它是上层方法和后台泵之间的“传话窗口”,用内部 channel 把活儿递过去。

调用图:被 1 处调用(send);外部调用 2 个(send, channel)。

WsStream::send139–142 ↗
async fn send(&self, message: Message) -> Result<(), WsError>

作用:把一条 WebSocket 消息发给服务器。调用者不用知道后台任务和回执通道怎么运转。

数据流:进去的是一条 Message → 它把这条消息包装成 Send 命令,交给 WsStream::request → 出来的是发送成功或发送失败的结果。

调用关系:send_websocket_request 会用它发送请求文本;WsStream::request 负责真正把命令交给后台任务。

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

WsStream::next144–146 ↗
async fn next(&mut self) -> Option<Result<Message, WsError>>

作用:等待服务器发来的下一条有意义的 WebSocket 消息。它隐藏了 ping、pong 等底层心跳细节。

数据流:进去的是当前 WsStream 的可变引用 → 它从内部消息队列里等待一条结果 → 出来的是一条服务器消息、一个 WebSocket 错误,或者连接结束的空值。

调用关系:run_websocket_response_stream 和 probe_handshake 会用它读取服务器消息。真正的网络读取是在 WsStream::new 启动的后台任务里做的。

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

WsStream::drop150–152 ↗
fn drop(&mut self)

作用:当 WsStream 不再使用时,停掉它背后的后台任务,避免留下没人管的异步工作。

数据流:进去的是即将被销毁的 WsStream → 它调用 abort 取消后台泵任务 → 出来没有返回值,但后台任务会被终止。

调用关系:当连接出错、被关闭或对象生命周期结束时自动触发。stream_request 遇到严重错误时会丢弃 failed_stream,间接触发这里的清理。

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

ResponsesWebsocketConnection::fmt173–182 ↗
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result

作用:给 ResponsesWebsocketConnection 提供调试打印方式,同时避免把真实 WebSocket 流和遥测对象的内部细节打印出来。

数据流:进去的是连接对象和格式化器 → 它把超时、服务器模型等安全信息写入调试结构,把敏感或复杂字段显示成占位文字 → 出来的是格式化结果。

调用关系:这是 Rust 的 Debug 打印路径使用的函数。它不参与网络收发,只帮助日志或调试时看清连接状态。

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

ResponsesWebsocketConnection::new186–202 ↗
fn new(
        stream: WsStream,
        idle_timeout: Duration,
        server_reasoning_included: bool,
        models_etag: Option<String>,
        server_model: Option<String>,
        telemetry:

作用:把刚建立好的 WsStream 和连接相关设置打包成一个可复用的 ResponsesWebsocketConnection。

数据流:进去的是 WebSocket 流、空闲超时、服务器返回的模型信息和遥测对象 → 它把流放进 Arc<Mutex<Option<...>>> 这种共享加锁容器里,确保同一时间只有一个请求使用这条连接 → 出来的是连接对象。

调用关系:ResponsesWebsocketClient::connect 在 connect_websocket 成功后调用它。之后 stream_request 会使用这个连接对象发起实际请求。

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

ResponsesWebsocketConnection::is_closed204–206 ↗
async fn is_closed(&self) -> bool

作用:检查这条 WebSocket 连接是不是已经被标记为关闭。外部可以用它判断是否还能复用连接。

数据流:进去的是连接对象 → 它拿锁查看内部 stream 是否还存在 → 出来是 true 或 false。

调用关系:它是连接状态查询的小入口。它不调用其他业务函数,只读取 stream 容器里的状态。

ResponsesWebsocketConnection::stream_request214–287 ↗
async fn stream_request(
        &self,
        request: ResponsesWsRequest,
        connection_reused: bool,
        turn_state: Option<Arc<OnceLock<String>>>,
    ) -> Result<ResponseStream, ApiErro

作用:在已有 WebSocket 连接上发送一次 Responses 请求,并返回一个可以持续读事件的 ResponseStream。它把“发请求、读服务器连续事件、出错清理连接”串起来。

数据流:进去的是请求、这次是否复用连接、可选的 turn_state 共享槽 → 它先把请求转成 JSON,创建事件通道,然后启动后台任务;后台任务先发送服务器握手时已知的信息,再独占 WebSocket,调用 run_websocket_response_stream 处理真实收发 → 出来的是 ResponseStream,调用者从里面读取回答事件;如果出错,连接会被取走并关闭。

调用关系:这是上层真正发起流式请求的主入口。它调用 serialize_websocket_request 准备请求文本,把核心收发交给 run_websocket_response_stream,并把 ResponseEvent 通过通道交给调用者。

调用图:调用 2 个内部函数(run_websocket_response_stream, serialize_websocket_request);外部调用 7 个(clone, current, ModelsEtag, ServerModel, ServerReasoningIncluded, Stream, spawn)。

ResponsesWebsocketClient::new324–326 ↗
fn new(provider: Provider, auth: SharedAuthProvider) -> Self

作用:创建一个面向某个 provider 的 WebSocket 客户端。provider 可以理解为服务供应方配置,auth 是认证来源。

数据流:进去的是 provider 和共享认证提供者 → 它把两者保存到结构体里 → 出来的是 ResponsesWebsocketClient。

调用关系:websocket_reachability_check 会创建它来做连通性检查。之后 connect 或 probe_handshake 会使用里面的 provider 和 auth。

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

ResponsesWebsocketClient::connect334–360 ↗
async fn connect(
        &self,
        extra_headers: HeaderMap,
        default_headers: HeaderMap,
        turn_state: Option<Arc<OnceLock<String>>>,
        telemetry: Option<Arc<dyn WebsocketTel

作用:建立一条真正可用的 Responses WebSocket 连接。它负责拼 URL、准备请求头、加认证,然后拨号。

数据流:进去的是额外请求头、默认请求头、turn_state 和遥测对象 → 它从 provider 生成 responses 的 WebSocket 地址,合并请求头并加入认证,再调用 connect_websocket → 出来的是 ResponsesWebsocketConnection,里面保存了流、超时和服务器握手信息。

调用关系:这是正常业务连接的入口。它调用 merge_request_headers、provider 的 websocket_url_for_path、认证的 add_auth_headers、connect_websocket,最后调用 ResponsesWebsocketConnection::new 包装结果。

调用图:调用 4 个内部函数(new, connect_websocket, merge_request_headers, websocket_url_for_path);外部调用 1 个(add_auth_headers)。

ResponsesWebsocketClient::probe_handshake369–404 ↗
async fn probe_handshake(
        &self,
        extra_headers: HeaderMap,
        default_headers: HeaderMap,
        immediate_close_timeout: Duration,
    ) -> Result<ResponsesWebsocketProbe, ApiEr

作用:只测试 WebSocket 握手是否成功,不发送真正请求。它常用于诊断:能不能连上、服务器是否马上拒绝。

数据流:进去的是请求头和等待立即关闭的超时时间 → 它用和正式连接一样的方式建连,然后短暂等待服务器是否立刻发关闭帧 → 出来的是探测报告,包括 URL、HTTP 升级状态、服务器能力标记,以及可能的关闭原因。

调用关系:这是诊断流程使用的旁路入口。它同样调用 merge_request_headers 和 connect_websocket,但连接后只用 WsStream::next 读取一次可能的关闭消息,再交给 immediate_close_from_message 解析。

调用图:调用 3 个内部函数(connect_websocket, merge_request_headers, websocket_url_for_path);外部调用 2 个(add_auth_headers, timeout)。

immediate_close_from_message407–412 ↗
fn immediate_close_from_message(message: Message) -> Option<ResponsesWebsocketClose>

作用:判断一条 WebSocket 消息是不是关闭消息;如果是,就提取成探测报告能展示的关闭信息。

数据流:进去的是一条 Message → 它检查消息类型,只有 Close 才继续处理 → 出来是 ResponsesWebsocketClose,或者不是关闭消息时返回空。

调用关系:probe_handshake 在短暂等待服务器消息后使用它。它把具体转换交给 close_frame_to_probe。

close_frame_to_probe414–419 ↗
fn close_frame_to_probe(frame: CloseFrame) -> ResponsesWebsocketClose

作用:把 WebSocket 标准关闭帧转换成项目自己的探测关闭信息。

数据流:进去的是 CloseFrame,里面有关闭码和原因 → 它把两项都转成字符串 → 出来的是 ResponsesWebsocketClose。

调用关系:immediate_close_from_message 在确认消息是关闭帧后调用它。它是诊断结果的数据转换小工具。

merge_request_headers421–434 ↗
fn merge_request_headers(
    provider_headers: &HeaderMap,
    extra_headers: HeaderMap,
    default_headers: HeaderMap,
) -> HeaderMap

作用:合并三来源请求头,并保证优先级正确:provider 自带头先放,extra 可以覆盖它,default 只补缺。

数据流:进去的是 provider_headers、extra_headers、default_headers → 它先克隆 provider 头,再合入 extra,最后只把还没有的 default 放进去 → 出来是一份最终 HeaderMap。

调用关系:connect 和 probe_handshake 都会在拨号前调用它。测试 merge_request_headers_matches_http_precedence 专门验证它的优先级规则。

调用图:被 3 处调用(connect, probe_handshake, merge_request_headers_matches_http_precedence);外部调用 1 个(clone)。

connect_websocket436–505 ↗
async fn connect_websocket(
    url: Url,
    headers: HeaderMap,
    turn_state: Option<Arc<OnceLock<String>>>,
) -> Result<(WsStream, StatusCode, bool, Option<String>, Option<String>), ApiError>

作用:真正执行 WebSocket 拨号,并读取握手响应里的服务器信息。它是本文件连接网络的核心函数。

数据流:进去的是 URL、请求头和可选 turn_state 槽 → 它确保 TLS 加密库就绪,构造 WebSocket 请求,套用自定义 CA 证书配置,使用带压缩的 WebSocket 配置发起连接;成功后读取响应头里的推理标记、模型 ETag、服务器模型和 turn_state → 出来的是 WsStream、HTTP 状态码和这些握手信息;失败则转成 ApiError。

调用关系:ResponsesWebsocketClient::connect 和 probe_handshake 都依赖它。它调用 websocket_config 设置协议选项,失败时调用 map_ws_error 统一翻译错误,成功时调用 WsStream::new 包装底层连接。

调用图:调用 3 个内部函数(new, map_ws_error, websocket_config);被 2 处调用(connect, probe_handshake);外部调用 6 个(as_str, maybe_build_rustls_client_config_with_custom_ca, ensure_rustls_crypto_provider, error!, info!, connect_async_tls_with_config)。

websocket_config507–514 ↗
fn websocket_config() -> WebSocketConfig

作用:生成 WebSocket 协议配置,并开启 permessage-deflate 压缩。这个压缩可以让来回传输的大段文本更省流量。

数据流:进去没有业务输入 → 它创建默认扩展配置,打开 permessage_deflate,再放进 WebSocketConfig → 出来的是连接时要用的配置对象。

调用关系:connect_websocket 拨号时会调用它。测试 websocket_config_enables_permessage_deflate 确认压缩确实被打开。

调用图:被 2 处调用(connect_websocket, websocket_config_enables_permessage_deflate);外部调用 3 个(default, default, default)。

map_ws_error516–538 ↗
fn map_ws_error(err: WsError, url: &Url) -> ApiError

作用:把底层 WebSocket 库的错误翻译成项目统一使用的 ApiError。这样上层不用理解 tungstenite 的错误类型。

数据流:进去的是 WsError 和当前 URL → 如果是 HTTP 握手失败,就保留状态码、响应头、正文和 URL;如果是连接关闭,就转成流错误;如果是网络错误,就转成网络传输错误 → 出来的是 ApiError。

调用关系:connect_websocket 在拨号失败时调用它。它把底层错误统一包装,方便上层 connect 返回一致的错误格式。

调用图:被 1 处调用(connect_websocket);外部调用 5 个(to_string, to_string, Stream, Transport, Network)。

parse_wrapped_websocket_error_event558–564 ↗
fn parse_wrapped_websocket_error_event(payload: &str) -> Option<WrappedWebsocketErrorEvent>

作用:从服务器发来的文本里识别“被包装成普通 WebSocket 消息的错误事件”。有些错误不是握手失败,而是在连接内用 JSON 发回来。

数据流:进去的是一段文本 payload → 它尝试按 WrappedWebsocketErrorEvent 解析 JSON,并确认 type 是 error → 出来的是错误事件结构;如果不是错误或解析失败,返回空。

调用关系:run_websocket_response_stream 每收到文本消息都会先调用它看是不是错误。多组测试覆盖了错误、非错误、缺状态码等情况。

调用图:被 6 处调用(run_websocket_response_stream, parse_wrapped_websocket_error_event_ignores_non_error_payloads, parse_wrapped_websocket_error_event_maps_to_transport_http, parse_wrapped_websocket_error_event_with_connection_limit_maps_retryable, parse_wrapped_websocket_error_event_with_status_maps_invalid_request, parse_wrapped_websocket_error_event_without_status_is_not_mapped);外部调用 1 个(from_str)。

map_wrapped_websocket_error_event566–601 ↗
fn map_wrapped_websocket_error_event(
    event: WrappedWebsocketErrorEvent,
    original_payload: String,
) -> Option<ApiError>

作用:把 WebSocket 文本里的错误事件翻译成 ApiError。特别地,连接超过时长限制会被标成可重试错误,提示新建连接。

数据流:进去的是解析后的错误事件和原始文本 → 它先检查是否是 websocket_connection_limit_reached;是的话返回 Retryable;否则读取 status,非成功状态会被转成 HTTP 传输错误,并把 JSON 里的 headers 转成 HTTP 头 → 出来是 ApiError,或无法映射时返回空。

调用关系:run_websocket_response_stream 在识别出包装错误后调用它。相关测试验证 429、400、连接时长限制和缺少 status 的行为。

调用图:被 5 处调用(run_websocket_response_stream, parse_wrapped_websocket_error_event_maps_to_transport_http, parse_wrapped_websocket_error_event_with_connection_limit_maps_retryable, parse_wrapped_websocket_error_event_with_status_maps_invalid_request, parse_wrapped_websocket_error_event_without_status_is_not_mapped);外部调用 2 个(from_u16, Transport)。

json_headers_to_http_headers603–615 ↗
fn json_headers_to_http_headers(headers: JsonMap<String, Value>) -> HeaderMap

作用:把 JSON 对象形式的响应头转换成真正的 HTTP HeaderMap。服务器错误事件里的 headers 就需要这样还原。

数据流:进去的是 JSON 字段名到值的映射 → 它逐个检查 header 名是否合法,再把字符串、数字、布尔值转成 HeaderValue,跳过不能表示成请求头的值 → 出来的是 HeaderMap。

调用关系:map_wrapped_websocket_error_event 在构造 HTTP 风格 ApiError 时会间接使用它。它把单个值转换交给 json_header_value。

调用图:调用 1 个内部函数(json_header_value);外部调用 2 个(new, from_bytes)。

json_header_value617–625 ↗
fn json_header_value(value: Value) -> Option<HeaderValue>

作用:把一个 JSON 值转换成 HTTP 请求头值。它只接受字符串、数字和布尔值这些能合理变成文本的值。

数据流:进去的是 serde_json::Value → 字符串原样用,数字和布尔值转成字符串,数组和对象等复杂值直接拒绝;最后检查是否是合法 HeaderValue → 出来是 HeaderValue 或空。

调用关系:json_headers_to_http_headers 对每个 header 值都会调用它。它是错误事件 header 还原过程里的小过滤器。

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

run_websocket_response_stream627–755 ↗
async fn run_websocket_response_stream(
    ws_stream: &mut WsStream,
    tx_event: mpsc::Sender<std::result::Result<ResponseEvent, ApiError>>,
    request_text: String,
    idle_timeout: Duration,

作用:处理一次 WebSocket 请求的完整收发过程:先发送请求,再持续读取服务器事件,直到 response.completed 或出错。

数据流:进去的是可用的 WsStream、事件发送通道、请求文本、超时、遥测信息、连接是否复用、turn_state 槽 → 它先调用 send_websocket_request 发出请求;之后循环等待消息,记录遥测,识别包装错误、限流事件、模型变化、验证信息、审核元数据,并把普通响应事件交给 process_responses_event 转成 ResponseEvent → 出来是成功完成,或返回 ApiError;同时会不断往 tx_event 推送事件。

调用关系:stream_request 启动的后台任务会调用它。它把发送交给 send_websocket_request,把错误识别交给 parse_wrapped_websocket_error_event 和 map_wrapped_websocket_error_event,把限流交给 parse_rate_limit_event,把普通 SSE 风格事件交给 process_responses_event。

调用图:调用 4 个内部函数(map_wrapped_websocket_error_event, parse_wrapped_websocket_error_event, send_websocket_request, parse_rate_limit_event);被 1 处调用(stream_request);外部调用 13 个(now, send, ModelVerifications, RateLimits, ServerModel, TurnModerationMetadata, next, Stream, process_responses_event, debug! (+3 more))。

send_websocket_request757–788 ↗
async fn send_websocket_request(
    ws_stream: &WsStream,
    request_text: String,
    idle_timeout: Duration,
    telemetry: Option<&Arc<dyn WebsocketTelemetry>>,
    connection_reused: bool,
) ->

作用:把已经序列化好的请求文本作为 WebSocket 文本消息发出去,并记录发送耗时和错误。

数据流:进去的是 WsStream、请求 JSON 字符串、空闲超时、可选遥测和连接是否复用 → 它在超时限制内调用 WsStream::send 发送 Text 消息,然后把耗时、错误、复用信息报告给遥测对象 → 出来是成功或 ApiError。

调用关系:run_websocket_response_stream 一开始会调用它。它是实际写入 WebSocket 的一层包装,下面交给 WsStream::send。

调用图:调用 1 个内部函数(send);被 1 处调用(run_websocket_response_stream);外部调用 4 个(now, timeout, trace!, Text)。

serialize_websocket_request790–793 ↗
fn serialize_websocket_request(request: &ResponsesWsRequest) -> Result<String, ApiError>

作用:把 ResponsesWsRequest 转成要发到 WebSocket 上的 JSON 字符串。网络传输需要文本格式,所以必须先做这一步。

数据流:进去的是请求对象引用 → 它用 serde_json 序列化 → 出来是 JSON 字符串;如果对象无法编码,就返回流错误 ApiError。

调用关系:stream_request 在启动后台收发前调用它。测试 direct_serialization_preserves_websocket_request_payload 确认它不会偷偷改变请求内容。

调用图:被 2 处调用(stream_request, direct_serialization_preserves_websocket_request_payload);外部调用 1 个(to_string)。

tests::direct_serialization_preserves_websocket_request_payload806–848 ↗
fn direct_serialization_preserves_websocket_request_payload()

作用:测试 WebSocket 请求序列化不会改变原始请求内容。也就是说,发到线上之前的 JSON 和 serde 默认理解的请求是一致的。

数据流:进去的是测试里构造的一份 ResponseCreate 请求 → 它分别转成 JSON 值和 WebSocket 文本,再把文本解析回 JSON → 最后断言两份 JSON 完全相同。

调用关系:它调用 serialize_websocket_request。这个测试保护 stream_request 的第一步,避免请求格式被改坏。

调用图:调用 1 个内部函数(serialize_websocket_request);外部调用 5 个(from, assert_eq!, ResponseCreate, to_value, vec!)。

tests::websocket_config_enables_permessage_deflate851–854 ↗
fn websocket_config_enables_permessage_deflate()

作用:测试 WebSocket 配置确实打开了 permessage-deflate 压缩。

数据流:进去没有外部输入 → 它调用 websocket_config 生成配置 → 断言配置里的压缩扩展存在。

调用关系:它直接覆盖 websocket_config,防止以后有人误删压缩设置,影响 connect_websocket 的传输效率。

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

tests::parse_wrapped_websocket_error_event_maps_to_transport_http857–906 ↗
fn parse_wrapped_websocket_error_event_maps_to_transport_http()

作用:测试 WebSocket 内部返回的 429 限额错误会被还原成 HTTP 传输错误,并保留响应头和正文。

数据流:进去的是测试构造的 error JSON,里面有 status、error 和 headers → 它先解析包装错误,再映射成 ApiError → 最后检查状态码是 429、headers 被正确转换、body 还包含原始错误信息。

调用关系:它调用 parse_wrapped_websocket_error_event 和 map_wrapped_websocket_error_event。这个测试保护 run_websocket_response_stream 遇到限额错误时的错误呈现方式。

调用图:调用 2 个内部函数(map_wrapped_websocket_error_event, parse_wrapped_websocket_error_event);外部调用 4 个(assert!, assert_eq!, json!, panic!)。

tests::parse_wrapped_websocket_error_event_ignores_non_error_payloads909–920 ↗
fn parse_wrapped_websocket_error_event_ignores_non_error_payloads()

作用:测试普通响应事件不会被误当成错误事件。这样正常的 response.created 之类消息才能继续走正常解析流程。

数据流:进去的是 type 为 response.created 的 JSON → 它调用 parse_wrapped_websocket_error_event → 出来应该是空,并用断言确认。

调用关系:它覆盖 parse_wrapped_websocket_error_event 的过滤逻辑,避免 run_websocket_response_stream 把普通事件误判为错误。

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

tests::parse_wrapped_websocket_error_event_with_status_maps_invalid_request923–945 ↗
fn parse_wrapped_websocket_error_event_with_status_maps_invalid_request()

作用:测试带 status 的无效请求错误会被映射成 HTTP 400 错误,并保留错误正文。

数据流:进去的是测试构造的 invalid_request_error JSON → 它解析后映射成 ApiError → 最后断言状态码是 400,正文里有错误类型和错误消息。

调用关系:它调用 parse_wrapped_websocket_error_event 和 map_wrapped_websocket_error_event。它保证 run_websocket_response_stream 能把服务器业务拒绝清楚地传给上层。

调用图:调用 2 个内部函数(map_wrapped_websocket_error_event, parse_wrapped_websocket_error_event);外部调用 4 个(assert!, assert_eq!, json!, panic!)。

tests::parse_wrapped_websocket_error_event_with_connection_limit_maps_retryable948–969 ↗
fn parse_wrapped_websocket_error_event_with_connection_limit_maps_retryable()

作用:测试 WebSocket 连接达到 60 分钟限制时,会被标记成可重试错误,而不是普通失败。

数据流:进去的是带 websocket_connection_limit_reached code 的 error JSON → 它解析并映射 → 出来应该是 ApiError::Retryable,消息等于预设提示,delay 为空。

调用关系:它调用 parse_wrapped_websocket_error_event 和 map_wrapped_websocket_error_event。这个测试保护自动重连或上层重试逻辑能识别这种特殊情况。

调用图:调用 2 个内部函数(map_wrapped_websocket_error_event, parse_wrapped_websocket_error_event);外部调用 3 个(assert_eq!, json!, panic!)。

tests::parse_wrapped_websocket_error_event_without_status_is_not_mapped972–990 ↗
fn parse_wrapped_websocket_error_event_without_status_is_not_mapped()

作用:测试缺少 HTTP 状态码的包装错误不会被硬转成 ApiError。因为没有状态码时,代码无法可靠判断它对应什么 HTTP 错误。

数据流:进去的是没有 status 的 error JSON → 它先能被解析成包装错误,但映射时因为缺少 status 返回空 → 断言结果为空。

调用关系:它调用 parse_wrapped_websocket_error_event 和 map_wrapped_websocket_error_event。它约束错误映射逻辑不要过度猜测。

调用图:调用 2 个内部函数(map_wrapped_websocket_error_event, parse_wrapped_websocket_error_event);外部调用 2 个(assert!, json!)。

tests::merge_request_headers_matches_http_precedence993–1023 ↗
fn merge_request_headers_matches_http_precedence()

作用:测试请求头合并规则符合预期:extra 覆盖 provider,default 只补没有的字段。

数据流:进去的是测试里构造的三组 HeaderMap → 它调用 merge_request_headers 合并 → 最后分别检查 originator、x-priority 和 x-default-only 的最终值。

调用关系:它直接覆盖 merge_request_headers。这个规则会影响 connect 和 probe_handshake 发出去的真实连接请求。

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

codex-api/src/endpoint/compact.rs源码 ↗
io_transportrequest handling

这个文件像一个专门跑腿的窗口:外部代码把需要压缩的内容交给 CompactClient,它会把内容变成服务器能看懂的 JSON,通过 HTTP(一种常见的网络请求方式)发到 responses/compact 这个地址,再把服务器回来的 JSON 拆成一组 ResponseItem 返回。它还会处理请求超时时间和额外请求头。如果服务器在响应头里带了 x-codex-turn-state,它会把这个“回合状态”存进 OnceLock(只能成功写入一次的小盒子),方便后续流程使用。这里还有一个很小的测试用假传输层,确保路径没有写错,避免客户端把请求发到错误地址。

函数细节8
CompactClient::new24–28 ↗
fn new(transport: T, provider: Provider, auth: SharedAuthProvider) -> Self

作用:创建一个新的压缩客户端。调用者把网络传输工具、服务提供方信息和认证信息交进来,它就组装出一个能发请求的客户端。

数据流:输入是 transportproviderauth。函数把它们交给 EndpointSession::new,做成内部会话对象,然后包进 CompactClient 返回。它不发送网络请求,只是完成准备工作。

调用关系:这是使用压缩接口前的第一步。它把真正发请求需要的基础零件交给 EndpointSession,后面的 compactcompact_input 都依赖这个会话去联系服务器。

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

CompactClient::with_telemetry30–34 ↗
fn with_telemetry(self, request: Option<Arc<dyn RequestTelemetry>>) -> Self

作用:给客户端加上可选的请求遥测。遥测可以理解成“记录请求过程的观察员”,用来统计或追踪请求发生了什么。

数据流:输入是已有的客户端和一个可选的遥测对象。函数把遥测对象交给内部 session.with_request_telemetry,得到带观察能力的新会话,再返回一个新的 CompactClient。原来的业务能力不变,只是多了记录请求的能力。

调用关系:它通常在创建客户端之后、真正发请求之前调用。它不自己记录数据,而是把这件事交给 EndpointSessionwith_request_telemetry 去接入。

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

CompactClient::path36–38 ↗
fn path() -> &'static str

作用:返回压缩接口固定使用的服务器路径。这样路径集中写在一个地方,避免到处手写字符串时写错。

数据流:没有输入,也不读取外部状态。它直接返回固定字符串 responses/compact。没有副作用。

调用关系compact 发请求时会用它决定请求发到哪里。测试 tests::path_is_responses_compact 也会检查它,确保这个地址保持正确。

CompactClient::compact40–70 ↗
async fn compact(
        &self,
        body: serde_json::Value,
        extra_headers: HeaderMap,
        request_timeout: Duration,
        turn_state: Option<&OnceLock<String>>,
    ) -> Result<Ve

作用:把已经准备好的 JSON 请求体发给服务器,请服务器压缩历史内容,并返回压缩后的响应条目。它是这个文件里真正执行网络请求的核心函数。

数据流:输入是 JSON 请求体、额外请求头、超时时间,以及一个可选的 turn_state 保存盒。函数用 POST 请求把这些发到 responses/compact,同时设置超时;拿到响应后,如果响应头里有回合状态,就尝试写入 turn_state;最后把响应正文从 JSON 字节解析成 CompactHistoryResponse,取出里面的 output 返回。如果请求或解析失败,就返回 ApiError

调用关系:它由外部直接调用,也会被 compact_input 调用。它把发请求的具体工作交给内部 session.execute_with,把路径交给 path,把响应解析交给 JSON 解析函数。

调用图:调用 1 个内部函数(execute_with);被 1 处调用(compact_input);外部调用 2 个(path, from_slice)。

CompactClient::compact_input72–83 ↗
async fn compact_input(
        &self,
        input: &CompactionInput<'_>,
        extra_headers: HeaderMap,
        request_timeout: Duration,
        turn_state: Option<&OnceLock<String>>,
    ) ->

作用:接收结构化的压缩输入,并替调用者把它转成 JSON 后发送。这样调用者不用自己关心 JSON 怎么拼。

数据流:输入是 CompactionInput、额外请求头、超时时间和可选的 turn_state。函数先用 to_value 把结构化输入编码成 JSON;如果编码失败,就返回错误;如果成功,就把 JSON 和其他参数继续交给 compact,最后返回服务器给出的 ResponseItem 列表。

调用关系:它是更方便、更安全的一层包装。调用者通常用它传入正常的压缩输入;它自己不发网络请求,而是整理好数据后交给 compact 完成真正的请求。

调用图:调用 1 个内部函数(compact);外部调用 1 个(to_value)。

tests::DummyTransport::execute103–105 ↗
async fn execute(&self, _req: Request) -> Result<Response, TransportError>

作用:这是测试里用的假网络执行函数。它的作用不是成功发请求,而是防止测试意外真的走到网络执行。

数据流:输入是一个请求,但函数故意不使用它。它直接返回一个构建错误,错误信息说明“execute 不应该运行”。它不产生真实响应,也不改动外部状态。

调用关系:它属于测试用的 DummyTransport。当前测试只检查路径,不应该发请求;如果哪天测试意外触发了 execute,这个函数会立刻报错,提醒测试写法有问题。

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

tests::DummyTransport::stream107–109 ↗
async fn stream(&self, _req: Request) -> Result<StreamResponse, TransportError>

作用:这是测试里用的假流式网络函数。流式可以理解成服务器一段一段返回内容;这里同样故意不让它真的运行。

数据流:输入是一个请求,但函数不使用。它直接返回一个构建错误,说明“stream 不应该运行”。没有真实网络流,也没有外部改动。

调用关系:它和 tests::DummyTransport::execute 一起补齐测试传输层需要的接口。当前路径测试不会调用它;如果被调用,说明测试偏离了只检查路径的目的。

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

tests::path_is_responses_compact113–115 ↗
fn path_is_responses_compact()

作用:检查压缩接口的路径是不是固定的 responses/compact。这是一个小保护,防止有人改代码时把服务器地址写错。

数据流:函数没有外部输入。它调用 CompactClient::<DummyTransport>::path() 取出路径,然后用断言比较它是否等于预期字符串。成功时什么也不改;失败时测试报错。

调用关系:这是本文件的单元测试。它使用 DummyTransport 只是为了满足客户端类型要求,并不会真的发网络请求;它关注的唯一事情是 path 返回的地址是否正确。

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

codex-api/src/endpoint/memories.rs源码 ↗
io_transport发起记忆总结请求时;测试时也会用假传输验证请求格式

这个文件像一个“邮递员”:别人只要把要总结的记忆内容交给它,它就知道该寄到哪个服务器地址、用什么 HTTP 方法、怎样带上认证信息,最后再把服务器回信拆开。核心是 MemoriesClient,它内部包着 EndpointSession,后者负责拼完整网址、加认证头、真正发送请求。这里固定使用的接口路径是 memories/trace_summarize,这很重要,因为服务端只认这个地址,改错就会请求失败。summarize 接收已经准备好的 JSON,请求服务器并解析 output 字段;summarize_input 更方便,它先把 MemorySummarizeInput 这种结构化输入转成 JSON,再复用 summarize。文件后半部分是测试用的假传输和假认证,用来确认请求地址、请求方法、请求内容和返回解析都符合预期,而不用真的访问网络。

函数细节14
MemoriesClient::new20–24 ↗
fn new(transport: T, provider: Provider, auth: SharedAuthProvider) -> Self

作用:创建一个新的记忆总结客户端。调用者提供网络传输工具、服务商配置和认证提供者后,就能用它向记忆总结接口发请求。

数据流:进去的是 transport、provider 和 auth:transport 负责实际发 HTTP 请求,provider 里有服务器地址等配置,auth 负责加认证头。函数把这些交给 EndpointSession::new 包起来。出来的是一个 MemoriesClient,里面已经准备好后续发请求所需的会话对象。

调用关系:它是使用这个客户端的起点。在测试 summarize_input_posts_expected_payload_and_parses_output 里,先用它组装客户端,然后再调用 summarize_input 验证完整请求流程。它把底层准备工作交给 EndpointSession::new。

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

MemoriesClient::with_telemetry26–30 ↗
fn with_telemetry(self, request: Option<Arc<dyn RequestTelemetry>>) -> Self

作用:给客户端加上或替换请求遥测。遥测可以理解成“记录请求过程的小探针”,方便观察请求耗时、状态等信息。

数据流:进去的是一个已经存在的 MemoriesClient 和可选的 RequestTelemetry。函数把遥测设置交给内部的 session.with_request_telemetry。出来的是一个新的 MemoriesClient,功能一样,但内部会话已经带上新的请求记录能力。

调用关系:它通常在创建客户端之后、真正发请求之前使用。它不直接发送网络请求,只是把“以后请求时要不要记录信息”这件事交给 EndpointSession 处理。

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

MemoriesClient::path32–34 ↗
fn path() -> &'static str

作用:返回记忆总结接口固定使用的路径。这个小函数保证所有地方都用同一个服务端地址片段,避免手写字符串写错。

数据流:它不需要输入,也不读取外部数据。它直接给出固定字符串 memories/trace_summarize。出来的是这个接口路径,后续会被拼到服务器基础地址后面。

调用关系:summarize 会用它来决定请求发到哪里。测试 path_is_memories_trace_summarize_for_wire_compatibility 专门检查这个值没有被改坏,因为它关系到和服务端的兼容性。

MemoriesClient::summarize36–48 ↗
async fn summarize(
        &self,
        body: serde_json::Value,
        extra_headers: HeaderMap,
    ) -> Result<Vec<MemorySummarizeOutput>, ApiError>

作用:把一份已经是 JSON 的记忆总结请求发给服务器,并把服务器返回的总结列表取出来。它适合调用者已经自己准备好请求正文的情况。

数据流:进去的是 JSON 请求体和额外 HTTP 头。函数用 POST 方法,把请求发到 path 给出的 memories/trace_summarize;收到响应后,从响应 body 里按 JSON 解析出 SummarizeResponse,再取出其中的 output。出来的是 MemorySummarizeOutput 列表;如果发送或解析失败,就返回 ApiError。

调用关系:它是实际发请求的核心步骤。summarize_input 会先把结构化输入转成 JSON,然后把活交给它。它内部依赖 EndpointSession::execute 发送请求,并依赖 serde_json::from_slice 把服务器返回的字节内容读成结构化结果。

调用图:调用 1 个内部函数(execute);被 1 处调用(summarize_input);外部调用 2 个(path, from_slice)。

MemoriesClient::summarize_input50–59 ↗
async fn summarize_input(
        &self,
        input: &MemorySummarizeInput,
        extra_headers: HeaderMap,
    ) -> Result<Vec<MemorySummarizeOutput>, ApiError>

作用:这是更顺手的记忆总结入口:调用者传入 MemorySummarizeInput 这种普通结构体,不用自己手写 JSON。它负责转换并调用真正的发送函数。

数据流:进去的是 MemorySummarizeInput 和额外 HTTP 头。函数先用 to_value 把输入转成 JSON;如果转换失败,就包装成 ApiError。转换成功后,它把 JSON 和头信息交给 summarize。出来的是服务器返回的 MemorySummarizeOutput 列表,或者错误。

调用关系:它站在 summarize 的上一层,给业务代码提供更安全、更省事的用法。测试 summarize_input_posts_expected_payload_and_parses_output 通过它触发完整流程,确认输入会被编码成正确请求体,并且响应会被正确读回。

调用图:调用 1 个内部函数(summarize);外部调用 1 个(to_value)。

tests::DummyTransport::execute92–94 ↗
async fn execute(&self, _req: Request) -> Result<Response, TransportError>

作用:这是测试用的假网络发送函数,目的不是发送请求,而是防止某些测试意外真的走到网络执行。只要被调用,它就报错提醒测试写错了。

数据流:进去的是一个 Request,但函数故意不使用它。它直接生成 TransportError::Build,错误信息是 execute should not run。出来的是失败结果,不会产生响应,也不会改动外部状态。

调用关系:它属于 DummyTransport 这个测试替身。path_is_memories_trace_summarize_for_wire_compatibility 只检查路径,不应该发请求;如果流程误触发 execute,这个函数会立刻暴露问题。

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

tests::DummyTransport::stream96–98 ↗
async fn stream(&self, _req: Request) -> Result<StreamResponse, TransportError>

作用:这是测试用的假流式请求函数。这里的记忆总结测试不需要流式响应,所以一旦调用它,就说明测试流程跑偏了。

数据流:进去的是一个 Request,但函数不读取。它直接返回 TransportError::Build,说明 stream should not run。出来的是失败结果,没有 StreamResponse。

调用关系:它和 DummyTransport::execute 一样,是测试里的安全护栏。当前文件的记忆总结接口走普通 execute,不应该走 stream。

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

tests::DummyAuth::add_auth_headers105–105 ↗
fn add_auth_headers(&self, _headers: &mut HeaderMap)

作用:这是测试用的假认证器。它实现认证接口,但什么认证头都不加,让测试能专注检查请求路径和正文。

数据流:进去的是一个可修改的 HeaderMap,也就是 HTTP 头集合。函数不往里面写任何东西。出来时头集合保持原样。

调用关系:它在测试创建 MemoriesClient 时被传进去,满足客户端需要认证提供者的要求。因为测试不关心真实登录,所以它故意保持空操作。

tests::CapturingTransport::new115–120 ↗
fn new(response_body: Vec<u8>) -> Self

作用:创建一个会“抓包”的测试传输器。它不会真的上网,而是记录最后一次收到的请求,并返回预先准备好的响应内容。

数据流:进去的是 response_body,也就是测试希望服务器返回的字节内容。函数创建一个共享的 last_request 存储位置,并把响应内容放进可共享的容器。出来的是 CapturingTransport,后续 execute 被调用时会记录请求并返回这份响应。

调用关系:summarize_input_posts_expected_payload_and_parses_output 用它搭建假服务器。这样测试既能检查客户端发出了什么请求,又能模拟服务器返回 output。

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

tests::CapturingTransport::execute124–131 ↗
async fn execute(&self, req: Request) -> Result<Response, TransportError>

作用:这是测试里的假 HTTP 发送动作。它把客户端发来的请求保存下来,然后装作服务器成功返回。

数据流:进去的是 Request。函数先把这个请求写进 last_request,方便测试稍后检查方法、地址和 JSON 正文;然后返回一个状态码 OK、空响应头、body 为预设内容的 Response。出来的是成功响应,同时内部记录了最后一次请求。

调用关系:它在 summarize_input 测试中被 EndpointSession 间接调用。客户端以为自己在发真实请求,但其实请求被这里截获,测试随后读取 last_request 来确认客户端行为正确。

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

tests::CapturingTransport::stream133–135 ↗
async fn stream(&self, _req: Request) -> Result<StreamResponse, TransportError>

作用:这是测试抓包传输器的流式请求占位函数。记忆总结接口不应该用流式请求,所以它被调用时会报错。

数据流:进去的是 Request,但函数不使用。它直接返回 TransportError::Build,提示 stream should not run。出来的是失败结果,不会记录请求,也不会返回流。

调用关系:它和 CapturingTransport::execute 形成对照:普通请求会被记录并成功返回,流式请求则明确禁止。这样测试能发现客户端是否走错了发送方式。

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

tests::provider138–153 ↗
fn provider(base_url: &str) -> Provider

作用:快速生成一个测试用的服务商配置。这样每个测试不用重复写基础地址、重试规则和超时时间。

数据流:进去的是 base_url 字符串。函数把它放进 Provider,并填入测试名称、空查询参数、空请求头、很短的重试延迟和流空闲超时。出来的是一个完整 Provider,可直接交给 MemoriesClient::new。

调用关系:summarize_input_posts_expected_payload_and_parses_output 调用它来指定假服务器基础地址。随后客户端会把这个基础地址和 MemoriesClient::path 拼成完整请求 URL。

调用图:外部调用 3 个(from_millis, from_secs, new)。

tests::path_is_memories_trace_summarize_for_wire_compatibility156–161 ↗
fn path_is_memories_trace_summarize_for_wire_compatibility()

作用:确认接口路径还是服务端约定的 memories/trace_summarize。这个测试防止有人无意中改了字符串,导致线上请求打到错误地址。

数据流:进去没有业务输入。测试调用 MemoriesClient::<DummyTransport>::path,拿到路径字符串,再和期望值比较。出来的是测试通过或失败;它不发送网络请求,也不改状态。

调用关系:它直接保护 MemoriesClient::path 这个关键常量。因为路径是客户端和服务端之间的“门牌号”,这个测试保证门牌号不会悄悄变掉。

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

tests::summarize_input_posts_expected_payload_and_parses_output164–224 ↗
async fn summarize_input_posts_expected_payload_and_parses_output()

作用:验证 summarize_input 的完整行为:它应该发 POST 请求到正确 URL,正文应包含输入里的模型和记忆信息,并且能读出服务器返回的总结。

数据流:进去的是测试里手工构造的假响应和 MemorySummarizeInput。测试创建 CapturingTransport、Provider 和 MemoriesClient,调用 summarize_input;假传输器记录请求并返回预设 JSON。出来时,测试检查返回列表长度和总结文本,也检查记录下来的请求方法、URL、model、trace id 和 source_path 都正确。

调用关系:这是本文件最完整的端到端式单元测试。它会用 MemoriesClient::new 创建客户端,用 tests::provider 提供配置,用 CapturingTransport::execute 截获请求,并间接覆盖 summarize_input 与 summarize 的配合。

调用图:调用 1 个内部函数(new);外部调用 8 个(new, new, assert_eq!, new, provider, json!, to_vec, vec!)。

codex-api/src/endpoint/images.rs源码 ↗
io_transportrequest handling

这个文件像一个“图片接口办事窗口”。外部想让模型画图,或在已有图片上改图,就把结构化的请求交给 ImagesClient。它会把请求变成 JSON(一种常见的数据文本格式),通过 EndpointSession 统一加上服务地址、认证信息和额外请求头,再用 HTTP POST 发到对应路径。收到服务器回来的字节后,它再把内容解析成 ImageResponse,也就是代码里能直接使用的图片结果。这里还把“生成图片”和“编辑图片”的共同步骤抽到了 post_image_request,避免两边各写一套几乎一样的发请求代码。文件后半部分是测试:用假的认证和假的网络传输,把真实网络替换掉,检查发出去的网址和请求体是否正确,也检查返回内容缺少必要图片数据时会报错。

函数细节16
ImagesClient::new21–25 ↗
fn new(transport: T, provider: Provider, auth: SharedAuthProvider) -> Self

作用:创建一个图片接口客户端。调用者给它网络传输工具、服务提供方配置和认证工具,它就准备好以后可以发图片请求。

数据流:进去的是 transport、provider 和 auth:transport 负责实际发网络请求,provider 说明服务地址和重试等配置,auth 负责加认证头。函数把这些交给 EndpointSession::new,包成一个 session。出来的是 ImagesClient,里面保存了这个会话,之后 generate 和 edit 都靠它发请求。

调用关系:这是使用图片接口前的第一步。测试里的 generate_posts_typed_request_and_parses_image_response、edit_posts_typed_request_and_parses_image_response、image_response_requires_image_data 都先调用它造客户端;项目里其他创建图片客户端的代码也会用到它。它把底层准备工作交给 EndpointSession::new。

调用图:调用 1 个内部函数(new);被 4 处调用(edit_posts_typed_request_and_parses_image_response, generate_posts_typed_request_and_parses_image_response, image_response_requires_image_data, client)。

ImagesClient::with_telemetry27–31 ↗
fn with_telemetry(self, request: Option<Arc<dyn RequestTelemetry>>) -> Self

作用:给图片客户端接上请求遥测工具。遥测可以理解成“记录这次请求发生了什么”的观察器,比如统计耗时或记录调试信息。

数据流:进去的是一个已有的 ImagesClient 和可选的 RequestTelemetry。函数不会改旧对象,而是把里面的 session 换成带遥测的新 session。出来的是新的 ImagesClient,之后发请求时会带上这套观察能力。

调用关系:它通常在客户端创建后、真正发请求前使用。它不直接发网络请求,而是把设置遥测这件事交给 session 的 with_request_telemetry。

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

ImagesClient::generate33–45 ↗
async fn generate(
        &self,
        request: &ImageGenerationRequest,
        extra_headers: HeaderMap,
    ) -> Result<ImageResponse, ApiError>

作用:发起“根据文字生成图片”的请求。调用者只需要给图片生成参数和额外请求头,就能拿到统一的图片响应。

数据流:进去的是 ImageGenerationRequest,例如提示词、模型、尺寸、质量等,还有额外的 HTTP 请求头。函数把目标路径固定为 images/generations,把操作名标成 image generation,然后交给 post_image_request。出来的是 ImageResponse,或者如果发送、编码、解析失败,就出来 ApiError。

调用关系:这是外部代码调用图片生成功能的主入口。它自己不重复写发请求细节,而是把通用流程交给 ImagesClient::post_image_request。对应测试会确认它确实把请求发到 images/generations,并能解析图片响应。

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

ImagesClient::edit47–54 ↗
async fn edit(
        &self,
        request: &ImageEditRequest,
        extra_headers: HeaderMap,
    ) -> Result<ImageResponse, ApiError>

作用:发起“编辑已有图片”的请求。比如给一张图片加帽子、换背景,调用者把图片和修改要求传进来即可。

数据流:进去的是 ImageEditRequest,包括原图地址或图片数据、提示词、模型和可选参数,还有额外请求头。函数把路径固定为 images/edits,把操作名标成 image edit,再交给 post_image_request。出来的是 ImageResponse,或者失败时返回 ApiError。

调用关系:这是外部代码调用图片编辑功能的主入口。它和 generate 共用同一个底层发送函数,只是接口路径和错误提示里的操作名不同。测试会检查它发到 images/edits,并且请求体只包含应该出现的字段。

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

ImagesClient::post_image_request56–71 ↗
async fn post_image_request(
        &self,
        path: &str,
        request: &R,
        extra_headers: HeaderMap,
        operation: &str,
    ) -> Result<ImageResponse, ApiError>

作用:这是图片请求的通用发送器。生成图片和编辑图片都会走这里,避免两套代码各自处理 JSON、HTTP 和返回解析。

数据流:进去的是接口路径、可序列化的请求对象、额外请求头和操作名称。它先用 serde_json::to_value 把请求对象转成 JSON 值;如果转不了,就返回带清楚说明的 ApiError。然后它让 session.execute 用 POST 方法发请求。拿到响应后,再用 serde_json::from_slice 把响应字节解析成 ImageResponse;如果服务器返回的格式不对,比如缺少 data 字段,也会变成 ApiError。最终出来的是图片响应,或错误。

调用关系:generate 和 edit 都把实际工作交给它。它站在 ImagesClient 和 EndpointSession 之间:上面接收图片业务请求,下面调用 session.execute 真正走网络,并用 serde_json 负责进出 JSON 的转换。

调用图:调用 1 个内部函数(execute);被 2 处调用(edit, generate);外部调用 2 个(from_slice, to_value)。

tests::DummyAuth::add_auth_headers98–98 ↗
fn add_auth_headers(&self, _headers: &mut HeaderMap)

作用:这是测试用的假认证工具。它故意什么认证头都不加,让测试可以专心检查图片请求本身。

数据流:进去的是一组 HTTP 请求头。函数不读取认证信息,也不修改这些请求头。出来时请求头保持原样。

调用关系:测试创建 ImagesClient 时需要一个认证提供者,所以用 DummyAuth 填位。它避免测试依赖真实登录、真实密钥或外部环境。

tests::CapturingTransport::new108–113 ↗
fn new(response_body: Vec<u8>) -> Self

作用:创建一个测试用的假网络传输器。它不会真的访问互联网,而是记住发来的请求,并返回预先准备好的响应内容。

数据流:进去的是 response_body,也就是测试希望服务器“假装返回”的字节内容。函数建立一个共享的 last_request 存放槽,用互斥锁(一把锁,防止多个任务同时改同一份数据)保护;同时保存响应内容。出来的是 CapturingTransport。

调用关系:各个测试在创建客户端前会先调用它。之后 ImagesClient 发请求时,EndpointSession 最终会调用这个 transport 的 execute,测试再通过 captured_request 取出刚才捕获的请求来检查。

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

tests::CapturingTransport::execute117–124 ↗
async fn execute(&self, req: Request) -> Result<Response, TransportError>

作用:模拟一次普通 HTTP 请求。它的作用不是联网,而是把请求拍照留档,然后交回一份假的成功响应。

数据流:进去的是 Request,里面有网址、方法、请求体等。函数把这个 Request 存到 last_request 里,然后构造一个状态码为 200 OK、空响应头、正文为预设 response_body 的 Response。出来的是成功的 Response。

调用关系:当被测的 ImagesClient 通过 EndpointSession 发请求时,最终会走到这里。它让测试能够检查“客户端到底发了什么”,同时避免真实网络的不稳定。

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

tests::CapturingTransport::stream126–128 ↗
async fn stream(&self, _req: Request) -> Result<StreamResponse, TransportError>

作用:模拟流式请求入口,但这个图片接口测试不应该用到流式请求。流式请求可以理解成服务器一边生成一边连续吐数据。

数据流:进去的是 Request,但函数不使用它。它直接返回一个 TransportError::Build,错误文字说明“stream should not run”。出来的是失败结果。

调用关系:CapturingTransport 必须实现 HttpTransport,所以要提供 stream 方法。这里故意让它报错,是为了如果代码意外走了流式通道,测试能立刻暴露问题。

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

tests::provider131–146 ↗
fn provider() -> Provider

作用:生成一份测试专用的服务提供方配置。它告诉客户端基础网址、重试规则和超时时间。

数据流:进去没有参数。函数写死 provider 名称、base_url、空请求头和很短的重试/超时配置。出来的是 Provider,供测试创建 ImagesClient 使用。

调用关系:多个测试都会调用它,保证客户端拼出来的网址有稳定预期。ImagesClient::new 会把这份 Provider 交给 EndpointSession。

调用图:外部调用 3 个(from_millis, from_secs, new)。

tests::response_body148–171 ↗
fn response_body() -> Vec<u8>

作用:造一份假的图片接口成功响应。测试用它来模拟服务器正常返回图片数据。

数据流:进去没有参数。函数用 json! 组出一段包含 created、background、data、quality、size、usage 等字段的 JSON,然后转成字节数组。出来的是 Vec<u8>,也就是假服务器响应正文。

调用关系:生成图片和编辑图片的成功测试都会把它传给 CapturingTransport::new。之后 post_image_request 会把这份字节解析成 ImageResponse。

调用图:外部调用 2 个(json!, to_vec)。

tests::expected_response173–183 ↗
fn expected_response() -> ImageResponse

作用:写出测试期望得到的图片响应对象。它是“正确答案”,用来和客户端解析出来的结果做比较。

数据流:进去没有参数。函数手工构造 ImageResponse,包括创建时间、背景、图片数据、质量和尺寸。出来的是这个 ImageResponse。

调用关系:成功测试会把 ImagesClient 返回的响应和它做 assert_eq 比较。这样能确认 JSON 响应不只是请求成功,而且被正确翻译成代码里的结构。

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

tests::captured_request185–192 ↗
fn captured_request(transport: &CapturingTransport) -> Request

作用:从假网络传输器里取出刚刚被捕获的请求。测试靠它查看客户端实际发出的网址和请求体。

数据流:进去的是 CapturingTransport。函数拿到 last_request 的锁,复制里面保存的 Request;如果没有请求被保存,就让测试失败。出来的是捕获到的 Request。

调用关系:成功测试在调用 generate 或 edit 之后使用它。它和 CapturingTransport::execute 配合:execute 负责存,captured_request 负责取。

tests::generate_posts_typed_request_and_parses_image_response195–231 ↗
async fn generate_posts_typed_request_and_parses_image_response()

作用:测试“生成图片”这条路是否正确。它同时检查请求是否发到正确地址、请求体是否符合预期、响应是否能正确解析。

数据流:进去没有外部参数。测试先准备假响应、假网络、Provider 和 ImagesClient;然后调用 generate,传入提示词、模型、背景、质量和尺寸。之后它拿返回值和 expected_response 比较,再取出 captured_request 检查 URL 和 JSON 请求体。测试通过表示生成图片接口包装是可信的。

调用关系:这个测试覆盖 ImagesClient::new、ImagesClient::generate、ImagesClient::post_image_request,以及 CapturingTransport 的捕获能力。它用 provider、response_body 和 captured_request 这些测试辅助函数把整条流程串起来。

调用图:调用 1 个内部函数(new);外部调用 7 个(new, new, assert_eq!, new, captured_request, provider, response_body)。

tests::edit_posts_typed_request_and_parses_image_response234–268 ↗
async fn edit_posts_typed_request_and_parses_image_response()

作用:测试“编辑图片”这条路是否正确。它确认客户端会把原图和修改提示发到编辑接口,并能读懂服务器返回的图片结果。

数据流:进去没有外部参数。测试准备假响应和客户端,然后调用 edit,传入一张 data URL 形式的图片和提示词。调用完成后,它比较返回的 ImageResponse,再检查捕获到的 URL 是 images/edits,请求体里包含 images、prompt、model,且没有把空的可选字段乱发出去。出来的结果是测试通过或失败。

调用关系:这个测试覆盖 ImagesClient::edit 和共用的 post_image_request。它和生成图片测试很像,但走的是另一条路径,确保两个公开方法没有混淆接口地址或请求格式。

调用图:调用 1 个内部函数(new);外部调用 8 个(new, new, assert_eq!, new, captured_request, provider, response_body, vec!)。

tests::image_response_requires_image_data271–299 ↗
async fn image_response_requires_image_data()

作用:测试服务器响应缺少图片数据时会不会报错。它确保客户端不会把不完整的返回当成成功结果。

数据流:进去没有外部参数。测试构造一个只有 created、没有 data 的假响应,然后调用 generate。它期待这次调用失败,并检查错误是 ApiError::Stream,错误文字说明解析图片生成响应时缺少 data 字段。出来的结果是测试通过或失败。

调用关系:这个测试重点压 post_image_request 的响应解析环节。它说明 ImageResponse 里的 data 是必需的;如果服务器返回格式不符合要求,客户端会在解析时拦住,而不是把坏数据继续传给上层代码。

调用图:调用 1 个内部函数(new);外部调用 8 个(new, new, assert!, new, provider, json!, panic!, to_vec)。

codex-api/src/endpoint/search.rs源码 ↗
io_transportrequest handling

这个文件解决的是“怎么安全、统一地调用搜索接口”的问题。搜索请求里可能有用户输入、图片、要搜索的关键词、允许访问的网站、地理位置等很多字段,如果每个地方都手写网络请求,很容易漏字段、拼错路径,或者解析返回值出错。这里的 SearchClient 像一个专门跑腿的前台:它保存一套 EndpointSession,里面知道服务商地址、鉴权信息和真正发网络请求的工具。调用 search 时,它先把 SearchRequest 转成 JSON(一种常见的数据交换格式),再用 POST 方法发到 alpha/search,最后把服务器返回的字节内容解析成 SearchResponse。文件后半部分是测试用的小工具:假鉴权器不加任何登录信息,假传输器不真的联网,只记录发出去的请求,并返回预设结果。这样测试就能确认客户端确实发了正确的内容,也能正确读回响应。

函数细节10
SearchClient::new19–23 ↗
fn new(transport: T, provider: Provider, auth: SharedAuthProvider) -> Self

作用:创建一个新的搜索客户端。调用方把“怎么发请求”的传输器、服务商配置和鉴权提供者交进来,它就组装好后续发搜索请求所需的会话。

数据流:进去的是 transport、provider 和 auth:transport 负责实际发送 HTTP 请求,provider 说明服务地址和重试规则,auth 负责加认证信息。函数把它们交给 EndpointSession::new,得到一个会话对象,再包进 SearchClient 里返回。它不立刻发请求,只是把工具准备好。

调用关系:它是使用搜索接口前的准备步骤。测试里的 tests::search_posts_typed_request_and_parses_output 会用它创建客户端,实际业务里的 handle_call 也会用它拿到可调用的 SearchClient。真正的请求之后由 SearchClient::search 发出去。

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

SearchClient::with_telemetry25–29 ↗
fn with_telemetry(self, request: Option<Arc<dyn RequestTelemetry>>) -> Self

作用:给搜索客户端加上可选的请求遥测。遥测可以理解成“记录这次请求发生了什么”的观察器,方便统计、调试或追踪问题。

数据流:进去的是已有的 SearchClient 和一个可选的 RequestTelemetry。函数把这个遥测对象交给内部 EndpointSession 的 with_request_telemetry,然后返回一个新的 SearchClient。原来的会话配置基本保留,只是多了或换了请求记录能力。

调用关系:它通常在客户端创建之后、真正调用 SearchClient::search 之前使用。它不自己联网,而是把后续请求中需要的观察功能交给 EndpointSession,让执行请求时能够顺手记录。

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

SearchClient::path31–33 ↗
fn path() -> &'static str

作用:返回搜索接口固定使用的路径,也就是 alpha/search。把路径集中写在这里,可以避免到处手写字符串导致拼错。

数据流:它不需要输入,也不读取外部状态。调用后直接给出固定字符串 alpha/search。它不会改动任何东西。

调用关系:SearchClient::search 在发请求前会调用它,拿到要访问的接口路径。这样 search 函数只关心“发搜索请求”,不用把路径散落在代码里。

tests::DummyAuth::add_auth_headers84–84 ↗
fn add_auth_headers(&self, _headers: &mut HeaderMap)

作用:测试用的假鉴权函数。它故意什么都不做,让测试可以专心检查搜索请求内容,而不被真实登录信息干扰。

数据流:进去的是一份可修改的 HTTP 头集合。函数不添加、不删除、不修改任何头,直接返回空结果。调用前后头信息保持一样。

调用关系:它实现了 AuthProvider 接口,好让 SearchClient::new 在测试里也能拿到一个“看起来像真的”鉴权提供者。真正发测试请求时,EndpointSession 可能会让它加认证头,但它会保持空白。

tests::CapturingTransport::new94–99 ↗
fn new(response_body: Vec<u8>) -> Self

作用:创建一个测试用的假网络传输器。它不会真的访问互联网,而是保存下一次要返回的响应,并准备记录发出去的请求。

数据流:进去的是一段 response_body,也就是测试希望服务器“假装返回”的内容。函数创建一个共享的 last_request 存储位置,用互斥锁(一把锁,防止两个任务同时改同一份数据)保护;同时把响应内容放进共享容器。最后返回 CapturingTransport。

调用关系:测试 tests::search_posts_typed_request_and_parses_output 先调用它准备假传输器,再把它交给 SearchClient::new。后面 SearchClient::search 发请求时,会进入 tests::CapturingTransport::execute,而不是走真实网络。

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

tests::CapturingTransport::execute103–110 ↗
async fn execute(&self, req: Request) -> Result<Response, TransportError>

作用:测试用的“假发送请求”函数。它的作用是把客户端想发的请求抓下来,并返回一份预先准备好的成功响应。

数据流:进去的是一个 Request。函数先把这个请求存进 last_request,方便测试稍后检查请求方法、路径和 JSON 内容。然后它构造一个状态码为 OK 的 Response,头为空,body 使用创建 CapturingTransport 时传入的响应内容。最后把这个响应返回给调用方。

调用关系:SearchClient::search 最终会通过 EndpointSession::execute 间接调用这里。它假装完成了网络请求,让测试不用联网也能验证 SearchClient::search 的请求编码和响应解析是否正确。

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

tests::CapturingTransport::stream112–114 ↗
async fn stream(&self, _req: Request) -> Result<StreamResponse, TransportError>

作用:测试用的流式请求入口,但这个搜索测试不应该走流式请求。只要有人误调用它,它就返回错误,提醒测试写错或代码走错路了。

数据流:进去的是一个 Request,但函数不使用它。它直接构造 TransportError::Build,错误信息是“stream should not run”,然后返回失败结果。没有请求被记录,也没有响应产生。

调用关系:它实现 HttpTransport 接口所必需的 stream 方法。当前测试期望 SearchClient::search 使用普通 execute 请求,所以如果流程意外交给 stream,测试会立刻失败,帮助定位问题。

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

tests::provider117–132 ↗
fn provider() -> Provider

作用:生成一份测试用的服务商配置。它告诉客户端基础地址、重试规则和超时时间,但这些值主要用于让 SearchClient 能完整初始化。

数据流:函数没有输入。它创建一个 Provider:名字是 test,基础地址是 https://example.com/v1,没有额外查询参数,头为空;重试配置设为最多尝试一次,并设置很短的延迟和超时。最后返回这份配置。

调用关系:tests::search_posts_typed_request_and_parses_output 会调用它,然后把结果传给 SearchClient::new。它让测试里的客户端像真实客户端一样有服务地址和策略,但不会触发真实外部服务。

调用图:外部调用 3 个(from_millis, from_secs, new)。

tests::search_posts_typed_request_and_parses_output135–266 ↗
async fn search_posts_typed_request_and_parses_output()

作用:验证搜索客户端真的能把复杂的 SearchRequest 发成正确 JSON,并能把服务器返回的 JSON 读成 SearchResponse。这是防止接口字段拼错、漏传或解析坏掉的安全网。

数据流:测试先准备一段假服务器响应,创建 CapturingTransport,再用 provider 和 DummyAuth 创建 SearchClient。接着它构造一个包含文字、图片、搜索词、打开网页命令、位置、过滤条件、图片设置和 token 限制的 SearchRequest,调用 client.search。返回后,它先检查解析出的 SearchResponse 是否符合预期;再从 CapturingTransport 里取出刚才捕获的请求体,确认 JSON 结构和值都和预期完全一致。

调用关系:这是文件内主要行为的验收测试。它会用 tests::CapturingTransport::new、tests::provider 和 SearchClient::new 搭好环境,再触发 SearchClient::search;SearchClient::search 又会间接调用 tests::CapturingTransport::execute。最后它用断言来证明整个“请求编码 → 发送 → 响应解析”的流程没有走偏。

调用图:调用 1 个内部函数(new);外部调用 10 个(new, default, new, assert_eq!, new, provider, Items, json!, to_vec, vec!)。

codex-api/src/endpoint/realtime_call.rs源码 ↗
io_transportrequest handling

实时通话不是只发一句普通请求就完事。客户端先要把 SDP 发给服务器,让双方知道怎么建立 WebRTC 连接;同时,有些场景还要把模型、声音、输出方式等会话配置一起带过去。这个文件就是这一步的“电话拨号器”。它封装了 RealtimeCallClient,负责拼好 POST 请求、加认证、选择正确的请求格式,然后解析服务器返回的 SDP 和 Location 头里的 call_id。这里还有一个细节:普通 API 走 multipart/form-data(像一个表单里放两份文件:sdp 和 session),而 backend-api 走 JSON,因为后端接口暂时长得不一样。文件末尾的测试用假 transport 把请求抓下来,确认请求体、请求头、URL 和错误情况都符合预期,避免实时通话一开始就接错线。

函数细节31
RealtimeCallClient::new50–54 ↗
fn new(transport: T, provider: Provider, auth: SharedAuthProvider) -> Self

作用:创建一个实时通话客户端。调用者把“怎么发 HTTP 请求”的 transport、服务商地址配置和认证方式交进来,它就组装成后面能发请求的对象。

数据流:输入 transport、provider 和 auth → 用它们创建一个 EndpointSession(可以理解成带地址、认证和重试能力的请求会话)→ 输出 RealtimeCallClient,里面保存这个会话,之后所有创建通话的请求都靠它发。

调用关系:测试里每个场景都会先用它造客户端,然后再调用 create 或 create_with_session。它内部把实际初始化工作交给 EndpointSession::new。

调用图:调用 1 个内部函数(new);被 6 处调用(errors_when_location_is_missing, extracts_call_id_from_forwarded_backend_location, sends_api_session_call_as_multipart_body, sends_avas_session_call_query_params, sends_backend_session_call_as_json_body, sends_sdp_offer_as_raw_body)。

RealtimeCallClient::with_telemetry56–60 ↗
fn with_telemetry(self, request: Option<Arc<dyn RequestTelemetry>>) -> Self

作用:给客户端加上请求遥测信息。遥测就是记录请求耗时、状态等观察数据,方便排查问题。

数据流:输入已有客户端和一个可选的 telemetry 对象 → 把 telemetry 装到内部的 EndpointSession 上 → 输出一个新的 RealtimeCallClient,功能一样但会带上这些观察记录。

调用关系:它是创建客户端后的可选增强步骤。它不直接发请求,只把设置转交给内部的 with_request_telemetry。

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

RealtimeCallClient::path62–64 ↗
fn path() -> &'static str

作用:返回创建实时通话要访问的固定路径。这样路径只写在一个地方,避免到处手敲写错。

数据流:不需要输入 → 返回字符串 realtime/calls → 后面的请求函数把它和 provider 的 base_url 拼成完整 URL。

调用关系:create_with_headers 和 create_with_session_architecture_and_headers 发请求时会用它确定 API 路径。

RealtimeCallClient::uses_backend_request_shape66–68 ↗
fn uses_backend_request_shape(&self) -> bool

作用:判断当前服务地址是不是 backend-api。如果是,请求体格式要换成 JSON,而不是普通 API 的 multipart 表单。

数据流:读取客户端内部 session 的 provider.base_url → 检查里面是否包含 /backend-api → 输出 true 或 false,告诉后续代码该走哪种请求格式。

调用关系:create_with_session_architecture_and_headers 会在带会话创建通话时调用它,用来在两种接口形状之间分岔。

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

RealtimeCallClient::create79–81 ↗
async fn create(&self, sdp: String) -> Result<RealtimeCallResponse, ApiError>

作用:用最简单的方式创建实时通话,只发送 SDP,不带额外会话配置或额外请求头。

数据流:输入本地 SDP 字符串 → 准备一个空的 HeaderMap → 调用 create_with_headers 发送请求 → 输出服务器返回的 SDP 和 call_id,或者错误。

调用关系:这是外部最常用的快捷入口。它把真正的 HTTP 请求工作交给 create_with_headers。

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

RealtimeCallClient::create_with_session83–90 ↗
async fn create_with_session(
        &self,
        sdp: String,
        session_config: RealtimeSessionConfig,
    ) -> Result<RealtimeCallResponse, ApiError>

作用:创建实时通话时,同时把初始会话配置一起发给服务器。这样 WebRTC 连接一起来,服务器就已经知道模型、语音等设置。

数据流:输入 SDP 和 RealtimeSessionConfig → 准备空请求头 → 调用 create_with_session_and_headers → 输出 RealtimeCallResponse 或错误。

调用关系:这是带会话配置的快捷入口。它把细节交给 create_with_session_and_headers。

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

RealtimeCallClient::create_with_headers92–116 ↗
async fn create_with_headers(
        &self,
        sdp: String,
        extra_headers: HeaderMap,
    ) -> Result<RealtimeCallResponse, ApiError>

作用:创建只带 SDP 的实时通话,并允许调用者额外加 HTTP 头。适合需要特殊头信息但不需要会话配置的场景。

数据流:输入 SDP 和额外 headers → 发 POST 到 realtime/calls,把 Content-Type 设为 application/sdp,并把 SDP 原样作为请求体 → 收到响应后,把响应 body 解成服务器 SDP,再从 Location 头里取 call_id → 输出 RealtimeCallResponse。

调用关系:create 会调用它。它负责真正发请求,并把响应解析工作交给 decode_sdp_response 和 decode_call_id_from_location。

调用图:调用 3 个内部函数(decode_call_id_from_location, decode_sdp_response, execute_with);被 1 处调用(create);外部调用 1 个(path)。

RealtimeCallClient::create_with_session_and_headers118–131 ↗
async fn create_with_session_and_headers(
        &self,
        sdp: String,
        session_config: RealtimeSessionConfig,
        extra_headers: HeaderMap,
    ) -> Result<RealtimeCallResponse, Api

作用:创建带会话配置的实时通话,并允许额外请求头。默认使用 RealtimeApi 这种会话架构。

数据流:输入 SDP、会话配置和 headers → 补上默认架构 RealtimeApi → 调用 create_with_session_architecture_and_headers → 输出创建结果或错误。

调用关系:create_with_session 会调用它。它自己不拼请求,只负责给更底层函数补一个默认架构参数。

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

RealtimeCallClient::create_with_session_architecture_and_headers133–208 ↗
async fn create_with_session_architecture_and_headers(
        &self,
        sdp: String,
        session_config: RealtimeSessionConfig,
        architecture: RealtimeConversationArchitecture,

作用:这是带会话创建通话的核心函数。它决定请求体到底用 JSON 还是 multipart,并在需要时给 URL 加上特定架构参数。

数据流:输入 SDP、会话配置、会话架构和额外 headers → 先把会话配置转成 JSON,并去掉 id 字段 → 如果是 backend-api,就组成 JSON 请求体;否则就手工拼 multipart 表单,里面一段是 SDP,一段是 session JSON → 发 POST 请求 → 把响应 body 解成 SDP,并从 Location 头解析 call_id → 输出 RealtimeCallResponse。

调用关系:create_with_session_and_headers 会调用它。它会用 realtime_session_json 转会话配置,用 uses_backend_request_shape 判断接口形状,用 configure_realtime_call_request 调整 URL,最后用 decode_sdp_response 和 decode_call_id_from_location 解析结果。

调用图:调用 5 个内部函数(uses_backend_request_shape, decode_call_id_from_location, decode_sdp_response, realtime_session_json, execute_with);被 1 处调用(create_with_session_and_headers);外部调用 6 个(path, new, format!, to_string, to_value, trace!)。

configure_realtime_call_request211–222 ↗
fn configure_realtime_call_request(
    request: &mut Request,
    architecture: RealtimeConversationArchitecture,
)

作用:按实时会话架构调整即将发出的请求。普通 RealtimeApi 不改;Avas 架构需要在 URL 上加两个查询参数。

数据流:输入可修改的 Request 和 architecture → 如果是 Avas,就往 request.url 后面加 intent=quicksilver 和 architecture=avas;如果是 RealtimeApi,就什么也不做 → 请求对象被原地改好。

调用关系:create_with_session_architecture_and_headers 在发带会话请求前调用它。它把具体加参数的动作交给 append_query_pair。

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

append_query_pair224–233 ↗
fn append_query_pair(url: &mut String, key: &str, value: &str)

作用:往 URL 后面追加一个 key=value 查询参数。它会自动判断该用 ? 开头还是用 & 继续追加。

数据流:输入可修改的 URL 字符串、参数名和参数值 → 检查 URL 里有没有 ? → 拼上 ?key=value 或 &key=value → URL 字符串被原地更新。

调用关系:configure_realtime_call_request 用它给 Avas 请求添加参数。它是一个很小的字符串拼接工具。

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

realtime_session_json235–238 ↗
fn realtime_session_json(session_config: RealtimeSessionConfig) -> Result<Value, ApiError>

作用:把实时会话配置转成可以放进请求里的 JSON。JSON 可以理解成前后端都容易读写的一种结构化文本格式。

数据流:输入 RealtimeSessionConfig → 调用 session_update_session_json 生成 session.update 里使用的 session JSON → 如果转换失败,就包装成 ApiError → 输出 serde_json::Value 或错误。

调用关系:create_with_session_architecture_and_headers 发带会话请求时会调用它。测试也调用它来算出期望的请求体内容。

调用图:被 3 处调用(create_with_session_architecture_and_headers, sends_api_session_call_as_multipart_body, sends_backend_session_call_as_json_body);外部调用 1 个(session_update_session_json)。

decode_sdp_response240–246 ↗
fn decode_sdp_response(body: &[u8]) -> Result<String, ApiError>

作用:把服务器响应里的原始字节转成 SDP 字符串。因为网络响应先是字节,这里要确认它确实是合法文字。

数据流:输入响应 body 的字节数组 → 按 UTF-8 文本解码 → 成功就输出字符串;失败就返回说明“SDP 响应解码失败”的 ApiError。

调用关系:create_with_headers 和 create_with_session_architecture_and_headers 都用它解析服务器返回的 SDP。

调用图:被 2 处调用(create_with_headers, create_with_session_architecture_and_headers);外部调用 1 个(from_utf8)。

decode_call_id_from_location248–268 ↗
fn decode_call_id_from_location(headers: &HeaderMap) -> Result<String, ApiError>

作用:从响应头 Location 里找出 call_id。这个 id 后面会让旁路 WebSocket 加入同一通实时通话,所以缺了它就无法继续配对。

数据流:输入响应 headers → 读取 Location 头 → 去掉问号后面的查询参数 → 从路径末尾往前找像实时通话 id 的片段 → 找到就输出 call_id;没有 Location、Location 非法或找不到 id 都返回错误。

调用关系:创建通话的两个主流程都会调用它。测试会专门验证缺 Location、没有 id、UUID 格式 id 这些情况。

调用图:被 4 处调用(create_with_headers, create_with_session_architecture_and_headers, accepts_uuid_call_id_from_location, rejects_location_without_call_id);外部调用 2 个(get, trace!)。

is_realtime_call_id_segment270–283 ↗
fn is_realtime_call_id_segment(segment: &str) -> bool

作用:判断 URL 的某一段是不是看起来像实时通话 id。它支持 rtc_ 开头的 id,也支持 UUID 这种带横杠的 36 位格式。

数据流:输入一个路径片段字符串 → 先看是否以 rtc_ 开头且后面还有内容;否则检查是否是标准 UUID 长度,并验证横杠和十六进制字符位置 → 输出 true 或 false。

调用关系:decode_call_id_from_location 用它在 Location 路径里筛出真正的 call_id。

tests::CapturingTransport::new310–312 ↗
fn new() -> Self

作用:创建一个测试用的假 HTTP transport,默认响应里带 /v1/realtime/calls/rtc_test 这个 Location。

数据流:不需要输入 → 调用 with_location 设置默认 Location → 输出 CapturingTransport,它会记录最后一次请求并返回固定响应。

调用关系:多个测试用它代替真实网络,方便检查客户端到底发出了什么请求。

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

tests::CapturingTransport::with_location314–321 ↗
fn with_location(location: &str) -> Self

作用:创建一个测试 transport,并指定服务器响应里的 Location 头。这样测试可以模拟不同的 call_id 地址。

数据流:输入 Location 字符串 → 新建 HeaderMap,把 Location 放进去 → 新建一个可共享的 last_request 存储位置 → 输出 CapturingTransport。

调用关系:tests::CapturingTransport::new 会调用它;测试 extracts_call_id_from_forwarded_backend_location 也直接用它模拟特殊 Location。

调用图:外部调用 4 个(new, new, from_str, new)。

tests::CapturingTransport::without_location323–328 ↗
fn without_location() -> Self

作用:创建一个故意不返回 Location 头的测试 transport。它用来确认客户端遇到缺 call_id 的响应时会报错。

数据流:不需要输入 → 新建空响应头和空的 last_request 记录槽 → 输出 CapturingTransport。

调用关系:errors_when_location_is_missing 测试会使用它,专门验证 decode_call_id_from_location 的错误路径。

调用图:外部调用 3 个(new, new, new)。

tests::CapturingTransport::execute332–339 ↗
async fn execute(&self, req: Request) -> Result<Response, TransportError>

作用:模拟一次 HTTP 请求执行。它不真的联网,只把请求保存下来,然后返回固定的成功响应。

数据流:输入 Request → 把 Request 存进 last_request,供测试稍后检查 → 返回状态 200、预设 headers、body 为 v=0\r\n 的 Response。

调用关系:RealtimeCallClient 发请求时会通过 EndpointSession 间接调用它。测试之后读取 last_request 来断言 URL、头和 body 是否正确。

调用图:外部调用 2 个(from_static, clone)。

tests::CapturingTransport::stream341–343 ↗
async fn stream(&self, _req: Request) -> Result<StreamResponse, TransportError>

作用:模拟流式请求接口,但这个测试 transport 不支持流式请求。只要有人误用它走 stream,就立刻返回错误。

数据流:输入 Request 但不使用 → 返回 TransportError::Build,错误内容说明 stream 不应该运行 → 不产生响应流。

调用关系:这些测试只应该走普通 execute。这个函数像保险丝,防止代码意外走到流式通道还没被发现。

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

tests::DummyAuth::add_auth_headers350–355 ↗
fn add_auth_headers(&self, headers: &mut HeaderMap)

作用:给测试请求加一个假的 Authorization 认证头。这样测试可以确认认证头确实被带进请求。

数据流:输入可修改的 headers → 插入 Authorization: Bearer test-token → headers 被原地更新。

调用关系:RealtimeCallClient 通过 EndpointSession 发请求时会调用认证提供者。sends_sdp_offer_as_raw_body 测试会检查这个头是否存在。

调用图:外部调用 2 个(insert, from_static)。

tests::provider358–373 ↗
fn provider(base_url: &str) -> Provider

作用:快速生成测试用的 Provider 配置。Provider 可以理解成“要连哪个服务、怎么重试、超时多久”的说明书。

数据流:输入 base_url → 填入固定名称、空查询参数、空 headers、很短的重试和超时设置 → 输出 Provider。

调用关系:各个测试用它分别构造普通 API 地址和 backend-api 地址,从而触发不同请求格式。

调用图:外部调用 3 个(from_millis, from_secs, new)。

tests::realtime_session_config375–385 ↗
fn realtime_session_config(session_id: &str) -> RealtimeSessionConfig

作用:生成一份固定风格的实时会话配置,只让测试传入不同 session_id。它包含指令、模型、解析器、模式、输出方式和声音。

数据流:输入 session_id → 组装 RealtimeSessionConfig,其中 instructions 是 hi,模型是 gpt-realtime,输出是音频,声音是 Marin → 输出这份配置。

调用关系:带会话的测试都用它来构造输入,保证测试重点放在请求格式,而不是配置内容本身。

tests::sends_sdp_offer_as_raw_body388–427 ↗
async fn sends_sdp_offer_as_raw_body()

作用:测试最简单的创建通话请求:SDP 应该被原样作为请求体发送,Content-Type 应该是 application/sdp。

数据流:先创建假 transport、provider、DummyAuth 和客户端 → 调用 create 发送 v=offer\r\n → 检查返回的 SDP 和 call_id → 再检查抓到的请求方法、URL、认证头、Content-Type 和 body。

调用关系:它覆盖 RealtimeCallClient::new、create、create_with_headers、decode_sdp_response 和 decode_call_id_from_location 这一条基本链路。

调用图:调用 1 个内部函数(new);外部调用 4 个(new, assert_eq!, new, provider)。

tests::extracts_call_id_from_forwarded_backend_location430–462 ↗
async fn extracts_call_id_from_forwarded_backend_location()

作用:测试 Location 路径比较绕时,客户端仍能从里面找出真正的 rtc_ call_id。

数据流:创建带特殊 Location 的假 transport,base_url 使用 backend-api → 调用 create → 检查响应里的 call_id 是 rtc_backend_test → 检查请求仍然发到 backend-api 下的 realtime/calls。

调用关系:它主要验证 decode_call_id_from_location 对转发后的 Location 也够稳,不会只看固定路径位置。

调用图:调用 1 个内部函数(new);外部调用 4 个(new, assert_eq!, with_location, provider)。

tests::sends_api_session_call_as_multipart_body465–524 ↗
async fn sends_api_session_call_as_multipart_body()

作用:测试普通 API 在带会话创建通话时,会把 SDP 和 session JSON 打包成 multipart 表单。

数据流:创建普通 API 客户端 → 调用 create_with_session → 检查响应 → 读取抓到的请求,确认 Content-Type 是 multipart/form-data,并把 body 转成文本和期望的表单内容逐字比较。

调用关系:它覆盖 create_with_session 到 create_with_session_architecture_and_headers 的普通 API 分支,也用 realtime_session_json 生成期望 session 内容。

调用图:调用 2 个内部函数(new, realtime_session_json);外部调用 8 个(new, assert_eq!, new, provider, realtime_session_config, panic!, to_string, from_utf8)。

tests::sends_avas_session_call_query_params527–559 ↗
async fn sends_avas_session_call_query_params()

作用:测试选择 Avas 架构时,请求 URL 会自动加上 intent=quicksilver 和 architecture=avas。

数据流:创建普通 API 客户端 → 调用 create_with_session_architecture_and_headers,并传入 Avas → 检查响应成功 → 检查抓到的 URL 带上两个查询参数。

调用关系:它重点验证 configure_realtime_call_request 和 append_query_pair 在 Avas 分支里的效果。

调用图:调用 1 个内部函数(new);外部调用 6 个(new, new, assert_eq!, new, provider, realtime_session_config)。

tests::sends_backend_session_call_as_json_body562–608 ↗
async fn sends_backend_session_call_as_json_body()

作用:测试 backend-api 在带会话创建通话时,使用 JSON 请求体,而不是 multipart 表单。

数据流:创建 base_url 含 /backend-api 的客户端 → 调用 create_with_session → 检查响应 → 构造期望的 BackendRealtimeCallRequest JSON → 和实际抓到的 request.body 比较。

调用关系:它覆盖 uses_backend_request_shape 返回 true 后的 backend 分支,并确认 realtime_session_json 生成的 session 会去掉 id 字段后再发送。

调用图:调用 2 个内部函数(new, realtime_session_json);外部调用 5 个(new, assert_eq!, new, provider, realtime_session_config)。

tests::errors_when_location_is_missing611–628 ↗
async fn errors_when_location_is_missing()

作用:测试服务器响应缺少 Location 头时,客户端会明确报错,而不是悄悄返回一个没有 call_id 的结果。

数据流:创建不带 Location 的假 transport → 调用 create → 期望得到错误 → 检查错误文字是 realtime call response missing Location。

调用关系:它验证 decode_call_id_from_location 的缺头错误路径,防止后续 WebSocket 因没有 call_id 才在更远的地方失败。

调用图:调用 1 个内部函数(new);外部调用 4 个(new, assert_eq!, without_location, provider)。

tests::rejects_location_without_call_id631–642 ↗
fn rejects_location_without_call_id()

作用:测试 Location 虽然存在,但里面没有可识别的 call_id 时会失败。

数据流:手工创建 headers,Location 是 /v1/realtime/calls → 调用 decode_call_id_from_location → 期望返回错误 → 检查错误文字说明 Location 不包含 call id。

调用关系:它直接测试 decode_call_id_from_location 和 is_realtime_call_id_segment 的拒绝逻辑。

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

tests::accepts_uuid_call_id_from_location645–655 ↗
fn accepts_uuid_call_id_from_location()

作用:测试 call_id 如果是 UUID 格式,也能被接受。UUID 是一种常见的带横杠长 id。

数据流:手工创建 headers,Location 最后一段是 UUID → 调用 decode_call_id_from_location → 得到这个 UUID 字符串 → 和期望值比较。

调用关系:它直接验证 is_realtime_call_id_segment 不只接受 rtc_ 开头的 id,也接受 UUID 格式的 id。

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

核心传输编排

这些文件在 core 中组装会话范围的模型传输行为,包括客户端设置、重试、预热,以及流式远程压缩流程。

core/src/client.rs源码 ↗
io_transportrequest handling / cross-cutting

没有这个文件,Codex 就像有提示词却没有电话线:不知道该用哪个账号、哪个模型、走 HTTP 还是 WebSocket,也不知道请求失败、登录过期、连接断开时该怎么办。它把“整个会话一直不变的东西”放在 ModelClient 里,比如认证、服务商、线程号和是否禁用 WebSocket;把“这一轮对话才有的东西”放在 ModelClientSession 里,比如本轮的粘性路由令牌和可复用的 WebSocket 连接。它还会给请求补上各种头信息(可以理解成包裹上的标签)、记录遥测数据(运行日志和性能数据)、处理 401 未授权时的 token 刷新,并把服务端持续返回的事件流转换成项目内部统一的 ResponseStream。一个重要点是:WebSocket 失败后,它可以为整个会话切到 HTTP,避免后续请求反复撞同一个坏通道。

函数细节58
RequestRouteTelemetry::for_endpoint203–205 ↗
fn for_endpoint(endpoint: &'static str) -> Self

作用:把一个接口路径包装成遥测用的小对象。后面记录日志时,就能知道这次请求打到了哪个后端接口。

数据流:输入一个固定的 endpoint 字符串 → 存进 RequestRouteTelemetry → 返回带有该接口名的对象,不改动外部状态。

调用关系:压缩、记忆总结、WebSocket 预连接、HTTP 流式请求和 WebSocket 流式请求都会先调用它,给后续 ApiTelemetry 打上“这是哪个接口”的标签。

调用图:被 5 处调用(compact_conversation_history, summarize_memories, preconnect_websocket, stream_responses_api, stream_responses_websocket)。

responses_request_properties_match272–321 ↗
fn responses_request_properties_match(
    previous: &ResponsesApiRequest,
    current: &ResponsesApiRequest,
) -> bool

作用:判断两次 Responses 请求除了输入内容和客户端元数据之外,其他关键设置是不是一样。只有一样,WebSocket 才敢只发送新增内容,避免服务端误解上下文。

数据流:输入上一次请求和当前请求 → 逐项比较模型、指令、工具、推理设置、服务层级等字段 → 返回 true 或 false,不修改请求。

调用关系:ModelClientSession::get_incremental_items 会用它做第一道检查;如果配置不一致,就放弃增量发送,改发完整请求。

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

WebsocketSession::set_connection_reused324–329 ↗
fn set_connection_reused(&self, connection_reused: bool)

作用:记录当前 WebSocket 连接是不是复用来的。这个信息主要用于统计和反馈,帮助判断连接复用是否正常。

数据流:输入 true 或 false → 加锁保护内部标记,防止并发读写冲突 → 更新 connection_reused 字段。

调用关系:预连接、重置连接、获取连接时都会调用它;之后 ModelClientSession::stream_responses_websocket 会读取这个标记并告诉底层 WebSocket 请求。

调用图:被 3 处调用(preconnect_websocket, reset_websocket_session, websocket_connection);外部调用 1 个(lock)。

WebsocketSession::connection_reused331–336 ↗
fn connection_reused(&self) -> bool

作用:读取当前 WebSocket 连接是否被复用。它让发送请求时能把“新连接还是旧连接”这件事传给统计系统。

数据流:没有业务输入 → 加锁读取内部布尔值 → 返回连接复用状态,不改动状态。

调用关系:ModelClientSession::stream_responses_websocket 在真正发 WebSocket 请求前调用它,把复用状态交给底层连接。

调用图:被 1 处调用(stream_responses_websocket);外部调用 1 个(lock)。

sideband_websocket_auth_headers360–364 ↗
fn sideband_websocket_auth_headers(api_auth: &dyn AuthProvider) -> ApiHeaderMap

作用:为实时通话的旁路 WebSocket 准备认证头。简单说,就是把创建通话时用的身份证明复制给后续控制通道。

数据流:输入一个认证提供者 → 新建 header 集合,并让认证提供者往里添加认证信息 → 返回这些 header。

调用关系:ModelClient::create_realtime_call_with_headers 会调用它,确保 HTTP 创建的实时通话和后续 WebSocket 控制连接使用同一套身份。

调用图:被 1 处调用(create_realtime_call_with_headers);外部调用 2 个(new, add_auth_headers)。

ModelClient::new372–408 ↗
fn new(
        auth_manager: Option<Arc<AuthManager>>,
        thread_id: ThreadId,
        provider_info: ModelProviderInfo,
        session_source: SessionSource,
        model_verbosity: Option<Ve

作用:创建一个会话级的模型客户端。它把认证、模型服务商、线程号、功能开关等长期配置收拢起来,供整个 Codex 会话反复使用。

数据流:输入认证管理器、线程号、服务商信息、会话来源、压缩和遥测等设置 → 创建模型服务商对象,收集认证环境信息,初始化 WebSocket 回退状态和缓存 → 返回 ModelClient。

调用关系:这是本文件的基础构造函数;测试和会话创建代码会调用它,后续所有流式请求、压缩、记忆总结、实时通话都依赖这个客户端。

调用图:调用 1 个内部函数(collect_auth_env_telemetry);被 13 处调用(model_client_with_counting_attestation, test_model_client, new, make_session_and_context, make_session_and_context_with_auth_config_home_and_rx, test_model_client_session, responses_respects_model_info_overrides_from_config, responses_stream_includes_subagent_header_on_other, responses_stream_includes_subagent_header_on_review, azure_responses_request_includes_store_and_reasoning_ids (+3 more));外部调用 5 个(new, new, new, create_model_provider, default)。

ModelClient::with_prompt_cache_key_override410–416 ↗
fn with_prompt_cache_key_override(
        mut self,
        prompt_cache_key_override: Option<String>,
    ) -> Self

作用:给客户端临时指定一个提示词缓存键。这样请求可以不用默认线程号当缓存标识。

数据流:输入可选的缓存键字符串 → 写入当前 ModelClient 的 override 字段 → 返回修改后的 ModelClient。

调用关系:它是构造后的微调工具;真正发请求时 ModelClient::prompt_cache_key 会读取这个值。

ModelClient::prompt_cache_key418–422 ↗
fn prompt_cache_key(&self) -> String

作用:决定这次请求用哪个提示词缓存键。缓存键像仓库货架编号,服务端可以用它复用相同上下文的处理结果。

数据流:读取客户端里的 override 和线程号 → 如果有 override 就用它,否则用 thread_id → 返回字符串。

调用关系:ModelClient::build_responses_request 在组装模型请求时调用它,把缓存键放进请求体。

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

ModelClient::new_session428–434 ↗
fn new_session(&self) -> ModelClientSession

作用:为一轮对话创建新的 ModelClientSession。每轮都新建,能避免上一轮的路由令牌误带到下一轮。

数据流:读取当前 ModelClient → 取出缓存的 WebSocketSession,创建新的 turn_state 容器 → 返回本轮专用的 ModelClientSession。

调用关系:上层每次开始模型回合时会调用它;它会使用 ModelClient::take_cached_websocket_session 来复用可能还活着的连接。

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

ModelClient::auth_manager436–438 ↗
fn auth_manager(&self) -> Option<Arc<AuthManager>>

作用:取出当前客户端背后的认证管理器。调用者需要处理登录、刷新 token 或检查认证状态时会用到它。

数据流:读取模型服务商里的 auth_manager → 返回一个可选的共享认证管理器,不修改状态。

调用关系:它是给外部访问认证系统的入口;本文件内部更多时候直接通过 provider 获取认证信息。

ModelClient::take_cached_websocket_session440–447 ↗
fn take_cached_websocket_session(&self) -> WebsocketSession

作用:从会话级缓存里拿走一个 WebSocket 会话状态。这样新的一轮对话可以尝试接着用旧连接。

数据流:读取并锁住 cached_websocket_session → 把里面的 WebsocketSession 取出,同时把缓存位置换成默认空状态 → 返回取出的会话状态。

调用关系:ModelClient::new_session 创建本轮 session 时调用它;本轮结束后 Drop 会把状态存回去。

调用图:被 1 处调用(new_session);外部调用 1 个(take)。

ModelClient::store_cached_websocket_session449–455 ↗
fn store_cached_websocket_session(&self, websocket_session: WebsocketSession)

作用:把 WebSocket 会话状态存回客户端缓存。它让连接可以跨轮次复用,也能在回退 HTTP 时清空旧连接。

数据流:输入一个 WebsocketSession → 加锁写入 cached_websocket_session → 覆盖旧缓存。

调用关系:ModelClientSession::drop 在一轮结束时会调用它;ModelClient::force_http_fallback 也会用它清掉缓存。

调用图:被 2 处调用(force_http_fallback, drop)。

ModelClient::force_http_fallback457–476 ↗
fn force_http_fallback(
        &self,
        session_telemetry: &SessionTelemetry,
        _model_info: &ModelInfo,
    ) -> bool

作用:强制本会话后续都改走 HTTP,不再用 WebSocket。遇到 WebSocket 不可用或服务端要求升级失败时,它能避免反复失败。

数据流:输入遥测对象和模型信息 → 检查 WebSocket 是否原本启用,设置 disable_websockets 标记,记录告警和计数 → 清空缓存连接,并返回这次是否真正激活了回退。

调用关系:ModelClientSession::try_switch_fallback_transport 会调用它;它是从 WebSocket 路线切换到 HTTP 路线的总开关。

调用图:调用 3 个内部函数(responses_websocket_enabled, store_cached_websocket_session, counter);被 1 处调用(try_switch_fallback_transport);外部调用 2 个(default, warn!)。

ModelClient::compact_conversation_history486–580 ↗
async fn compact_conversation_history(
        &self,
        prompt: &Prompt,
        model_info: &ModelInfo,
        turn_state: Option<Arc<OnceLock<String>>>,
        settings: CompactConversationR

作用:调用后端的压缩接口,把很长的对话历史压成更短的 ResponseItem 列表。这样后续请求不会因为上下文太长而浪费或超限。

数据流:输入提示词、模型信息、压缩设置、遥测和元数据 → 如果输入为空直接返回空列表;否则准备认证、请求体、header、attestation 和超时 → 调用 compact_input,记录压缩 trace,返回压缩后的条目或错误。

调用关系:这是普通流式对话之外的单次 HTTP 请求;它复用 ModelClient::build_responses_request、build_responses_headers、build_responses_compatibility_headers 等统一组装规则。

调用图:调用 11 个内部函数(new, new, build_responses_compatibility_headers, build_responses_request, current_client_setup, generate_attestation_header_for, for_endpoint, add_responses_lite_header, build_responses_headers, build_reqwest_client (+1 more));外部调用 7 个(new, new, from_str, build_request_telemetry, new, build_session_headers, default)。

ModelClient::create_realtime_call_with_headers582–616 ↗
async fn create_realtime_call_with_headers(
        &self,
        sdp: String,
        session_config: ApiRealtimeSessionConfig,
        architecture: RealtimeConversationArchitecture,
        mut ex

作用:创建一个 WebRTC 实时通话,并把后续旁路 WebSocket 要用的认证头一起准备好。WebRTC 可以理解成实时音视频/低延迟通道。

数据流:输入 SDP、实时会话配置、架构、额外 header 和可选服务商覆盖 → 准备当前认证和服务商,补 attestation,把认证信息复制到 sideband header → 调用实时通话客户端创建通话,返回服务端 SDP、call_id 和旁路 header。

调用关系:实时对话启动时会用它;它把 sideband_websocket_auth_headers 的结果带回给后续 WebSocket 控制流程。

调用图:调用 5 个内部函数(new, current_client_setup, generate_attestation_header_for, sideband_websocket_auth_headers, build_reqwest_client);外部调用 3 个(clone, insert, new)。

ModelClient::summarize_memories624–665 ↗
async fn summarize_memories(
        &self,
        raw_memories: Vec<ApiRawMemory>,
        model_info: &ModelInfo,
        effort: Option<ReasoningEffortConfig>,
        session_telemetry: &SessionT

作用:把原始记忆条目交给后端总结成更适合保存或展示的摘要。没有记忆输入时,它会直接返回空结果,避免多余网络请求。

数据流:输入 raw_memories、模型信息、推理强度和遥测 → 若列表为空返回空列表;否则准备认证、遥测、请求体和子代理 header → 调用 memories summarize 接口并返回摘要列表。

调用关系:记忆整理流程会调用它;它使用 ModelClient::build_subagent_headers 标记请求来源,并用 RequestRouteTelemetry::for_endpoint 标记接口路径。

调用图:调用 6 个内部函数(new, new, build_subagent_headers, current_client_setup, for_endpoint, build_reqwest_client);外部调用 4 个(new, build_request_telemetry, new, default)。

ModelClient::build_subagent_headers667–684 ↗
fn build_subagent_headers(&self) -> ApiHeaderMap

作用:为子代理或记忆整合类请求添加特殊 header。header 可以理解成请求包裹上的标签,告诉后端“这是谁发来的、用途是什么”。

数据流:读取 session_source → 如果能算出 subagent 值就加入 x-openai-subagent;如果是记忆整合请求,再加入 memgen 标记 → 返回 header 集合。

调用关系:ModelClient::summarize_memories 会调用它,让记忆总结请求带上正确身份标签。

调用图:调用 1 个内部函数(subagent_header_value);被 1 处调用(summarize_memories);外部调用 4 个(new, from_static, from_str, matches!)。

ModelClient::build_responses_compatibility_headers686–701 ↗
fn build_responses_compatibility_headers(
        &self,
        responses_metadata: &CodexResponsesMetadata,
    ) -> ApiHeaderMap

作用:生成 Responses API 需要的兼容性 header,并在记忆整合场景加上额外标记。它保证不同后端和不同内部来源能按约定识别请求。

数据流:输入 responses_metadata → 先取出 metadata 里已有的兼容性 header → 如果会话来源是 MemoryConsolidation,就加入 memgen 标记 → 返回 header 集合。

调用关系:WebSocket 握手、对话压缩、HTTP Responses 请求都会调用它,确保几条通道带的兼容标签一致。

调用图:调用 1 个内部函数(compatibility_headers);被 3 处调用(build_websocket_headers, compact_conversation_history, build_responses_options);外部调用 2 个(from_static, matches!)。

ModelClient::build_ws_client_metadata703–716 ↗
fn build_ws_client_metadata(
        &self,
        responses_metadata: &CodexResponsesMetadata,
        use_responses_lite: bool,
    ) -> HashMap<String, String>

作用:为 WebSocket 请求准备客户端元数据。它会把普通元数据和 Responses Lite 标记合在一起。

数据流:输入 responses_metadata 和是否使用 Responses Lite → 复制基础 client_metadata;如果开启 Lite,就加入对应键值 → 返回 HashMap。

调用关系:ModelClientSession::stream_responses_websocket 在构造 WebSocket 请求体时调用它。

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

ModelClient::generate_attestation_header_for718–730 ↗
async fn generate_attestation_header_for(&self) -> Option<HeaderValue>

作用:按需生成 attestation header。attestation 可以理解成一份“客户端可信证明”,用于后端校验请求环境。

数据流:读取 include_attestation 和 attestation_provider → 如果不开启或没有 provider,返回 None;否则用 thread_id 生成 header → 返回可选 HeaderValue。

调用关系:压缩请求、实时通话、HTTP Responses 选项和 WebSocket 握手都会调用它,把可信证明附到请求上。

调用图:被 4 处调用(build_websocket_headers, compact_conversation_history, create_realtime_call_with_headers, build_responses_options)。

ModelClient::build_request_telemetry733–747 ↗
fn build_request_telemetry(
        session_telemetry: &SessionTelemetry,
        auth_context: AuthRequestTelemetryContext,
        request_route_telemetry: RequestRouteTelemetry,
        auth_env_te

作用:为一次非流式 API 请求创建遥测对象。遥测就是记录请求耗时、状态码、认证信息等运行数据。

数据流:输入会话遥测、认证上下文、路由信息和认证环境信息 → 创建 ApiTelemetry → 作为 RequestTelemetry 返回。

调用关系:压缩和记忆总结这类单次请求会用它;流式请求则用 ModelClientSession::build_streaming_telemetry。

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

ModelClient::build_reasoning749–771 ↗
fn build_reasoning(
        model_info: &ModelInfo,
        effort: Option<ReasoningEffortConfig>,
        summary: ReasoningSummaryConfig,
    ) -> Option<Reasoning>

作用:根据模型能力决定是否给请求加推理设置。不是所有模型都支持推理摘要,所以这里会先看模型信息。

数据流:输入模型信息、推理强度和摘要配置 → 如果模型支持推理摘要,就生成 Reasoning;否则返回 None → 不修改外部状态。

调用关系:ModelClient::build_responses_request 会调用它,把推理相关字段放进最终请求体。

ModelClient::build_responses_request774–831 ↗
fn build_responses_request(
        &self,
        provider: &codex_api::Provider,
        prompt: &Prompt,
        model_info: &ModelInfo,
        effort: Option<ReasoningEffortConfig>,
        summa

作用:把项目内部的 Prompt 组装成后端 Responses API 能看懂的请求体。这是“把人话和工具配置打包成模型订单”的核心步骤。

数据流:输入服务商、提示词、模型信息、推理设置、服务层级和元数据 → 格式化输入,生成工具 JSON,设置推理、输出格式、缓存键、是否存储、是否并行工具调用等字段 → 返回 ResponsesApiRequest。

调用关系:压缩、HTTP 流式请求和 WebSocket 流式请求都调用它,保证不同运输方式发送的是同一套业务内容。

调用图:调用 5 个内部函数(is_azure_responses_endpoint, prompt_cache_key, get_formatted_input_for_request, client_metadata, service_tier_for_request);被 3 处调用(compact_conversation_history, stream_responses_api, stream_responses_websocket);外部调用 6 个(build_reasoning, new, create_text_param_for_request, create_tools_json_for_responses_api, vec!, warn!)。

ModelClient::responses_websocket_enabled836–844 ↗
fn responses_websocket_enabled(&self) -> bool

作用:判断当前会话还能不能用 Responses WebSocket。它同时看服务商是否支持,以及本会话是否已经被强制回退到 HTTP。

数据流:读取 provider capability 和 disable_websockets 标记 → 如果不支持或已禁用返回 false,否则返回 true。

调用关系:预连接、预热、正式 stream 和强制回退都会用它决定是否走 WebSocket 路线。

调用图:被 4 处调用(force_http_fallback, preconnect_websocket, prewarm_websocket, stream)。

ModelClient::current_client_setup850–859 ↗
async fn current_client_setup(&self) -> Result<CurrentClientSetup>

作用:拿到当前请求需要的认证和服务商配置。这样预热、HTTP 请求、WebSocket 请求都走同一个准备流程。

数据流:从 provider 异步读取 CodexAuth、ApiProvider 和 ApiAuth → 打包成 CurrentClientSetup → 返回给调用者。

调用关系:压缩、实时通话、记忆总结、预连接、HTTP 流式和 WebSocket 流式都会调用它,是所有外发请求前的统一准备站。

调用图:被 6 处调用(compact_conversation_history, create_realtime_call_with_headers, summarize_memories, preconnect_websocket, stream_responses_api, stream_responses_websocket)。

ModelClient::connect_websocket866–946 ↗
async fn connect_websocket(
        &self,
        session_telemetry: &SessionTelemetry,
        api_provider: codex_api::Provider,
        api_auth: SharedAuthProvider,
        responses_metadata: &C

作用:真正建立 Responses WebSocket 连接,并把握手过程的耗时、错误和认证情况记录下来。

数据流:输入遥测、服务商、认证、元数据和认证上下文 → 生成握手 header 和 WebSocket 遥测,按超时时间发起连接 → 记录连接结果和反馈标签 → 返回连接或 ApiError。

调用关系:ModelClientSession::preconnect_websocket 和 ModelClientSession::websocket_connection 都调用它,保证预连接和正式重连的握手行为一致。

调用图:调用 4 个内部函数(build_websocket_headers, build_websocket_telemetry, default_headers, record_websocket_connect);被 2 处调用(preconnect_websocket, websocket_connection);外部调用 5 个(new, now, Transport, emit_feedback_request_tags_with_auth_env, timeout)。

ModelClient::build_websocket_headers949–979 ↗
async fn build_websocket_headers(
        &self,
        responses_metadata: &CodexResponsesMetadata,
    ) -> ApiHeaderMap

作用:构造 WebSocket 握手时要带的 header。握手可以理解成连接前互相确认身份和规则。

数据流:输入 responses_metadata → 加入 beta、请求 id、会话 id、线程 id、兼容性 header、attestation、WebSocket v2 标记和可选 timing 标记 → 返回 header 集合。

调用关系:ModelClient::connect_websocket 在连接前调用它。

调用图:调用 3 个内部函数(build_responses_compatibility_headers, generate_attestation_header_for, build_responses_headers);被 1 处调用(connect_websocket);外部调用 3 个(from_static, from_str, build_session_headers)。

ModelClientSession::drop983–987 ↗
fn drop(&mut self)

作用:在一轮对话 session 被销毁时,把 WebSocket 状态存回 ModelClient。这样连接有机会被下一轮继续使用。

数据流:从当前 ModelClientSession 取走 websocket_session → 交给 ModelClient::store_cached_websocket_session → 当前对象结束生命周期。

调用关系:Rust 自动在 ModelClientSession 离开作用域时调用它;它和 ModelClient::take_cached_websocket_session 配成一进一出。

调用图:调用 1 个内部函数(store_cached_websocket_session);外部调用 1 个(take)。

ModelClientSession::turn_state991–993 ↗
fn turn_state(&self) -> Arc<OnceLock<String>>

作用:取出本轮对话的粘性路由令牌容器。粘性路由就是让同一轮的请求尽量回到同一个后端位置,避免状态错乱。

数据流:读取内部 Arc<OnceLock<String>> → 克隆共享指针 → 返回给调用者,不复制实际字符串内容。

调用关系:自动压缩流程 run_auto_compact 会用它,把同一轮的 turn_state 传给压缩请求。

调用图:被 1 处调用(run_auto_compact);外部调用 1 个(clone)。

ModelClientSession::reset_websocket_session995–1002 ↗
fn reset_websocket_session(&mut self)

作用:把本轮 WebSocket 状态清空。连接超时或需要彻底重来时,会用它防止继续使用坏状态。

数据流:清掉 connection、last_request、last_response_rx 和 warmup 标记 → 把 connection_reused 设为 false → 不返回值。

调用关系:ModelClientSession::websocket_connection 在连接超时等场景会调用它。

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

ModelClientSession::build_responses_options1009–1037 ↗
async fn build_responses_options(
        &self,
        responses_metadata: &CodexResponsesMetadata,
        compression: Compression,
        use_responses_lite: bool,
    ) -> ApiResponsesOptions

作用:构造 HTTP Responses API 调用时的附加选项,包括会话信息、header、压缩方式和 turn_state。

数据流:输入元数据、压缩方式和是否 Lite → 组装 session_id、thread_id、session_source、各种 header、attestation、Lite 标记和 turn_state → 返回 ApiResponsesOptions。

调用关系:ModelClientSession::stream_responses_api 在发 HTTP 流式请求前调用它。

调用图:调用 4 个内部函数(build_responses_compatibility_headers, generate_attestation_header_for, add_responses_lite_header, build_responses_headers);被 1 处调用(stream_responses_api);外部调用 1 个(clone)。

ModelClientSession::get_incremental_items1039–1079 ↗
fn get_incremental_items(
        &self,
        request: &ResponsesApiRequest,
        last_response: Option<&LastResponse>,
        allow_empty_delta: bool,
    ) -> Option<Vec<ResponseItem>>

作用:找出当前请求相比上次 WebSocket 请求新增了哪些输入项。这样可以只发增量,像聊天软件只同步新消息,而不是每次重传全部聊天记录。

数据流:输入当前请求、上次响应和是否允许空增量 → 比较非输入设置是否一致,再从当前输入里剥掉上次输入和上次服务端输出 → 返回新增 ResponseItem 列表,或返回 None 表示不能增量发送。

调用关系:ModelClientSession::prepare_websocket_request 会调用它;它依赖 responses_request_properties_match 判断请求配置是否可复用。

调用图:调用 1 个内部函数(responses_request_properties_match);被 1 处调用(prepare_websocket_request);外部调用 1 个(trace!)。

ModelClientSession::get_last_response1081–1089 ↗
fn get_last_response(&mut self) -> Option<LastResponse>

作用:尝试取出上一条 WebSocket 请求完成后留下的响应信息。这个信息用于后续请求接着 previous_response_id 发送增量。

数据流:从 last_response_rx 取出一次性接收器 → 非阻塞检查是否已经收到 LastResponse → 返回 Some 或 None,并消费掉接收器。

调用关系:ModelClientSession::prepare_websocket_request 在准备下一次 WebSocket 请求时调用它。

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

ModelClientSession::prepare_websocket_request1091–1122 ↗
fn prepare_websocket_request(
        &mut self,
        payload: ResponseCreateWsRequest,
        request: &ResponsesApiRequest,
    ) -> (ResponsesWsRequest, bool)

作用:决定这次 WebSocket 请求是发完整 payload,还是基于上次 response_id 发增量 payload。这样能减少重复传输。

数据流:输入 WebSocket payload 和完整 Responses 请求 → 先取上次响应;如果无法取到或不能增量,就返回完整 ResponseCreate;如果能增量且有 previous_response_id,就把输入换成新增项并带上 previous_response_id → 返回 WebSocket 请求和一个 warmup 相关标记。

调用关系:ModelClientSession::stream_responses_websocket 在真正发送前调用它。

调用图:调用 2 个内部函数(get_incremental_items, get_last_response);被 1 处调用(stream_responses_websocket);外部调用 2 个(ResponseCreate, trace!)。

ModelClientSession::preconnect_websocket1127–1164 ↗
async fn preconnect_websocket(
        &mut self,
        session_telemetry: &SessionTelemetry,
        responses_metadata: &CodexResponsesMetadata,
    ) -> std::result::Result<(), ApiError>

作用:提前建立 WebSocket 连接,但不发送提示词内容。它像提前把电话拨通,等真正说话时更快。

数据流:输入遥测和元数据 → 如果 WebSocket 不可用或已有连接就直接成功;否则准备认证和连接参数 → 调用 ModelClient::connect_websocket,保存连接并标记不是复用连接。

调用关系:启动或回合前的预连接流程会调用它;正式请求之后通过 ModelClientSession::websocket_connection 复用这条连接。

调用图:调用 6 个内部函数(new, connect_websocket, current_client_setup, responses_websocket_enabled, for_endpoint, set_connection_reused);外部调用 1 个(default)。

ModelClientSession::websocket_connection1178–1233 ↗
async fn websocket_connection(
        &mut self,
        params: WebsocketConnectParams<'_>,
    ) -> std::result::Result<&ApiWebSocketConnection, ApiError>

作用:为本轮请求拿到一条可用的 WebSocket 连接。如果旧连接关了,就重新连;如果还活着,就复用。

数据流:输入连接所需参数 → 检查当前连接是否关闭;需要新连接时清理旧请求状态并调用 connect_websocket,不需要时标记 connection_reused=true → 返回连接引用或错误。

调用关系:ModelClientSession::stream_responses_websocket 在每次 WebSocket 请求前调用它;连接超时时会触发 reset_websocket_session。

调用图:调用 3 个内部函数(connect_websocket, reset_websocket_session, set_connection_reused);被 1 处调用(stream_responses_websocket);外部调用 2 个(Stream, matches!)。

ModelClientSession::responses_request_compression1235–1244 ↗
fn responses_request_compression(&self, auth: Option<&CodexAuth>) -> Compression

作用:决定 HTTP Responses 请求是否使用压缩。压缩能省流量,但只在配置允许、认证和服务商都合适时启用。

数据流:输入可选 CodexAuth → 检查 enable_request_compression、是否走 Codex 后端、服务商是否 OpenAI → 返回 Zstd 压缩或不压缩。

调用关系:ModelClientSession::stream_responses_api 在构造 HTTP 请求选项前调用它。

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

ModelClientSession::stream_responses_api1263–1364 ↗
async fn stream_responses_api(
        &self,
        prompt: &Prompt,
        model_info: &ModelInfo,
        session_telemetry: &SessionTelemetry,
        effort: Option<ReasoningEffortConfig>,

作用:通过 HTTP 的 Responses API 发起一次流式模型请求。流式就是服务端一边生成一边把事件传回来。

数据流:输入提示词、模型信息、遥测、推理设置、元数据和 trace → 循环准备认证、请求体、header、压缩和遥测 → 调用 ApiResponsesClient.stream_request;成功时把事件流映射成内部 ResponseStream;遇到 401 时尝试刷新认证后重试;其他错误记录 trace 后返回。

调用关系:ModelClientSession::stream 在不能或不该用 WebSocket 时调用它;它把底层流交给 map_response_stream 统一转换。

调用图:调用 12 个内部函数(new, new, build_responses_request, current_client_setup, build_responses_options, responses_request_compression, from_recovery, for_endpoint, handle_unauthorized, map_response_stream (+2 more));被 1 处调用(stream);外部调用 7 个(new, build_streaming_telemetry, map_api_error, extract_response_debug_context, extract_response_debug_context_from_api_error, default, clone)。

ModelClientSession::stream_responses_websocket1381–1520 ↗
async fn stream_responses_websocket(
        &mut self,
        prompt: &Prompt,
        model_info: &ModelInfo,
        session_telemetry: &SessionTelemetry,
        effort: Option<ReasoningEffortCon

作用:通过 WebSocket 发送一次 Responses 请求。它支持预热请求、连接复用、增量发送、401 认证恢复,以及必要时提示回退 HTTP。

数据流:输入提示词、模型信息、遥测、推理设置、元数据、是否 warmup、trace → 准备认证和请求体,补客户端元数据和 turn_state,获取 WebSocket 连接,准备完整或增量请求,记录 trace,发送并接收流 → 返回 ResponseStream,或返回 FallbackToHttp,或返回错误。

调用关系:ModelClientSession::prewarm_websocket 和 ModelClientSession::stream 都会调用它;它串起 current_client_setup、build_responses_request、websocket_connection、prepare_websocket_request、map_response_stream 等步骤。

调用图:调用 15 个内部函数(from, new, build_responses_request, build_ws_client_metadata, current_client_setup, prepare_websocket_request, websocket_connection, from_recovery, for_endpoint, connection_reused (+5 more));被 2 处调用(prewarm_websocket, stream);外部调用 6 个(clone, map_api_error, response_create_client_metadata, default, Stream, clone)。

ModelClientSession::build_streaming_telemetry1523–1538 ↗
fn build_streaming_telemetry(
        session_telemetry: &SessionTelemetry,
        auth_context: AuthRequestTelemetryContext,
        request_route_telemetry: RequestRouteTelemetry,
        auth_env_

作用:为 HTTP 流式请求创建两种遥测对象:普通请求遥测和 SSE 遥测。SSE 是服务器持续推送事件的一种 HTTP 流格式。

数据流:输入会话遥测、认证上下文、路由和认证环境 → 创建一个 ApiTelemetry → 分别作为 RequestTelemetry 和 SseTelemetry 返回。

调用关系:ModelClientSession::stream_responses_api 在发起 HTTP 流式请求前使用它。

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

ModelClientSession::build_websocket_telemetry1541–1555 ↗
fn build_websocket_telemetry(
        session_telemetry: &SessionTelemetry,
        auth_context: AuthRequestTelemetryContext,
        request_route_telemetry: RequestRouteTelemetry,
        auth_env_

作用:为 WebSocket 通道创建遥测对象,用来记录 WebSocket 请求和事件的耗时、错误、复用情况。

数据流:输入会话遥测、认证上下文、路由和认证环境 → 创建 ApiTelemetry → 作为 WebsocketTelemetry 返回。

调用关系:ModelClient::connect_websocket 在建立连接时调用它,把遥测挂到底层 WebSocket 客户端上。

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

ModelClientSession::prewarm_websocket1558–1608 ↗
async fn prewarm_websocket(
        &mut self,
        prompt: &Prompt,
        model_info: &ModelInfo,
        session_telemetry: &SessionTelemetry,
        effort: Option<ReasoningEffortConfig>,

作用:给本轮 WebSocket 做预热:发送一个 generate=false 的请求,让连接和 previous_response_id 先准备好,但不真正生成模型回答。

数据流:输入提示词、模型信息、遥测、推理设置和元数据 → 如果 WebSocket 不可用或已经有请求记录就跳过;否则用 disabled trace 调用 stream_responses_websocket 的 warmup 模式 → 等到 Completed 事件,或在需要时切到 HTTP。

调用关系:回合执行前可调用它优化第一次正式请求;它会在 FallbackToHttp 时调用 try_switch_fallback_transport。

调用图:调用 4 个内部函数(responses_websocket_enabled, stream_responses_websocket, try_switch_fallback_transport, disabled);外部调用 1 个(current_span_w3c_trace_context)。

ModelClientSession::stream1619–1670 ↗
async fn stream(
        &mut self,
        prompt: &Prompt,
        model_info: &ModelInfo,
        session_telemetry: &SessionTelemetry,
        effort: Option<ReasoningEffortConfig>,
        summar

作用:这是本轮模型请求的主入口。它优先用 WebSocket,失败或被禁用时改用 HTTP。

数据流:输入提示词、模型、遥测、推理设置、服务层级、元数据和 trace → 查看 wire_api;若支持且启用 WebSocket,就先试 stream_responses_websocket;若返回回退信号,则禁用 WebSocket;最后必要时调用 stream_responses_api → 返回统一的 ResponseStream。

调用关系:上层采样、远程压缩、测试辅助等会调用它;它把 WebSocket 和 HTTP 两条运输路线藏在统一接口后面。

调用图:调用 4 个内部函数(responses_websocket_enabled, stream_responses_api, stream_responses_websocket, try_switch_fallback_transport);被 5 处调用(drain_to_completed, run_remote_compaction_request_v2, try_run_sampling_request, stream_until_complete_with_metadata, stream_until_complete_with_model_info);外部调用 1 个(current_span_w3c_trace_context)。

ModelClientSession::try_switch_fallback_transport1678–1688 ↗
fn try_switch_fallback_transport(
        &mut self,
        session_telemetry: &SessionTelemetry,
        model_info: &ModelInfo,
    ) -> bool

作用:把当前 session 和整个客户端都切到 HTTP 回退模式。之后同一 Codex 会话内不再尝试 WebSocket。

数据流:输入遥测和模型信息 → 调用 ModelClient::force_http_fallback 设置全局禁用标记并记录统计 → 把本轮 websocket_session 重置为空 → 返回是否刚刚激活回退。

调用关系:预热、正式 stream 和外部错误处理逻辑会调用它,用来统一处理 WebSocket 不可用的情况。

调用图:调用 1 个内部函数(force_http_fallback);被 3 处调用(prewarm_websocket, stream, handle_retryable_response_stream_error);外部调用 1 个(default)。

stamp_ws_stream_request_start_ms1695–1704 ↗
fn stamp_ws_stream_request_start_ms(request: &mut ResponsesWsRequest)

作用:在 WebSocket 请求即将发出前,给请求元数据盖上当前时间戳。这样后端或日志能更准确地计算传输时间。

数据流:输入可变的 ResponsesWsRequest → 找到其中的 ResponseCreate payload,确保 client_metadata 存在 → 写入当前毫秒时间戳。

调用关系:ModelClientSession::stream_responses_websocket 在发送请求前调用它。

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

build_responses_headers1712–1730 ↗
fn build_responses_headers(
    beta_features_header: Option<&str>,
    turn_state: Option<&Arc<OnceLock<String>>>,
) -> ApiHeaderMap

作用:生成 Responses API 请求通用的 Codex header,包括 beta 功能和本轮粘性路由令牌。

数据流:输入可选 beta_features_header 和可选 turn_state 容器 → 如果 beta 值有效就加入 x-codex-beta-features;如果 turn_state 已有值就加入 x-codex-turn-state → 返回 header 集合。

调用关系:WebSocket 握手、压缩请求、HTTP Responses 选项都会调用它,保证这些关键标签一致。

调用图:被 3 处调用(build_websocket_headers, compact_conversation_history, build_responses_options);外部调用 2 个(new, from_str)。

add_responses_lite_header1732–1739 ↗
fn add_responses_lite_header(headers: &mut ApiHeaderMap, use_responses_lite: bool)

作用:在需要时给请求加上 Responses Lite 标记。这个标记告诉后端走更轻量的 Responses 行为。

数据流:输入可变 header 集合和 use_responses_lite 布尔值 → 如果为 true,就插入 x-openai-internal-codex-responses-lite=true → 没有返回值。

调用关系:压缩请求和 HTTP Responses 选项会调用它;WebSocket 则通过 client metadata 传类似信息。

调用图:被 2 处调用(compact_conversation_history, build_responses_options);外部调用 2 个(insert, from_static)。

map_response_stream1744–1763 ↗
fn map_response_stream(
    api_stream: codex_api::ResponseStream,
    session_telemetry: SessionTelemetry,
    inference_trace_attempt: InferenceTraceAttempt,
) -> (ResponseStream, oneshot::Receiver<

作用:把 codex_api 返回的底层响应流转换成 core 内部使用的 ResponseStream。它还保留上游请求 id 用于日志和反馈。

数据流:输入底层 api_stream、会话遥测和 trace attempt → 拆出 rx_event 和 upstream_request_id → 调用 map_response_events 做实际转换 → 返回内部 ResponseStream 和 LastResponse 接收器。

调用关系:HTTP 和 WebSocket 流式请求成功后都会调用它。

调用图:调用 1 个内部函数(map_response_events);被 2 处调用(stream_responses_api, stream_responses_websocket)。

map_response_events1765–1910 ↗
fn map_response_events(
    upstream_request_id: Option<String>,
    api_stream: S,
    session_telemetry: SessionTelemetry,
    inference_trace_attempt: InferenceTraceAttempt,
) -> (ResponseStream, o

作用:逐个搬运并翻译模型事件流,同时记录完成、失败、取消和 token 用量。它像一个中转站,一边收服务端事件,一边发给 Codex 内部消费者。

数据流:输入上游请求 id、事件流、遥测和 trace → 创建内部 mpsc 通道和 last_response oneshot 通道,启动后台任务读取事件 → OutputItemDone 会累计输出项,Completed 会记录用量并发送 LastResponse,错误会映射成 CodexErr → 返回内部 ResponseStream 和 LastResponse 接收器。

调用关系:map_response_stream 调用它;WebSocket 增量发送依赖它保存的 LastResponse。

调用图:调用 5 个内部函数(see_event_completed_failed, sse_event_completed, record_cancelled, record_completed, record_failed);被 1 处调用(map_response_stream);外部调用 9 个(new, new, OutputItemDone, map_api_error, extract_response_debug_context_from_api_error, feedback_tags!, take, select!, spawn)。

PendingUnauthorizedRetry::from_recovery1930–1936 ↗
fn from_recovery(recovery: UnauthorizedRecoveryExecution) -> Self

作用:把一次成功的认证恢复结果变成“下一次请求是 401 后重试”的标记。这样遥测能看出这次请求是不是刷新 token 后的跟进请求。

数据流:输入 UnauthorizedRecoveryExecution → 设置 retry_after_unauthorized=true,并复制 recovery mode 和 phase → 返回 PendingUnauthorizedRetry。

调用关系:HTTP 和 WebSocket 请求遇到 401 并恢复成功后会调用它;随后 AuthRequestTelemetryContext::new 会把这些信息写进遥测上下文。

调用图:被 3 处调用(stream_responses_api, stream_responses_websocket, auth_request_telemetry_context_tracks_attached_auth_and_retry_phase)。

AuthRequestTelemetryContext::new1950–1970 ↗
fn new(
        auth_mode: Option<AuthMode>,
        api_auth: &dyn AuthProvider,
        retry: PendingUnauthorizedRetry,
    ) -> Self

作用:整理一次请求的认证遥测信息,比如使用哪种登录方式、有没有带认证头、是否是 401 后重试。

数据流:输入可选 AuthMode、认证提供者和 pending retry 信息 → 检查认证头是否附带及名称,把认证模式归类成 ApiKey 或 Chatgpt → 返回 AuthRequestTelemetryContext。

调用关系:压缩、记忆总结、预连接、HTTP 流式和 WebSocket 流式都会调用它,再交给 ApiTelemetry 记录。

调用图:被 6 处调用(compact_conversation_history, summarize_memories, preconnect_websocket, stream_responses_api, stream_responses_websocket, auth_request_telemetry_context_tracks_attached_auth_and_retry_phase);外部调用 1 个(auth_header_telemetry)。

handle_unauthorized1982–2096 ↗
async fn handle_unauthorized(
    transport: TransportError,
    auth_recovery: &mut Option<UnauthorizedRecovery>,
    session_telemetry: &SessionTelemetry,
) -> Result<UnauthorizedRecoveryExecution>

作用:处理 401 未授权错误,并在可能时刷新 ChatGPT token 后让调用者重试。401 就是服务端说“你现在的身份凭证不通过”。

数据流:输入 TransportError、可选认证恢复器和遥测 → 提取调试信息;如果有可用恢复步骤,就执行一次刷新并记录成功或失败;成功返回恢复执行信息,失败返回对应 CodexErr;如果无法恢复,则记录未执行并返回映射后的错误。

调用关系:HTTP 和 WebSocket 流式请求在遇到 401 时调用它;成功后调用方会用 PendingUnauthorizedRetry::from_recovery 标记下一次重试。

调用图:调用 2 个内部函数(emit_feedback_auth_recovery_tags, record_auth_recovery);被 2 处调用(stream_responses_api, stream_responses_websocket);外部调用 5 个(Transport, map_api_error, extract_response_debug_context, Io, RefreshTokenFailed)。

api_error_http_status2098–2103 ↗
fn api_error_http_status(error: &ApiError) -> Option<u16>

作用:从 ApiError 里取出 HTTP 状态码。只有错误确实来自 HTTP 响应时才有状态码。

数据流:输入 ApiError 引用 → 如果是 TransportError::Http,就返回 status.as_u16;否则返回 None。

调用关系:WebSocket 连接和 WebSocket 请求遥测会用它,把错误状态码写进统计和反馈标签。

ApiTelemetry::new2113–2125 ↗
fn new(
        session_telemetry: SessionTelemetry,
        auth_context: AuthRequestTelemetryContext,
        request_route_telemetry: RequestRouteTelemetry,
        auth_env_telemetry: AuthEnvTelem

作用:创建一个统一遥测记录器。它可以同时服务普通 HTTP 请求、SSE 事件流和 WebSocket。

数据流:输入会话遥测、认证上下文、路由信息和认证环境 → 存入 ApiTelemetry 字段 → 返回新对象。

调用关系:ModelClient::build_request_telemetry、ModelClientSession::build_streaming_telemetry 和 build_websocket_telemetry 都会调用它。

调用图:被 3 处调用(build_request_telemetry, build_streaming_telemetry, build_websocket_telemetry)。

ApiTelemetry::on_request2129–2183 ↗
fn on_request(
        &self,
        attempt: u64,
        status: Option<HttpStatusCode>,
        error: Option<&TransportError>,
        duration: Duration,
    )

作用:在一次普通 HTTP 请求结束时记录结果。它会记录耗时、状态码、错误、认证头情况和调试信息。

数据流:输入尝试次数、可选状态码、可选传输错误和耗时 → 提取错误消息和调试上下文 → 写入 session_telemetry,并发出反馈标签 → 不返回值。

调用关系:底层 HTTP 客户端会在请求完成时回调它;压缩、记忆总结和 HTTP Responses 请求都能受益于这套统计。

调用图:调用 1 个内部函数(record_api_request);外部调用 1 个(emit_feedback_request_tags_with_auth_env)。

ApiTelemetry::on_sse_poll2187–2196 ↗
fn on_sse_poll(
        &self,
        result: &std::result::Result<
            Option<std::result::Result<Event, EventStreamError<TransportError>>>,
            tokio::time::error::Elapsed,

作用:记录一次 SSE 事件轮询的结果和耗时。SSE 是 HTTP 上服务端持续推送事件的方式。

数据流:输入本次 poll 的结果和耗时 → 直接交给 session_telemetry.log_sse_event → 不返回值。

调用关系:HTTP 流式 Responses 请求读取事件时,底层 SSE 客户端会回调它。

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

ApiTelemetry::on_ws_request2200–2237 ↗
fn on_ws_request(&self, duration: Duration, error: Option<&ApiError>, connection_reused: bool)

作用:记录一次 WebSocket 请求的耗时、错误和连接是否复用。它也会发出认证相关反馈标签。

数据流:输入耗时、可选 ApiError 和 connection_reused → 提取错误消息、HTTP 状态和调试上下文 → 写入 WebSocket 请求遥测并发出反馈标签 → 不返回值。

调用关系:底层 WebSocket 客户端在一次请求结束时调用它;ModelClient::connect_websocket 创建的 WebSocketTelemetry 会提供这个回调。

调用图:调用 1 个内部函数(record_websocket_request);外部调用 1 个(emit_feedback_request_tags_with_auth_env)。

ApiTelemetry::on_ws_event2239–2246 ↗
fn on_ws_event(
        &self,
        result: &std::result::Result<Option<std::result::Result<Message, Error>>, ApiError>,
        duration: Duration,
    )

作用:记录一次 WebSocket 事件读取的结果和耗时。这样可以观察 WebSocket 接收消息是否卡住或出错。

数据流:输入事件读取结果和耗时 → 交给 session_telemetry.record_websocket_event → 不返回值。

调用关系:底层 WebSocket 客户端每次等待或收到事件时会调用它。

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

core/src/responses_retry.rs源码 ↗
orchestrationrequest handling

Responses 请求是和模型对话时用的一类流式请求,意思是结果会像水流一样一段段回来。网络不稳时,这条“水管”可能断掉。这个文件就是断线后的应急规则:先看还能不能重试;能重试就按退避时间等待,退避就是每次失败后稍微多等一会儿,避免疯狂重连;如果重试次数用完了,还会尝试把传输方式从 WebSocket(一种长连接通道)切到 HTTPS(更普通的请求方式)。它还会把“正在重连”的消息发给前端或界面,让用户知道系统在自救。这里用 ResponsesStreamRequest 区分是哪种请求出问题,比如普通采样请求,或者远端压缩请求,这样日志里能写得更准确。

函数细节2
handle_retryable_response_stream_error22–79 ↗
async fn handle_retryable_response_stream_error(
    retries: &mut u64,
    max_retries: u64,
    err: CodexErr,
    client_session: &mut ModelClientSession,
    sess: &Session,
    turn_context: &Tur

作用:这个函数在流式请求出错但可能还能挽救时被调用。它决定下一步是重试、切换传输方式,还是把错误原样交回去表示真的失败了。

数据流:进去的是当前已重试次数、最大重试次数、这次错误、模型客户端会话、当前会话、当前回合上下文,以及请求类型。它先看重试次数是不是已经到顶;如果到顶但还能从 WebSocket 切到 HTTPS,就发一条警告给界面,把重试次数清零,然后告诉调用者可以再试。若还没到顶,它会增加重试次数,根据错误里建议的等待时间或通用退避算法算出要等多久,写日志,必要时通知界面“正在重连”,然后睡一会儿再返回成功。出来的结果是:返回 Ok 表示外层请求循环应该再跑一次;返回 Err 表示这次错误已经没法继续兜底。

调用关系:它是在 run_sampling_request 和 run_remote_compaction_request_v2 这类真正发起 Responses 流式请求的地方被叫到的,相当于请求循环旁边的“故障处理员”。它会把记录日志的活交给 log_retry,把等待时间交给 backoff 算法来估算,还会调用会话对象去通知前端,或调用客户端会话去尝试切换备用传输通道。

调用图:调用 3 个内部函数(try_switch_fallback_transport, log_retry, backoff);被 2 处调用(run_remote_compaction_request_v2, run_sampling_request);外部调用 6 个(cfg!, notify_stream_error, send_event, format!, Warning, sleep)。

log_retry81–105 ↗
fn log_retry(
    request: ResponsesStreamRequest,
    turn_context: &TurnContext,
    err: &CodexErr,
    retries: u64,
    max_retries: u64,
    delay: Duration,
)

作用:这个函数只负责把“准备重试”这件事写进日志。它会根据出问题的是哪种请求,写出不同但更有用的提示。

数据流:进去的是请求类型、当前回合上下文、错误内容、已重试次数、最大重试次数和本次等待时间。它不改变请求状态,也不返回新数据,只是把这些信息整理成日志。对于普通采样请求,它记录断流后第几次重试和要等多久;对于远端压缩请求,它还带上回合编号和错误内容,方便之后排查。

调用关系:它只被 handle_retryable_response_stream_error 调用,位置很靠内:当外层已经决定“这次还要重试”时,它负责留下现场记录。它不参与是否重试的判断,也不通知用户,只给开发者或运维人员看的日志系统写信息。

调用图:被 1 处调用(handle_retryable_response_stream_error);外部调用 1 个(warn!)。

core/src/session_startup_prewarm.rs源码 ↗
orchestrationstartup 到 first regular turn

这个文件解决的是“第一次对话太慢”的问题。它像餐厅提前烧水:客人还没点茶,先把水烧上;客人一来,如果水已经开了,就能马上泡茶。这里的“水”是模型客户端会话,尤其是 websocket(一种保持长连接的网络通道)连接。文件先判断当前模型服务是否支持这种预热;支持的话,就在后台开一个异步任务(一边做、不挡住主流程的任务),创建一套启动用的回合上下文,准备工具列表,拼好提示词,再让模型客户端提前建立 websocket。等用户真正开始普通对话时,代码会来“领取”这个预热结果:成功就拿来用;还没好就等一小会;超时、失败或被取消,就放弃预热,避免卡住正常对话。它还会记录各种耗时指标,方便开发者知道预热到底省了多少时间,或者失败在哪里。

函数细节7
SessionStartupPrewarmHandle::new40–50 ↗
fn new(
        task: JoinHandle<CodexResult<ModelClientSession>>,
        started_at: Instant,
        timeout: Duration,
    ) -> Self

作用:把一个后台预热任务包装成一个“可控把手”。以后可以用这个把手等待预热完成,也可以在不用时中止它。

数据流:进去的是一个后台任务、任务开始时间和允许等待的最长时间 → 它把任务放进 AbortOnDropHandle 里,这相当于给任务套上一个“丢掉就能自动中止”的外壳 → 出来的是 SessionStartupPrewarmHandle,里面保存了任务、开始时间和超时限制。

调用关系:Session::schedule_startup_prewarm 启动后台预热后会调用它,把原始任务变成会话里可保存、可消费的预热句柄。测试里也会直接用它来模拟预热中的各种情况。

调用图:被 3 处调用(interrupting_regular_turn_waiting_on_startup_prewarm_emits_turn_aborted, regular_turn_emits_turn_started_with_trace_id_without_waiting_for_startup_prewarm, schedule_startup_prewarm);外部调用 1 个(new)。

SessionStartupPrewarmHandle::abort52–55 ↗
async fn abort(self)

作用:主动取消还在跑的预热任务,并等它真正结束。用于明确告诉系统:这个提前准备的连接不要了。

数据流:进去的是这个预热把手本身 → 它先对内部任务发出 abort,也就是“停下”的信号,然后等待任务收尾 → 出来没有业务结果,但后台预热会被停止,避免继续占资源。

调用关系:它是预热句柄的清理按钮。虽然这个文件里主要通过 resolve 在超时或取消时中止任务,但外部如果决定完全不要这个预热,也可以调用它。

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

SessionStartupPrewarmHandle::resolve58–154 ↗
async fn resolve(
        self,
        session_telemetry: &SessionTelemetry,
        cancellation_token: &CancellationToken,
    ) -> SessionStartupPrewarmResolution

作用:在第一轮普通对话要开始时,决定这个预热连接到底能不能用。它会处理三种情况:已经好了、等一会后还没好、用户取消了。

数据流:进去的是预热把手、会话遥测记录器和取消令牌(一个可通知任务停止的信号)→ 它先算预热已经跑了多久,以及还剩多少等待时间;如果任务已结束,就直接读取结果;如果还没结束,就同时等待“任务完成”“超时”“取消”三件事 → 出来的是一个结果:Ready 表示拿到了可用的预热模型会话,Unavailable 表示失败或超时等不可用,Cancelled 表示整个等待被取消;同时它会写入多项耗时和状态指标。

调用关系:Session::consume_startup_prewarm_for_regular_turn 会在普通回合开始前调用它。它内部会把任务结束结果交给 SessionStartupPrewarmHandle::resolution_from_join_result 翻译成更好懂的业务结果;如果超时或取消,它会中止后台任务并记录原因。

调用图:调用 2 个内部函数(record_duration, record_startup_phase);外部调用 5 个(now, resolution_from_join_result, Ready, info!, select!)。

SessionStartupPrewarmHandle::resolution_from_join_result156–179 ↗
fn resolution_from_join_result(
        result: std::result::Result<CodexResult<ModelClientSession>, tokio::task::JoinError>,
        started_at: Instant,
    ) -> SessionStartupPrewarmResolution

作用:把后台任务的原始结束结果翻译成预热专用的三类结论。这样外层不用关心任务是正常返回、业务失败,还是任务本身崩了。

数据流:进去的是后台任务的返回值,以及预热开始时间 → 如果任务成功并返回模型会话,就包装成 Ready;如果任务返回错误,就记一条警告并标成 failed;如果后台任务本身 join 失败,也就是任务没有正常收尾,就记警告并标成 join_failed,同时带上已经花掉的时间 → 出来的是 SessionStartupPrewarmResolution。

调用关系:它只负责“翻译结果”。SessionStartupPrewarmHandle::resolve 在拿到后台任务结果后调用它,再继续记录指标并把最终结论交给普通回合使用。

调用图:外部调用 4 个(new, elapsed, Ready, warn!)。

Session::schedule_startup_prewarm183–214 ↗
async fn schedule_startup_prewarm(self: &Arc<Self>, base_instructions: String)

作用:在会话启动早期安排一次后台预热。它不阻塞用户操作,而是悄悄先把模型 websocket 连接准备好。

数据流:进去的是会话本身和基础指令文本 → 它先检查模型客户端是否启用了 websocket 预热;没启用就直接返回;启用了就读取连接超时时间,记录开始时间,复制会话引用,然后用 tokio::spawn 开一个后台异步任务去执行真正的预热 → 出来没有直接返回预热结果,而是把一个 SessionStartupPrewarmHandle 保存进会话,等待后面的普通回合来领取。

调用关系:这是预热流程的入口。它创建后台任务,任务内部调用 schedule_startup_prewarm_inner 做实际准备;任务创建后,它用 SessionStartupPrewarmHandle::new 包起来,再交给会话保存。

调用图:调用 2 个内部函数(new, schedule_startup_prewarm_inner);外部调用 3 个(clone, now, spawn)。

Session::consume_startup_prewarm_for_regular_turn216–229 ↗
async fn consume_startup_prewarm_for_regular_turn(
        &self,
        cancellation_token: &CancellationToken,
    ) -> SessionStartupPrewarmResolution

作用:普通对话开始前来取走启动时准备好的预热连接。如果没有预热、预热失败或超时,它会给出明确状态,让正常对话可以继续走普通路径。

数据流:进去的是会话和取消令牌 → 它先从会话里取出之前保存的预热把手;如果根本没有,就返回 not_scheduled;如果有,就调用把手的 resolve,等待或判断预热是否可用 → 出来的是 Ready、Unavailable 或 Cancelled 之一;同时这个预热把手会被取走,避免被重复消费。

调用关系:它连接“启动预热”和“正式第一轮对话”。普通回合开始时会调用它;它把具体等待、超时、取消、记录指标等细节交给 SessionStartupPrewarmHandle::resolve。

schedule_startup_prewarm_inner232–300 ↗
async fn schedule_startup_prewarm_inner(
    session: Arc<Session>,
    base_instructions: String,
) -> CodexResult<ModelClientSession>

作用:真正执行预热的核心步骤:创建一个启动专用的回合环境,准备工具和提示词,然后让模型客户端提前连上 websocket。

数据流:进去的是会话和基础指令文本 → 它创建启动预热用的 turn context(一次对话回合所需的环境资料),记录耗时;再创建取消令牌,调用 built_tools 准备本轮可用工具;接着调用 build_prompt 拼出要发给模型的提示词;然后生成请求元数据,创建新的模型客户端会话,并调用 prewarm_websocket 进行实际网络预热 → 出来是一个已经预热过的 ModelClientSession;如果中间工具构建或 websocket 预热失败,就返回错误。

调用关系:它由 Session::schedule_startup_prewarm 创建的后台任务调用,是整个预热流水线的实际工人。它会把工具准备交给 built_tools,把提示词构建交给 build_prompt,最后把网络预热交给模型客户端的 prewarm_websocket。

调用图:调用 2 个内部函数(build_prompt, built_tools);被 1 处调用(schedule_startup_prewarm);外部调用 3 个(new, now, new)。

core/src/compact_remote_v2.rs源码 ↗
orchestrationcompaction during request handling

可以把它想成一次“整理聊天记录”。系统发现上下文太长,或者用户手动要求压缩时,这个文件会先通知外界“压缩开始”,再运行压缩前钩子(钩子就是外部可插入的小检查或拦截步骤)。如果允许继续,它会复制当前历史,先把很占空间的工具调用输出修剪掉,再组装一份提示词发给远端模型。远端模型必须返回一个专门的压缩结果。文件会检查返回流是否正常结束、是否刚好有一个压缩结果,然后把旧历史筛选、截断、拼上新摘要,安装回会话里。它还记录用量、追踪信息和分析数据;如果中途失败,会发错误事件。这里比较重要的一点是:它不会无脑保留所有旧消息,只保留用户、开发者、系统消息里该留的部分,并且优先保留较新的内容。

函数细节22
run_inline_remote_auto_compact_task56–74 ↗
async fn run_inline_remote_auto_compact_task(
    sess: Arc<Session>,
    turn_context: Arc<TurnContext>,
    client_session: &mut ModelClientSession,
    initial_context_injection: InitialContextInje

作用:这是自动压缩时走的入口。它用于正常对话过程中发现上下文快满了,于是顺手做一次远端压缩,而不是开一个独立的新回合。

数据流:进去的是会话、当前回合信息、已经在用的模型连接、是否要插入初始上下文,以及压缩原因和阶段 → 它把这些参数补上“自动触发”的标记 → 出来是压缩成功或失败的结果,本身不直接改历史,而是交给内部主流程去做。

调用关系:它由 run_auto_compact 在需要自动压缩时调用,马上把活儿交给 run_remote_compact_task_inner。这样自动压缩和手动压缩可以共用同一套核心流程。

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

run_remote_compact_task76–99 ↗
async fn run_remote_compact_task(
    sess: Arc<Session>,
    turn_context: Arc<TurnContext>,
) -> CodexResult<()>

作用:这是用户手动要求压缩时走的入口。它会先发出一个“新回合开始”的事件,让界面或上层系统知道现在开始执行一次独立的压缩任务。

数据流:进去的是会话和当前回合信息 → 它创建并发送 TurnStarted 事件,然后把触发方式设为“手动”、原因设为“用户请求” → 出来是压缩任务的成功或错误,同时可能已经向会话发送了开始事件。

调用关系:它由 run 调用,属于手动压缩的外层包装。真正的压缩工作仍然交给 run_remote_compact_task_inner。

调用图:调用 1 个内部函数(run_remote_compact_task_inner);被 1 处调用(run);外部调用 1 个(TurnStarted)。

run_remote_compact_task_inner101–177 ↗
async fn run_remote_compact_task_inner(
    sess: &Arc<Session>,
    turn_context: &Arc<TurnContext>,
    client_session: Option<&mut ModelClientSession>,
    initial_context_injection: InitialContext

作用:这是远端压缩的总调度员。它负责记录分析数据、执行压缩前后钩子、调用真正的压缩实现,并在失败时通知会话。

数据流:进去的是会话、回合、可选的模型连接、上下文注入方式、触发原因和阶段 → 它建立压缩元数据和统计记录,先跑压缩前钩子;如果被拦截就返回中止错误;否则调用实际实现;成功后再跑压缩后钩子;最后记录成功、失败或中断状态 → 出来是成功或 Codex 错误,同时可能发送错误事件并写入分析追踪。

调用关系:它被自动入口 run_inline_remote_auto_compact_task 和手动入口 run_remote_compact_task 共同调用。它把核心工作交给 run_remote_compact_task_inner_impl,并用 run_pre_compact_hooks、run_post_compact_hooks、compaction_status_from_result 和分析 attempt 把整个流程包起来。

调用图:调用 6 个内部函数(begin, compaction_status_from_result, run_remote_compact_task_inner_impl, run_post_compact_hooks, run_pre_compact_hooks, new);被 2 处调用(run_inline_remote_auto_compact_task, run_remote_compact_task);外部调用 2 个(default, Error)。

run_remote_compact_task_inner_impl179–321 ↗
async fn run_remote_compact_task_inner_impl(
    sess: &Arc<Session>,
    turn_context: &Arc<TurnContext>,
    client_session: Option<&mut ModelClientSession>,
    initial_context_injection: InitialCo

作用:这是实际完成“拿旧历史去压缩,并把新历史装回去”的核心函数。它像搬家工人:先打包旧物,再请模型做摘要,最后把精简后的新房间布置好。

数据流:进去的是会话、当前回合、可选模型连接、初始上下文注入方式、压缩元数据和统计容器 → 它创建压缩任务项,复制历史,修剪工具调用历史,组装 Prompt(给模型看的输入),建立追踪信息,调用远端压缩请求;拿到压缩结果后统计 token 用量,用 build_v2_compacted_history 生成保留历史,再用 process_compacted_history 做最终整理,最后替换会话历史并重新计算 token → 出来是成功或错误;成功时会话中的历史已经变成压缩后的版本。

调用关系:它由 run_remote_compact_task_inner 调用,是整个文件的中心。它会调用 trim_function_call_history_to_fit_context_window 先减肥,调用 built_tools 准备模型可见工具,调用 run_remote_compaction_request_v2 和模型通信,调用 build_v2_compacted_history 生成新历史,并调用 process_compacted_history 做安装前加工。

调用图:调用 6 个内部函数(process_compacted_history, trim_function_call_history_to_fit_context_window, build_v2_compacted_history, run_remote_compaction_request_v2, built_tools, new);被 1 处调用(run_remote_compact_task_inner);外部调用 6 个(new, new, ContextCompaction, Compaction, info!, json!)。

run_remote_compaction_request_v2328–376 ↗
async fn run_remote_compaction_request_v2(
    sess: &Session,
    turn_context: &TurnContext,
    client_session: &mut ModelClientSession,
    prompt: &Prompt,
    responses_metadata: &CodexResponses

作用:这个函数专门负责向远端模型发起压缩请求,并处理可重试的网络或流式错误。简单说,它是“打电话给模型,并在临时占线时最多再拨几次”。

数据流:进去的是会话、回合、模型连接、提示词和请求元数据 → 它根据服务商配置决定最多重试几次,调用 client_session.stream 发起流式请求,然后交给 collect_compaction_output 收集结果;如果错误不可重试就直接返回,如果可重试就调用 handle_retryable_response_stream_error 处理并继续下一轮 → 出来是远端压缩输出和 token 用量,或最终错误。

调用关系:它由 run_remote_compact_task_inner_impl 调用,位于“本地组装好请求”和“拿到模型压缩结果”之间。它自己不解析业务含义,而是把流里的内容交给 collect_compaction_output。

调用图:调用 4 个内部函数(stream, collect_compaction_output, handle_retryable_response_stream_error, disabled);被 1 处调用(run_remote_compact_task_inner_impl)。

collect_compaction_output378–426 ↗
async fn collect_compaction_output(
    mut stream: ResponseStream,
) -> CodexResult<RemoteCompactionV2Output>

作用:这个函数从模型的流式回复里挑出真正的压缩结果。它会严格检查:回复必须正常完成,而且必须刚好有一个压缩结果。

数据流:进去的是 ResponseStream,也就是模型一边生成一边发来的事件流 → 它逐个读取事件,数一共有多少输出项、其中有多少 Compaction 输出,并保存第一个压缩输出;看到 Completed 事件后记录 token 用量并停止 → 出来是 RemoteCompactionV2Output;如果流提前断了,或压缩结果不是刚好一个,就返回错误。

调用关系:它由 run_remote_compaction_request_v2 在每次请求成功建立流之后调用。测试 tests::collect_compaction_output_accepts_additional_output_items 也会直接调用它,确认它能忽略额外普通输出,只认压缩项。

调用图:被 2 处调用(run_remote_compaction_request_v2, collect_compaction_output_accepts_additional_output_items);外部调用 5 个(next, format!, Fatal, Stream, unreachable!)。

build_v2_compacted_history428–446 ↗
fn build_v2_compacted_history(
    prompt_input: &[ResponseItem],
    compaction_output: ResponseItem,
) -> (Vec<ResponseItem>, usize)

作用:这个函数把“旧输入”和“新压缩摘要”拼成即将安装回会话的新历史。它决定哪些旧消息还能留下、留下多少,以及最后把压缩摘要放进去。

数据流:进去的是原始提示输入列表和远端返回的压缩输出 → 它先筛出远端压缩后仍值得保留的消息,再过滤掉不该进入压缩历史的项,接着按固定 token 预算截断保留消息,统计留下了多少张输入图片,最后追加压缩输出 → 出来是一份新的历史列表和保留图片数量。

调用关系:它由 run_remote_compact_task_inner_impl 在拿到模型摘要后调用。多个测试也直接调用它,验证过滤形状、先丢弃无用消息再截断、以及图片计数是否正确。

调用图:调用 1 个内部函数(truncate_retained_messages_for_remote_compaction);被 4 处调用(run_remote_compact_task_inner_impl, build_v2_compacted_history_counts_retained_input_images, build_v2_compacted_history_discards_messages_before_truncating, build_v2_compacted_history_filters_to_installed_retention_shape);外部调用 1 个(iter)。

is_retained_for_remote_compaction_v2448–454 ↗
fn is_retained_for_remote_compaction_v2(item: &ResponseItem) -> bool

作用:这个小函数判断一条回复项是否属于远端压缩后可以考虑继续保留的消息类型。它只放行用户、开发者和系统消息。

数据流:进去的是一个 ResponseItem → 它先看这个项是不是 Message;如果不是,直接判定不保留;如果是,再检查 role 是否为 user、developer 或 system → 出来是 true 或 false,不修改输入。

调用关系:它是 build_v2_compacted_history 里的筛选规则之一。它帮主流程避免把助手回答、工具调用、旧压缩结果等不合适的东西混进新历史。

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

retained_input_image_count456–465 ↗
fn retained_input_image_count(item: &ResponseItem) -> usize

作用:这个函数数一条保留下来的消息里有几张输入图片。这个数字会进入压缩分析数据,方便知道压缩后还带着多少图片上下文。

数据流:进去的是一个 ResponseItem → 如果它不是消息,就返回 0;如果是消息,就遍历内容,统计 ContentItem::InputImage 的数量 → 出来是图片数量,不改动原消息。

调用关系:它在 build_v2_compacted_history 生成新历史时使用,用来汇总 retained_image_count。相关测试会确认图片数量统计正确。

truncate_retained_messages_for_remote_compaction467–491 ↗
fn truncate_retained_messages_for_remote_compaction(
    items: Vec<ResponseItem>,
    max_tokens: usize,
) -> Vec<ResponseItem>

作用:这个函数给保留下来的旧消息做“限额裁剪”。它保证这些旧消息大致不超过固定 token 预算,并且优先保留最新消息。

数据流:进去的是一组候选保留消息和最大 token 数 → 它从最新消息往旧消息倒着看,能完整放下就放下;放不下但还有一点空间时,就调用 truncate_message_text_to_token_budget 截短这条消息;空间用完后更旧的消息就不要了;最后再把顺序翻回正常时间顺序 → 出来是裁剪后的消息列表。

调用关系:它由 build_v2_compacted_history 调用,是新历史安装前的安全阀。多组测试直接调用它,确认它优先保留新消息、保留图片、图片消息也会占一个最小名额、预算用完后会丢弃更旧内容。

调用图:调用 2 个内部函数(message_text_token_count, truncate_message_text_to_token_budget);被 5 处调用(build_v2_compacted_history, retained_history_truncation_charges_image_only_messages, retained_history_truncation_drops_image_only_messages_after_budget_is_spent, retained_history_truncation_keeps_newest_messages_first, retained_history_truncation_preserves_images_and_truncates_later_text_parts);外部调用 1 个(with_capacity)。

message_text_token_count493–507 ↗
fn message_text_token_count(item: &ResponseItem) -> usize

作用:这个函数粗略估算一条消息里的文字占多少 token。token 可以理解成模型读文本时用的小块单位,越多越占上下文空间。

数据流:进去的是一个 ResponseItem → 如果不是消息就返回 0;如果是消息,就遍历其中的输入文字和输出文字,用 approx_token_count 粗估 token 数;图片不算文字 token → 出来是总估算值,不修改消息。

调用关系:它只服务于 truncate_retained_messages_for_remote_compaction。后者靠它判断某条消息能不能塞进剩余预算。

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

truncate_message_text_to_token_budget509–559 ↗
fn truncate_message_text_to_token_budget(
    item: ResponseItem,
    max_tokens: usize,
) -> Option<ResponseItem>

作用:这个函数把单条消息的文字内容裁到指定 token 预算内,同时尽量保留图片。它是在“整条消息放不下,但还能放一点”的时候用的。

数据流:进去的是一条 ResponseItem 和最多允许的 token 数 → 如果不是消息,就原样返回;如果是消息,它逐段处理内容:文字段能放就保留,放不下就用 truncate_text 截短,图片段直接保留;如果最后没有任何内容留下,就返回 None → 出来是裁剪后的消息,或表示应丢弃的 None。

调用关系:它由 truncate_retained_messages_for_remote_compaction 在预算不足时调用。它依赖 approx_token_count 估算文字大小,依赖 truncate_text 按 token 策略真正截断文本。

调用图:被 1 处调用(truncate_retained_messages_for_remote_compaction);外部调用 4 个(with_capacity, approx_token_count, truncate_text, Tokens)。

tests::message570–580 ↗
fn message(role: &str, text: &str, phase: Option<MessagePhase>) -> ResponseItem

作用:这是测试里用的小工具,用来快速造一条普通消息。它让测试不用反复手写完整的 ResponseItem 结构。

数据流:进去的是角色名、文字和可选阶段 → 它把这些包装成一个带 InputText 内容的 Message → 出来是一条可用于测试的 ResponseItem。

调用关系:它只在本文件测试中使用,被多个测试调用来构造旧历史、用户消息或助手消息。

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

tests::response_stream582–594 ↗
fn response_stream(events: Vec<CodexResult<ResponseEvent>>) -> ResponseStream

作用:这是测试里用来伪造模型流式回复的小工具。它不用真的连远端模型,就能模拟一串 ResponseEvent。

数据流:进去的是一组测试事件,每个事件可能成功也可能是错误 → 它创建一个异步通道,把事件塞进去,再包装成 ResponseStream → 出来是 collect_compaction_output 可以读取的假流。

调用关系:它服务于 tests::collect_compaction_output_accepts_additional_output_items。那个测试用它来验证流收集函数面对额外输出时仍能正确拿到压缩项。

调用图:外部调用 2 个(new, channel)。

tests::build_v2_compacted_history_filters_to_installed_retention_shape597–628 ↗
fn build_v2_compacted_history_filters_to_installed_retention_shape()

作用:这个测试确认压缩后的历史不会保留不该保留的项目。比如助手回复、工具调用、旧压缩结果都应该被排除。

数据流:进去的是测试构造的一串不同类型历史和一个新的压缩输出 → 它调用 build_v2_compacted_history → 出来用断言检查结果只剩符合安装形状的用户消息和新压缩输出。

调用关系:它直接测试 build_v2_compacted_history,保证该函数的过滤规则符合压缩历史最终要安装回会话的要求。

调用图:调用 1 个内部函数(build_v2_compacted_history);外部调用 2 个(assert_eq!, vec!)。

tests::build_v2_compacted_history_discards_messages_before_truncating631–653 ↗
fn build_v2_compacted_history_discards_messages_before_truncating()

作用:这个测试确认系统会先丢掉不应保留的巨大上下文消息,再做长度截断。这样无用的大块文本不会挤掉真正该留下的新旧用户消息。

数据流:进去的是包含旧用户消息、超长开发者消息、超长环境上下文消息和新用户消息的测试输入 → 它调用 build_v2_compacted_history → 出来断言最终只保留旧用户消息、新用户消息和压缩输出。

调用关系:它直接测试 build_v2_compacted_history,重点覆盖“先过滤、后截断”的顺序,避免截断预算被无关内容耗光。

调用图:调用 1 个内部函数(build_v2_compacted_history);外部调用 4 个(assert_eq!, message, format!, vec!)。

tests::build_v2_compacted_history_counts_retained_input_images656–684 ↗
fn build_v2_compacted_history_counts_retained_input_images()

作用:这个测试确认保留下来的图片会被正确计数。图片数量会用于压缩分析,所以不能漏算。

数据流:进去的是一条带一段文字和两张输入图片的用户消息,以及一个压缩输出 → 它调用 build_v2_compacted_history → 出来断言 retained_image_count 等于 2。

调用关系:它直接测试 build_v2_compacted_history 和其中的 retained_input_image_count 统计效果。

调用图:调用 1 个内部函数(build_v2_compacted_history);外部调用 2 个(assert_eq!, vec!)。

tests::retained_history_truncation_keeps_newest_messages_first687–706 ↗
fn retained_history_truncation_keeps_newest_messages_first()

作用:这个测试确认裁剪旧消息时优先保留最新内容。因为越新的聊天通常越可能对下一步回答有用。

数据流:进去的是旧、中、新三条用户消息和很小的 token 预算 → 它调用 truncate_retained_messages_for_remote_compaction → 出来断言最新消息完整保留,中间消息被截短,最旧消息被丢弃。

调用关系:它直接测试 truncate_retained_messages_for_remote_compaction,确保倒序挑选再恢复顺序的策略没有写错。

调用图:调用 1 个内部函数(truncate_retained_messages_for_remote_compaction);外部调用 3 个(assert_eq!, message, vec!)。

tests::retained_history_truncation_preserves_images_and_truncates_later_text_parts709–753 ↗
fn retained_history_truncation_preserves_images_and_truncates_later_text_parts()

作用:这个测试确认一条消息里既有文字又有图片时,裁剪会保留图片,并按预算截短文字部分。这样图片上下文不会因为文字超长而轻易丢掉。

数据流:进去的是一条包含输入文字、输入图片、输出文字的消息和有限 token 预算 → 它调用 truncate_retained_messages_for_remote_compaction → 出来断言第一段文字和图片保留,后面的文字按预算被截短。

调用关系:它直接测试 truncate_retained_messages_for_remote_compaction,间接覆盖 truncate_message_text_to_token_budget 对混合内容的处理。

调用图:调用 1 个内部函数(truncate_retained_messages_for_remote_compaction);外部调用 2 个(assert_eq!, vec!)。

tests::retained_history_truncation_charges_image_only_messages756–778 ↗
fn retained_history_truncation_charges_image_only_messages()

作用:这个测试确认纯图片消息虽然没有文字 token,也会占用一个最小预算名额。否则大量图片消息可能绕过文字预算限制。

数据流:进去的是旧文字消息、纯图片消息、最新文字消息和预算 2 → 它调用 truncate_retained_messages_for_remote_compaction → 出来断言最新文字消息和纯图片消息保留,旧文字消息被丢弃。

调用关系:它直接测试 truncate_retained_messages_for_remote_compaction 中 message_text_token_count 结果至少按 1 计费的规则。

调用图:调用 1 个内部函数(truncate_retained_messages_for_remote_compaction);外部调用 3 个(assert_eq!, message, vec!)。

tests::retained_history_truncation_drops_image_only_messages_after_budget_is_spent781–799 ↗
fn retained_history_truncation_drops_image_only_messages_after_budget_is_spent()

作用:这个测试确认预算用完后,即使是纯图片消息也不能继续保留。这样截断规则不会因为图片没有文字 token 而失控。

数据流:进去的是纯图片消息、最新文字消息和预算 1 → 它调用 truncate_retained_messages_for_remote_compaction → 出来断言只保留最新文字消息,较旧的纯图片消息被丢弃。

调用关系:它直接测试 truncate_retained_messages_for_remote_compaction 的预算耗尽分支,和上一条图片计费测试互相补充。

调用图:调用 1 个内部函数(truncate_retained_messages_for_remote_compaction);外部调用 3 个(assert_eq!, message, vec!)。

tests::collect_compaction_output_accepts_additional_output_items802–842 ↗
async fn collect_compaction_output_accepts_additional_output_items()

作用:这个测试确认模型流里即使夹着额外普通输出,只要有且仅有一个压缩输出并正常完成,就能被接受。

数据流:进去的是伪造的事件流:先有一条普通助手输出,再有一条 Compaction 输出,最后是 Completed 和 token 用量 → 它调用 collect_compaction_output → 出来断言拿到的压缩输出和 token 用量都正确。

调用关系:它用 tests::response_stream 造假流,直接测试 collect_compaction_output。这个测试保护了远端模型偶尔多给普通输出时的兼容行为。

调用图:调用 1 个内部函数(collect_compaction_output);外部调用 3 个(assert_eq!, response_stream, vec!)。

ext/image-generation/src/backend.rs源码 ↗
io_transportrequest handling

图片生成不是本地凭空完成的,而是要把请求发到当前配置的模型服务商那里。这个文件里的 CodexImagesBackend 就像一个办事窗口:外面只需要说“帮我生成图”或“帮我改图”,它会先去 SharedModelProvider 里查当前应该用哪个 API 服务商、用什么认证信息,也就是“去哪办”和“凭什么身份办”。然后它用 reqwest 这个网络工具创建 ImagesClient,再把图片生成或编辑请求发出去。这里有个重要点:每次 generate 或 edit 都会重新解析当前服务商和登录信息,所以如果用户切换了配置,后续请求能跟着变化。HeaderMap::new() 表示这类请求没有额外加特殊 HTTP 头,只用客户端和认证信息来完成调用。

函数细节4
CodexImagesBackend::new17–19 ↗
fn new(provider: SharedModelProvider) -> Self

作用:创建一个图片后端对象,把“当前模型服务商”的共享入口保存起来。之后生成图、编辑图时,都靠它找到该发往哪里、用什么账号。

数据流:进去的是一个 SharedModelProvider,也就是能查询当前 API 服务商和认证信息的共享对象。函数只是把它放进 CodexImagesBackend 结构里。出来的是一个可复用的图片后端实例,没有立刻发网络请求。

调用关系:它通常在图片功能初始化时被调用,先把后端搭好。后面的 CodexImagesBackend::generate 和 CodexImagesBackend::edit 会借助这个实例继续工作,但这个函数本身不调用别的业务步骤。

CodexImagesBackend::client22–38 ↗
async fn client(&self) -> Result<ImagesClient<ReqwestTransport>, String>

作用:为一次图片 API 请求临时准备好真正能发网络请求的客户端。它会查清楚服务商地址和认证信息,避免请求发错地方或没带登录凭证。

数据流:进去的是已有的 CodexImagesBackend,它里面有 provider。函数先从 provider 读取当前 API 服务商,再读取认证信息;如果读取失败,就把错误转成普通字符串返回。成功后,它用 build_reqwest_client 创建底层网络客户端,再包装成 ReqwestTransport,最后组装出 ImagesClient。出来的是一个可以调用图片生成、图片编辑接口的客户端。

调用关系:CodexImagesBackend::generate 和 CodexImagesBackend::edit 在真正发请求前都会先调用它。它自己会调用外部的 api_provider、api_auth 来取配置和身份,也会调用 build_reqwest_client 以及若干 new 函数来搭起网络发送工具。

调用图:调用 3 个内部函数(new, new, build_reqwest_client);被 2 处调用(edit, generate);外部调用 2 个(api_auth, api_provider)。

CodexImagesBackend::generate41–50 ↗
async fn generate(
        &self,
        request: ImageGenerationRequest,
    ) -> Result<ImageResponse, String>

作用:发送一条“从文字或参数生成图片”的请求,并拿回图片 API 的响应。外部调用者不需要关心服务商、认证和网络客户端怎么拼起来。

数据流:进去的是 ImageGenerationRequest,也就是生成图片所需的请求内容。函数先调用 CodexImagesBackend::client 拿到可用的 ImagesClient,然后把请求和一个空的 HeaderMap 交给客户端的 generate 方法。成功时出来的是 ImageResponse,里面是图片接口返回的结果;失败时出来的是错误字符串。它不会修改请求本身。

调用关系:它会被更上层的 handle_call 调用,通常表示用户或工具发起了“生成图片”的动作。它把准备客户端的工作交给 CodexImagesBackend::client,再把真正的远程调用交给 ImagesClient 的 generate。

调用图:调用 1 个内部函数(client);被 1 处调用(handle_call);外部调用 1 个(new)。

CodexImagesBackend::edit53–59 ↗
async fn edit(&self, request: ImageEditRequest) -> Result<ImageResponse, String>

作用:发送一条“编辑已有图片”的请求,并拿回图片 API 的响应。它让上层只管提交编辑需求,不必处理网络和登录细节。

数据流:进去的是 ImageEditRequest,也就是要编辑哪张图、怎么编辑等信息。函数先通过 CodexImagesBackend::client 准备好图片 API 客户端,再把请求和空的 HeaderMap 交给客户端的 edit 方法。成功时出来的是 ImageResponse;如果准备客户端或远程调用失败,就把错误转成字符串返回。

调用关系:它会被上层的 handle_call 调用,通常对应一次“编辑图片”的工具请求。它先依赖 CodexImagesBackend::client 解决服务商和认证问题,然后把实际编辑请求交给 ImagesClient 的 edit 去发出。

调用图:调用 1 个内部函数(client);被 1 处调用(handle_call);外部调用 1 个(new)。

实时 WebSocket 协议栈

这些文件组织实时 WebSocket 模块,定义特定版本的消息形状和解析器,并提供统一的 WebSocket 传输方法。

codex-api/src/endpoint/realtime_websocket/mod.rs源码 ↗
othercross-cutting

实时 WebSocket 可以理解成一条一直开着的双向通道,客户端和服务端能不停地互相发消息。这个文件本身不处理消息,也不写具体算法;它的作用更像一本书的目录页。它声明了这一目录下有哪些子模块,比如不同版本的方法实现、通用方法、协议解析、协议版本等。这样代码可以按职责拆开,不会全挤在一个大文件里。同时,它把常用的几个名字公开出去,例如连接对象、客户端对象、事件流、写入器、会话配置、输出模式等。外部代码不需要知道这些东西具体藏在哪个子文件里,只要从这个模块入口拿就行。这样做的好处是:内部文件结构以后可以调整,但对外使用方式尽量保持稳定。

codex-api/src/endpoint/realtime_websocket/methods_common.rs源码 ↗
orchestrationrequest handling / cross-cutting

实时 WebSocket 就像一条正在通话的线路,但这条线路有两个“方言”:老版 V1 和新版 RealtimeV2。这个文件的作用,就是让上层代码只说“我要发一条用户消息”“我要更新会话”“我要返回函数调用结果”,不用关心底层到底是哪种方言。它会根据 event_parser(事件解析器,也就是判断当前连接用哪套协议的标记)选择对应的 V1 或 V2 实现。这里还有一个重要兼容点:V1 不支持完整的会话模式选择,所以会被统一当成 Conversational(对话模式)。另外,V1 返回函数调用结果时,会把文本包装成带有 “Agent Final Message” 前缀的交接消息;V2 则直接使用专门的函数调用输出消息。整体看,它像一个插座转换头:外面接口统一,里面按版本接到不同线路。

函数细节6
normalized_session_mode24–32 ↗
fn normalized_session_mode(
    event_parser: RealtimeEventParser,
    session_mode: RealtimeSessionMode,
) -> RealtimeSessionMode

作用:这个函数把会话模式整理成当前协议真正能用的模式。尤其是老版 V1 不认多种模式,所以这里强制把它变成普通对话模式,防止后面生成不兼容的请求。

数据流:进去的是当前协议版本和调用方想要的会话模式。函数先看协议版本:如果是 V1,就丢掉原来的模式并改成 Conversational;如果是 RealtimeV2,就保留原来的模式。出来的是一个已经适配好协议版本的会话模式。

调用关系:它会在发送会话更新时被 send_session_update 使用,也会被本文件的 session_update_session 先调用一次。它的位置很靠前,像过安检一样,先把不适合当前协议的设置拦下来或修正掉。

调用图:被 2 处调用(send_session_update, session_update_session)。

conversation_item_create_message34–43 ↗
fn conversation_item_create_message(
    event_parser: RealtimeEventParser,
    text: String,
    role: ConversationTextRole,
) -> RealtimeOutboundMessage

作用:这个函数用来生成“往对话里新增一条文字消息”的 WebSocket 出站消息。调用方只需要给文字和角色,比如用户或助手,它会自动选 V1 或 V2 的消息格式。

数据流:进去的是协议版本、消息文本、说话角色。函数根据协议版本分流:V1 就交给 V1 的 conversation_item_create_message,RealtimeV2 就交给 V2 的 conversation_item_create_message。出来的是一条可以发到 WebSocket 的 RealtimeOutboundMessage。

调用关系:它被 send_conversation_item_create 调用,处在“准备发送消息”这一步。它自己不直接发网络包,而是把具体造消息的活交给对应版本的实现,保证上层发送流程不用知道协议细节。

调用图:调用 2 个内部函数(conversation_item_create_message, conversation_item_create_message);被 1 处调用(send_conversation_item_create)。

conversation_function_call_output_message45–59 ↗
fn conversation_function_call_output_message(
    event_parser: RealtimeEventParser,
    call_id: String,
    output_text: String,
) -> RealtimeOutboundMessage

作用:这个函数用来把一次函数调用的结果变成可发送的实时消息。新版协议有专门的函数输出消息;老版协议没有,所以这里用兼容方式把结果包装成一条交接消息。

数据流:进去的是协议版本、函数调用的 call_id、要返回的输出文本。V1 路径会先用固定前缀 “Agent Final Message” 加到输出文本前面,再调用 conversation_handoff_append_message;RealtimeV2 路径直接调用 conversation_function_call_output_message。出来的是一条适合当前协议的出站消息。

调用关系:它被 send_conversation_function_call_output 调用,发生在系统要把工具或函数执行结果回传给实时会话时。它是一个兼容层:V2 走正式通道,V1 走“伪装成交接文本”的通道。

调用图:调用 2 个内部函数(conversation_handoff_append_message, conversation_function_call_output_message);被 1 处调用(send_conversation_function_call_output);外部调用 1 个(format!)。

session_update_session61–75 ↗
fn session_update_session(
    event_parser: RealtimeEventParser,
    instructions: String,
    session_mode: RealtimeSessionMode,
    output_modality: RealtimeOutputModality,
    voice: RealtimeVoice

作用:这个函数用来构造“更新实时会话设置”的内容,比如系统指令、输出方式、声音和会话模式。它会先把设置调整到当前协议能接受的形态,再交给对应版本生成最终结构。

数据流:进去的是协议版本、指令文本、会话模式、输出模态和语音。它先调用 normalized_session_mode 修正会话模式;然后如果是 V1,只把指令和语音交给 V1 的 session_update_session;如果是 RealtimeV2,则把指令、会话模式、输出模态和语音都交给 V2 的 session_update_session。出来的是一个 SessionUpdateSession 结构,表示要更新的会话配置。

调用关系:它被 send_session_update 和 session_update_session_json 使用,是会话更新流程里的核心组装步骤。它上接调用方给出的配置,下接 V1 或 V2 的具体消息格式生成函数。

调用图:调用 3 个内部函数(normalized_session_mode, session_update_session, session_update_session);被 2 处调用(send_session_update, session_update_session_json)。

session_update_session_json77–88 ↗
fn session_update_session_json(config: RealtimeSessionConfig) -> JsonResult<Value>

作用:这个函数把一整份实时会话配置变成 JSON 值。JSON 是一种常见的数据文本格式,方便通过网络发送或记录。

数据流:进去的是 RealtimeSessionConfig,里面包含协议版本、指令、模式、输出方式、声音、会话 id 和模型名。函数先调用 session_update_session 生成会话更新结构,再把 session_id 和 model 补进去,最后用 to_value 转成 JSON。出来的是一个 JSON 结果;如果转换失败,就返回错误。

调用关系:它是把内部配置变成可传输数据的最后一步之一。它依赖 session_update_session 先做协议适配,然后把结果交给 serde_json 的 to_value 做序列化,也就是把程序里的结构变成 JSON 数据。

调用图:调用 1 个内部函数(session_update_session);外部调用 1 个(to_value)。

websocket_intent90–95 ↗
fn websocket_intent(event_parser: RealtimeEventParser) -> Option<&'static str>

作用:这个函数返回建立 WebSocket 连接时需要带上的 intent(可以理解成“连接目的”)字符串。不同协议版本可能需要不同的目的声明,也可能不需要。

数据流:进去的是协议版本。函数根据版本分流:V1 就调用 V1 的 websocket_intent,RealtimeV2 就调用 V2 的 websocket_intent。出来的是一个可选的静态字符串;有值表示连接 URL 里要带这个目的,没有值表示不用带。

调用关系:它被 websocket_url_from_api_url 调用,通常发生在拼 WebSocket 地址的时候。它不负责拼完整 URL,只负责告诉上层:当前协议是否需要额外声明连接意图。

调用图:调用 2 个内部函数(websocket_intent, websocket_intent);被 1 处调用(websocket_url_from_api_url)。

codex-api/src/endpoint/realtime_websocket/methods_v1.rs源码 ↗
io_transportrequest handling / session setup

实时 WebSocket 可以理解成一条一直开着的双向通道,客户端和服务端可以随时互发消息。这个文件不直接发网络请求,而是负责生成要发出去的“标准消息”。比如用户或助手的一段文字,会被包装成“创建一条对话消息”;函数调用交接时产生的文本,会被包装成“追加到 handoff(交接任务)里”;开始或更新会话时,会生成一份会话配置,里面写清楚指令、语音、音频格式等。这里还固定使用 quicksilver 这个会话类型和意图,并把输入音频设成 PCM(一种原始音频数据格式)以及统一采样率。它重要的地方在于:实时接口通常很挑格式,字段放错、少放,服务端就可能不认;有了这些小函数,其他代码只要传入真正关心的内容,就能得到结构正确的协议消息。

函数细节4
conversation_item_create_message18–32 ↗
fn conversation_item_create_message(
    text: String,
    role: ConversationTextRole,
) -> RealtimeOutboundMessage

作用:把一段文字和它的说话身份,做成一条“新增对话消息”的外发消息。别人想往实时会话里塞一条用户或助手文本时,会用它来保证格式正确。

数据流:进去的是一段 text 文字和 role 角色;它把这些内容放进一个 Message 类型的对话条目里,并标明内容类型是输入文本;出来的是一个 RealtimeOutboundMessage,也就是准备通过实时通道发给服务端的消息,函数本身不修改外部状态。

调用关系:调用图里显示它会构造 Message,并用 vec! 建一个只含一段内容的列表;也显示它由同名流程调用,说明在这一版实时方法的封装里,它是“把普通文本变成协议消息”的那一步。后面的网络发送代码可以直接拿它的结果往 WebSocket 通道里送。

调用图:被 1 处调用(conversation_item_create_message);外部调用 2 个(Message, vec!)。

conversation_handoff_append_message34–42 ↗
fn conversation_handoff_append_message(
    handoff_id: String,
    output_text: String,
) -> RealtimeOutboundMessage

作用:把某次 handoff(一次交接或转交中的任务)的输出文字,包装成“追加到这次交接里”的消息。这样函数调用或外部步骤产生的结果,可以继续接到实时对话流程中。

数据流:进去的是 handoff_id,也就是要追加到哪次交接,以及 output_text,也就是要追加的文本;它把两者放进 ConversationHandoffAppend 消息;出来的是一个 RealtimeOutboundMessage,表示一条可发送的追加消息,不会额外读写别的数据。

调用关系:调用图显示它会被 conversation_function_call_output_message 使用。也就是说,当系统拿到某个函数调用的输出后,上层函数会请它把输出文字装成 handoff 追加消息,再交给实时通道发送。

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

session_update_session44–72 ↗
fn session_update_session(
    instructions: String,
    voice: RealtimeVoice,
) -> SessionUpdateSession

作用:生成一份“更新实时会话设置”的配置,主要告诉服务端:这次会话按什么指令工作、用什么声音说话、音频输入输出怎么设。启动或调整实时语音会话时会用到它。

数据流:进去的是 instructions 指令文本和 voice 语音选择;它创建一个 SessionUpdateSession,把会话类型设为 Quicksilver,把指令填进去,把输入音频格式设为 PCM,并使用统一的实时音频采样率,同时把输出语音设成传入的 voice;出来的是完整的会话更新对象。很多字段填 None,意思是这里不指定,让别处或服务端使用默认值。

调用关系:调用图显示它由同名会话更新流程调用。它处在会话准备阶段:上层决定要更新会话后,把指令和声音交给它;它只负责拼好配置对象,之后再由发送层把这个对象变成真正的 WebSocket 消息。

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

websocket_intent74–76 ↗
fn websocket_intent() -> Option<&'static str>

作用:返回这条 WebSocket 连接的意图标记,也就是告诉外部:这里要走 quicksilver 这类实时能力。它像在连接请求上贴一个标签,方便服务端或路由层识别。

数据流:它不需要输入,也不读取外部状态;每次调用都返回 Some("quicksilver"),表示有明确的意图字符串;它不会改动任何东西。

调用关系:调用图显示它由同名意图获取流程调用。它通常会在建立或配置 WebSocket 连接时被询问,用来给连接带上 quicksilver 标识,让后续会话更新和消息格式与这一版实时协议保持一致。

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

codex-api/src/endpoint/realtime_websocket/methods_v2.rs源码 ↗
io_transportwebsocket session setup and message sending

可以把这个文件想成“翻译员”和“表格填写员”。程序内部只知道:我要发一段用户文字、我要回传一个工具执行结果、我要开一个实时语音会话。但外部的 Realtime WebSocket 接口要求消息必须长成固定格式,比如消息类型是什么、角色是谁、音频采样率是多少、是否要自动判断用户说完了没。这个文件就负责把这些信息填进正确的协议结构里。它还区分两种会话:一种是正常对话,会配置语音输入输出、降噪、转写、自动打断,以及两个工具:background_agent 用来把用户请求交给后台代理,remain_silent 用来明确表示“此时不要说话”;另一种是纯转写,只保留音频输入和转写模型,不配置输出和工具。这样其他代码不用反复记协议细节,只要调用这里的函数就能生成合格消息。

函数细节5
conversation_item_create_message39–53 ↗
fn conversation_item_create_message(
    text: String,
    role: ConversationTextRole,
) -> RealtimeOutboundMessage

作用:把一段文字和说话人的身份,做成一条可以发给实时接口的“会话消息”。有人想把用户输入或系统文字塞进对话历史时,就会用它。

数据流:进去的是一段文字 text 和一个角色 role,比如用户或助手 → 它把文字放进消息内容里,标成 input_text,再外面套上“创建会话项”的协议外壳 → 出来的是一个 RealtimeOutboundMessage,可以直接交给 WebSocket 发送层发出去;它不改动别的状态。

调用关系:它处在“准备发消息”的最后包装步骤。调用图里把它记录为同名入口的使用点;它内部只借助协议里的 Message 结构和列表容器,把原始文字变成标准会话消息,不再把工作交给本文件其他函数。

调用图:被 1 处调用(conversation_item_create_message);外部调用 2 个(Message, vec!)。

conversation_function_call_output_message55–66 ↗
fn conversation_function_call_output_message(
    call_id: String,
    output_text: String,
) -> RealtimeOutboundMessage

作用:把某个工具调用的执行结果,包装成实时接口要求的“函数调用输出”消息。模型之前要求调用工具,程序拿到结果后,就靠它把结果送回去。

数据流:进去的是 call_id,也就是要回应哪一次工具调用,以及 output_text,也就是工具执行后的文字结果 → 它把这两样放进 FunctionCallOutput 格式里,再包成“创建会话项”的外发消息 → 出来的是一个 RealtimeOutboundMessage;它不会自己发送,也不会保存结果。

调用关系:它连接“工具已经跑完”和“把结果告诉模型”这两个步骤。调用图里它同样被记录为同名入口的使用点;内部主要交给协议里的 FunctionCallOutput 数据结构来装结果。

调用图:被 1 处调用(conversation_function_call_output_message);外部调用 1 个(FunctionCallOutput)。

session_update_session68–162 ↗
fn session_update_session(
    instructions: String,
    session_mode: RealtimeSessionMode,
    output_modality: RealtimeOutputModality,
    voice: RealtimeVoice,
) -> SessionUpdateSession

作用:生成一份“实时会话应该怎么工作”的配置。开新会话或更新会话时,用它告诉服务端:这是语音对话还是只做转写、要不要声音输出、用什么声音、能调用哪些工具。

数据流:进去的是 instructions 指令、session_mode 会话模式、output_modality 输出形式,以及 voice 声音 → 如果是 Conversational 对话模式,它会填入指令、输出形式、输入输出音频格式、降噪、语音转文字、服务器端自动判断停顿、后台代理工具和静默工具;如果是 Transcription 转写模式,它只配置音频输入和转写模型,不配置回答、工具或声音输出 → 出来的是一个 SessionUpdateSession 配置对象,供外层发送给实时服务端。

调用关系:这是本文件最核心的“会话配置生成器”,通常在 WebSocket 会话建立或切换模式时被调用。调用图显示它会创建列表;从源码看,它还会借助 output_modality_value 把程序内部的输出枚举翻译成协议字符串。它不负责真正联网,只负责把配置单填对。

调用图:被 1 处调用(session_update_session);外部调用 1 个(vec!)。

output_modality_value164–169 ↗
fn output_modality_value(output_modality: RealtimeOutputModality) -> &'static str

作用:把程序内部的输出类型,翻译成接口协议要的英文字符串。比如内部说“文字”或“音频”,外部接口要看到的是 text 或 audio。

数据流:进去的是 RealtimeOutputModality,也就是输出形式的枚举值 → 它判断是 Text 还是 Audio → 出来的是固定字符串 text 或 audio;没有副作用,也不读取外部状态。

调用关系:它是 session_update_session 里的一个小翻译工具,专门避免在大函数里到处硬写字符串。这样如果协议字符串以后要改,相关知识也集中在这里。

websocket_intent171–173 ↗
fn websocket_intent() -> Option<&'static str>

作用:返回这个 v2 WebSocket 连接是否需要额外声明某种“意图”。当前它明确返回 None,意思是不加特殊意图标记。

数据流:进去没有参数 → 它不读取配置,也不做判断 → 出来固定是 None,表示没有额外的 intent 值;它不改动任何东西。

调用关系:它给外层连接建立流程提供一个统一接口:别的版本可能需要返回某个意图字符串,而这个 v2 版本不需要。调用图里它被记录为同名入口的使用点;实际工作就是告诉调用方“这里没有额外设置”。

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

codex-api/src/endpoint/realtime_websocket/protocol_v1.rs源码 ↗
io_transportwebsocket message handling

这个文件像一个“翻译员”。实时 WebSocket 可以理解成一条一直开着的网络通道,外部会不断发来 JSON 格式的小纸条。每张纸条都有一个类型,比如“音频片段来了”“字幕更新了”“会话设置变了”“出错了”。这里的核心函数会先把原始文字解析成 JSON,再看消息类型,然后转成统一的 RealtimeEvent。这样后面的系统不用关心外部协议里字段叫 delta 还是 data、channels 还是 num_channels,只要处理内部事件就行。它还会兼容一些旧字段名或不同事件名。遇到不认识的事件,它不会让程序崩掉,而是记一条调试日志,然后返回空结果,表示这条消息暂时忽略。

函数细节2
parse_realtime_event_v112–91 ↗
fn parse_realtime_event_v1(payload: &str) -> Option<RealtimeEvent>

作用:把实时协议 v1 发来的一整段 JSON 字符串,翻译成程序内部的 RealtimeEvent。别人会在收到 WebSocket 消息时调用它,好让后续代码能用统一格式处理音频、字幕、错误和会话事件。

数据流:进去的是一段原始文本 payload。它先交给 parse_realtime_payload,把文本变成 JSON,并取出消息类型;然后按类型分流:会话更新交给 parse_session_updated_event,字幕增量和完成消息交给对应的字幕解析函数,错误交给 parse_error_event,音频片段则从 JSON 里取出音频数据、采样率和声道数,组装成 RealtimeAudioFrame。最后出来的是 Some(RealtimeEvent),表示成功认出一条内部事件;如果缺关键字段、格式不对或类型不支持,就出来 None,并可能写一条 debug 调试日志。

调用关系:它是这个 v1 协议文件的总入口,由更外层的 parse_realtime_event 在需要解析实时消息时调用。它自己不把所有细节都写死,而是把通用解析工作交给 protocol_common 里的几个函数;只有 v1 特有的字段兼容和事件分支在这里处理。遇到 conversation.item.done 时,它会把更小的提取工作交给 parse_conversation_item_done_event。

调用图:调用 6 个内部函数(parse_error_event, parse_realtime_payload, parse_session_updated_event, parse_transcript_delta_event, parse_transcript_done_event, parse_conversation_item_done_event);被 1 处调用(parse_realtime_event);外部调用 4 个(new, debug!, AudioOut, HandoffRequested)。

parse_conversation_item_done_event93–99 ↗
fn parse_conversation_item_done_event(parsed: &Value) -> Option<RealtimeEvent>

作用:专门解析“某个对话条目完成了”这类消息,从里面找出完成的是哪一个条目。它的作用很小但很关键,因为后续代码需要知道具体 item_id,才能把对应内容标记为结束。

数据流:进去的是已经解析好的 JSON。它先找 item 这个对象,再从 item 里找 id 字段,并把这个 id 转成字符串。成功时出来的是 RealtimeEvent::ConversationItemDone,里面带着 item_id;如果 item 不存在、不是对象,或者没有字符串形式的 id,就出来 None,不制造错误事件。

调用关系:它只在 parse_realtime_event_v1 识别到 conversation.item.done 类型时被调用。也就是说,大函数负责判断“这是什么消息”,这个小函数负责从这种消息里安全地拿到最重要的编号。

调用图:被 1 处调用(parse_realtime_event_v1);外部调用 1 个(get)。

codex-api/src/endpoint/realtime_websocket/protocol_v2.rs源码 ↗
io_transportrequest handling

可以把这个文件想成“翻译员”:WebSocket(一条服务器和外部服务持续通话的网络连接)收到一段 JSON 文本后,它先看消息的 type,也就是“这封信是什么类型”。如果是会话更新、音频片段、文字转写、回答完成、错误等常见事件,它就交给对应的小解析函数,把外部格式变成统一的 RealtimeEvent。这里还处理两个特别重要的工具调用:background_agent 表示模型想把任务交给后台代理继续做;remain_silent 表示模型明确要求保持沉默。文件也会照顾不同字段写法,比如 response id 可能藏在 response.id,也可能直接叫 response_id;音频缺少采样率时会用默认值 24000Hz、单声道。遇到不认识的 v2 事件,它不会让程序崩掉,而是记录调试日志并忽略。

函数细节7
parse_realtime_event_v224–79 ↗
fn parse_realtime_event_v2(payload: &str) -> Option<RealtimeEvent>

作用:这是本文件的总入口,专门解析实时协议 v2 的一条原始消息。别人把 WebSocket 收到的字符串交给它,它判断消息类型,再变成内部统一的 RealtimeEvent。

数据流:进去的是一段 JSON 字符串。它先调用 parse_realtime_payload 把字符串拆成 JSON 数据和消息类型;然后按类型分流:音频交给 parse_output_audio_delta_event,文字转写交给通用转写解析函数,会话更新、错误也交给公共函数,conversation.item.done 再交给 parse_conversation_item_done_event。出来的是一个可选的 RealtimeEvent;如果消息坏了或类型不支持,就出来 None,并且对未知类型写一条调试日志。

调用关系:它被上层的 parse_realtime_event 调用,是 v2 协议消息进入内部事件系统的关口。它自己不把所有细节都做完,而是像前台分诊员一样,把不同消息派给 parse_error_event、parse_session_updated_event、parse_transcript_delta_event、parse_transcript_done_event、parse_output_audio_delta_event、parse_conversation_item_done_event 和 parse_response_event_response_id 等函数。

调用图:调用 8 个内部函数(parse_error_event, parse_realtime_payload, parse_session_updated_event, parse_transcript_delta_event, parse_transcript_done_event, parse_conversation_item_done_event, parse_output_audio_delta_event, parse_response_event_response_id);被 1 处调用(parse_realtime_event);外部调用 5 个(debug!, InputAudioSpeechStarted, ResponseCancelled, ResponseCreated, ResponseDone)。

parse_response_event_response_id81–94 ↗
fn parse_response_event_response_id(parsed: &Value) -> Option<String>

作用:这个函数专门从“响应相关事件”里找 response id,也就是某次回答的编号。因为外部消息可能用两种不同位置放这个编号,所以它负责兼容这些写法。

数据流:进去的是已经解析好的 JSON 值。它先尝试找 response.id;如果没有,再尝试找顶层的 response_id。找到字符串就复制成一个 String 返回;找不到就返回 None,不会报错。

调用关系:它只由 parse_realtime_event_v2 在处理 response.created、response.cancelled、response.done 这类事件时调用。这样主解析函数不用关心编号到底藏在哪个字段里,只拿到整理好的结果。

调用图:被 1 处调用(parse_realtime_event_v2);外部调用 1 个(get)。

parse_output_audio_delta_event96–125 ↗
fn parse_output_audio_delta_event(parsed: &Value) -> Option<RealtimeEvent>

作用:这个函数把服务端发来的一小段输出音频,整理成内部的音频帧事件。音频帧可以理解为一小包声音数据,后面可以继续播放或转发。

数据流:进去的是一条 JSON 音频消息。它必须先拿到 delta 字段里的音频数据;然后读取 sample_rate(采样率,也就是每秒多少个声音采样点)、channels 或 num_channels(声道数),缺失时分别用 24000 和 1;还会读取 samples_per_channel 和 item_id。最后出来的是 RealtimeEvent::AudioOut,里面包着 RealtimeAudioFrame;如果连音频数据都没有,就返回 None。

调用关系:它由 parse_realtime_event_v2 在遇到 response.output_audio.delta 或 response.audio.delta 时调用。它把协议里的零散字段拼成 AudioOut 事件,后面的播放或处理模块就不需要再懂外部 JSON 的细节。

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

parse_conversation_item_done_event127–140 ↗
fn parse_conversation_item_done_event(parsed: &Value) -> Option<RealtimeEvent>

作用:这个函数处理“某个对话条目完成了”的消息。它不只是报告完成,还会检查这个条目是不是模型发起的特殊工具请求,比如交给后台代理或要求保持沉默。

数据流:进去的是一条 JSON 消息。它先取出 item 对象;如果 item 像 background_agent 工具调用,就交给 parse_handoff_requested_event 生成交接请求;如果像 remain_silent 工具调用,就交给 parse_noop_requested_event 生成静默请求;如果都不是,就尝试取 item.id,返回普通的 ConversationItemDone 事件。取不到必要字段时返回 None。

调用关系:它由 parse_realtime_event_v2 在遇到 conversation.item.done 时调用。它再把两种特殊工具调用分别交给 parse_handoff_requested_event 和 parse_noop_requested_event,只有都不匹配时才按普通完成事件处理。

调用图:调用 2 个内部函数(parse_handoff_requested_event, parse_noop_requested_event);被 1 处调用(parse_realtime_event_v2);外部调用 1 个(get)。

parse_handoff_requested_event142–166 ↗
fn parse_handoff_requested_event(item: &JsonMap<String, Value>) -> Option<RealtimeEvent>

作用:这个函数识别模型是否在请求“把任务交给后台代理”。这很重要,因为它把一次普通对话里的工具调用,变成系统内部明确的 HandoffRequested 事件。

数据流:进去的是 conversation item 里的 JSON 对象。它先检查 type 必须是 function_call,name 必须是 background_agent;不符合就返回 None。符合时,它取 call_id 或 id 作为交接编号,取 id 作为条目编号,再从 arguments 字符串里调用 extract_input_transcript 提取用户输入内容。最后出来的是 RealtimeEvent::HandoffRequested,里面带着 handoff_id、item_id、输入文本和一个暂时为空的活动转写列表。

调用关系:它由 parse_conversation_item_done_event 调用,专门负责识别 background_agent 这个工具名。它还会把 arguments 的文本整理工作交给 extract_input_transcript,自己只负责确认“这是不是一次交接请求”并组装事件。

调用图:调用 1 个内部函数(extract_input_transcript);被 1 处调用(parse_conversation_item_done_event);外部调用 3 个(get, new, HandoffRequested)。

parse_noop_requested_event168–189 ↗
fn parse_noop_requested_event(item: &JsonMap<String, Value>) -> Option<RealtimeEvent>

作用:这个函数识别模型是否在请求“什么也别做,保持沉默”。它把 remain_silent 工具调用翻译成内部可以明确理解的 NoopRequested 事件。

数据流:进去的是 conversation item 的 JSON 对象。它检查 type 是否为 function_call,name 是否为 remain_silent;不符合就返回 None。符合时,它取 call_id 或 id 作为调用编号,也取 id 或 call_id 作为条目编号。最后出来的是 RealtimeEvent::NoopRequested,告诉后续流程这次不是失败,而是模型主动要求不回应。

调用关系:它由 parse_conversation_item_done_event 调用,是特殊工具调用分支之一。parse_conversation_item_done_event 会先试交接请求,再试这个静默请求,最后才把消息当成普通条目完成。

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

extract_input_transcript191–210 ↗
fn extract_input_transcript(arguments: &str) -> String

作用:这个函数从工具调用的 arguments 字符串里尽量找出真正的用户输入文本。因为不同调用可能把文本字段命名成 input_transcript、input、text、prompt 或 query,所以它负责统一提取。

数据流:进去的是 arguments 字符串。空字符串直接变成空结果;如果它能被解析成 JSON 对象,就按预设字段名顺序查找第一个非空文本,并去掉前后空格后返回;如果不是 JSON,或者里面没有合适字段,就把原始 arguments 原样返回。

调用关系:它只被 parse_handoff_requested_event 调用,用来给后台代理交接请求补上“用户到底说了什么”。这样交接事件里带的是尽量干净、可用的文本,而不是一整坨可能很乱的参数字符串。

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

codex-api/src/endpoint/realtime_websocket/methods.rs源码 ↗
io_transport连接建立、实时会话收发期间、关闭连接、测试运行时

实时接口像一通不断线的电话:客户端一边往外说话或发指令,一边等服务器回音频、文字、工具调用等事件。这个文件就是这通电话的接线员。它先根据普通 API 地址拼出 WebSocket 地址,再带上请求头和会话配置去连接;连接成功后立刻发送 session.update,告诉服务器本次会话要用什么模式、声音和指令。内部把读和写拆开:WsStream 像一个泵,专门在后台收消息、回 ping、按命令发送消息,避免“等消息时发不出去”。Writer 负责把业务动作变成 JSON 发走,Events 负责把服务器文本消息解析成 RealtimeEvent。它还维护 active transcript,也就是当前对话的简短流水账,方便服务器请求 handoff(把任务交给后台代理)时带上上下文。文件末尾的大量测试确保各种事件、地址拼接、请求头优先级和端到端收发都符合预期。

函数细节80
WsStream::new67–168 ↗
fn new(
        inner: WebSocketStream<MaybeTlsStream<TcpStream>>,
    ) -> (Self, async_channel::Receiver<Result<Message, WsError>>)

作用:创建一个 WebSocket 后台泵。它把真正的网络连接藏起来,让外面可以一边发消息、一边收消息,不会互相卡住。

数据流:进去的是底层 WebSocket 连接 → 它建两个通道:一个收发送/关闭命令,一个把收到的服务器消息吐出来;同时启动后台任务循环读写网络,并自动回应 ping → 出来的是可发命令的 WsStream 和可接收消息的队列。

调用关系:连接流程在成功连上服务器后调用它。之后发送侧通过 WsStream::send/close 给后台泵下命令,接收侧由 RealtimeWebsocketEvents 从它给出的消息队列取事件。

调用图:被 2 处调用(connect_realtime_websocket_url, connect_websocket);外部调用 3 个(info!, select!, spawn)。

WsStream::request170–179 ↗
async fn request(
        &self,
        make_command: impl FnOnce(oneshot::Sender<Result<(), WsError>>) -> WsCommand,
    ) -> Result<(), WsError>

作用:给后台 WebSocket 泵发一条命令,并等它告诉你成没成功。它像递工单并等回执。

数据流:进去的是一个能生成命令的函数 → 它创建一次性回执通道,把命令送进后台任务 → 出来的是发送或关闭操作的成功/失败结果;如果后台没了,就返回连接已关闭。

调用关系:WsStream::send 和 WsStream::close 都用它来统一处理“发命令并等结果”的套路。

调用图:被 2 处调用(close, send);外部调用 2 个(send, channel)。

WsStream::send181–184 ↗
async fn send(&self, message: Message) -> Result<(), WsError>

作用:通过后台泵发送一帧 WebSocket 消息。外部不直接碰网络连接,而是用它排队发送。

数据流:进去的是一条 WebSocket Message → 它包装成 Send 命令交给 WsStream::request → 出来的是发送成功或具体 WebSocket 错误。

调用关系:更上层的 RealtimeWebsocketWriter::send_payload 最终会走到这里,把 JSON 文本真正发到网络上。

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

WsStream::close186–189 ↗
async fn close(&self) -> Result<(), WsError>

作用:请求后台泵优雅关闭 WebSocket。它不是粗暴断线,而是让底层连接发关闭帧。

数据流:进去没有业务数据 → 它包装 Close 命令交给 WsStream::request → 出来的是关闭成功或关闭失败的错误。

调用关系:RealtimeWebsocketWriter::close 关闭实时连接时会用到它。

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

WsStream::drop193–195 ↗
fn drop(&mut self)

作用:当 WsStream 被丢弃时,停止后台读写任务。避免连接对象没了,后台任务还在空转。

数据流:进去的是即将销毁的 WsStream → 它中止 pump_task → 没有返回值,但后台任务会被停止。

调用关系:这是 Rust 的 Drop 自动清理钩子。调用者通常不会手动调用,连接生命周期结束时自动发生。

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

RealtimeWebsocketConnection::send_audio_frame227–229 ↗
async fn send_audio_frame(&self, frame: RealtimeAudioFrame) -> Result<(), ApiError>

作用:对外提供“发送一小段音频”的入口。使用者不需要知道音频要包装成什么 WebSocket JSON。

数据流:进去的是 RealtimeAudioFrame 音频帧 → 它把活交给内部 writer 的 send_audio_frame → 出来的是发送成功或 ApiError。

调用关系:这是连接对象的便捷方法,实际编码和发送由 RealtimeWebsocketWriter::send_audio_frame 完成。

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

RealtimeWebsocketConnection::send_conversation_item_create231–237 ↗
async fn send_conversation_item_create(
        &self,
        text: String,
        role: ConversationTextRole,
    ) -> Result<(), ApiError>

作用:对外提供“往实时会话里放一条文字消息”的入口,比如用户文字或开发者提示。

数据流:进去的是文本和角色 → 它转交给 writer 构造 conversation.item.create 消息 → 出来的是发送结果。

调用关系:调用方拿到 RealtimeWebsocketConnection 后会用它发文字;具体协议差异由 RealtimeWebsocketWriter 处理。

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

RealtimeWebsocketConnection::send_conversation_function_call_output239–247 ↗
async fn send_conversation_function_call_output(
        &self,
        call_id: String,
        output_text: String,
    ) -> Result<(), ApiError>

作用:对外提供“把工具/后台代理的结果回填给实时会话”的入口。服务器发起工具调用后,需要用它把答案送回去。

数据流:进去的是 call_id 和输出文本 → 它交给 writer 生成对应的函数调用结果消息 → 出来的是发送成功或错误。

调用关系:这是连接层的门面方法,内部使用 RealtimeWebsocketWriter::send_conversation_function_call_output。

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

RealtimeWebsocketConnection::close249–251 ↗
async fn close(&self) -> Result<(), ApiError>

作用:关闭整个实时连接。使用者可以只面对 Connection,不必单独找 writer。

数据流:进去没有额外数据 → 它调用 writer.close → 出来的是关闭结果,并把连接标记为已关闭。

调用关系:上层结束会话时调用它;真正的关闭动作由 RealtimeWebsocketWriter::close 和 WsStream::close 完成。

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

RealtimeWebsocketConnection::next_event253–255 ↗
async fn next_event(&self) -> Result<Option<RealtimeEvent>, ApiError>

作用:等待服务器发来的下一个可理解事件。它是实时会话“收消息”的主要入口。

数据流:进去没有业务参数 → 它从 events 读取 WebSocket 消息并解析 → 出来是 Some(event),或连接结束时 None,或读取失败的错误。

调用关系:上层事件循环会反复调用它;解析和转写状态维护由 RealtimeWebsocketEvents::next_event 完成。

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

RealtimeWebsocketConnection::writer257–259 ↗
fn writer(&self) -> RealtimeWebsocketWriter

作用:拿到一个可克隆的写入器,方便其他任务也能往同一连接发消息。

数据流:进去是当前连接 → 它克隆内部 writer 句柄 → 出来是 RealtimeWebsocketWriter,指向同一个底层连接。

调用关系:当读事件和写消息分在不同异步任务里做时会用它。

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

RealtimeWebsocketConnection::events261–263 ↗
fn events(&self) -> RealtimeWebsocketEvents

作用:拿到一个可克隆的事件读取器,方便专门的任务负责收服务器事件。

数据流:进去是当前连接 → 它克隆内部 events 句柄 → 出来是 RealtimeWebsocketEvents,共享同一条消息队列和转写状态。

调用关系:用于把 Connection 拆成读写两部分;事件读取仍由 RealtimeWebsocketEvents::next_event 处理。

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

RealtimeWebsocketConnection::new265–285 ↗
fn new(
        stream: WsStream,
        rx_message: async_channel::Receiver<Result<Message, WsError>>,
        event_parser: RealtimeEventParser,
    ) -> Self

作用:把底层 WebSocket 泵组装成一个高层实时连接。它同时准备写入器、事件读取器和共享的关闭标记。

数据流:进去的是 WsStream、消息接收队列和事件解析器 → 它用 Arc(一种共享指针)包装底层流,创建关闭标记和转写状态 → 出来是 RealtimeWebsocketConnection。

调用关系:connect_realtime_websocket_url 在网络连接成功后调用它,随后再通过 writer 发送 session.update。

调用图:被 1 处调用(connect_realtime_websocket_url);外部调用 5 个(clone, new, new, new, default)。

RealtimeWebsocketWriter::send_audio_frame289–292 ↗
async fn send_audio_frame(&self, frame: RealtimeAudioFrame) -> Result<(), ApiError>

作用:把一段音频包装成实时协议里的 input_audio_buffer.append 消息并发送。

数据流:进去的是音频帧,其中主要用到 base64 音频数据 → 它构造 RealtimeOutboundMessage::InputAudioBufferAppend,再交给 send_json → 出来是发送结果。

调用关系:Connection 的同名方法和处理用户音频输入的流程会调用它;真正的 JSON 编码和网络发送由 send_json/send_payload 完成。

调用图:调用 1 个内部函数(send_json);被 2 处调用(send_audio_frame, handle_user_audio_input)。

RealtimeWebsocketWriter::send_conversation_item_create294–305 ↗
async fn send_conversation_item_create(
        &self,
        text: String,
        role: ConversationTextRole,
    ) -> Result<(), ApiError>

作用:发送一条新的对话文字项。它会根据协议版本生成正确格式,避免调用方关心 V1 和 RealtimeV2 的差别。

数据流:进去的是文本和说话角色 → conversation_item_create_message 生成协议消息 → send_json 编成 JSON 并发出 → 出来是发送结果。

调用关系:Connection 门面、文本输入处理、handoff 输出处理都会调用它。

调用图:调用 2 个内部函数(send_json, conversation_item_create_message);被 3 处调用(send_conversation_item_create, handle_handoff_output, handle_text_input)。

RealtimeWebsocketWriter::send_conversation_handoff_append307–317 ↗
async fn send_conversation_handoff_append(
        &self,
        handoff_id: String,
        output_text: String,
    ) -> Result<(), ApiError>

作用:给一次 handoff 追加后台代理的输出。它用于某些协议形态下把代理结果接回实时会话。

数据流:进去的是 handoff_id 和输出文本 → 它构造 ConversationHandoffAppend 消息 → 通过 send_json 发送 → 出来是成功或错误。

调用关系:handle_handoff_output 会在需要追加 handoff 结果时调用它。

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

RealtimeWebsocketWriter::send_conversation_function_call_output319–330 ↗
async fn send_conversation_function_call_output(
        &self,
        call_id: String,
        output_text: String,
    ) -> Result<(), ApiError>

作用:把函数调用或后台代理调用的结果发回服务器。服务器才能知道这个工具调用已经有答案了。

数据流:进去的是 call_id 和输出文本 → conversation_function_call_output_message 按协议版本生成消息 → send_json 发出 → 出来是发送结果。

调用关系:Connection 门面、handoff 输出处理、服务器事件处理都会用它回应工具调用。

调用图:调用 2 个内部函数(send_json, conversation_function_call_output_message);被 3 处调用(send_conversation_function_call_output, handle_handoff_output, handle_realtime_server_event)。

RealtimeWebsocketWriter::send_response_create332–335 ↗
async fn send_response_create(&self) -> Result<(), ApiError>

作用:请求服务器立刻创建一次响应。可以理解为按下“现在回答”的按钮。

数据流:进去没有额外内容 → 它生成 RealtimeOutboundMessage::ResponseCreate → 通过 send_json 发出 → 出来是发送结果。

调用关系:send_create_now 会调用它来触发实时模型生成回复。

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

RealtimeWebsocketWriter::send_session_update337–354 ↗
async fn send_session_update(
        &self,
        instructions: String,
        session_mode: RealtimeSessionMode,
        output_modality: RealtimeOutputModality,
        voice: RealtimeVoice,

作用:发送会话配置,告诉服务器这次实时会话的规则:提示词、模式、输出形式和声音。

数据流:进去的是 instructions、session_mode、output_modality、voice → 它先把模式按协议版本标准化,再生成 session 配置消息 → 通过 send_json 发出 → 出来是发送结果。

调用关系:connect_realtime_websocket_url 建好连接后马上调用它,这是实时会话开始前的必要握手。

调用图:调用 3 个内部函数(send_json, normalized_session_mode, session_update_session)。

RealtimeWebsocketWriter::close356–368 ↗
async fn close(&self) -> Result<(), ApiError>

作用:关闭写入器对应的 WebSocket,并保证重复关闭不会报错。它用原子布尔值(一种线程安全开关)记住是否已关。

数据流:进去没有业务数据 → 它先把 is_closed 从 false 改成 true;如果以前已关闭就直接成功,否则调用底层 stream.close → 出来是成功,或非正常关闭错误。

调用关系:Connection::close 会调用它;它再把关闭命令交给 WsStream。

调用图:被 1 处调用(close);外部调用 3 个(Stream, format!, matches!)。

RealtimeWebsocketWriter::send_json370–375 ↗
async fn send_json(&self, message: &RealtimeOutboundMessage) -> Result<(), ApiError>

作用:把内部消息对象变成 JSON 字符串再发送。调用方只管给结构化消息,不用手写 JSON。

数据流:进去的是 RealtimeOutboundMessage → serde_json 把它序列化成字符串 → send_payload 把字符串发到 WebSocket → 出来是发送成功或编码/发送错误。

调用关系:所有高层发送方法都汇到这里,包括音频、文字、session.update、response.create 等。

调用图:调用 1 个内部函数(send_payload);被 6 处调用(send_audio_frame, send_conversation_function_call_output, send_conversation_handoff_append, send_conversation_item_create, send_response_create, send_session_update);外部调用 2 个(debug!, to_string)。

RealtimeWebsocketWriter::send_payload377–390 ↗
async fn send_payload(&self, payload: String) -> Result<(), ApiError>

作用:发送一段已经准备好的文本载荷。它是写入器最贴近 WebSocket 的发送出口。

数据流:进去的是 JSON 文本字符串 → 它先检查连接是否已关闭,再包成 WebSocket 文本帧发送 → 出来是成功或 ApiError;不会修改业务数据。

调用关系:send_json 会调用它;某些服务器事件处理也可以直接用它发送原始载荷。

调用图:被 2 处调用(send_json, handle_realtime_server_event);外部调用 3 个(Stream, trace!, Text)。

RealtimeWebsocketEvents::next_event394–443 ↗
async fn next_event(&self) -> Result<Option<RealtimeEvent>, ApiError>

作用:从 WebSocket 消息流里取下一条服务器事件,并过滤掉不支持或无意义的帧。

数据流:进去没有业务参数 → 它从消息队列 recv;遇到文本就解析成 RealtimeEvent,并更新活跃转写;遇到关闭帧就标记关闭;遇到二进制帧返回错误事件 → 出来是事件、None 或 ApiError。

调用关系:Connection::next_event 调用它。它依赖 parse_realtime_event 做协议解析,并调用 update_active_transcript 维护对话流水账。

调用图:调用 3 个内部函数(update_active_transcript, parse_realtime_event, recv);被 1 处调用(next_event);外部调用 7 个(Stream, debug!, error!, format!, info!, Error, trace!)。

RealtimeWebsocketEvents::update_active_transcript445–507 ↗
async fn update_active_transcript(&self, event: &mut RealtimeEvent)

作用:维护当前对话的文字记录,特别是为了 handoff 时能带上最近上下文。

数据流:进去的是刚解析出的事件的可修改引用 → 它根据事件类型追加用户/助手转写片段、替换最终转写、记录新回合;遇到 handoff 请求时把这段活跃记录写回事件里 → 没有单独返回值,但会改内部状态和某些事件内容。

调用关系:RealtimeWebsocketEvents::next_event 每解析出事件后调用它;它把细小操作交给 append_transcript_delta、apply_transcript_done、append_handoff_input。

调用图:调用 3 个内部函数(append_handoff_input, append_transcript_delta, apply_transcript_done);被 1 处调用(next_event)。

append_transcript_delta510–532 ↗
fn append_transcript_delta(
    entries: &mut Vec<RealtimeTranscriptEntry>,
    role: &str,
    delta: &str,
    force_new: bool,
)

作用:把一小段增量转写接到当前对话记录里。增量就是服务器一点点吐出来的文字碎片。

数据流:进去的是记录列表、角色、文字碎片和是否强制开新条 → 如果碎片为空就不动;如果最后一条同角色且不用开新条,就拼到最后;否则新增一条记录 → 改动 entries,没有返回值。

调用关系:update_active_transcript 在收到输入或输出转写 delta 时调用它。

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

apply_transcript_done534–556 ↗
fn apply_transcript_done(
    entries: &mut Vec<RealtimeTranscriptEntry>,
    role: &str,
    text: &str,
    force_new: bool,
)

作用:把服务器给出的最终转写写入记录。最终文本比增量更权威,所以可能替换最后一条。

数据流:进去的是记录列表、角色、最终文本和是否强制开新条 → 空文本忽略;可合并时替换最后一条同角色文本,否则新增记录 → 改动 entries,没有返回值。

调用关系:update_active_transcript 在收到 InputTranscriptDone 或 OutputTranscriptDone 时调用它。

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

append_handoff_input558–568 ↗
fn append_handoff_input(entries: &mut Vec<RealtimeTranscriptEntry>, input: &str)

作用:在 handoff 时确保用户输入被放进转写记录,但避免重复放同一句。

数据流:进去的是记录列表和 handoff 里的输入文本 → 它先去掉首尾空白,再检查是否为空或已存在;不重复时新增 user 记录 → 改动 entries,没有返回值。

调用关系:update_active_transcript 处理 HandoffRequested 时调用它;查重工作交给 contains_transcript_entry。

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

contains_transcript_entry570–574 ↗
fn contains_transcript_entry(entries: &[RealtimeTranscriptEntry], role: &str, text: &str) -> bool

作用:检查转写记录里是否已经有同角色、同文本的一条。用于防止 handoff 输入重复。

数据流:进去的是记录列表、角色和文本 → 它遍历列表,比较角色和去空白后的文本 → 出来是 true 或 false。

调用关系:append_handoff_input 在决定是否新增用户输入前调用它。

调用图:被 1 处调用(append_handoff_input);外部调用 1 个(iter)。

RealtimeWebsocketClient::new581–583 ↗
fn new(provider: Provider) -> Self

作用:创建一个实时 WebSocket 客户端,把服务商配置保存起来。

数据流:进去的是 Provider,里面有基础地址、请求头、重试配置等 → 它存入结构体 → 出来是 RealtimeWebsocketClient。

调用关系:测试和上层代码先创建 client,再调用 connect 或 connect_webrtc_sideband 建立连接。

调用图:被 12 处调用(e2e_connect_and_exchange_events_against_mock_ws_server, realtime_v2_session_update_includes_background_agent_tool_and_handoff_output_item, send_does_not_block_while_next_event_waits_for_inbound_data, transcription_mode_session_update_omits_output_audio_and_instructions, v1_transcription_mode_is_treated_as_conversational, realtime_ws_connect_webrtc_sideband_retries_join_until_server_is_available, realtime_ws_e2e_disconnected_emitted_once, realtime_ws_e2e_ignores_unknown_text_events, realtime_ws_e2e_realtime_v2_parser_emits_handoff_requested, realtime_ws_e2e_send_while_next_event_waits (+2 more))。

RealtimeWebsocketClient::connect585–600 ↗
async fn connect(
        &self,
        config: RealtimeSessionConfig,
        extra_headers: HeaderMap,
        default_headers: HeaderMap,
    ) -> Result<RealtimeWebsocketConnection, ApiError>

作用:用普通实时 WebSocket 方式发起新会话连接。

数据流:进去的是会话配置、额外请求头和默认请求头 → 它先把 API 地址拼成 WebSocket URL → 再调用 connect_realtime_websocket_url 真正联网 → 出来是可读写的 RealtimeWebsocketConnection。

调用关系:这是最常用的连接入口;URL 拼接由 websocket_url_from_api_url 负责。

调用图:调用 2 个内部函数(connect_realtime_websocket_url, websocket_url_from_api_url)。

RealtimeWebsocketClient::connect_webrtc_sideband602–640 ↗
async fn connect_webrtc_sideband(
        &self,
        config: RealtimeSessionConfig,
        call_id: &str,
        extra_headers: HeaderMap,
        default_headers: HeaderMap,
    ) -> Result<Rea

作用:连接已有 WebRTC 通话的 sideband 控制 WebSocket。sideband 可以理解为旁路控制线,用来加入已存在的实时会话。

数据流:进去的是会话配置、call_id 和请求头 → 它循环尝试连接,失败时按 backoff(逐步变长的等待时间)睡一会再试 → 成功返回连接,重试耗尽返回错误。

调用关系:它把单次连接交给 connect_webrtc_sideband_once;用于 WebRTC 会话已存在但控制通道暂时还没准备好的场景。

调用图:调用 1 个内部函数(connect_webrtc_sideband_once);外部调用 6 个(clone, clone, Stream, backoff, sleep, warn!)。

RealtimeWebsocketClient::connect_webrtc_sideband_once642–660 ↗
async fn connect_webrtc_sideband_once(
        &self,
        config: RealtimeSessionConfig,
        call_id: &str,
        extra_headers: HeaderMap,
        default_headers: HeaderMap,
    ) -> Resul

作用:执行一次加入 WebRTC sideband 的连接尝试。

数据流:进去的是配置、call_id 和请求头 → 它拼出带 call_id 的 WebSocket URL → 调用 connect_realtime_websocket_url 建连接 → 出来是连接或错误。

调用关系:只被 connect_webrtc_sideband 的重试循环调用;URL 生成交给 websocket_url_from_api_url_for_call。

调用图:调用 2 个内部函数(connect_realtime_websocket_url, websocket_url_from_api_url_for_call);被 1 处调用(connect_webrtc_sideband)。

RealtimeWebsocketClient::connect_realtime_websocket_url662–718 ↗
async fn connect_realtime_websocket_url(
        &self,
        ws_url: Url,
        config: RealtimeSessionConfig,
        extra_headers: HeaderMap,
        default_headers: HeaderMap,
    ) -> Resul

作用:真正执行 WebSocket 握手并初始化实时会话。这里是“拨号、带请求头、配 TLS、建连接、发 session.update”的核心流程。

数据流:进去的是完整 ws_url、会话配置和三类请求头 → 它准备 TLS,加工请求头,发起 WebSocket 连接,创建 WsStream 和 RealtimeWebsocketConnection,然后发送 session.update → 出来是初始化好的连接。

调用关系:普通 connect 和 sideband 单次连接都会调用它;它调用 merge_request_headers、with_session_id_header、websocket_config、WsStream::new、RealtimeWebsocketConnection::new 等零件。

调用图:调用 5 个内部函数(new, new, merge_request_headers, websocket_config, with_session_id_header);被 2 处调用(connect, connect_webrtc_sideband_once);外部调用 6 个(as_str, maybe_build_rustls_client_config_with_custom_ca, ensure_rustls_crypto_provider, debug!, info!, connect_async_tls_with_config)。

merge_request_headers721–734 ↗
fn merge_request_headers(
    provider_headers: &HeaderMap,
    extra_headers: HeaderMap,
    default_headers: HeaderMap,
) -> HeaderMap

作用:合并三来源请求头,并按优先级决定谁覆盖谁。优先级是 provider 先打底,extra 覆盖它,default 只补空缺。

数据流:进去的是 provider_headers、extra_headers、default_headers → 它克隆 provider,扩展 extra,再只插入还不存在的 default → 出来是最终 HeaderMap。

调用关系:connect_realtime_websocket_url 建握手请求时调用它;对应测试会验证优先级。

调用图:被 2 处调用(connect_realtime_websocket_url, merge_request_headers_matches_http_precedence);外部调用 1 个(clone)。

with_session_id_header736–750 ↗
fn with_session_id_header(
    mut headers: HeaderMap,
    session_id: Option<&str>,
) -> Result<HeaderMap, ApiError>

作用:如果配置里有 session_id,就把它放进 x-session-id 请求头。没有就保持原样。

数据流:进去的是请求头集合和可选 session_id → 有 session_id 时尝试转换成合法 HeaderValue 并插入;格式非法则返回 ApiError → 出来是更新后的 HeaderMap。

调用关系:connect_realtime_websocket_url 在合并请求头前调用它,确保会话 ID 随握手请求发出。

调用图:被 1 处调用(connect_realtime_websocket_url);外部调用 2 个(insert, from_str)。

websocket_config752–754 ↗
fn websocket_config() -> WebSocketConfig

作用:提供 WebSocket 库的配置。目前直接使用默认配置,留出统一调整的位置。

数据流:进去没有参数 → 返回 WebSocketConfig::default → 不改动外部状态。

调用关系:connect_realtime_websocket_url 发起 WebSocket 连接时把它传给底层库。

调用图:被 1 处调用(connect_realtime_websocket_url);外部调用 1 个(default)。

websocket_url_from_api_url756–806 ↗
fn websocket_url_from_api_url(
    api_url: &str,
    query_params: Option<&HashMap<String, String>>,
    model: Option<&str>,
    event_parser: RealtimeEventParser,
    _session_mode: RealtimeSession

作用:把普通 API 地址变成实时 WebSocket 地址,并补上路径和查询参数。

数据流:进去的是 api_url、可选查询参数、可选模型名、事件解析器和会话模式 → 它解析 URL,规范路径,把 http/https 改成 ws/wss,追加 intent、model 和额外查询参数 → 出来是 Url 或格式错误。

调用关系:RealtimeWebsocketClient::connect 调用它;多个测试覆盖不同基础地址、协议版本和查询参数情况。

调用图:调用 2 个内部函数(normalize_realtime_path, websocket_intent);被 10 处调用(connect, websocket_url_from_http_base_defaults_to_ws_path, websocket_url_from_nested_v1_base_appends_realtime_path, websocket_url_from_v1_base_appends_realtime_path, websocket_url_from_ws_base_defaults_to_ws_path, websocket_url_omits_intent_for_realtime_v2_conversational_mode, websocket_url_omits_intent_for_realtime_v2_transcription_mode, websocket_url_preserves_existing_realtime_path_and_extra_query_params, websocket_url_v1_ignores_transcription_mode, websocket_url_from_api_url_for_call);外部调用 3 个(parse, Stream, format!)。

websocket_url_from_api_url_for_call808–824 ↗
fn websocket_url_from_api_url_for_call(
    api_url: &str,
    query_params: Option<&HashMap<String, String>>,
    event_parser: RealtimeEventParser,
    session_mode: RealtimeSessionMode,
    call_id

作用:为加入已有 WebRTC 通话生成 WebSocket 地址。它在普通实时地址上额外加 call_id。

数据流:进去的是 api_url、查询参数、解析器、会话模式和 call_id → 先调用 websocket_url_from_api_url 生成基础地址,再追加 call_id 查询参数 → 出来是 Url 或错误。

调用关系:connect_webrtc_sideband_once 调用它;测试会验证生成的地址能表示“加入已有通话”。

调用图:调用 1 个内部函数(websocket_url_from_api_url);被 2 处调用(connect_webrtc_sideband_once, websocket_url_for_call_id_joins_existing_realtime_session)。

normalize_realtime_path826–850 ↗
fn normalize_realtime_path(url: &mut Url)

作用:把各种可能的基础路径整理成实时接口路径 /v1/realtime。

数据流:进去的是可修改 Url → 如果路径空或 /,设为 /v1/realtime;如果是 /v1 或 /v1/,追加 realtime;如果已经是 realtime,只做必要去尾斜杠 → 直接修改 Url。

调用关系:websocket_url_from_api_url 在拼查询参数前调用它,保证连接打到正确接口。

调用图:被 1 处调用(websocket_url_from_api_url);外部调用 3 个(path, set_path, format!)。

tests::parse_session_updated_event876–890 ↗
fn parse_session_updated_event()

作用:测试 session.updated 文本能被解析成会话已更新事件。

数据流:进去是测试里构造的 JSON → 调用解析函数并和期望事件比较 → 测试通过或失败。

调用关系:测试运行器单独执行,用来守住事件解析行为。

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

tests::parse_audio_delta_event893–912 ↗
fn parse_audio_delta_event()

作用:测试服务器发来的音频增量事件能变成 AudioOut 音频帧。

数据流:进去是包含 delta、采样率和声道数的 JSON → 解析后比较 RealtimeAudioFrame → 输出测试结果。

调用关系:它验证实时音频接收路径依赖的解析格式。

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

tests::parse_conversation_item_added_event915–927 ↗
fn parse_conversation_item_added_event()

作用:测试 conversation.item.added 会保留原始 item 内容。

数据流:进去是 item.added JSON → 解析成 ConversationItemAdded → 与期望 JSON 比较。

调用关系:作为协议解析测试之一,防止新增对话项事件被误丢。

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

tests::parse_conversation_item_done_event930–942 ↗
fn parse_conversation_item_done_event()

作用:测试 conversation.item.done 能提取 item_id。

数据流:进去是 item.done JSON → 解析器取出 item.id → 与期望事件比较。

调用关系:它保护对话项完成事件的解析逻辑。

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

tests::parse_handoff_requested_event945–963 ↗
fn parse_handoff_requested_event()

作用:测试 V1 handoff 请求事件能正确解析出 handoff_id、item_id 和输入文本。

数据流:进去是 handoff.requested JSON → 解析为 HandoffRequested,active_transcript 初始为空 → 与期望结构比较。

调用关系:它验证后续后台代理交接流程的入口事件。

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

tests::parse_input_transcript_delta_event966–981 ↗
fn parse_input_transcript_delta_event()

作用:测试输入转写增量事件能解析出文字碎片。

数据流:进去是 input_transcript.delta JSON → 解析出 RealtimeTranscriptDelta → 与期望 delta 比较。

调用关系:它覆盖用户语音转文字的增量接收格式。

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

tests::parse_v1_input_audio_transcription_delta_event984–1001 ↗
fn parse_v1_input_audio_transcription_delta_event()

作用:测试 V1 的另一种输入音频转写增量名字也能被识别。

数据流:进去是 conversation.item.input_audio_transcription.delta JSON → 解析成 InputTranscriptDelta → 比较结果。

调用关系:它保证同类事件的不同协议命名不会漏掉。

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

tests::parse_v1_input_audio_transcription_completed_event1004–1019 ↗
fn parse_v1_input_audio_transcription_completed_event()

作用:测试 V1 输入音频转写完成事件能变成最终输入转写。

数据流:进去是 completed JSON,里面有 transcript → 解析成 InputTranscriptDone → 与期望文本比较。

调用关系:它保护最终转写覆盖增量的上游数据格式。

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

tests::parse_v1_input_transcript_turn_marked_event1022–1035 ↗
fn parse_v1_input_transcript_turn_marked_event()

作用:测试 V1 的 turn_marked 事件也能当作输入转写完成来处理。

数据流:进去是 turn_marked JSON → 解析成 InputTranscriptDone → 输出断言结果。

调用关系:它确保服务器用不同事件名标记一轮结束时,客户端仍能理解。

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

tests::parse_output_transcript_delta_event1038–1053 ↗
fn parse_output_transcript_delta_event()

作用:测试助手输出转写增量能被解析。

数据流:进去是 output_transcript.delta JSON → 解析成 OutputTranscriptDelta → 与期望比较。

调用关系:它覆盖助手回复文字逐段到达的场景。

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

tests::parse_v1_output_audio_transcript_delta_event1056–1071 ↗
fn parse_v1_output_audio_transcript_delta_event()

作用:测试 V1 输出音频对应的文字增量事件能被识别。

数据流:进去是 response.output_audio_transcript.delta JSON → 解析为 OutputTranscriptDelta → 比较结果。

调用关系:它保证音频回复的字幕/转写不会被忽略。

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

tests::parse_v1_output_audio_transcript_done_event1074–1089 ↗
fn parse_v1_output_audio_transcript_done_event()

作用:测试 V1 输出音频转写完成事件能解析出最终文本。

数据流:进去是 done JSON → 解析成 OutputTranscriptDone → 与期望文本比较。

调用关系:它保护助手最终转写的解析入口。

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

tests::parse_v1_item_done_output_text_event1092–1113 ↗
fn parse_v1_item_done_output_text_event()

作用:测试 V1 item.done 里带 output_text 内容时仍按完成事件处理。

数据流:进去是含多个 output_text 的 item.done JSON → 解析器提取 item_id 并返回 ConversationItemDone → 与期望比较。

调用关系:它避免富内容 item.done 影响完成事件识别。

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

tests::parse_realtime_v2_handoff_tool_call_event1116–1138 ↗
fn parse_realtime_v2_handoff_tool_call_event()

作用:测试 RealtimeV2 中 background_agent 函数调用会被解释成 handoff 请求。

数据流:进去是 function_call item.done JSON,arguments 里有 prompt → 解析成 HandoffRequested → 与期望 handoff 信息比较。

调用关系:它验证 V2 通过工具调用触发后台代理的协议映射。

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

tests::parse_realtime_v2_noop_tool_call_event1141–1161 ↗
fn parse_realtime_v2_noop_tool_call_event()

作用:测试 RealtimeV2 中 remain_silent 工具调用会被解释成“不用回应”的请求。

数据流:进去是 remain_silent function_call JSON → 解析成 NoopRequested → 与期望 call_id 和 item_id 比较。

调用关系:它保护静默工具调用的特殊分支。

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

tests::parse_realtime_v2_input_audio_transcription_delta_event1164–1181 ↗
fn parse_realtime_v2_input_audio_transcription_delta_event()

作用:测试 RealtimeV2 输入音频转写增量事件能解析。

数据流:进去是 V2 transcription delta JSON → 解析成 InputTranscriptDelta → 比较文字碎片。

调用关系:它确保 V2 语音输入转文字的增量格式可用。

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

tests::parse_realtime_v2_output_audio_transcript_done_event1184–1199 ↗
fn parse_realtime_v2_output_audio_transcript_done_event()

作用:测试 RealtimeV2 输出音频转写完成事件能解析。

数据流:进去是 output_audio_transcript.done JSON → 解析成 OutputTranscriptDone → 比较最终文本。

调用关系:它覆盖 V2 音频回复的最终字幕事件。

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

tests::parse_realtime_v2_output_text_done_event1202–1217 ↗
fn parse_realtime_v2_output_text_done_event()

作用:测试 RealtimeV2 文本输出完成事件能解析成助手最终转写。

数据流:进去是 response.output_text.done JSON → 解析成 OutputTranscriptDone → 与期望比较。

调用关系:它保证纯文本回复也进入同一转写事件体系。

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

tests::parse_realtime_v2_conversation_item_created_event1220–1233 ↗
fn parse_realtime_v2_conversation_item_created_event()

作用:测试 RealtimeV2 的 conversation.item.created 能被当作新增对话项。

数据流:进去是 item.created JSON → 解析成 ConversationItemAdded → 比较保留的 item 内容。

调用关系:它覆盖 V2 与 V1 不同事件名的兼容。

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

tests::parse_realtime_v2_item_done_output_text_event1236–1257 ↗
fn parse_realtime_v2_item_done_output_text_event()

作用:测试 RealtimeV2 item.done 带输出文本时仍能提取完成项 ID。

数据流:进去是含 output_text 内容的 V2 item.done JSON → 解析为 ConversationItemDone → 比较 item_id。

调用关系:它确保 V2 完成事件不会因内容结构复杂而解析失败。

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

tests::parse_realtime_v2_output_audio_delta_defaults_audio_shape1260–1277 ↗
fn parse_realtime_v2_output_audio_delta_defaults_audio_shape()

作用:测试 RealtimeV2 音频增量没带音频形状信息时会使用默认采样率和声道。

数据流:进去是只含 delta 的 response.output_audio.delta JSON → 解析成 AudioOut,默认 24000Hz、单声道 → 与期望比较。

调用关系:它保护客户端对简化音频事件的默认处理。

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

tests::parse_realtime_v2_response_audio_delta_with_item_id1280–1298 ↗
fn parse_realtime_v2_response_audio_delta_with_item_id()

作用:测试 RealtimeV2 response.audio.delta 能带着 item_id 解析。

数据流:进去是含 delta 和 item_id 的 JSON → 解析成 AudioOut,并保存 item_id → 与期望比较。

调用关系:它验证音频片段能关联回具体对话项。

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

tests::parse_realtime_v2_speech_started_event1301–1316 ↗
fn parse_realtime_v2_speech_started_event()

作用:测试 RealtimeV2 语音开始事件能解析出输入项 ID。

数据流:进去是 input_audio_buffer.speech_started JSON → 解析成 InputAudioSpeechStarted → 比较 item_id。

调用关系:它关系到 active transcript 何时开启新的用户输入条目。

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

tests::parse_realtime_v2_response_cancelled_event1319–1334 ↗
fn parse_realtime_v2_response_cancelled_event()

作用:测试 RealtimeV2 响应取消事件能解析出 response_id。

数据流:进去是 response.cancelled JSON → 解析成 ResponseCancelled → 与期望比较。

调用关系:它保护取消回复这一控制事件的解析。

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

tests::parse_realtime_v2_response_done_event1337–1358 ↗
fn parse_realtime_v2_response_done_event()

作用:测试 RealtimeV2 response.done 能被识别为响应结束事件。

数据流:进去是 response.done JSON,里面可能含函数调用输出 → 解析成 ResponseDone → 与期望比较。

调用关系:它验证响应结束不会被其中的 output 内容干扰。

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

tests::parse_realtime_v2_response_created_event1361–1374 ↗
fn parse_realtime_v2_response_created_event()

作用:测试 RealtimeV2 响应创建事件能解析出 response_id。

数据流:进去是 response.created JSON → 解析成 ResponseCreated → 与期望比较。

调用关系:它也间接支持 active transcript 在新回复开始时开新助手条。

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

tests::merge_request_headers_matches_http_precedence1377–1407 ↗
fn merge_request_headers_matches_http_precedence()

作用:测试请求头合并的优先级符合预期。

数据流:进去是三组手工构造的 HeaderMap → 调用 merge_request_headers → 检查 provider、extra、default 谁覆盖谁。

调用关系:它直接验证 connect_realtime_websocket_url 建请求时依赖的合并规则。

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

tests::websocket_url_from_http_base_defaults_to_ws_path1410–1423 ↗
fn websocket_url_from_http_base_defaults_to_ws_path()

作用:测试 http 基础地址会自动变成 ws,并补 /v1/realtime 路径。

数据流:进去是 http://127.0.0.1 地址 → 调用 websocket_url_from_api_url → 比较最终 ws URL。

调用关系:它覆盖最基础的本地实时 WebSocket 地址生成。

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

tests::websocket_url_from_ws_base_defaults_to_ws_path1426–1439 ↗
fn websocket_url_from_ws_base_defaults_to_ws_path()

作用:测试已经是 wss 的基础地址也会补实时路径和模型参数。

数据流:进去是 wss://example.com 和模型名 → 生成 URL → 检查路径、intent、model。

调用关系:它验证直接传 WebSocket 地址时不会被错误改协议。

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

tests::websocket_url_from_v1_base_appends_realtime_path1442–1455 ↗
fn websocket_url_from_v1_base_appends_realtime_path()

作用:测试以 /v1 结尾的 API 地址会追加 /realtime。

数据流:进去是 https://api.openai.com/v1 → 生成 wss://.../v1/realtime?... → 与期望比较。

调用关系:它保护常见 OpenAI 风格基础地址的兼容。

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

tests::websocket_url_from_nested_v1_base_appends_realtime_path1458–1471 ↗
fn websocket_url_from_nested_v1_base_appends_realtime_path()

作用:测试带前缀路径的 /v1 地址也能正确追加 realtime。

数据流:进去是 https://example.com/openai/v1 → 生成对应嵌套路徑的 wss URL → 比较结果。

调用关系:它覆盖代理或网关把 API 放在子路径下的情况。

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

tests::websocket_url_preserves_existing_realtime_path_and_extra_query_params1474–1490 ↗
fn websocket_url_preserves_existing_realtime_path_and_extra_query_params()

作用:测试已有 /v1/realtime 路径和原查询参数不会丢失,还会追加允许的额外参数。

数据流:进去是带 foo=bar 的 URL、额外 trace 和被忽略的 intent → 生成最终 URL → 检查保留和过滤结果。

调用关系:它验证 websocket_url_from_api_url 的查询参数合并规则。

调用图:调用 1 个内部函数(websocket_url_from_api_url);外部调用 2 个(from, assert_eq!)。

tests::websocket_url_v1_ignores_transcription_mode1493–1506 ↗
fn websocket_url_v1_ignores_transcription_mode()

作用:测试 V1 解析器下即使传 transcription 模式,URL 仍按 V1 intent 生成。

数据流:进去是 V1 parser 和 Transcription 模式 → 生成 URL → 检查仍有 intent=quicksilver。

调用关系:它说明 V1 的转写模式在 URL 层被当作普通实时会话处理。

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

tests::websocket_url_omits_intent_for_realtime_v2_conversational_mode1509–1525 ↗
fn websocket_url_omits_intent_for_realtime_v2_conversational_mode()

作用:测试 RealtimeV2 会话 URL 不再自动加 V1 的 intent 参数。

数据流:进去是 RealtimeV2 parser、模型名和额外参数 → 生成 URL → 检查没有 intent,但有 model 和 trace。

调用关系:它保护 V2 协议和 V1 URL 参数的差异。

调用图:调用 1 个内部函数(websocket_url_from_api_url);外部调用 2 个(from, assert_eq!)。

tests::websocket_url_omits_intent_for_realtime_v2_transcription_mode1528–1538 ↗
fn websocket_url_omits_intent_for_realtime_v2_transcription_mode()

作用:测试 RealtimeV2 转写模式在无模型和额外参数时 URL 可以没有查询串。

数据流:进去是 https://example.com 和 V2 Transcription 模式 → 生成 wss://example.com/v1/realtime → 与期望比较。

调用关系:它验证 V2 转写连接地址足够干净。

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

tests::websocket_url_for_call_id_joins_existing_realtime_session1541–1554 ↗
fn websocket_url_for_call_id_joins_existing_realtime_session()

作用:测试 sideband 连接地址会带 call_id,用来加入已有实时通话。

数据流:进去是 API 地址和 call_id → 调用 websocket_url_from_api_url_for_call → 检查 URL 查询参数只有 call_id。

调用关系:它直接覆盖 connect_webrtc_sideband_once 依赖的地址生成。

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

tests::e2e_connect_and_exchange_events_against_mock_ws_server1557–1855 ↗
async fn e2e_connect_and_exchange_events_against_mock_ws_server()

作用:端到端测试普通 V1 实时 WebSocket:连接、发 session.update、发音频/文字/handoff 输出、收事件。

数据流:进去是测试创建的本地假 WebSocket 服务器和客户端配置 → 客户端连接后与服务器互发 JSON,服务器检查收到的消息,客户端检查解析出的事件和 active transcript → 测试通过或失败。

调用关系:它把 RealtimeWebsocketClient、WsStream、Writer、Events 和转写维护串起来验证。

调用图:调用 1 个内部函数(new);外部调用 12 个(from_millis, from_secs, new, new, bind, assert_eq!, format!, json!, from_str, spawn (+2 more))。

tests::realtime_v2_session_update_includes_background_agent_tool_and_handoff_output_item1858–2067 ↗
async fn realtime_v2_session_update_includes_background_agent_tool_and_handoff_output_item()

作用:端到端测试 RealtimeV2 的 session.update 会包含后台代理工具配置,并且函数调用结果格式正确。

数据流:进去是本地假服务器和 V2 客户端配置 → 服务器检查 session.update 里的音频、工具、声音等字段;客户端再发送文字和函数调用输出 → 断言所有 JSON 符合预期。

调用关系:它验证 V2 会话初始化和 handoff 输出发送路径。

调用图:调用 1 个内部函数(new);外部调用 12 个(from_millis, from_secs, new, new, bind, assert_eq!, format!, json!, from_str, spawn (+2 more))。

tests::transcription_mode_session_update_omits_output_audio_and_instructions2070–2181 ↗
async fn transcription_mode_session_update_omits_output_audio_and_instructions()

作用:测试 RealtimeV2 转写模式下 session.update 不应包含输出音频、工具和 instructions。

数据流:进去是转写模式配置和假服务器 → 客户端连接并发送 session.update;服务器检查只配置输入转写;客户端再发送音频 → 测试结果表明转写模式格式正确。

调用关系:它验证 send_session_update 通过 session_update_session 生成转写会话配置的行为。

调用图:调用 1 个内部函数(new);外部调用 13 个(from_millis, from_secs, new, new, bind, assert!, assert_eq!, format!, json!, from_str (+3 more))。

tests::v1_transcription_mode_is_treated_as_conversational2184–2274 ↗
async fn v1_transcription_mode_is_treated_as_conversational()

作用:测试 V1 下传入转写模式仍会按 conversational/quicksilver 会话发配置。

数据流:进去是 V1 parser 加 Transcription 模式配置 → 客户端连接,服务器检查 session.update 仍有 instructions 和输出声音 → 客户端收到 session.updated 后关闭。

调用关系:它验证 normalized_session_mode 对 V1 模式的兼容处理。

调用图:调用 1 个内部函数(new);外部调用 13 个(from_millis, from_secs, new, new, bind, assert!, assert_eq!, format!, json!, from_str (+3 more))。

tests::send_does_not_block_while_next_event_waits_for_inbound_data2277–2380 ↗
async fn send_does_not_block_while_next_event_waits_for_inbound_data()

作用:测试在等待服务器事件时,发送音频不会被卡住。也就是读和写真的被拆开了。

数据流:进去是假服务器和客户端连接 → 客户端同时执行 send_audio_frame 和 next_event;服务器先收到音频再回事件 → 测试确认发送能在超时内完成,事件也能收到。

调用关系:它专门验证 WsStream::new 的后台泵设计,防止读等待阻塞写发送。

调用图:调用 1 个内部函数(new);外部调用 13 个(from_millis, from_secs, new, new, bind, assert_eq!, format!, json!, from_str, join! (+3 more))。

实时会话运行时

这些文件通过将 WebSocket/WebRTC 传输桥接到会话级运行时行为和原生 WebRTC 处理,来执行实时对话。

core/src/realtime_conversation.rs源码 ↗
orchestrationrequest handling, main loop, teardown

这个文件像一个实时通话的总机。用户说话或输入文字时,它把内容排队送到实时模型;模型返回语音、转写、错误或“请后台 agent 帮忙”的请求时,它再把这些事件转发给会话前端,必要时还会把请求包装成后台 Codex 能读懂的文本。它同时支持 WebSocket(网页常见的长连接)和 WebRTC(一种偏语音视频通话的连接方式)。文件里最核心的是 RealtimeConversationManager,它保存当前是否有通话、各种输入队列和后台任务。另一个关键点是“handoff”,也就是实时模型把一段任务交给后台 agent 做,再把进度或结果送回实时模型。代码还处理一些容易出错的细节:比如队列满了丢弃音频帧、一次只能有一个默认回复、用户打断语音时截断还没播完的音频、不同实时协议版本 v1/v2 的差异。

函数细节54
RealtimeResponseCreateQueue::request_create137–148 ↗
async fn request_create(
        &mut self,
        writer: &RealtimeWebsocketWriter,
        events_tx: &Sender<RealtimeEvent>,
        reason: &str,
    ) -> anyhow::Result<()>

作用:请求实时模型开始生成一次回复。它会避免在已有回复还没结束时又硬塞一个新回复,防止服务端报“已经在回复了”。

数据流:输入是 WebSocket 写入器、事件队列和一个说明原因的文字。它先看当前是否已经有默认回复在进行;如果有,就只记一笔“等会儿再发”。如果没有,就立刻交给 send_create_now 发送。结果是要么真的发出创建回复请求,要么把请求暂存起来。

调用关系:handle_handoff_output 和 handle_realtime_server_event 在需要模型继续说话时会调用它;它把真正发送的工作交给 RealtimeResponseCreateQueue::send_create_now。

调用图:调用 1 个内部函数(send_create_now);被 2 处调用(handle_handoff_output, handle_realtime_server_event)。

RealtimeResponseCreateQueue::mark_started150–152 ↗
fn mark_started(&mut self)

作用:标记“模型已经开始生成回复了”。这相当于把队列状态切到忙碌,后面的创建请求需要排队。

数据流:没有外部输入,只修改内部的 active_default_response 标记。之前状态可能是空闲,之后会被认为已有回复正在进行。

调用关系:handle_realtime_server_event 收到服务端的 ResponseCreated 事件时调用它,用来同步本地状态和服务端状态。

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

RealtimeResponseCreateQueue::mark_finished154–166 ↗
async fn mark_finished(
        &mut self,
        writer: &RealtimeWebsocketWriter,
        events_tx: &Sender<RealtimeEvent>,
        reason: &str,
    ) -> anyhow::Result<()>

作用:标记当前回复结束,并检查之前有没有被推迟的“再生成一次回复”请求。它让连续请求按顺序发生,而不是撞车。

数据流:输入是写入器、事件队列和原因文字。它先把 active_default_response 设为 false;如果没有等待中的请求,就结束;如果有,就清掉等待标记并调用 send_create_now。输出是可能发出一个新的 response.create。

调用关系:handle_realtime_server_event 在收到 ResponseDone 或 ResponseCancelled 时调用它;如果有积压请求,它继续交给 RealtimeResponseCreateQueue::send_create_now。

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

RealtimeResponseCreateQueue::send_create_now168–189 ↗
async fn send_create_now(
        &mut self,
        writer: &RealtimeWebsocketWriter,
        events_tx: &Sender<RealtimeEvent>,
        reason: &str,
    ) -> anyhow::Result<()>

作用:真正向实时服务端发送“请开始生成回复”的命令。它还专门处理一种竞争情况:刚发时服务端说已有回复在跑,就改成稍后重试。

数据流:输入是 WebSocket 写入器、事件队列和原因文字。它调用 writer.send_response_create;成功后标记当前有活动回复。失败时把 API 错误转换成人能读的错误;如果是“已有活动回复”,就改为排队;其他错误会发到事件队列并返回失败。

调用关系:它是 RealtimeResponseCreateQueue::request_create 和 RealtimeResponseCreateQueue::mark_finished 的底层发送动作,负责和实时服务端直接打交道。

调用图:调用 1 个内部函数(send_response_create);被 2 处调用(mark_finished, request_create);外部调用 4 个(send, map_api_error, Error, warn!)。

RealtimeHandoffState::new211–225 ↗
fn new(
        output_tx: Sender<RealtimeOutbound>,
        codex_responses_as_items: bool,
        codex_response_item_prefix: Option<String>,
        session_kind: RealtimeSessionKind,
    ) -> Sel

作用:创建一份 handoff 状态,也就是记录后台 agent 和实时模型之间交接任务时需要的共享信息。

数据流:输入是输出队列、是否把 Codex 结果作为对话条目、可选前缀和会话版本。它创建两个带锁的共享字段:当前活跃 handoff 的编号、最近一次后台输出。输出是一份 RealtimeHandoffState。

调用关系:RealtimeConversationManager::start_inner 启动新实时会话时会调用它,之后 handoff_out、handoff_complete、服务端事件处理都会读写这份状态。

调用图:被 2 处调用(start_inner, clears_active_handoff_explicitly);外部调用 2 个(new, new)。

RealtimeConversationManager::new259–263 ↗
fn new() -> Self

作用:创建实时对话管理器。它一开始没有任何正在运行的对话。

数据流:没有输入。它创建一个互斥锁(一把锁,防止两个任务同时改同一份数据)保护的空状态。输出是 RealtimeConversationManager。

调用关系:Session 初始化或测试搭会话时会用它;后续所有实时启动、输入、关闭都通过这个管理器。

调用图:被 3 处调用(new, make_session_and_context, make_session_and_context_with_auth_config_home_and_rx);外部调用 1 个(new)。

RealtimeConversationManager::running_state265–270 ↗
async fn running_state(&self) -> Option<()>

作用:检查当前是否还有实时对话在运行。调用方常用它来判断一个错误是正常收尾造成的,还是用户真的操作错了。

数据流:输入是管理器自身。它读取内部状态和 realtime_active 开关;如果有活跃会话就返回 Some,否则返回 None。它不修改状态。

调用关系:handle_audio、handle_text、handle_speech 在发送输入失败后会查看它,决定是否要把错误报告给用户。

RealtimeConversationManager::is_running_v2272–280 ↗
async fn is_running_v2(&self) -> bool

作用:判断当前运行中的实时会话是不是 v2 协议。v2 和 v1 在前缀、handoff、回复创建等行为上不一样。

数据流:输入是管理器自身。它读取当前状态、活跃标记和 session_kind;只有正在运行且版本是 V2 时返回 true。

调用关系:这是给外部代码做分支判断的小工具,帮助别的流程知道能不能使用 v2 才有的实时行为。

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

RealtimeConversationManager::start282–292 ↗
async fn start(&self, start: RealtimeStart) -> CodexResult<RealtimeStartOutput>

作用:启动一场新的实时对话,并保证旧对话不会继续占着资源。它像接新电话前先挂掉旧电话。

数据流:输入是一份 RealtimeStart,里面有连接信息、配置和模型客户端。它先取出并停止旧 ConversationState,再调用 start_inner 建立新连接。输出是 RealtimeStartOutput,包含事件接收端、活跃标记和可能的 SDP。

调用关系:handle_start_inner 准备好参数后会间接调用它;它负责先清场,再把实际启动工作交给 RealtimeConversationManager::start_inner。

调用图:调用 2 个内部函数(start_inner, stop_conversation_state)。

RealtimeConversationManager::start_inner294–396 ↗
async fn start_inner(&self, start: RealtimeStart) -> CodexResult<RealtimeStartOutput>

作用:真正搭建实时对话的所有管道:输入队列、输出事件队列、网络连接和后台任务。

数据流:输入是 RealtimeStart。它拆出配置,判断协议版本,创建音频、文字、handoff、事件四组队列;然后按 WebSocket 或 WebRTC 分别连接服务端,并启动输入处理任务。最后把 ConversationState 存进管理器,输出 RealtimeStartOutput。

调用关系:只由 RealtimeConversationManager::start 调用。它会创建 RealtimeHandoffState,并根据传输方式调用 spawn_realtime_input_task 或 spawn_webrtc_sideband_input_task。

调用图:调用 5 个内部函数(new, new, spawn_realtime_input_task, spawn_webrtc_sideband_input_task, default_headers);被 1 处调用(start);外部调用 3 个(clone, new, new)。

RealtimeConversationManager::register_fanout_task398–417 ↗
async fn register_fanout_task(
        &self,
        realtime_active: &Arc<AtomicBool>,
        fanout_task: JoinHandle<()>,
    )

作用:登记“事件转发任务”。这个任务负责把实时服务端事件转给 Session 的外部订阅者。

数据流:输入是当前会话的活跃标记和一个异步任务句柄。它先确认这个任务确实属于当前会话;如果是,就存到状态里;如果不是,说明会话已经换了,就中止这个多余任务。

调用关系:handle_start_inner 创建 fanout task 后调用它。这样 shutdown 或 finish_if_active 之后能找到并处理这个转发任务。

调用图:外部调用 3 个(ptr_eq, abort, take)。

RealtimeConversationManager::finish_if_active419–431 ↗
async fn finish_if_active(&self, realtime_active: &Arc<AtomicBool>)

作用:如果传入的活跃标记对应当前会话,就把这场实时对话收尾。它避免旧任务误关新会话。

数据流:输入是某个会话的 realtime_active。它用指针相等确认这就是当前状态;匹配则取出状态并停止输入任务,同时让 fanout 任务自然脱离。输出为空,但会清掉管理器里的当前会话。

调用关系:handle_start_inner 里的事件转发任务在服务端连接结束时调用它;它把停止细节交给 stop_conversation_state。

调用图:调用 1 个内部函数(stop_conversation_state);外部调用 1 个(ptr_eq)。

RealtimeConversationManager::audio_in433–455 ↗
async fn audio_in(&self, frame: RealtimeAudioFrame) -> CodexResult<()>

作用:把用户麦克风的一帧音频塞进实时对话。这里用队列缓冲,避免录音和网络发送互相卡住。

数据流:输入是一帧 RealtimeAudioFrame。它先找到当前会话的 audio_tx;没有会话就返回“conversation is not running”。有会话则尝试立刻放入队列;队列满了会丢弃这一帧并记录警告,队列关闭则报错。

调用关系:handle_audio 收到外部音频请求时调用它;真正把音频发给服务端的是后台 run_realtime_input_task 中的 handle_user_audio_input。

调用图:外部调用 2 个(InvalidRequest, warn!)。

RealtimeConversationManager::text_in457–480 ↗
async fn text_in(&self, mut params: ConversationTextParams) -> CodexResult<()>

作用:把用户或开发者输入的文字送进实时对话。对于 v2 用户文字,它会自动加上 [USER] 前缀,帮模型区分来源。

数据流:输入是 ConversationTextParams。它读取当前会话的文字队列和版本;如果没有会话就报错。如果角色是 User,则可能调用 prefix_realtime_text 加前缀。之后把参数发送到 text_tx 队列。

调用关系:handle_text 和其他会话路由会调用它;后台输入任务之后用 handle_text_input 把队列里的文字发给实时服务端。

调用图:调用 1 个内部函数(prefix_realtime_text);外部调用 1 个(InvalidRequest)。

RealtimeConversationManager::handoff_out482–533 ↗
async fn handoff_out(&self, output_text: String) -> CodexResult<()>

作用:把后台 Codex agent 的输出送回实时模型。它会根据当前是否有活跃 handoff,决定这是任务进度、任务完成内容,还是一条独立后台消息。

数据流:输入是后台输出文字。它读取 handoff 状态:如果有活跃 handoff,就保存最近输出,并按配置生成 HandoffUpdate 或 ConversationItem;如果没有活跃 handoff,空白文字会忽略,非空文字作为独立 handoff 或对话条目。文字会经过 realtime_backend_output/realtime_backend_item 加前缀和截断,最后送入 output_tx。

调用关系:后台 agent 产出结果时会走这里;run_realtime_input_task 里的 handle_handoff_output 再把这些 RealtimeOutbound 发给实时服务端。

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

RealtimeConversationManager::append_speech535–558 ↗
async fn append_speech(&self, text: String) -> CodexResult<()>

作用:把一段已经识别出的语音文字作为后台消息追加进实时对话。空白内容会直接忽略。

数据流:输入是一段 text。它先去掉纯空白的情况;再找到当前 handoff 输出队列,把文字包装成 StandaloneHandoff,并通过 realtime_backend_output 加后端前缀和截断。输出是队列里多了一条要发给实时模型的消息。

调用关系:handle_speech 收到外部语音文本请求时调用它;后续仍由 handle_handoff_output 负责写到服务端。

调用图:调用 1 个内部函数(realtime_backend_output);外部调用 1 个(InvalidRequest)。

RealtimeConversationManager::handoff_complete560–594 ↗
async fn handoff_complete(&self) -> CodexResult<()>

作用:通知实时模型:当前后台 handoff 已经完成。它主要服务 v2,因为 v1 不需要这里额外确认。

数据流:它读取当前 handoff 状态;如果没有会话、不是 v2、没有活跃 handoff、没有最近输出,都会直接返回。否则按配置发送 HandoffCompleteAck 或 CompletedHandoff 到 output_tx。结果是服务端稍后会收到一次完成确认。

调用关系:后台 agent 完成任务时可调用它;最终由 handle_handoff_output 转成对应的实时 API 消息。

RealtimeConversationManager::clear_active_handoff596–605 ↗
async fn clear_active_handoff(&self)

作用:清掉当前记录的活跃 handoff 和最近输出。它相当于把“正在交接的任务”标记擦干净。

数据流:输入是管理器自身。它如果找到当前会话,就把 active_handoff 和 last_output_text 两个共享字段都设为 None。没有会话时什么也不做。

调用关系:当外部流程明确知道 handoff 已结束或要重置时会用它,避免后续后台输出误归到旧任务上。

RealtimeConversationManager::shutdown607–617 ↗
async fn shutdown(&self) -> CodexResult<()>

作用:关闭当前实时对话,释放后台任务和状态。用户主动关闭或系统清理时会用它。

数据流:它从管理器里取走当前 ConversationState;如果存在,就调用 stop_conversation_state 并要求中止 fanout 任务。输出是成功结果,状态被清空。

调用关系:handle_close 通过 end_realtime_conversation 调用它;启动新对话前也会有类似停止旧状态的流程。

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

stop_conversation_state620–637 ↗
async fn stop_conversation_state(
    mut state: ConversationState,
    fanout_task_stop: RealtimeFanoutTaskStop,
)

作用:停止一场实时对话的内部任务。它负责把活跃开关关掉,并处理输入任务和事件转发任务。

数据流:输入是 ConversationState 和 fanout 停止策略。它把 realtime_active 设为 false,中止并等待 input_task;如果有 fanout_task,则按策略中止它或放它自己结束。输出为空,但任务会被停止或脱离。

调用关系:RealtimeConversationManager::start、shutdown、finish_if_active 都调用它,是统一的收尾工具。

调用图:被 3 处调用(finish_if_active, shutdown, start)。

handle_start639–672 ↗
async fn handle_start(
    sess: &Arc<Session>,
    sub_id: String,
    params: ConversationStartParams,
) -> CodexResult<()>

作用:处理外部发来的“开始实时对话”请求。它负责把错误变成实时事件发回去,而不是让请求直接炸掉。

数据流:输入是 Session、订阅 id 和启动参数。它先调用 prepare_realtime_start 做准备;准备失败就发送 RealtimeEvent::Error。准备成功后调用 handle_start_inner;如果启动失败,也把错误作为实时事件发回。

调用关系:submission_loop 收到开始指令时调用它;它是请求入口,下面分成准备阶段 prepare_realtime_start 和启动阶段 handle_start_inner。

调用图:调用 2 个内部函数(handle_start_inner, prepare_realtime_start);被 1 处调用(submission_loop);外部调用 3 个(error!, RealtimeConversationRealtime, Error)。

prepare_realtime_start687–755 ↗
async fn prepare_realtime_start(
    sess: &Arc<Session>,
    params: ConversationStartParams,
) -> CodexResult<PreparedRealtimeConversationStart>

作用:把用户的启动参数、配置文件和登录信息整理成真正能连接实时服务的启动包。

数据流:输入是 Session 和 ConversationStartParams。它读取 provider、认证、配置和传输方式,确定 API 地址、版本、架构、会话配置、请求头和会话 id。它会校验架构、选择 API key、构造请求头。输出是 PreparedRealtimeConversationStart。

调用关系:handle_start 先调用它;它内部会调用 validate_realtime_architecture、build_realtime_session_config、realtime_api_key 和 realtime_request_headers。

调用图:调用 4 个内部函数(build_realtime_session_config, realtime_api_key, realtime_request_headers, validate_realtime_architecture);被 1 处调用(handle_start)。

validate_realtime_architecture757–782 ↗
fn validate_realtime_architecture(
    architecture: RealtimeConversationArchitecture,
    version: RealtimeWsVersion,
    transport: &ConversationStartTransport,
    session_type: RealtimeWsMode,
) -

作用:检查用户选择的实时架构和传输方式是否搭配正确。尤其是 AVAS 架构要求很严格。

数据流:输入是架构、版本、传输方式和会话类型。如果不是 AVAS 就直接通过;如果是 AVAS,则要求 v1、WebRTC、conversational 模式,任何不满足都会返回 InvalidRequest。

调用关系:prepare_realtime_start 在真正启动前调用它,防止用不兼容的组合去连服务端。

调用图:被 1 处调用(prepare_realtime_start);外部调用 2 个(matches!, InvalidRequest)。

build_realtime_session_config784–853 ↗
async fn build_realtime_session_config(
    sess: &Arc<Session>,
    params: &ConversationStartParams,
    version: RealtimeWsVersion,
) -> CodexResult<RealtimeSessionConfig>

作用:生成实时服务端需要的会话配置,比如提示词、模型、会话 id、输出模式和声音。

数据流:输入是 Session、启动参数和实时版本。它读取配置,准备后端提示词,按需要加入启动上下文,选择模型和事件解析器,检查 v1 不允许纯文本输出,选择会话模式和声音并校验。输出是 RealtimeSessionConfig。

调用关系:prepare_realtime_start 调用它。它会用 prepare_realtime_backend_prompt、build_realtime_startup_context、default_realtime_voice 和 validate_realtime_voice。

调用图:调用 3 个内部函数(build_realtime_startup_context, validate_realtime_voice, prepare_realtime_backend_prompt);被 1 处调用(prepare_realtime_start);外部调用 4 个(new, format!, matches!, InvalidRequest)。

default_realtime_voice855–861 ↗
fn default_realtime_voice(version: RealtimeWsVersion) -> RealtimeVoice

作用:按实时协议版本选择默认声音。不同版本支持的默认 voice 可能不一样。

数据流:输入是 RealtimeWsVersion。它读取内置声音列表;v1 返回 default_v1,v2 返回 default_v2。输出是一个 RealtimeVoice。

调用关系:build_realtime_session_config 在用户和配置都没指定声音时调用它。

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

prefix_realtime_text863–868 ↗
fn prefix_realtime_text(text: String, prefix: &str, session_kind: RealtimeSessionKind) -> String

作用:给 v2 实时文本加来源前缀,比如 [USER] 或 [BACKEND]。这样模型能知道这句话来自用户还是后台。

数据流:输入是原文字、前缀和会话版本。如果不是 v2、文字为空、或已经有这个前缀,就原样返回;否则拼上前缀返回新文字。

调用关系:RealtimeConversationManager::text_in 给用户文字加前缀;realtime_backend_output 给后台输出加前缀。

调用图:被 2 处调用(text_in, realtime_backend_output);外部调用 1 个(format!)。

realtime_backend_output870–873 ↗
fn realtime_backend_output(output_text: String, session_kind: RealtimeSessionKind) -> String

作用:把后台 agent 的输出整理成适合发给实时模型的文本。它会加 [BACKEND] 前缀,并限制长度。

数据流:输入是后台输出文字和会话版本。它先调用 prefix_realtime_text 按 v2 规则加前缀,再调用 truncate_realtime_text_to_token_budget 按 token 预算截断。输出是处理后的文字。

调用关系:handoff_out 和 append_speech 在把后台内容送回实时模型前都会调用它。

调用图:调用 2 个内部函数(truncate_realtime_text_to_token_budget, prefix_realtime_text);被 2 处调用(append_speech, handoff_out)。

realtime_backend_item875–881 ↗
fn realtime_backend_item(text: String, prefix: Option<&str>) -> String

作用:把后台输出整理成一个开发者对话条目。可选前缀会放在正文前面,最后也会限制长度。

数据流:输入是文本和可选前缀。前缀非空时拼成“前缀 + 空行 + 文本”,否则只用文本;然后按 token 预算截断。输出是最终对话条目文字。

调用关系:handoff_out 在 codex_responses_as_items 开启时调用它,把 Codex 输出作为 ConversationItem 发送。

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

validate_realtime_voice883–906 ↗
fn validate_realtime_voice(version: RealtimeWsVersion, voice: RealtimeVoice) -> CodexResult<()>

作用:检查选中的声音是否被当前实时版本支持。它能给出清楚的错误信息,告诉用户可用声音有哪些。

数据流:输入是版本和声音。它读取内置声音列表,按版本选择允许集合;如果包含该声音就成功,否则返回 InvalidRequest,错误里带上当前版本和支持列表。

调用关系:build_realtime_session_config 在确定 voice 后调用它,避免启动后服务端再拒绝。

调用图:调用 1 个内部函数(builtin);被 1 处调用(build_realtime_session_config);外部调用 2 个(format!, InvalidRequest)。

handle_start_inner908–1033 ↗
async fn handle_start_inner(
    sess: &Arc<Session>,
    sub_id: &str,
    prepared_start: PreparedRealtimeConversationStart,
) -> CodexResult<()>

作用:完成实时对话启动后的运行安排:启动管理器、通知前端已开始、必要时返回 SDP,并开一个任务持续转发实时事件。

数据流:输入是 Session、订阅 id 和准备好的启动包。它把传输参数转换成 RealtimeStart,调用会话管理器启动;然后发送 Started 事件和可选 SDP。接着创建 fanout 任务,不断从 events_rx 读服务端事件,可能把 handoff 请求路由给后台 agent,同时把事件发给前端。结束时发送 Closed 事件。

调用关系:handle_start 调用它。它内部会用 realtime_delegation_from_handoff 处理 handoff,用 send_realtime_conversation_closed 做结束通知,并把 fanout 任务注册到 RealtimeConversationManager::register_fanout_task。

调用图:调用 2 个内部函数(realtime_delegation_from_handoff, send_realtime_conversation_closed);被 1 处调用(handle_start);外部调用 7 个(clone, debug!, info!, RealtimeConversationRealtime, RealtimeConversationSdp, RealtimeConversationStarted, spawn)。

handle_audio1035–1049 ↗
async fn handle_audio(
    sess: &Arc<Session>,
    sub_id: String,
    params: ConversationAudioParams,
)

作用:处理外部送来的实时音频帧。它把音频交给当前实时对话,并在失败时决定是否报告错误。

数据流:输入是 Session、订阅 id 和 ConversationAudioParams。它调用 sess.conversation.audio_in 送入队列;失败时,如果会话仍在运行就认为可能是收尾竞态并警告,否则发 BadRequest 错误事件。

调用关系:submission_loop 收到音频消息时调用它;底层工作交给 RealtimeConversationManager::audio_in,错误发送交给 send_conversation_error。

调用图:调用 1 个内部函数(send_conversation_error);被 1 处调用(submission_loop);外部调用 2 个(error!, warn!)。

realtime_transcript_delta_from_handoff1051–1059 ↗
fn realtime_transcript_delta_from_handoff(handoff: &RealtimeHandoffRequested) -> Option<String>

作用:从 handoff 请求里整理“当前活跃转写片段”。如果实时模型只给了增量转写,这个函数把它拼成可读文本。

数据流:输入是 RealtimeHandoffRequested。它遍历 active_transcript,把每条整理成“角色: 文本”,用换行拼起来;如果结果为空返回 None,否则返回这段文本。

调用关系:realtime_delegation_from_handoff 会调用它,作为后台 agent 了解上下文的补充信息。

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

realtime_text_from_handoff_request1061–1065 ↗
fn realtime_text_from_handoff_request(handoff: &RealtimeHandoffRequested) -> Option<String>

作用:从 handoff 请求里挑出最适合交给后台 agent 的输入文字。优先用完整输入转写,没有就退回到活跃转写片段。

数据流:输入是 RealtimeHandoffRequested。它先看 input_transcript 是否非空;非空就返回它,否则调用 realtime_transcript_delta_from_handoff 找替代文本。输出是可选字符串。

调用关系:realtime_delegation_from_handoff 调用它来确定有没有东西值得交给后台。

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

realtime_delegation_from_handoff1067–1073 ↗
fn realtime_delegation_from_handoff(handoff: &RealtimeHandoffRequested) -> Option<String>

作用:把实时模型的 handoff 请求转换成后台 Codex agent 能处理的一段结构化文本。

数据流:输入是 handoff 请求。它先用 realtime_text_from_handoff_request 找主输入;没有输入就返回 None。有输入时再取可选转写增量,并调用 wrap_realtime_delegation_input 包成 XML 风格标签文本。

调用关系:handle_start_inner 的事件转发任务收到 HandoffRequested 时调用它,然后把生成的文本 route_realtime_text_input 给后台流程。

调用图:调用 3 个内部函数(realtime_text_from_handoff_request, realtime_transcript_delta_from_handoff, wrap_realtime_delegation_input);被 1 处调用(handle_start_inner)。

wrap_realtime_delegation_input1075–1085 ↗
fn wrap_realtime_delegation_input(input: &str, transcript_delta: Option<&str>) -> String

作用:把 handoff 输入包装成带标签的文本,方便后台 agent 明确识别哪部分是用户输入、哪部分是转写增量。

数据流:输入是 input 和可选 transcript_delta。它先用 escape_xml_text 转义特殊字符,避免文本里的 <、>、& 破坏标签结构;然后输出 <realtime_delegation> 包住的字符串。

调用关系:realtime_delegation_from_handoff 调用它生成最终交给后台 agent 的文本。

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

escape_xml_text1087–1092 ↗
fn escape_xml_text(input: &str) -> String

作用:转义 XML 风格文本里的特殊字符。简单说,就是把可能被误认为标签的字符变成安全写法。

数据流:输入是一段字符串切片。它依次把 & 替换成 &amp;,把 < 替换成 &lt;,把 > 替换成 &gt;。输出是安全后的字符串。

调用关系:wrap_realtime_delegation_input 在拼标签前调用它,防止用户内容破坏包装格式。

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

realtime_api_key1094–1118 ↗
fn realtime_api_key(auth: Option<&CodexAuth>, provider: &ModelProviderInfo) -> CodexResult<String>

作用:为实时连接找可用的 API key。它会按多个来源依次查找,尽量兼容不同登录方式。

数据流:输入是可选登录认证和模型 provider。它先查 provider 自带 api_key,再查实验 bearer token,再查 CodexAuth 里的 key;如果是 OpenAI provider,还临时尝试环境变量。都没有就返回“实时对话需要 API key”。

调用关系:prepare_realtime_start 在 WebSocket 传输需要认证头时调用它。

调用图:调用 2 个内部函数(api_key, is_openai);被 1 处调用(prepare_realtime_start);外部调用 2 个(read_openai_api_key_from_env, InvalidRequest)。

realtime_request_headers1120–1145 ↗
fn realtime_request_headers(
    realtime_session_id: Option<&str>,
    api_key: Option<&str>,
    version: RealtimeWsVersion,
) -> CodexResult<Option<HeaderMap>>

作用:构造连接实时服务时要带的 HTTP 请求头。请求头可以理解为网络请求上的附加说明和凭证。

数据流:输入是可选实时会话 id、可选 API key 和版本。它新建 HeaderMap;v1 会加入 openai-alpha 标记;有会话 id 就加入 x-session-id;有 API key 就生成 Authorization: Bearer ...。输出是 Some(headers)。

调用关系:prepare_realtime_start 调用它,为 WebSocket 或 WebRTC sideband 连接准备额外请求头。

调用图:被 1 处调用(prepare_realtime_start);外部调用 4 个(new, from_static, from_str, format!)。

handle_text1147–1162 ↗
async fn handle_text(
    sess: &Arc<Session>,
    sub_id: String,
    params: ConversationTextParams,
)

作用:处理外部送来的实时文字输入。它负责记录调试信息、送入会话,并把失败变成用户能收到的错误事件。

数据流:输入是 Session、订阅 id 和 ConversationTextParams。它调用 sess.conversation.text_in;失败时,如果会话仍在运行就只警告,否则发送 BadRequest 错误。

调用关系:submission_loop 收到文字消息时调用它;底层排队由 RealtimeConversationManager::text_in 做,错误事件由 send_conversation_error 发。

调用图:调用 1 个内部函数(send_conversation_error);被 1 处调用(submission_loop);外部调用 3 个(debug!, error!, warn!)。

handle_speech1164–1179 ↗
async fn handle_speech(
    sess: &Arc<Session>,
    sub_id: String,
    params: ConversationSpeechParams,
)

作用:处理外部送来的语音识别文本,把它追加到实时对话里。它和 handle_text 类似,但走的是 append_speech。

数据流:输入是 Session、订阅 id 和 ConversationSpeechParams。它把 params.text 传给 sess.conversation.append_speech;失败时按会话是否仍运行决定警告还是发送 BadRequest。

调用关系:submission_loop 收到 speech 消息时调用它;底层由 RealtimeConversationManager::append_speech 包装成后台输出。

调用图:调用 1 个内部函数(send_conversation_error);被 1 处调用(submission_loop);外部调用 3 个(debug!, error!, warn!)。

handle_close1181–1183 ↗
async fn handle_close(sess: &Arc<Session>, sub_id: String)

作用:处理外部“关闭实时对话”的请求。它把关闭原因标成 requested。

数据流:输入是 Session 和订阅 id。它调用 end_realtime_conversation,要求关闭当前实时会话并发送关闭事件。

调用关系:submission_loop 收到关闭消息时调用它;实际关闭和通知由 end_realtime_conversation 完成。

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

spawn_realtime_input_task1185–1187 ↗
fn spawn_realtime_input_task(input: RealtimeInputTask) -> JoinHandle<()>

作用:为普通 WebSocket 实时连接启动后台输入循环。这样主请求线程不用一直阻塞等网络。

数据流:输入是 RealtimeInputTask。它用 tokio::spawn 开一个异步任务运行 run_realtime_input_task。输出是 JoinHandle,也就是这个后台任务的句柄。

调用关系:RealtimeConversationManager::start_inner 在 WebSocket 模式连接成功后调用它。

调用图:调用 1 个内部函数(run_realtime_input_task);被 1 处调用(start_inner);外部调用 1 个(spawn)。

spawn_webrtc_sideband_input_task1202–1260 ↗
fn spawn_webrtc_sideband_input_task(input: RealtimeWebrtcSidebandInputTask) -> JoinHandle<()>

作用:为 WebRTC 模式启动 sideband 后台连接。sideband 可以理解为语音通话旁边的一条控制通道,用来传文字、事件和 handoff。

数据流:输入是 RealtimeWebrtcSidebandInputTask。它先检查会话是否仍活跃,再用 client.connect_webrtc_sideband 连接;失败时向事件队列发送错误。成功后继续调用 run_realtime_input_task 处理文字、handoff、音频和服务端事件。

调用关系:RealtimeConversationManager::start_inner 在 WebRTC 模式创建 realtime call 后调用它;连接完成后把工作交给 run_realtime_input_task。

调用图:调用 2 个内部函数(run_realtime_input_task, default_headers);被 1 处调用(start_inner);外部调用 4 个(map_api_error, Error, spawn, warn!)。

run_realtime_input_task1262–1324 ↗
async fn run_realtime_input_task(input: RealtimeInputTask)

作用:这是实时对话的后台主循环。它同时盯着四件事:用户文字、后台 handoff 输出、服务端事件、用户音频。

数据流:输入是 RealtimeInputTask,里面有写入器、事件流和三个输入队列。它初始化输出音频状态和回复创建队列,然后用 tokio::select! 等待任何一路有消息;根据来源分别调用 handle_text_input、handle_handoff_output、handle_realtime_server_event 或 handle_user_audio_input。任一路返回错误就退出循环。

调用关系:spawn_realtime_input_task 和 spawn_webrtc_sideband_input_task 都会启动它。它是文件里实时数据流的中心泵。

调用图:被 2 处调用(spawn_realtime_input_task, spawn_webrtc_sideband_input_task);外部调用 2 个(default, select!)。

handle_text_input1326–1345 ↗
async fn handle_text_input(
    params: Result<ConversationTextParams, RecvError>,
    writer: &RealtimeWebsocketWriter,
    events_tx: &Sender<RealtimeEvent>,
) -> anyhow::Result<()>

作用:把排队好的文字输入真正发给实时服务端。它只处理“从本地队列到 WebSocket”的这一小步。

数据流:输入是从 text_rx 收到的结果、写入器和事件队列。队列关闭则报错;收到文字后调用 writer.send_conversation_item_create。发送失败会映射错误、发 RealtimeEvent::Error,并返回失败。

调用关系:run_realtime_input_task 在文字队列有消息时调用它。

调用图:调用 1 个内部函数(send_conversation_item_create);外部调用 4 个(send, map_api_error, Error, warn!)。

handle_handoff_output1347–1443 ↗
async fn handle_handoff_output(
    handoff_output: Result<RealtimeOutbound, RecvError>,
    writer: &RealtimeWebsocketWriter,
    events_tx: &Sender<RealtimeEvent>,
    handoff_state: &RealtimeHandof

作用:把后台 agent 的输出按协议版本翻译成实时服务端能懂的消息。v1 和 v2 的发送方式差别很大,所以这里做分流。

数据流:输入是 handoff 输出队列结果、写入器、事件队列、handoff 状态、事件解析器和回复创建队列。它先取出 RealtimeOutbound,再按 V1 或 V2 选择发送 handoff append、function call output、conversation item,或请求 response.create。发送失败会转成错误事件。

调用关系:run_realtime_input_task 在 handoff_output_rx 有消息时调用它。它会在需要模型继续回应时调用 RealtimeResponseCreateQueue::request_create。

调用图:调用 4 个内部函数(send_conversation_function_call_output, send_conversation_handoff_append, send_conversation_item_create, request_create);外部调用 6 个(send, new, map_api_error, debug!, Error, warn!)。

handle_realtime_server_event1445–1625 ↗
async fn handle_realtime_server_event(
    event: Result<Option<RealtimeEvent>, ApiError>,
    writer: &RealtimeWebsocketWriter,
    events_tx: &Sender<RealtimeEvent>,
    handoff_state: &RealtimeHand

作用:处理实时服务端发回来的各种事件,并决定本地是否要更新状态、回发确认、截断音频或结束连接。

数据流:输入是服务端事件结果、写入器、事件队列、handoff 状态、会话版本、输出音频状态和回复创建队列。它先处理流结束或 API 错误;正常事件则按类型更新音频播放进度、处理用户打断、标记回复开始/结束、记录 handoff、回复 noop、转发错误等。最后把事件送入 events_tx;如果是错误事件则返回失败。

调用关系:run_realtime_input_task 在服务端有事件时调用它。它会调用 update_output_audio_state、RealtimeResponseCreateQueue::mark_started、mark_finished、request_create,并直接使用 writer 给服务端发确认。

调用图:调用 6 个内部函数(send_conversation_function_call_output, send_payload, mark_finished, mark_started, request_create, update_output_audio_state);外部调用 9 个(send, new, bail!, map_api_error, error!, info!, json!, Error, warn!)。

handle_user_audio_input1627–1643 ↗
async fn handle_user_audio_input(
    frame: Result<RealtimeAudioFrame, RecvError>,
    writer: &RealtimeWebsocketWriter,
    events_tx: &Sender<RealtimeEvent>,
) -> anyhow::Result<()>

作用:把用户麦克风音频帧真正发送给实时服务端。它是 audio_in 入队后的发送端。

数据流:输入是音频队列结果、写入器和事件队列。队列关闭则报错;收到帧后调用 writer.send_audio_frame。发送失败会映射错误、记录日志、发送 RealtimeEvent::Error,并返回失败。

调用关系:run_realtime_input_task 在 audio_rx 有消息时调用它。

调用图:调用 1 个内部函数(send_audio_frame);外部调用 4 个(send, map_api_error, error!, Error)。

update_output_audio_state1645–1668 ↗
fn update_output_audio_state(
    output_audio_state: &mut Option<OutputAudioState>,
    frame: &RealtimeAudioFrame,
)

作用:记录实时模型当前输出音频已经播放到哪里。这样用户打断时,可以告诉服务端截断到正确时间点。

数据流:输入是可变的 OutputAudioState 和一帧输出音频。它先取 frame.item_id,没有就不处理;再计算这一帧时长。如果还是同一个 item,就累加毫秒数;如果换了 item,就创建新的状态。

调用关系:handle_realtime_server_event 收到 AudioOut 事件时调用它;音频时长计算交给 audio_duration_ms。

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

audio_duration_ms1670–1679 ↗
fn audio_duration_ms(frame: &RealtimeAudioFrame) -> u32

作用:计算一帧音频大约有多少毫秒。它优先用帧里直接给的样本数,没有就尝试从音频数据本身推算。

数据流:输入是 RealtimeAudioFrame。它取 samples_per_channel,若没有则调用 decoded_samples_per_channel;拿到样本数后按 sample_rate 换算成毫秒。算不出来就返回 0。

调用关系:update_output_audio_state 用它来累加输出音频播放进度。

调用图:调用 1 个内部函数(decoded_samples_per_channel);被 1 处调用(update_output_audio_state);外部调用 1 个(from)。

decoded_samples_per_channel1681–1686 ↗
fn decoded_samples_per_channel(frame: &RealtimeAudioFrame) -> Option<u32>

作用:从 base64 编码的音频数据里推算每个声道有多少采样点。base64 是一种把二进制数据转成文本传输的编码。

数据流:输入是音频帧。它先解码 frame.data,按 16 位采样也就是每个样本 2 字节,再除以声道数,最后转成 u32。任何一步失败都返回 None。

调用关系:audio_duration_ms 在帧没有直接提供 samples_per_channel 时调用它。

调用图:被 1 处调用(audio_duration_ms);外部调用 2 个(try_from, from)。

send_conversation_error1688–1702 ↗
async fn send_conversation_error(
    sess: &Arc<Session>,
    sub_id: String,
    message: String,
    codex_error_info: CodexErrorInfo,
)

作用:向前端发送一个普通会话错误事件。它把错误消息和错误类型包装成协议里的 ErrorEvent。

数据流:输入是 Session、订阅 id、错误文字和 CodexErrorInfo。它构造 EventMsg::Error,并通过 sess.send_event_raw 发出去。没有返回业务数据。

调用关系:handle_audio、handle_text、handle_speech 在输入失败且不是正常收尾时调用它。

调用图:被 3 处调用(handle_audio, handle_speech, handle_text);外部调用 1 个(Error)。

end_realtime_conversation1704–1711 ↗
async fn end_realtime_conversation(
    sess: &Arc<Session>,
    sub_id: String,
    end: RealtimeConversationEnd,
)

作用:关闭实时会话并发送关闭通知。它是主动结束流程的小包装。

数据流:输入是 Session、订阅 id 和结束原因。它先调用 sess.conversation.shutdown 关闭内部状态,然后调用 send_realtime_conversation_closed 发 Closed 事件。

调用关系:handle_close 调用它;关闭通知的具体构造交给 send_realtime_conversation_closed。

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

send_realtime_conversation_closed1713–1729 ↗
async fn send_realtime_conversation_closed(
    sess: &Arc<Session>,
    sub_id: String,
    end: RealtimeConversationEnd,
)

作用:向前端发送“实时对话已关闭”的事件,并带上关闭原因。

数据流:输入是 Session、订阅 id 和 RealtimeConversationEnd。它把原因转换成 requested、transport_closed 或 error 字符串,构造 RealtimeConversationClosedEvent,然后通过 sess.send_event_raw 发出。

调用关系:end_realtime_conversation 在用户主动关闭时调用它;handle_start_inner 的 fanout 任务在连接自然结束或出错时也调用它。

调用图:被 2 处调用(end_realtime_conversation, handle_start_inner);外部调用 1 个(RealtimeConversationClosed)。

realtime-webrtc/src/lib.rs源码 ↗
io_transportsession startup and realtime connection handling

这个文件像一层门面。外面的人不需要知道底层怎么和系统音频、WebRTC 打交道,只要调用这里的类型和函数就行。WebRTC 可以理解成一种让两端实时传音视频的数据通道;SDP 是双方开通连接前交换的一份“连接说明书”。启动时,RealtimeWebrtcSession::start 会创建本地会话,拿到 offer_sdp,也就是本机先写好的连接说明,并返回一个 handle,之后用它继续控制会话。对方回传 answer_sdp 后,apply_answer_sdp 把这份回复交给底层,让连接真正完成。events 是一个消息接收口,会吐出 Connected、Closed、Failed 等状态,也会报告本地音量。local_audio_peak 用原子整数保存音量峰值,原子整数可以理解成“多人同时看也不容易读乱的数字”。重要的是,这个文件只在 macOS 上接到真正实现;非 macOS 平台不会假装能用,而是直接报 UnsupportedPlatform,避免后面出现更难懂的错误。

函数细节5
RealtimeWebrtcSessionHandle::fmt40–43 ↗
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result

作用:这个函数让 RealtimeWebrtcSessionHandle 能被安全地打印成调试信息。它不会把底层连接细节全暴露出来,只告诉你这是一个会话句柄。

数据流:进去的是一个会话句柄和一个打印用的格式化工具 → 它创建名为 RealtimeWebrtcSessionHandle 的调试结构 → 出来的是格式化结果,外部看到的是简略的调试文本,不会改变会话本身。

调用关系:当开发者用调试打印,比如 {:?},查看这个句柄时会走到这里。它内部只借助标准的 debug_struct 来拼出调试展示,不参与 WebRTC 连接流程。

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

RealtimeWebrtcSessionHandle::apply_answer_sdp47–57 ↗
fn apply_answer_sdp(&self, answer_sdp: String) -> Result<()>

作用:这个函数把对方发回来的 answer_sdp 交给当前 WebRTC 会话。通俗说,就是把“对方同意并补全的连接说明书”交回去,让两边能完成配对。

数据流:进去的是一段 answer_sdp 字符串和当前会话句柄 → 在 macOS 上,它把这段字符串转交给底层原生会话处理;在非 macOS 上,它丢弃输入并返回“不支持此平台” → 出来的是成功或错误结果,会话可能因此进入可连接状态。

调用关系:通常在 RealtimeWebrtcSession::start 先生成 offer_sdp 并发给远端之后调用它。它自己不解析所有细节,而是把活交给 macOS 的底层 apply_answer_sdp 实现;如果平台不支持,就在这里直接挡住。

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

RealtimeWebrtcSessionHandle::close59–62 ↗
fn close(&self)

作用:这个函数用来主动关闭实时 WebRTC 会话。就像挂断电话,调用后底层连接会被要求停下来。

数据流:进去的是当前会话句柄 → 在 macOS 上,它通知底层原生会话关闭;在非 macOS 上没有实际动作 → 没有返回值,但会话的底层资源可能被释放,后续事件可能出现 Closed。

调用关系:它通常由使用者在不再需要通话时调用,比如用户退出、页面关闭或上层清理资源时。真正关闭动作交给底层 close 实现,这个文件只提供统一按钮。

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

RealtimeWebrtcSessionHandle::local_audio_peak64–66 ↗
fn local_audio_peak(&self) -> Arc<AtomicU16>

作用:这个函数返回一个可以共享读取的本地音量峰值。外部可以用它显示麦克风音量条,或者判断本地是否正在说话。

数据流:进去的是当前会话句柄 → 它复制一份指向同一个原子音量数字的共享引用 → 出来的是 Arc<AtomicU16>,也就是一个可被多处共同持有、读写时更安全的 16 位整数引用;原来的音量数据没有被复制,只是多了一个入口。

调用关系:它不直接启动或控制连接,而是给界面或监控代码一个观察本地声音大小的通道。这个值在 start 创建句柄时初始化,之后其他部分可以拿着这个共享引用读取当前峰值。

RealtimeWebrtcSession::start72–89 ↗
fn start() -> Result<StartedRealtimeWebrtcSession>

作用:这个函数启动一个新的实时 WebRTC 会话。它是外部开始通话时最先调用的入口,会返回要发给对方的 offer_sdp、后续控制用的 handle,以及接收状态事件的通道。

数据流:进去不需要参数 → 在 macOS 上,它调用底层 start 创建原生会话,拿到 offer_sdp、底层 handle 和事件接收器,并额外新建一个初始为 0 的本地音量峰值数字包装进公开 handle → 出来的是 StartedRealtimeWebrtcSession;在非 macOS 上,直接返回 UnsupportedPlatform 错误。

调用关系:这是整个会话流程的起点。调用者先用它拿到 offer_sdp 发给远端,然后等待事件,并在收到远端 answer_sdp 后调用 RealtimeWebrtcSessionHandle::apply_answer_sdp。它把真正复杂的启动工作交给 native::start,并把结果包装成项目统一使用的结构。

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

realtime-webrtc/src/native.rs源码 ↗
io_transportstartup, main loop, request handling, teardown

可以把这个文件想成一个专门管 WebRTC 通话的小工人。外面的代码不直接碰复杂的 WebRTC 细节,而是启动这个小工人,拿到一段 offer SDP(通话邀请书,告诉对方“我怎么连”),再把对方返回的 answer SDP(对方的确认书)交回来。文件里用 mpsc channel(一个线程安全的消息管道,像信箱)把主流程和后台工作线程隔开,避免 WebRTC 的异步操作把外面搞复杂。后台线程里又开了 Tokio runtime(异步任务运行器),用来等待 WebRTC 创建 offer、设置远端 answer、定时读取统计信息。连接成功后,它每 200 毫秒读取一次本地音频音量,并转换成更好用的峰值数字发出去。它还会在失败或关闭时发事件,让上层知道现在发生了什么。

函数细节10
SessionHandle::apply_answer_sdp40–48 ↗
fn apply_answer_sdp(&self, answer_sdp: String) -> Result<()>

作用:把远端返回的 answer SDP 交给后台 WebRTC 工人,让它完成连接握手。调用者用它来告诉系统:“对方已经回信了,可以正式连了。”

数据流:进去的是一段 answer_sdp 字符串。函数先建一个临时回复管道,然后把“应用 answer”这条命令连同回复地址一起发给后台线程;后台处理完后,再从回复管道等结果。出来的是成功或错误;如果后台线程已经停了,就返回“worker stopped”这类错误。

调用关系:它是外部控制 WebRTC 会话的入口之一。它通过消息管道把活儿交给 worker_main;worker_main 收到后会调用 apply_answer 真正设置远端描述,成功后还会启动音量监听任务。

调用图:外部调用 2 个(send, channel)。

SessionHandle::close50–52 ↗
fn close(&self)

作用:通知后台 WebRTC 工人关闭这次会话。它是一个“按下挂断键”的动作。

数据流:进去不需要额外数据,只使用 SessionHandle 里保存的命令发送端。它往后台线程发一条 Close 命令;不等待确认,也不把发送失败当成需要处理的错误。结果是后台线程稍后会关闭连接并发出 Closed 事件。

调用关系:它把关闭请求交给 worker_main。worker_main 收到 Close 后会调用 peer_connection.close(),然后通知外面连接已经关闭。

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

start55–76 ↗
fn start() -> Result<StartedSession>

作用:启动一场新的本机 WebRTC 会话,并把最开始要发给对方的 offer SDP 准备好。调用者用它来“一键开局”。

数据流:进去没有业务参数。函数创建三条消息管道:一条给命令,一条给事件,一条专门等 offer;然后启动名为 codex-realtime-webrtc 的后台线程。它等待后台线程生成 offer SDP。出来的是 StartedSession,里面有 offer_sdp、可继续控制会话的 SessionHandle、以及可接收事件的 events;如果线程启动失败或后台提前失败,就返回错误。

调用关系:它是这个文件对外开会话的主入口。它启动 worker_main,让 worker_main 去创建 PeerConnection 和 offer;自己只负责搭好通道、等第一份 offer,然后把控制权和事件流交给上层。

调用图:被 1 处调用(start);外部调用 2 个(channel, new)。

worker_main78–134 ↗
fn worker_main(
    command_rx: mpsc::Receiver<Command>,
    events_tx: mpsc::Sender<RealtimeWebrtcEvent>,
    offer_tx: mpsc::Sender<Result<String>>,
)

作用:这是后台 WebRTC 工人的主循环。它负责真正创建连接、处理“应用 answer”和“关闭”命令,并把成功、失败、关闭、音量等事件发给外面。

数据流:进去是命令接收端、事件发送端、offer 发送端。它先创建 Tokio runtime;失败就把错误通过 offer 和事件发出去。然后它运行 create_peer_connection_and_offer,拿到 WebRTC 连接和 offer SDP,把 offer 发给启动者。之后它循环读取命令:收到 ApplyAnswer 就调用 apply_answer,成功后发 Connected 并启动本地音量任务;收到 Close 就关闭连接、发 Closed、退出。命令通道断开时,它也会关闭连接并发 Closed。

调用关系:它是整个文件的调度中心。start 启动它;SessionHandle::apply_answer_sdp 和 SessionHandle::close 都是给它发命令;它再把具体工作交给 create_peer_connection_and_offer、apply_answer、start_local_audio_level_task。

调用图:调用 3 个内部函数(apply_answer, create_peer_connection_and_offer, start_local_audio_level_task);外部调用 6 个(clone, send, format!, Message, Failed, new_multi_thread)。

create_peer_connection_and_offer136–174 ↗
async fn create_peer_connection_and_offer() -> Result<(PeerConnection, String)>

作用:创建 WebRTC 连接对象,并生成本机的 offer SDP。简单说,它准备好“我要怎么跟你通话”的邀请书。

数据流:进去没有参数。它创建 PeerConnectionFactory(WebRTC 连接工厂),再创建 PeerConnection(一次具体连接)。接着添加音频收发器,意思是这条连接既能发音频也能收音频;然后创建本地麦克风音轨并挂到发送端。最后创建 offer,设置为本地描述。出来的是 PeerConnection 和 offer 字符串;中间任何一步失败都会包装成可读的错误。

调用关系:它由 worker_main 在后台线程刚启动时调用。它完成 WebRTC 会话的初始准备,worker_main 拿到结果后把 offer 发给 start。它内部多次用 message_error 把底层错误改写成更容易读的错误信息。

调用图:被 1 处调用(worker_main);外部调用 4 个(with_platform_adm, default, new, vec!)。

apply_answer176–184 ↗
async fn apply_answer(peer_connection: &PeerConnection, answer_sdp: String) -> Result<()>

作用:把远端发回来的 answer SDP 设置到 WebRTC 连接里,完成握手的另一半。没有这一步,双方只交换了邀请,还没有真正确认连接。

数据流:进去是已有的 PeerConnection 和 answer_sdp 字符串。它先把字符串解析成 WebRTC 能理解的 SessionDescription,并标记类型是 Answer;然后调用 set_remote_description 设置为远端描述。出来是成功或错误;解析失败或设置失败都会变成带说明的错误。

调用关系:它由 worker_main 在收到 ApplyAnswer 命令时调用。调用成功后,worker_main 才会发 Connected 事件,并开始本地音量监测任务。

调用图:被 1 处调用(worker_main);外部调用 2 个(set_remote_description, parse)。

message_error186–188 ↗
fn message_error(prefix: &str, err: impl Display) -> RealtimeWebrtcError

作用:把底层库给出的错误包装成项目自己的 RealtimeWebrtcError,并加上一句上下文说明。这样上层看到错误时,不只是看到生硬的底层报错。

数据流:进去是一段前缀说明和一个可显示的错误对象。它把两者拼成类似“failed to create WebRTC offer: 具体原因”的文字,再放进 RealtimeWebrtcError::Message。出来是统一格式的错误值。

调用关系:它是本文件里的小工具函数。create_peer_connection_and_offer 和 apply_answer 在调用 WebRTC 库失败时会用它,让错误信息更像人话。

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

start_local_audio_level_task190–213 ↗
fn start_local_audio_level_task(
    runtime: &tokio::runtime::Runtime,
    peer_connection: PeerConnection,
    events_tx: mpsc::Sender<RealtimeWebrtcEvent>,
)

作用:连接成功后,启动一个后台小任务,定时读取本机麦克风音量并发事件。它让界面或上层逻辑可以显示“你这边有没有声音”。

数据流:进去是 Tokio runtime、PeerConnection、事件发送端。它在 runtime 里 spawn(启动一个异步后台任务),每 200 毫秒醒来一次;如果连接已经 Closed 或 Failed,就停止;否则调用 local_audio_level 读取音量,有结果就发送 RealtimeWebrtcEvent::LocalAudioLevel。出来没有直接返回值,但会持续向事件通道推送音量事件。

调用关系:它由 worker_main 在 apply_answer 成功后启动。它自己不解析统计细节,而是把读取音量的工作交给 local_audio_level;拿到结果后通过事件通道通知外部。

调用图:调用 1 个内部函数(local_audio_level);被 1 处调用(worker_main);外部调用 6 个(spawn, send, matches!, LocalAudioLevel, from_millis, interval)。

local_audio_level215–223 ↗
async fn local_audio_level(peer_connection: &PeerConnection) -> Option<u16>

作用:从 WebRTC 的统计信息里找出本地音频源的音量。它是“去仪表盘里读麦克风音量”的那一步。

数据流:进去是 PeerConnection。它异步调用 get_stats 取得一批统计数据;如果读取失败就返回 None。然后它在统计项里寻找类型为 MediaSource 且 kind 是 audio 的项目,拿出 audio_level,并交给 audio_level_to_peak 转成峰值数字。出来是 Option<u16>:有音频数据就是 Some(峰值),没有或失败就是 None。

调用关系:它由 start_local_audio_level_task 周期性调用。它负责从 WebRTC 原始统计里挑出音频音量,并依赖 audio_level_to_peak 做数值转换。

调用图:被 1 处调用(start_local_audio_level_task);外部调用 1 个(get_stats)。

audio_level_to_peak225–227 ↗
fn audio_level_to_peak(audio_level: f64) -> u16

作用:把 WebRTC 给出的 0.0 到 1.0 之间的小数音量,转换成更常见的 16 位峰值数字。这样上层更容易拿它画音量条。

数据流:进去是 audio_level 小数。函数先把它限制在 0.0 到 1.0,防止异常值越界;再乘以 i16 最大值,四舍五入,最后转成 u16。出来是一个 0 到大约 32767 的整数峰值。

调用关系:它被 local_audio_level 使用,是音量读取流程的最后一步。local_audio_level 负责找数据,它负责把数据变成上层更好用的格式。