Codex 系统手册

生成的后端与 protobuf 契约

stage-18.324 个文件

这一层不是主循环,也不是开关机,而是幕后“对表”的公共部分。后端接口会返回很多数据,比如额度、限流、任务、拉取请求、配置文件等,OpenAPI 生成的模型先把这些数据规定成固定样子,目录文件再统一出口给别人用。客户端的 types.rs 负责按这些样子拆包,并把复杂结果整理成文字、补丁和错误。protobuf/gRPC 文件则规定远程配置加载和中继通信的消息格式,relay_proto 再包一层,方便项目内部引用。这样各处传话都不容易说岔。

本阶段的文件24

后端类型门面

这些文件介绍面向后端的手写类型层,以及它所基于的生成模型 crate 接口。

backend-client/src/types.rs源码 ↗
data_modelrequest handling / cross-cutting

这个文件主要解决一个现实问题:后端接口返回的 JSON 数据不总是干净统一,有些字段可能没有,有些列表可能是空值,有些账号数据甚至可能有两种格式。这里用 Rust 的结构体和反序列化(把 JSON 变成程序里的数据)规则,把这些返回值变成客户端能放心使用的对象。它还把任务详情里的内容进一步“提炼”:比如从助手回合里找出代码补丁 diff,从消息碎片里拼出普通文本,从失败回合里拼出错误摘要。文件前半部分复用 OpenAPI 自动生成的类型,后半部分手写更好用的任务详情模型,因为自动生成的模型不够顺手。整体上,它是后端数据进入客户端后的第一道整理关口。

函数细节23
RawAccounts::default91–93 ↗
fn default() -> Self

作用:给原始账号列表准备一个默认值:没有账号数据时,就当作一个空列表。这样后端少返回字段时,客户端不会因为“没东西可读”而出错。

数据流:进去没有外部输入 → 它新建一个空的账号列表包装起来 → 出来的是 RawAccounts::List,里面没有任何账号,也不会改动别的东西。

调用关系:它主要配合 RawAccountsCheckResponse 的反序列化使用。当后端响应里缺少 accounts 字段时,serde(负责把 JSON 转成 Rust 数据的库)会用它补一个安全的空值。

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

AccountsCheckResponse::deserialize113–139 ↗
fn deserialize(deserializer: D) -> Result<Self, D::Error>

作用:把后端返回的账号检查结果读成统一格式。它特别处理了两种账号返回方式:一种已经是列表,另一种是按账号 ID 存在表里。

数据流:进去的是一段待解析的后端响应 → 它先读成 RawAccountsCheckResponse,再看 accounts 是列表还是映射表;如果是映射表,就按 account_ordering 指定的顺序取账号,并丢掉没有真实 account_id 的项 → 出来的是 AccountsCheckResponse,里面有统一的账号列表、排序信息和默认账号 ID。

调用关系:这是账号检查接口入口处的“翻译器”。外部代码只需要拿 AccountsCheckResponse,不必关心后端这次到底用了列表格式还是映射格式。它内部把具体解析工作交给 serde 的 deserialize。

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

ContentFragment::text245–267 ↗
fn text(&self) -> Option<&str>

作用:从一个内容碎片里取出真正可显示的文字。它会过滤掉不是文本的结构化内容,也会忽略空白字符串。

数据流:进去的是一个 ContentFragment,可能是纯字符串,也可能是带 content_type 的结构化内容 → 它判断这块内容是不是文本,并检查文本是不是空的 → 出来是可用的文字引用,或者没有文字时返回 None。

调用关系:这是后面提取消息内容的基础小零件。TurnItem::text_values 和 WorklogMessage::text_values 都靠它把零散内容筛成真正的文字。

TurnItem::text_values271–276 ↗
fn text_values(&self) -> Vec<String>

作用:把一个回合条目里的所有文本片段收集成字符串列表。比如一条消息可能被拆成好几段,它负责把这些段挑出来。

数据流:进去的是一个 TurnItem,它里面有 content 列表 → 它逐个查看内容碎片,调用 ContentFragment::text 只保留有意义的文本 → 出来是 Vec<String>,也就是一组文字;原数据不被修改。

调用关系:它是 Turn::message_texts 和 Turn::user_prompt 的帮手。上层函数想要“消息文字”,不用自己理解内容碎片的各种格式,只调用它就行。

TurnItem::diff_text278–293 ↗
fn diff_text(&self) -> Option<String>

作用:从一个回合条目里找代码补丁内容,也就是 diff。它兼容两种后端格式:直接的 output_diff,以及 PR 类型里包着的 output_diff。

数据流:进去的是一个 TurnItem → 如果 kind 是 output_diff,就读它自己的 diff 字段;如果 kind 是 pr,就读 output_diff.diff;空字符串会被当作没有内容 → 出来是补丁文本,或者 None。

调用关系:它被 Turn::unified_diff 用来在一堆输出条目里找第一个可用补丁。这样上层不用知道后端把 diff 放在哪种字段里。

Turn::unified_diff297–299 ↗
fn unified_diff(&self) -> Option<String>

作用:从一个回合的输出里找出统一格式的代码补丁。统一 diff 可以理解成“文件哪里删了、哪里加了”的文本说明。

数据流:进去的是一个 Turn,它有多个 output_items → 它按顺序检查每个输出条目,调用 TurnItem::diff_text 找补丁 → 出来是找到的第一个 diff,找不到就返回 None。

调用关系:它是 CodeTaskDetailsResponse::unified_diff 的下一级工具。整个任务详情会先决定看哪个回合,再由它在单个回合里找具体补丁。

Turn::message_texts301–318 ↗
fn message_texts(&self) -> Vec<String>

作用:收集一个回合里助手说过的普通文字消息,不包括代码补丁。它还会把 worklog(工作日志)里助手写的文字也算进去。

数据流:进去的是一个 Turn → 它先从 output_items 里挑 kind 为 message 的条目并取文本;然后如果有 worklog,就只取作者是 assistant 的日志消息文本 → 出来是一组助手文字消息,原回合不变。

调用关系:它被 CodeTaskDetailsResponse::assistant_text_messages 调用。TurnItem::text_values 负责拆普通输出,WorklogMessage::is_assistant 和 WorklogMessage::text_values 负责拆工作日志。

Turn::user_prompt320–343 ↗
fn user_prompt(&self) -> Option<String>

作用:从用户回合里提取用户输入的提示词。它会把多个文本片段用空行隔开,拼成一段更接近人类阅读习惯的文字。

数据流:进去的是一个 Turn → 它从 input_items 里筛选 kind 为 message、角色是 user 或没有标角色的条目,再提取文本片段 → 如果没有文本,出来是 None;如果有,就用两个换行拼接成一个字符串。

调用关系:它被 CodeTaskDetailsResponse::user_text_prompt 调用,用来给外部提供“用户到底问了什么”。它依赖 TurnItem::text_values 处理内容碎片。

Turn::error_summary345–347 ↗
fn error_summary(&self) -> Option<String>

作用:从一个回合里取出错误摘要。它不自己拼错误,而是把工作交给 TurnError::summary。

数据流:进去的是一个 Turn → 它查看 error 字段是否存在;存在就调用 TurnError::summary,不存在就返回 None → 出来是简短错误文字或空。

调用关系:它被 CodeTaskDetailsResponse::assistant_error_message 使用。这个分层让“从回合拿错误”和“怎么拼错误文字”分开。

WorklogMessage::is_assistant351–357 ↗
fn is_assistant(&self) -> bool

作用:判断一条工作日志消息是不是助手写的。这样提取助手回复时,不会把用户或其他角色的文字混进去。

数据流:进去的是一条 WorklogMessage → 它查看 author.role,并且不区分大小写地比较是否等于 assistant → 出来是 true 或 false;缺少作者或角色时默认 false。

调用关系:它被 Turn::message_texts 用在工作日志过滤阶段。只有它判断为助手的日志,才会继续提取文本。

WorklogMessage::text_values359–370 ↗
fn text_values(&self) -> Vec<String>

作用:从一条工作日志消息里提取所有可读文字。日志内容也可能被拆成多个片段,所以这里负责把片段筛出来。

数据流:进去的是一条 WorklogMessage → 如果有 content,就遍历 content.parts,并调用 ContentFragment::text 取出非空文本;如果没有 content,就当作空列表 → 出来是一组字符串。

调用关系:它通常跟 WorklogMessage::is_assistant 一起被 Turn::message_texts 使用:先判断是不是助手,再提取文字。

TurnError::summary374–383 ↗
fn summary(&self) -> Option<String>

作用:把错误代码和错误消息合成一段适合显示的短文本。比如同时有代码和消息时,会变成“代码: 消息”。

数据流:进去的是一个 TurnError,里面可能有 code,也可能有 message → 它把缺失字段当作空字符串,并按四种情况处理:都没有就返回 None,只一个有就返回那个,两个都有就格式化成一行 → 出来是错误摘要或 None。

调用关系:它被 Turn::error_summary 调用,再间接服务于 CodeTaskDetailsResponse::assistant_error_message。它使用 format! 只是为了拼接“代码: 消息”这种文本。

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

CodeTaskDetailsResponse::unified_diff398–406 ↗
fn unified_diff(&self) -> Option<String>

作用:从整个任务详情里提取最应该展示的代码补丁。它优先看专门的 diff 回合,找不到时再看助手回合。

数据流:进去的是 CodeTaskDetailsResponse,里面可能有 current_diff_task_turn 和 current_assistant_turn → 它按“diff 回合优先、助手回合其次”的顺序检查,并调用 Turn::unified_diff → 出来是第一个找到的补丁文本,或 None。

调用关系:这是外部代码最可能直接调用的便捷方法。它把多个回合的选择规则藏起来,让调用方只问一句“有没有 diff”。

CodeTaskDetailsResponse::assistant_text_messages408–420 ↗
fn assistant_text_messages(&self) -> Vec<String>

作用:从任务详情里收集助手输出的普通文字消息。它会看 diff 回合和助手回合,把能显示的文字合在一起。

数据流:进去的是 CodeTaskDetailsResponse → 它新建一个空列表,然后依次检查 current_diff_task_turn 和 current_assistant_turn;每个存在的回合都调用 Turn::message_texts,把结果追加进去 → 出来是一组助手文字消息。

调用关系:这是给界面或日志展示助手回复用的上层入口。真正的文本提取由 Turn::message_texts、TurnItem::text_values 和 WorklogMessage::text_values 完成。

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

CodeTaskDetailsResponse::user_text_prompt422–424 ↗
fn user_text_prompt(&self) -> Option<String>

作用:从任务详情里拿到用户输入的提示词。没有用户回合时,它会直接说没有。

数据流:进去的是 CodeTaskDetailsResponse → 它查看 current_user_turn 是否存在;存在就调用 Turn::user_prompt 提取并拼接文本 → 出来是用户提示词字符串,或 None。

调用关系:这是外部代码查看“用户原始需求”的便捷入口。具体怎么从输入条目里筛文字,交给 Turn::user_prompt。

CodeTaskDetailsResponse::assistant_error_message426–430 ↗
fn assistant_error_message(&self) -> Option<String>

作用:从任务详情里取助手回合的失败信息。它只看当前助手回合,因为错误通常挂在那里。

数据流:进去的是 CodeTaskDetailsResponse → 它查看 current_assistant_turn 是否存在;存在就调用 Turn::error_summary → 出来是一段错误摘要,或 None。

调用关系:这是给调用方展示失败原因的上层入口。它把错误提取交给 Turn 和 TurnError,自己只负责选择正确的回合。

deserialize_vec433–439 ↗
fn deserialize_vec(deserializer: D) -> Result<Vec<T>, D::Error>

作用:把可能缺失或为 null 的 JSON 列表安全地读成空列表。这样代码后面就能直接遍历,不用处处判断有没有列表。

数据流:进去的是反序列化器和目标元素类型 → 它先尝试把字段读成 Option<Vec<T>>;如果是 Some 就取出列表,如果是 None 就换成空列表 → 出来永远是 Vec<T>,不会是空值。

调用关系:它被多个结构体字段通过 serde 的 deserialize_with 使用,比如 sibling_turn_ids、input_items、output_items、worklog messages。它是这个文件里处理“后端字段可能不给”的保险垫。

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

tests::fixture473–480 ↗
fn fixture(name: &str) -> CodeTaskDetailsResponse

作用:测试用的小助手:按名字加载一份任务详情 JSON 样本,并解析成 CodeTaskDetailsResponse。

数据流:进去的是样本名字,比如 diff 或 error → 它用 include_str! 在编译时把对应 JSON 文件读进来,再用 serde_json::from_str 解析;如果名字不认识就 panic,也就是让测试立刻失败 → 出来是一份解析好的任务详情。

调用关系:下面几个测试都靠它准备输入数据。它让每个测试不用重复写读文件和解析 JSON 的代码。

调用图:外部调用 3 个(include_str!, panic!, from_str)。

tests::unified_diff_prefers_current_diff_task_turn483–487 ↗
fn unified_diff_prefers_current_diff_task_turn()

作用:确认提取 diff 时会优先使用专门的 current_diff_task_turn。这样如果任务里有多个可能的补丁来源,结果不会乱选。

数据流:进去没有运行时输入 → 它加载 diff 样本,调用 details.unified_diff,再检查结果里包含“diff --git” → 如果检查通过测试成功,否则失败。

调用关系:它验证 CodeTaskDetailsResponse::unified_diff 的优先级规则。样本数据由 tests::fixture 提供。

调用图:外部调用 2 个(assert!, fixture)。

tests::unified_diff_falls_back_to_pr_output_diff490–494 ↗
fn unified_diff_falls_back_to_pr_output_diff()

作用:确认没有直接 output_diff 时,代码还能从 PR 类型的 output_diff 里找到补丁。这个测试防止兼容格式被改坏。

数据流:进去没有运行时输入 → 它加载 error 样本,调用 details.unified_diff,并检查补丁里包含“lib.rs” → 检查不满足就让测试失败。

调用关系:它主要覆盖 TurnItem::diff_text 里处理 kind 为 pr 的分支,也间接验证 CodeTaskDetailsResponse::unified_diff 的兜底行为。

调用图:外部调用 2 个(assert!, fixture)。

tests::assistant_text_messages_extracts_text_content497–501 ↗
fn assistant_text_messages_extracts_text_content()

作用:确认助手普通文本能被正确提取出来,而且不会把不该显示的内容混进去。

数据流:进去没有运行时输入 → 它加载 diff 样本,调用 assistant_text_messages,得到消息列表 → 它用 assert_eq! 检查列表正好是“Assistant response”。

调用关系:它验证 CodeTaskDetailsResponse::assistant_text_messages 以及下游的 Turn::message_texts、ContentFragment::text 是否配合正确。

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

tests::user_text_prompt_joins_parts_with_spacing504–513 ↗
fn user_text_prompt_joins_parts_with_spacing()

作用:确认用户提示词的多个片段会用空行拼起来。这样显示出来的提示词更清楚,不会挤成一团。

数据流:进去没有运行时输入 → 它加载 diff 样本,调用 user_text_prompt → 它检查结果是不是“First line”、空行、“Second line”这种格式。

调用关系:它专门验证 Turn::user_prompt 的拼接规则,也通过 CodeTaskDetailsResponse::user_text_prompt 走了一遍外部会用的入口。

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

tests::assistant_error_message_combines_code_and_message516–522 ↗
fn assistant_error_message_combines_code_and_message()

作用:确认助手错误信息会把错误代码和错误消息合成一行。这样用户看到的不只是冷冰冰的代码,也有具体原因。

数据流:进去没有运行时输入 → 它加载 error 样本,调用 assistant_error_message → 它检查结果正好是“APPLY_FAILED: Patch could not be applied”。

调用关系:它验证 CodeTaskDetailsResponse::assistant_error_message、Turn::error_summary 和 TurnError::summary 这一整条错误提取链路。

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

codex-backend-openapi-models/src/lib.rs源码 ↗
data_modelcross-cutting

这个文件的作用很像一本书的封面和目录入口:读者不需要知道里面每一章文件放在哪里,只要从这里进入,就能找到 OpenAPI 模型。OpenAPI 可以理解成一份“接口说明书”,里面会定义请求和返回的数据长什么样。这个库把这些数据结构生成到 src/models 目录下,而 lib.rs 只做一件事:公开 pub mod models,让别的代码可以使用这些模型。开头允许 unwrap 和 expect,是因为生成代码里可能会用到这类写法,项目选择在这里不让代码检查工具为此报错。重要的是,这里明确说明没有手写类型,避免以后有人误以为模型逻辑藏在这个文件里。

codex-backend-openapi-models/src/models/mod.rs源码 ↗
data_modelcross-cutting

这个文件本身不写具体算法,也不处理请求。它的作用更像仓库门口的货架索引:哪些数据结构可以对外使用,都在这里登记并转发出去。这里的“模型”指的是 API 收发数据时用的固定格式,比如配置文件响应、云端任务信息、拉取请求信息、限额状态等。文件先声明内部小模块,再用 pub use 把里面真正的类型重新导出,让同一个工作区里的其他代码可以直接引用这些名字。注释说明它以前可能是 OpenAPI generator(根据接口说明自动生成代码的工具)生成的,但现在被人工精简过,只保留当前项目真的会用到的类型。这样做的好处是入口清楚、依赖少;坏处是如果以后新代码需要别的 API 类型,必须记得在这里补上导出。

速率和支出状态模型

这些生成的 schema 定义后端响应使用的嵌套传输类型,用于速率限制、额度可用性和支出控制报告。

codex-backend-openapi-models/src/models/additional_rate_limit_details.rs源码 ↗
data_modelrequest handling / response serialization

这个文件是 OpenAPI 工具生成的数据模型文件。OpenAPI 可以理解成一份接口说明书,生成出来的模型就像统一印好的表单。这里的表单叫 AdditionalRateLimitDetails,表示某个限流规则的补充说明:limit_name 是限流规则的名字,metered_feature 是被计量的功能,rate_limit 则可能带着更详细的限流状态。特别的是,rate_limit 用了两层 Option:外层表示这个字段在数据里有没有出现,内层表示出现了但值是不是 null。这样能区分“没告诉你”和“明确告诉你为空”,这在接口通信里很重要。文件还让这个结构可以被序列化和反序列化,也就是能在 Rust 数据和 JSON 这类传输格式之间来回转换。

函数细节1
AdditionalRateLimitDetails::new31–37 ↗
fn new(limit_name: String, metered_feature: String) -> AdditionalRateLimitDetails

作用:这个函数用来快速创建一份额外限流信息。调用者只需要提供限流规则名和被计量的功能名,详细限流状态先默认不填。

数据流:输入是两个字符串:limit_name 和 metered_feature。函数把它们放进一个新的 AdditionalRateLimitDetails 结构里,并把 rate_limit 设成 None,意思是暂时没有提供详细限流状态。输出就是这份新建好的数据对象。

调用关系:它是这个数据模型自带的便捷构造入口。其他代码在需要组装接口返回值或内部传递限流补充信息时,可以先调用它生成基础对象;如果后面拿到了更详细的 RateLimitStatusDetails,再由调用者自己填进 rate_limit。

codex-backend-openapi-models/src/models/credit_status_details.rs源码 ↗
generatedrequest handling / serialization

这个文件是 OpenAPI 工具生成的模型文件。可以把它理解成一张固定格式的表格,用来装“信用额度状态”的信息。最核心的两个字段是 has_credits 和 unlimited:前者表示还有没有可用额度,后者表示是不是无限额度。后面的 balance、approx_local_messages、approx_cloud_messages 都是可选信息,可能没有,也可能明确是空值。这里用了 serde,也就是 Rust 里常用的“把数据结构和 JSON 互相转换”的工具,这样后端和外部接口交换数据时,字段名能按接口要求变成 has_credits、balance 这些名字。特别要注意的是,几个可选字段用了双层 Option:这不是多余,而是为了区分“这个字段根本没传”和“字段传了,但值是 null”。这对接口兼容很重要,否则程序可能分不清对方是忘了给,还是故意说没有。

函数细节1
CreditStatusDetails::new43–51 ↗
fn new(has_credits: bool, unlimited: bool) -> CreditStatusDetails

作用:这是创建一份额度状态详情的便捷方法。调用者只需要先给出两个必填信息:有没有额度、是否无限额度,其他可选内容先留空。

数据流:进去的是 has_credits 和 unlimited 两个布尔值,也就是两个“是或否”的答案。函数把它们放进新的 CreditStatusDetails 结构里,并把 balance、approx_local_messages、approx_cloud_messages 都设成 None,表示这些可选字段暂时没有提供。出来的是一份可以继续填写、也可以直接序列化成接口数据的额度状态对象。

调用关系:它通常会在代码需要拼出一份 CreditStatusDetails 时被调用,比如准备返回给接口调用方之前。它自己不再调用别的业务函数,只是做最基础的对象初始化,相当于先拿出一张空表,把必填格子填好。

codex-backend-openapi-models/src/models/rate_limit_window_snapshot.rs源码 ↗
generatedrequest handling / serialization

这个文件是 OpenAPI 工具生成的模型文件。你可以把它理解成一张标准表格,专门记录“请求额度”的当前状态。比如系统规定一分钟只能请求 100 次,那这里会告诉外部:已经用了多少百分比,这个限制窗口有多长,还要多少秒重置,以及具体什么时候重置。结构体 RateLimitWindowSnapshot 就是这个数据盒子。它带有序列化和反序列化能力,意思是它可以方便地在 Rust 程序里的数据和网络上传输的 JSON 数据之间来回转换。字段上的 serde rename 指明了 JSON 里的名字,比如 used_percent 对应“已用百分比”。new 函数只是一个方便的构造器,让代码可以一次性把四个数字填进去,得到一个完整的快照对象。

函数细节1
RateLimitWindowSnapshot::new26–38 ↗
fn new(
        used_percent: i32,
        limit_window_seconds: i32,
        reset_after_seconds: i32,
        reset_at: i32,
    ) -> RateLimitWindowSnapshot

作用:这个函数用来创建一个新的限流窗口快照。调用者把四个数字交给它,它会打包成一个 RateLimitWindowSnapshot 对象,方便后面返回给接口调用方或继续传递。

数据流:进去的是四个整数:已使用百分比、限制窗口秒数、距离重置还有多少秒、重置时间。函数不做计算,也不检查数值是否合理,只是把这些值按字段名放进结构体里。出来的是一个填好内容的 RateLimitWindowSnapshot;它不会修改别的东西。

调用关系:它是这个数据模型自带的便捷入口。其他代码在需要生成一份限流状态说明时,会调用它来组装数据;组装好以后,serde 相关机制可以把这个对象转成 JSON,发给客户端或其他服务。

codex-backend-openapi-models/src/models/rate_limit_status_details.rs源码 ↗
data_modelrequest handling

这个文件是 OpenAPI 工具生成的模型文件,也就是前后端或服务之间传数据时用的“标准表格”。它描述一次限流判断的结果:allowed 表示请求是否被允许;limit_reached 表示是否已经达到限制;primary_window 和 secondary_window 可以附带两个限流时间窗口的快照。时间窗口可以理解成“某段时间内最多能用多少次”的计数器,比如一分钟最多 60 次。这里用了 serde(Rust 里常用的序列化工具,负责把结构体和 JSON 互相转换),所以这个结构可以方便地从接口 JSON 读进来,也可以再写成 JSON 发出去。primary_window 和 secondary_window 外面套了两层 Option,是为了区分“字段没出现”和“字段出现了但值是 null”,这对接口兼容很重要。

函数细节1
RateLimitStatusDetails::new38–45 ↗
fn new(allowed: bool, limit_reached: bool) -> RateLimitStatusDetails

作用:这个函数用最基本的两个信息创建一个限流状态对象:请求是否允许,以及是否到达限制。它把两个可选的窗口详情先留空,方便之后需要时再补上。

数据流:输入 allowed 和 limit_reached 两个布尔值,也就是两个“是/否”答案;函数把它们放进新的 RateLimitStatusDetails 里;输出这个新对象,同时 primary_window 和 secondary_window 都设为 None,表示暂时没有提供窗口快照。

调用关系:当代码需要手动构造一个限流结果时,会调用 RateLimitStatusDetails::new 先搭出最小可用的数据对象。它不调用别的函数,也不做判断,只是把传进来的状态整理成符合接口格式的结构体。

codex-backend-openapi-models/src/models/spend_control_limit_details.rs源码 ↗
data_modelrequest handling / serialization

这个文件是 OpenAPI 工具自动生成的数据模型文件。可以把它理解成一张固定格式的表单,名字叫 SpendControlLimitDetails,专门记录一项消费限制的状态:额度上限、已使用量、剩余额度、使用百分比、剩余百分比,以及多久后重置、具体何时重置。它还支持 serde,也就是 Rust 里常用的“把结构体和 JSON 互相转换”的工具,所以这个结构可以很方便地从接口返回的 JSON 读进来,也可以再写成 JSON 发出去。比较特别的是 source 字段用了“双层 Option”:外层表示这个字段有没有出现,内层表示它出现了但值是不是 null。这样能准确区分“没给这个字段”和“明确给了空值”,对接口兼容很重要。new 函数提供了一个简单入口,用必填信息创建这张数据卡片,source 默认先不填。

函数细节1
SpendControlLimitDetails::new40–59 ↗
fn new(
        limit: String,
        used: String,
        remaining: String,
        used_percent: i32,
        remaining_percent: i32,
        reset_after_seconds: i32,
        reset_at: i32,

作用:这个函数用来新建一份消费额度详情。调用者只需要提供主要的额度和重置信息,它会把这些值装进 SpendControlLimitDetails 这个数据结构里,并把可选的 source 先留空。

数据流:进去的是额度上限 limit、已用 used、剩余 remaining、两个百分比、以及重置时间相关的数字 → 函数把这些值逐个放到同名字段里 → 出来的是一个完整的 SpendControlLimitDetails 对象,其中 source 被设为 None,表示目前没有提供来源信息。

调用关系:它是这个数据模型的便捷构造入口。其他代码在需要创建一份消费额度详情、准备返回给接口或进行序列化时,会调用它;它本身不再调用别的项目函数,只是把传入的数据组装成结构体。

codex-backend-openapi-models/src/models/spend_control_status_details.rs源码 ↗
generatedrequest/response data serialization

这个文件是 OpenAPI 工具自动生成的数据模型文件。可以把它理解成一张固定格式的表单:里面最重要的一项是 reached,表示花费控制有没有“达到上限”;另一项 individual_limit 是可选的个人额度详情,可能没有,也可能明确为空,也可能带着一份具体的额度说明。这里用到了序列化和反序列化,也就是把程序里的结构变成接口常用的 JSON,或把 JSON 读回程序里的结构。特别要注意 individual_limit 的双层 Option:它不是简单的“有或没有”,而是能区分“字段没传”和“字段传了但值是空”。这对接口很重要,因为两种情况在业务上可能含义不同。

函数细节1
SpendControlStatusDetails::new29–34 ↗
fn new(reached: bool) -> SpendControlStatusDetails

作用:这是创建 SpendControlStatusDetails 的快捷方法。调用者只要告诉它花费限制是否已经达到,它就会生成一份默认的状态详情。

数据流:输入一个布尔值 reached,表示是否达到花费上限 → 函数把这个值放进新结构里,并把 individual_limit 设为 None,表示暂时不提供个人额度详情 → 输出一个完整的 SpendControlStatusDetails 对象,供后续返回接口或继续填充。

调用关系:它是这个数据模型自带的构造入口,通常在代码需要新建一份花费控制状态时使用。它不调用别的函数,也不做复杂判断,只负责先把这份“表单”的必填项填好。

codex-backend-openapi-models/src/models/rate_limit_status_payload.rs源码 ↗
data_modelrequest handling

这个文件是 OpenAPI 生成出来的数据模型文件。可以把它理解成一张标准表格:后端返回限流信息时,字段名、字段类型、可选内容都按这里来。核心结构是 RateLimitStatusPayload,里面一定有 plan_type,也就是用户套餐类型;其他内容像 rate_limit、credits、spend_control 可能有,也可能没有,甚至可能明确是空值。这里用 serde 做序列化和反序列化,也就是把 Rust 里的结构和接口里的 JSON 互相转换。比较特别的是一些字段用了“双层 Option”,它能区分“这个字段没传”和“这个字段传了,但值是 null”,这对接口兼容很重要。文件还定义了套餐枚举 PlanType,以及触发限制的原因 RateLimitReachedKind。遇到新值时会落到 Unknown,避免服务因为不认识新类型就直接崩掉。

函数细节1
RateLimitStatusPayload::new57–66 ↗
fn new(plan_type: PlanType) -> RateLimitStatusPayload

作用:这个函数用来快速创建一份最基础的限流状态数据。调用者只需要先提供用户套餐类型,其他可选信息先留空,之后需要时再补。

数据流:进去的是一个 plan_type,也就是用户当前属于哪种套餐。函数把这个套餐放进新的 RateLimitStatusPayload 里,并把限流详情、额度详情、消费控制、额外限制、触发原因这些字段都设成 None,表示暂时没有提供这些信息。出来的是一份可以继续填充、也可以直接序列化成接口数据的结构体。

调用关系:它是这个数据模型的便捷构造入口。其他代码在准备返回或传递限流状态时,可以先调用它做出一个“空壳但套餐已确定”的对象;这个函数本身不再调用别的业务函数,只负责把各个字段摆到初始状态。

任务和 pull request 响应

这些模型涵盖任务列表、详细任务载荷,以及附加到助手工作的 pull request 结构。

codex-backend-openapi-models/src/models/code_task_details_response.rs源码 ↗
generatedrequest handling

这个文件是 OpenAPI 工具生成的数据模型文件。OpenAPI 可以理解成一份“接口说明书”,生成出来的模型用来保证程序传数据时不乱套。这里的 CodeTaskDetailsResponse 表示一次代码任务详情的返回结果:一定包含一个 task,也就是任务本身的信息;还可能包含当前用户轮次、当前助手轮次、当前 diff 任务轮次这些额外内容。这里的“轮次”可以理解成对话或任务推进中的某一步。后面三个字段都是可选的,所以没有内容时不会被写进返回的 JSON 里。serde 是 Rust 里常用的“打包/拆包”工具,负责把这个结构变成 JSON,或者从 JSON 读回来。这个文件本身不做业务判断,只规定数据的形状,重要性在于让接口返回稳定、清楚、可被前后端共同理解。

函数细节1
CodeTaskDetailsResponse::new34–41 ↗
fn new(task: models::TaskResponse) -> CodeTaskDetailsResponse

作用:这个函数用一个已有的任务信息,创建一份新的“代码任务详情返回结果”。它适合在后端已经拿到任务主体信息、但还没有当前轮次信息时使用。

数据流:进去的是一个 models::TaskResponse,也就是任务本身的返回数据。函数把它放进 Box 里,Box 可以简单理解成“把较大的东西装到一个指针盒子里保存”,然后把三个可选的当前轮次字段都设为空。出来的是一个 CodeTaskDetailsResponse,里面有任务信息,但还没有用户轮次、助手轮次或 diff 轮次。

调用关系:它通常会在组装接口返回值时被调用,先搭好最基本的任务详情外壳。函数内部把任务交给 Box::new 这类底层构造动作来包装,之后调用方如果有更多轮次信息,可以再把这些可选字段补上。

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

codex-backend-openapi-models/src/models/git_pull_request.rs源码 ↗
generatedrequest handling / response serialization

这个文件是 OpenAPI 工具生成的模型文件。OpenAPI 可以理解成一份接口说明书,生成器会按说明书自动写出这些数据结构。这里的 GitPullRequest 表示一个 Git 平台上的拉取请求,也就是别人提交一组代码改动、等待合并进主分支的记录。它保存了编号、网页地址、当前状态、是否已合并、是否可合并等必填信息,也保存标题、正文、分支名、提交编号、评论、代码差异、用户信息等可选信息。可选信息用 Option 表示,意思是“可能有,也可能没有”;没有时序列化成 JSON 时会被跳过,避免发出一堆空字段。Serialize 和 Deserialize 是 serde 提供的能力,简单说就是让这个结构能和 JSON 互相转换,方便接口收发数据。

函数细节1
GitPullRequest::new51–76 ↗
fn new(
        number: i32,
        url: String,
        state: String,
        merged: bool,
        mergeable: bool,
    ) -> GitPullRequest

作用:这个函数用来快速新建一个 GitPullRequest,只要求填最核心、必须知道的几项信息。其它不一定有的内容会先统一设成空,之后需要时再补。

数据流:调用者传进拉取请求编号、地址、状态、是否已合并、是否可合并这五个必填值 → 函数把这些值放进新的 GitPullRequest 里 → 返回这个新对象,同时把标题、正文、分支、提交号、评论、差异、用户等可选字段都设为 None,也就是“暂时没有”。

调用关系:当代码需要构造一个拉取请求数据对象时会调用它,通常是在准备把数据返回给接口、保存起来,或传给后续处理步骤之前。它不再调用别的项目函数,只是把输入整理成一个完整但很多可选栏为空的数据包。

codex-backend-openapi-models/src/models/external_pull_request_response.rs源码 ↗
generatedrequest handling / serialization

这个文件是 OpenAPI 工具生成的模型文件。OpenAPI 可以理解成一份接口说明书,生成出来的模型就像统一表格模板:大家都按这个模板填数据。这里的 ExternalPullRequestResponse 表示一次外部 Pull Request 的返回结果,里面有响应自己的 id、对应的 assistant_turn_id、真正的 pull_request 信息,以及一个可选的 codex_updated_sha。可选的意思是:有就带上,没有就不出现在 JSON 里。它还支持 serde 的序列化和反序列化,serde 是 Rust 里常用的“把结构体和 JSON 互相转换”的工具。pull_request 用 Box 包起来,可以简单理解成把较大的对象放到一个盒子里,通过指针引用它,减少结构体本身直接装太多东西。

函数细节1
ExternalPullRequestResponse::new28–39 ↗
fn new(
        id: String,
        assistant_turn_id: String,
        pull_request: models::GitPullRequest,
    ) -> ExternalPullRequestResponse

作用:这个函数用来快速创建一份 ExternalPullRequestResponse。调用者只需要提供必填的 id、assistant_turn_id 和 pull_request,它会自动把可选的 codex_updated_sha 先设为空。

数据流:进去的是三个必需信息:响应 id、助手回合 id、以及一个 GitPullRequest。函数把 GitPullRequest 放进 Box 这个“盒子”里,再组装成 ExternalPullRequestResponse;出来的是一份完整的响应对象,其中 codex_updated_sha 默认是 None,也就是暂时没有这个值。

调用关系:它是这个数据模型的便捷构造入口。接口层或业务代码在准备返回 Pull Request 结果时会用它来搭好基础响应;函数内部把 pull_request 交给 Box::new 之类的创建动作包装起来,让调用者不用关心这个小细节。

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

codex-backend-openapi-models/src/models/task_list_item.rs源码 ↗
generated接口数据传输和序列化时

这个文件是由 OpenAPI 工具生成的模型文件。OpenAPI 可以理解成一份接口说明书,生成出来的代码就是把说明书里的数据格式变成 Rust 里的结构。这里的 TaskListItem 表示任务列表中的一行:有任务 id、标题、是否归档、是否有未读内容,也可能带创建时间、更新时间、状态展示信息、相关的拉取请求等。它还使用 serde,也就是 Rust 里常用的“把结构和 JSON 互相转换”的工具,来规定这些字段在接口里叫什么名字,以及哪些空字段不用发出去。可以把它想成一张标准表单:服务端填表,客户端按表读;如果没有这种统一表单,接口两边就很容易因为字段缺失或名字不一致而出错。

函数细节1
TaskListItem::new44–62 ↗
fn new(
        id: String,
        title: String,
        has_generated_title: Option<bool>,
        archived: bool,
        has_unread_turn: bool,
    ) -> TaskListItem

作用:这个函数用来快速创建一个最基本的任务列表项。调用者只需要提供必填信息,其他暂时没有的数据会自动留空。

数据流:进去的是任务 id、标题、是否为自动生成标题、是否归档、是否有未读内容这些基础信息;函数把它们放进一个新的 TaskListItem 里;出来的是一个完整的任务列表项对象,其中更新时间、创建时间、状态展示和拉取请求这些可选内容先设为没有。

调用关系:它是创建 TaskListItem 的便捷入口。需要组装任务列表返回数据的代码会调用它先做出一个基础对象;它自己不再把工作交给别的函数,只是把传进来的值和默认空值装到结构体里。

codex-backend-openapi-models/src/models/paginated_list_task_list_item_.rs源码 ↗
generatedrequest handling / response serialization

这个文件是由 OpenAPI 工具自动生成的模型文件。OpenAPI 可以理解成一份接口说明书,生成器根据说明书做出 Rust 代码,避免人手写错字段名。这里的核心结构叫 PaginatedListTaskListItem,意思是一页 TaskListItem 任务条目。它有两个字段:items 是这一页真正的任务列表;cursor 是“游标”,可以理解成书签,告诉客户端下一次从哪里继续拿数据。cursor 是可选的,因为最后一页可能没有下一页。文件还加了序列化和反序列化能力,也就是能在 Rust 数据和接口常用的 JSON 数据之间互相转换。没有这个结构,后端和客户端就很难统一“分页任务列表”到底长什么样。

函数细节1
PaginatedListTaskListItem::new24–29 ↗
fn new(items: Vec<models::TaskListItem>) -> PaginatedListTaskListItem

作用:这个函数用来快速创建一页任务列表。调用者只需要给它这一页的任务 items,它会先把下一页游标 cursor 留空。

数据流:进去的是一个 TaskListItem 的数组,也就是这一页要返回的任务。函数把这些任务放进 PaginatedListTaskListItem 的 items 字段里,并把 cursor 设成 None,表示暂时没有提供“下一页书签”。出来的是一个完整的 PaginatedListTaskListItem 对象,之后调用者如果需要,可以再补上 cursor。

调用关系:它是这个数据结构的便捷构造入口。业务代码在准备返回分页任务列表时会用它先搭好基本对象;它不再把工作交给别的函数,只是把传入的数据装进统一的接口模型里。

codex-backend-openapi-models/src/models/task_response.rs源码 ↗
generatedrequest handling / serialization

这个文件是由 OpenAPI 工具生成的。OpenAPI 可以理解成一份“接口说明书”,生成代码后,程序就能用固定的结构来表示接口里的数据。这里的 TaskResponse 就是一张任务信息卡:有任务 id、标题、是否归档、外部 pull request 列表等必填信息,也有创建时间、当前轮次、是否有未读内容、额外元数据等可选信息。serde 是 Rust 里常用的“翻译工具”,负责把这个结构变成 JSON,或从 JSON 读回来。字段上的 rename 规定了 JSON 里的名字,比如 created_at;skip_serializing_if 表示如果这个可选字段没有值,就不要写进 JSON,避免传一堆空内容。它本身不做业务判断,只保证任务数据能按接口约定被安全、统一地传来传去。

函数细节1
TaskResponse::new44–61 ↗
fn new(
        id: String,
        title: String,
        archived: bool,
        external_pull_requests: Vec<models::ExternalPullRequestResponse>,
    ) -> TaskResponse

作用:这是创建 TaskResponse 的便捷函数。调用者只需要提供最核心、一定要有的任务信息,其他可选字段会先留空。

数据流:进去的是任务 id、标题、是否已归档,以及外部 pull request 列表。函数把这些值放进新的 TaskResponse 结构里;创建时间、是否自动生成标题、当前轮次、未读状态、额外元数据这些可选信息都会设成 None,也就是“暂时没有”。出来的是一个完整但只填了必要字段的任务响应对象。

调用关系:它通常在代码需要组装一个任务返回给接口调用方时使用。它不再调用别的函数,只是把传进来的值摆到正确字段里,让后续的 JSON 序列化工具可以按接口格式把它发出去。

配置交付载荷

这些生成类型描述后端返回的已交付配置文件和 TOML 片段包。

codex-backend-openapi-models/src/models/config_bundle_response.rs源码 ↗
generatedrequest handling / serialization

这个文件是 OpenAPI 工具自动生成的模型文件。OpenAPI 可以理解成一份接口说明书,工具会按说明书生成 Rust 代码,避免手写接口数据结构时写错字段名。这里的 ConfigBundleResponse 就是一张“返回单”的格式:里面有两个可选字段,一个是 config_toml,一个是 requirements_toml。它们都用了双层 Option,可以把三种情况分清楚:字段完全没出现、字段出现但值是空、字段出现且有内容。这对接口很重要,因为客户端可能需要知道“服务器没提这件事”和“服务器明确说没有”是两回事。serde 是 Rust 里常用的序列化工具,负责把结构体和 JSON 这类传输格式互相转换;这里的标注说明了字段在 JSON 里的名字,以及什么时候不输出。

函数细节1
ConfigBundleResponse::new34–39 ↗
fn new() -> ConfigBundleResponse

作用:创建一个空的 ConfigBundleResponse。也就是说,刚创建出来时,config_toml 和 requirements_toml 这两个返回字段都不带任何内容。

数据流:进去不需要任何参数 → 函数新建一个 ConfigBundleResponse,并把 config_toml 和 requirements_toml 都设成 None,意思是这些字段暂时不出现在结果里 → 出来一个可以继续填充、也可以直接序列化返回的空响应对象。

调用关系:当后端准备构造配置包接口的返回值时,可以先调用它得到一个干净的空壳,再按实际情况往里面放 config.toml 或 requirements.toml。这个函数本身不调用别的业务函数,只是这个数据模型的便捷创建入口。

codex-backend-openapi-models/src/models/config_file_response.rs源码 ↗
data_modelrequest handling

这个文件像一张固定格式的表单,专门用来表示一个配置文件的返回结果。它不是去读取文件,也不是去修改配置,而是规定“返回给调用方的数据里有哪些格子”。这些格子包括:contents,配置文件内容;sha256,一串用来校验内容是否变化或是否完整的指纹;updated_at,最后更新时间;updated_by_user_id,最后是谁改的。每一项都是 Option,也就是“可能有,也可能没有”,所以接口可以只返回它知道的信息。serde 是 Rust 里常用的序列化工具,序列化就是把程序里的结构变成 JSON 这类可传输格式;这里还规定了如果某项为空,就不要写进输出里。这个文件是 OpenAPI 工具生成的模型文件,重点是保证前后端、客户端和服务端对这份响应的格式理解一致。

函数细节1
ConfigFileResponse::new27–39 ↗
fn new(
        contents: Option<String>,
        sha256: Option<String>,
        updated_at: Option<String>,
        updated_by_user_id: Option<String>,
    ) -> ConfigFileResponse

作用:这个函数用来快速创建一个 ConfigFileResponse。调用者把配置内容、校验指纹、更新时间和更新人交给它,它就打包成一个标准的响应对象。

数据流:进去的是四个可能为空的字符串:contents、sha256、updated_at、updated_by_user_id。函数不做计算,也不检查格式,只是把这四个值分别放进 ConfigFileResponse 对应的位置。出来的是一个新的 ConfigFileResponse 对象,里面保存了调用者传入的这些信息。

调用关系:它是这个数据模型的便捷构造入口。需要返回配置文件信息的代码会在准备好这些字段后调用它,把零散数据装成统一对象;之后对象通常会交给 serde 变成 JSON,再作为接口响应发出去。

codex-backend-openapi-models/src/models/delivered_toml_fragment.rs源码 ↗
data_modelrequest handling / serialization

这个文件是由 OpenAPI 工具自动生成的模型文件。OpenAPI 可以理解成一份“接口说明书”,生成工具会根据说明书造出这些 Rust 结构体,保证前后端说的是同一种数据格式。这里的 DeliveredTomlFragment 就像一个贴好标签的文件夹:id 是唯一编号,name 是这段配置的名字,contents 是真正的 TOML 文本内容。TOML 是一种常见的配置文件格式,写起来像“键 = 值”。这个结构体还支持 serde 的序列化和反序列化;简单说,就是可以在 Rust 对象和网络上传的 JSON 等格式之间来回转换。没有这个文件,代码里就只能到处用散乱的字符串传递配置片段,容易漏字段、写错字段名,接口也更难保持一致。

函数细节1
DeliveredTomlFragment::new25–27 ↗
fn new(id: String, name: String, contents: String) -> DeliveredTomlFragment

作用:这个函数用来创建一个新的 DeliveredTomlFragment。调用者把编号、名字和 TOML 内容交给它,它会打包成一个完整的数据对象。

数据流:进去的是三个字符串:id、name 和 contents。函数不做额外加工,也不检查内容,只是把这三个值分别放进结构体对应的位置。出来的是一个新的 DeliveredTomlFragment 对象,原来的三个字符串会被移动进去,成为这个对象的数据。

调用关系:它是这个数据模型的便捷构造入口。其他代码在需要表示“一段已经交付的 TOML 配置”时,可以调用它来组装对象;组装好之后,这个对象通常会交给 serde 去转成接口传输格式,或者从接口数据转回来。

codex-backend-openapi-models/src/models/delivered_config_toml.rs源码 ↗
generatedrequest handling / serialization

这个文件是 OpenAPI 工具生成的模型文件。可以把它理解成一张“表格模板”:后端要传递一份配置,里面可能有一栏叫 enterprise_managed,表示企业统一管理的配置片段。这个字段比较特别,它用了两层 Option:外层表示“接口里有没有这个字段”,内层表示“字段出现了但值是不是空”。这样程序能分清三种情况:没提这件事、明确说没有值、确实给了一组配置片段。serde 是 Rust 里常用的序列化工具,意思是把程序里的结构变成 JSON,或把 JSON 读回结构。这里的标记规定了字段名、默认值、空值时是否跳过等规则,避免前后端因为字段缺失或空值理解不一致。

函数细节1
DeliveredConfigToml::new27–31 ↗
fn new() -> DeliveredConfigToml

作用:创建一份空的 DeliveredConfigToml 配置对象。别人需要先有一个默认容器,再往里面填企业管理配置时,会用它。

数据流:进去时不需要任何参数 → 它新建一个 DeliveredConfigToml,并把 enterprise_managed 设成 None,意思是暂时没有提供这个字段 → 出来的是一个可以继续填写或直接序列化发送的空配置对象。

调用关系:它是这个数据模型自带的便捷创建方法。调用方在构造 API 响应或测试数据时可以先调用它拿到初始对象;它不再把工作交给其他函数,只是把结构体的默认状态准备好。

codex-backend-openapi-models/src/models/delivered_requirements_toml.rs源码 ↗
generatedcross-cutting

这是一个由 OpenAPI 工具自动生成的数据模型文件。OpenAPI 可以理解成一份“接口说明书”,生成器根据说明书写出 Rust 里的结构体,避免人手写错字段。这里的 DeliveredRequirementsToml 就像一个快递盒标签,告诉程序:这份数据里可能有一个叫 enterprise_managed 的字段。这个字段本身可以不存在,也可以明确是空值,还可以是一组 DeliveredTomlFragment。这种“双层 Option”可以区分三种情况:没提这个字段、字段写了但值是 null、字段里真的有列表。文件还让这个结构体可以被序列化和反序列化,也就是能在 Rust 对象和接口传输用的 JSON 等格式之间来回转换。new 函数提供了一个默认空对象,表示一开始没有填写企业托管相关内容。

函数细节1
DeliveredRequirementsToml::new27–31 ↗
fn new() -> DeliveredRequirementsToml

作用:创建一个空的 DeliveredRequirementsToml 对象。别人需要先拿到一个干净的数据容器,再按需填入企业托管相关内容时,会用它。

数据流:进去没有参数 → 它新建一个结构体,并把 enterprise_managed 设成 None,意思是这个字段暂时不存在或未提供 → 出来一个可继续使用、可序列化的空数据对象,不会改动别的东西。

调用关系:它是这个数据模型自己的便捷构造函数。外部代码在准备返回接口数据、组装模型对象或测试数据时会调用它;它不把工作交给其他项目函数,只是按这个模型的默认规则生成一个初始值。

线程配置 protobuf 绑定

这个生成的 protobuf 模块提供用于远程加载线程配置的线路消息和 gRPC 服务定义。

config/src/thread_config/proto/codex.thread_config.v1.rs源码 ↗
generatedrequest handling

这个文件不是人手写的,而是由 prost-build 根据 Protocol Buffers 定义生成的。Protocol Buffers 可以理解成一张“标准表格”,规定网络上传哪些字段;gRPC 则像一条固定格式的电话线,让一个程序调用另一个程序的方法。这里定义了加载线程配置时要传的请求,比如 thread_id 和 cwd,也定义了返回内容,比如配置来源、模型提供方、认证信息、HTTP 头、重试次数等。它还生成了 ThreadConfigLoaderClient,方便别的代码像调用本地函数一样发起 Load 请求;也生成了 ThreadConfigLoaderServer,把收到的网络请求分发到真正实现 load 的业务代码。重要的是,这个文件主要负责“格式和通道”,不决定配置怎么找、怎么合并,那些逻辑在实现这个服务的其他代码里。

函数细节21
WireApi::as_str_name109–114 ↗
fn as_str_name(&self) -> &'static str

作用:把 WireApi 这个枚举值转换成协议里固定的字符串名字。这样日志、调试或跨语言通信时,可以看到稳定、不容易误解的名称。

数据流:进去的是一个 WireApi 值,比如 Responses → 它用匹配表查这个值对应的协议名字 → 出来的是类似“WIRE_API_RESPONSES”的固定字符串,不改动任何数据。

调用关系:它是 WireApi 这个小型协议枚举的辅助函数,通常在需要把内部枚举展示成协议原名时使用;它不调用别的项目逻辑,也不参与网络发送本身。

WireApi::from_str_name116–122 ↗
fn from_str_name(value: &str) -> ::core::option::Option<Self>

作用:把协议里的字符串名字转回 WireApi 枚举值。外部传来文字时,用它判断这个文字是不是项目认识的接口类型。

数据流:进去的是一个字符串,比如“WIRE_API_RESPONSES” → 它逐个对照已知名字 → 如果认识就出来 Some(WireApi),不认识就出来 None,表示无法转换。

调用关系:它和 WireApi::as_str_name 是一对反向工具,一个负责转出去,一个负责转回来;常用于解析协议字段或兼容跨语言生成代码。

thread_config_loader_client::ThreadConfigLoaderClient::connect141–148 ↗
async fn connect(dst: D) -> Result<Self, tonic::transport::Error>

作用:根据一个服务地址创建客户端,并真正连上远端的线程配置加载服务。别人想远程加载配置时,通常先用它“拨号”。

数据流:进去的是目标地址,比如一个 URL 或 endpoint → 它把地址变成 tonic 能连接的 Endpoint,然后异步建立连接 → 成功后出来一个 ThreadConfigLoaderClient,失败则出来连接错误。

调用关系:这是客户端侧的入口之一;它内部借助 tonic 的连接能力建立通道,连好后交给 ThreadConfigLoaderClient::new 包装成可调用 Load 方法的客户端。

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

thread_config_loader_client::ThreadConfigLoaderClient::new157–160 ↗
fn new(inner: T) -> Self

作用:把一条已经准备好的底层通信通道包装成 ThreadConfigLoaderClient。适合调用者已经自己建好了连接或测试通道时使用。

数据流:进去的是一个能发送 gRPC 请求的底层对象 → 它用 tonic 的 Grpc 包一层,变成这个服务专用的客户端 → 出来的是可以调用 load 的 ThreadConfigLoaderClient。

调用关系:connect 连好网络后会走到它;with_interceptor 等构造方式也会用它收尾。它不负责连网,只负责把通道装进客户端外壳。

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

thread_config_loader_client::ThreadConfigLoaderClient::with_origin161–164 ↗
fn with_origin(inner: T, origin: Uri) -> Self

作用:创建一个带指定 origin 的客户端。origin 可以理解成请求里标明的“从哪个源头来的”地址信息,某些代理或服务器会用到。

数据流:进去的是底层通信通道和一个 Uri → 它让 tonic 在客户端里记录这个 origin → 出来的是带 origin 设置的 ThreadConfigLoaderClient。

调用关系:这是客户端的特殊构造方式,给需要自定义请求来源信息的场景使用;之后实际发送请求仍由 load 完成。

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

thread_config_loader_client::ThreadConfigLoaderClient::with_interceptor165–182 ↗
fn with_interceptor(
            inner: T,
            interceptor: F,
        ) -> ThreadConfigLoaderClient<InterceptedService<T, F>>

作用:创建一个带拦截器的客户端。拦截器就像出门前的门卫,可以在每个请求发出前加认证信息、改元数据或做检查。

数据流:进去的是底层通道和一个拦截器函数/对象 → 它把通道包成 InterceptedService → 再生成 ThreadConfigLoaderClient → 出来的是每次请求都会先经过拦截器的客户端。

调用关系:它服务于需要统一加 token、日志或元数据的客户端流程;包装完成后,请求还是通过 ThreadConfigLoaderClient::load 发出去。

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

thread_config_loader_client::ThreadConfigLoaderClient::send_compressed188–191 ↗
fn send_compressed(mut self, encoding: CompressionEncoding) -> Self

作用:设置客户端发送请求时使用压缩。压缩像把包裹抽真空,能省流量,但服务端也要支持才行。

数据流:进去的是客户端自身和一种压缩格式 → 它把压缩选项写到底层 gRPC 客户端里 → 出来的是更新过设置的客户端。

调用关系:它通常在发请求前链式调用,用来调整传输方式;真正发送时,load 会使用这里设置好的压缩选项。

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

thread_config_loader_client::ThreadConfigLoaderClient::accept_compressed194–197 ↗
fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self

作用:告诉客户端可以接收某种压缩格式的响应。这样服务端如果用这种方式压缩返回内容,客户端能正确解开。

数据流:进去的是客户端自身和一种压缩格式 → 它把“可接收这种压缩响应”的设置交给底层 gRPC 客户端 → 出来的是更新过设置的客户端。

调用关系:它和 send_compressed 分别管请求和响应的压缩能力;配置完成后由 load 在实际通信时使用。

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

thread_config_loader_client::ThreadConfigLoaderClient::max_decoding_message_size202–205 ↗
fn max_decoding_message_size(mut self, limit: usize) -> Self

作用:限制客户端最多愿意解码多大的响应消息。这个限制能防止异常的大返回包占太多内存。

数据流:进去的是客户端自身和字节大小上限 → 它把这个上限写到底层解码设置中 → 出来的是带新限制的客户端。

调用关系:它是发送前的安全/资源控制设置;当 load 收到服务端响应时,底层 tonic 会按这个限制检查消息大小。

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

thread_config_loader_client::ThreadConfigLoaderClient::max_encoding_message_size210–213 ↗
fn max_encoding_message_size(mut self, limit: usize) -> Self

作用:限制客户端最多允许编码多大的请求消息。这样可以避免把过大的请求发出去。

数据流:进去的是客户端自身和字节大小上限 → 它把这个上限写到底层编码设置中 → 出来的是带新限制的客户端。

调用关系:它在调用 load 前设置传输边界;真正编码 LoadThreadConfigRequest 时,底层 gRPC 会参考这个限制。

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

thread_config_loader_client::ThreadConfigLoaderClient::load214–232 ↗
async fn load(
            &mut self,
            request: impl tonic::IntoRequest<super::LoadThreadConfigRequest>,
        ) -> std::result::Result<tonic::Response<super::LoadThreadConfigResponse>, t

作用:向远端服务发起“加载线程配置”的请求,并拿回配置来源列表。对调用者来说,这是客户端最核心的实际调用。

数据流:进去的是 LoadThreadConfigRequest,里面可能有 thread_id 和 cwd → 它先确认连接可用,把请求转成 gRPC 请求,填好固定路径“/codex.thread_config.v1.ThreadConfigLoader/Load”,再用 protobuf 编码发送 → 出来的是 LoadThreadConfigResponse,或者一个 tonic::Status 错误。

调用关系:这是客户端调用远端 ThreadConfigLoader 服务的主动作;它会使用前面构造函数和压缩、大小限制设置好的 inner 客户端,最终把请求交给 tonic 的 unary 单次请求机制。服务端对应接收点是 ThreadConfigLoaderServer::call 中的 Load 分支。

调用图:外部调用 6 个(ready, unary, new, into_request, from_static, default)。

thread_config_loader_server::ThreadConfigLoaderServer::new262–264 ↗
fn new(inner: T) -> Self

作用:用真正的服务实现创建一个 gRPC 服务端外壳。真正会加载配置的代码放在 inner 里,这里负责把它接到网络接口上。

数据流:进去的是一个实现了 ThreadConfigLoader trait 的对象 → 它先把对象放进 Arc(一种可安全共享的引用计数指针),再调用 from_arc → 出来的是 ThreadConfigLoaderServer。

调用关系:这是服务端安装服务时常用的入口;它把业务实现包起来,之后 tonic 收到请求时会通过 ThreadConfigLoaderServer::call 转给这个实现。

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

thread_config_loader_server::ThreadConfigLoaderServer::from_arc265–273 ↗
fn from_arc(inner: Arc<T>) -> Self

作用:用已经放在 Arc 里的服务实现创建服务端。Arc 可以让多个请求共享同一个服务对象,而不用反复复制。

数据流:进去的是 Arc<T> 形式的服务实现 → 它保存这个共享对象,并把压缩设置和消息大小限制设成默认状态 → 出来的是 ThreadConfigLoaderServer。

调用关系:ThreadConfigLoaderServer::new 会调用它;如果外部已经有 Arc,也可以直接用它,之后请求分发仍由 call 完成。

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

thread_config_loader_server::ThreadConfigLoaderServer::with_interceptor274–279 ↗
fn with_interceptor(inner: T, interceptor: F) -> InterceptedService<Self, F>

作用:创建一个带拦截器的服务端。拦截器像服务入口处的检查岗,可以在请求进入业务代码前做认证、记录日志或拒绝请求。

数据流:进去的是服务实现和拦截器 → 它先用 new 建出服务端,再把服务端和拦截器包在一起 → 出来的是 InterceptedService,也就是带前置检查的服务。

调用关系:这是服务端接入认证或统一检查时使用的包装方式;通过拦截器后,请求仍会进入 ThreadConfigLoaderServer::call。

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

thread_config_loader_server::ThreadConfigLoaderServer::accept_compressed282–285 ↗
fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self

作用:设置服务端可以接收某种压缩格式的请求。客户端如果压缩了请求,服务端需要先声明自己能解压。

数据流:进去的是服务端自身和一种压缩格式 → 它把这种格式加入“可接收压缩”列表 → 出来的是更新过设置的服务端。

调用关系:它通常在服务启动配置阶段调用;之后 call 处理请求时,会把这个压缩设置交给 tonic 的 Grpc 处理器。

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

thread_config_loader_server::ThreadConfigLoaderServer::send_compressed288–291 ↗
fn send_compressed(mut self, encoding: CompressionEncoding) -> Self

作用:设置服务端可以用某种压缩格式返回响应。这样响应数据较大时,可以减少网络传输量。

数据流:进去的是服务端自身和一种压缩格式 → 它把这种格式加入“可发送压缩”列表 → 出来的是更新过设置的服务端。

调用关系:它和 accept_compressed 分别控制进来的请求和出去的响应;ThreadConfigLoaderServer::call 会把这些设置应用到实际 gRPC 响应里。

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

thread_config_loader_server::ThreadConfigLoaderServer::max_decoding_message_size296–299 ↗
fn max_decoding_message_size(mut self, limit: usize) -> Self

作用:限制服务端最多愿意解码多大的请求消息。这个限制可以防止客户端发来超大请求拖垮服务。

数据流:进去的是服务端自身和字节大小上限 → 它把上限保存到 max_decoding_message_size → 出来的是带新限制的服务端。

调用关系:这是服务启动时的资源保护设置;真正收到请求时,call 会把这个限制交给底层 gRPC 解码器执行。

thread_config_loader_server::ThreadConfigLoaderServer::max_encoding_message_size304–307 ↗
fn max_encoding_message_size(mut self, limit: usize) -> Self

作用:限制服务端最多允许编码多大的响应消息。这样可以避免返回过大的响应包。

数据流:进去的是服务端自身和字节大小上限 → 它把上限保存到 max_encoding_message_size → 出来的是带新限制的服务端。

调用关系:这是服务端发送响应前的安全边界设置;call 创建 gRPC 处理器时会把它应用进去。

thread_config_loader_server::ThreadConfigLoaderServer::poll_ready318–323 ↗
fn poll_ready(
            &mut self,
            _cx: &mut Context<'_>,
        ) -> Poll<std::result::Result<(), Self::Error>>

作用:告诉底层运行框架:这个服务现在可以接请求。这里总是立刻回答“可以”。

数据流:进去的是服务端自身和异步任务上下文 → 它不检查额外状态,直接返回 Ready(Ok(())) → 出来的是“已准备好”的信号,不改动服务数据。

调用关系:这是 tonic 服务框架要求实现的方法;在 call 真正处理请求前,框架会用它确认服务是否可用。

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

thread_config_loader_server::ThreadConfigLoaderServer::call324–381 ↗
fn call(&mut self, req: http::Request<B>) -> Self::Future

作用:这是服务端收到 HTTP/gRPC 请求后的分发入口。它看请求路径,如果是 Load,就把请求交给真正的 load 实现;如果不是,就返回“未实现”。

数据流:进去的是一个 HTTP 请求 → 它读取 URI 路径;路径匹配 Load 时,创建 LoadSvc,把共享的业务实现拿出来,配置 protobuf 编解码、压缩和消息大小限制,然后调用底层 unary 处理;路径不匹配时,生成一个 gRPC Unimplemented 响应 → 出来的是 HTTP 响应。

调用关系:这是客户端 ThreadConfigLoaderClient::load 在服务端对应的接收点。它本身不加载配置,而是把 Load 请求转给实现了 ThreadConfigLoader trait 的对象的 load 方法;同时它负责处理未知路径,避免请求掉到空处。

调用图:外部调用 6 个(pin, uri, new, default, new, default)。

thread_config_loader_server::ThreadConfigLoaderServer::clone384–393 ↗
fn clone(&self) -> Self

作用:复制一个服务端外壳,让多个任务或连接可以共用同一个服务配置和内部实现。这里的复制主要是复制指针和设置,不是复制整套业务对象。

数据流:进去的是已有的 ThreadConfigLoaderServer → 它克隆内部 Arc,并拷贝压缩和消息大小配置 → 出来的是一个行为一致的新 ThreadConfigLoaderServer。

调用关系:这是 tonic 服务在并发处理或注册服务时可能需要的能力;克隆出来的服务端继续通过 call 处理请求,并共享同一个 inner 业务实现。

Exec 中继 protobuf 绑定

这些文件公开生成的中继协议类型,以及一个小型手写包装器,用于重新导出 exec-server 使用的子集。

exec-server/src/proto/codex.exec_server.relay.v1.rs源码 ↗
generatedcross-cutting

这个文件就像一张快递面单规范:每个中继消息都必须按这里规定的格式打包。最外层是 RelayMessageFrame,里面有协议版本、数据流编号、确认信息,以及真正的消息内容。真正内容只能是几种之一:数据、确认、恢复、重置、心跳、握手。比如 RelayData 装实际传输的字节,还带序号和分片信息,方便把大数据拆开再拼回去;RelayAck 表示“我收到了”;RelayResume 表示断了以后从哪个序号继续;RelayReset 说明要重置并带原因;RelayHeartbeat 像“我还活着”的敲门声;RelayHandshake 用来一开始交换初始化信息。它由 prost-build 自动从协议定义生成,平时不应该手改,因为手改可能和协议源文件不一致。

exec-server/src/relay_proto.rs源码 ↗
data_modelcross-cutting

这个文件的作用很像一本说明书的目录页:真正厚厚的内容在生成文件里,但大家平时只需要从这里拿到要用的章节。这里引入了由协议定义自动生成的 Rust 代码,里面包含几种中继消息,比如握手、数据、重置、恢复,以及消息外壳。所谓“协议”,可以理解成两边通信时约好的格式:先说什么、数据长什么样、断线后怎么继续。没有这个统一入口,项目里其他地方就要直接引用那个生成文件,路径会更乱,也更容易和自动生成代码绑死。这个文件通过重新导出这些类型,让内部代码可以稳定地使用这些消息格式,同时把“生成代码放在哪里”这个细节藏起来。