Codex 系统手册

核心共享协议与领域类型

stage-18.146 个文件

这一阶段像整套系统的“通用词典”和“零件规格书”,属于幕后支撑部分,不直接干活,但大家都离不开。它先把会话号、线程号、智能体路径、工具名这些“身份证”定统一;再规定消息、权限、审批、错误、配置、用户输入、工具结果等数据该怎么写。插件、工具、技能、云任务、线程存储和界面事件也用同一套格式对齐。这样前端、后端、命令执行、存档和外部服务传话时,不会各说各话,也更不容易误放权限、读错配置或恢复不了旧会话。

本阶段的文件46

Protocol crate 基础

这些文件建立 protocol crate 的共享标识符、底层值类型,以及 crate 范围的模块接口,然后再构建更高层 schema。

protocol/src/thread_id.rs源码 ↗
data_modelcross-cutting

系统里很多事件、消息、子任务都需要知道自己属于哪一条“线索”。这个文件就像给每条线索发一张身份证:ThreadId。它内部用 UUID(一种几乎不会重复的唯一编号)保存真实值,但对外尽量像字符串一样使用,方便写进网络消息、日志、数据库和前端代码。新建时它用 UUID v7,这种编号带有时间顺序,既唯一,也更适合按时间排列。文件还规定了几件重要的事:字符串可以解析成 ThreadId;ThreadId 可以显示成字符串;序列化和反序列化(把内存里的值变成 JSON,或从 JSON 读回来)时也都是字符串;生成 JSON Schema(给接口文档和校验用的结构说明)时,它也被当作字符串看。最后有一个小测试,确保默认生成的编号不是全零的无效编号。

函数细节11
ThreadId::new18–22 ↗
fn new() -> Self

作用:生成一个新的 ThreadId。有人要开启一条新的对话线索、任务线索或事件链时,会用它拿到一个新的唯一编号。

数据流:进去不需要任何参数 → 它调用 UUID 库生成一个新的 UUID v7 编号 → 出来一个包着这个编号的 ThreadId,原来的系统状态不被改动。

调用关系:它是创建新线索编号的源头。很多测试和事件构造流程会直接用它;ThreadId::default 也把活儿交给它,所以默认创建 ThreadId 时同样会得到一个新编号。

调用图:被 465 处调用(collab_resume_begin_maps_to_item_started_resume_agent, collab_resume_end_maps_to_item_completed_resume_agent, ignores_user_message_item_lifecycle_events, preserves_user_message_client_id_from_legacy_event, rebuilds_sleep_item_from_persisted_completion, command_execution_started_helper_emits_once, complete_command_execution_item_emits_declined_once_for_pending_command, guardian_assessment_aborted_emits_completed_review_payload, guardian_assessment_completed_emits_review_payload, guardian_assessment_started_uses_event_turn_id_fallback (+15 more));外部调用 1 个(now_v7)。

ThreadId::from_string24–28 ↗
fn from_string(s: &str) -> Result<Self, uuid::Error>

作用:把外部传进来的字符串变成 ThreadId。比如从 JSON、数据库、请求参数里读到一个编号时,需要先确认它真的是合法 UUID。

数据流:进去一个字符串切片 → 它让 UUID 库尝试解析这个字符串 → 如果格式正确,出来一个 ThreadId;如果格式不对,出来一个 uuid::Error 错误,提醒调用方这个编号不能用。

调用关系:它是“从文字恢复 ThreadId”的公共入口。TryFrom 的转换实现会调用它,很多序列化测试、事件重建和请求解析流程也会通过它把字符串编号变回强类型的 ThreadId。

调用图:被 318 处调用(thread_id, compaction_event_ingests_custom_fact, subagent_events_use_inherited_connection_unless_turn_connection_is_explicit, subagent_thread_started_other_serializes_explicit_parent_thread_id, subagent_thread_started_thread_spawn_serializes_thread_lineage, conversation_id_serializes_as_plain_string, serialize_get_conversation_summary, serialize_server_request, rollback_response_rebuilds_pathless_thread_from_stored_history, source_kind_matches_distinguishes_subagent_variants (+15 more));外部调用 1 个(parse_str)。

ThreadId::try_from42–44 ↗
fn try_from(value: String) -> Result<Self, Self::Error>

作用:提供 Rust 标准转换写法,把 String 尝试转换成 ThreadId。这样调用方可以用通用的 try_from 语法,而不用记住专门的 from_string 名字。

数据流:进去一个拥有所有权的 String → 它把这个 String 当作普通字符串引用交给 ThreadId::from_string → 出来合法的 ThreadId,或者出来解析失败的 UUID 错误。

调用关系:它是标准转换接口的一部分,内部不自己解析,而是交给 ThreadId::from_string。需要从已有字符串构造 ThreadId 的测试、模型会话和数据恢复流程会用到这种转换方式。

调用图:被 10 处调用(reconstructs_collab_spawn_end_item_with_model_metadata, reconstructs_interrupted_send_input_as_completed_collab_call, test_model_client_session, fixed_thread_id, try_from, try_from, get_phase2_input_selection, stage1_output_from_row_if_thread_enabled, generic_url_target, suggestion_target);外部调用 1 个(from_string)。

String::from48–50 ↗
fn from(value: ThreadId) -> Self

作用:把 ThreadId 转成普通 String。这样需要把编号放进文本、JSON 字段或其他只认字符串的地方时,不用手动拆开它。

数据流:进去一个 ThreadId → 它调用这个编号的 to_string 显示逻辑 → 出来一个 UUID 格式的字符串。

调用关系:它依赖 ThreadId 的显示格式,也就是 ThreadId::fmt 间接提供的字符串样子。它让 ThreadId 能自然进入 Rust 的 From 转换体系。

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

ThreadId::default54–56 ↗
fn default() -> Self

作用:给 ThreadId 提供默认值,但这个默认值不是空号,而是一个新生成的唯一编号。这样代码在需要“随手给一个编号”时也不会拿到危险的全零值。

数据流:进去不需要参数 → 它直接调用 ThreadId::new → 出来一个新的 ThreadId。

调用关系:它是默认构造入口,本质上只是把工作交给 ThreadId::new。很多测试和事件构造代码会用 default 来快速得到一个可用编号。

调用图:被 59 处调用(app_server_event_sink_uses_listener_fifo_for_goal_updates_and_clears, record_initial_history_reconstructs_typed_inter_agent_message, record_initial_history_resumed_aborted_turn_without_id_clears_active_turn_for_compaction_accounting, record_initial_history_resumed_bare_turn_context_does_not_hydrate_previous_turn_settings, record_initial_history_resumed_bare_turn_context_does_not_seed_reference_context_item, record_initial_history_resumed_does_not_seed_reference_context_item_after_compaction, record_initial_history_resumed_hydrates_previous_turn_settings_from_lifecycle_turn_with_missing_turn_context_id, record_initial_history_resumed_replaced_incomplete_compacted_turn_clears_reference_context_item, record_initial_history_resumed_rollback_drops_incomplete_user_turn_compaction_metadata, record_initial_history_resumed_rollback_skips_only_user_turns (+15 more));外部调用 1 个(new)。

ThreadId::fmt60–62 ↗
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result

作用:规定 ThreadId 被打印或转成文字时长什么样。它让 ThreadId 显示成内部 UUID 的标准字符串格式。

数据流:进去一个 ThreadId 和一个格式化输出器 → 它把内部 UUID 的显示结果写进输出器 → 出来格式化是否成功的结果。

调用关系:它服务于所有需要把 ThreadId 当文字看的地方,比如 to_string、日志输出,以及 String::from 间接使用的转换。

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

ThreadId::serialize66–71 ↗
fn serialize(&self, serializer: S) -> Result<S::Ok, S::Error>

作用:规定 ThreadId 写成 JSON 或其他序列化格式时,表现为一个字符串。这样接口里不会暴露复杂内部结构,只看到普通编号文本。

数据流:进去一个 ThreadId 和序列化器 → 它把内部 UUID 收集成字符串形式交给序列化器 → 出来序列化成功后的结果,或者序列化错误。

调用关系:它在发送网络消息、保存数据、生成事件内容时被 serde(Rust 常用的序列化框架)调用。它和 ThreadId::deserialize 正好一进一出,保证写出去和读回来格式一致。

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

ThreadId::deserialize75–82 ↗
fn deserialize(deserializer: D) -> Result<Self, D::Error>

作用:规定从 JSON 或其他序列化格式读 ThreadId 时,应该先读一个字符串,再把它当 UUID 校验。这样错误编号会在入口处被挡住。

数据流:进去一个反序列化器 → 它先读出字符串,再用 UUID 解析器检查格式 → 如果合法,出来 ThreadId;如果不合法,出来带有原因的反序列化错误。

调用关系:它在接收请求、读取保存的数据或恢复事件时由 serde 自动调用。它和 ThreadId::serialize 配套,保证 ThreadId 对外永远像字符串,但内部永远是合法 UUID。

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

ThreadId::schema_name86–88 ↗
fn schema_name() -> String

作用:告诉 JSON Schema 生成工具,这个类型在文档里叫 ThreadId。JSON Schema 是用来描述 JSON 数据长什么样、方便校验和生成文档的说明书。

数据流:进去不需要参数 → 它返回固定文字 ThreadId → 没有改动任何状态。

调用关系:它是 JsonSchema 实现的一部分,会在系统生成接口结构说明时被工具调用。它和 ThreadId::json_schema 一起告诉外部工具如何理解这个类型。

ThreadId::json_schema90–92 ↗
fn json_schema(generator: &mut SchemaGenerator) -> Schema

作用:告诉 JSON Schema 生成工具:ThreadId 在 JSON 里按字符串处理。也就是说,文档和校验规则看到的是 string,而不是一个复杂对象。

数据流:进去一个 SchemaGenerator(生成结构说明的工具)→ 它直接复用 String 的 JSON Schema → 出来一份字符串类型的 Schema。

调用关系:它是 JsonSchema 实现的核心部分。生成接口文档、校验规则或前后端契约时,会通过它把 ThreadId 描述成字符串。

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

tests::test_thread_id_default_is_not_zeroes99–102 ↗
fn test_thread_id_default_is_not_zeroes()

作用:测试默认生成的 ThreadId 不是全零 UUID。全零 UUID 通常像“空身份证号”,如果默认值是它,很多地方可能误把无效编号当成有效编号。

数据流:进去不需要参数 → 它调用 ThreadId::default 生成一个编号,再和 UUID 的全零值比较 → 如果两者不同,测试通过;如果相同,测试失败。

调用关系:它只在测试时运行,用来保护 ThreadId::default 和 ThreadId::new 的行为。只要有人改动默认生成逻辑,这个测试就能及时提醒不能生成空编号。

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

protocol/src/session_id.rs源码 ↗
data_modelcross-cutting

系统里很多东西都要知道“这是哪一次会话”。如果只到处传普通字符串,容易传错格式,也容易把线程编号、会话编号混着用。这个文件用 SessionId 这个小包装,把 UUID(一种几乎不会重复的通用编号)变成专门的会话编号。它能新建编号,能从字符串解析回来,能转成字符串发出去,也能被 serde 序列化/反序列化(把程序里的值变成 JSON,或从 JSON 读回来)。它还告诉 JSON Schema 和 TypeScript:这个东西在外部看起来就是一个字符串。特别的是,它可以和 ThreadId 互相转换,说明在这套协议里,会话和线程在底层用的是同一种 UUID,只是语义名字不同。

函数细节14
SessionId::new20–24 ↗
fn new() -> Self

作用:新建一个会话编号。有人开启新会话、测试里需要假会话,或者系统要给某次交互贴标签时会用它。

数据流:进去不需要任何输入 → 它调用 UUID 库生成一个新的 v7 UUID(带时间顺序特征的唯一编号)→ 出来一个包着这个 UUID 的 SessionId。

调用关系:它是创建会话编号的源头。默认值会间接用它,一些 websocket 测试、事件通知测试和配置摘要测试也会直接调用它,保证每次都有一个真实的新编号。

调用图:被 5 处调用(websocket_harness_with_provider_options, config_summary_entries_include_runtime_workspace_roots, test_send_event_as_notification, test_send_event_as_notification_with_meta, test_send_event_as_notification_with_meta_and_thread_id);外部调用 1 个(now_v7)。

SessionId::from_string26–30 ↗
fn from_string(s: &str) -> Result<Self, uuid::Error>

作用:把外部传进来的字符串变成 SessionId。比如从配置、请求或保存的数据里读到一个会话编号时,就要先确认它真的是合法 UUID。

数据流:进去一个字符串 → 它交给 UUID 库检查并解析 → 如果格式对,出来 SessionId;如果格式错,出来解析错误,不会硬塞进系统。

调用关系:它是“外部文字编号进入程序”的入口。序列化事件、从线程响应配置会话等流程会用它,TryFrom 的转换实现也把具体解析工作交给它。

调用图:被 2 处调用(session_configured_from_thread_response, serialize_event);外部调用 1 个(parse_str)。

SessionId::try_from44–46 ↗
fn try_from(value: String) -> Result<Self, Self::Error>

作用:提供一种 Rust 标准写法,把字符串类输入尝试转换成 SessionId。好处是调用方可以用统一的“尝试转换”习惯,而不用记住具体函数名。

数据流:进去一个字符串值 → 它取出字符串内容并调用 SessionId::from_string → 出来合法的 SessionId,或者 UUID 格式错误。

调用关系:它只是一个方便入口,真正干活的是 SessionId::from_string。它让别的代码能用标准转换接口接入这个会话编号类型。

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

String::from50–52 ↗
fn from(value: SessionId) -> Self

作用:把 SessionId 变成普通字符串。需要把会话编号写进日志、JSON、网络消息或界面显示时会用到。

数据流:进去一个 SessionId → 它调用这个编号的字符串显示能力 → 出来 UUID 的文本形式。

调用关系:它连接了程序内部的强类型编号和外部常见的字符串格式。底层依赖 SessionId::fmt 提供的显示规则。

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

SessionId::from56–58 ↗
fn from(value: ThreadId) -> Self

作用:把 ThreadId 转成 SessionId。也就是说,同一个底层 UUID 可以换一个“会话编号”的身份来使用。

数据流:进去一个 ThreadId → 它取出里面的 UUID,不重新生成、不改变值 → 出来一个包着同一个 UUID 的 SessionId。

调用关系:它用于会话和线程概念需要对齐的地方,比如创建会话上下文、恢复子代理会话、生成线程开始事件等。测试也会验证转过去再转回来不会丢信息。

调用图:被 8 处调用(new, emit_subagent_session_started_includes_fork_lineage_from_session_configuration, make_session_and_context, make_session_and_context_with_auth_config_home_and_rx, resumed_subagent_session_keeps_inherited_session_id, session_configured_produces_thread_started_event, stream_stage_one_prompt, converts_to_and_from_thread_id)。

ThreadId::from62–64 ↗
fn from(value: SessionId) -> Self

作用:把 SessionId 转成 ThreadId。它和反方向转换配套使用,让同一个编号能在“会话”和“线程”两个语义之间切换。

数据流:进去一个 SessionId → 它拿出里面的 UUID,原样放进 ThreadId → 出来一个 ThreadId。

调用关系:它是 SessionId::from 的反向搭档。转换测试会用它确认 SessionId 和 ThreadId 来回转换后仍然是同一个编号。

SessionId::default68–70 ↗
fn default() -> Self

作用:给 SessionId 提供默认创建方式。别人写“给我一个默认会话编号”时,拿到的不是空编号,而是一个新的真实编号。

数据流:进去不需要输入 → 它调用 SessionId::new → 出来一个新生成的 SessionId。

调用关系:它把 Rust 的默认值机制接到 SessionId::new 上。测试 tests::test_session_id_default_is_not_zeroes 专门确认默认值不是全零 UUID。

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

SessionId::fmt74–76 ↗
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result

作用:规定 SessionId 被打印或转成文本时长什么样。简单说,就是显示里面那串 UUID。

数据流:进去一个 SessionId 和一个输出位置 → 它把内部 UUID 的显示结果写进去 → 调用方得到标准 UUID 字符串形式。

调用关系:String::from 和很多隐式的 to_string 场景都会依赖这个显示规则。它保证日志、调试输出和字符串转换看起来一致。

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

SessionId::serialize80–85 ↗
fn serialize(&self, serializer: S) -> Result<S::Ok, S::Error>

作用:把 SessionId 写成可传输、可保存的格式。这里它会被写成一个字符串,而不是复杂对象。

数据流:进去一个 SessionId 和 serde 的序列化器(负责把数据写成 JSON 等格式的工具)→ 它把内部 UUID 当作字符串交给序列化器 → 出来的 JSON 等数据里就是一段 UUID 文本。

调用关系:当协议消息、事件或配置要输出 SessionId 时,serde 会自动调用它。这样外部系统看到的会话编号始终是简单字符串。

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

SessionId::deserialize89–96 ↗
fn deserialize(deserializer: D) -> Result<Self, D::Error>

作用:从 JSON 等外部数据里读回 SessionId。它会先按字符串读取,再检查是不是合法 UUID。

数据流:进去一个 serde 反序列化器(负责从 JSON 等格式读数据的工具)→ 它先读出字符串,再用 UUID 解析器校验 → 成功就出来 SessionId,失败就返回清楚的反序列化错误。

调用关系:当外部请求或保存的数据带着会话编号进入程序时,serde 会自动调用它。它和 SessionId::serialize 配成一对,保证写出去和读回来格式一致。

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

SessionId::schema_name100–102 ↗
fn schema_name() -> String

作用:告诉 JSON Schema 生成器,这个类型在接口说明里叫 SessionId。JSON Schema 可以理解成给 JSON 数据写的“格式说明书”。

数据流:进去不需要输入 → 它返回固定名字 SessionId → 生成接口文档或校验规则时会用这个名字标识该字段类型。

调用关系:它是 JsonSchema 实现的一部分,配合 SessionId::json_schema 使用,让外部文档既知道名字,也知道实际格式。

SessionId::json_schema104–106 ↗
fn json_schema(generator: &mut SchemaGenerator) -> Schema

作用:告诉 JSON Schema 生成器:SessionId 在 JSON 里按字符串看待。这样前端或其他服务不用知道 Rust 里的包装结构。

数据流:进去一个 schema 生成器 → 它直接复用 String 的 schema → 出来一份“这是字符串”的 JSON Schema 描述。

调用关系:它和 schema_name 一起服务于接口描述生成。虽然程序内部是 SessionId 类型,协议文档里会表现成普通字符串,方便跨语言使用。

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

tests::test_session_id_default_is_not_zeroes114–117 ↗
fn test_session_id_default_is_not_zeroes()

作用:这个测试确认默认创建出来的 SessionId 不是全零 UUID。全零编号通常像“空值”或“占位符”,不能拿来当真实会话编号。

数据流:进去不需要输入 → 它调用 SessionId::default 生成一个编号 → 检查这个编号不等于 nil UUID(全零 UUID)。

调用关系:它保护 SessionId::default 的行为,防止以后有人把默认值改成无效的空编号。

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

tests::converts_to_and_from_thread_id120–125 ↗
fn converts_to_and_from_thread_id()

作用:这个测试确认 ThreadId 和 SessionId 互相转换时不会改变底层编号。也就是换了名字,但身份证号没变。

数据流:进去不需要输入 → 它新建一个 ThreadId,转成 SessionId,再转回 ThreadId → 最后检查转回来的值和原来完全一样。

调用关系:它覆盖 SessionId::from 和 ThreadId::from 这对转换函数,保证会话和线程之间共享 UUID 的设计不会被破坏。

调用图:调用 2 个内部函数(from, new);外部调用 1 个(assert_eq!)。

protocol/src/agent_path.rs源码 ↗
data_modelcross-cutting

可以把 AgentPath 理解成系统里的“门牌号”。每个 agent 都要有一个合法地址,通常从 /root 开始,也允许一个特殊地址 /morpheus。这个文件不只是存一段字符串,它会先检查这段字符串是不是安全、规范:必须以 /root 开头,不能以斜杠结尾,名字只能用小写字母、数字和下划线,不能用 root... 这些容易造成混淆的名字。这样做的好处是,后面的代码只要拿到 AgentPath,就可以相信它不是随手拼出来的坏路径。文件还提供了取根路径、拼子路径、解析相对路径、转成字符串、打印显示等小工具,让路径在 Rust、JSON、TypeScript schema 之间都能作为普通字符串使用,但内部仍保持校验过的安全格式。

函数细节22
AgentPath::root22–24 ↗
fn root() -> Self

作用:生成系统默认的根 agent 路径,也就是 /root。很多流程需要从根 agent 开始找人、发消息或列出子 agent,所以这是最常用的起点。

数据流:进去不需要任何输入 → 它把固定字符串 /root 包成一个 AgentPath → 出来的是一个已经确定合法的根路径对象,不会改动外部状态。

调用关系:它被很多业务流程当作起点使用,比如列出 agent、恢复 agent、处理父子 agent 通信等。resolve 在遇到引用正好是 /root 时,也会把活交给它来返回标准根路径。

调用图:被 38 处调用(list_agents, encrypted_inter_agent_communication_clears_existing_last_task_message, ensure_v2_agent_loaded_reloads_registered_unloaded_agent, list_agent_subtree_thread_ids_includes_anonymous_and_closed_descendants, multi_agent_v2_completion_ignores_dead_direct_parent, multi_agent_v2_completion_queues_message_for_direct_parent, resume_agent_from_rollout_does_not_reopen_v2_descendants, send_inter_agent_communication_without_turn_queues_message_without_triggering_turn, spawn_agent_can_fork_parent_thread_history_with_sanitized_items, spawn_agent_fork_last_n_turns_keeps_only_recent_turns (+15 more))。

AgentPath::morpheus26–28 ↗
fn morpheus() -> Self

作用:生成特殊的 /morpheus 路径。这个路径不是普通 /root/... 树上的节点,而是系统允许的一个特别地址。

数据流:进去不需要任何输入 → 它把固定字符串 /morpheus 包成 AgentPath → 出来的是一个特殊但合法的路径对象。

调用关系:目前调用事实里主要由测试 morpheus_has_expected_name 使用,用来确认这个特殊路径能被正确创建和识别。

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

AgentPath::from_string30–33 ↗
fn from_string(path: String) -> Result<Self, String>

作用:把外部传进来的一段字符串变成 AgentPath。它的重要点是不会盲目信任字符串,而是先检查这是不是合法路径。

数据流:进去是一段 String 路径文本 → 它调用 validate_absolute_path 检查必须是 /root.../morpheus 这种绝对路径 → 如果合法就包成 AgentPath 返回,如果不合法就返回错误文字。

调用关系:这是创建 AgentPath 的核心入口之一。恢复子 agent、按路径筛选 agent 等流程会直接用它;其他转换函数也会绕到这里,确保所有字符串路径都经过同一套检查。

调用图:调用 1 个内部函数(validate_absolute_path);被 2 处调用(resume_thread_subagent_restores_stored_nickname_and_role, multi_agent_v2_list_agents_filters_by_relative_path_prefix)。

AgentPath::as_str35–37 ↗
fn as_str(&self) -> &str

作用:把 AgentPath 里面保存的路径当作普通字符串借出来看。它适合用于比较、打印、传给只需要字符串的函数。

数据流:进去是一个 AgentPath 自身 → 它不复制内容,只把内部那段路径字符串的只读引用拿出来 → 出来是 &str,外部不能通过它修改路径。

调用关系:它是很多便捷方法的底层小零件。is_rootnameas_refdereffmt 都会调用它,其他流程如根据路径生成 agent id、释放保留路径、转发子 agent 完成消息时也会读这个字符串。

调用图:被 8 处调用(agent_id_for_path, release_reserved_agent_path, forward_child_completion_to_parent, as_ref, deref, fmt, is_root, name)。

AgentPath::is_root39–41 ↗
fn is_root(&self) -> bool

作用:判断这个路径是不是根路径 /root。这能帮助代码区分“根节点”和普通子 agent。

数据流:进去是一个 AgentPath → 它通过 as_str 取出路径文字,再和固定的 /root 比较 → 出来是 true 或 false,不改变任何东西。

调用关系:它被 name 用来给根路径返回特殊名字 root,也被匹配路径前缀的逻辑使用,用于判断当前 agent 是否处在根位置。

调用图:调用 1 个内部函数(as_str);被 2 处调用(agent_matches_prefix, name)。

AgentPath::name43–52 ↗
fn name(&self) -> &str

作用:取出路径最后一段,也就是这个 agent 的名字。比如 /root/researcher/worker 的名字就是 worker

数据流:进去是一个 AgentPath → 它先用 is_root 判断是不是根路径;如果是就返回 root;否则用 as_str 取字符串并按 / 从后往前找最后一段 → 出来是一段名字文本。

调用关系:它依赖 is_rootas_str 来保证根路径和普通路径都能得到合理名字。上层代码需要显示 agent 名称、比较名称或做界面展示时会用这种结果。

调用图:调用 2 个内部函数(as_str, is_root)。

AgentPath::join54–57 ↗
fn join(&self, agent_name: &str) -> Result<Self, String>

作用:在当前路径下面拼出一个子 agent 路径。比如从 /root 加上 researcher,得到 /root/researcher

数据流:进去是当前 AgentPath 和一个子 agent 名字 → 它先用 validate_agent_name 检查名字不能乱写,再用格式化字符串拼成 当前路径/名字,最后通过 from_string 再做一次完整路径检查 → 出来是新的 AgentPath,或者一条错误原因。

调用关系:它是创建子 agent 路径的安全拼接器。它把“检查单个名字”的活交给 validate_agent_name,再把“检查完整路径”的活交给 from_string,避免手写字符串拼接绕过规则。

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

AgentPath::resolve59–72 ↗
fn resolve(&self, reference: &str) -> Result<Self, String>

作用:把一个路径引用解析成真正的绝对 AgentPath。引用可以是绝对路径,比如 /root/other,也可以是相对路径,比如 worker

数据流:进去是当前路径和一段引用文字 → 如果引用为空就报错;如果正好是 /root 就返回根路径;如果以 / 开头就按绝对路径转换;否则先检查相对路径每一段名字,再拼到当前路径后面 → 出来是解析后的 AgentPath 或错误文字。

调用关系:它用于“从当前 agent 看另一个 agent 在哪里”的场景。它会根据情况调用 roottry_fromvalidate_relative_referencefrom_string,把不同写法统一成一个经过校验的绝对路径。

调用图:调用 1 个内部函数(validate_relative_reference);外部调用 4 个(from_string, root, try_from, format!)。

AgentPath::try_from86–88 ↗
fn try_from(value: &str) -> Result<Self, Self::Error>

作用:提供 Rust 标准的“尝试转换”入口,把字符串形式的路径转成 AgentPath。名字叫“try”是因为转换可能失败,会返回错误原因。

数据流:进去是一段路径文本 → 它把文本交给 from_string 做完整校验和包装 → 出来是合法 AgentPath,或者说明哪里不合法的错误字符串。

调用关系:很多测试和业务流程会用它从字面量或外部数据创建路径,比如处理中断的子 agent、加密通信、恢复 agent、邮箱投递等。它本身不重复规则,而是统一转交给 from_string

调用图:被 29 处调用(interrupted_subagent_activity_removes_missing_thread_watch, encrypted_inter_agent_communication_clears_existing_last_task_message, ensure_v2_agent_loaded_reloads_registered_unloaded_agent, send_inter_agent_communication_without_turn_queues_message_without_triggering_turn, spawn_agent_can_fork_parent_thread_history_with_sanitized_items, spawn_agent_fork_last_n_turns_keeps_only_recent_turns, agent_path, input_queue_drains_mailbox_in_delivery_order, input_queue_notifies_mailbox_subscribers, input_queue_tracks_pending_trigger_turn_mail (+15 more));外部调用 1 个(from_string)。

String::from92–94 ↗
fn from(value: AgentPath) -> Self

作用:把 AgentPath 转回普通 String。这样在需要保存、序列化或传给只认字符串的地方,可以取出里面的路径文本。

数据流:进去是一个 AgentPath 值本身 → 它拿走内部保存的 String → 出来是普通字符串;因为是拿走值,所以原来的 AgentPath 会被消费掉。

调用关系:这是配合序列化和类型转换使用的小接口。文件顶部声明 AgentPath 在 serde 中可以转成 String,这个函数就是这种转换的一部分。

AgentPath::from_str100–102 ↗
fn from_str(s: &str) -> Result<Self, Self::Err>

作用:支持从字符串切片解析 AgentPath,比如调用标准的 "/root/a".parse()。这让 AgentPath 用起来像 Rust 里常见的可解析类型。

数据流:进去是一段 &str 文本 → 它调用 try_from 走统一校验流程 → 出来是合法 AgentPath 或错误文字。

调用关系:它是标准 FromStr 接口的实现。它不自己检查规则,而是把转换工作交给 try_from,保证所有入口的规则一致。

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

AgentPath::as_ref106–108 ↗
fn as_ref(&self) -> &str

作用:让 AgentPath 可以在需要 AsRef<str> 的地方当字符串引用使用。简单说,就是让它更容易传给通用字符串函数。

数据流:进去是 AgentPath 的引用 → 它调用 as_str 取出内部路径的只读字符串引用 → 出来是 &str

调用关系:它是 Rust 通用接口 AsRef<str> 的实现。具体取字符串的活交给 as_str,自己只负责把 AgentPath 接到标准接口上。

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

AgentPath::deref114–116 ↗
fn deref(&self) -> &Self::Target

作用:让 AgentPath 在很多场合能像字符串一样被读取。Deref 可以理解成“自动拆开外壳看里面”。

数据流:进去是 AgentPath 的引用 → 它调用 as_str 拿内部路径 → 出来是字符串切片引用,不复制也不修改。

调用关系:它是 Rust 解引用接口的实现,方便其他代码把 AgentPath 传到一些期望字符串的地方。底层仍然依赖 as_str,保持取值方式统一。

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

AgentPath::fmt120–122 ↗
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result

作用:定义 AgentPath 被打印或格式化时显示什么。结果就是直接显示路径本身,比如 /root/researcher

数据流:进去是 AgentPath 和一个格式化输出器 → 它用 as_str 取出路径,再用输出器的 write_str 写进去 → 出来是格式化成功或失败的结果。

调用关系:这是 Display 接口的实现。日志、错误信息、字符串拼接里的 {self} 这类显示方式会走到它;它把真正的文本来源交给 as_str

调用图:调用 1 个内部函数(as_str);外部调用 1 个(write_str)。

validate_agent_name125–147 ↗
fn validate_agent_name(agent_name: &str) -> Result<(), String>

作用:检查单个 agent 名字是否合法。它像门卫一样,挡住空名字、保留字、带斜杠的名字、大写字母等不安全或不统一的写法。

数据流:进去是一段 agent 名字 → 它逐条检查:不能为空,不能是 root...,不能包含 /,只能有小写字母、数字、下划线 → 如果全部通过就返回成功,否则返回具体错误文字。

调用关系:它被 joinvalidate_absolute_pathvalidate_relative_reference 调用,是路径规则中最基础的一层。完整路径和相对路径最终都要拆成一段段名字交给它检查。

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

validate_absolute_path149–171 ↗
fn validate_absolute_path(path: &str) -> Result<(), String>

作用:检查一整条绝对 agent 路径是否合法。绝对路径就是从根开始写清楚的完整地址。

数据流:进去是一段路径字符串 → 它先允许特殊值 /morpheus;否则要求必须以 / 开头,第一段必须是 root,不能以 / 结尾,然后把后面的每一段交给 validate_agent_name 检查 → 出来是成功或错误原因。

调用关系:它是 from_string 的核心检查器。任何从普通字符串创建 AgentPath 的入口,最后都会通过它确认这是一条可信的完整路径。

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

validate_relative_reference173–181 ↗
fn validate_relative_reference(reference: &str) -> Result<(), String>

作用:检查相对路径引用是否合法。相对路径不是从 /root 开始,而是相对于当前 agent 往下找,比如 worker/helper

数据流:进去是一段相对引用文本 → 它先拒绝以 / 结尾的写法,再按 / 拆成多段,并把每一段交给 validate_agent_name 检查 → 出来是成功或错误原因。

调用关系:它被 resolve 调用。resolve 发现引用不是绝对路径时,会先用它确认相对写法安全,再拼到当前路径后面。

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

tests::root_has_expected_name189–194 ↗
fn root_has_expected_name()

作用:测试根路径的基本行为是不是正确。它确认 /root 创建出来后,字符串、名字和根判断都符合预期。

数据流:进去没有外部输入 → 它调用 AgentPath::root 创建根路径,然后用断言检查路径文本是 /root、名字是 rootis_root 为真 → 如果都对测试通过,否则测试失败。

调用关系:这是对 rootas_strnameis_root 这一组基础行为的保护。以后有人改路径规则时,如果破坏根路径语义,这个测试会及时报错。

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

tests::morpheus_has_expected_name197–202 ↗
fn morpheus_has_expected_name()

作用:测试特殊路径 /morpheus 的行为。它确认这个特殊地址能正常创建,名字是 morpheus,但不会被误认为根路径。

数据流:进去没有外部输入 → 它调用 AgentPath::morpheus,再断言字符串、名字和根判断结果 → 全部符合就通过,否则失败。

调用关系:它保护 morpheus 这个例外规则。因为 /morpheus 不走普通 /root/... 树,单独测试可以避免以后被误改成普通根路径逻辑。

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

tests::join_builds_child_paths205–210 ↗
fn join_builds_child_paths()

作用:测试从父路径拼子路径是否正确。它用最常见的场景确认 /rootresearcher 会得到 /root/researcher

数据流:进去没有外部输入 → 它先创建根路径,再调用 join("researcher"),最后断言生成的路径文本和名字都正确 → 符合预期则测试通过。

调用关系:它主要保护 join、名字校验和 name 的配合。子 agent 创建依赖这种拼接,测试能防止拼错斜杠或名字取错。

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

tests::resolve_supports_relative_and_absolute_references213–223 ↗
fn resolve_supports_relative_and_absolute_references()

作用:测试 resolve 同时支持相对引用和绝对引用。这样可以确保调用方既能写 worker,也能直接写 /root/other

数据流:进去没有外部输入 → 它先用 try_from 创建当前路径 /root/researcher,再分别解析 worker/root/other,并和期望的 AgentPath 比较 → 两种情况都正确则通过。

调用关系:它保护 resolve 的分支逻辑,也间接使用 try_from 创建对照路径。这个测试保证路径引用在父子 agent 通信或查找时不会被解析错。

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

tests::invalid_names_and_paths_are_rejected226–239 ↗
fn invalid_names_and_paths_are_rejected()

作用:测试非法名字和非法路径会被拒绝,并且错误信息符合预期。它确保校验规则真的在挡住危险或混乱的输入。

数据流:进去没有外部输入 → 它分别尝试大写名字、错误根路径、包含 .. 的相对路径 → 每次都断言返回的是预期错误文字 → 如果非法输入被放行或错误不对,测试失败。

调用关系:它保护 validate_agent_namevalidate_absolute_pathvalidate_relative_reference 这些校验门槛。只要有人放宽规则或改错错误信息,这个测试就会提醒。

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

protocol/src/tool_name.rs源码 ↗
data_modelcross-cutting

这份文件像是在给所有可调用工具发一张“身份证”。身份证上有必填的名字,也可能有一个命名空间;命名空间可以理解成“来自哪个柜台”或“属于哪个分组”。没有它,系统只看到一个普通字符串,遇到外部工具、内置工具、同名工具时就容易混在一起。ToolName 这个结构体保存这两部分,并且支持序列化和反序列化,也就是可以在网络消息或配置数据里安全地存取。它还提供了几种创建方式:普通名字、带命名空间的名字、或直接传可选命名空间。显示成文字时,它会把命名空间和名字直接拼起来;但内部仍然保留拆分后的两部分。文件还规定了排序规则,让工具名放进有序列表或集合时结果稳定可预期。

函数细节7
ToolName::new15–20 ↗
fn new(namespace: Option<String>, name: impl Into<String>) -> Self

作用:用一个可有可无的命名空间和一个名字,创建一个 ToolName。适合调用者已经自己判断好了“有没有命名空间”的场景。

数据流:进去的是 namespace,也就是可能存在的分组名,以及 name,也就是工具本身的名字;函数把 name 转成真正存储用的字符串,再连同 namespace 放进 ToolName;出来的是一个完整的工具名对象,原数据不会被额外修改。

调用关系:它会被 from_parts 和 build_tool_call 这类流程使用:前者像是把拆开的文本重新组装,后者像是在生成一次工具调用时给工具贴上准确名字。它自己只做装配,不再把活儿交给别的项目内函数,只用到标准的字符串转换能力。

调用图:被 2 处调用(from_parts, build_tool_call);外部调用 1 个(into)。

ToolName::plain22–27 ↗
fn plain(name: impl Into<String>) -> Self

作用:创建一个没有命名空间的普通工具名。有人只关心工具叫“什么”,不关心它属于哪个分组时,就会用它。

数据流:进去的是一个名字;函数把它变成字符串,并把 namespace 固定设成 None,也就是“没有命名空间”;出来的是一个只带普通名字的 ToolName。

调用关系:它是最常用的快捷入口,很多工具定义、权限检查、测试和描述生成都会用它来表示内置或简单工具。它不需要判断分组,只负责把一个普通名字包装成统一格式。

调用图:被 73 处调用(augment_tool_definition_appends_typed_declaration, augment_tool_definition_includes_property_descriptions_as_comments, code_mode_only_description_includes_nested_tools, blocking_tool, danger_full_access_tool_attempts_do_not_enforce_managed_network, guardian_allows_shell_command_additional_permissions_requests_past_policy_validation, guardian_allows_unified_exec_additional_permissions_requests_past_policy_validation, shell_command_allows_sticky_turn_permissions_without_inline_request_permissions_feature, strict_auto_review_turn_grant_forces_guardian_for_shell_command_policy_skip, rejects_escalated_permissions_when_policy_not_on_request (+15 more));外部调用 1 个(into)。

ToolName::namespaced29–34 ↗
fn namespaced(namespace: impl Into<String>, name: impl Into<String>) -> Self

作用:创建一个带命名空间的工具名。适合表示“某个来源下面的某个工具”,比如外部服务提供的一组工具。

数据流:进去的是 namespace 和 name;函数把两者都转成字符串,把 namespace 放进 Some 里表示“确实有分组”;出来的是一个保留了分组和名字两部分的 ToolName。

调用关系:它会被工具名规范化、MCP 工具处理、工具描述生成等流程调用。简单说,凡是系统需要清楚区分“这个工具来自哪个命名空间”时,就会走到这里。

调用图:被 32 处调用(code_mode_only_description_groups_namespace_instructions_once, code_mode_only_description_omits_empty_namespace_sections, code_mode_only_description_renders_shared_mcp_types_once, canonical_tool_name, tool_name, image_generation_publication_is_finalized_by_core, mcp_post_tool_use_payload_uses_prefixed_tool_name_args_and_result, mcp_updated_input_rewrites_builtin_like_tool_names_as_mcp, tool_name, tool_name (+15 more));外部调用 1 个(into)。

ToolName::fmt38–43 ↗
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result

作用:把 ToolName 变成人能看见的一段文字。这样日志、提示词、错误信息里就能直接显示工具名。

数据流:进去的是一个 ToolName 和一个输出位置;如果有命名空间,它就把命名空间和名字连在一起写出去;如果没有命名空间,就只写名字;出来的是格式化是否成功的结果。

调用关系:它属于 Rust 的 Display 显示接口,别人只要把 ToolName 当成文字打印,就会自动用到它。它不参与创建工具名,只负责最后展示。

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

ToolName::cmp47–57 ↗
fn cmp(&self, other: &Self) -> Ordering

作用:比较两个 ToolName 谁应该排在前面。这样工具名放进排序列表、树形集合或需要稳定顺序的地方时,不会乱跳。

数据流:进去的是两个 ToolName;函数先把每个工具名整理成可比较的形式:带命名空间的用“命名空间、名字”比较,普通工具名用“名字、无额外名字”比较;出来的是小于、等于或大于这样的排序结果。

调用关系:它是 ToolName 的正式排序规则,partial_cmp 会直接借用它。任何需要对 ToolName 排序的标准库功能,最终都会依赖这个规则。

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

ToolName::partial_cmp61–63 ↗
fn partial_cmp(&self, other: &Self) -> Option<Ordering>

作用:提供“可比较大小”的接口,让 ToolName 能用在更多需要排序的通用代码里。它其实是把工作交给完整排序函数 cmp。

数据流:进去的是两个 ToolName;函数调用 cmp 得到确定的排序结果,再把结果包成 Option,表示“比较一定有结果”;出来的是这个包装后的排序结果,不改动任何数据。

调用关系:它站在通用排序接口和真正排序逻辑之间。外部排序工具可能先找 partial_cmp,而它会立刻转给 cmp,保证所有地方用同一套排序规则。

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

ToolName::from73–75 ↗
fn from(name: &str) -> Self

作用:让普通字符串可以方便地变成 ToolName。这样调用者不用每次都手写 ToolName::plain,代码更顺手。

数据流:进去的是一个字符串形式的名字;函数把它当作没有命名空间的普通工具名,交给 plain 来包装;出来的是 namespace 为空、name 为输入内容的 ToolName。

调用关系:它是 Rust 的 From 转换接口的一部分,常在代码需要“自动把字符串变成工具名”时触发。它把实际创建工作交给 plain,所以普通字符串转换和手动创建普通工具名保持完全一致。

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

protocol/src/exec_output.rs源码 ↗
utilrequest handling / cross-cutting

运行外部命令时,程序拿到的常常不是现成的文字,而是一串字节。UTF-8 是现在常见的文字编码,也就是“字节怎么翻译成字符”的规则;但 Windows 命令行有时会用 CP1252、IBM866 这类老代码页,直接按 UTF-8 读就会乱码。这个文件先定义了输出盒子:一份流输出里有文字和“是否被截断”的记录;一次命令执行结果里有退出码、标准输出、错误输出、合并输出、耗时、是否超时。然后它提供一套“尽力读懂字节”的办法:先试 UTF-8,不行就让探测器猜编码,再解码。它还专门处理 IBM866 和 Windows-1252 都会抢同一批字节的坑,避免把弯引号误读成俄文字母。

函数细节8
StreamOutput::new22–27 ↗
fn new(text: String) -> Self

作用:创建一份最普通的文本输出记录。调用者给它一段文字,它会默认认为这段输出没有被截断。

数据流:进去的是一个字符串 → 它把这个字符串放进 StreamOutput 的 text 字段,并把 truncated_after_lines 设成空,表示没有截断信息 → 出来的是一个可继续传递的输出对象。

调用关系:很多执行命令的流程都会用它来包装输出,比如生成执行结果、发出执行结束事件、检查沙箱拒绝信息时。它是把散落的字符串变成统一输出格式的入口小零件。

调用图:被 15 处调用(make_exec_output, includes_timed_out_message, execute_user_shell_command, run, map_exec_result, emit_exec_end_for_unified_exec, emit_failed_exec_end_for_unified_exec, check_for_sandbox_denial_with_text, formats_basic_record, uses_aggregated_output_over_streams (+5 more))。

StreamOutput::from_utf8_lossy31–36 ↗
fn from_utf8_lossy(&self) -> StreamOutput<String>

作用:把一份“字节形式”的输出变成“字符串形式”的输出。名字里有 lossy,意思是必要时会用尽力而为的方式避免程序崩掉,但这里会先智能判断编码,尽量少丢信息。

数据流:进去的是 StreamOutput<Vec<u8>>,也就是字节输出和截断记录 → 它把字节交给 bytes_to_string_smart 转成文字,同时原样保留截断记录 → 出来的是 StreamOutput<String>。

调用关系:它站在原始进程输出和最终展示文字之间。真正复杂的识别编码工作交给 bytes_to_string_smart,自己负责保持 StreamOutput 这个外壳不变。

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

ExecToolCallOutput::default50–59 ↗
fn default() -> Self

作用:提供一个“空的命令执行结果”。当代码需要先占个位,或者遇到没有真实输出的情况时,可以拿它当安全默认值。

数据流:进去没有参数 → 它创建退出码为 0、三份空输出、耗时为 0、未超时的结果 → 出来的是一份完整但内容为空的 ExecToolCallOutput。

调用关系:它会调用 StreamOutput::new 来生成空的标准输出、错误输出和合并输出。它服务于需要默认执行结果的地方,让别的代码不用手工拼每个字段。

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

bytes_to_string_smart63–74 ↗
fn bytes_to_string_smart(bytes: &[u8]) -> String

作用:把任意一串字节尽量正确地变成可读字符串。它解决的核心问题是:外部命令输出可能不是 UTF-8,不能简单粗暴地直接读。

数据流:进去的是字节切片 → 如果为空就返回空字符串;如果本来就是 UTF-8,就直接转成字符串;否则先调用 detect_encoding 猜编码,再调用 decode_bytes 解码 → 出来是一段尽量不乱码的文字。

调用关系:收集子进程输出的 spawn_process_output、collect_spawn_process_output 会用它,StreamOutput::from_utf8_lossy 也会用它。它是整个文件里“字节变文字”的主通道。

调用图:调用 2 个内部函数(decode_bytes, detect_encoding);被 3 处调用(spawn_process_output, collect_spawn_process_output, from_utf8_lossy);外部调用 2 个(new, from_utf8)。

detect_encoding97–116 ↗
fn detect_encoding(bytes: &[u8]) -> &'static Encoding

作用:猜一串字节最可能用了哪种文字编码。它还修正一个常见误判:把 Windows 的弯引号错当成 IBM866 俄文编码。

数据流:进去的是原始字节 → 它用 chardetng 这个编码探测器猜测编码;如果猜成 IBM866,又发现字节更像 Windows-1252 的弯引号、破折号之类标点,就改判为 Windows-1252 → 出来的是一个编码规则。

调用关系:它只被 bytes_to_string_smart 调用,是智能解码的判断步骤。遇到 IBM866 的可疑情况时,它会把细查工作交给 looks_like_windows_1252_punctuation。

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

decode_bytes118–126 ↗
fn decode_bytes(bytes: &[u8], encoding: &'static Encoding) -> String

作用:按照指定编码把字节翻译成字符串。如果指定编码翻译时出错,它会退回到宽容的 UTF-8 读法,保证至少能给出结果。

数据流:进去的是字节和一个编码规则 → 它用这个编码规则解码;如果报告有错误,就改用 String::from_utf8_lossy 这种“坏字节用替代字符兜底”的方式 → 出来是一段字符串。

调用关系:它被 bytes_to_string_smart 在选好编码后调用。前面的 detect_encoding 负责“选字典”,它负责“查字典翻译”。

调用图:被 1 处调用(bytes_to_string_smart);外部调用 2 个(decode, from_utf8_lossy)。

looks_like_windows_1252_punctuation141–161 ↗
fn looks_like_windows_1252_punctuation(bytes: &[u8]) -> bool

作用:判断一串字节是不是很像 Windows-1252 的智能标点夹在普通英文里。这个检查用来避免把弯引号、长破折号误显示成奇怪的俄文字母。

数据流:进去的是字节 → 它逐个查看:如果出现太高的字节就否定;如果出现 0x80 到 0x9F 之间的字节,必须是允许的 Windows-1252 标点;同时还要看到 ASCII 英文字母 → 最后返回真或假。

调用关系:它被 detect_encoding 在探测器猜到 IBM866 时调用。它再用 is_windows_1252_punct 检查单个字节是不是允许的标点白名单成员。

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

is_windows_1252_punct163–165 ↗
fn is_windows_1252_punct(byte: u8) -> bool

作用:检查某个字节是不是 Windows-1252 里常见的智能标点,比如弯引号、项目符号、长短破折号、商标符号。

数据流:进去的是一个字节 → 它在预先列好的 WINDOWS_1252_PUNCT_BYTES 清单里查找 → 出来的是布尔值,表示这个字节是不是认可的标点。

调用关系:它只服务于 looks_like_windows_1252_punctuation。这个小函数像门卫,只回答“这个可疑字节是不是我们允许的那几种标点”。

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

protocol/src/lib.rs源码 ↗
orchestrationcross-cutting

这个文件本身不写具体业务动作,更像一本书的目录页。项目里和“协议”有关的东西很多:账号、认证、会话编号、线程编号、工具名称、权限申请、执行输出、模型信息、网络策略、用户输入等等。如果没有这个总入口,外部代码就得知道每个小文件藏在哪里,引用起来又乱又容易错。这里用 pub mod 把一些模块公开出去,表示外部可以直接使用;用 mod 把内部模块引入但不完全暴露;再用 pub use 把常用类型,比如 AgentPathSessionIdThreadIdToolName,放到库的最外层,像把常用工具摆在柜台上,别人不用进仓库翻找。它的重要性在于稳定了整个 protocol 包的公共入口:外面的人只需要认识这个库,不需要关心内部文件怎么拆分。

核心协议 schema

这些文件定义主要的可复用协议数据模型,用于配置、权限、内容、工具、命令,以及面向账户的 payload,并输入到顶层会话协议中。

protocol/src/permissions.rs源码 ↗
domain_logiccross-cutting

可以把这个文件理解成系统的“门禁规则本”。它把文件系统权限分成不受限、外部沙箱、受限几种情况;再用一条条规则说明某个路径是可读、可写,还是禁止。它还会把“项目根目录”“临时目录”“/tmp”这类特殊名字,换成当前机器上的真实路径。运行时要判断一个工具能不能碰某个文件,就会来这里查规则。比较重要的是,它默认会把 .git、.codex、.agents 这类目录当成敏感区:即使项目目录整体可写,也不一定允许直接写这些地方,除非规则明确放行。文件里还负责把新权限模型和旧沙箱模型互相转换,并判断旧运行时是否足够表达当前规则。 这部分代码主要围绕“文件沙箱”做安全兜底。沙箱可以理解成给程序画的一圈活动范围:项目目录可以写,但像 .git、.agents、.codex 这种保存仓库和代理元数据的目录,默认要特别保护,不能随便改。代码还要处理很多现实里的麻烦情况,比如 Git 工作树里的 .git 可能不是目录而是一个指向真实 git 目录的小文件;路径可能是符号链接(类似快捷方式);规则可能来自老版本格式,也可能来自新格式;还可能用 glob 通配符匹配一批文件。这里的测试重点验证:规则越具体越优先、显式允许可以覆盖默认保护、符号链接路径不能被偷偷“变成真实路径”后改变含义、拒绝读取的规则能拦住精确路径和通配路径。

函数细节129
is_protected_metadata_name35–39 ↗
fn is_protected_metadata_name(name: &OsStr) -> bool

作用:判断一个文件名是不是受保护的项目元数据名,比如 .git、.codex、.agents。调用方用它来快速识别“这里不能随便写”。

数据流:输入一个操作系统文件名 → 和内置的受保护名字列表逐个比较 → 输出 true 或 false,不改任何数据。

调用关系:这是保护元数据规则的入口小工具,后面的元数据路径判断会用同一套名字表来决定哪些目录要额外小心。

is_protected_metadata_directory_name41–44 ↗
fn is_protected_metadata_directory_name(name: &OsStr) -> bool

作用:判断一个目录名是不是受保护的元数据目录名,当前重点是 .agents 和 .codex。它比完整列表更窄,只看特定目录。

数据流:输入一个目录名 → 分别和 .agents、.codex 对比 → 返回是否命中,不改外部状态。

调用关系:它是给外部检查用的轻量判断,和更通用的 is_protected_metadata_name 一起服务于元数据保护规则。

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

forbidden_agent_metadata_write48–77 ↗
fn forbidden_agent_metadata_write(
    path: &Path,
    cwd: &Path,
    file_system_sandbox_policy: &FileSystemSandboxPolicy,
) -> Option<&'static str>

作用:判断代理是不是正在尝试写受保护的元数据路径,并在禁止时告诉调用方命中了哪个元数据名。它用于给用户或上层逻辑一个明确原因,而不只是说“不能写”。

数据流:输入目标路径、当前工作目录、文件系统沙箱规则 → 只在受限沙箱下继续检查;先把相对路径变成绝对路径,再看它是否落在可写根目录里的受保护元数据区;如果没有明确写入放行且普通写权限也不允许,就返回元数据名,否则返回 None。

调用关系:它把路径解析、可写根目录检查、显式放行检查和普通写权限判断串起来;内部会调用 resolve_candidate_path、metadata_child_of_writable_root、has_explicit_write_entry_for_metadata_path 和 can_write_path_with_cwd。

调用图:调用 4 个内部函数(can_write_path_with_cwd, has_explicit_write_entry_for_metadata_path, metadata_child_of_writable_root, resolve_candidate_path);外部调用 1 个(matches!)。

NetworkSandboxPolicy::is_enabled91–93 ↗
fn is_enabled(self) -> bool

作用:回答网络沙箱策略是否允许联网。很多地方只需要一个简单的是/否结果。

数据流:输入一个网络策略值 → 判断它是不是 Enabled → 输出布尔值,不改任何数据。

调用关系:创建沙箱、兼容旧策略、决定是否安装网络限制时都会问它;它是网络权限判断里最基础的小开关。

调用图:被 10 处调用(spawn_debug_sandbox_child, should_install_network_seccomp, bwrap_network_mode, network_access_from_policy, from, to_legacy_sandbox_policy, compatibility_workspace_write_policy, should_require_platform_sandbox, dynamic_network_policy_for_network, should_apply_network_block);外部调用 1 个(matches!)。

FileSystemAccessMode::can_read127–129 ↗
fn can_read(self) -> bool

作用:判断某种文件权限是否允许读取。只要不是明确拒绝,就算可读。

数据流:输入一个访问模式 → 如果不是 Deny 就返回 true,否则 false。

调用关系:更高层的 access_covers 和各种路径权限判断会用它,把“读不读得了”统一成一个简单规则。

调用图:被 1 处调用(access_covers);外部调用 1 个(matches!)。

FileSystemAccessMode::can_write131–133 ↗
fn can_write(self) -> bool

作用:判断某种文件权限是否允许写入。只有 Write 才算真正可写。

数据流:输入一个访问模式 → 检查是否等于 Write → 返回 true 或 false。

调用关系:写权限收窄、可写根目录计算、元数据保护检查都会依赖它。

调用图:被 1 处调用(access_covers);外部调用 1 个(matches!)。

FileSystemSpecialPath::project_roots167–169 ↗
fn project_roots(subpath: Option<PathBuf>) -> Self

作用:创建一个代表“项目根目录”的特殊路径,可以附带子路径。它让规则不用立刻知道真实路径,也能表达“当前项目下面某处”。

数据流:输入可选子路径 → 包装成 ProjectRoots 这个特殊路径值 → 返回这个值。

调用关系:workspace_write 和旧策略投影会用它表达项目根可写,后续再由 materialize 或 resolve 系列函数换成真实绝对路径。

FileSystemSpecialPath::unknown171–176 ↗
fn unknown(path: impl Into<String>, subpath: Option<PathBuf>) -> Self

作用:创建一个系统暂时不认识的特殊路径标记。这样配置里出现新类型时,不至于直接丢掉信息。

数据流:输入路径名字和可选子路径 → 转成字符串并保存 → 返回 Unknown 特殊路径。

调用关系:它主要是数据兼容用,后续匹配和解析函数会把 Unknown 当成不能直接解析的特殊项。

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

FileSystemSandboxEntry::from186–191 ↗
fn from(value: FileSystemSandboxEntry<AbsolutePathBuf>) -> Self

作用:把使用绝对路径的沙箱规则项转换成使用路径 URI 的规则项。URI 可以理解成更适合传输或序列化的路径写法。

数据流:输入一条绝对路径规则 → 转换其中的 path 字段,保留 access 权限不变 → 输出新规则项。

调用关系:这是模型转换层的小桥,帮助权限规则在内部绝对路径和外部传输格式之间来回走。

FileSystemSandboxEntry::try_from197–202 ↗
fn try_from(value: FileSystemSandboxEntry<PathUri>) -> Result<Self, Self::Error>

作用:把使用路径 URI 的沙箱规则项尽量转回绝对路径规则项。如果 URI 不是合法绝对路径,就返回错误。

数据流:输入一条 URI 路径规则 → 尝试把 path 转成绝对路径,保留 access → 成功返回规则项,失败返回转换错误。

调用关系:它和 FileSystemSandboxEntry::from 成对出现,用在配置或协议数据进入内部权限判断之前。

ReadDenyMatcher::new257–266 ↗
fn new(file_system_sandbox_policy: &FileSystemSandboxPolicy, cwd: &Path) -> Option<Self>

作用:为“禁止读取”的规则创建匹配器。匹配器就像筛子,之后拿一个路径过来就能判断是否被禁止读。

数据流:输入沙箱规则和当前工作目录 → 用 fail-closed 策略构建匹配器;fail-closed 的意思是规则坏了也按更安全的方式处理 → 返回可能存在的匹配器。

调用关系:is_read_denied 需要先有这个匹配器;它把实际构建工作交给 ReadDenyMatcher::build。

调用图:被 1 处调用(is_read_denied);外部调用 2 个(build, unreachable!)。

ReadDenyMatcher::try_new273–282 ↗
fn try_new(
        file_system_sandbox_policy: &FileSystemSandboxPolicy,
        cwd: &Path,
    ) -> Result<Option<Self>, String>

作用:尝试创建禁止读取匹配器,但遇到错误会把错误返回给调用方。它适合需要把配置错误明确报告出来的场景。

数据流:输入沙箱规则和当前工作目录 → 用 ReturnError 策略构建 → 成功返回匹配器或 None,失败返回错误文字。

调用关系:resolve_windows_deny_read_paths 会用它;实际构建仍交给 ReadDenyMatcher::build。

调用图:被 1 处调用(resolve_windows_deny_read_paths);外部调用 1 个(build)。

ReadDenyMatcher::build284–321 ↗
fn build(
        file_system_sandbox_policy: &FileSystemSandboxPolicy,
        cwd: &Path,
        invalid_glob_behavior: InvalidDenyReadGlobBehavior,
    ) -> Result<Option<Self>, String>

作用:真正组装禁止读取匹配器。它把精确路径和通配符规则分别整理好,方便后续快速判断。

数据流:输入沙箱规则、当前工作目录和坏通配符的处理方式 → 如果没有拒读规则就返回 None;否则收集不可读根目录的不同路径写法,再编译不可读通配符 → 输出匹配器,或在要求报错时输出错误。

调用关系:ReadDenyMatcher::new 和 try_new 都靠它;它会读取策略里的 get_unreadable_roots_with_cwd、get_unreadable_globs_with_cwd,并调用 build_glob_matcher。

调用图:调用 4 个内部函数(get_unreadable_globs_with_cwd, get_unreadable_roots_with_cwd, has_denied_read_restrictions, build_glob_matcher);外部调用 2 个(new, format!)。

ReadDenyMatcher::is_read_denied324–350 ↗
fn is_read_denied(&self, path: &Path) -> bool

作用:判断某个路径是否被禁止读取。它同时考虑精确目录规则和通配符规则。

数据流:输入一个路径 → 如果之前有坏通配符且采用保守处理,直接禁止;否则生成普通写法和真实路径写法,再看是否落在禁止根目录下或匹配禁止通配符 → 返回 true 或 false。

调用关系:collect_existing_glob_matches 会用它检查实际文件;它依赖 normalized_and_canonical_candidates 来处理符号链接和规范路径。

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

FileSystemPath::from377–385 ↗
fn from(value: FileSystemPath<AbsolutePathBuf>) -> Self

作用:把内部绝对路径形式的 FileSystemPath 转成 URI 形式,方便传输或保存。

数据流:输入一个 FileSystemPath<AbsolutePathBuf> → 如果是普通路径就转成 PathUri;如果是通配符或特殊路径就原样保留 → 输出 FileSystemPath<PathUri>。

调用关系:它是路径模型转换的一部分,和 try_from 反向配合。

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

FileSystemPath::try_from391–399 ↗
fn try_from(value: FileSystemPath<PathUri>) -> Result<Self, Self::Error>

作用:把 URI 形式的 FileSystemPath 转回内部绝对路径形式。普通路径可能失败,所以它返回 Result。

数据流:输入 FileSystemPath<PathUri> → 普通路径尝试转绝对路径,通配符和特殊路径原样保留 → 成功输出内部路径,失败输出错误。

调用关系:配置或协议传来的权限路径进入内部判断前,会通过这类转换变成可计算的绝对路径。

project_roots_glob_pattern404–406 ↗
fn project_roots_glob_pattern(subpath: &Path) -> String

作用:生成一个表示“项目根目录下某个子路径”的通配符模式字符串。它让通配符规则也能先写成相对项目根的形式。

数据流:输入子路径 → 拼上项目根通配符前缀 → 输出字符串模式。

调用关系:compile_scoped_filesystem_pattern 会用它;后续 parse_project_roots_glob_pattern 和 resolve_project_roots_glob_pattern 会把它解析和落地。

调用图:被 1 处调用(compile_scoped_filesystem_pattern);外部调用 1 个(format!)。

read_only_file_system_entries408–415 ↗
fn read_only_file_system_entries() -> Vec<FileSystemSandboxEntry>

作用:生成最基础的只读文件系统规则:整个文件系统根目录可读,但不可写。

数据流:无输入 → 创建一条 Root=Read 的规则 → 返回规则列表。

调用关系:FileSystemSandboxPolicy::read_only 调用它来构建默认只读策略。

调用图:被 1 处调用(read_only);外部调用 1 个(vec!)。

FileSystemSandboxPolicy::default418–420 ↗
fn default() -> Self

作用:给文件系统沙箱提供默认策略。默认是只读,比较安全。

数据流:无输入 → 调用 read_only → 返回只读策略。

调用关系:测试和默认配置会依赖它;它把默认选择委托给 FileSystemSandboxPolicy::read_only。

调用图:被 8 处调用(file_system_sandbox_context_uses_active_attempt, default_exec_approval_requirement_keeps_prompt_when_granular_allows_sandbox_approval, default_exec_approval_requirement_rejects_sandbox_prompt_when_granular_disables_it, extension_tool_receives_turn_environment_sandbox, view_image_tool_applies_local_sandbox_read_denies, default_policy_with_unreadable_glob, default_policy_with_unreadable_glob, unreadable_glob_policy_includes_canonicalized_static_prefix);外部调用 1 个(read_only)。

FileSystemSandboxPolicy::read_only424–426 ↗
fn read_only() -> Self

作用:创建一个受限的只读沙箱策略。工具可以看文件,但不能改文件。

数据流:无输入 → 生成只读规则列表 → 包装成 Restricted 策略返回。

调用关系:default 会调用它;它用 read_only_file_system_entries 准备规则,再交给 restricted。

调用图:调用 1 个内部函数(read_only_file_system_entries);被 2 处调用(extensible_builtin_parent_profile, read_only);外部调用 1 个(restricted)。

FileSystemSandboxPolicy::unrestricted428–434 ↗
fn unrestricted() -> Self

作用:创建一个文件系统完全不受限的策略。它表示对磁盘读写没有沙箱限制。

数据流:无输入 → 设置 kind 为 Unrestricted,清空规则 → 返回策略。

调用关系:从旧的 DangerFullAccess 策略转换、全盘访问场景都会用它。

调用图:被 15 处调用(managed_full_disk_with_restricted_network_reports_external_sandbox, windows_restricted_token_rejects_network_only_restrictions, exec_server_params_use_path_uri_and_env_policy_overlay_contract, full_disk_write_full_network_returns_unwrapped_command, full_disk_write_proxy_only_keeps_full_filesystem_but_unshares_network, managed_proxy_preflight_argv_is_wrapped_for_full_access_policy, to_sandbox_policy, file_system_sandbox_policy, disabled_permission_profile_ignores_runtime_network_policy, permission_profile_from_runtime_permissions_preserves_unrestricted_managed_network (+5 more));外部调用 1 个(new)。

FileSystemSandboxPolicy::external_sandbox436–442 ↗
fn external_sandbox() -> Self

作用:创建一个由外部系统负责限制文件访问的策略。当前代码不再自己列具体文件规则。

数据流:无输入 → 设置 kind 为 ExternalSandbox,规则为空 → 返回策略。

调用关系:旧策略 ExternalSandbox 转换和权限配置保留时会用它。

调用图:被 4 处调用(external_sandbox_auto_approves_in_on_request, file_system_sandbox_policy, permission_profile_from_runtime_permissions_preserves_external_sandbox, from);外部调用 1 个(new)。

FileSystemSandboxPolicy::restricted444–450 ↗
fn restricted(entries: Vec<FileSystemSandboxEntry>) -> Self

作用:用给定规则创建一个受限文件系统策略。它是大多数精细权限控制的外壳。

数据流:输入规则列表 → 设置 kind 为 Restricted,保存这些规则 → 返回策略。

调用关系:read_only、workspace_write、权限 profile 编译和很多测试都会用它作为受限策略构造器。

调用图:被 138 处调用(requested_permissions_trust_project_uses_permission_profile_intent, permission_profile_override_keeps_memories_root_out_of_legacy_projection, permission_profile_override_preserves_split_write_roots, compile_permission_profile, workspace_write_permission_profile_with_private_denials, managed_cwd_write_profile_has_filesystem_restrictions, managed_full_disk_write_profile_has_no_filesystem_restrictions, managed_unresolvable_write_profile_has_filesystem_restrictions, writable_windows_policy_without_sandbox_backend_still_requires_approval, windows_elevated_allows_split_restricted_read_policies (+15 more))。

FileSystemSandboxPolicy::has_root_access452–461 ↗
fn has_root_access(&self, predicate: impl Fn(FileSystemAccessMode) -> bool) -> bool

作用:检查受限策略里是否对文件系统根目录有某种访问能力,比如能读或能写。

数据流:输入一个权限判断函数 → 只在 Restricted 下检查规则里是否有 Root 且满足该判断 → 返回 true 或 false。

调用关系:has_full_disk_read_access 和 has_full_disk_write_access 用它判断是否拥有全盘级别访问。

调用图:被 2 处调用(has_full_disk_read_access, has_full_disk_write_access);外部调用 1 个(matches!)。

FileSystemSandboxPolicy::has_denied_read_restrictions463–469 ↗
fn has_denied_read_restrictions(&self) -> bool

作用:判断策略里有没有明确的“禁止读取”规则。即使有全盘读权限,只要有拒读条目,也不能算完全可读。

数据流:读取当前策略的 kind 和 entries → 如果是 Restricted 且存在 Deny 规则,返回 true → 否则 false。

调用关系:ReadDenyMatcher::build、全盘读判断和是否允许无沙箱执行等流程都会用它。

调用图:被 3 处调用(unsandboxed_execution_allowed, has_full_disk_read_access, build);外部调用 1 个(matches!)。

FileSystemSandboxPolicy::from_legacy_sandbox_policy_preserving_deny_entries471–493 ↗
fn from_legacy_sandbox_policy_preserving_deny_entries(
        sandbox_policy: &SandboxPolicy,
        cwd: &Path,
        existing: &Self,
    ) -> Self

作用:从旧沙箱策略重建新文件系统策略,同时保留已有的拒读规则。这样更新旧配置时,不会把更细的安全限制弄丢。

数据流:输入旧沙箱策略、当前目录、已有新策略 → 先按旧策略重建;如果结果不是受限策略就直接返回;否则复制扫描深度,并补上已有 Deny 条目 → 返回合并后的策略。

调用关系:配置 apply 和兼容旧策略时会用它;它调用 from_legacy_sandbox_policy_for_cwd 做基础转换。

调用图:被 2 处调用(apply, legacy_bridge_preserves_explicit_deny_entries);外部调用 2 个(from_legacy_sandbox_policy_for_cwd, matches!)。

FileSystemSandboxPolicy::preserve_deny_read_restrictions_from497–528 ↗
fn preserve_deny_read_restrictions_from(&mut self, existing: &Self)

作用:把另一个策略里的拒读限制保留到当前策略里。它防止权限变更时意外放开本来禁止读取的区域。

数据流:输入已有策略 → 如果当前是不受限但已有拒读规则,就先降成根目录可写的受限策略;再复制扫描深度和不存在的 Deny 条目 → 修改当前策略本身。

调用关系:它用于策略叠加或替换时的安全兜底;内部会在必要时调用 restricted。

调用图:外部调用 3 个(restricted, matches!, vec!)。

FileSystemSandboxPolicy::has_write_narrowing_entries537–556 ↗
fn has_write_narrowing_entries(&self) -> bool

作用:判断写权限是否被更窄的规则收紧了。比如根目录可写,但某个子目录只读,这就不是“真正全盘可写”。

数据流:读取受限策略里的每条规则 → 跳过可写规则;对路径、通配符和特殊路径判断是否形成写入限制 → 返回是否存在这种收窄。

调用关系:has_full_disk_write_access 用它排除“看似全盘写、实际有例外”的策略。

调用图:被 1 处调用(has_full_disk_write_access);外部调用 1 个(matches!)。

FileSystemSandboxPolicy::has_same_target_write_override560–566 ↗
fn has_same_target_write_override(&self, entry: &FileSystemSandboxEntry) -> bool

作用:检查某个非写规则是否被同一目标上的更强写规则覆盖。这样可以避免把已经明确放行的地方误判为受限。

数据流:输入一条规则 → 遍历当前策略规则,找访问权限更高且指向同一目标的写规则 → 返回是否找到。

调用关系:has_write_narrowing_entries 会用它判断非写规则是否真的收窄了写权限。

FileSystemSandboxPolicy::workspace_write570–627 ↗
fn workspace_write(
        writable_roots: &[AbsolutePathBuf],
        exclude_tmpdir_env_var: bool,
        exclude_slash_tmp: bool,
    ) -> Self

作用:创建“工作区可写”的常用策略:整体可读,项目根、指定目录和临时目录可写,但敏感元数据默认只读。

数据流:输入额外可写根目录,以及是否排除 TMPDIR 和 /tmp → 先加入根目录只读、项目根可写、临时目录可写规则;再加入用户指定可写目录;最后给 .git、.agents、.codex 等敏感位置补只读规则 → 返回受限策略。

调用关系:旧 WorkspaceWrite 策略转换和多处工作区写权限配置都会调用它;它会用 default_read_only_subpaths_for_writable_root 和 append_default_read_only 系列函数补保护规则。

调用图:调用 4 个内部函数(restricted, append_default_read_only_path_if_no_explicit_rule, append_default_read_only_project_root_subpath_if_no_explicit_rule, default_read_only_subpaths_for_writable_root);被 8 处调用(extensible_builtin_parent_profile, test_writable_roots_constraint, write_permissions_for_paths_keep_dirs_outside_workspace_root, write_permissions_for_paths_skip_dirs_already_writable_under_workspace_root, ignores_missing_writable_roots, mounts_dev_before_writable_dev_binds, workspace_write_with, from);外部调用 3 个(project_roots, iter, vec!)。

FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd636–663 ↗
fn from_legacy_sandbox_policy_for_cwd(sandbox_policy: &SandboxPolicy, cwd: &Path) -> Self

作用:把旧版沙箱策略转换成新版文件系统策略,并按当前目录补上默认保护项。

数据流:输入旧策略和当前工作目录 → 先做基础转换;如果是工作区可写策略,就为当前目录和额外可写根目录补 .git、.codex 等只读保护 → 返回新策略。

调用关系:一次性命令执行、设置旧策略、会话配置更新等兼容路径会用它;它依赖 default_read_only_subpaths_for_writable_root 和 append_default_read_only_path_if_no_explicit_rule。

调用图:调用 3 个内部函数(append_default_read_only_path_if_no_explicit_rule, default_read_only_subpaths_for_writable_root, from_absolute_path);被 19 处调用(exec_one_off_command_inner, can_set_legacy_sandbox_policy, set_legacy_sandbox_policy, file_system_policy_with_unreadable_glob, session_configuration_apply_permission_profile_preserves_existing_deny_read_entries, session_configuration_apply_retargets_legacy_workspace_root_on_cwd_update, non_legacy_file_system_sandbox_policy, build_agent_spawn_config_uses_turn_context_values, spawn_agent_reapplies_runtime_sandbox_after_role_config, network_approval_retry_keeps_deny_read_sandbox_for_escalated_command (+9 more));外部调用 1 个(from)。

FileSystemSandboxPolicy::has_full_disk_read_access666–674 ↗
fn has_full_disk_read_access(&self) -> bool

作用:判断策略是否等同于拥有全盘读取能力。受限策略必须根目录可读,并且没有拒读例外,才算全盘可读。

数据流:读取策略类型 → 不受限或外部沙箱直接算 true;受限时检查根目录可读且没有 Deny 读限制 → 输出布尔值。

调用关系:生成沙箱参数、计算可读根、语义签名和权限扩展都会问它。

调用图:调用 2 个内部函数(has_denied_read_restrictions, has_root_access);被 7 处调用(add_helper_runtime_permissions, create_filesystem_args, get_readable_roots_with_cwd, include_platform_defaults, semantic_signature, with_additional_readable_roots, has_full_disk_read_access)。

FileSystemSandboxPolicy::has_full_disk_write_access677–685 ↗
fn has_full_disk_write_access(&self) -> bool

作用:判断策略是否等同于拥有全盘写入能力。受限策略必须根目录可写,并且没有更窄的写入限制。

数据流:读取策略类型 → 不受限或外部沙箱直接算 true;受限时检查根目录可写且没有写权限收窄条目 → 输出布尔值。

调用关系:沙箱命令生成、写权限判断、旧策略转换和提示文案都会用它。

调用图:调用 2 个内部函数(has_root_access, has_write_narrowing_entries);被 10 处调用(patch_rejection_reason, create_bwrap_command_args, create_filesystem_args, sandbox_prompt_from_policy, can_write_path_with_cwd, get_writable_roots_with_cwd, semantic_signature, to_legacy_sandbox_policy, ensure_linux_bubblewrap_is_supported, should_require_platform_sandbox)。

FileSystemSandboxPolicy::include_platform_defaults688–699 ↗
fn include_platform_defaults(&self) -> bool

作用:判断是否应该加入平台默认可读位置。平台默认位置可以理解成系统运行所需的一些基础文件。

数据流:读取策略 → 如果没有全盘读、是受限策略,并且 Minimal 特殊路径可读,就返回 true → 否则 false。

调用关系:创建文件系统沙箱参数和生成语义签名时会用它。

调用图:调用 1 个内部函数(has_full_disk_read_access);被 3 处调用(create_filesystem_args, semantic_signature, include_platform_defaults);外部调用 1 个(matches!)。

FileSystemSandboxPolicy::resolve_access_with_cwd701–719 ↗
fn resolve_access_with_cwd(&self, path: &Path, cwd: &Path) -> FileSystemAccessMode

作用:计算某个路径在当前工作目录下最终是什么权限:拒绝、可读,还是可写。

数据流:输入路径和当前目录 → 不受限或外部沙箱直接返回 Write;受限时先把路径转绝对,再找所有能覆盖它的规则,选最具体且权限优先级最高的一条 → 返回访问模式。

调用关系:can_read_path_with_cwd、can_write_path_with_cwd 和请求内授权判断都会以它为核心。

调用图:调用 2 个内部函数(resolved_entries_with_cwd, resolve_candidate_path);被 3 处调用(can_read_path_with_cwd, can_write_path_with_cwd, granted_file_system_entry_within_request)。

FileSystemSandboxPolicy::can_read_path_with_cwd721–723 ↗
fn can_read_path_with_cwd(&self, path: &Path, cwd: &Path) -> bool

作用:判断某个路径在当前目录下能不能读。它是 resolve_access_with_cwd 的简单读权限包装。

数据流:输入路径和当前目录 → 先算最终访问模式 → 调用 can_read → 返回是否可读。

调用关系:增加可读根、Windows 根读判断等流程会调用它。

调用图:调用 1 个内部函数(resolve_access_with_cwd);被 3 处调用(windows_policy_has_root_read_access, add_helper_runtime_permissions, with_additional_readable_roots)。

FileSystemSandboxPolicy::can_write_path_with_cwd725–733 ↗
fn can_write_path_with_cwd(&self, path: &Path, cwd: &Path) -> bool

作用:判断某个路径在当前目录下能不能写。除了普通写权限,它还额外检查受保护元数据目录。

数据流:输入路径和当前目录 → 先算规则是否允许写;如果不允许就返回 false;如果全盘写就返回 true;否则再检查是否被元数据保护挡住 → 输出最终是否可写。

调用关系:添加可写根、元数据写入拒绝、兼容策略和可写根保护名计算都会用它。

调用图:调用 3 个内部函数(has_full_disk_write_access, is_metadata_write_denied, resolve_access_with_cwd);被 4 处调用(with_additional_writable_roots, forbidden_agent_metadata_write, compatibility_workspace_write_policy, protected_metadata_names_for_writable_root)。

FileSystemSandboxPolicy::is_metadata_write_denied735–755 ↗
fn is_metadata_write_denied(&self, path: &Path, cwd: &Path) -> bool

作用:判断某次写入是不是被“.git/.codex/.agents 等元数据保护”拦住。它只在受限策略下生效。

数据流:输入路径和当前目录 → 解析成绝对路径;如果目标位于可写根里的受保护元数据目录,就检查是否有明确写入放行;没有放行则返回 true → 否则 false。

调用关系:can_write_path_with_cwd 在普通写权限通过后会调用它,作为额外安全门。

调用图:调用 3 个内部函数(has_explicit_write_entry_for_metadata_path, metadata_child_of_writable_root, resolve_candidate_path);被 1 处调用(can_write_path_with_cwd);外部调用 1 个(matches!)。

FileSystemSandboxPolicy::materialize_project_roots_with_cwd762–787 ↗
fn materialize_project_roots_with_cwd(mut self, cwd: &Path) -> Self

作用:把规则里的“项目根”特殊写法,按当前工作目录换成真实路径。这样后续沙箱运行时不用再理解这个抽象名字。

数据流:输入当前策略和当前目录 → 遍历规则;ProjectRoots 特殊路径转成普通绝对路径,项目根通配符也改成真实路径通配符 → 返回修改后的策略。

调用关系:它依赖 resolve_file_system_path、parse_project_roots_glob_pattern 和 resolve_project_roots_glob_pattern,是规则落地的一步。

调用图:调用 4 个内部函数(parse_project_roots_glob_pattern, resolve_file_system_path, resolve_project_roots_glob_pattern, from_absolute_path);外部调用 1 个(as_ref)。

FileSystemSandboxPolicy::materialize_project_roots_with_workspace_roots791–845 ↗
fn materialize_project_roots_with_workspace_roots(
        mut self,
        workspace_roots: &[AbsolutePathBuf],
    ) -> Self

作用:把“项目根”规则展开成多个工作区根目录的真实规则。一个抽象规则可以变成多条具体规则。

数据流:输入策略和工作区根目录列表 → 遍历每条规则;ProjectRoots 被展开到每个根目录,相关通配符也分别展开;其他规则原样保留 → 返回新策略。

调用关系:多工作区场景会用它把抽象权限变成实际可执行的路径列表。

调用图:调用 1 个内部函数(parse_project_roots_glob_pattern);外部调用 2 个(with_capacity, iter)。

FileSystemSandboxPolicy::with_materialized_project_roots_for_workspace_roots849–862 ↗
fn with_materialized_project_roots_for_workspace_roots(
        mut self,
        workspace_roots: &[AbsolutePathBuf],
    ) -> Self

作用:在保留原规则的同时,额外补上按工作区根目录展开后的规则。它既不丢抽象规则,也提供具体规则。

数据流:输入策略和工作区根目录 → 克隆一份并展开项目根规则 → 把展开后当前策略没有的条目追加进来 → 返回增强后的策略。

调用关系:它包了一层 materialize_project_roots_with_workspace_roots,用于需要兼容两种表示的地方。

FileSystemSandboxPolicy::with_additional_readable_roots864–885 ↗
fn with_additional_readable_roots(
        mut self,
        cwd: &Path,
        additional_readable_roots: &[AbsolutePathBuf],
    ) -> Self

作用:给策略补充额外可读目录,但不会重复添加已经可读的目录。全盘可读时什么也不用做。

数据流:输入当前目录和额外可读根列表 → 如果已有全盘读则原样返回;否则逐个检查是否已可读,未可读的追加 Read 规则 → 返回新策略。

调用关系:它用 has_full_disk_read_access 和 can_read_path_with_cwd 做避免重复和避免无效添加的判断。

调用图:调用 2 个内部函数(can_read_path_with_cwd, has_full_disk_read_access)。

FileSystemSandboxPolicy::with_additional_writable_roots887–904 ↗
fn with_additional_writable_roots(
        mut self,
        cwd: &Path,
        additional_writable_roots: &[AbsolutePathBuf],
    ) -> Self

作用:给策略补充额外可写目录,但跳过已经可写的目录。

数据流:输入当前目录和额外可写根列表 → 逐个检查能不能写;不能写的追加 Write 规则 → 返回新策略。

调用关系:它调用 can_write_path_with_cwd;常用于根据运行请求临时扩大写入范围。

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

FileSystemSandboxPolicy::with_additional_legacy_workspace_writable_roots912–942 ↗
fn with_additional_legacy_workspace_writable_roots(
        mut self,
        additional_writable_roots: &[AbsolutePathBuf],
    ) -> Self

作用:按旧工作区写入规则给策略加可写目录,并补上默认元数据只读保护。

数据流:输入额外可写根目录 → 只在受限策略下处理;没有同路径写规则就添加 Write;然后为每个根目录补 .git、.codex 等只读保护 → 返回策略。

调用关系:它服务于旧 WorkspaceWrite 兼容路径,内部调用 default_read_only_subpaths_for_writable_root 和 append_default_read_only_path_if_no_explicit_rule。

调用图:调用 2 个内部函数(append_default_read_only_path_if_no_explicit_rule, default_read_only_subpaths_for_writable_root);外部调用 1 个(matches!)。

FileSystemSandboxPolicy::needs_direct_runtime_enforcement944–964 ↗
fn needs_direct_runtime_enforcement(
        &self,
        network_policy: NetworkSandboxPolicy,
        cwd: &Path,
    ) -> bool

作用:判断当前新权限规则是否必须由新运行时直接执行,而不能只靠旧沙箱模型表达。旧模型表达不了时,必须启用更直接的检查。

数据流:输入文件系统策略、网络策略和当前目录 → 非受限策略返回 false;尝试转旧策略,失败则返回 true;再检查受保护元数据和整体语义是否和旧运行时投影一致 → 不一致就返回 true。

调用关系:ensure_legacy_landlock_mode_supports_policy 会用它决定旧沙箱是否够用;它调用 to_legacy_sandbox_policy、protected_metadata_names_need_direct_runtime_enforcement 和 semantic_signature。

调用图:调用 4 个内部函数(semantic_signature, to_legacy_sandbox_policy, legacy_runtime_file_system_policy_for_cwd, protected_metadata_names_need_direct_runtime_enforcement);被 1 处调用(ensure_legacy_landlock_mode_supports_policy);外部调用 1 个(matches!)。

FileSystemSandboxPolicy::is_semantically_equivalent_to968–970 ↗
fn is_semantically_equivalent_to(&self, other: &Self, cwd: &Path) -> bool

作用:判断两个策略在实际效果上是否一样,而不是只看规则写法是否一模一样。

数据流:输入另一个策略和当前目录 → 分别生成语义签名 → 比较签名是否相等 → 返回布尔值。

调用关系:它把比较工作交给 semantic_signature,适合测试或配置更新判断。

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

FileSystemSandboxPolicy::get_readable_roots_with_cwd973–987 ↗
fn get_readable_roots_with_cwd(&self, cwd: &Path) -> Vec<AbsolutePathBuf>

作用:列出当前策略明确允许读取的根目录。全盘可读时返回空列表,因为没必要列出每个目录。

数据流:输入当前目录 → 如果全盘可读返回空;否则解析规则,筛出可读且实际可读的路径,去重并规范化 → 返回绝对路径列表。

调用关系:创建沙箱参数、生成语义签名和查询可读根都会用它。

调用图:调用 3 个内部函数(has_full_disk_read_access, resolved_entries_with_cwd, dedup_absolute_paths);被 3 处调用(create_filesystem_args, semantic_signature, readable_roots_for_cwd);外部调用 1 个(new)。

FileSystemSandboxPolicy::get_writable_roots_with_cwd991–1103 ↗
fn get_writable_roots_with_cwd(&self, cwd: &Path) -> Vec<WritableRoot>

作用:列出当前策略明确允许写入的根目录,并附带每个可写根下面仍要只读保护的子路径。它是落地沙箱文件挂载规则的重要来源。

数据流:输入当前目录 → 全盘可写时返回空;否则解析可写条目、去重,逐个可写根计算受保护元数据名、默认只读子路径和更窄的只读 carveout → 输出 WritableRoot 列表。

调用关系:沙箱参数生成、权限提示、旧策略兼容和直接运行时判断都会用它;它会调用 protected_metadata_names_for_writable_root、default_read_only_subpaths_for_writable_root 和 dedup_absolute_paths 等辅助函数。

调用图:调用 3 个内部函数(has_full_disk_write_access, resolved_entries_with_cwd, dedup_absolute_paths);被 6 处调用(patch_rejection_reason, create_filesystem_args, sandbox_prompt_from_policy, semantic_signature, protected_metadata_names_need_direct_runtime_enforcement, compatibility_workspace_write_policy);外部调用 1 个(new)。

FileSystemSandboxPolicy::get_unreadable_roots_with_cwd1106–1128 ↗
fn get_unreadable_roots_with_cwd(&self, cwd: &Path) -> Vec<AbsolutePathBuf>

作用:列出明确禁止读取的具体根路径。它不会把文件系统根目录本身随便物化成拒读项,以免覆盖更细的可读例外。

数据流:输入当前目录 → 非受限策略返回空;受限策略中解析 Deny 条目,筛出实际不可读且不是根目录整体的路径,去重规范化 → 返回不可读根列表。

调用关系:创建沙箱参数、拒读文案、语义签名和 ReadDenyMatcher::build 都会用它。

调用图:调用 3 个内部函数(resolved_entries_with_cwd, dedup_absolute_paths, from_absolute_path);被 5 处调用(create_filesystem_args, denied_reads_text, semantic_signature, build, resolve_windows_deny_read_paths);外部调用 2 个(new, matches!)。

FileSystemSandboxPolicy::get_unreadable_globs_with_cwd1131–1151 ↗
fn get_unreadable_globs_with_cwd(&self, cwd: &Path) -> Vec<String>

作用:列出明确禁止读取的通配符规则。通配符就是像 *.secret 这样能匹配一批路径的模式。

数据流:输入当前目录 → 非受限策略返回空;受限策略中找 Deny 且是 GlobPattern 的条目,把它们按当前目录转成字符串,排序去重 → 返回模式列表。

调用关系:沙箱参数生成、拒读文案、语义签名、ReadDenyMatcher::build 和平台特定拒读策略都会用它。

调用图:被 7 处调用(create_bwrap_command_args, create_filesystem_args, denied_reads_text, semantic_signature, build, build_seatbelt_unreadable_glob_policy, resolve_windows_deny_read_paths);外部调用 2 个(new, matches!)。

FileSystemSandboxPolicy::to_legacy_sandbox_policy1153–1266 ↗
fn to_legacy_sandbox_policy(
        &self,
        network_policy: NetworkSandboxPolicy,
        cwd: &Path,
    ) -> io::Result<SandboxPolicy>

作用:把新版文件系统策略尽量翻译成旧版 SandboxPolicy。旧版表达能力较弱,所以有些新规则会无法翻译并返回错误。

数据流:输入文件系统策略、网络策略和当前目录 → 外部沙箱、不受限和受限策略分别映射;受限时统计工作区是否可写、额外可写根、临时目录是否可写;如果旧模型能表达就返回旧策略,否则返回 InvalidInput 错误。

调用关系:needs_direct_runtime_enforcement 会调用它来判断旧运行时是否够用;它还依赖 NetworkSandboxPolicy::is_enabled、has_full_disk_write_access 和 resolve_file_system_special_path。

调用图:调用 5 个内部函数(has_full_disk_write_access, is_enabled, dedup_absolute_paths, resolve_file_system_special_path, from_absolute_path);被 1 处调用(needs_direct_runtime_enforcement);外部调用 2 个(new, new)。

FileSystemSandboxPolicy::resolved_entries_with_cwd1268–1281 ↗
fn resolved_entries_with_cwd(&self, cwd: &Path) -> Vec<ResolvedFileSystemEntry>

作用:把策略里的路径规则解析成真实绝对路径规则。不能解析的通配符或抽象项会被跳过。

数据流:输入当前目录 → 先把 cwd 变成绝对路径;遍历 entries,把每条可解析路径转成 ResolvedFileSystemEntry,保留权限 → 返回解析后的列表。

调用关系:访问判断、可读/可写/不可读根计算、元数据检查都会用它;它内部调用 resolve_entry_path。

调用图:调用 1 个内部函数(from_absolute_path);被 6 处调用(get_readable_roots_with_cwd, get_unreadable_roots_with_cwd, get_writable_roots_with_cwd, resolve_access_with_cwd, has_explicit_write_entry_for_metadata_path, metadata_child_of_writable_root)。

FileSystemSandboxPolicy::semantic_signature1283–1293 ↗
fn semantic_signature(&self, cwd: &Path) -> FileSystemSemanticSignature

作用:生成策略的“语义签名”,也就是把实际效果整理成一个可比较的摘要。规则顺序或写法不同,但效果相同,就应该得到相同签名。

数据流:输入当前目录 → 计算是否全盘读写、是否包含平台默认、可读根、可写根、不可读根和拒读通配符 → 排序整理后返回签名对象。

调用关系:is_semantically_equivalent_to 和 needs_direct_runtime_enforcement 都靠它比较策略效果。

调用图:调用 9 个内部函数(get_readable_roots_with_cwd, get_unreadable_globs_with_cwd, get_unreadable_roots_with_cwd, get_writable_roots_with_cwd, has_full_disk_read_access, has_full_disk_write_access, include_platform_defaults, sorted_absolute_paths, sorted_writable_roots);被 2 处调用(is_semantically_equivalent_to, needs_direct_runtime_enforcement)。

NetworkSandboxPolicy::from1297–1303 ↗
fn from(value: &SandboxPolicy) -> Self

作用:从旧版 SandboxPolicy 提取新版网络策略。旧策略如果有完整网络访问,就转成 Enabled,否则 Restricted。

数据流:输入旧沙箱策略 → 调用旧策略的 has_full_network_access → 输出新版网络策略。

调用关系:会话配置、执行命令和代理启动时从旧配置迁移网络权限会用它。

调用图:被 14 处调用(exec_one_off_command_inner, can_set_legacy_sandbox_policy, set_legacy_sandbox_policy, apply, session_configuration_apply_preserves_profile_file_system_policy_on_cwd_only_update, session_configuration_apply_retargets_legacy_workspace_root_on_cwd_update, build_agent_spawn_config_uses_turn_context_values, spawn_agent_reapplies_runtime_sandbox_after_role_config, from_legacy_sandbox_policy, from_legacy_sandbox_policy (+4 more));外部调用 1 个(has_full_network_access)。

FileSystemSandboxPolicy::from1307–1330 ↗
fn from(value: &SandboxPolicy) -> Self

作用:从旧版 SandboxPolicy 生成新版文件系统策略。它是旧权限模型进入新模型的基础转换。

数据流:输入旧沙箱策略 → DangerFullAccess 转不受限,ExternalSandbox 转外部沙箱,ReadOnly 转根只读,WorkspaceWrite 转工作区可写策略 → 输出新版策略。

调用关系:from_legacy_sandbox_policy 和 legacy_runtime_file_system_policy_for_cwd 会用它作为基础桥梁。

调用图:调用 4 个内部函数(external_sandbox, restricted, unrestricted, workspace_write);被 2 处调用(from_legacy_sandbox_policy, legacy_runtime_file_system_policy_for_cwd);外部调用 1 个(vec!)。

resolve_file_system_path1333–1342 ↗
fn resolve_file_system_path(
    path: &FileSystemPath,
    cwd: Option<&AbsolutePathBuf>,
) -> Option<AbsolutePathBuf>

作用:把一个 FileSystemPath 尽量解析成绝对路径。普通路径可直接用,特殊路径要按当前目录或环境变量解释,通配符不在这里解析。

数据流:输入路径表达和可选当前目录 → 普通路径克隆返回;通配符返回 None;特殊路径交给 resolve_file_system_special_path → 输出可选绝对路径。

调用关系:materialize_project_roots_with_cwd 和 resolve_entry_path 会调用它。

调用图:调用 1 个内部函数(resolve_file_system_special_path);被 2 处调用(materialize_project_roots_with_cwd, resolve_entry_path);外部调用 1 个(clone)。

resolve_entry_path1344–1354 ↗
fn resolve_entry_path(
    path: &FileSystemPath,
    cwd: Option<&AbsolutePathBuf>,
) -> Option<AbsolutePathBuf>

作用:解析规则条目的路径,特别处理 Root:Root 会变成当前文件系统的根目录,比如 /。

数据流:输入路径表达和可选当前目录 → 如果是 Root,就从 cwd 找系统根;否则调用 resolve_file_system_path → 返回可选绝对路径。

调用关系:resolved_entries_with_cwd 间接使用它把规则落成可比较的绝对路径。

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

parse_project_roots_glob_pattern1356–1360 ↗
fn parse_project_roots_glob_pattern(pattern: &str) -> Option<&Path>

作用:识别一个通配符字符串是不是“项目根通配符”格式,并取出里面的子路径。

数据流:输入模式字符串 → 检查是否有固定前缀 → 有则返回前缀后的 Path,没有返回 None。

调用关系:项目根规则落地到当前目录或多个工作区根时会调用它。

调用图:被 2 处调用(materialize_project_roots_with_cwd, materialize_project_roots_with_workspace_roots)。

resolve_project_roots_glob_pattern1362–1366 ↗
fn resolve_project_roots_glob_pattern(subpath: &Path, root: &AbsolutePathBuf) -> String

作用:把项目根通配符里的子路径,和某个真实项目根拼成实际通配符字符串。

数据流:输入子路径和项目根绝对路径 → 按项目根解析子路径 → 转成字符串返回。

调用关系:materialize_project_roots_with_cwd 和工作区根展开流程会用它生成真实模式。

调用图:调用 2 个内部函数(as_path, resolve_path_against_base);被 1 处调用(materialize_project_roots_with_cwd)。

resolve_candidate_path1368–1374 ↗
fn resolve_candidate_path(path: &Path, cwd: &Path) -> Option<AbsolutePathBuf>

作用:把用户传入的候选路径转成绝对路径。相对路径会按当前工作目录补全。

数据流:输入路径和当前目录 → 如果路径本来是绝对路径就验证并返回;否则把 cwd 变成绝对路径后拼接相对路径 → 返回可选绝对路径。

调用关系:访问判断和元数据写入保护都会先调用它,保证后面比较的是同一种路径形态。

调用图:调用 1 个内部函数(from_absolute_path);被 3 处调用(is_metadata_write_denied, resolve_access_with_cwd, forbidden_agent_metadata_write);外部调用 1 个(is_absolute)。

file_system_paths_share_target1382–1400 ↗
fn file_system_paths_share_target(left: &FileSystemPath, right: &FileSystemPath) -> bool

作用:判断两个文件系统路径规则是不是指向同一个目标。它能比较普通路径、特殊路径和通配符。

数据流:输入两个 FileSystemPath → 根据类型分别比较:普通路径比绝对路径,特殊路径比含义,特殊路径和绝对路径按少数已知情况匹配,通配符只和相同通配符匹配 → 返回是否同目标。

调用关系:写规则覆盖判断和追加默认只读规则时会用它,避免重复或冲突。

调用图:调用 2 个内部函数(special_path_matches_absolute_path, special_paths_share_target)。

special_paths_share_target1404–1426 ↗
fn special_paths_share_target(left: &FileSystemSpecialPath, right: &FileSystemSpecialPath) -> bool

作用:判断两个特殊路径是否代表同一类位置,比如两个都是 /tmp 或两个都是同一个项目根子路径。

数据流:输入两个特殊路径值 → 按具体枚举种类比较,ProjectRoots 比子路径,Unknown 比名字和子路径 → 返回是否相同。

调用关系:file_system_paths_share_target 会调用它处理特殊路径之间的比较。

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

special_path_matches_absolute_path1433–1442 ↗
fn special_path_matches_absolute_path(
    value: &FileSystemSpecialPath,
    path: &AbsolutePathBuf,
) -> bool

作用:判断一个特殊路径是否能对应到某个绝对路径。目前只处理少数明确情况,比如 Root 和 /tmp。

数据流:输入特殊路径和绝对路径 → Root 检查该路径是否没有父目录,SlashTmp 检查是否等于 /tmp,其他返回 false。

调用关系:file_system_paths_share_target 在比较特殊路径和普通绝对路径时会调用它。

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

resolved_entry_precedence1446–1449 ↗
fn resolved_entry_precedence(entry: &ResolvedFileSystemEntry) -> (usize, FileSystemAccessMode)

作用:给已解析规则算优先级。路径越具体,优先级越高;同样具体时再看访问模式。

数据流:输入一条已解析规则 → 统计路径组件数量作为具体程度,并带上访问模式 → 返回排序用的二元组。

调用关系:resolve_access_with_cwd 用它从多条覆盖同一路径的规则里挑最终生效的一条。

absolute_root_path_for_cwd1451–1459 ↗
fn absolute_root_path_for_cwd(cwd: &AbsolutePathBuf) -> AbsolutePathBuf

作用:从当前工作目录找出所在文件系统的根目录。比如 Unix 上通常是 /。

数据流:输入绝对 cwd → 取 ancestors 的最后一个作为根 → 验证它是绝对路径并返回;异常时会 panic,因为 cwd 理论上必须有根。

调用关系:Root 特殊路径解析和不可读根过滤会用它。

调用图:调用 2 个内部函数(as_path, from_absolute_path)。

normalized_and_canonical_candidates1461–1480 ↗
fn normalized_and_canonical_candidates(path: &Path) -> Vec<PathBuf>

作用:为一个路径生成几种可比较的写法:普通绝对写法和真实解析后的写法。这样符号链接不会轻易绕过拒读规则。

数据流:输入路径 → 先保存规范化后的绝对路径,失败则保存原路径;再尝试 canonicalize,也就是跟随系统解析真实目标,并加入列表 → 返回去重后的候选路径。

调用关系:ReadDenyMatcher::build 和 is_read_denied 用它比较拒读根和待检查路径。

调用图:调用 2 个内部函数(push_unique, from_absolute_path);被 1 处调用(is_read_denied);外部调用 3 个(canonicalize, to_path_buf, new)。

push_unique1482–1486 ↗
fn push_unique(candidates: &mut Vec<PathBuf>, candidate: PathBuf)

作用:往列表里追加路径,但如果已经有相同路径就不加。它是简单去重工具。

数据流:输入路径列表和候选路径 → 检查列表里是否已有 → 没有就追加,有就保持不变。

调用关系:normalized_and_canonical_candidates 用它避免同一路径的多种解析结果重复出现。

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

build_glob_matcher1488–1497 ↗
fn build_glob_matcher(pattern: &str) -> Result<GlobMatcher, String>

作用:把通配符字符串编译成可执行的匹配器。匹配器之后可以拿来判断路径是否符合模式。

数据流:输入通配符模式 → 设置匹配规则:星号和问号不跨路径分隔符,未闭合的方括号按普通字符处理 → 编译成功返回 GlobMatcher,失败返回错误文字。

调用关系:ReadDenyMatcher::build 用它处理拒读通配符。

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

resolve_file_system_special_path1499–1535 ↗
fn resolve_file_system_special_path(
    value: &FileSystemSpecialPath,
    cwd: Option<&AbsolutePathBuf>,
) -> Option<AbsolutePathBuf>

作用:把特殊路径名解析成机器上的真实绝对路径,比如项目根、TMPDIR、/tmp。不能确定的特殊路径返回 None。

数据流:输入特殊路径和可选当前目录 → ProjectRoots 依赖 cwd 解析,Tmpdir 读取环境变量 TMPDIR,SlashTmp 检查 /tmp 是否存在,Root/Minimal/Unknown 不在这里解析 → 返回可选绝对路径。

调用关系:to_legacy_sandbox_policy 和 resolve_file_system_path 会调用它,把抽象规则落成实际路径。

调用图:调用 2 个内部函数(from_absolute_path, resolve_path_against_base);被 2 处调用(to_legacy_sandbox_policy, resolve_file_system_path);外部调用 2 个(from, var_os)。

dedup_absolute_paths1537–1554 ↗
fn dedup_absolute_paths(
    paths: Vec<AbsolutePathBuf>,
    normalize_effective_paths: bool,
) -> Vec<AbsolutePathBuf>

作用:对绝对路径列表去重,可选择先按实际有效路径规范化。它用来减少重复挂载或重复规则。

数据流:输入绝对路径列表和是否规范化的开关 → 逐个路径可选调用 normalize_effective_absolute_path,再用集合记录见过的路径 → 返回去重后的列表。

调用关系:可读根、可写根、不可读根、旧策略转换和默认只读子路径计算都会用它。

调用图:调用 1 个内部函数(normalize_effective_absolute_path);被 5 处调用(get_readable_roots_with_cwd, get_unreadable_roots_with_cwd, get_writable_roots_with_cwd, to_legacy_sandbox_policy, default_read_only_subpaths_for_writable_root);外部调用 2 个(new, with_capacity)。

sorted_absolute_paths1556–1559 ↗
fn sorted_absolute_paths(mut paths: Vec<AbsolutePathBuf>) -> Vec<AbsolutePathBuf>

作用:把绝对路径列表按路径文字排序。排序让比较结果稳定。

数据流:输入路径列表 → 按路径大小排序 → 返回排序后的列表。

调用关系:semantic_signature 和 sorted_writable_roots 用它生成稳定的语义摘要。

调用图:被 2 处调用(semantic_signature, sorted_writable_roots)。

sorted_writable_roots1561–1570 ↗
fn sorted_writable_roots(mut roots: Vec<WritableRoot>) -> Vec<WritableRoot>

作用:把可写根列表和其中的只读子路径、受保护元数据名整理成稳定顺序。这样两个等价策略更容易比较。

数据流:输入 WritableRoot 列表 → 每个根内部对子路径排序、元数据名排序去重;再按根路径排序 → 返回整理后的列表。

调用关系:semantic_signature 调用它生成可比较的写权限摘要。

调用图:调用 1 个内部函数(sorted_absolute_paths);被 1 处调用(semantic_signature);外部调用 1 个(take)。

normalize_effective_absolute_path1572–1591 ↗
fn normalize_effective_absolute_path(path: AbsolutePathBuf) -> AbsolutePathBuf

作用:尽量把路径规范成系统实际会访问的有效路径,同时保留路径末尾可能还不存在的部分。它主要处理符号链接和系统别名。

数据流:输入绝对路径 → 从路径祖先开始找存在的部分,尝试 canonicalize_preserving_symlinks,然后把剩余后缀接回去 → 成功返回规范路径,失败返回原路径。

调用关系:dedup_absolute_paths 在需要按实际路径去重时会调用它。

调用图:调用 2 个内部函数(from_absolute_path, to_path_buf);被 1 处调用(dedup_absolute_paths);外部调用 2 个(canonicalize_preserving_symlinks, symlink_metadata)。

default_read_only_subpaths_for_writable_root1593–1630 ↗
fn default_read_only_subpaths_for_writable_root(
    writable_root: &AbsolutePathBuf,
    protect_missing_dot_codex: bool,
) -> Vec<AbsolutePathBuf>

作用:为一个可写根目录找出默认仍应只读保护的子路径,重点是 .git、.agents、.codex。这样项目可写时也不会轻易破坏仓库和代理元数据。

数据流:输入可写根和是否保护尚未存在的 .codex → 检查 .git 是目录还是指针文件,必要时解析真实 gitdir;检查 .agents 和 .codex 是否存在或是否要预先保护 → 去重后返回只读子路径列表。

调用关系:workspace_write、旧策略转换、追加旧工作区可写根和运行时投影都会用它。

调用图:调用 4 个内部函数(dedup_absolute_paths, is_git_pointer_file, resolve_gitdir_from_file, join);被 5 处调用(from_legacy_sandbox_policy_for_cwd, with_additional_legacy_workspace_writable_roots, workspace_write, legacy_runtime_file_system_policy_for_cwd, legacy_workspace_write_projection_accepts_relative_cwd);外部调用 1 个(new)。

legacy_runtime_file_system_policy_for_cwd1639–1711 ↗
fn legacy_runtime_file_system_policy_for_cwd(
    sandbox_policy: &SandboxPolicy,
    cwd: &Path,
) -> FileSystemSandboxPolicy

作用:模拟旧运行时真正会执行出的文件系统策略。它用于和新策略比较,看旧运行时是否能完整表达新规则。

数据流:输入旧沙箱策略和当前目录 → 如果不是 WorkspaceWrite,直接转新策略;如果是,就按旧运行时规则重建根只读、项目根可写、临时目录和额外可写根,并补默认元数据只读保护 → 返回受限策略。

调用关系:needs_direct_runtime_enforcement 会用它和当前策略的 semantic_signature 对比。

调用图:调用 5 个内部函数(from, restricted, append_default_read_only_path_if_no_explicit_rule, default_read_only_subpaths_for_writable_root, from_absolute_path);被 4 处调用(needs_direct_runtime_enforcement, legacy_projection_runtime_enforcement_ignores_entry_order, missing_symbolic_metadata_carveouts_need_direct_runtime_enforcement, split_only_nested_carveouts_need_direct_runtime_enforcement);外部调用 1 个(vec!)。

append_default_read_only_project_root_subpath_if_no_explicit_rule1713–1723 ↗
fn append_default_read_only_project_root_subpath_if_no_explicit_rule(
    entries: &mut Vec<FileSystemSandboxEntry>,
    subpath: impl Into<PathBuf>,
)

作用:给项目根下某个子路径追加默认只读规则,但前提是没有已有规则明确提到同一目标。

数据流:输入规则列表和子路径 → 把子路径包装成 ProjectRoots 特殊路径 → 交给通用追加函数处理 → 可能修改规则列表。

调用关系:workspace_write 用它默认保护项目根下的 .git、.agents、.codex。

调用图:调用 1 个内部函数(append_default_read_only_entry_if_no_explicit_rule);被 1 处调用(workspace_write);外部调用 2 个(into, project_roots)。

append_default_read_only_path_if_no_explicit_rule1725–1730 ↗
fn append_default_read_only_path_if_no_explicit_rule(
    entries: &mut Vec<FileSystemSandboxEntry>,
    path: AbsolutePathBuf,
)

作用:给某个具体绝对路径追加默认只读规则,但不会覆盖已有显式规则。

数据流:输入规则列表和绝对路径 → 包装成 FileSystemPath::Path → 交给通用追加函数 → 可能追加 Read 规则。

调用关系:旧策略转换、工作区写策略和旧运行时投影都会用它补敏感路径保护。

调用图:调用 1 个内部函数(append_default_read_only_entry_if_no_explicit_rule);被 4 处调用(from_legacy_sandbox_policy_for_cwd, with_additional_legacy_workspace_writable_roots, workspace_write, legacy_runtime_file_system_policy_for_cwd)。

append_default_read_only_entry_if_no_explicit_rule1732–1747 ↗
fn append_default_read_only_entry_if_no_explicit_rule(
    entries: &mut Vec<FileSystemSandboxEntry>,
    path: FileSystemPath,
)

作用:通用的“补默认只读规则”函数。它只有在没有任何规则指向同一目标时才追加,避免和用户明确配置冲突。

数据流:输入规则列表和路径表达 → 先检查是否已有同目标规则;有则不动,没有则追加 access=Read 的规则 → 修改规则列表。

调用关系:append_default_read_only_path_if_no_explicit_rule 和 append_default_read_only_project_root_subpath_if_no_explicit_rule 都把实际追加工作交给它。

调用图:被 2 处调用(append_default_read_only_path_if_no_explicit_rule, append_default_read_only_project_root_subpath_if_no_explicit_rule)。

has_explicit_resolved_path_entry1749–1754 ↗
fn has_explicit_resolved_path_entry(
    entries: &[ResolvedFileSystemEntry],
    path: &AbsolutePathBuf,
) -> bool

作用:检查已解析规则里是否有某个确切路径。它用于判断默认保护项是不是已经被显式规则覆盖。

数据流:输入已解析规则列表和目标路径 → 遍历比较路径是否相等 → 返回 true 或 false。

调用关系:get_writable_roots_with_cwd 在计算只读子路径时用它避免重复加入已有显式规则。

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

metadata_path_name1756–1761 ↗
fn metadata_path_name(name: &OsStr) -> Option<&'static str>

作用:如果一个文件名属于受保护元数据名,就返回对应的标准字符串。否则返回 None。

数据流:输入文件名 → 在受保护名字表里查找相等项 → 命中返回静态字符串,未命中返回 None。

调用关系:metadata_child_of_writable_root 用它识别可写根下的第一层路径是不是敏感元数据目录。

metadata_child_of_writable_root1763–1779 ↗
fn metadata_child_of_writable_root(
    policy: &FileSystemSandboxPolicy,
    target: &Path,
    cwd: &Path,
) -> Option<(AbsolutePathBuf, &'static str)>

作用:判断目标路径是否位于某个可写根目录下的受保护元数据区域里。比如项目可写,但项目/.git/config 就应被识别出来。

数据流:输入策略、目标路径和当前目录 → 解析策略里的可写根;对每个可写根尝试取目标的相对路径;如果第一层目录是受保护元数据名,就返回该元数据目录路径和名字 → 否则返回 None。

调用关系:is_metadata_write_denied 和 forbidden_agent_metadata_write 会调用它,判断写入是否碰到敏感区。

调用图:调用 1 个内部函数(resolved_entries_with_cwd);被 2 处调用(is_metadata_write_denied, forbidden_agent_metadata_write)。

protected_metadata_names_for_writable_root1781–1804 ↗
fn protected_metadata_names_for_writable_root(
    policy: &FileSystemSandboxPolicy,
    root: &AbsolutePathBuf,
    raw_writable_roots: &[&AbsolutePathBuf],
    cwd: &Path,
) -> Vec<String>

作用:计算某个可写根下哪些受保护元数据名仍然不能写。结果会交给下游沙箱去遮住这些名字。

数据流:输入策略、规范化后的可写根、原始可写根别名和当前目录 → 对每个受保护名字生成可能路径;如果这些路径全都不可写,就把名字加入结果 → 返回字符串列表。

调用关系:get_writable_roots_with_cwd 用它给每个 WritableRoot 附带 protected_metadata_names。

调用图:外部调用 3 个(new, iter, vec!)。

protected_metadata_names_need_direct_runtime_enforcement1806–1834 ↗
fn protected_metadata_names_need_direct_runtime_enforcement(
    policy: &FileSystemSandboxPolicy,
    legacy_policy: &SandboxPolicy,
    cwd: &Path,
) -> bool

作用:判断受保护元数据名的限制是否超出了旧沙箱能表达的范围。如果旧沙箱没有正确保护这些名字,就需要新运行时直接管。

数据流:输入新策略、旧策略和当前目录 → 取旧策略和新策略的可写根;逐个比较新策略要求保护的元数据名,在旧策略对应根的只读子路径里是否存在 → 有缺失就返回 true。

调用关系:needs_direct_runtime_enforcement 调用它作为旧运行时能力检查的一部分。

调用图:调用 1 个内部函数(get_writable_roots_with_cwd);被 1 处调用(needs_direct_runtime_enforcement);外部调用 1 个(get_writable_roots_with_cwd)。

has_explicit_write_entry_for_metadata_path1836–1850 ↗
fn has_explicit_write_entry_for_metadata_path(
    policy: &FileSystemSandboxPolicy,
    protected_metadata_path: &AbsolutePathBuf,
    target: &Path,
    cwd: &Path,
) -> bool

作用:判断受保护元数据目录里是否有明确的写入放行规则。只有这种明确规则才能绕过默认保护。

数据流:输入策略、受保护元数据目录、目标路径和当前目录 → 解析所有规则;寻找一条可写规则,它既覆盖目标路径,又位于受保护元数据目录之内 → 找到返回 true,否则 false。

调用关系:is_metadata_write_denied 和 forbidden_agent_metadata_write 用它决定元数据写入是否被明确允许。

调用图:调用 1 个内部函数(resolved_entries_with_cwd);被 2 处调用(is_metadata_write_denied, forbidden_agent_metadata_write)。

is_git_pointer_file1852–1855 ↗
fn is_git_pointer_file(path: &AbsolutePathBuf) -> bool

作用:判断某个路径是不是名为 .git 的普通文件。Git 工作树里 .git 可能是一个指向真实 git 目录的文件。

数据流:输入绝对路径 → 检查它是文件,并且文件名等于 .git → 返回 true 或 false。

调用关系:default_read_only_subpaths_for_writable_root 用它决定是否需要解析 .git 文件里的真实 gitdir 并一起保护。

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

resolve_gitdir_from_file1857–1914 ↗
fn resolve_gitdir_from_file(dot_git: &AbsolutePathBuf) -> Option<AbsolutePathBuf>

作用:读取一个 .git 文件里的“gitdir: 路径”指针,找出真正的 Git 元数据目录。这样工作树使用 Git 的分离目录时,沙箱仍然知道该保护哪里。

数据流:输入是一个绝对路径,通常指向名叫 .git 的文件 → 它读取文件内容,检查格式是不是 gitdir: <path>,把里面的路径按 .git 文件所在目录解析成绝对路径,并确认目标存在 → 成功时返回真实 git 目录路径;失败时记录错误并返回空值,不改动外部状态。

调用关系:它被 default_read_only_subpaths_for_writable_root 使用;当系统准备给一个可写项目根目录自动加上只读保护区时,会靠它识别 .git 文件背后真正的 Git 目录。它内部把具体读文件、路径拼接和报错分别交给标准库、AbsolutePathBuf::resolve_path_against_base 和日志宏。

调用图:调用 2 个内部函数(as_path, resolve_path_against_base);被 1 处调用(default_read_only_subpaths_for_writable_root);外部调用 2 个(error!, read_to_string)。

tests::unknown_special_paths_are_ignored_by_legacy_bridge1934–1965 ↗
fn unknown_special_paths_are_ignored_by_legacy_bridge() -> std::io::Result<()>

作用:确认新版本里未来可能出现的特殊路径规则,转换到老沙箱格式时不会把系统搞坏。未知规则会被忽略,而不是误放权限。

数据流:输入是一组权限规则:根目录只读,另一个未知特殊路径可写 → 转成旧版 SandboxPolicy → 输出应是只读沙箱,并且网络不开放。

调用关系:由测试运行器执行,用来保护新旧权限格式之间的桥接代码。它间接检查 to_legacy_sandbox_policy 在遇到不认识的特殊路径时选择安全退让。

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

tests::writable_roots_proactively_protect_missing_dot_codex1969–1992 ↗
fn writable_roots_proactively_protect_missing_dot_codex()

作用:确认即使项目里暂时没有 .codex 目录,沙箱也会提前把这个位置当成受保护区域。这样程序不能先创建它再写入敏感配置。

数据流:输入是一个临时工作目录和“项目根可写”的规则 → 计算可写根目录 → 输出里应该包含该根目录,同时把根下的 .codex 放进只读子路径。

调用关系:测试 get_writable_roots_with_cwd 的默认保护行为,确保后续运行时执行权限时有足够信息拦住 .codex 写入。

调用图:调用 2 个内部函数(restricted, from_absolute_path);外部调用 4 个(new, assert!, assert_eq!, vec!)。

tests::legacy_workspace_write_projection_preserves_symbolic_project_root1995–2038 ↗
fn legacy_workspace_write_projection_preserves_symbolic_project_root()

作用:确认旧版“工作区可写”策略转换成新版规则时,仍然用“项目根”这个符号概念,而不是硬编码某一个路径。

数据流:输入是旧版工作区可写策略 → 转成 FileSystemSandboxPolicy → 输出应包含根目录只读、项目根可写,以及 .git.agents.codex 在项目根下只读的规则。

调用关系:测试 FileSystemSandboxPolicy::from 的转换结果,防止老策略升级后丢失项目元数据保护。

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

tests::legacy_current_working_directory_special_path_deserializes_as_project_roots2041–2059 ↗
fn legacy_current_working_directory_special_path_deserializes_as_project_roots() -> serde_json::Result<()>

作用:确认老 JSON 里叫 current_working_directory 的特殊路径,读进来后会当成现在的 project_roots。这是为了兼容旧配置文件。

数据流:输入是一段旧格式 JSON → 反序列化成 FileSystemSpecialPath → 得到项目根特殊路径;再序列化回 JSON 时输出新名字 project_roots

调用关系:测试 serde JSON 读写兼容层,确保用户升级版本后旧配置还能用,但保存时会变成新格式。

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

tests::writable_roots_skip_default_dot_codex_when_explicit_user_rule_exists2063–2109 ↗
fn writable_roots_skip_default_dot_codex_when_explicit_user_rule_exists()

作用:确认用户如果明确允许写 .codex,这条显式规则会压过默认保护。也就是说默认安全规则不是不可覆盖的死规则。

数据流:输入是项目根可写规则,加上一条明确的 .codex 可写路径规则 → 计算可写根并检查写权限 → 输出中 .codex 不再被列为受保护名字或只读子路径,写 .codex/config.toml 返回允许。

调用关系:测试 get_writable_roots_with_cwdcan_write_path_with_cwd 的配合,说明显式用户意图会影响默认元数据保护。

调用图:调用 2 个内部函数(restricted, from_absolute_path);外部调用 3 个(new, assert!, vec!)。

tests::filesystem_policy_blocks_protected_metadata_path_writes_by_default2112–2141 ↗
fn filesystem_policy_blocks_protected_metadata_path_writes_by_default()

作用:确认只要把项目目录设为可写,系统仍会默认挡住 .git.agents.codex 这些敏感目录里的写入。

数据流:输入是一个可写根路径规则 → 分别询问三个元数据配置文件能不能写,并计算可写根 → 输出都是不能写,且可写根记录了三个受保护名字。

调用关系:测试普通路径可写规则下的默认安全保护,防止“一整个目录可写”意外包含仓库和代理元数据。

调用图:调用 2 个内部函数(restricted, from_absolute_path);外部调用 4 个(new, assert!, assert_eq!, vec!)。

tests::legacy_workspace_write_projection_accepts_relative_cwd2144–2218 ↗
fn legacy_workspace_write_projection_accepts_relative_cwd()

作用:确认旧版工作区策略在当前目录是相对路径时也能正确转换。相对路径就是像 workspace 这样不从根开始的路径。

数据流:输入是相对工作目录和旧版工作区可写策略 → 转成新版文件系统策略,并补上默认只读保护路径 → 输出应等于预期规则;同时检查 .git.codex.agents 下写入会被挡住。

调用关系:测试 from_legacy_sandbox_policy_for_cwddefault_read_only_subpaths_for_writable_rootforbidden_agent_metadata_write 和写权限判断,保证老策略在非绝对 cwd 下也安全。

调用图:调用 3 个内部函数(from_legacy_sandbox_policy_for_cwd, default_read_only_subpaths_for_writable_root, from_absolute_path);外部调用 5 个(new, assert!, assert_eq!, current_dir, vec!)。

tests::effective_runtime_roots_preserve_symlinked_paths2222–2269 ↗
fn effective_runtime_roots_preserve_symlinked_paths()

作用:确认可写根是符号链接时,运行时看到的权限根仍保留这个链接路径。这样报给底层沙箱的路径和用户使用的路径一致。

数据流:输入是一个真实目录、指向它的链接目录,以及链接目录下的拒绝规则 → 计算不可读根和可写根 → 输出使用链接路径表示根、被拒绝子路径和 .codex 保护路径。

调用关系:测试 get_unreadable_roots_with_cwdget_writable_roots_with_cwd,防止符号链接被规范化后破坏权限规则的匹配。

调用图:调用 2 个内部函数(restricted, from_absolute_path);外部调用 6 个(new, assert!, assert_eq!, create_dir_all, symlink_dir, vec!)。

tests::project_roots_special_path_preserves_symlinked_root2273–2340 ↗
fn project_roots_special_path_preserves_symlinked_root()

作用:确认当当前项目根本身是符号链接时,特殊路径 project_roots 展开后仍保留链接形式。

数据流:输入是一个链接形式的当前目录,以及项目根可写、某子目录拒绝的规则 → 计算可读、不可读、可写根 → 输出都应以链接路径为准,并把 .agents.codex 和拒绝子目录列为只读保护。

调用关系:测试特殊路径展开逻辑和符号链接处理,保证项目根规则不会偷偷变成真实目录路径。

调用图:调用 2 个内部函数(restricted, from_absolute_path);外部调用 6 个(new, assert!, assert_eq!, create_dir_all, symlink_dir, vec!)。

tests::writable_roots_preserve_symlinked_protected_subpaths2344–2380 ↗
fn writable_roots_preserve_symlinked_protected_subpaths()

作用:确认像 .codex 这种受保护子目录如果本身是符号链接,保护的是用户路径里的 .codex,不是它最终指向的目标目录名。

数据流:输入是一个可写根,根下 .codex 链接到 decoy-codex → 计算可写根 → 输出只读子路径是根下的 .codex,不把真实目标 decoy-codex 当成保护项。

调用关系:测试默认元数据保护与符号链接相遇时的路径表达方式,避免保护规则变成难以理解的真实路径。

调用图:调用 2 个内部函数(restricted, from_absolute_path);外部调用 6 个(new, assert!, assert_eq!, create_dir_all, symlink_dir, vec!)。

tests::writable_roots_preserve_explicit_symlinked_carveouts_under_symlinked_roots2384–2426 ↗
fn writable_roots_preserve_explicit_symlinked_carveouts_under_symlinked_roots()

作用:确认在一个符号链接形式的可写根里,显式拒绝的链接子目录会按链接路径保留下来。

数据流:输入是链接根可写,以及链接根下 linked-private 拒绝 → 计算可写根 → 输出根是链接根,只读子路径是链接根下的 linked-private,不改成它指向的真实目录。

调用关系:测试显式拒绝规则进入可写根的只读 carveout(可写区域里挖掉的一块只读区)时,仍尊重用户写的路径。

调用图:调用 2 个内部函数(restricted, from_absolute_path);外部调用 6 个(new, assert!, assert_eq!, create_dir_all, symlink_dir, vec!)。

tests::writable_roots_preserve_explicit_symlinked_carveouts_that_escape_root2430–2473 ↗
fn writable_roots_preserve_explicit_symlinked_carveouts_that_escape_root()

作用:确认拒绝的链接子目录即使指向可写根外面,也仍按原来的链接位置作为保护洞处理。

数据流:输入是链接根可写,根内一个链接目录指向外部私密目录,并对这个链接目录设拒绝 → 计算可写根 → 输出只读子路径是根内链接路径,不把外部真实目录塞进列表。

调用关系:测试沙箱对“链接逃出根目录”的情况不改变规则语义,避免路径解析导致保护范围看起来跳到别处。

调用图:调用 2 个内部函数(restricted, from_absolute_path);外部调用 6 个(new, assert!, assert_eq!, create_dir_all, symlink_dir, vec!)。

tests::writable_roots_preserve_explicit_symlinked_carveouts_that_alias_root2477–2507 ↗
fn writable_roots_preserve_explicit_symlinked_carveouts_that_alias_root()

作用:确认一个拒绝子路径如果是指回根目录自身的符号链接,也会作为根下的那个别名路径被保护。

数据流:输入是可写根,以及根下 alias-root 链接回根本身并被拒绝 → 计算可写根 → 输出根为规范后的根路径,只读子路径为根下 alias-root

调用关系:测试显式拒绝规则遇到“自我别名”符号链接时不会被消掉,避免绕过只读 carveout。

调用图:调用 2 个内部函数(restricted, from_absolute_path);外部调用 5 个(new, assert_eq!, create_dir_all, symlink_dir, vec!)。

tests::tmpdir_special_path_preserves_symlinked_tmpdir2511–2581 ↗
fn tmpdir_special_path_preserves_symlinked_tmpdir()

作用:确认临时目录 TMPDIR 如果是符号链接,特殊路径 Tmpdir 展开后也保留这个链接路径。测试用子进程运行,是为了安全地改环境变量。

数据流:第一次进入时如果没有测试环境标记,就启动当前测试二进制的子进程并设置标记 → 子进程里创建真实临时目录和链接临时目录,设置 TMPDIR 指向链接 → 根据 Tmpdir 可写和子路径拒绝规则计算权限 → 输出应使用链接形式的临时目录、被拒绝子路径和 .codex 保护路径。

调用关系:测试 FileSystemSpecialPath::Tmpdir 的展开逻辑。它通过重新执行自己来隔离 TMPDIR 环境变量,避免影响同一进程里的其他测试。

调用图:调用 2 个内部函数(restricted, from_absolute_path);外部调用 10 个(new, assert!, assert_eq!, new, create_dir_all, symlink_dir, current_exe, set_var, var_os, vec!)。

tests::resolve_access_with_cwd_uses_most_specific_entry2584–2631 ↗
fn resolve_access_with_cwd_uses_most_specific_entry()

作用:确认多条权限规则互相重叠时,最具体的路径规则优先。就像“整栋楼可进,但某个房间禁入,房间里的展柜又可进”这种层层覆盖。

数据流:输入是项目根可写、docs 只读、docs/private 拒绝、docs/private/public 可写的规则 → 分别查询几个路径权限 → 输出依次是写、读、拒绝、写。

调用关系:测试 resolve_access_with_cwd 的核心判定规则,很多读写检查最终都依赖这个“谁更具体谁说了算”的逻辑。

调用图:调用 2 个内部函数(restricted, resolve_path_against_base);外部调用 3 个(new, assert_eq!, vec!)。

tests::split_only_nested_carveouts_need_direct_runtime_enforcement2634–2663 ↗
fn split_only_nested_carveouts_need_direct_runtime_enforcement()

作用:确认如果可写区域里挖了只读子区域,这种细规则需要运行时直接执行,不能只靠老沙箱格式表达。

数据流:输入是项目根可写但 docs 只读的规则 → 判断是否需要直接运行时强制执行 → 输出为需要;同时检查旧版工作区策略产生的元数据保护也需要直接执行。

调用关系:测试 needs_direct_runtime_enforcement,说明有些新规则太细,旧 SandboxPolicy 不能完整表达,必须由新权限系统自己守住。

调用图:调用 3 个内部函数(restricted, legacy_runtime_file_system_policy_for_cwd, resolve_path_against_base);外部调用 4 个(new, new_workspace_write_policy, assert!, vec!)。

tests::legacy_projection_runtime_enforcement_ignores_entry_order2666–2690 ↗
fn legacy_projection_runtime_enforcement_ignores_entry_order()

作用:确认权限条目的排列顺序不影响最终语义。否则同一批规则换个顺序就变成另一种权限,会很危险。

数据流:输入是旧版工作区策略转换出的规则,再把规则顺序倒过来 → 比较两者语义和是否需要直接执行 → 输出应完全一致。

调用关系:测试 is_semantically_equivalent_toneeds_direct_runtime_enforcement,保证判断逻辑按规则内容算,不按列表顺序碰运气。

调用图:调用 2 个内部函数(restricted, legacy_runtime_file_system_policy_for_cwd);外部调用 4 个(new, new, assert!, assert_eq!)。

tests::missing_symbolic_metadata_carveouts_need_direct_runtime_enforcement2693–2717 ↗
fn missing_symbolic_metadata_carveouts_need_direct_runtime_enforcement()

作用:确认对还不存在的 .git.agents 等元数据路径的符号保护,也必须走直接运行时执行。老沙箱只认识具体存在的路径,表达不了“将来出现也要保护”。

数据流:输入是旧版工作区可写策略 → 分别生成配置投影和运行时投影 → 两者判断是否需要直接执行 → 输出都应为需要。

调用关系:测试旧策略转换后的保护强度,防止因为元数据目录暂时不存在就漏掉保护。

调用图:调用 2 个内部函数(from_legacy_sandbox_policy_for_cwd, legacy_runtime_file_system_policy_for_cwd);外部调用 3 个(new, new, assert!)。

tests::root_write_with_read_only_child_is_not_full_disk_write2720–2749 ↗
fn root_write_with_read_only_child_is_not_full_disk_write()

作用:确认“根目录可写但里面有只读子目录”不能被当成真正的全盘可写。因为有例外,就不是无限制写入。

数据流:输入是文件系统根可写、某个 docs 子路径只读的规则 → 检查全盘写权限、查询 docs 权限、判断是否需直接执行、尝试转老策略 → 输出为没有全盘写、docs 只读、需要直接执行、老策略转换失败。

调用关系:测试 has_full_disk_write_accessresolve_access_with_cwdneeds_direct_runtime_enforcement 和旧桥接,保证复杂根级规则不会被简化错。

调用图:调用 2 个内部函数(restricted, resolve_path_against_base);外部调用 4 个(new, assert!, assert_eq!, vec!)。

tests::root_deny_does_not_materialize_as_unreadable_root2752–2783 ↗
fn root_deny_does_not_materialize_as_unreadable_root()

作用:确认“根目录拒绝”不会被直接展开成一个不可读根列表项,否则几乎会封死整个文件系统。更具体的允许规则仍应生效。

数据流:输入是根拒绝、docs 可读的规则 → 查询 docs 权限,并计算可读根和不可读根 → 输出 docs 可读,可读根只有 docs,不可读根为空。

调用关系:测试根级拒绝和具体允许规则的配合,确保物化运行时根列表时不会把抽象根拒绝处理得过头。

调用图:调用 3 个内部函数(restricted, from_absolute_path, resolve_path_against_base);外部调用 5 个(new, assert!, assert_eq!, canonicalize_preserving_symlinks, vec!)。

tests::duplicate_root_deny_prevents_full_disk_write_access2786–2811 ↗
fn duplicate_root_deny_prevents_full_disk_write_access()

作用:确认同一个根目录既有写又有拒绝时,拒绝会赢,不能算作全盘写。

数据流:输入是根写入规则和根拒绝规则 → 检查全盘写权限并查询根路径权限 → 输出没有全盘写,根路径最终是拒绝。

调用关系:测试同等范围规则冲突时的优先级,配合 FileSystemAccessMode 的冲突顺序使用。

调用图:调用 2 个内部函数(restricted, from_absolute_path);外部调用 4 个(new, assert!, assert_eq!, vec!)。

tests::same_specificity_write_override_keeps_full_disk_write_access2814–2839 ↗
fn same_specificity_write_override_keeps_full_disk_write_access()

作用:确认如果一个只读子路径后来又被同样具体的可写规则覆盖,全盘写权限可以恢复成立。

数据流:输入是根可写、docs 只读、同一个 docs 又可写的规则 → 检查全盘写权限并查询 docs 权限 → 输出有全盘写,docs 最终可写。

调用关系:测试同一具体路径上后续写规则覆盖只读规则的效果,防止已经被抵消的例外还影响全盘写判断。

调用图:调用 2 个内部函数(restricted, resolve_path_against_base);外部调用 4 个(new, assert!, assert_eq!, vec!)。

tests::with_additional_readable_roots_skips_existing_effective_access2842–2857 ↗
fn with_additional_readable_roots_skips_existing_effective_access()

作用:确认给策略追加可读根时,如果这个根本来就有效可读,就不会重复添加规则。

数据流:输入是项目根可读策略和同一个当前目录作为额外可读根 → 调用追加方法 → 输出策略保持不变。

调用关系:测试 with_additional_readable_roots 的去重和有效权限判断,避免生成冗余规则。

调用图:调用 2 个内部函数(restricted, from_absolute_path);外部调用 4 个(new, assert_eq!, from_ref, vec!)。

tests::with_additional_writable_roots_skips_existing_effective_access2860–2875 ↗
fn with_additional_writable_roots_skips_existing_effective_access()

作用:确认给策略追加可写根时,如果这个根本来就有效可写,也不会重复添加。

数据流:输入是项目根可写策略和同一个当前目录作为额外可写根 → 调用追加方法 → 输出策略保持不变。

调用关系:测试 with_additional_writable_roots 的去重逻辑,避免相同权限被反复塞进规则列表。

调用图:调用 2 个内部函数(restricted, from_absolute_path);外部调用 4 个(new, assert_eq!, from_ref, vec!)。

tests::with_additional_writable_roots_adds_new_root2878–2907 ↗
fn with_additional_writable_roots_adds_new_root()

作用:确认额外的可写根如果当前没有权限,会被真正加入策略。

数据流:输入是工作区可写策略,以及另一个 extra 目录作为额外可写根 → 调用追加方法 → 输出是在原规则后新增 extra 可写规则。

调用关系:测试 with_additional_writable_roots 在需要扩权时的正常添加路径。

调用图:调用 2 个内部函数(restricted, from_absolute_path);外部调用 4 个(new, assert_eq!, from_ref, vec!)。

tests::materialize_project_roots_with_workspace_roots_expands_exact_and_glob_entries2910–2991 ↗
fn materialize_project_roots_with_workspace_roots_expands_exact_and_glob_entries()

作用:确认把“项目根”这种符号规则落到多个实际工作区根时,普通路径和通配符规则都会分别展开。

数据流:输入是项目根可写、项目根下 .git 只读、项目根下 **/*.env 拒绝的规则,以及两个实际根目录 → 物化项目根 → 输出为每个根各自生成可写、.git 只读和 .env 通配拒绝规则。

调用关系:测试 materialize_project_roots_with_workspace_roots,这是把抽象策略变成具体路径策略的重要步骤。

调用图:调用 2 个内部函数(restricted, from_absolute_path);外部调用 3 个(new, assert_eq!, vec!)。

tests::materialize_project_roots_with_cwd_expands_symbolic_glob_entries2994–3016 ↗
fn materialize_project_roots_with_cwd_expands_symbolic_glob_entries()

作用:确认只有当前目录一个项目根时,带项目根占位的通配符规则会展开到当前目录下。

数据流:输入是项目根下 **/*.env 拒绝的通配规则和当前目录 → 物化项目根 → 输出是以当前目录为 base 的绝对通配模式。

调用关系:测试 materialize_project_roots_with_cwd 对符号通配规则的展开,保证运行时匹配拿到具体路径模式。

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

tests::with_additional_legacy_workspace_writable_roots_protects_metadata3019–3057 ↗
fn with_additional_legacy_workspace_writable_roots_protects_metadata()

作用:确认按旧版工作区方式追加可写根时,会顺手保护里面已有的 .git 等元数据目录。

数据流:输入是项目根可写策略和一个额外目录,额外目录里已有 .git → 调用旧版追加可写根方法 → 输出新增额外目录可写规则,并新增额外目录下 .git 只读规则。

调用关系:测试 with_additional_legacy_workspace_writable_roots,保证老接口扩展工作区时也不放开仓库元数据写入。

调用图:调用 2 个内部函数(restricted, from_absolute_path);外部调用 5 个(new, assert_eq!, create_dir_all, from_ref, vec!)。

tests::file_system_access_mode_orders_by_conflict_precedence3060–3063 ↗
fn file_system_access_mode_orders_by_conflict_precedence()

作用:确认权限模式的大小顺序符合冲突优先级:拒绝最高,写高于读。

数据流:输入是权限枚举的比较 → 执行断言 → 输出没有实际数据,只要比较关系不对测试就失败。

调用关系:测试 FileSystemAccessMode 的排序约定,因为多条规则冲突时会用这个顺序决定谁赢。

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

tests::legacy_bridge_preserves_explicit_deny_entries3066–3091 ↗
fn legacy_bridge_preserves_explicit_deny_entries()

作用:确认从旧策略重建新策略时,原来用户明确写下的拒绝规则不会丢。

数据流:输入是已有策略里拒绝 /tmp/private,以及一个旧版工作区策略 → 调用保留拒绝规则的转换方法 → 输出的新策略中仍能找到这条拒绝规则。

调用关系:测试 from_legacy_sandbox_policy_preserving_deny_entries,防止桥接旧策略时把用户的安全限制洗掉。

调用图:调用 3 个内部函数(from_legacy_sandbox_policy_preserving_deny_entries, restricted, try_from);外部调用 4 个(new, new_workspace_write_policy, assert!, vec!)。

tests::preserving_deny_entries_keeps_unrestricted_policy_enforceable3094–3113 ↗
fn preserving_deny_entries_keeps_unrestricted_policy_enforceable()

作用:确认一个原本不限制的策略,如果要保留旧策略里的拒绝读取规则,会变成可执行的受限策略,而不是继续完全放开。

数据流:输入是带拒绝 glob 的现有受限策略,以及一个 unrestricted(不受限)替换策略 → 从现有策略保留拒绝读取限制 → 输出策略变成根可写加拒绝 glob,并继承 glob 扫描深度。

调用关系:测试 preserve_deny_read_restrictions_from,保证“放开大部分权限”时仍能保留专门的不可读文件规则。

调用图:调用 2 个内部函数(restricted, unrestricted);外部调用 3 个(assert_eq!, unreadable_glob_entry, vec!)。

tests::deny_policy3115–3122 ↗
fn deny_policy(path: &Path) -> FileSystemSandboxPolicy

作用:测试辅助函数:快速做一个只拒绝某个绝对路径的文件系统策略。

数据流:输入是一个路径 → 转成绝对路径并包成一条 Deny 规则 → 输出包含这一条规则的受限策略。

调用关系:被后面多组读取拒绝测试复用,用来少写重复的策略搭建代码。

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

tests::unreadable_glob_entry3124–3129 ↗
fn unreadable_glob_entry(pattern: String) -> FileSystemSandboxEntry

作用:测试辅助函数:创建一条按通配符拒绝读取的规则。

数据流:输入是 glob 模式字符串 → 放进 FileSystemPath::GlobPattern,权限设为 Deny → 输出一条沙箱规则。

调用关系:被多个 glob 相关测试和策略构造辅助函数使用,用来统一创建不可读通配规则。

tests::default_policy_with_unreadable_glob3131–3135 ↗
fn default_policy_with_unreadable_glob(pattern: String) -> FileSystemSandboxPolicy

作用:测试辅助函数:在默认策略上加一条不可读通配规则。

数据流:输入是 glob 模式字符串 → 创建默认文件系统策略,把拒绝 glob 规则追加进去 → 输出这个新策略。

调用关系:被通配符匹配测试复用,用来专注测试匹配行为,而不是每个测试都手写策略。

调用图:调用 1 个内部函数(default);外部调用 1 个(unreadable_glob_entry)。

tests::is_read_denied3137–3144 ↗
fn is_read_denied(
        path: &Path,
        file_system_sandbox_policy: &FileSystemSandboxPolicy,
        cwd: &Path,
    ) -> bool

作用:测试辅助函数:问某个路径在某个策略和当前目录下是否被禁止读取。

数据流:输入是待检查路径、文件系统策略和当前目录 → 创建 ReadDenyMatcher(读取拒绝匹配器,用来判断路径是否命中禁止读取规则)并询问路径 → 输出布尔值;如果没有匹配器,就返回 false。

调用关系:被读取拒绝相关测试反复调用。它把测试和底层 ReadDenyMatcher::newmatcher.is_read_denied 连接起来。

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

tests::exact_path_and_descendants_are_denied3147–3162 ↗
fn exact_path_and_descendants_are_denied()

作用:确认拒绝一个目录时,这个目录本身和它下面的文件都会被拒绝读取。

数据流:输入是临时目录里的 denied 目录和其中的文件 → 建立拒绝该目录的策略 → 输出判断结果:目录和子文件被拒绝,旁边的其他文件不被拒绝。

调用关系:测试 ReadDenyMatcher 对精确路径和后代路径的基本行为。

调用图:外部调用 5 个(new, assert!, deny_policy, create_dir_all, write)。

tests::literal_patterns_and_globs_are_denied3182–3197 ↗
fn literal_patterns_and_globs_are_denied()

作用:确认普通拒绝路径和 glob 通配拒绝规则可以同时生效。

数据流:输入是一个私有目录和一个文本文件 → 策略拒绝私有目录,并追加匹配所有 txt 的通配拒绝 → 输出私有目录和文本文件都被拒绝读取。

调用关系:测试 ReadDenyMatcher 同时处理路径规则和通配规则的能力。

调用图:外部调用 7 个(new, assert!, format!, deny_policy, unreadable_glob_entry, create_dir_all, write)。

tests::glob_patterns_deny_matching_paths3200–3212 ↗
fn glob_patterns_deny_matching_paths()

作用:确认简单 glob 通配模式能拒绝匹配到的文件。

数据流:输入是路径 private/secret1.txt 和模式 private/secret?.txt → 创建带该模式的策略并检查文件 → 输出为读取被拒绝。

调用关系:测试 glob 匹配里 ? 这类单字符通配的基本效果。

调用图:外部调用 6 个(new, assert!, format!, default_policy_with_unreadable_glob, create_dir_all, write)。

tests::glob_patterns_do_not_cross_path_separators3215–3236 ↗
fn glob_patterns_do_not_cross_path_separators()

作用:确认普通 glob 的 * 不会跨过目录分隔符。也就是说它只匹配一层路径,不会一下钻进更深目录。

数据流:输入是同层文件、嵌套文件、长度不匹配文件和字母不匹配文件,以及模式 */file[0-9]?.txt → 检查这些路径 → 输出只有同层且格式匹配的文件被拒绝。

调用关系:测试 glob 语法细节,防止拒绝范围比用户写的模式更宽。

调用图:外部调用 6 个(new, assert!, format!, default_policy_with_unreadable_glob, create_dir_all, write)。

tests::globstar_patterns_deny_root_and_nested_matches3239–3255 ↗
fn globstar_patterns_deny_root_and_nested_matches()

作用:确认 ** 这种 globstar 通配能匹配根目录下和更深目录里的文件。

数据流:输入是根下 .env、子目录里的 .env 和普通 notes 文件,以及模式 **/*.env → 检查这些路径 → 输出两个 .env 被拒绝,普通文件不被拒绝。

调用关系:测试递归通配规则,常用于保护所有层级的环境变量文件。

调用图:外部调用 6 个(new, assert!, format!, default_policy_with_unreadable_glob, create_dir_all, write)。

tests::unclosed_character_classes_match_literal_brackets3258–3268 ↗
fn unclosed_character_classes_match_literal_brackets()

作用:确认不完整的字符类写法,比如单独一个 [,会按普通字符处理,而不是导致匹配器崩掉或乱匹配。

数据流:输入是名为 [ 的文件、普通 notes 文件,以及模式末尾也是 [ → 检查读取拒绝 → 输出只有 [ 文件被拒绝。

调用关系:测试 glob 解析的容错行为,保证用户写了特殊字符也能得到可预期的安全结果。

调用图:外部调用 5 个(new, assert!, format!, default_policy_with_unreadable_glob, write)。

protocol/src/models.rs源码 ↗
data_modelcross-cutting

可以把这个文件理解成项目里的“通用表格和翻译器”。一边是运行时真正用的权限和消息对象,另一边是要存成 JSON、发给前端或发给模型的格式。这里负责把它们互相转换,并且照顾旧格式兼容。比如文件权限既支持老的 read/write 根目录写法,也支持新的 entries 写法;权限配置能表示“系统管理沙箱”“完全关闭沙箱”“外部沙箱”。它还处理用户消息里的本地图片:读文件、转成模型能看的图片内容,失败时放一段可读的错误文字。工具调用输出也在这里被整理成纯文本、图片列表或 JSON 字符串。没有这些统一模型,权限可能被误放大,消息可能丢元数据,旧版本数据也可能读不回来。 从这段可以看出,models.rs 不只是放结构名字,还严格规定了这些结构怎么变成 JSON(常见的文本数据格式)以及怎么从 JSON 读回来。这里的测试重点盯住几类容易出错的地方:工具输出里有文字、图片、加密内容时,必须按数组发出去;图片如果已经是 data URL 就不要重复包装;图片清晰度这样的元数据不能丢;一些旧名字或旧消息要能兼容;本地图片读不到、格式不支持时,要用一段说明文字代替,而不是让整个请求崩掉。可以把它理解成“协议说明书加质检员”:格式定得清楚,测试再保证改代码时不会把这些约定弄坏。

函数细节148
SandboxPermissions::requires_escalated_permissions49–51 ↗
fn requires_escalated_permissions(self) -> bool

作用:判断这次请求是不是明确要求“提权”,也就是要比默认沙箱更大的权限。调用方用它来决定是否需要走更严格的批准流程。

数据流:输入是一个沙箱权限选项 → 它只检查这个选项是不是 RequireEscalated → 输出 true 或 false,不改任何数据。

调用关系:它被执行环境、网络权限和首次尝试沙箱覆盖等逻辑调用,用来把“是否要提权”这个判断集中到一个地方。

调用图:被 4 处调用(exec_env_for_sandbox_permissions, managed_network_for_sandbox_permissions, sandbox_override_for_first_attempt, sandbox_permissions_preserving_denied_reads);外部调用 1 个(matches!)。

SandboxPermissions::requests_sandbox_override55–57 ↗
fn requests_sandbox_override(self) -> bool

作用:判断请求有没有想改变默认沙箱设置。只要不是 UseDefault,就说明它提出了某种覆盖要求。

数据流:输入是当前权限选项 → 它检查是否不是默认值 → 返回一个布尔值,表示是否请求覆盖默认沙箱。

调用关系:命令事件和 shell 事件生成时会用它标记这次执行是否带有特殊权限意图,方便后续审批和记录。

调用图:被 3 处调用(exec_command_event, shell_event_with_prefix_rule, exec_command_event);外部调用 1 个(matches!)。

SandboxPermissions::uses_additional_permissions61–63 ↗
fn uses_additional_permissions(self) -> bool

作用:判断这次请求是不是要使用额外授予的权限,而不是直接完全提权。它区分“加一点权限”和“全量提权”。

数据流:输入是沙箱权限选项 → 检查是否为 WithAdditionalPermissions → 输出 true 或 false。

调用关系:授予回合权限和隐式权限计算会调用它,用来决定是否把额外权限合并进当前执行。

调用图:被 2 处调用(apply_granted_turn_permissions, implicit_granted_permissions);外部调用 1 个(matches!)。

FileSystemPermissions::try_from88–97 ↗
fn try_from(value: FileSystemPermissions<PathUri>) -> Result<Self, Self::Error>

作用:把用 URI 表示路径的文件权限,转换成真正的绝对本地路径版本。这样运行时就能安全地按本机路径检查访问。

数据流:输入是一组 PathUri 形式的文件权限 → 逐条把权限里的路径转成 AbsolutePathBuf → 成功返回新权限,失败返回输入输出错误。

调用关系:这是路径格式转换的一道关口,常用于从外部协议数据进入本地运行时之前。

FileSystemPermissions::default101–106 ↗
fn default() -> Self

作用:生成一个空的文件系统权限设置。意思是没有额外允许的路径,也没有通配符扫描深度限制。

数据流:没有输入 → 创建 entries 为空、glob_scan_max_depth 为空的对象 → 返回这个默认权限对象。

调用关系:测试和权限归一化会用它作为“什么都没配置”的基准。

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

FileSystemPermissions::is_empty112–114 ↗
fn is_empty(&self) -> bool

作用:判断文件系统权限里有没有任何路径规则。外部可以用它快速知道这份权限是不是空壳。

数据流:输入是权限对象本身 → 查看 entries 列表是否为空 → 返回 true 或 false。

调用关系:它是权限对象上的小检查器,常被更大的配置判断逻辑使用。

FileSystemPermissions::from_read_write_roots116–137 ↗
fn from_read_write_roots(
        read: Option<Vec<PathType>>,
        write: Option<Vec<PathType>>,
    ) -> Self

作用:把老式的“可读目录列表”和“可写目录列表”变成新的统一权限条目。这样老配置还能继续被新系统理解。

数据流:输入是可选的读路径列表和写路径列表 → 每个读路径变成 Read 条目,每个写路径变成 Write 条目 → 返回新的 FileSystemPermissions。

调用关系:反序列化旧格式、测试权限授予、远程执行授权等场景会用它把老数据接入新权限模型。

调用图:被 39 处调用(request_permissions_response_materializes_session_cwd_grants_before_recording, write_permissions_for_paths, file_system_permissions, file_system_sandbox_context_uses_active_attempt, preapproved_additional_permissions_escalate_intercepted_exec, shell_request_escalation_execution_is_explicit, extension_tool_uses_granted_turn_permissions, remote_request_permissions_grant_unblocks_later_remote_exec, normalized_directory_write_permissions, partial_request_permissions_grants_do_not_preapprove_new_permissions (+15 more));外部调用 1 个(new)。

FileSystemPermissions::explicit_path_entries139–144 ↗
fn explicit_path_entries(&self) -> impl Iterator<Item = (&PathType, FileSystemAccessMode)>

作用:只拿出明确写了具体路径的权限条目,忽略通配符和特殊路径。适合那些只关心“某个真实目录”的地方。

数据流:输入是权限对象 → 遍历 entries → 对 Path 类型条目输出路径和访问模式,其他类型跳过。

调用关系:它像一个过滤器,把复杂权限列表中最直接的路径规则交给调用方使用。

FileSystemPermissions::legacy_read_write_roots146–152 ↗
fn legacy_read_write_roots(&self) -> Option<LegacyReadWriteRoots<PathType>>

作用:尝试把新格式权限还原成老式 read/write 根目录格式。只有足够简单、老格式能表达时才会成功。

数据流:输入是新权限对象 → 先调用 as_legacy_permissions 尝试转换 → 成功时返回读列表和写列表,失败返回 None。

调用关系:它依赖 as_legacy_permissions 做实际判断,主要服务于需要兼容旧接口的地方。

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

FileSystemPermissions::as_legacy_permissions154–180 ↗
fn as_legacy_permissions(&self) -> Option<LegacyFileSystemPermissions<PathType>>

作用:检查一份新文件权限能不能安全地用老格式表示。遇到通配符、拒绝规则或扫描深度这类老格式不懂的内容,就拒绝转换。

数据流:输入是新权限对象 → 检查是否没有 glob_scan_max_depth,且每条都是普通路径的读或写 → 输出老格式权限;不兼容则输出 None。

调用关系:legacy_read_write_roots 和 serialize 都调用它,保证只有不丢信息时才写成老格式。

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

FileSystemPermissions::serialize214–227 ↗
fn serialize(&self, serializer: S) -> Result<S::Ok, S::Error>

作用:把文件权限写成 JSON 时,优先用旧系统也认识的简单格式;如果表达不了,就用新格式。这样兼顾兼容和完整性。

数据流:输入是权限对象和序列化器 → 先尝试 as_legacy_permissions → 能转就写旧格式,不能转就写 canonical 新格式。

调用关系:它在保存配置、发协议数据时自动工作,是新旧权限格式之间的出口适配器。

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

FileSystemPermissions::deserialize234–250 ↗
fn deserialize(deserializer: D) -> Result<Self, D::Error>

作用:读取 JSON 里的文件权限,既能读新格式,也能读老格式。这样升级后不会把旧配置读坏。

数据流:输入是 JSON 反序列化器 → 解析成新格式或老格式枚举 → 新格式直接返回,老格式通过 from_read_write_roots 转成新对象。

调用关系:它和 serialize 配套,负责权限数据进入系统时的兼容处理。

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

NetworkPermissions::is_empty259–261 ↗
fn is_empty(&self) -> bool

作用:判断网络权限有没有被明确设置。enabled 为空就表示这块没有配置。

数据流:输入是网络权限对象 → 查看 enabled 是否为 None → 返回是否为空。

调用关系:它帮助上层判断额外权限配置里是否真的包含网络设置。

AdditionalPermissionProfile::is_empty273–275 ↗
fn is_empty(&self) -> bool

作用:判断额外权限配置是不是完全没写网络和文件系统部分。注意:只要字段存在,即使里面是空的,也不算完全空。

数据流:输入是额外权限配置 → 检查 network 和 file_system 两个字段是否都为 None → 返回 true 或 false。

调用关系:权限合并和配置清理会用它区分“没提供配置”和“提供了一个空配置”。

SandboxEnforcement::from_legacy_sandbox_policy293–299 ↗
fn from_legacy_sandbox_policy(sandbox_policy: &SandboxPolicy) -> Self

作用:把老的沙箱策略翻译成新的“由谁执行沙箱”分类:关闭、外部执行、系统自己管理。它是旧权限模型迁移到新模型的入口之一。

数据流:输入是旧 SandboxPolicy → 根据策略类型映射到 Disabled、External 或 Managed → 返回新的 enforcement 值。

调用关系:配置应用、旧策略设置、执行一次性命令等流程都用它保持旧策略语义不变。

调用图:被 12 处调用(exec_one_off_command_inner, can_set_legacy_sandbox_policy, set_legacy_sandbox_policy, apply, session_configuration_apply_permission_profile_preserves_existing_deny_read_entries, session_configuration_apply_preserves_profile_file_system_policy_on_cwd_only_update, session_configuration_apply_retargets_legacy_workspace_root_on_cwd_update, build_agent_spawn_config_uses_turn_context_values, spawn_agent_reapplies_runtime_sandbox_after_role_config, from_legacy_sandbox_policy (+2 more))。

ManagedFileSystemPermissions::from321–337 ↗
fn from(value: ManagedFileSystemPermissions<AbsolutePathBuf>) -> Self

作用:把本地绝对路径版的受管文件权限转换成 URI 路径版,方便通过协议发送或存储。

数据流:输入是 AbsolutePathBuf 路径的 ManagedFileSystemPermissions → Restricted 时逐条路径转为 PathUri,Unrestricted 时保持不受限 → 返回转换后的对象。

调用关系:它承担本地运行时模型到协议模型的路径翻译。

ManagedFileSystemPermissions::try_from345–361 ↗
fn try_from(value: ManagedFileSystemPermissions<PathUri>) -> Result<Self, Self::Error>

作用:把 URI 路径版的受管文件权限转换成本地绝对路径版。转换可能失败,所以返回结果类型。

数据流:输入是 PathUri 版本的权限 → Restricted 时逐条尝试转成本地绝对路径,Unrestricted 直接保留 → 成功返回新对象,失败返回 IO 错误。

调用关系:它通常出现在协议数据进入本地执行前,确保路径能被本机识别。

ManagedFileSystemPermissions::from_sandbox_policy365–378 ↗
fn from_sandbox_policy(file_system_sandbox_policy: &FileSystemSandboxPolicy) -> Self

作用:从运行时的文件沙箱策略生成权限配置里的文件系统部分。它把底层策略包装成更适合配置和传输的模型。

数据流:输入是 FileSystemSandboxPolicy → Restricted 复制规则和扫描深度,Unrestricted 转为不受限,ExternalSandbox 被认为不该走这里 → 返回 ManagedFileSystemPermissions。

调用关系:PermissionProfile 构造读写预设、运行时权限转换时都会调用它。

调用图:被 4 处调用(from_runtime_permissions_with_enforcement, materialize_project_roots_with_workspace_roots, read_only, workspace_write_with);外部调用 1 个(unreachable!)。

ManagedFileSystemPermissions::to_sandbox_policy380–392 ↗
fn to_sandbox_policy(&self) -> FileSystemSandboxPolicy

作用:把权限配置里的文件系统部分还原成运行时真正执行的沙箱策略。

数据流:输入是 ManagedFileSystemPermissions → Restricted 转成带规则的 FileSystemSandboxPolicy,Unrestricted 转成不限制策略 → 返回运行时策略。

调用关系:PermissionProfile 输出运行时权限、转换旧策略时依赖它。

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

PermissionProfile::try_from443–455 ↗
fn try_from(value: PermissionProfile<PathUri>) -> Result<Self, Self::Error>

作用:把 URI 路径版的权限档案转换成本地绝对路径版。这样收到的配置可以进入本地运行时。

数据流:输入是 PermissionProfile<PathUri> → Managed 时转换文件系统路径,Disabled 和 External 原样保留网络信息 → 返回本地路径版或错误。

调用关系:它是权限档案跨协议边界后的落地转换。

ActivePermissionProfile::new479–484 ↗
fn new(id: impl Into<String>) -> Self

作用:创建一个当前启用的权限档案引用,只填档案 id,不继承其他档案。

数据流:输入是可转成字符串的 id → 转成 String,extends 设为 None → 返回 ActivePermissionProfile。

调用关系:read_only 等便捷构造会用它创建内置权限档案引用。

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

ActivePermissionProfile::read_only486–488 ↗
fn read_only() -> Self

作用:创建一个指向内置只读权限档案的当前档案引用。

数据流:没有输入 → 使用内置只读档案名调用 new → 返回 ActivePermissionProfile。

调用关系:它是选择只读内置权限档案的快捷方式。

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

PermissionProfile::default492–500 ↗
fn default() -> Self

作用:给权限档案一个保守默认值:文件系统受限且没有允许条目,网络也受限。默认情况下先保护起来。

数据流:没有输入 → 创建 Managed 档案,文件 entries 为空,网络 Restricted → 返回默认权限档案。

调用关系:很多工具列表和缓存相关测试会用到它,表示没有特别放权的起点。

调用图:被 14 处调用(list_all_tools_accepts_canonical_namespaced_tool_names, list_all_tools_adds_server_metadata_to_cached_tools, list_all_tools_applies_legacy_mcp_prefix_by_default, list_all_tools_blocks_while_client_is_pending_without_cached_tool_info_snapshot, list_all_tools_does_not_block_when_cached_tool_info_snapshot_is_empty, list_all_tools_uses_cached_tool_info_snapshot_when_client_startup_fails, list_all_tools_uses_cached_tool_info_snapshot_while_client_is_pending, list_available_server_infos_uses_cache_while_client_is_pending, no_local_runtime_fails_local_stdio_but_keeps_local_http_server, shutdown_cancels_pending_tool_listing (+4 more));外部调用 1 个(new)。

PermissionProfile::read_only505–511 ↗
fn read_only() -> Self

作用:生成内置“只读”权限档案。它允许读,但不允许随便写,网络仍受限。

数据流:没有输入 → 从 FileSystemSandboxPolicy::read_only 得到底层文件策略 → 转成受管文件权限并配上受限网络 → 返回 Managed 权限档案。

调用关系:线程恢复、Windows 沙箱请求、默认权限和配置推导等场景会用它。

调用图:调用 2 个内部函数(from_sandbox_policy, read_only);被 153 处调用(rollback_response_rebuilds_pathless_thread_from_stored_history, cancellation_expiration_keeps_process_alive_until_terminated, timeout_or_cancellation_reports_cancellation_without_timeout_exit_code, windows_sandbox_exec_request, requested_permissions_trust_project_uses_permission_profile_intent, summary_from_stored_thread_preserves_millisecond_precision, default, try_from, derive_permission_profile, load_config_with_layer_stack (+15 more))。

PermissionProfile::workspace_write518–525 ↗
fn workspace_write() -> Self

作用:生成内置“工作区可写”权限档案。它通常允许在项目工作区内写文件,但网络仍默认受限。

数据流:没有输入 → 调用 workspace_write_with,传空的额外可写根、受限网络和默认临时目录选项 → 返回权限档案。

调用关系:权限推导、调试沙箱和配置测试会用它作为常见开发模式。

调用图:被 77 处调用(requested_permissions_trust_project_uses_permission_profile_intent, debug_sandbox_honors_explicit_builtin_permission_profile, derive_permission_profile, derive_sandbox_policy_preserves_windows_downgrade_for_unsupported_fallback, permission_snapshot_setter_preserves_permission_constraints, managed_allowed_domains_only_disables_default_mode_allowlist_expansion, managed_allowed_domains_only_ignores_user_allowlist_and_hard_denies_misses, managed_allowed_domains_only_without_managed_allowlist_blocks_all_user_domains, requirements_allowed_domains_do_not_override_user_denies_for_same_pattern, requirements_allowlist_expansion_keeps_user_entries_mutable (+15 more));外部调用 1 个(workspace_write_with)。

PermissionProfile::workspace_write_with532–547 ↗
fn workspace_write_with(
        writable_roots: &[AbsolutePathBuf],
        network: NetworkSandboxPolicy,
        exclude_tmpdir_env_var: bool,
        exclude_slash_tmp: bool,
    ) -> Self

作用:生成一个可定制的工作区可写权限档案,可以指定哪些根目录可写、网络策略以及是否排除临时目录。

数据流:输入是可写根列表、网络策略和两个临时目录开关 → 构造底层 workspace_write 文件沙箱策略 → 转成 Managed 权限档案。

调用关系:配置解析、远程沙箱匹配和权限推导会调用它创建具体工作区权限。

调用图:调用 2 个内部函数(from_sandbox_policy, workspace_write);被 33 处调用(deserialize_allowed_sandbox_modes, remote_sandbox_config_first_match_overrides_top_level, derive_permission_profile, builtin_permission_profile, windows_restricted_token_allows_workspace_write_profiles, granular_sandbox_approval_false_rejects_out_of_root_patch, granular_with_all_flags_true_matches_on_request_for_out_of_root_patch, missing_project_dot_codex_config_requires_approval, restrictive_workspace_write_profile, restrictive_workspace_write_profile (+15 more))。

PermissionProfile::materialize_project_roots_with_workspace_roots549–569 ↗
fn materialize_project_roots_with_workspace_roots(
        self,
        workspace_roots: &[AbsolutePathBuf],
    ) -> Self

作用:把权限档案里的“项目根”占位含义落到实际工作区路径上。这样抽象规则能变成具体路径规则。

数据流:输入是权限档案和工作区根目录列表 → Managed 时把文件策略 materialize 后再包回档案,Disabled 和 External 原样返回 → 输出新权限档案。

调用关系:它在项目路径确定后使用,连接配置阶段的抽象权限和运行时的真实路径。

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

PermissionProfile::from_runtime_permissions571–586 ↗
fn from_runtime_permissions(
        file_system_sandbox_policy: &FileSystemSandboxPolicy,
        network_sandbox_policy: NetworkSandboxPolicy,
    ) -> Self

作用:从运行时已经有的文件沙箱策略和网络策略,推导出一个权限档案。它自动判断这是系统管理沙箱还是外部沙箱。

数据流:输入是文件沙箱策略和网络策略 → 根据文件策略 kind 选择 enforcement → 调用 from_runtime_permissions_with_enforcement → 返回 PermissionProfile。

调用关系:配置加载和权限意图保存会用它把底层运行时状态变成可展示、可存储的档案。

调用图:被 65 处调用(requested_permissions_trust_project_uses_permission_profile_intent, load_config_with_layer_stack, permission_profile_override_keeps_memories_root_out_of_legacy_projection, workspace_write_permission_profile_with_private_denials, managed_cwd_write_profile_has_filesystem_restrictions, managed_full_disk_write_profile_has_no_filesystem_restrictions, managed_unresolvable_write_profile_has_filesystem_restrictions, writable_windows_policy_without_sandbox_backend_still_requires_approval, windows_elevated_allows_split_restricted_read_policies, windows_elevated_rejects_reopened_writable_descendants (+15 more));外部调用 1 个(from_runtime_permissions_with_enforcement)。

PermissionProfile::from_runtime_permissions_with_enforcement588–609 ↗
fn from_runtime_permissions_with_enforcement(
        enforcement: SandboxEnforcement,
        file_system_sandbox_policy: &FileSystemSandboxPolicy,
        network_sandbox_policy: NetworkSandboxPolic

作用:按明确的执行方式,把运行时权限转换成权限档案。它能正确区分外部沙箱、完全关闭沙箱和受管沙箱。

数据流:输入是 enforcement、文件策略、网络策略 → ExternalSandbox 生成 External;不受限且 enforcement 为 Disabled 生成 Disabled;其他文件策略生成 Managed → 返回档案。

调用关系:旧策略转换、配置应用、权限投影设置等核心流程都用它,避免权限语义在转换中丢失。

调用图:调用 1 个内部函数(from_sandbox_policy);被 23 处调用(managed_full_disk_with_restricted_network_reports_external_sandbox, exec_one_off_command_inner, load_config_with_layer_stack, can_set_legacy_sandbox_policy, set_legacy_sandbox_policy, permission_profile_override_preserves_split_write_roots, apply, set_permission_profile_projection, record_context_updates_and_set_reference_context_item_persists_split_file_system_policy_to_rollout, session_configuration_apply_permission_profile_preserves_existing_deny_read_entries (+13 more))。

PermissionProfile::from_legacy_sandbox_policy611–617 ↗
fn from_legacy_sandbox_policy(sandbox_policy: &SandboxPolicy) -> Self

作用:把老的 SandboxPolicy 完整转换成新的 PermissionProfile。它同时处理文件、网络和沙箱执行方式。

数据流:输入是旧沙箱策略 → 分别转 enforcement、文件策略、网络策略 → 调用 from_runtime_permissions_with_enforcement → 返回新权限档案。

调用关系:旧策略回归测试和兼容层用它确保老配置还能得到同样的行为。

调用图:调用 3 个内部函数(from_legacy_sandbox_policy, from, from);被 2 处调用(permission_profile_round_trip_preserves_disabled_sandbox, permission_profile_round_trip_preserves_external_sandbox);外部调用 1 个(from_runtime_permissions_with_enforcement)。

PermissionProfile::from_legacy_sandbox_policy_for_cwd619–625 ↗
fn from_legacy_sandbox_policy_for_cwd(sandbox_policy: &SandboxPolicy, cwd: &Path) -> Self

作用:按当前工作目录把旧沙箱策略转换成新权限档案。某些旧策略里的工作区含义需要依赖 cwd 才能落成具体路径。

数据流:输入是旧 SandboxPolicy 和 cwd → 生成 enforcement、按 cwd 转文件策略、转网络策略 → 返回 PermissionProfile。

调用关系:提交回合、线程设置恢复和界面展示权限档案时会用它处理旧数据。

调用图:调用 3 个内部函数(from_legacy_sandbox_policy, from_legacy_sandbox_policy_for_cwd, from);被 6 处调用(submit_turn_with_policies, deserialize, apply_thread_settings_to_session, display_permission_profile_from_thread_response, thread_session_state_from_thread_resume_response, apply_thread_settings);外部调用 1 个(from_runtime_permissions_with_enforcement)。

PermissionProfile::enforcement627–633 ↗
fn enforcement(&self) -> SandboxEnforcement

作用:告诉调用方这个权限档案的沙箱到底是系统管理、完全关闭,还是交给外部沙箱。

数据流:输入是权限档案 → 根据枚举分支返回 Managed、Disabled 或 External → 不修改对象。

调用关系:权限投影、文件系统沙箱上下文和有效权限计算会用它决定后续执行路径。

调用图:被 4 处调用(set_permission_profile_projection, file_system_sandbox_context, with_managed_mitm_ca_readable_root, effective_permission_profile)。

PermissionProfile::file_system_sandbox_policy635–641 ↗
fn file_system_sandbox_policy(&self) -> FileSystemSandboxPolicy

作用:从权限档案中取出运行时需要的文件系统沙箱策略。不同档案类型会映射成受限、不受限或外部沙箱。

数据流:输入是 PermissionProfile → Managed 转出内部文件策略,Disabled 返回不受限,External 返回外部沙箱策略 → 输出 FileSystemSandboxPolicy。

调用关系:运行时执行、沙箱模式判断和权限标签生成都会调用它。

调用图:调用 2 个内部函数(external_sandbox, unrestricted);被 13 处调用(sandbox_policy_mode, permission_profile_trusts_project, sandbox_mode_requirement_for_permission_profile, profile_has_managed_filesystem_restrictions, permission_profile_policy_tag, file_system_sandbox_policy, sandbox_mode_from_permission_profile, from_permission_profile, to_runtime_permissions, add_dir_warning_message (+3 more))。

PermissionProfile::network_sandbox_policy643–648 ↗
fn network_sandbox_policy(&self) -> NetworkSandboxPolicy

作用:从权限档案中取出网络沙箱策略。完全关闭沙箱时网络被视为可用。

数据流:输入是 PermissionProfile → Managed 或 External 返回保存的 network,Disabled 返回 Enabled → 输出 NetworkSandboxPolicy。

调用关系:网络代理、Linux 沙箱启动和运行时权限转换会用它。

调用图:被 11 处调用(sandbox_policy_mode, network_proxy_spec_for_active_permission_profile, spawn_command_under_linux_sandbox, network_sandbox_policy, sandbox_mode_from_permission_profile, from_permission_profile, to_runtime_permissions, sandbox_mode_from_permission_profile, preset_matches_current, legacy_compatible_permission_profile (+1 more))。

PermissionProfile::to_legacy_sandbox_policy650–667 ↗
fn to_legacy_sandbox_policy(&self, cwd: &Path) -> io::Result<SandboxPolicy>

作用:把新的权限档案尽量转换回旧 SandboxPolicy。这样旧接口、旧记录和旧展示还能继续工作。

数据流:输入是权限档案和当前目录 → Managed 通过文件策略转旧策略,Disabled 转 DangerFullAccess,External 转 ExternalSandbox 并带网络状态 → 返回旧策略或 IO 错误。

调用关系:回合权限字段、兼容权限档案和权限摘要会调用它。

调用图:被 4 处调用(turn_permission_fields, compatibility_sandbox_policy_for_permission_profile, legacy_compatible_permission_profile, summarize_permission_profile)。

PermissionProfile::to_runtime_permissions669–674 ↗
fn to_runtime_permissions(&self) -> (FileSystemSandboxPolicy, NetworkSandboxPolicy)

作用:把权限档案拆成运行时真正使用的文件系统策略和网络策略。

数据流:输入是 PermissionProfile → 分别调用 file_system_sandbox_policy 和 network_sandbox_policy → 返回二元组。

调用关系:构建执行请求、沙箱上下文和当前线程权限应用会用它。

调用图:调用 2 个内部函数(file_system_sandbox_policy, network_sandbox_policy);被 12 处调用(build_exec_request, resolve_windows_elevated_filesystem_overrides, resolve_windows_restricted_token_filesystem_overrides, new, set_permission_profile_projection, file_system_sandbox_context, sandbox_exec_request, apply_permission_profile_to_current_thread, should_warn_about_system_bwrap, with_managed_mitm_ca_readable_root (+2 more))。

PermissionProfile::from718–743 ↗
fn from(value: LegacyPermissionProfile<PathType>) -> Self

作用:把旧版权限档案结构转换成新版 PermissionProfile。缺少文件权限时按空的受限文件权限处理。

数据流:输入是 LegacyPermissionProfile → 文件部分存在就取规则,不存在就建空受限规则;网络 enabled 为 true 才放开网络 → 返回 Managed 档案。

调用关系:PermissionProfile::deserialize 读到旧形状数据时会交给它完成升级。

PermissionProfile::deserialize757–765 ↗
fn deserialize(deserializer: D) -> Result<Self, D::Error>

作用:读取权限档案 JSON,既支持带标签的新格式,也支持旧 rollout 形状。这样数据格式升级不会破坏老记录。

数据流:输入是反序列化器 → 先解析成 Tagged 或 Legacy → 分别转换成 PermissionProfile → 返回结果。

调用关系:它是权限档案从磁盘或网络进入系统时的兼容入口。

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

NetworkPermissions::from769–773 ↗
fn from(value: NetworkSandboxPolicy) -> Self

作用:把运行时网络沙箱策略转成可序列化的网络权限对象。

数据流:输入是 NetworkSandboxPolicy → 调用 is_enabled 得到是否允许联网 → 放进 enabled 字段并返回。

调用关系:权限策略需要写入配置或协议数据时会用到这类转换。

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

FileSystemPermissions::from777–793 ↗
fn from(value: &FileSystemSandboxPolicy) -> Self

作用:把运行时文件沙箱策略转成可序列化的文件权限对象。不受限或外部沙箱会被表示成根目录可写。

数据流:输入是 FileSystemSandboxPolicy 引用 → Restricted 复制规则;Unrestricted 或 ExternalSandbox 生成 Root 写权限条目 → 带上扫描深度后返回。

调用关系:它服务于运行时权限向协议权限的投影。

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

FileSystemSandboxPolicy::from797–801 ↗
fn from(value: &FileSystemPermissions) -> Self

作用:把可序列化的文件权限对象转回运行时的受限文件沙箱策略。

数据流:输入是 FileSystemPermissions → 用 entries 创建 restricted 策略 → 把 glob_scan_max_depth 转成 usize 写回 → 返回 FileSystemSandboxPolicy。

调用关系:当配置权限需要真正执行时,会通过它落到运行时策略。

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

plaintext_agent_message_content867–878 ↗
fn plaintext_agent_message_content(content: &[AgentMessageInputContent]) -> Option<String>

作用:把代理消息里的纯文本片段合并成一段文字。如果里面夹了加密内容,就拒绝返回,避免把不该看的内容当明文处理。

数据流:输入是一组 AgentMessageInputContent → 收集 InputText,遇到 EncryptedContent 立刻返回 None → 文本用换行拼接,空白则返回 None。

调用关系:收集守护进程转录、构建当前线程内容和推送可见消息时会用它提取可展示文本。

调用图:被 3 处调用(collect_guardian_transcript_entries, build_current_thread_section, push_visible_message);外部调用 2 个(with_capacity, len)。

ResponseItem::is_user_message1122–1124 ↗
fn is_user_message(&self) -> bool

作用:判断一个响应条目是不是用户消息。它只看类型是不是 Message 且 role 等于 user。

数据流:输入是 ResponseItem → 匹配消息类型和角色 → 返回 true 或 false。

调用关系:这是消息处理中的小分类器,帮助上层区分用户发言和其他系统事件。

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

ResponseItem::turn_id1127–1131 ↗
fn turn_id(&self) -> Option<&str>

作用:读取响应条目上的回合编号,并且过滤掉空字符串。回合编号用来把消息归到某一次对话回合。

数据流:输入是 ResponseItem → 调用 metadata 取元数据 → 取 turn_id,若为空则丢弃 → 返回 Option<&str>。

调用关系:stamp_turn_id_if_missing 会先调用它,避免覆盖已有的有效回合编号。

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

ResponseItem::stamp_turn_id_if_missing1134–1144 ↗
fn stamp_turn_id_if_missing(&mut self, turn_id: &str)

作用:给响应条目补上回合编号,但只在编号非空且原本没有编号时补。它不会覆盖已有编号。

数据流:输入是可变 ResponseItem 和 turn_id 字符串 → 若输入为空或已有 turn_id 就退出 → 找到可写元数据槽并写入 turn_id。

调用关系:它依赖 turn_id 和 metadata_mut,在整理历史消息或补全回合信息时使用。

调用图:调用 2 个内部函数(metadata_mut, turn_id)。

ResponseItem::clear_metadata1147–1151 ↗
fn clear_metadata(&mut self)

作用:清掉响应条目上的元数据。适合在发给不需要内部信息的一方之前做脱敏。

数据流:输入是可变 ResponseItem → 找到元数据槽 → 把它设为 None;没有元数据槽则不做事。

调用关系:它通过 metadata_mut 统一处理各种 ResponseItem 分支。

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

ResponseItem::metadata1153–1172 ↗
fn metadata(&self) -> Option<&ResponseItemMetadata>

作用:从各种响应条目里统一读取元数据。不同条目字段不同,这个函数把读取方式统一起来。

数据流:输入是 ResponseItem → 对支持 metadata 的分支返回 metadata.as_ref(),Other 返回 None → 不修改数据。

调用关系:turn_id 调用它读取回合编号,避免每个调用方都写一长串匹配。

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

ResponseItem::metadata_mut1174–1193 ↗
fn metadata_mut(&mut self) -> Option<&mut Option<ResponseItemMetadata>>

作用:从各种响应条目里统一拿到可修改的元数据槽。这样写元数据和清元数据不用关心具体条目类型。

数据流:输入是可变 ResponseItem → 支持 metadata 的分支返回可变 Option 引用,Other 返回 None → 调用方可据此修改。

调用关系:stamp_turn_id_if_missing 和 clear_metadata 都依赖它完成元数据写入或清除。

调用图:被 2 处调用(clear_metadata, stamp_turn_id_if_missing)。

BaseInstructions::default1206–1210 ↗
fn default() -> Self

作用:提供默认基础指令文本。基础指令就是系统给模型的默认行为说明。

数据流:没有输入 → 把 BASE_INSTRUCTIONS_DEFAULT 转成 String → 返回 BaseInstructions。

调用关系:线程持久化、摘要和默认配置相关流程会用它保证没有自定义指令时仍有基础指令。

调用图:被 12 处调用(get_conversation_summary_by_thread_id_reads_pathless_store_thread, thread_delete_with_non_local_thread_store_does_not_create_local_persistence, seed_pathless_store_thread, thread_unarchive_preserves_pathless_store_metadata, default, attach_thread_persistence, shutdown_complete_does_not_append_to_thread_store_after_shutdown, find_locates_rollout_file_written_by_recorder, persist_reports_filesystem_error_and_retries_buffered_items, recorder_materializes_on_flush_with_pending_items (+2 more))。

format_allow_prefixes1217–1254 ↗
fn format_allow_prefixes(prefixes: Vec<Vec<String>>) -> Option<String>

作用:把允许执行的命令前缀列表整理成给人看的文本,并限制数量和长度,防止输出太长。

数据流:输入是多组命令 token 前缀 → 先排序,再最多取 MAX_RENDERED_PREFIXES 条,每条渲染成列表行 → 如超数量或超字节长度就截断并加省略标记。

调用关系:执行策略变更消息和批准命令前缀展示会用它,把机器规则变成用户能读的清单。

调用图:被 5 处调用(record_execpolicy_amendment_message, approved_command_prefixes_text, format_allow_prefixes_limits_output, render_command_prefix_list_limits_output_to_max_prefixes, render_command_prefix_list_sorts_by_len_then_total_len_then_alphabetical);外部调用 1 个(format!)。

prefix_combined_str_len1256–1258 ↗
fn prefix_combined_str_len(prefix: &[String]) -> usize

作用:计算一个命令前缀里所有字符串 token 的总长度。它用于排序时比较前缀复杂度。

数据流:输入是一组字符串 → 把每个字符串长度相加 → 返回总长度。

调用关系:format_allow_prefixes 在排序规则里调用它,让短而简单的前缀先显示。

render_command_prefix1260–1267 ↗
fn render_command_prefix(prefix: &[String]) -> String

作用:把一组命令 token 渲染成像 JSON 数组一样的可读文本。这样空格、引号等字符也能看清楚。

数据流:输入是字符串 token 列表 → 每个 token 尽量用 JSON 字符串格式转义 → 用逗号拼接并加方括号 → 返回文本。

调用关系:format_allow_prefixes 用它把每条允许前缀变成一行展示内容。

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

should_serialize_reasoning_content1269–1276 ↗
fn should_serialize_reasoning_content(content: &Option<Vec<ReasoningItemContent>>) -> bool

作用:决定推理内容字段是否应该被序列化。只要里面包含普通推理文本,就不序列化这个字段。

数据流:输入是可选的推理内容列表 → None 返回 false;Some 时检查是否没有 ReasoningText → 返回是否应该写出。

调用关系:它通常作为序列化条件使用,控制哪些推理内容能进入 JSON。

local_image_error_placeholder1278–1289 ↗
fn local_image_error_placeholder(
    path: &std::path::Path,
    error: impl std::fmt::Display,
) -> ContentItem

作用:当本地图片读取或处理失败时,生成一段普通文本作为占位说明。这样用户和模型都能知道图片没附上,而不是静默丢失。

数据流:输入是图片路径和错误 → 拼出说明文字 → 返回 ContentItem::InputText。

调用关系:本地图片准备失败时,local_image_content_items_with_label_number 和用户输入转换会使用它。

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

image_open_tag_text1299–1301 ↗
fn image_open_tag_text() -> String

作用:返回通用图片开始标签文本。这个标签用于在文本流里标出图片位置。

数据流:没有输入 → 把常量 IMAGE_OPEN_TAG 转成 String → 返回。

调用关系:图片占位和解析逻辑用它保持标签文本一致。

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

image_close_tag_text1303–1305 ↗
fn image_close_tag_text() -> String

作用:返回通用图片结束标签文本。它和开始标签配对,标明图片占位结束。

数据流:没有输入 → 把 IMAGE_CLOSE_TAG 转成 String → 返回。

调用关系:解析用户消息和生成图片占位时会依赖同一套标签。

local_image_label_text1307–1309 ↗
fn local_image_label_text(label_number: usize) -> String

作用:按编号生成本地图片标签,比如“[Image #1]”。这让多张图片在消息里能被清楚地区分。

数据流:输入是图片编号 → 格式化成标签字符串 → 返回。

调用关系:附加图片、重新标号图片和生成本地图片开始标签都会调用它。

调用图:被 12 处调用(local_image_open_tag_text_with_path, attach_image, relabel_local_images, apply_external_edit_drops_missing_attachments, apply_external_edit_limits_duplicates_to_occurrences, apply_external_edit_rebuilds_text_and_attachments, apply_external_edit_renumbers_image_placeholders, clear_for_ctrl_c_preserves_image_draft_state, deleting_reordered_image_one_renumbers_text_in_place, set_text_content_reattaches_images_without_placeholder_metadata (+2 more));外部调用 1 个(format!)。

local_image_open_tag_text_with_path1311–1315 ↗
fn local_image_open_tag_text_with_path(label_number: usize, path: &std::path::Path) -> String

作用:生成带编号和文件路径的本地图片开始标签。它让文本记录里既能看到第几张图,也能看到来自哪个文件。

数据流:输入是图片编号和路径 → 先生成标签,再把路径显示字符串嵌入模板 → 返回开始标签文本。

调用关系:local_image_content_items 用它包装本地图片内容。

调用图:调用 1 个内部函数(local_image_label_text);被 1 处调用(local_image_content_items);外部调用 2 个(display, format!)。

is_local_image_open_tag_text1317–1320 ↗
fn is_local_image_open_tag_text(text: &str) -> bool

作用:判断一段文本是不是本地图片开始标签。它不解析细节,只确认前后固定格式匹配。

数据流:输入是文本 → 检查是否有指定前缀且以指定后缀结尾 → 返回 true 或 false。

调用关系:parse_user_message 用它识别文本里的本地图片占位。

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

is_local_image_close_tag_text1322–1324 ↗
fn is_local_image_close_tag_text(text: &str) -> bool

作用:判断一段文本是不是本地图片结束标签。本地图片的结束标签复用通用图片结束标签。

数据流:输入是文本 → 调用 is_image_close_tag_text → 返回判断结果。

调用关系:parse_user_message 用它识别本地图片占位结束。

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

is_image_open_tag_text1326–1328 ↗
fn is_image_open_tag_text(text: &str) -> bool

作用:判断文本是否正好是通用图片开始标签。

数据流:输入是文本 → 和 IMAGE_OPEN_TAG 常量比较 → 返回 true 或 false。

调用关系:用户消息解析会用它识别图片边界。

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

is_image_close_tag_text1330–1332 ↗
fn is_image_close_tag_text(text: &str) -> bool

作用:判断文本是否正好是通用图片结束标签。

数据流:输入是文本 → 和 IMAGE_CLOSE_TAG 常量比较 → 返回 true 或 false。

调用关系:parse_user_message 和 is_local_image_close_tag_text 都用它保持结束标签判断一致。

调用图:被 2 处调用(parse_user_message, is_local_image_close_tag_text)。

invalid_image_error_placeholder1334–1345 ↗
fn invalid_image_error_placeholder(
    path: &std::path::Path,
    error: impl std::fmt::Display,
) -> ContentItem

作用:当文件看起来是图片但内容无效时,生成一段说明文字。它把“无效图片”和普通读取失败区分开。

数据流:输入是路径和错误 → 格式化成“图片无效”的文本 → 返回 InputText 内容项。

调用关系:local_image_content_items_with_label_number 在图片解码发现无效数据时调用它。

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

unsupported_image_error_placeholder1347–1355 ↗
fn unsupported_image_error_placeholder(path: &std::path::Path, mime: &str) -> ContentItem

作用:当图片格式不支持时,生成一段可读的错误占位文本。

数据流:输入是路径和 MIME 类型(文件内容类型说明) → 格式化说明不支持该图片类型 → 返回 InputText。

调用关系:本地图片处理遇到 UnsupportedImageFormat 时会调用它。

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

local_image_content_items_with_label_number1357–1388 ↗
fn local_image_content_items_with_label_number(
    path: &std::path::Path,
    file_bytes: Vec<u8>,
    label_number: Option<usize>,
    detail: ImageDetail,
) -> Vec<ContentItem>

作用:把本地图片文件字节变成模型可接收的内容项,并可带编号标签。失败时不会崩溃,而是返回错误文字占位。

数据流:输入是路径、文件字节、可选编号和图片清晰度要求 → 根据 detail 选择原图或缩放处理 → 成功时转成 data URL 并包装成内容项;失败时按错误类型返回对应文本占位。

调用关系:ResponseInputItem::from_user_input 读取本地图片后会调用它;它再把成功结果交给 local_image_content_items。

调用图:调用 1 个内部函数(local_image_content_items);外部调用 2 个(load_for_prompt_bytes, vec!)。

local_image_content_items1396–1418 ↗
fn local_image_content_items(
    path: &std::path::Path,
    image_url: String,
    label_number: Option<usize>,
    detail: ImageDetail,
) -> Vec<ContentItem>

作用:把一张已经准备好的图片 URL 包装成消息内容项,可选地在前后加本地图片标签。

数据流:输入是路径、图片 data URL、可选编号和 detail → 有编号时先加开始标签,再加 InputImage,最后加结束标签 → 返回内容项列表。

调用关系:local_image_content_items_with_label_number 和延迟处理本地图片的路径都会用它。

调用图:调用 1 个内部函数(local_image_open_tag_text_with_path);被 1 处调用(local_image_content_items_with_label_number);外部调用 1 个(with_capacity)。

ResponseItem::from1421–1470 ↗
fn from(item: ResponseInputItem) -> Self

作用:把输入给模型的响应项转换成系统内部保存的响应项。转换时会补上内部字段的默认值,比如 id 和 metadata。

数据流:输入是 ResponseInputItem → 按类型映射成对应 ResponseItem;MCP 工具输出会先转成功能调用输出载荷 → 返回 ResponseItem。

调用关系:用户输入转响应项、测试消息阶段保留等流程会用它统一输入和历史记录格式。

调用图:被 2 处调用(response_item_from_user_input, response_input_message_conversion_preserves_phase)。

ResponseInputItem::from1540–1542 ↗
fn from(items: Vec<UserInput>) -> Self

作用:把一组用户输入直接转换成一条用户消息输入项,并默认处理本地图片。

数据流:输入是 Vec<UserInput> → 调用 from_user_input,LocalImagePreparation 设为 Process → 返回 ResponseInputItem。

调用关系:紧凑任务和图片输入测试用它作为便捷转换入口。

调用图:被 8 处调用(run_compact_task_inner_impl, image_user_input_preserves_requested_detail, local_image_non_image_adds_placeholder, local_image_read_error_adds_placeholder, local_image_unsupported_image_format_adds_placeholder, local_image_user_input_preserves_requested_detail, mixed_remote_and_local_images_share_label_sequence, serializes_image_user_input_without_tags);外部调用 1 个(from_user_input)。

ResponseInputItem::from_user_input1546–1595 ↗
fn from_user_input(
        items: Vec<UserInput>,
        local_image_preparation: LocalImagePreparation,
    ) -> Self

作用:把用户输入列表整理成模型能接收的一条 user 消息。它处理文字、远程图片、本地图片,并忽略稍后才注入的技能和提及内容。

数据流:输入是用户输入列表和本地图片处理方式 → 逐项转换为 ContentItem;本地图片会读文件,成功后处理或延迟包装,失败则放错误占位 → 输出 ResponseInputItem::Message。

调用关系:ResponseInputItem::from 和 response_item_from_user_input 会调用它,是用户输入进入协议模型的主要入口。

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

function_call_output_content_items_to_text1663–1683 ↗
fn function_call_output_content_items_to_text(
    content_items: &[FunctionCallOutputContentItem],
) -> Option<String>

作用:从工具调用输出的内容项里提取可读文本,忽略图片、加密内容和空白文本。需要日志或纯文本回退时会用它。

数据流:输入是内容项切片 → 过滤出非空 InputText → 用换行拼接;没有文本则返回 None。

调用关系:FunctionCallOutputBody::to_text、日志预览和调用处理逻辑会用它获得纯文本版本。

调用图:被 9 处调用(into_text, log_preview, handle_call, handle_call, handle_call, expect_text_output, to_text, function_call_output_content_items_to_text_ignores_blank_text_and_images, function_call_output_content_items_to_text_joins_text_segments);外部调用 1 个(iter)。

FunctionCallOutputContentItem::from1688–1700 ↗
fn from(item: crate::dynamic_tools::DynamicToolCallOutputContentItem) -> Self

作用:把动态工具输出内容项转换成协议里的工具输出内容项。图片会带上默认清晰度。

数据流:输入是动态工具的文本或图片项 → 文本原样转 InputText,图片转 InputImage 并设置默认 detail → 返回 FunctionCallOutputContentItem。

调用关系:动态工具系统把输出交给通用协议层时会用到这个转换。

FunctionCallOutputBody::to_text1727–1732 ↗
fn to_text(&self) -> Option<String>

作用:把功能调用输出正文尽量变成纯文本。纯文本正文直接返回,混合内容则提取其中的文字。

数据流:输入是 FunctionCallOutputBody → Text 克隆字符串返回,ContentItems 调用 function_call_output_content_items_to_text → 返回可选文本。

调用关系:日志、断言和调用处理需要文本形式时会用它。

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

FunctionCallOutputBody::default1736–1738 ↗
fn default() -> Self

作用:提供一个默认的空文本工具输出正文。

数据流:没有输入 → 创建 Text(String::new()) → 返回。

调用关系:当没有显式输出正文时,序列化或结构初始化可使用这个默认值。

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

FunctionCallOutputPayload::from_text1742–1747 ↗
fn from_text(content: String) -> Self

作用:用一段纯文本创建工具调用输出载荷。success 字段不预设,让上层决定是否标成功。

数据流:输入是字符串 → 包成 FunctionCallOutputBody::Text,success 设为 None → 返回 payload。

调用关系:自定义工具输出、历史种子、响应请求构建等场景会用它。

调用图:被 9 处调用(dynamic_tool_call_round_trip_sends_text_content_items_to_model, custom_tool_call_output, record_items_truncates_custom_tool_call_output_content, ensure_call_outputs_present, seed_guardian_parent_history, external_context_pollution_items_exclude_local_tool_calls, to_response_item, azure_responses_request_includes_store_and_reasoning_ids, serializes_success_as_plain_string);外部调用 1 个(Text)。

FunctionCallOutputPayload::from_content_items1749–1754 ↗
fn from_content_items(content_items: Vec<FunctionCallOutputContentItem>) -> Self

作用:用一组文本或图片内容项创建工具调用输出载荷。适合工具返回图片或混合内容时使用。

数据流:输入是内容项列表 → 包成 FunctionCallOutputBody::ContentItems,success 设为 None → 返回 payload。

调用关系:图片输出估算和工具输出序列化测试会用它构造混合内容。

调用图:被 12 处调用(encrypted_function_output_uses_plaintext_byte_estimate, image_data_url_payload_does_not_dominate_custom_tool_call_output_estimate, image_data_url_payload_does_not_dominate_function_call_output_estimate, non_base64_image_urls_are_unchanged, non_image_base64_data_url_is_unchanged, original_detail_images_are_capped_at_max_patch_count, original_detail_images_scale_with_dimensions, original_detail_webp_images_scale_with_dimensions, image_output, to_response_item (+2 more));外部调用 1 个(ContentItems)。

FunctionCallOutputPayload::text_content1756–1761 ↗
fn text_content(&self) -> Option<&str>

作用:如果工具输出是纯文本,就借出这段文本;如果是混合内容,就返回 None。

数据流:输入是 payload → 检查 body 分支 → Text 返回 &str,ContentItems 返回 None。

调用关系:需要只处理纯文本输出的调用方可用它安全判断。

FunctionCallOutputPayload::text_content_mut1763–1768 ↗
fn text_content_mut(&mut self) -> Option<&mut String>

作用:如果工具输出是纯文本,就拿到可修改的字符串引用。混合内容不能用这个接口修改。

数据流:输入是可变 payload → Text 返回 &mut String,ContentItems 返回 None → 调用方可直接改文本。

调用关系:输出截断、清洗或追加说明时可用它处理纯文本载荷。

FunctionCallOutputPayload::content_items1770–1775 ↗
fn content_items(&self) -> Option<&[FunctionCallOutputContentItem]>

作用:如果工具输出是内容项列表,就借出这些内容项;纯文本输出则返回 None。

数据流:输入是 payload → ContentItems 返回切片,Text 返回 None。

调用关系:output_image_urls 等函数用它从工具输出里找图片。

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

FunctionCallOutputPayload::content_items_mut1777–1782 ↗
fn content_items_mut(&mut self) -> Option<&mut Vec<FunctionCallOutputContentItem>>

作用:如果工具输出是内容项列表,就拿到可修改列表。纯文本输出不能通过这里修改。

数据流:输入是可变 payload → ContentItems 返回可变 Vec 引用,Text 返回 None。

调用关系:需要调整图片或内容项的后处理代码可使用它。

FunctionCallOutputPayload::serialize1789–1797 ↗
fn serialize(&self, serializer: S) -> Result<S::Ok, S::Error>

作用:把工具调用输出写成 JSON 时,保持兼容:纯文本写成字符串,混合内容写成数组。

数据流:输入是 payload 和序列化器 → Text 调 serialize_str,ContentItems 调列表序列化 → 输出 JSON 序列化结果。

调用关系:响应输入项序列化时自动调用它,保证外部看到简单直观的 output 字段。

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

FunctionCallOutputPayload::deserialize1801–1810 ↗
fn deserialize(deserializer: D) -> Result<Self, D::Error>

作用:从 JSON 读取工具调用输出载荷,并把 success 初始化为空。

数据流:输入是反序列化器 → 先读成 FunctionCallOutputBody → 包成 FunctionCallOutputPayload,success 为 None → 返回。

调用关系:它和 serialize 配套,支持字符串或内容项数组两种 wire 格式。

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

CallToolResult::from_result1814–1819 ↗
fn from_result(result: Result<Self, String>) -> Self

作用:把 Result 形式的工具调用结果变成统一的 CallToolResult。失败字符串会被包装成错误结果。

数据流:输入是 Result<CallToolResult, String> → Ok 直接取出,Err 调 from_error_text → 返回 CallToolResult。

调用关系:工具执行层可以用它把 Rust 的成功/失败结果转成协议对象。

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

CallToolResult::from_error_text1821–1831 ↗
fn from_error_text(text: String) -> Self

作用:用一段错误文字创建标准工具错误结果。内容里会放一个 text 类型 JSON,并标记 is_error 为 true。

数据流:输入是错误字符串 → 构造 content JSON 数组,structured_content 和 meta 为空,is_error 为 Some(true) → 返回 CallToolResult。

调用关系:from_result 和工具错误处理会调用它。

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

CallToolResult::success1833–1835 ↗
fn success(&self) -> bool

作用:判断工具结果是否成功。只有 is_error 明确等于 true 才算失败。

数据流:输入是 CallToolResult → 检查 is_error != Some(true) → 返回布尔值。

调用关系:as_function_call_output_payload 用它把成功状态写进输出载荷。

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

CallToolResult::as_function_call_output_payload1837–1878 ↗
fn as_function_call_output_payload(&self) -> FunctionCallOutputPayload

作用:把 MCP 工具结果转换成模型能接收的功能调用输出。它优先使用结构化内容,其次尝试把文本和图片转换成内容项。

数据流:输入是 CallToolResult → 若 structured_content 非空就序列化成文本;否则序列化 content,并尝试 convert_mcp_content_to_items;有图片时输出内容项,否则输出 JSON 文本 → success 带上成功状态。

调用关系:into_function_call_output_payload 调用它,ResponseItem::from 处理 MCP 工具输出时也间接依赖它。

调用图:调用 2 个内部函数(success, convert_mcp_content_to_items);被 1 处调用(into_function_call_output_payload);外部调用 3 个(ContentItems, Text, to_string)。

CallToolResult::into_function_call_output_payload1880–1882 ↗
fn into_function_call_output_payload(self) -> FunctionCallOutputPayload

作用:消费一个 CallToolResult,并转换成功能调用输出载荷。当前实现直接借用自身转换逻辑。

数据流:输入是 CallToolResult 本身 → 调用 as_function_call_output_payload → 返回 FunctionCallOutputPayload。

调用关系:ResponseInputItem::McpToolCallOutput 转内部响应项时会用它。

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

convert_mcp_content_to_items1885–1950 ↗
fn convert_mcp_content_to_items(
    contents: &[serde_json::Value],
) -> Option<Vec<FunctionCallOutputContentItem>>

作用:把 MCP 内容数组尽量转换成协议里的文本和图片内容项。只有看到图片时才返回内容项列表,否则让调用方继续用纯 JSON 文本。

数据流:输入是 JSON 内容数组 → 每项按 type 解析成 text、image 或 unknown;图片 data 没有 data: 前缀时补成 data URL,并读取图片 detail 元数据 → 如果至少有图片则返回列表,否则返回 None。

调用关系:CallToolResult::as_function_call_output_payload 用它把工具返回的图片保留下来,而不是压成普通字符串。

调用图:被 3 处调用(as_function_call_output_payload, convert_mcp_content_to_items_builds_data_urls_when_missing_prefix, convert_mcp_content_to_items_preserves_data_urls);外部调用 4 个(len, with_capacity, format!, to_string)。

FunctionCallOutputPayload::fmt1957–1965 ↗
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result

作用:定义工具调用输出载荷显示成字符串时的样子。纯文本直接显示,内容项列表显示成 JSON 字符串。

数据流:输入是 payload 和格式化器 → Text 写入原文本;ContentItems 先转 JSON 字符串再写入 → 返回格式化结果。

调用关系:日志、调试输出或任何 Display 使用场景都会经过它。

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

tests::plaintext_agent_message_content_rejects_mixed_encrypted_content1988–1999 ↗
fn plaintext_agent_message_content_rejects_mixed_encrypted_content()

作用:测试纯文本提取遇到加密内容时会拒绝返回。它防止加密内容被误当作普通文本拼进去。

数据流:输入是一个含文本和加密片段的测试列表 → 调 plaintext_agent_message_content → 断言结果是 None。

调用关系:它覆盖 plaintext_agent_message_content 的安全边界。

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

tests::response_input_message_conversion_preserves_phase2002–2023 ↗
fn response_input_message_conversion_preserves_phase()

作用:测试 ResponseInputItem 转 ResponseItem 时不会丢掉消息阶段 phase。

数据流:构造一条带 Commentary 阶段的输入消息 → 调 ResponseItem::from → 断言输出消息仍带相同 phase。

调用关系:它保护 ResponseItem::from 的字段保留行为。

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

tests::response_item_metadata_round_trips_and_stamps_turn_ids2026–2058 ↗
fn response_item_metadata_round_trips_and_stamps_turn_ids() -> Result<()>

作用:测试响应条目的元数据能序列化往返,并且补回合编号时遵守不覆盖已有值的规则。

数据流:构造带元数据的消息 → JSON 往返后比较;再测试未知字段被忽略、已有 turn_id 不被覆盖、空或缺失 turn_id 可被补上、Other 不可补 → 返回测试结果。

调用关系:它覆盖 ResponseItem 的 metadata、turn_id 和 stamp_turn_id_if_missing 行为。

调用图:外部调用 6 个(assert_eq!, response_item_metadata, response_item_with_metadata, from_value, json!, to_value)。

tests::response_item_with_metadata2060–2070 ↗
fn response_item_with_metadata(metadata: Option<ResponseItemMetadata>) -> ResponseItem

作用:为测试创建一条带可选元数据的用户消息。它减少重复构造代码。

数据流:输入是可选 ResponseItemMetadata → 构造 role 为 user、内容为 hello 的 ResponseItem::Message → 返回。

调用关系:response_item_metadata_round_trips_and_stamps_turn_ids 调用它准备样本。

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

tests::response_item_metadata2072–2076 ↗
fn response_item_metadata(turn_id: &str) -> ResponseItemMetadata

作用:为测试创建只包含 turn_id 的元数据对象。

数据流:输入是 turn_id 字符串 → 转成 Some(String) 放入 ResponseItemMetadata → 返回。

调用关系:元数据往返测试用它生成不同 turn_id 的样本。

tests::image_detail_roundtrips_all_wire_values2079–2106 ↗
fn image_detail_roundtrips_all_wire_values() -> Result<()>

作用:测试图片清晰度 detail 的 JSON 值能正确读写,尤其是 auto 和 low。

数据流:输入是几个 JSON 字符串和图片内容 JSON → 反序列化、序列化并比较预期枚举和值 → 成功返回 Ok。

调用关系:它保护 ImageDetail 的协议兼容性。

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

tests::sandbox_permissions_helpers_match_documented_semantics2109–2141 ↗
fn sandbox_permissions_helpers_match_documented_semantics()

作用:测试 SandboxPermissions 三个辅助判断和文档语义一致。

数据流:准备三个权限枚举及预期布尔值 → 逐个调用 requires_escalated_permissions、requests_sandbox_override、uses_additional_permissions → 断言结果相等。

调用关系:它覆盖 SandboxPermissions 的三个小判断函数。

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

tests::convert_mcp_content_to_items_preserves_data_urls2144–2159 ↗
fn convert_mcp_content_to_items_preserves_data_urls()

作用:测试 MCP 图片内容如果已经是 data URL,就不会被重复包装。

数据流:构造带 data:image/png;base64 前缀的图片 JSON → 调 convert_mcp_content_to_items → 断言输出图片 URL 原样保留。

调用关系:它保护 convert_mcp_content_to_items 对已有 data URL 的处理。

调用图:调用 1 个内部函数(convert_mcp_content_to_items);外部调用 2 个(assert_eq!, vec!)。

tests::response_item_parses_image_generation_call2162–2182 ↗
fn response_item_parses_image_generation_call()

作用:测试图片生成调用响应项能从 JSON 正确解析,包括 revised_prompt。

数据流:输入一段 image_generation_call JSON → 反序列化成 ResponseItem → 断言各字段符合预期。

调用关系:它覆盖 ResponseItem 里图片生成调用分支的反序列化。

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

tests::response_item_parses_image_generation_call_without_revised_prompt2185–2204 ↗
fn response_item_parses_image_generation_call_without_revised_prompt()

作用:测试图片生成调用没有 revised_prompt 时也能解析成功。

数据流:输入缺少 revised_prompt 的 JSON → 反序列化 → 断言 revised_prompt 为 None 且其他字段正确。

调用关系:它保证可选字段缺失不会导致 ResponseItem 解析失败。

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

tests::additional_permission_profile_is_empty_when_all_fields_are_none2207–2209 ↗
fn additional_permission_profile_is_empty_when_all_fields_are_none()

作用:测试额外权限配置在网络和文件系统都没写时被认为是空的。

数据流:创建默认 AdditionalPermissionProfile → 调 is_empty → 断言为 true。

调用关系:它覆盖 AdditionalPermissionProfile::is_empty 的全空情况。

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

tests::additional_permission_profile_is_not_empty_when_field_is_present_but_nested_empty2212–2218 ↗
fn additional_permission_profile_is_not_empty_when_field_is_present_but_nested_empty()

作用:测试只要字段存在,即使里面没有具体值,也不算完全空配置。

数据流:构造 network 为 Some 但 enabled 为 None 的配置 → 调 is_empty → 断言为 false。

调用关系:它保护 AdditionalPermissionProfile::is_empty 对“字段存在”的语义。

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

tests::permission_profile_round_trip_preserves_glob_scan_max_depth2221–2240 ↗
fn permission_profile_round_trip_preserves_glob_scan_max_depth()

作用:测试权限档案转换不会丢掉通配符扫描最大深度。

数据流:构造带 Deny 通配符和 glob_scan_max_depth 的文件沙箱策略 → 转成 PermissionProfile → 再取回文件策略并比较。

调用关系:它覆盖 PermissionProfile::from_runtime_permissions 和 file_system_sandbox_policy 的往返。

调用图:调用 2 个内部函数(from_runtime_permissions, restricted);外部调用 2 个(assert_eq!, vec!)。

tests::permission_profile_deserializes_legacy_rollout_shape2243–2280 ↗
fn permission_profile_deserializes_legacy_rollout_shape() -> Result<()>

作用:测试旧版 rollout 形状的权限档案 JSON 还能被新版读取。

数据流:输入旧 JSON,包含 network 和 file_system → 反序列化成 PermissionProfile → 断言变成预期 Managed 档案。

调用关系:它覆盖 PermissionProfile::deserialize 和 LegacyPermissionProfile 转换。

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

tests::permission_profile_presets_match_legacy_defaults2283–2294 ↗
fn permission_profile_presets_match_legacy_defaults()

作用:测试新版内置只读和工作区可写档案,与旧默认沙箱策略转换结果一致。

数据流:分别生成 PermissionProfile::read_only/workspace_write 和旧策略转换结果 → 断言相等。

调用关系:它保护内置权限预设的向后兼容性。

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

tests::permission_profile_round_trip_preserves_disabled_sandbox2297–2315 ↗
fn permission_profile_round_trip_preserves_disabled_sandbox() -> Result<()>

作用:测试完全关闭沙箱的旧策略转换到新档案后,再转回旧策略和运行时权限都保持语义。

数据流:输入 DangerFullAccess → 转 PermissionProfile → 断言为 Disabled;再转旧策略和运行时权限并比较。

调用关系:它覆盖 PermissionProfile::from_legacy_sandbox_policy、to_legacy_sandbox_policy 和 to_runtime_permissions。

调用图:调用 1 个内部函数(from_legacy_sandbox_policy);外部调用 2 个(assert_eq!, tempdir)。

tests::disabled_permission_profile_ignores_runtime_network_policy2318–2326 ↗
fn disabled_permission_profile_ignores_runtime_network_policy()

作用:测试当沙箱执行方式是 Disabled 时,即使传入受限网络,最终也表示为 Disabled 档案。

数据流:输入 Disabled enforcement、不受限文件策略和受限网络 → 调 from_runtime_permissions_with_enforcement → 断言结果为 Disabled。

调用关系:它保护 PermissionProfile::from_runtime_permissions_with_enforcement 对关闭沙箱的特殊处理。

调用图:调用 2 个内部函数(from_runtime_permissions_with_enforcement, unrestricted);外部调用 1 个(assert_eq!)。

tests::permission_profile_from_runtime_permissions_preserves_external_sandbox2329–2349 ↗
fn permission_profile_from_runtime_permissions_preserves_external_sandbox()

作用:测试外部沙箱文件策略会被保存为 External 权限档案,并保留网络策略。

数据流:输入 external_sandbox 文件策略和受限网络 → 转 PermissionProfile → 断言为 External;再用显式 enforcement 转换并比较。

调用关系:它覆盖 PermissionProfile::from_runtime_permissions 对外部沙箱的处理。

调用图:调用 2 个内部函数(from_runtime_permissions, external_sandbox);外部调用 1 个(assert_eq!)。

tests::permission_profile_from_runtime_permissions_preserves_unrestricted_managed_network2352–2374 ↗
fn permission_profile_from_runtime_permissions_preserves_unrestricted_managed_network()

作用:测试“不限制文件但网络受限”的组合不会被误折叠成旧式外部沙箱含义。

数据流:输入 External enforcement、不受限文件策略和受限网络 → 转 PermissionProfile → 断言是 Managed 且文件 Unrestricted、网络 Restricted,并检查运行时权限往返。

调用关系:它保护 from_runtime_permissions_with_enforcement 对拆分文件/网络权限的精确表达。

调用图:调用 2 个内部函数(from_runtime_permissions_with_enforcement, unrestricted);外部调用 1 个(assert_eq!)。

tests::permission_profile_round_trip_preserves_external_sandbox2377–2402 ↗
fn permission_profile_round_trip_preserves_external_sandbox() -> Result<()>

作用:测试旧的 ExternalSandbox 策略转换到新档案后,转回旧策略和运行时权限都保持外部沙箱语义。

数据流:构造旧 ExternalSandbox 且网络受限 → 转 PermissionProfile → 断言为 External;再转旧策略和运行时权限比较。

调用关系:它覆盖外部沙箱兼容转换链路。

调用图:调用 1 个内部函数(from_legacy_sandbox_policy);外部调用 2 个(assert_eq!, tempdir)。

tests::file_system_permissions_with_glob_scan_depth_uses_canonical_json2405–2434 ↗
fn file_system_permissions_with_glob_scan_depth_uses_canonical_json() -> Result<()>

作用:测试带 glob_scan_max_depth 的文件权限必须使用新 canonical JSON,而不是旧 read/write JSON。

数据流:构造带普通读路径和扫描深度的 FileSystemPermissions → 序列化成 JSON → 断言没有 read/write、有 entries 和 glob_scan_max_depth,并能反序列化回来。

调用关系:它覆盖 FileSystemPermissions::serialize/deserialize 对新旧格式的选择。

调用图:调用 1 个内部函数(try_from);外部调用 7 个(new, from, assert!, assert_eq!, cfg!, to_value, vec!)。

tests::file_system_permissions_rejects_zero_glob_scan_depth2437–2443 ↗
fn file_system_permissions_rejects_zero_glob_scan_depth()

作用:测试 glob_scan_max_depth 不能为 0。0 没有实际意义,所以反序列化应失败。

数据流:输入 glob_scan_max_depth 为 0 的 JSON → 尝试反序列化 FileSystemPermissions → 断言得到错误。

调用关系:它保护文件权限模型中 NonZeroUsize 的约束。

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

tests::convert_mcp_content_to_items_builds_data_urls_when_missing_prefix2446–2461 ↗
fn convert_mcp_content_to_items_builds_data_urls_when_missing_prefix()

作用:测试 MCP 图片只给 base64 数据时,系统会补成完整 data URL。

数据流:构造 data 为 Zm9v、mimeType 为 image/png 的图片 JSON → 调 convert_mcp_content_to_items → 断言输出 image_url 带 data:image/png;base64 前缀。

调用关系:它覆盖 convert_mcp_content_to_items 的图片 URL 补全逻辑。

调用图:调用 1 个内部函数(convert_mcp_content_to_items);外部调用 2 个(assert_eq!, vec!)。

tests::convert_mcp_content_to_items_returns_none_without_images2464–2471 ↗
fn convert_mcp_content_to_items_returns_none_without_images()

作用:测试 MCP 内容只有文本时不会转换成内容项列表,而是让调用方保留 JSON 文本路径。

数据流:输入一个 text 类型 JSON → 调 convert_mcp_content_to_items → 断言返回 None。

调用关系:它保护 convert_mcp_content_to_items 的“只有图片才启用内容项输出”规则。

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

tests::function_call_output_content_items_to_text_joins_text_segments2474–2490 ↗
fn function_call_output_content_items_to_text_joins_text_segments()

作用:测试从混合内容项提取文本时,会跳过图片并用换行连接文字。

数据流:构造文本、图片、文本三个内容项 → 调 function_call_output_content_items_to_text → 断言得到两行文本。

调用关系:它覆盖工具输出文本提取的正常混合内容场景。

调用图:调用 1 个内部函数(function_call_output_content_items_to_text);外部调用 2 个(assert_eq!, vec!)。

tests::function_call_output_content_items_to_text_ignores_blank_text_and_images2493–2509 ↗
fn function_call_output_content_items_to_text_ignores_blank_text_and_images()

作用:测试空白文本、图片和加密内容都不会产生纯文本输出。

数据流:构造空白文本、图片和加密内容项 → 调 function_call_output_content_items_to_text → 断言返回 None。

调用关系:它保护文本提取函数不会把无意义或不可读内容当成文本。

调用图:调用 1 个内部函数(function_call_output_content_items_to_text);外部调用 2 个(assert_eq!, vec!)。

tests::function_call_output_body_to_text_returns_plain_text_content2512–2516 ↗
fn function_call_output_body_to_text_returns_plain_text_content()

作用:测试纯文本工具输出正文调用 to_text 会直接返回文本。

数据流:构造 FunctionCallOutputBody::Text("ok") → 调 to_text → 断言返回 Some("ok")。

调用关系:它覆盖 FunctionCallOutputBody::to_text 的 Text 分支。

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

tests::function_call_output_body_to_text_uses_content_item_fallback2519–2532 ↗
fn function_call_output_body_to_text_uses_content_item_fallback()

作用:测试内容项形式的工具输出也能通过 to_text 提取其中的文字。

数据流:构造包含文本和图片的 ContentItems 正文 → 调 to_text → 断言只返回文字。

调用关系:它覆盖 FunctionCallOutputBody::to_text 的 ContentItems 分支。

调用图:外部调用 3 个(assert_eq!, ContentItems, vec!)。

tests::function_call_deserializes_optional_namespace2535–2556 ↗
fn function_call_deserializes_optional_namespace()

作用:测试 function_call JSON 中可选的 namespace 字段能被正确读取。

数据流:输入带 namespace 的 function_call JSON → 反序列化为 ResponseItem → 断言 namespace 和其他字段正确。

调用关系:它保护 ResponseItem::FunctionCall 的协议字段兼容性。

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

tests::render_command_prefix_list_sorts_by_len_then_total_len_then_alphabetical2559–2580 ↗
fn render_command_prefix_list_sorts_by_len_then_total_len_then_alphabetical()

作用:测试允许命令前缀展示时的排序规则:先按 token 数,再按总长度,最后按字母顺序。

数据流:输入多组乱序前缀 → 调 format_allow_prefixes → 断言输出顺序符合规则。

调用关系:它覆盖 format_allow_prefixes、prefix_combined_str_len 和 render_command_prefix 的组合效果。

调用图:调用 1 个内部函数(format_allow_prefixes);外部调用 2 个(assert_eq!, vec!)。

tests::render_command_prefix_list_limits_output_to_max_prefixes2583–2592 ↗
fn render_command_prefix_list_limits_output_to_max_prefixes()

作用:测试前缀太多时,展示内容会限制条数并加截断标记。

数据流:生成超过 MAX_RENDERED_PREFIXES 的前缀 → 调 format_allow_prefixes → 断言末尾有截断标记且行数符合预期。

调用关系:它保护 format_allow_prefixes 的数量上限。

调用图:调用 1 个内部函数(format_allow_prefixes);外部调用 2 个(assert_eq!, eprintln!)。

tests::format_allow_prefixes_limits_output2595–2612 ↗
fn format_allow_prefixes_limits_output()

作用:测试允许前缀展示文本不会超过最大字节限制加截断标记长度。

数据流:构造很多很长的允许前缀规则 → 获取前缀并格式化 → 断言输出长度在限制内。

调用关系:它覆盖 format_allow_prefixes 的字节级截断逻辑,避免界面或消息被撑爆。

调用图:调用 1 个内部函数(format_allow_prefixes);外部调用 3 个(assert!, empty, format!)。

tests::serializes_success_as_plain_string2615–2627 ↗
fn serializes_success_as_plain_string() -> Result<()>

作用:测试成功的功能调用输出序列化时,output 字段是普通字符串。

数据流:构造 FunctionCallOutputPayload::from_text("ok") 的输出项 → 序列化再读成 JSON 值 → 断言 output 是字符串 ok。

调用关系:它保护 FunctionCallOutputPayload::serialize 的纯文本兼容格式。

调用图:调用 1 个内部函数(from_text);外部调用 3 个(assert_eq!, from_str, to_string)。

tests::serializes_failure_as_string2630–2644 ↗
fn serializes_failure_as_string() -> Result<()>

作用:测试失败的功能调用输出即使带 success=false,output 本身仍序列化成字符串内容。

数据流:构造 body 为 Text("bad")、success 为 Some(false) 的输出项 → 序列化成 JSON → 断言 output 是字符串 bad。

调用关系:它确保失败状态不会改变 output 字段的 wire 形状。

调用图:外部调用 4 个(assert_eq!, Text, from_str, to_string)。

tests::serializes_image_outputs_as_array2647–2689 ↗
fn serializes_image_outputs_as_array() -> Result<()>

作用:这个测试确认:工具返回文字加图片时,最终给模型看的输出必须是一个数组,而不是一整段普通字符串。这样模型才能分别识别“这是文字”“这是图片”。

数据流:进去的是一个模拟的工具结果,里面有一条文字和一张 base64 图片 → 测试把它转换成函数调用输出,再序列化成 JSON → 出来应当是 success 为真、内容里有文字项和图片项,并且 JSON 的 output 字段是数组。

调用关系:测试运行器会调用它;它主要验证 CallToolResult 到 FunctionCallOutputPayload,再到 ResponseInputItem JSON 的整条链路,防止图片工具输出被错误压成字符串。

调用图:外部调用 6 个(assert!, assert_eq!, panic!, from_str, to_string, vec!)。

tests::serializes_custom_tool_image_outputs_as_array2692–2711 ↗
fn serializes_custom_tool_image_outputs_as_array() -> Result<()>

作用:这个测试确认:自定义工具返回图片时,output 也要按数组写进 JSON。它防的是自定义工具和普通函数工具格式不一致。

数据流:进去的是一个自定义工具输出项,里面只有一张图片 → 测试用 from_content_items 组成输出载荷,再转成 JSON → 出来的 output 字段必须是数组。

调用关系:测试运行器会调用它;它补充验证 CustomToolCallOutput 这条路径,确保它和普通 FunctionCallOutput 用同一套图片输出规则。

调用图:调用 1 个内部函数(from_content_items);外部调用 4 个(assert!, from_str, to_string, vec!)。

tests::serializes_encrypted_function_output_content_as_array2714–2740 ↗
fn serializes_encrypted_function_output_content_as_array() -> Result<()>

作用:这个测试确认:加密内容作为函数输出时,也要放在数组里,并保留 encrypted_content 字段。加密内容是不透明的,程序不能擅自改它。

数据流:进去的是一个带 encrypted_content 的输出项 → 测试把它包成函数调用输出并转成 JSON 值 → 出来应当正好是 type 为 function_call_output、call_id 正确、output 为加密内容数组的 JSON。

调用关系:测试运行器会调用它;它验证 FunctionCallOutputContentItem::EncryptedContent 的序列化规则,防止加密载荷被包错或字段名被改坏。

调用图:调用 1 个内部函数(from_content_items);外部调用 3 个(assert_eq!, to_value, vec!)。

tests::preserves_existing_image_data_urls2743–2769 ↗
fn preserves_existing_image_data_urls() -> Result<()>

作用:这个测试确认:如果工具返回的图片数据本来已经是完整的 data URL,就不要再给它套一层前缀。否则图片地址会坏掉。

数据流:进去的是一个图片工具结果,data 字段已经是 data:image/png;base64,... → 转成函数输出载荷 → 出来的图片 URL 应保持原样,并带默认图片清晰度。

调用关系:测试运行器会调用它;它盯住 CallToolResult 转换图片时的特殊分支,确保已有的图片地址不会被重复加工。

调用图:外部调用 3 个(assert_eq!, panic!, vec!)。

tests::preserves_original_detail_metadata_on_mcp_images2772–2801 ↗
fn preserves_original_detail_metadata_on_mcp_images() -> Result<()>

作用:这个测试确认:MCP 工具返回图片时,如果元数据说要原始清晰度,就要照做。MCP 可以理解成外部工具接入协议,元数据是工具附带的小纸条。

数据流:进去的是一个图片工具结果,_meta 里写着 codex/imageDetail 为 original → 转成函数输出载荷 → 出来的图片项应使用 data URL,并把 detail 设成 Original。

调用关系:测试运行器会调用它;它验证 MCP 图片元数据到 ImageDetail 的转换,避免用户或工具指定的图片质量被默认值覆盖。

调用图:外部调用 3 个(assert_eq!, panic!, vec!)。

tests::preserves_standard_detail_metadata_on_mcp_images2804–2833 ↗
fn preserves_standard_detail_metadata_on_mcp_images() -> Result<()>

作用:这个测试确认:MCP 图片元数据里写 high 时,图片清晰度会保留为 high。它保证标准清晰度选项不会在转换时丢失。

数据流:进去的是一个带 _meta.codex/imageDetail=high 的图片结果 → 转换成函数输出载荷 → 出来的图片项应是 png 的 data URL,detail 应为 High。

调用关系:测试运行器会调用它;它和 original 清晰度测试一起覆盖图片元数据解析,确保不同清晰度值都能被正确识别。

调用图:外部调用 3 个(assert_eq!, panic!, vec!)。

tests::deserializes_array_payload_into_items2836–2864 ↗
fn deserializes_array_payload_into_items() -> Result<()>

作用:这个测试确认:当 JSON 本身就是一个输出项数组时,程序能把它读成内部的内容列表。也就是说,外面发来的数组格式不会被误读。

数据流:进去的是一段 JSON 数组,包含 input_text 和 input_image → 反序列化成 FunctionCallOutputPayload → 出来应当没有 success 标记,body 是对应的内容项列表,再转回 JSON 时仍是同样的数组。

调用关系:测试运行器会调用它;它验证 FunctionCallOutputPayload 的读取和写回规则,保证数组载荷可以稳定来回转换。

调用图:外部调用 3 个(assert_eq!, from_str, vec!)。

tests::deserializes_encrypted_array_payload_into_items2867–2888 ↗
fn deserializes_encrypted_array_payload_into_items() -> Result<()>

作用:这个测试确认:加密内容数组也能被读成内部内容项,并能原样写回。这样旧数据或远端返回的加密片段不会被破坏。

数据流:进去的是只有 encrypted_content 的 JSON 数组 → 反序列化成 FunctionCallOutputPayload → 出来应当是 ContentItems 形式,success 为空,再序列化时保持为同样的数组。

调用关系:测试运行器会调用它;它补齐加密内容在数组载荷里的反序列化路径,和普通文字、图片数组测试配套。

调用图:外部调用 3 个(assert_eq!, from_str, vec!)。

tests::deserializes_compaction_alias2891–2904 ↗
fn deserializes_compaction_alias() -> Result<()>

作用:这个测试确认:旧名字 compaction_summary 还能被读成现在的 Compaction。这样历史记录或旧版本产生的数据不会突然打不开。

数据流:进去的是 type 为 compaction_summary、带 encrypted_content 的 JSON → 读成 ResponseItem → 出来应当是 ResponseItem::Compaction,里面保存 abc,metadata 为空。

调用关系:测试运行器会调用它;它验证 ResponseItem 的兼容别名逻辑,让新代码能接住旧协议名字。

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

tests::deserializes_context_compaction2907–2920 ↗
fn deserializes_context_compaction() -> Result<()>

作用:这个测试确认:context_compaction 类型能被正确读出来。它表示一次上下文压缩信息,也就是把长对话整理成更短内容。

数据流:进去的是 type 为 context_compaction 的 JSON → 反序列化成 ResponseItem → 出来应当是 ContextCompaction,并保留 encrypted_content。

调用关系:测试运行器会调用它;它验证 ResponseItem 对上下文压缩消息的识别,确保相关记录不会被当成未知类型。

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

tests::serializes_compaction_trigger_without_payload2923–2933 ↗
fn serializes_compaction_trigger_without_payload() -> Result<()>

作用:这个测试确认:压缩触发信号即使没有额外内容,也能写成合法 JSON。它像一个“该整理上下文了”的空铃声。

数据流:进去的是 metadata 为空的 CompactionTrigger → 转成 JSON 值 → 出来应当只有 type: compaction_trigger,没有多余空字段。

调用关系:测试运行器会调用它;它检查 ResponseItem::CompactionTrigger 的最小输出格式,避免无意义的 null 字段影响协议。

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

tests::serializes_stamped_compaction_trigger_metadata2936–2950 ↗
fn serializes_stamped_compaction_trigger_metadata() -> Result<()>

作用:这个测试确认:压缩触发信号可以补上 turn_id,也就是标记它属于哪一轮对话。这样后续追踪时知道信号从哪里来。

数据流:进去的是一个没有 metadata 的 CompactionTrigger → 调用 stamp_turn_id_if_missing 写入 turn-1 → 转成 JSON 后应带 metadata.turn_id。

调用关系:测试运行器会调用它;它验证 ResponseItem 上给元数据盖章的行为,确保压缩触发消息能被关联到具体回合。

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

tests::deserializes_compaction_trigger_without_payload2953–2960 ↗
fn deserializes_compaction_trigger_without_payload() -> Result<()>

作用:这个测试确认:只有 type、没有额外字段的 compaction_trigger 也能被读回来。它保证最简消息格式双向可用。

数据流:进去的是 {"type":"compaction_trigger"} 这段 JSON → 反序列化成 ResponseItem → 出来应当是 metadata 为空的 CompactionTrigger。

调用关系:测试运行器会调用它;它和序列化测试成对出现,保证压缩触发信号发出去和收回来都没问题。

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

tests::deserializes_legacy_ghost_snapshot_as_other2963–2978 ↗
fn deserializes_legacy_ghost_snapshot_as_other() -> Result<()>

作用:这个测试确认:老版本的 ghost_snapshot 消息现在会被当成 Other,而不是让解析失败。这样遇到废弃消息时系统能平稳跳过。

数据流:进去的是一段旧 ghost_snapshot JSON,里面有 ghost_commit 信息 → 反序列化成 ResponseItem → 出来应当是 ResponseItem::Other。

调用关系:测试运行器会调用它;它验证旧协议兼容策略:不认识或不再使用的历史类型,不崩溃,只归为其他。

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

tests::roundtrips_web_search_call_actions2981–3068 ↗
fn roundtrips_web_search_call_actions() -> Result<()>

作用:这个测试确认:网页搜索动作的几种形态都能正确读写,包括搜索、打开网页、在网页里查找。roundtrip 的意思是“读进来再写出去还一样”。

数据流:进去是一组 web_search_call JSON 样例 → 每个样例先读成 ResponseItem,再转回 JSON → 出来应匹配预期的 action、status 和 id;对于不该原样保留 id 的部分样例,也按规则去掉 id 后比较。

调用关系:测试运行器会调用它;它覆盖 WebSearchAction 的多个分支,确保 ResponseItem::WebSearchCall 在不同搜索状态下都能稳定通信。

调用图:外部调用 4 个(assert_eq!, from_str, to_value, vec!)。

tests::serializes_image_user_input_without_tags3071–3091 ↗
fn serializes_image_user_input_without_tags() -> Result<()>

作用:这个测试确认:用户直接输入远程图片时,不会额外加文字标签,只会变成一条图片内容。没有指定清晰度时,用默认清晰度。

数据流:进去的是一个 UserInput::Image,只有 image_url、没有 detail → 转成 ResponseInputItem → 出来应当是 Message,内容里只有 InputImage,并带默认 detail。

调用关系:测试运行器会调用它;它验证 UserInput 到 ResponseInputItem 的转换,特别是远程图片和本地图片不同:远程图片不需要包裹说明标签。

调用图:调用 1 个内部函数(from);外部调用 3 个(assert_eq!, panic!, vec!)。

tests::image_user_input_preserves_requested_detail3094–3116 ↗
fn image_user_input_preserves_requested_detail() -> Result<()>

作用:这个测试确认:用户给远程图片指定原始清晰度时,转换后不会被改成默认值。用户的明确要求优先。

数据流:进去的是一个带 ImageDetail::Original 的 UserInput::Image → 转成 ResponseInputItem → 出来的 Message 第一项应是同一个 image_url,并保留 Original detail。

调用关系:测试运行器会调用它;它紧跟默认清晰度测试,验证图片输入转换时既会补默认值,也会尊重用户指定值。

调用图:调用 1 个内部函数(from);外部调用 3 个(assert_eq!, panic!, vec!)。

tests::tool_search_call_roundtrips3119–3161 ↗
fn tool_search_call_roundtrips() -> Result<()>

作用:这个测试确认:工具搜索请求能从 JSON 读出来,也能再写回 JSON。工具搜索就是先查有哪些工具可用,而不是马上执行某个工具。

数据流:进去的是一段 tool_search_call JSON,包含 call_id、执行位置和查询参数 → 读成 ResponseItem::ToolSearchCall → 再转成 JSON,输出应保留原来的关键字段和值。

调用关系:测试运行器会调用它;它验证 ResponseItem 对工具搜索调用的协议支持,确保客户端发来的搜索请求不会丢参数。

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

tests::tool_search_output_roundtrips3164–3233 ↗
fn tool_search_output_roundtrips() -> Result<()>

作用:这个测试确认:工具搜索结果能在输入项和响应项之间转换,并能写成正确 JSON。结果里包含找到的工具说明。

数据流:进去的是 ResponseInputItem::ToolSearchOutput,里面有 call_id、状态、执行位置和一个工具定义 → 转成 ResponseItem 后应字段一致 → 序列化成 JSON 后也应完整保留工具列表。

调用关系:测试运行器会调用它;它验证工具搜索“返回结果”这条链路,连接 ResponseInputItem、ResponseItem 和 JSON 输出格式。

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

tests::tool_search_server_items_allow_null_call_id3236–3283 ↗
fn tool_search_server_items_allow_null_call_id() -> Result<()>

作用:这个测试确认:服务端执行的工具搜索可以没有 call_id,也就是 JSON 里 call_id 为 null 时不算错误。这样服务端内部搜索不必伪造一个调用编号。

数据流:进去的是两段 JSON:一个 tool_search_call、一个 tool_search_output,call_id 都是 null → 分别读成 ResponseItem → 出来应当 call_id 为 None,其他状态和参数保留。

调用关系:测试运行器会调用它;它验证工具搜索协议对服务端场景更宽松的规则,避免因为没有 call_id 而拒绝合法消息。

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

tests::mixed_remote_and_local_images_share_label_sequence3286–3336 ↗
fn mixed_remote_and_local_images_share_label_sequence() -> Result<()>

作用:这个测试确认:远程图片和本地图片混在一起时,图片编号按同一套顺序走。本地图片会加文字标签,方便模型知道它对应哪个文件。

数据流:进去的是一个远程图片和一个临时创建的本地 PNG 文件 → 转成 ResponseInputItem::Message → 出来第一项是远程图片,第二项是本地图片的打开标签且编号为 Image #2,后面跟本地图片内容和关闭标签。

调用关系:测试运行器会调用它;它会用临时目录和写文件模拟真实本地图片,验证 UserInput 转换时远程、本地图片共享编号规则。

调用图:调用 1 个内部函数(from);外部调用 6 个(assert!, assert_eq!, panic!, write, tempdir, vec!)。

tests::local_image_open_tag_preserves_path3339–3347 ↗
fn local_image_open_tag_preserves_path()

作用:这个测试确认:本地图片的打开标签会原样保留文件路径,即使路径里有特殊字符。这样提示文字能准确指向用户选择的文件。

数据流:进去的是编号 1 和一个包含特殊字符的路径 → 调用 local_image_open_tag_text_with_path → 出来的字符串应包含 Image #1 和未被改写的原始路径。

调用关系:测试运行器会调用它;它直接检查本地图片标签生成的小工具函数,防止路径被转义或截断导致用户看不懂。

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

tests::local_image_user_input_preserves_requested_detail3350–3374 ↗
fn local_image_user_input_preserves_requested_detail() -> Result<()>

作用:这个测试确认:用户上传本地图片并指定原始清晰度时,读文件并转成图片内容后仍保留这个清晰度要求。

数据流:进去的是一个临时写出的本地 PNG 路径,detail 为 Original → 转成 ResponseInputItem::Message → 出来的图片内容项应带 ImageDetail::Original。

调用关系:测试运行器会调用它;它通过真实临时文件走本地图片读取路径,验证本地图片和远程图片一样尊重用户指定的 detail。

调用图:调用 1 个内部函数(from);外部调用 5 个(assert!, panic!, write, tempdir, vec!)。

tests::local_image_read_error_adds_placeholder3377–3408 ↗
fn local_image_read_error_adds_placeholder() -> Result<()>

作用:这个测试确认:本地图片文件不存在或读不到时,程序不会崩溃,而是放一段说明文字。占位文字会告诉模型和用户哪个路径出了问题。

数据流:进去的是一个不存在的本地图片路径 → 转成 ResponseInputItem → 因为读文件失败,出来的 Message 只有一条 InputText,文字里包含路径和 could not read。

调用关系:测试运行器会调用它;它验证本地图片读取失败时的兜底行为,保证请求还能继续发出,只是用文字说明替代图片。

调用图:调用 1 个内部函数(from);外部调用 5 个(assert!, assert_eq!, panic!, tempdir, vec!)。

tests::local_image_non_image_adds_placeholder3411–3442 ↗
fn local_image_non_image_adds_placeholder() -> Result<()>

作用:这个测试确认:如果用户把非图片文件当图片传入,程序会用占位说明提示“不支持这种图片类型”。比如 JSON 文件不能当图片发给模型。

数据流:进去的是一个临时写出的 example.json 文件路径 → 转成 ResponseInputItem → MIME 类型被识别为 application/json,不是支持的图片,于是出来一条说明文字,包含类型和路径。

调用关系:测试运行器会调用它;它验证本地附件的类型检查,防止把普通文件错误包装成图片内容。

调用图:调用 1 个内部函数(from);外部调用 6 个(assert!, assert_eq!, panic!, write, tempdir, vec!)。

tests::local_image_unsupported_image_format_adds_placeholder3445–3475 ↗
fn local_image_unsupported_image_format_adds_placeholder() -> Result<()>

作用:这个测试确认:即使文件确实是图片类格式,比如 SVG,只要系统不支持,也会给出清楚的占位错误文字。这样用户知道不是文件丢了,而是格式不行。

数据流:进去的是一个临时写出的 SVG 文件路径 → 转成 ResponseInputItem → 因为 image/svg+xml 不受支持,出来的 Message 只有一条固定格式的 InputText,说明该路径的图片无法附加。

调用关系:测试运行器会调用它;它补充覆盖“是图片但格式不支持”的情况,和读不到文件、非图片文件两个测试一起保护本地图片错误处理。

调用图:调用 1 个内部函数(from);外部调用 6 个(assert_eq!, format!, panic!, write, tempdir, vec!)。

protocol/src/account.rs源码 ↗
data_model账号信息读取、认证结果转换、接口返回数据时

这个文件像一张“账号套餐对照表”。它列出用户可能拥有的套餐,比如 Free、Plus、Pro、Team、Business、Enterprise 等,也处理一些特殊的接口名字,比如 self_serve_business_usage_based。因为这些名字会通过 JSON(一种常见的数据交换文本格式)发给前端或其他服务,所以这里明确规定了它们在网络上传输时该怎么写。文件还定义了 ProviderAccount,表示模型提供方返回的账号状态:可能是 API Key,可能是 ChatGPT 账号,也可能是 Amazon Bedrock 凭证。几个小函数用来判断某个套餐是不是团队类、商业类、工作区类账号。最后还有从认证模块里的套餐类型转换成这里套餐类型的规则,这样内部登录系统和对外协议之间不会各说各话。

函数细节8
PlanType::is_team_like55–57 ↗
fn is_team_like(self) -> bool

作用:判断一个套餐是不是“团队类”套餐。有人需要根据套餐决定是否展示团队相关功能时,会用到它。

数据流:进去的是一个 PlanType 套餐值 → 它检查这个值是不是 Team 或 SelfServeBusinessUsageBased → 出来一个 true 或 false,不改动任何数据。

调用关系:它是 PlanType 这个套餐类型上的小判断工具。业务代码拿到账号套餐后,可以调用它来把普通套餐和团队类套餐分开;内部用 matches! 这个 Rust 匹配工具做简单判断。

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

PlanType::is_business_like59–61 ↗
fn is_business_like(self) -> bool

作用:判断一个套餐是不是“商业类”套餐。它帮助代码把 Business 和按量计费的企业商业套餐归到同一组。

数据流:进去的是一个套餐值 → 它看这个值是不是 Business 或 EnterpriseCbpUsageBased → 返回 true 或 false,不产生其他副作用。

调用关系:它和 is_team_like 类似,都是给套餐分类用的便捷入口。后续如果界面或权限规则需要识别商业套餐,就可以直接问它,而不用到处重复写判断条件。

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

PlanType::is_workspace_account63–73 ↗
fn is_workspace_account(self) -> bool

作用:判断这个套餐是否属于“工作区账号”。工作区账号通常不是单纯个人账号,可能涉及团队、企业或教育组织。

数据流:进去的是一个套餐值 → 它把这个值和 Team、Business、Enterprise、Edu 等工作区相关套餐逐一对照 → 返回这个套餐是否算工作区账号。

调用关系:这是更大的分类判断,覆盖团队类、商业类、企业类和教育类套餐。账号展示、权限开关或组织功能判断时,可以用它快速知道用户是否属于某种组织空间。

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

PlanType::from86–100 ↗
fn from(plan: KnownPlan) -> Self

作用:把认证模块里认识的套餐名转换成这个协议文件里的套餐名。这样登录系统说的套餐,可以安全地变成接口对外返回的套餐。

数据流:进去的是认证模块中的 KnownPlan 套餐值 → 它按一一对应关系把 Free、Go、Plus、Pro、Team、Business 等转换成本文件里的 PlanType → 出来的是可用于账号协议的 PlanType。

调用关系:它站在“认证结果”和“账号接口数据”之间,像翻译员一样做名称对齐。文件里还配套有从更宽泛的认证套餐类型转换的逻辑,遇到未知套餐会落到 Unknown,避免程序因为新套餐名直接崩掉。

tests::usage_based_plan_types_use_expected_wire_names111–140 ↗
fn usage_based_plan_types_use_expected_wire_names()

作用:测试特殊套餐在 JSON 里是否使用约定好的名字。这样可以防止改代码时不小心把对外接口名字改坏。

数据流:进去的是几个 PlanType 测试值和几段 JSON 字符串 → 测试把套餐转成 JSON,再从 JSON 转回套餐 → 检查结果是否和预期完全一样,不会修改正式数据。

调用关系:这是测试环境里运行的保护网。它用 assert_eq! 比较实际结果和期望结果,重点保护 self_serve_business_usage_based、enterprise_cbp_usage_based、prolite 这些容易写错的对外名字。

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

tests::plan_family_helpers_group_usage_based_variants_with_existing_plans143–151 ↗
fn plan_family_helpers_group_usage_based_variants_with_existing_plans()

作用:测试套餐分类函数是否把按量计费的新套餐归到正确家族里。它防止新旧套餐分组规则出现偏差。

数据流:进去的是 Team、SelfServeBusinessUsageBased、Business、EnterpriseCbpUsageBased 等套餐值 → 分别调用团队类和商业类判断函数 → 检查返回的 true 或 false 是否符合预期。

调用关系:它专门验证 PlanType::is_team_like 和 PlanType::is_business_like。也就是说,当分类函数被修改时,这个测试会提醒开发者有没有把团队套餐和商业套餐混在一起。

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

tests::workspace_account_helper_includes_usage_based_workspace_plans154–168 ↗
fn workspace_account_helper_includes_usage_based_workspace_plans()

作用:测试工作区账号判断是否包含所有该算进去的套餐。它确保团队、商业、企业、教育套餐会被识别为工作区账号,而个人 Pro 不会。

数据流:进去的是多个套餐值 → 调用 PlanType::is_workspace_account → 对每个返回结果做真假校验,确认工作区和非工作区没有分错。

调用关系:它保护 PlanType::is_workspace_account 这条大分类规则。以后如果新增或调整套餐,这个测试能帮助发现工作区相关功能是否会漏开或误开。

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

tests::auth_plan_type_converts_to_account_plan_type171–184 ↗
fn auth_plan_type_converts_to_account_plan_type()

作用:测试认证模块里的套餐能不能正确转换成本文件里的账号套餐。它也检查未知套餐会不会被安全地标成 Unknown。

数据流:进去的是认证模块的套餐值,包括已知套餐和一个模拟的未知字符串 → 调用 PlanType::from 做转换 → 检查输出是不是对应的账号 PlanType。

调用关系:它验证认证层和协议层之间的“翻译”是否可靠。这样登录系统返回套餐后,账号接口拿到的类型就能保持一致;未知套餐也会被稳妥处理,而不是造成错误。

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

protocol/src/approvals.rs源码 ↗
data_modelrequest handling / cross-cutting

这个文件像一套“审批表模板”。系统里的智能体想执行命令、访问网络、申请更多文件权限、打补丁,或者让用户填写表单时,都需要把事情说清楚:要做什么、在哪个目录做、风险是什么、用户能选哪些按钮。这里的结构体和枚举就是这些固定格式。比如 ExecApprovalRequestEvent 描述一次命令执行审批;GuardianAssessmentEvent 描述守护审查器对某个动作的风险判断;ElicitationRequest 描述向用户追问信息的方式。文件还兼顾新旧版本:如果新字段 available_decisions 没有传来,就用老规则自动推导可选决定。它本身不真正执行命令,也不真的弹窗,而是规定“消息长什么样”和少量默认规则,保证各个模块能安全、稳定地传递审批信息。

函数细节9
ExecPolicyAmendment::new45–47 ↗
fn new(command: Vec<String>) -> Self

作用:创建一个“执行策略补丁”,意思是把某个命令开头加入允许名单,以后类似命令可以少问一次批准。调用者传入一串命令词,它包装成统一的数据格式。

数据流:进去的是一个字符串列表,比如命令及参数的前缀 → 函数把它放进 ExecPolicyAmendment 的 command 字段里 → 出来的是一个新的 ExecPolicyAmendment 对象,没有改动外部状态。

调用关系:这是 ExecPolicyAmendment 的便捷构造入口。别的代码在准备向用户展示“以后允许这类命令吗”时,可以用它快速生成提案。

ExecPolicyAmendment::command49–51 ↗
fn command(&self) -> &[String]

作用:取出这个策略补丁里保存的命令前缀。它让外部只读查看命令内容,而不是直接拿走内部数据。

数据流:进去的是已有的 ExecPolicyAmendment 自身 → 函数读取其中的 command 字段 → 出来的是对命令字符串列表的只读引用,不复制也不修改。

调用关系:它是读取 ExecPolicyAmendment 内容的小窗口。需要展示、比较或写入策略时,其他代码可以通过它看到这条补丁要允许哪类命令。

ExecPolicyAmendment::from55–57 ↗
fn from(command: Vec<String>) -> Self

作用:把一个普通的字符串列表直接转换成 ExecPolicyAmendment。这样调用者可以用更自然的写法生成策略补丁。

数据流:进去的是 Vec<String>,也就是一串命令词 → 函数把这串词放入 command 字段 → 出来的是 ExecPolicyAmendment,原来的列表所有权被移进去。

调用关系:这是 Rust 的 From 转换接口,用来配合通用转换写法。它和 ExecPolicyAmendment::new 做的事基本一样,只是让调用方式更顺手。

ExecApprovalRequestEvent::effective_approval_id268–272 ↗
fn effective_approval_id(&self) -> String

作用:找出这次审批真正应该使用的编号。有些审批有单独的 approval_id;没有的话,就退回使用命令本身的 call_id。

数据流:进去的是一个 ExecApprovalRequestEvent,里面可能有 approval_id,也一定有 call_id → 函数先看 approval_id 是否存在,存在就复制它,不存在就复制 call_id → 出来的是一个字符串编号,不修改原请求。

调用关系:handle_exec_approval 和 handle_exec_approval_now 在处理命令审批时会用它。这样无论是普通命令审批,还是被拦截出来的子命令审批,后续流程都能拿到一个统一可用的审批编号。

调用图:被 2 处调用(handle_exec_approval, handle_exec_approval_now)。

ExecApprovalRequestEvent::effective_available_decisions274–286 ↗
fn effective_available_decisions(&self) -> Vec<ReviewDecision>

作用:算出这次审批界面应该给用户哪些选择。新请求可以直接带 available_decisions;老请求没带时,它会按旧规则自己推导。

数据流:进去的是一个命令审批请求,可能包含明确的可选决定,也可能只包含网络、策略补丁、额外权限等信息 → 如果 available_decisions 存在,就复制这份列表;如果不存在,就把相关字段交给默认规则计算 → 出来的是 ReviewDecision 列表,也就是用户可点的选项。

调用关系:handle_exec_approval_now 在马上处理审批时会调用它。它在新旧协议之间起桥梁作用:新客户端按新字段走,旧发送方也不会因为少字段而坏掉;需要推导时,它把工作交给 ExecApprovalRequestEvent::default_available_decisions。

调用图:被 1 处调用(handle_exec_approval_now);外部调用 1 个(default_available_decisions)。

ExecApprovalRequestEvent::default_available_decisions288–321 ↗
fn default_available_decisions(
        network_approval_context: Option<&NetworkApprovalContext>,
        proposed_execpolicy_amendment: Option<&ExecPolicyAmendment>,
        proposed_network_policy_

作用:按老规则生成默认审批选项。它根据请求的类型决定给用户“允许一次”“本会话允许”“修改网络策略”“修改执行策略”或“中止”等选择。

数据流:进去的是几类可选上下文:是否是网络审批、是否有执行策略补丁、是否有网络策略补丁、是否申请额外权限 → 函数按优先级判断:网络请求走网络选项,额外权限请求只给允许或中止,普通命令请求给允许、可选的策略放行、再加中止 → 出来的是一个 ReviewDecision 列表。

调用关系:它是 effective_available_decisions 的后备规则库。当前端或上游没有明确告诉系统该显示哪些按钮时,这个函数负责补上一套兼容旧行为的按钮组合。

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

ElicitationRequest::message346–350 ↗
fn message(&self) -> &str

作用:从一次“向用户追问信息”的请求里取出要展示给用户看的提示语。不管请求是表单模式还是网址模式,都能用同一个方法拿到 message。

数据流:进去的是 ElicitationRequest,可能是 Form,也可能是 Url → 函数匹配具体类型,并读取其中的 message 字段 → 出来的是一段字符串引用,不复制也不修改请求。

调用关系:它给展示层或处理层提供统一入口。调用者不用关心追问是表单还是链接,只要想显示提示文字,就调用这个方法。

tests::guardian_assessment_action_deserializes_command_shape400–417 ↗
fn guardian_assessment_action_deserializes_command_shape()

作用:测试“命令审查动作”的 JSON 能不能正确读成 Rust 数据。简单说,就是确认外部传来的 command 格式不会被系统误解。

数据流:进去的是测试里手写的一段 JSON,包含 type、source、command 和 cwd → 测试用 serde_json 把它解析成 GuardianAssessmentAction → 最后断言解析结果等于预期的 Command 结构,测试通过则说明格式兼容。

调用关系:这是测试代码,只在跑测试时活跃。它调用 JSON 构造、反序列化和断言工具,保护 GuardianAssessmentAction::Command 这种协议形状不被后续改代码时弄坏。

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

tests::guardian_assessment_action_round_trips_execve_shape421–450 ↗
fn guardian_assessment_action_round_trips_execve_shape()

作用:测试 execve 形式的审查动作能“读进来再写出去”仍保持原样。execve 可以理解为更底层的程序启动格式,包含程序名和参数列表。

数据流:进去的是一段 execve JSON,里面有 program、argv 和 cwd → 测试先把 JSON 解析成 GuardianAssessmentAction,再把这个对象序列化回 JSON → 最后检查回写的 JSON 和原始 JSON 一样,并检查内部数据也符合预期。

调用关系:这是 Unix 平台上的测试,只在测试阶段运行。它用来保证 GuardianAssessmentAction::Execve 的协议格式稳定,避免前端、后端或审查器之间因为字段变化而对不上。

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

protocol/src/auth.rs源码 ↗
data_modelcross-cutting auth parsing and token refresh errors

服务器返回的套餐名可能是“pro”“enterprise”,也可能是旧名字或系统暂时不认识的新名字。这个文件就像一张翻译表:把这些字符串翻译成代码里的套餐类型,同时保留未知值,避免因为服务器新增套餐就直接崩掉。PlanType 表示“已知套餐或未知字符串”,KnownPlan 列出当前认识的套餐,并提供显示给人看的名字、发给服务器用的原始值,以及判断它是不是团队/企业类工作区账号。文件还定义了 RefreshTokenFailedError,用来表达“刷新令牌失败”这件事。刷新令牌可以理解成系统用来续登录状态的凭据,失败时不只要有一句错误信息,还要知道原因,比如过期、用完、被撤销或其他原因。serde 是 Rust 常用的序列化/反序列化工具,这里用它把这些类型和 JSON 之类的数据互相转换。

函数细节6
PlanType::from_raw_value13–30 ↗
fn from_raw_value(raw: &str) -> Self

作用:把服务器或配置里拿到的原始套餐字符串,转换成系统内部认识的 PlanType。这样后面的代码不用到处比较字符串,只要判断标准化后的套餐类型。

数据流:进去的是一个原始文本,比如“PLUS”“hc”或一个从没见过的新套餐名。它先把文本转成小写,再按已知名单逐个匹配;匹配上就产出 KnownPlan 包装成的已知套餐,匹配不上就把原文保存成 Unknown。结果是一个 PlanType,不会因为遇到陌生套餐名就丢信息。

调用关系:它是套餐字符串进入系统后的翻译入口之一。它会创建 KnownPlan 对应的已知值,或者创建 Unknown 保存未知值;后续显示、权限判断、账号类型判断就可以基于这个结果继续做。

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

KnownPlan::display_name54–68 ↗
fn display_name(self) -> &'static str

作用:把内部套餐枚举变成适合给人看的名字。比如内部叫 ProLite,显示时变成“Pro Lite”。

数据流:进去的是一个已知套餐值。它根据套餐是哪一种,挑出固定的英文展示名;不修改任何状态。出来的是一段静态文本,可以直接放到界面、日志或提示信息里。

调用关系:它通常用在需要把套餐信息展示给用户或写进可读日志的时候。它不再调用别的项目函数,只负责把机器内部的代号换成人更容易读的名称。

KnownPlan::raw_value70–84 ↗
fn raw_value(self) -> &'static str

作用:把内部套餐枚举变回服务器使用的原始字符串。这样系统要发请求、保存数据或比较协议值时,可以用统一的标准写法。

数据流:进去的是一个已知套餐值。它查表返回对应的协议字符串,比如 ProLite 返回“prolite”,Enterprise 返回“enterprise”。出来的是固定字符串,不改动外部数据。

调用关系:它和 PlanType::from_raw_value 像一进一出两道门:from_raw_value 负责把外部字符串读进来,raw_value 负责把内部套餐再按协议格式写出去。

KnownPlan::is_workspace_account86–96 ↗
fn is_workspace_account(self) -> bool

作用:判断一个套餐是不是工作区类账号,也就是更像团队、企业、教育组织使用的账号,而不是个人免费或个人付费账号。

数据流:进去的是一个已知套餐。它检查这个套餐是否属于 Team、Business、Enterprise、Edu 等组织类套餐名单;如果是就返回 true,否则返回 false。不改变任何数据。

调用关系:它会在代码需要区分个人账号和组织/工作区账号时被调用。内部用 matches! 这个 Rust 匹配工具做判断,像拿套餐去一张名单上勾选一样。

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

RefreshTokenFailedError::new107–112 ↗
fn new(reason: RefreshTokenFailedReason, message: impl Into<String>) -> Self

作用:创建一个“刷新令牌失败”的错误对象,同时带上失败原因和给人看的错误信息。这样调用方既能机器判断原因,也能把信息展示或记录下来。

数据流:进去的是一个失败原因,比如过期或被撤销,以及一段错误说明。它把说明转换成 String 字符串,和原因一起装进 RefreshTokenFailedError。出来的是一个完整错误值,供上层返回、记录或继续分类处理。

调用关系:它会被刷新登录令牌的流程使用,例如 refresh_token、next、classify_refresh_token_failure 这些地方在确认失败原因后,会调用它把原因和说明打包成统一错误。测试 refresh_failure_is_scoped_to_the_matching_auth_snapshot 也会用它来验证错误是否只影响对应的认证状态。

调用图:被 4 处调用(refresh_failure_is_scoped_to_the_matching_auth_snapshot, refresh_token, next, classify_refresh_token_failure);外部调用 1 个(into)。

tests::plan_type_deserializes_raw_aliases130–140 ↗
fn plan_type_deserializes_raw_aliases()

作用:这是一个测试,用来确认旧别名或替代名字也能被正确读成标准套餐。比如“hc”应该算 Enterprise,“education”应该算 Edu。

数据流:进去的是两段 JSON 字符串测试数据:“hc”和“education”。测试用 serde_json 把它们读成 PlanType,然后用 assert_eq! 比较结果是不是预期的已知套餐。如果不一致,测试会失败,提醒开发者别把兼容规则改坏了。

调用关系:它由测试运行器在跑测试时调用,不参与正式运行。它保护的是 PlanType 和 KnownPlan 上的反序列化规则,确保服务器还在使用旧名字或别名时,客户端仍然能正确理解。

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

protocol/src/capabilities.rs源码 ↗
data_modelcross-cutting

这个文件不是在执行操作,而是在定规矩:一份“能力根目录”的数据应该包含哪些字段。可以把它理解成一张标准表格,大家都按这张表格填写,系统就不会把资源位置、环境编号这些信息搞混。SelectedCapabilityRoot 表示用户选中的一个根资源,里面有稳定的 id,还有它的位置 location。CapabilityRootLocation 表示这个位置怎么解析,目前只有一种:Environment,也就是这个路径属于某个执行环境。这里的 serde 用来把 Rust 数据和 JSON 互相转换,schemars 用来生成 JSON Schema(给别人校验数据格式的说明书),ts_rs 用来生成 TypeScript 类型,方便网页或前端代码也用同一套定义。这样做的好处是:协议格式集中在这里,少了手写多份类型导致前后端不一致的风险。

protocol/src/config_types.rs源码 ↗
configconfig load, request handling, cross-cutting

这个文件主要是给配置系统立规矩。比如沙箱能不能写文件、审批请求交给用户还是自动审查、网页搜索带不带地点、模型认证命令怎么跑、TUI 界面启动用哪种协作模式,都在这里用 Rust 类型写清楚。这样配置从 TOML、JSON 或接口里读进来时,可以自动检查、转换和生成说明文档。它还处理一些容易出错的细节:profile 名只能是普通名字,不能带路径,防止读到不该读的文件;服务等级会把用户熟悉的 fast 转成接口需要的 priority;协作模式支持旧名字,但内部统一成默认模式。整体上,它不是运行主流程的地方,而是给其他模块提供可靠的配置“零件”。

函数细节37
ProfileV2Name::as_str103–105 ↗
fn as_str(&self) -> &str

作用:把已经验证过的 profile 名字当作普通字符串拿出来用。别人需要拼配置文件名或显示这个名字时,会用它。

数据流:进去的是一个 ProfileV2Name 对象 → 它不改任何内容,只取出内部保存的那段文字 → 出来的是一个字符串引用,还是原来的名字。

调用关系:它是 ProfileV2Name 最基础的取值口。ProfileV2Name::deref 会调用它,让这个类型在很多地方可以像普通字符串一样使用。

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

ProfileV2NameParseError::fmt114–120 ↗
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result

作用:把 profile 名字解析失败的原因写成人能看懂的错误消息。这样用户输错 --profile 时,不会只看到生硬的内部错误。

数据流:进去的是错误对象和一个用来写文字的格式化器 → 它把错误里保存的原始输入塞进提示语 → 出来的是格式化结果,外面可以显示给用户。

调用关系:当 ProfileV2Name::from_str 发现名字不合法时会生成这个错误;之后错误被打印或展示时,这个函数负责把它变成一句清楚的话。

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

ProfileV2Name::from_str128–140 ↗
fn from_str(value: &str) -> Result<Self, Self::Err>

作用:检查用户给的 profile 名是不是安全的普通名字。它拒绝空名字,也拒绝点号、斜杠等路径字符,防止用户借 profile 参数去读任意文件。

数据流:进去的是一段用户输入的文字 → 它逐个检查字符,只允许英文字母、数字、下划线和短横线 → 合法就产出 ProfileV2Name,不合法就产出 ProfileV2NameParseError。

调用关系:这是把命令行或配置里的 profile 字符串变成安全类型的入口。后面的代码只要拿到 ProfileV2Name,就可以相信它不是路径。

ProfileV2Name::deref146–148 ↗
fn deref(&self) -> &Self::Target

作用:让 ProfileV2Name 在需要字符串的场合更顺手地使用。可以把它理解成给这个安全名字开了一个“像字符串一样看待”的小门。

数据流:进去的是 ProfileV2Name → 它调用 ProfileV2Name::as_str 取内部文字 → 出来的是字符串引用,不会复制也不会修改。

调用关系:它把实际取值工作交给 ProfileV2Name::as_str。很多 Rust 标准用法会自动借助这个函数,把自定义名字类型当作 str 来读。

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

ProfileV2Name::fmt152–154 ↗
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result

作用:决定 ProfileV2Name 被打印时显示什么。它直接显示内部保存的 profile 名字。

数据流:进去的是 ProfileV2Name 和格式化器 → 它把内部字符串写入格式化器 → 出来的是格式化结果,内容就是 profile 名。

调用关系:当日志、错误提示或界面需要展示 profile 名时会间接用到它。它不做校验,因为校验已经在 ProfileV2Name::from_str 做过。

ApprovalsReviewer::schema_name175–177 ↗
fn schema_name() -> String

作用:给“审批由谁来审”这个配置项生成 JSON Schema 名字。JSON Schema 是一种机器可读的配置说明书。

数据流:进去没有业务数据 → 它返回固定文字 ApprovalsReviewer → 出来的是 schema 里使用的类型名。

调用关系:它属于 JsonSchema 支持代码。生成配置文档或校验规则时,schema 系统会调用它来给这个枚举命名。

ApprovalsReviewer::json_schema179–184 ↗
fn json_schema(_generator: &mut SchemaGenerator) -> Schema

作用:告诉配置文档:审批审查人这个字段允许填哪些字符串,以及这些值是什么意思。它还保留旧名字 guardian_subagent,方便老配置继续可用。

数据流:进去的是 schema 生成器,但这里不用它 → 它列出 user、auto_review、guardian_subagent,并附上一段说明 → 出来的是一份字符串枚举 schema。

调用关系:它把具体造 schema 的活交给 string_enum_schema_with_description。配置文档生成器或校验器需要描述 ApprovalsReviewer 时会用到它。

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

ShellEnvironmentPolicy::default234–243 ↗
fn default() -> Self

作用:给运行 shell 命令时的环境变量策略提供默认值。没有用户特殊配置时,系统就按这套安全且兼容的规则来。

数据流:进去没有输入 → 它创建一个策略:继承全部环境变量、不启用默认敏感名过滤、没有额外排除或只保留规则、不使用 shell profile → 出来的是完整的默认 ShellEnvironmentPolicy。

调用关系:很多创建运行环境的流程会把它当起点,例如从审批和 profile 生成配置、给 agent 启动配置补环境变量、测试默认环境行为等。

调用图:被 7 处调用(from_approval_and_profile, populate_env_inserts_thread_id, populate_env_omits_thread_id_when_missing, test_core_inherit_defaults_keep_sensitive_vars, build_agent_spawn_config_uses_turn_context_values, create_env_from_core_vars, create_env_from_core_vars);外部调用 2 个(new, new)。

string_enum_schema_with_description246–262 ↗
fn string_enum_schema_with_description(values: &[&str], description: &str) -> Schema

作用:快速造一份“这个字段只能是这些字符串之一”的 JSON Schema,并带上人类说明。它避免每个枚举都重复写同样的 schema 组装代码。

数据流:进去的是一组允许值和一段说明文字 → 它创建一个字符串类型的 schema,把允许值放进枚举列表,把说明放进元数据 → 出来的是 Schema 对象。

调用关系:ApprovalsReviewer::json_schema 调用它。它是这个文件里的小工具,只负责把一组选项包装成标准说明书格式。

调用图:被 1 处调用(json_schema);外部调用 3 个(new, default, Object)。

WebSearchLocation::merge330–337 ↗
fn merge(&self, other: &Self) -> Self

作用:把两份网页搜索地点信息合成一份,并让后来的配置优先。就像先填默认地址,再用用户新填的字段覆盖其中一部分。

数据流:进去的是当前地点和另一份地点 → 对国家、地区、城市、时区逐项检查,另一份有值就用另一份,没有就保留当前值 → 出来的是合并后的新地点。

调用关系:WebSearchToolConfig::merge 在合并网页搜索工具配置时会用到它,专门处理 location 这个嵌套字段。

WebSearchToolConfig::merge349–363 ↗
fn merge(&self, other: &Self) -> Self

作用:把两份网页搜索工具配置合并成一份,适合处理“默认配置 + 用户覆盖配置”的场景。

数据流:进去的是当前配置和另一份配置 → context_size 和 allowed_domains 优先采用另一份的非空值;location 如果两边都有,就逐项合并 → 出来的是新的 WebSearchToolConfig,不会改原对象。

调用关系:它是网页搜索配置叠加时的核心方法。遇到地点信息时,它把更细的合并工作交给 WebSearchLocation::merge。

WebSearchUserLocation::from402–410 ↗
fn from(location: WebSearchLocation) -> Self

作用:把内部使用的网页搜索地点格式,转换成发送给网页搜索接口的用户地点格式。

数据流:进去的是 WebSearchLocation,里面可能有国家、地区、城市、时区 → 它把这些字段原样搬过去,并把类型设为 approximate(大致位置)→ 出来的是 WebSearchUserLocation。

调用关系:WebSearchConfig::from 在把工具配置转成最终搜索配置时会调用这个转换。它负责地点这一小块格式对接。

WebSearchConfig::from414–424 ↗
fn from(config: WebSearchToolConfig) -> Self

作用:把较简洁的网页搜索工具配置,转换成接口真正需要的网页搜索配置结构。

数据流:进去的是 WebSearchToolConfig → 它把 allowed_domains 包成 filters,把 location 转成 user_location,把 context_size 改名放到 search_context_size → 出来的是 WebSearchConfig。

调用关系:当系统准备调用网页搜索功能时,会需要这种更贴近接口的配置。地点转换会交给 WebSearchUserLocation::from。

ServiceTier::request_value442–447 ↗
fn request_value(self) -> &'static str

作用:把内部的服务等级选项转换成请求接口认识的字符串。比如内部叫 Fast,请求里实际要写 priority。

数据流:进去的是 ServiceTier 枚举值 → 它按固定表转换:Fast 变 priority,Flex 变 flex → 出来的是可放进请求的字符串。

调用关系:发送模型请求时会用它来填写服务等级字段。它把用户和内部代码的叫法,翻译成后端接口的叫法。

ServiceTier::from_request_value449–455 ↗
fn from_request_value(value: &str) -> Option<Self>

作用:把请求或配置里的服务等级字符串读回内部枚举。它兼容 fast 和 priority 两种说法,都当作 Fast。

数据流:进去的是一段字符串 → 它匹配 fast、priority、flex → 匹配成功就返回对应 ServiceTier,匹配不到就返回 None 表示不认识。

调用关系:配置应用流程 apply 会调用它,把外部传来的文字解释成内部能安全使用的选项。

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

ModelProviderAuthInfo::timeout496–498 ↗
fn timeout(&self) -> Duration

作用:把模型供应商认证命令的超时时间,从毫秒数字变成 Duration(时间长度对象)。这样运行命令的代码可以直接拿去计时。

数据流:进去的是 ModelProviderAuthInfo → 它读取 timeout_ms 这个非零毫秒数 → 出来的是对应的 Duration。

调用关系:执行获取 bearer token(访问令牌)命令时会用到它。它只负责把配置里的数字翻译成计时器能用的格式。

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

ModelProviderAuthInfo::refresh_interval500–502 ↗
fn refresh_interval(&self) -> Option<Duration>

作用:决定缓存的认证令牌多久主动刷新一次。配置为 0 时,表示不主动刷新,只等接口返回 401 之类的失败后再重试。

数据流:进去的是 ModelProviderAuthInfo → 它读取 refresh_interval_ms;如果是 0,就返回 None;如果非零,就转成 Duration → 出来的是可选的刷新间隔。

调用关系:认证令牌缓存逻辑会用它判断何时重新运行取 token 的命令。它把“0 有特殊含义”这件事封装起来。

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

default_provider_auth_timeout_ms505–510 ↗
fn default_provider_auth_timeout_ms() -> NonZeroU64

作用:提供模型供应商认证命令的默认超时时间:5000 毫秒。这样用户没配置时,命令不会无限等下去。

数据流:进去没有输入 → 它把默认数字 5000 交给 non_zero_u64 检查并包装成非零数 → 出来的是 NonZeroU64。

调用关系:反序列化 ModelProviderAuthInfo 时,如果 timeout_ms 没写,会调用它。它依赖 non_zero_u64 保证默认值绝不为 0。

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

default_provider_auth_refresh_interval_ms512–514 ↗
fn default_provider_auth_refresh_interval_ms() -> u64

作用:提供模型供应商认证令牌的默认主动刷新间隔:300000 毫秒,也就是 5 分钟。

数据流:进去没有输入 → 它直接返回固定数字 300000 → 出来的是毫秒数。

调用关系:反序列化 ModelProviderAuthInfo 时,如果 refresh_interval_ms 没写,会用它填默认值。后续 ModelProviderAuthInfo::refresh_interval 会把它转成时间长度。

non_zero_u64516–521 ↗
fn non_zero_u64(value: u64, field_name: &str) -> NonZeroU64

作用:把普通整数变成“保证不是 0 的整数”。如果传进来的是 0,它会立刻报错,因为某些配置字段不能接受 0。

数据流:进去的是一个 u64 数字和字段名 → 它尝试创建 NonZeroU64;成功就返回,失败就用字段名 panic 报错 → 出来的是非零整数,或者程序在开发错误下停止。

调用关系:default_provider_auth_timeout_ms 调用它来保护默认超时时间。这个函数主要防止代码里写错默认值,而不是处理普通用户输入。

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

default_provider_auth_cwd523–533 ↗
fn default_provider_auth_cwd() -> AbsolutePathBuf

作用:给运行认证命令时的工作目录提供默认值。优先表示当前目录,实在解析不了才取进程当前目录。

数据流:进去没有输入 → 它先尝试把字符串 . 解析成绝对路径;成功就返回;失败则读取当前工作目录;如果连当前目录也拿不到,就 panic → 出来的是 AbsolutePathBuf。

调用关系:ModelProviderAuthInfo 的 cwd 字段没配置时会用这个默认值。is_default_provider_auth_cwd 也会调用它来判断某个路径是不是默认值。

调用图:调用 2 个内部函数(current_dir, deserialize);被 1 处调用(is_default_provider_auth_cwd);外部调用 2 个(panic!, new)。

is_default_provider_auth_cwd535–537 ↗
fn is_default_provider_auth_cwd(path: &AbsolutePathBuf) -> bool

作用:判断某个认证命令工作目录是不是默认目录。这个判断常用于序列化时省略默认字段,让输出配置更干净。

数据流:进去的是一个绝对路径 → 它调用 default_provider_auth_cwd 得到默认路径,再和输入路径比较 → 出来的是 true 或 false。

调用关系:它服务于 ModelProviderAuthInfo 的 schema/序列化行为。为了知道默认值是什么,它把计算默认目录的工作交给 default_provider_auth_cwd。

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

ModeKind::display_name601–608 ↗
fn display_name(self) -> &'static str

作用:把协作模式的内部名字变成界面上好读的名字。比如 PairProgramming 会显示成 Pair Programming。

数据流:进去的是 ModeKind → 它按固定表选择展示文字 → 出来的是静态字符串。

调用关系:当系统需要告诉用户某个模式叫什么,比如生成“当前模式不能请求用户输入”的提示时,会调用它。

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

ModeKind::is_tui_visible610–612 ↗
fn is_tui_visible(self) -> bool

作用:判断某个协作模式是否应该出现在 TUI(终端用户界面)的可选列表里。隐藏的旧模式或内部模式不会显示给用户。

数据流:进去的是 ModeKind → 它检查是否为 Plan 或 Default → 出来的是布尔值,表示界面是否可见。

调用关系:mask_for_kind 会用它筛选可展示模式。测试也会确认可见模式列表和这个判断保持一致。

调用图:被 1 处调用(mask_for_kind);外部调用 1 个(matches!)。

ModeKind::allows_request_user_input614–616 ↗
fn allows_request_user_input(self) -> bool

作用:判断这个协作模式是否允许主动请求用户补充输入。目前只有 Plan 模式允许。

数据流:进去的是 ModeKind → 它检查是否为 Plan → 出来的是 true 或 false。

调用关系:需要决定能不能打断流程、向用户提问时会用它。它把这个规则集中放在模式类型旁边,避免各处写散。

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

CollaborationMode::settings_ref629–631 ↗
fn settings_ref(&self) -> &Settings

作用:拿到协作模式里的 settings 配置引用。它是内部小帮手,避免其他方法重复写取字段的代码。

数据流:进去的是 CollaborationMode → 它返回内部 settings 的只读引用 → 出来的是 Settings 引用,不会复制也不会修改。

调用关系:CollaborationMode::model、reasoning_effort、with_updates 和 apply_mask 都会调用它,先拿到设置,再读取或生成更新后的设置。

调用图:被 4 处调用(apply_mask, model, reasoning_effort, with_updates)。

CollaborationMode::model633–635 ↗
fn model(&self) -> &str

作用:读取当前协作模式使用的模型名。外部需要知道当前会调用哪个模型时会用它。

数据流:进去的是 CollaborationMode → 它通过 settings_ref 找到设置,再取 model 字符串 → 出来的是模型名引用。

调用关系:thread_config_snapshot 会调用它,把当前线程或会话的模型配置记录成快照。实际取 settings 的动作交给 CollaborationMode::settings_ref。

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

CollaborationMode::reasoning_effort637–639 ↗
fn reasoning_effort(&self) -> Option<ReasoningEffort>

作用:读取当前协作模式设置的推理强度。推理强度可以理解成让模型思考得更省、更普通或更深入的选项。

数据流:进去的是 CollaborationMode → 它通过 settings_ref 读取 reasoning_effort,并复制出一个可选值 → 出来的是 Some 强度或 None。

调用关系:thread_config_snapshot 会调用它记录当前配置。它不判断默认值,只如实返回这个模式里存的设置。

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

CollaborationMode::with_updates648–666 ↗
fn with_updates(
        &self,
        model: Option<String>,
        effort: Option<Option<ReasoningEffort>>,
        developer_instructions: Option<Option<String>>,
    ) -> Self

作用:基于当前协作模式生成一份更新后的新模式,可以改模型、推理强度和开发者说明,也可以选择保持原样。

数据流:进去的是当前 CollaborationMode,以及三个可选更新项 → 它没给更新的字段保留旧值,给了更新的字段就替换;可选字段还能被明确清空 → 出来的是新的 CollaborationMode,原对象不变。

调用关系:with_model 会调用它来做局部更新。它先通过 settings_ref 读取旧设置,再拼出新的 Settings。

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

CollaborationMode::apply_mask673–689 ↗
fn apply_mask(&self, mask: &CollaborationModeMask) -> Self

作用:把一个“遮罩配置”套到当前协作模式上,只覆盖遮罩里明确写了的字段。遮罩像一张透明改字贴,没写的地方就露出原配置。

数据流:进去的是当前 CollaborationMode 和 CollaborationModeMask → 它忽略 mask 的 name,只用其中非空的 mode、model、reasoning_effort、developer_instructions 覆盖旧值 → 出来的是新 CollaborationMode。

调用关系:它用于根据预设或配置片段调整协作模式。读取旧设置时调用 settings_ref;测试 apply_mask_can_clear_optional_fields 专门确认它能清空可选字段。

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

tests::apply_mask_can_clear_optional_fields717–743 ↗
fn apply_mask_can_clear_optional_fields()

作用:验证 apply_mask 不只会设置新值,也能把原本有值的可选字段清空。这对关闭推理强度或开发者说明很重要。

数据流:进去的是测试里手工构造的模式和遮罩 → 遮罩把 reasoning_effort 和 developer_instructions 设为 Some(None) → 断言输出的新模式里这两个字段确实变成 None。

调用关系:这是 CollaborationMode::apply_mask 的安全网。以后有人改合并规则,如果破坏“可以清空字段”的行为,这个测试会失败。

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

tests::mode_kind_deserializes_alias_values_to_default746–752 ↗
fn mode_kind_deserializes_alias_values_to_default()

作用:验证旧的协作模式名字仍然能被读进来,并统一变成 Default。这样老配置不会因为改名就坏掉。

数据流:进去的是测试里的别名列表 code、pair_programming、execute、custom → 每个别名被包装成 JSON 字符串并反序列化 → 断言结果都是 ModeKind::Default。

调用关系:它保护 ModeKind 上的 serde alias 设置。配置加载代码依赖这些别名兼容历史配置。

调用图:外部调用 3 个(assert_eq!, format!, from_str)。

tests::approvals_reviewer_serializes_auto_review_and_accepts_legacy_guardian_subagent755–777 ↗
fn approvals_reviewer_serializes_auto_review_and_accepts_legacy_guardian_subagent()

作用:验证审批审查人字段的读写格式正确,尤其是新名字 auto_review 和旧名字 guardian_subagent 的兼容。

数据流:进去的是几个固定枚举值和字符串 → 它检查序列化输出是不是 user 或 auto_review,再把 user、auto_review、guardian_subagent 读回来 → 断言读出的枚举符合预期。

调用关系:它保护 ApprovalsReviewer 的配置兼容性。生成 schema 的代码说明了旧值可接受,这个测试确认实际解析也真的接受。

调用图:外部调用 3 个(assert_eq!, format!, from_str)。

tests::profile_v2_name_rejects_paths_and_empty_names780–795 ↗
fn profile_v2_name_rejects_paths_and_empty_names()

作用:验证 profile 名不会接受路径或空字符串。这是防止用户通过 profile 参数访问任意文件的重要安全测试。

数据流:进去的是 ../foo 和空字符串两个非法输入 → 它调用 ProfileV2Name::from_str → 断言得到的错误里保留了对应输入。

调用关系:它直接保护 ProfileV2Name::from_str 的校验规则。以后如果有人放宽字符限制,这个测试会提醒风险。

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

tests::tui_visible_collaboration_modes_match_mode_kind_visibility798–808 ↗
fn tui_visible_collaboration_modes_match_mode_kind_visibility()

作用:验证 TUI 显示的协作模式列表和 ModeKind::is_tui_visible 的判断一致。这样界面不会漏显示或误显示内部模式。

数据流:进去的是固定的可见模式数组 → 它断言数组等于 Default 和 Plan,并逐个检查它们可见,同时确认隐藏模式不可见 → 出来是测试通过或失败。

调用关系:它保护 TUI_VISIBLE_COLLABORATION_MODES 常量和 ModeKind::is_tui_visible 之间的一致性。界面筛选逻辑依赖这两者不打架。

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

tests::web_search_location_merge_prefers_overlay_values811–833 ↗
fn web_search_location_merge_prefers_overlay_values()

作用:验证网页搜索地点合并时,覆盖配置里的字段优先,但没写的字段会保留原值。

数据流:进去的是 base 地点和 overlay 地点 → 调用 WebSearchLocation::merge → 断言地区和城市采用 overlay,国家和时区保留 base。

调用关系:它保护 WebSearchLocation::merge 的覆盖规则。WebSearchToolConfig::merge 依赖这个规则合并嵌套地点。

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

tests::web_search_tool_config_merge_prefers_overlay_values836–870 ↗
fn web_search_tool_config_merge_prefers_overlay_values()

作用:验证整份网页搜索工具配置合并时,覆盖配置优先,同时嵌套地点会逐项合并。

数据流:进去的是带 context_size、allowed_domains、location 的 base 配置,以及一份 overlay 配置 → 调用 WebSearchToolConfig::merge → 断言 context_size 被覆盖,allowed_domains 被保留,location 被逐项合并。

调用关系:它保护 WebSearchToolConfig::merge 的整体行为,并间接确认它正确使用 WebSearchLocation::merge。

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

protocol/src/dynamic_tools.rs源码 ↗
data_modelconfig load / session restore / request handling

这个文件像一份“工具菜单说明书”。动态工具就是运行时才出现的功能,比如某个外部服务临时提供了一个可调用函数。文件里先规定了工具可以是单个函数,也可以放进一个命名空间(一组同类工具的文件夹)。每个函数都有名字、说明、输入格式,以及是否延迟加载。它还规定了调用工具时要带哪些信息,比如本次调用编号、轮次编号、工具名和参数;工具返回时又会给出文字或图片等内容,以及是否成功。比较重要的是兼容旧格式:以前工具是扁平列表,可能带 namespace 或 exposeToContext 字段;现在有明确的 type 和 namespace 结构。这里会检查新旧格式不能混着用,然后把旧格式整理成新格式。这样系统升级后,老数据还能继续用,不会因为格式变了就崩掉。

函数细节3
normalize_dynamic_tool_specs88–131 ↗
fn normalize_dynamic_tool_specs(
    values: Vec<JsonValue>,
) -> Result<Vec<DynamicToolSpec>, serde_json::Error>

作用:这个函数把一批动态工具配置整理成系统现在统一使用的格式。它的用处是兼容新旧两种写法,但不允许一半新格式、一半旧格式混在一起,避免系统误解工具清单。

数据流:进去的是一组 JSON 值,也就是还没完全确定形状的工具配置。函数先看里面有没有旧格式特征,比如 namespace、exposeToContext,或者缺少 type;也看有没有新格式特征,也就是带 type。如果发现新旧混用,就返回错误。若全是新格式,就直接逐个解析成 DynamicToolSpec。若是旧格式,就先解析成 LegacyDynamicToolSpec,再把 exposeToContext 这种旧字段换算成 deferLoading,最后按命名空间重新分组,输出新的 DynamicToolSpec 列表。

调用关系:它是动态工具配置进入系统时的“格式清洗员”。命令行参数解析 parse_dynamic_tools_arg 会用它,反序列化入口 deserialize_dynamic_tool_specs 也会用它。遇到旧格式需要整理时,它会把分组工作交给 group_dynamic_tools_by_namespace。

调用图:调用 1 个内部函数(group_dynamic_tools_by_namespace);被 2 处调用(parse_dynamic_tools_arg, deserialize_dynamic_tool_specs);外部调用 1 个(custom)。

group_dynamic_tools_by_namespace133–159 ↗
fn group_dynamic_tools_by_namespace(
    tools: Vec<(Option<String>, DynamicToolFunctionSpec)>,
) -> Vec<DynamicToolSpec>

作用:这个函数把带有可选命名空间的工具函数整理成最终的工具列表。没有命名空间的工具保持为单独函数;有同一个命名空间的工具会被放进同一个工具组里。

数据流:进去的是一串二元组:每项包含一个可能为空的命名空间名字,以及一个工具函数说明。函数从头遍历:如果没有命名空间,就直接放进结果列表;如果有命名空间,就查这个命名空间之前有没有创建过。创建过就把工具追加进去;没创建过就新建一个命名空间条目,并记录它在结果列表里的位置。最后出来的是按新结构组织好的 DynamicToolSpec 列表。

调用关系:它是 normalize_dynamic_tool_specs 处理旧格式时调用的“小整理工”。normalize_dynamic_tool_specs 先把旧工具读成函数,再让它按 namespace 合并成新格式里的 Namespace。它本身不读取外部数据,也不负责判断新旧格式,只专心做分组。

调用图:被 1 处调用(normalize_dynamic_tool_specs);外部调用 8 个(new, new, with_capacity, Function, Function, Namespace, unreachable!, vec!)。

deserialize_dynamic_tool_specs161–173 ↗
fn deserialize_dynamic_tool_specs(
    deserializer: D,
) -> Result<Option<Vec<DynamicToolSpec>>, D::Error>

作用:这个函数专门给 serde 反序列化使用。serde 是 Rust 里常用的“把 JSON 等数据变成程序结构”的工具;这里它负责在读取动态工具字段时,顺手做格式兼容和规范化。

数据流:进去的是一个反序列化器,也就是 serde 提供的数据读取通道。函数先尝试把字段读成可选的 JSON 数组:如果字段不存在或是空的可选值,就返回 None。若读到了数组,就交给 normalize_dynamic_tool_specs 统一整理;成功后包成 Some 返回,失败则把错误转换成反序列化错误返回。

调用关系:它位于配置或会话数据被读取的入口处。外部代码不需要先手动判断工具格式,只要在字段上使用这个反序列化函数,它就会调用 normalize_dynamic_tool_specs 完成新旧格式兼容。

调用图:调用 1 个内部函数(normalize_dynamic_tool_specs);外部调用 1 个(deserialize)。

protocol/src/error.rs源码 ↗
domain_logiccross-cutting/error handling

程序出错时,最怕两件事:机器不知道能不能重试,用户也看不懂发生了什么。这个文件就是为了解决这件事。它定义了 CodexErr 这个总错误类型,像一个总分类箱,把请求超时、服务器返回异常、用量超限、沙箱拒绝执行、环境变量缺失等情况分门别类放好。它还会判断错误是否值得自动重试,把内部错误翻译成对客户端可见的协议错误,并生成界面上显示的短消息。比如服务器返回一大段 HTML,它会截短;如果 Cloudflare 拦截访问,它会换成更友好的提示;如果用户额度用完,它会按套餐和重置时间生成不同文案。可以把它理解成医院分诊台:先判断病情类别,再决定是否复诊、给病人什么解释、给系统什么编号。

函数细节23
CodexErr::from167–169 ↗
fn from(_: CancelErr) -> Self

作用:把取消操作的错误转换成 Codex 自己认识的错误。这样别的地方只需要处理 CodexErr,不用到处认识各种外部错误类型。

数据流:进去的是一个 CancelErr,也就是“任务被取消”的信号;函数不关心里面的细节,直接把它解释成一次对话回合被中止;出来的是 CodexErr::TurnAborted。

调用关系:它是自动转换用的小桥。任何地方遇到取消错误并需要返回 CodexErr 时,Rust 可以自动走到这里,让上层统一按“本轮中止”处理。

CodexErr::is_retryable173–210 ↗
fn is_retryable(&self) -> bool

作用:判断一个错误值不值得自动再试一次。比如网络断了可能能重试,额度用完或请求本身不合法就不该重试。

数据流:进去的是一个 CodexErr;函数按错误种类查表式地分类;出来的是 true 或 false,表示上层流程是否可以自动重试。

调用关系:它通常被会话循环、请求发送逻辑或重试控制器使用。它不修复错误,只给出“要不要再试”的判断,避免对永久失败做无意义重试。

CodexErr::downcast_ref215–217 ↗
fn downcast_ref(&self) -> Option<&T>

作用:提供一个兼容旧代码的小接口,让旧代码还能用“看看这个错误是不是某种具体类型”的写法。

数据流:进去的是当前 CodexErr 和想检查的目标类型;函数把自己临时当成通用类型来尝试匹配;如果类型对得上就返回引用,对不上就返回空。

调用关系:它主要是迁移时期的兼容垫片。旧代码原来可能依赖 anyhow 这类通用错误库的 downcast_ref,这里让调用方不用大改也能继续编译。

CodexErr::to_codex_protocol_error220–247 ↗
fn to_codex_protocol_error(&self) -> CodexErrorInfo

作用:把内部错误翻译成协议层能理解的错误类别。客户端不需要知道 Rust 内部有多少错误分支,只需要收到一个稳定的错误类型。

数据流:进去的是一个具体的 CodexErr;函数按类别把它映射成 CodexErrorInfo,比如额度问题、服务器过载、沙箱错误、认证失败等;有些网络类错误还会带上 HTTP 状态码,也就是网页请求返回的数字状态。

调用关系:它会调用 CodexErr::http_status_code_value 取状态码,并被 CodexErr::to_error_event 使用。它站在内部错误和外部协议之间,像翻译官一样把复杂错误压成客户端可消费的信息。

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

CodexErr::to_error_event249–259 ↗
fn to_error_event(&self, message_prefix: Option<String>) -> ErrorEvent

作用:把一个错误包装成可以发给客户端的错误事件。这个事件里既有人看的文字,也有机器看的错误分类。

数据流:进去的是 CodexErr 和一个可选的消息前缀;函数先把错误转成文字,再按需加上前缀,然后调用 to_codex_protocol_error 得到协议错误类型;出来的是 ErrorEvent。

调用关系:当系统需要把错误通过协议流发出去时会用它。它把展示文案和机器分类合在一起,方便前端既能显示,也能做特殊处理。

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

CodexErr::http_status_code_value261–270 ↗
fn http_status_code_value(&self) -> Option<u16>

作用:从错误里尽量取出 HTTP 状态码。HTTP 状态码就是服务器请求结果的数字,比如 403 表示禁止访问、500 表示服务器内部错误。

数据流:进去的是一个 CodexErr;函数只检查那些可能带状态码的错误,如重试耗尽、异常状态、连接失败、响应流失败;出来的是一个可选的 u16 数字,没有就返回空。

调用关系:它被 CodexErr::to_codex_protocol_error 使用,也会被其他错误上报路径使用,比如 from_codex_err 和 notify_stream_error。它负责把藏在底层网络错误里的状态码挖出来。

调用图:被 3 处调用(from_codex_err, notify_stream_error, to_codex_protocol_error)。

ConnectionFailedError::fmt279–281 ↗
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result

作用:把“连接失败”错误变成一句能打印的文字。这样日志和用户提示里不会只看到难懂的结构体。

数据流:进去的是 ConnectionFailedError,里面包含 reqwest 的网络错误;函数把底层错误拼到“Connection failed”后面;出来的是写入格式化器的一段文本。

调用关系:当有人调用 to_string、打印错误或把它放进 CodexErr 显示时,会走到这里。它不改变错误,只负责把它说清楚。

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

ResponseStreamFailed::fmt291–301 ↗
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result

作用:把“读取服务器响应流时失败”变成一段说明文字,并尽量带上请求编号,方便排查。

数据流:进去的是 ResponseStreamFailed,里面有网络错误和可选 request id;函数生成“读取服务器响应时出错”的消息,如果有请求编号就附上;出来的是可打印文本。

调用关系:服务器已经开始回应但中途断掉时,这类错误会被显示或记录。它给排障人员留下 request id 这类线索。

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

UnexpectedResponseError::display_body320–331 ↗
fn display_body(&self) -> String

作用:决定服务器异常响应的正文该怎么显示。它会优先提取清楚的错误消息,不行再显示截短后的原文。

数据流:进去的是 UnexpectedResponseError,主要读取 body;函数先调用 extract_error_message 尝试从 JSON 里拿 error.message,拿不到就去掉空白并检查是否为空,最后必要时调用 truncate_with_ellipsis 截短;出来的是适合显示的正文文字。

调用关系:它被 UnexpectedResponseError::fmt 调用,是异常 HTTP 响应文案的一部分。它让错误提示既尽量有用,又不会被超长响应淹没。

调用图:调用 2 个内部函数(extract_error_message, truncate_with_ellipsis);被 1 处调用(fmt)。

UnexpectedResponseError::extract_error_message333–345 ↗
fn extract_error_message(&self) -> Option<String>

作用:从服务器返回的 JSON 正文里找出真正的人类可读错误消息。很多接口会把错误放在 error.message 这个位置。

数据流:进去的是响应正文字符串;函数尝试把它当 JSON 解析,再一路找到 error.message,并去掉首尾空白;如果成功且非空就返回这段消息,否则返回空。

调用关系:它只被 display_body 使用。它承担“从机器格式里抠出人话”的小任务,失败时让上层继续用原文兜底。

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

UnexpectedResponseError::friendly_message347–375 ↗
fn friendly_message(&self) -> Option<String>

作用:专门识别 Cloudflare 拦截访问的情况,并生成更友好的提示。Cloudflare 是常见的网络防护服务,可能会因为地区等原因拦截请求。

数据流:进去的是 UnexpectedResponseError;函数先确认状态码是 403 禁止访问,再检查正文里是否同时出现 Cloudflare 和 blocked;如果符合,就把状态、网址、cf-ray、request id、认证错误等线索拼成一句解释;不符合就返回空。

调用关系:它被 UnexpectedResponseError::fmt 优先调用。它让某些常见但晦涩的网页拦截错误,变成用户和客服都更容易理解的信息。

调用图:被 1 处调用(fmt);外部调用 1 个(format!)。

UnexpectedResponseError::fmt379–403 ↗
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result

作用:把“服务器返回了没想到的状态码”整理成最终可打印的错误消息。

数据流:进去的是 UnexpectedResponseError;函数先问 friendly_message 有没有更友好的说法,有就直接用;否则调用 display_body 得到正文摘要,再拼上状态码、网址、cf-ray、request id、认证错误等信息;出来的是写入格式化器的文本。

调用关系:当 UnexpectedResponseError 被打印、记录或放进 CodexErr::UnexpectedStatus 显示时会用到它。它是这类 HTTP 异常错误的最终出口。

调用图:调用 2 个内部函数(display_body, friendly_message);外部调用 2 个(format!, write!)。

truncate_with_ellipsis408–420 ↗
fn truncate_with_ellipsis(text: &str, max_bytes: usize) -> String

作用:把太长的文字从末尾截短,并加上省略号。这样错误提示不会占满屏幕或拖垮界面。

数据流:进去的是一段文本和最大字节数;如果文本不超长就原样返回;如果超长,就找到一个不会切坏 UTF-8 字符的位置截断,再加上“...”;出来的是较短的字符串。

调用关系:它被 UnexpectedResponseError::display_body 使用。它特别注意不要把中文、表情等多字节字符切坏。

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

truncate_text422–427 ↗
fn truncate_text(content: &str, policy: TruncationPolicy) -> String

作用:按指定策略截短文本。策略可以按字节数,也可以按 token 数,token 可以粗略理解成模型读文字时用的小片段单位。

数据流:进去的是内容字符串和 TruncationPolicy;如果策略是 Bytes,就调用 truncate_middle_chars 从中间省略字符;如果策略是 Tokens,就调用 truncate_middle_with_token_budget 按模型预算截;出来的是截短后的字符串。

调用关系:它被 get_error_message_ui 使用,用来限制界面错误消息大小。它把“怎么截”这件事集中在一个地方。

调用图:被 1 处调用(get_error_message_ui);外部调用 2 个(truncate_middle_chars, truncate_middle_with_token_budget)。

RetryLimitReachedError::fmt436–446 ↗
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result

作用:把“重试次数已经用完”变成一条可读消息,并告诉最后一次服务器状态是什么。

数据流:进去的是 RetryLimitReachedError,包含最后的 HTTP 状态码和可选 request id;函数把这些拼成一句“超过重试限制”的文字;出来的是可打印文本。

调用关系:当网络或服务错误重试太多次后,这个格式化函数会用于日志、界面或协议消息。它告诉人们不是没试,而是已经试到上限了。

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

UsageLimitReachedError::fmt459–553 ↗
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result

作用:生成“用量达到上限”的用户提示。它会根据套餐、工作区额度、促销文案和恢复时间,给出不同的下一步建议。

数据流:进去的是 UsageLimitReachedError,里面可能有套餐类型、重置时间、限额快照、促销消息和限额类型;函数按优先级判断:先看是否是某个具体模型或额度名,再看工作区信用和花费上限,再看促销消息,最后按 Free、Plus、Pro、Team、Enterprise 等套餐生成文案;出来的是一段最终提示文字。

调用关系:当 CodexErr::UsageLimitReached 需要显示时会调用它。它是用户体验很关键的一层,因为同样是“不能继续用”,不同用户该看到的解决办法不一样。

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

retry_suffix556–563 ↗
fn retry_suffix(resets_at: Option<&DateTime<Utc>>) -> String

作用:生成“什么时候再试”的句尾。它用于那些只能等待恢复的提示。

数据流:进去的是可选的 UTC 重置时间;如果有时间,就调用 format_retry_timestamp 转成本地好读格式并返回“Try again at ...”;如果没有,就返回“Try again later.”;出来的是一段英文句尾。

调用关系:它被 UsageLimitReachedError::fmt 间接使用,用在额度类提示里。它把时间格式化细节和主文案分开。

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

retry_suffix_after_or565–572 ↗
fn retry_suffix_after_or(resets_at: Option<&DateTime<Utc>>) -> String

作用:生成“或者等到某个时间再试”的句尾。它适合放在“升级/购买/联系管理员,或者稍后再试”这种提示后面。

数据流:进去的是可选的 UTC 重置时间;如果有时间,就调用 format_retry_timestamp 转成本地时间并返回“or try again at ...”;如果没有,就返回“or try again later.”;出来的是一段英文句尾。

调用关系:它主要服务 UsageLimitReachedError::fmt。和 retry_suffix 的区别是语气上多了“or”,方便接在替代方案后面。

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

format_retry_timestamp574–585 ↗
fn format_retry_timestamp(resets_at: &DateTime<Utc>) -> String

作用:把 UTC 时间转换成用户本地时间,并格式化成好读的恢复时间。

数据流:进去的是一个 UTC 时间;函数把它转成本地时区,再调用 now_for_retry 取得当前时间;如果恢复时间是今天,只显示几点几分,否则显示月份、日期、年份和时间,并调用 day_suffix 给英文日期加 st、nd、rd、th;出来的是格式化后的字符串。

调用关系:它被 retry_suffix 和 retry_suffix_after_or 调用。它负责把机器时间变成用户能直觉理解的时间。

调用图:调用 2 个内部函数(day_suffix, now_for_retry);被 2 处调用(retry_suffix, retry_suffix_after_or);外部调用 2 个(with_timezone, format!)。

day_suffix587–597 ↗
fn day_suffix(day: u32) -> &'static str

作用:给英文日期数字选择正确的后缀,比如 1st、2nd、3rd、4th。

数据流:进去的是日期中的日数字;函数先特殊处理 11、12、13,因为它们都用 th,再按个位数决定 st、nd、rd 或 th;出来的是后缀字符串。

调用关系:它被 format_retry_timestamp 使用。它是一个小工具,让英文日期看起来更自然。

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

now_for_retry605–613 ↗
fn now_for_retry() -> DateTime<Utc>

作用:取得当前 UTC 时间,供重试时间格式化时判断是不是今天。测试环境里还可以用假时间,保证测试结果稳定。

数据流:进去没有业务输入;在测试模式下,它先看有没有设置 NOW_OVERRIDE,有就返回这个假时间;否则调用 Utc::now 获取真实当前时间;出来的是当前时间。

调用关系:它被 format_retry_timestamp 调用。它把“现在是什么时候”隔离出来,这样测试不用依赖真实时钟。

调用图:被 1 处调用(format_retry_timestamp);外部调用 1 个(now)。

EnvVarError::fmt625–631 ↗
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result

作用:把“缺少环境变量”错误变成清楚的提示。环境变量可以理解成系统外部塞给程序的配置值,比如密钥或开关。

数据流:进去的是 EnvVarError,包含变量名和可选说明;函数先写出缺少哪个变量,如果有补救说明就追加上去;出来的是可打印文本。

调用关系:当 CodexErr::EnvVar 被显示时会用到它。它帮助用户知道不是程序随便坏了,而是少了哪项外部配置。

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

get_error_message_ui634–668 ↗
fn get_error_message_ui(e: &CodexErr) -> String

作用:生成适合界面显示的错误消息。它会特别照顾沙箱执行失败这类情况,优先展示命令真正输出的错误内容。

数据流:进去的是 CodexErr;如果是沙箱拒绝执行,它先看合并输出,再看 stderr 和 stdout,最后才用退出码兜底;如果是沙箱超时,就显示超时时长;其他错误直接转成字符串;最后调用 truncate_text,把消息限制在大约 2KB 内;出来的是界面要显示的短文本。

调用关系:这是 UI 展示错误时的重要入口。它会调用 truncate_text 控制长度,并把底层命令输出整理成人能看懂的形式,避免用户只看到泛泛的“sandbox error”。

调用图:调用 1 个内部函数(truncate_text);外部调用 3 个(format!, to_string, Bytes)。

protocol/src/mcp.rs源码 ↗
data_modelcross-cutting

这个文件像一本“小字典”,规定 MCP 服务器发来的信息在 Codex 里应该长什么样。比如一个工具叫什么、需要什么输入;一个资源在哪里、是什么类型、多大;一次请求的编号是字符串还是数字。它还特别照顾 JSON 和 TypeScript:JSON 是常见的数据交换格式,TypeScript 是前端常用语言,所以这里的类型都能自动生成对应说明,方便别的地方安全使用。文件后半段有一组“适配器”:外部 MCP JSON 可能字段名不完全一样,比如 inputSchema 和 input_schema 都可能出现,这里会把它们统一转成 Codex 自己的类型。还有一个小心处理资源大小的函数,避免大数字硬塞进小范围整数时出错;太大的数字会变成空值,而不是乱掉。

函数细节9
RequestId::fmt21–26 ↗
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result

作用:把请求编号变成一段可显示的文字。请求编号可能本来就是文字,也可能是数字;这个函数让它们都能被打印、记录日志或展示。

数据流:进去的是一个 RequestId:要么包着字符串,要么包着整数。函数检查是哪一种;如果是字符串,就直接写出这段字;如果是整数,就按数字格式写出。出来的是格式化是否成功的结果,不会改动原来的编号。

调用关系:当别的代码想把 RequestId 当成普通文字显示时,Rust 的显示机制会自动走到这里。它只做最后一步“写到输出里”的事;字符串版本会把活交给底层的写字符串动作。

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

deserialize_lossy_opt_i64171–187 ↗
fn deserialize_lossy_opt_i64(deserializer: D) -> Result<Option<i64>, D::Error>

作用:把 JSON 里的可选数字读成 Rust 的 i64(有正负范围的 64 位整数),并且尽量不让不合适的大数字造成崩溃。它专门用于资源大小 size 这种字段。

数据流:进去的是 JSON 反序列化器,也就是正在读 JSON 的机器。它先尝试读出一个可有可无的 JSON 数字;如果没有数字,就返回 None。若是普通 i64 范围内的数字,就保留下来;若是无符号大整数,就尝试安全转换;如果大到 i64 装不下,就返回 None。出来的是 Option<i64>,表示“有一个可用大小”或“没有可用大小”。

调用关系:ResourceSerde 读取 size 字段时会用它来把外部 JSON 变成安全的内部数值。测试函数也专门验证它的效果:大但能装下的数保留,负数保留,太大的数丢成空值。

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

Tool::from210–231 ↗
fn from(value: ToolSerde) -> Self

作用:把临时解析出来的工具数据 ToolSerde,整理成 Codex 对外使用的 Tool。它的作用像把快递外包装拆掉,把里面的物品放进正式货架。

数据流:进去的是 ToolSerde,里面已经从 JSON 读出了工具名、标题、说明、输入格式、输出格式、图标、额外信息等字段。函数把这些字段逐个取出来,再原样放进正式的 Tool 结构。出来的是一个 Tool,不额外改写字段内容。

调用关系:它通常由 Tool::from_mcp_value 解析 JSON 后通过转换机制间接使用。Tool::from_mcp_value 负责“读 JSON”,这个函数负责“换成正式类型”,两者合起来完成外部 MCP 工具到 Codex 工具的转换。

Resource::from256–279 ↗
fn from(value: ResourceSerde) -> Self

作用:把临时解析出来的资源数据 ResourceSerde,整理成正式的 Resource。这样 Codex 后续看到的资源字段名和类型都是统一的。

数据流:进去的是 ResourceSerde,里面包含资源名、地址 uri、说明、媒体类型、大小、标题、图标和额外信息。函数把这些字段取出后放进 Resource。出来的是正式 Resource;其中 size 已经在读取阶段用安全方式处理过。

调用关系:它通常接在 Resource::from_mcp_value 后面使用。前一步把外部 JSON 读进临时结构,这一步把临时结构变成系统内通用的资源对象。

ResourceTemplate::from299–316 ↗
fn from(value: ResourceTemplateSerde) -> Self

作用:把临时解析出来的资源模板 ResourceTemplateSerde,整理成正式的 ResourceTemplate。资源模板可以理解成“资源地址的填写规则”,比如某类文件地址该怎么拼。

数据流:进去的是 ResourceTemplateSerde,包含模板地址 uri_template、名称、标题、说明、媒体类型和注解。函数把这些字段逐一搬到 ResourceTemplate。出来的是正式资源模板,不改变字段含义。

调用关系:它配合 ResourceTemplate::from_mcp_value 使用。JSON 先被读成宽松兼容的临时结构,再由这个函数换成 Codex 协议里稳定使用的结构。

Tool::from_mcp_value320–322 ↗
fn from_mcp_value(value: serde_json::Value) -> Result<Self, serde_json::Error>

作用:把一段 MCP 风格的 JSON 工具描述,转换成 Codex 自己的 Tool。外部来的字段名可能有差异,这里负责兼容并统一。

数据流:进去的是 serde_json::Value,也就是一块还没定型的 JSON 数据。函数先按 ToolSerde 的宽松规则读取它,比如同时接受 inputSchema 和 input_schema;读取成功后再转成正式 Tool。出来的是成功的 Tool,或者 JSON 解析错误。

调用关系:当 protocol_tool_from_rmcp_tool 需要把别的 MCP 工具模型接进 Codex 协议时,会调用它。它站在外部数据和内部协议之间,先做翻译,再把整理工作交给 Tool::from 这类转换。

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

Resource::from_mcp_value326–328 ↗
fn from_mcp_value(value: serde_json::Value) -> Result<Self, serde_json::Error>

作用:把一段 MCP 风格的 JSON 资源描述,转换成 Codex 的 Resource。它特别处理资源大小,避免数字过大时出错。

数据流:进去的是一块 JSON 数据。函数按 ResourceSerde 的规则读取字段,兼容 mimeType 和 mime_type 这样的不同写法;size 字段会经过安全数字转换。读取成功后变成 Resource;读取失败则返回 JSON 错误。

调用关系:resource_from_rmcp 会用它把外部资源接入 Codex。测试 tests::resource_size_deserializes_without_narrowing 也会调用它,确认大数字、负数和超大数字都按预期处理。

调用图:被 2 处调用(resource_from_rmcp, resource_size_deserializes_without_narrowing)。

ResourceTemplate::from_mcp_value332–334 ↗
fn from_mcp_value(value: serde_json::Value) -> Result<Self, serde_json::Error>

作用:把一段 MCP 风格的 JSON 资源模板,转换成 Codex 的 ResourceTemplate。它让不同来源的模板字段名变得统一。

数据流:进去的是 JSON 数据。函数按 ResourceTemplateSerde 的兼容规则读取,比如 uriTemplate 和 uri_template 都能认;然后转换成正式 ResourceTemplate。出来的是资源模板,或解析失败时的错误。

调用关系:它用于需要把外部 MCP 资源模板纳入 Codex 协议的地方。它先完成 JSON 读取,再把字段整理交给 ResourceTemplate::from。

tests::resource_size_deserializes_without_narrowing344–371 ↗
fn resource_size_deserializes_without_narrowing()

作用:验证资源 size 字段不会被错误缩小或读坏。它确保大文件大小、负数和超出范围的数字都有明确结果。

数据流:进去的是测试里临时构造的三段 JSON:一个 50 亿大小的资源、一个 size 为 -1 的资源、一个大到 i64 装不下的资源。测试分别调用 Resource::from_mcp_value 解析它们,然后比较解析出的 size。结果是:能装下的大正数保留,负数保留,太大的数变成 None。

调用关系:这是文件里的安全网,专门盯住 deserialize_lossy_opt_i64 和 Resource::from_mcp_value 的配合是否正确。以后有人改资源解析时,如果把大数字处理弄坏,这个测试会提醒。

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

protocol/src/memory_citation.rs源码 ↗
data_modelcross-cutting

这个文件本身不做计算,也没有函数;它是在规定一种通用的数据格式。MemoryCitation 表示一组引用信息,里面有具体引用条目 entries,也有 rollout_ids,用来记录这些引用来自哪些运行过程或展开记录。每个 MemoryCitationEntry 就是一条具体来源:文件路径 path、开始行 line_start、结束行 line_end,以及一段说明 note。这样做的好处是,别的模块在传递“这段话依据哪里”时,不用各说各话,而是都按同一种结构来写。文件还给这些结构加了序列化能力,也就是可以方便地变成 JSON 这类可传输文本;JsonSchema 和 TS 则让它能生成接口说明和 TypeScript 类型,方便前后端或不同语言之间保持一致。

protocol/src/network_policy.rs源码 ↗
data_modelrequest handling

当程序想访问网络时,系统需要知道:是允许、拒绝,还是要问用户;这个判断是谁给的;访问的是哪个主机和端口;为什么这么判。这个文件里的 NetworkPolicyDecisionPayload 就是装这些信息的“小表格”。它可以从传入的数据里反序列化出来,反序列化就是把 JSON 这类文本数据变成程序能直接使用的结构。字段名按 camelCase 读取,也就是像 decisionSource 这种常见接口写法。这里还有一个很小但重要的判断函数:它专门检查当前结果是不是“由决策器要求询问用户”。这能帮助后续代码区分普通的询问和特定来源的询问,避免把不同情况混在一起处理。

函数细节1
NetworkPolicyDecisionPayload::is_ask_from_decider19–21 ↗
fn is_ask_from_decider(&self) -> bool

作用:这个函数用来判断这份网络策略结果是不是“需要询问”,而且这个要求是由 Decider,也就是自动决策器发出的。调用方可以用它快速分辨一种特殊情况,而不用每次手写两个条件判断。

数据流:进去的是当前这份 NetworkPolicyDecisionPayload,也就是已经装好的网络策略结果。它读取里面的 decision 和 source 两个字段:如果 decision 是 Ask,表示需要询问;同时 source 是 Decider,表示来源是决策器,就返回 true。否则返回 false。它不会修改任何数据,只给出一个是或否的判断。

调用关系:它被 network_approval_context_from_payload 调用。后者在根据这份网络策略结果组装“网络审批上下文”时,会用这个函数先确认是否属于“决策器要求询问”的场景,然后再决定后面该怎样组织审批信息。

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

protocol/src/openai_models.rs源码 ↗
data_modelcross-cutting;模型列表加载、配置解析、构造请求和界面展示时都会用到

可以把这个文件理解成一张“模型说明书模板”。后端把每个可用模型的能力写成统一格式,客户端再按这份格式展示模型、选择默认模型、决定请求里该带哪些选项。这里的大部分内容是数据类型,比如模型信息、升级提示、服务档位、输入类型等;同时也放了一些小规则:旧数据没写输入类型时默认支持文字和图片;未知的新推理强度不会直接报错,而是当作自定义值保留下来;未知的工具模式会被当成没填,避免老客户端被新字段绊倒。它还负责把后端用的 ModelInfo 转成界面更容易用的 ModelPreset,并根据模型的模板和用户选择的“性格”生成最终指令。

函数细节49
ReasoningEffort::as_str54–64 ↗
fn as_str(&self) -> &str

作用:把“推理强度”这个枚举值变成真正要传给服务端的文字,比如 high 或 medium。别人需要打印、保存或发网络请求时会用它。

数据流:进去的是一个 ReasoningEffort 值 → 它按已知档位查出固定字符串,遇到 Custom 就拿里面原本的字符串 → 出来的是可直接放进 JSON 或界面的文本。

调用关系:它是 ReasoningEffort 的基础转换工具;显示函数 fmt 和序列化函数 serialize 都会先找它要标准文字。

调用图:被 2 处调用(fmt, serialize)。

ReasoningEffort::fmt68–70 ↗
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result

作用:让 ReasoningEffort 可以像普通文字一样被打印出来。这样日志、错误信息或界面显示不用另外写转换代码。

数据流:进去的是推理强度和值格式化器 → 它调用 as_str 拿到文字,再写进格式化器 → 出来的是格式化成功或失败的结果。

调用关系:它服务于 Rust 的 Display 打印机制;具体文字交给 as_str 决定,最后交给标准写入方法 write_str。

调用图:调用 1 个内部函数(as_str);外部调用 1 个(write_str)。

ReasoningEffort::schema_name74–76 ↗
fn schema_name() -> String

作用:给 ReasoningEffort 的 JSON Schema(描述 JSON 长什么样的说明书)起名字。生成接口文档或 TypeScript 类型时会用到。

数据流:没有业务输入 → 直接返回固定名字 ReasoningEffort → 不改动任何数据。

调用关系:它属于 JsonSchema 这套自动生成结构说明的接口,配合 json_schema 一起告诉外部系统这个字段怎么传。

ReasoningEffort::json_schema78–93 ↗
fn json_schema(_generator: &mut SchemaGenerator) -> Schema

作用:说明 ReasoningEffort 在 JSON 里应该是一个非空字符串,而不是只允许几个写死的值。这样未来服务端新增强度时,老客户端也能接住。

数据流:进去的是 schema 生成器参数 → 它构造一个“字符串、最短长度为 1、带说明文字”的 Schema → 出来的是这份 JSON 字段说明。

调用关系:它被 JSON Schema 生成流程调用;这个设计和 from_str 的 Custom 分支配套,让新旧版本更兼容。

调用图:外部调用 3 个(new, default, Object)。

ReasoningEffort::serialize97–102 ↗
fn serialize(&self, serializer: S) -> Result<S::Ok, S::Error>

作用:把 ReasoningEffort 写成 JSON 字符串。发给后端或保存配置时会用到。

数据流:进去的是推理强度和序列化器 → 它先用 as_str 得到标准文字,再让序列化器写成字符串 → 出来的是序列化结果。

调用关系:它是 serde(Rust 常用的 JSON 读写框架)调用的入口;真正的取值逻辑仍然复用 as_str。

调用图:调用 1 个内部函数(as_str);外部调用 1 个(serialize_str)。

ReasoningEffort::deserialize106–112 ↗
fn deserialize(deserializer: D) -> Result<Self, D::Error>

作用:从 JSON 字符串读出 ReasoningEffort。收到后端模型清单或用户配置时会用到。

数据流:进去的是 JSON 反序列化器 → 它先读出字符串,再按 from_str 的规则解析 → 出来的是已知档位、自定义档位,或空字符串错误。

调用关系:它接在 serde 的读取流程里;解析规则交给 from_str,所以 JSON 和手动字符串解析保持一致。

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

ReasoningEffort::from_str118–129 ↗
fn from_str(s: &str) -> Result<Self, Self::Err>

作用:把一段文字解析成推理强度。它既认识 none、low、high 等已知值,也允许未来出现的新值。

数据流:进去的是字符串 → 它匹配已知名称;空字符串返回错误;其他非空字符串包装成 Custom → 出来的是 ReasoningEffort 或错误文字。

调用关系:deserialize 会用它读取 JSON;测试也专门确认它能接受未知新值但拒绝空值。

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

default_input_modalities160–162 ↗
fn default_input_modalities() -> Vec<InputModality>

作用:给没写输入类型的老模型数据补默认值:文字和图片都支持。这样旧格式不会因为缺字段就表现得太保守。

数据流:没有输入 → 它新建一个包含 Text 和 Image 的列表 → 出来的是默认输入能力列表。

调用关系:serde 在 input_modalities 字段缺失时会调用它;很多构造模型数据和测试流程也复用它,保证默认行为一致。

调用图:被 43 处调用(preset_to_info, drop_last_n_user_turns_clears_reference_context_for_mixed_developer_context_bundles, drop_last_n_user_turns_ignores_session_prefix_user_messages, drop_last_n_user_turns_preserves_prefix, drop_last_n_user_turns_trims_context_updates_above_rolled_back_turn, for_prompt_strips_images_when_model_does_not_support_images, normalization_retains_local_shell_outputs, normalize_adds_missing_output_for_custom_tool_call, normalize_adds_missing_output_for_custom_tool_call_panics_in_debug, normalize_adds_missing_output_for_function_call (+15 more));外部调用 1 个(vec!)。

deserialize_optional_model_selector305–314 ↗
fn deserialize_optional_model_selector(deserializer: D) -> Result<Option<T>, D::Error>

作用:读取一些“可选的模型选择字段”,比如工具模式或多智能体版本;如果遇到老客户端不认识的新字符串,就当作没填。这样新后端不会轻易弄坏旧客户端。

数据流:进去的是反序列化器和目标类型 → 它先尝试读一个可选字符串;没有就返回 None;有字符串就再尝试转成目标类型,失败也返回 None → 出来的是 Some 值或 None。

调用关系:它被 ModelInfo 的 tool_mode 和 multi_agent_version 字段指定使用,是兼容未来枚举值的安全阀。

调用图:外部调用 3 个(deserialize, String, from_value)。

TruncationPolicyConfig::bytes323–328 ↗
fn bytes(limit: i64) -> Self

作用:快速创建一个“按字节数截断”的上下文限制。模型上下文太长时,系统需要知道按什么单位裁剪。

数据流:进去的是限制数值 limit → 它把模式设为 Bytes,并保存这个限制 → 出来的是 TruncationPolicyConfig 配置对象。

调用关系:很多测试和模型构造代码用它生成默认截断策略;它和 tokens 是两个便捷构造函数。

调用图:被 19 处调用(preset_to_info, remote_model_with_auto_review_override, model_switch_to_smaller_model_updates_token_context_window, test_model_info, test_remote_model, remote_model_friendly_personality_instructions_with_feature, user_turn_personality_remote_model_template_includes_update_message, remote_models_apply_remote_base_instructions, remote_models_get_model_info_uses_longest_matching_prefix, remote_models_long_model_slug_is_sent_with_custom_reasoning (+9 more))。

TruncationPolicyConfig::tokens330–335 ↗
fn tokens(limit: i64) -> Self

作用:快速创建一个“按 token 数截断”的上下文限制。token 可以粗略理解成模型读文字时用的小块单位。

数据流:进去的是限制数值 limit → 它把模式设为 Tokens,并保存这个限制 → 出来的是 TruncationPolicyConfig 配置对象。

调用关系:配置覆盖和一些模型构造流程会用它;它和 bytes 一起避免调用方手动拼结构体。

调用图:被 2 处调用(with_config_overrides, model_with_shell_type)。

default_effective_context_window_percent342–344 ↗
fn default_effective_context_window_percent() -> i64

作用:提供默认的有效上下文比例:95%。意思是模型标称能装很多内容,但系统默认只把其中 95% 当作安全可用空间。

数据流:没有输入 → 返回数字 95 → 不修改任何状态。

调用关系:当 ModelInfo 的 effective_context_window_percent 字段在旧数据里缺失时,serde 会用这个默认值补上。

ModelInfo::resolved_context_window429–431 ↗
fn resolved_context_window(&self) -> Option<i64>

作用:算出这个模型最终可用的上下文窗口大小。上下文窗口可以理解成模型一次能“记住并阅读”的最大内容量。

数据流:进去的是一个 ModelInfo → 它优先看 context_window;如果没有,再看 max_context_window → 出来的是一个可选的数字限制。

调用关系:构造模型上下文、生成请求前的输入,以及 auto_compact_token_limit 都会用它来拿统一的窗口值。

调用图:被 3 处调用(model_context_window, build_stage_one_input_message, auto_compact_token_limit)。

ModelInfo::auto_compact_token_limit433–444 ↗
fn auto_compact_token_limit(&self) -> Option<i64>

作用:算出什么时候该自动压缩对话历史,避免内容太长塞不进模型。它会把阈值限制在上下文窗口的 90% 以内。

数据流:进去的是 ModelInfo → 它先用 resolved_context_window 找窗口,再算 90%;如果配置里也给了阈值,就取两者较小的那个;没有窗口就直接用配置值 → 出来的是可选 token 阈值。

调用关系:它依赖 resolved_context_window;在长对话准备请求前,系统会用这个结果决定是否需要压缩历史。

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

ModelInfo::supports_personality446–450 ↗
fn supports_personality(&self) -> bool

作用:判断这个模型是否真的支持“性格化指令”,比如友好风格或务实风格。不是只看字段存在,而是看模板和变量是否完整。

数据流:进去的是 ModelInfo → 它查看 model_messages 是否存在,并让 ModelMessages 判断是否支持性格 → 出来的是 true 或 false。

调用关系:ModelPreset::from 会调用它,把后端模型信息转换成界面模型预设时标出是否支持性格。

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

ModelInfo::get_model_instructions452–471 ↗
fn get_model_instructions(&self, personality: Option<Personality>) -> String

作用:生成最终发给模型的基础指令。它会把模板里的“性格占位符”替换成用户选择的性格文字;没有模板时就退回普通 base_instructions。

数据流:进去的是 ModelInfo 和可选 Personality → 如果有 instructions_template,就取对应性格文字并替换 {{ personality }};如果没模板但用户要求性格,就记一条警告并返回 base_instructions;如果都没有特殊情况,也返回 base_instructions → 出来的是最终指令字符串。

调用关系:请求模型前会需要这段指令;它会向 ModelMessages 取性格文案,并在缺少模型消息但请求性格时通过 warn! 打日志提醒。

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

ModelMessages::has_personality_placeholder483–488 ↗
fn has_personality_placeholder(&self) -> bool

作用:检查指令模板里有没有 {{ personality }} 这个占位符。没有占位符,就算有性格文案也没地方放。

数据流:进去的是 ModelMessages → 它查看 instructions_template 是否存在,并检查其中是否包含固定占位符 → 出来的是 true 或 false。

调用关系:它只给 supports_personality 使用,是判断模型是否支持性格化的一半条件。

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

ModelMessages::supports_personality490–496 ↗
fn supports_personality(&self) -> bool

作用:判断一套模型消息是否完整支持性格化。必须既有占位符,也有完整的默认、友好、务实三种文案。

数据流:进去的是 ModelMessages → 它先调用 has_personality_placeholder,再检查 instructions_variables 是否存在且 is_complete → 出来的是 true 或 false。

调用关系:ModelInfo::supports_personality 会调用它;它又依赖 ModelInstructionsVariables::is_complete 来确认变量齐不齐。

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

ModelMessages::get_personality_message498–502 ↗
fn get_personality_message(&self, personality: Option<Personality>) -> Option<String>

作用:从模型消息里取出某个性格对应的文字。比如用户选 Friendly,就取友好文案。

数据流:进去的是 ModelMessages 和可选 Personality → 它找到 instructions_variables 后,把选择交给变量对象处理;没有变量就返回 None → 出来的是可选的性格文案。

调用关系:ModelInfo::get_model_instructions 会用它拿替换占位符的内容;具体选择规则由 ModelInstructionsVariables::get_personality_message 决定。

ModelInstructionsVariables::is_complete513–517 ↗
fn is_complete(&self) -> bool

作用:检查性格文案是否三项都填了:默认、友好、务实。用于判断模型能不能稳定支持性格化。

数据流:进去的是 ModelInstructionsVariables → 它逐个检查三个字段是不是都有值 → 出来的是 true 或 false。

调用关系:ModelMessages::supports_personality 会调用它;这是把“能用”和“字段偶然存在”区分开的关键检查。

ModelInstructionsVariables::get_personality_message519–529 ↗
fn get_personality_message(&self, personality: Option<Personality>) -> Option<String>

作用:按用户选择返回对应的性格文案。选择 None 性格时,它返回空字符串,表示明确不要额外性格。

数据流:进去的是变量集合和可选 Personality → 有具体性格就按 Friendly、Pragmatic、None 分支取值;没有具体选择就取默认文案 → 出来的是对应字符串,或缺字段时的 None。

调用关系:ModelMessages::get_personality_message 会把工作交给它;ModelInfo::get_model_instructions 最终用它的结果替换模板占位符。

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

ModelInfoUpgrade::from539–544 ↗
fn from(upgrade: &ModelUpgrade) -> Self

作用:把旧一点的升级信息 ModelUpgrade 转成后端模型信息里使用的 ModelInfoUpgrade。这样两个相近格式之间可以方便互转。

数据流:进去的是 ModelUpgrade 引用 → 它复制升级目标 id,迁移说明没有就补空字符串 → 出来的是 ModelInfoUpgrade。

调用关系:这是 From 转换接口的一部分,供需要把模型预设升级信息整理成 ModelInfoUpgrade 的地方使用。

ModelPreset::from555–584 ↗
fn from(info: ModelInfo) -> Self

作用:把后端返回的详细 ModelInfo 转成客户端选择器更容易用的 ModelPreset。界面通常不需要所有后端细节,只需要展示、筛选和默认选择相关的信息。

数据流:进去的是 ModelInfo → 它复制名称、描述、推理强度、服务档位、升级提示、输入类型等字段;缺描述就用空字符串;默认推理强度缺失就用 None;还调用 supports_personality 算性格支持 → 出来的是 ModelPreset。

调用关系:构建可选模型列表时会用它;测试也验证它保留可用性提示、快速模式和服务档位信息。

调用图:调用 1 个内部函数(supports_personality);被 3 处调用(build_available_models_picks_default_after_hiding_hidden_models, model_preset_preserves_availability_nux, model_preset_supports_fast_mode_from_service_tiers)。

ModelPreset::supports_fast_mode588–596 ↗
fn supports_fast_mode(&self) -> bool

作用:判断这个模型是否支持“快速模式”。它同时兼容新的 service_tiers 字段和旧的 additional_speed_tiers 字段。

数据流:进去的是 ModelPreset → 它先看服务档位里有没有 fast,再看旧速度档位里有没有 fast → 出来的是 true 或 false。

调用关系:界面或请求构造逻辑可以用它决定是否显示快速选项;测试确认新旧两种写法都能识别。

ModelInfo::supports_service_tier600–604 ↗
fn supports_service_tier(&self, service_tier: &str) -> bool

作用:检查模型是否支持某个服务档位,比如 fast。服务档位可以理解成同一个模型的不同服务速度或优先级。

数据流:进去的是 ModelInfo 和档位字符串 → 它遍历 service_tiers,看有没有 id 相同的档位 → 出来的是 true 或 false。

调用关系:service_tier_for_request 会调用它,先确认用户请求的档位确实被该模型支持。

ModelInfo::service_tier_for_request606–611 ↗
fn service_tier_for_request(&self, service_tier: Option<String>) -> Option<String>

作用:决定请求里到底要不要带服务档位。用户选了默认档位或选了模型不支持的档位时,它会不传,避免发出无效请求。

数据流:进去的是 ModelInfo 和可选档位字符串 → 它过滤掉“默认”这个特殊值,再检查模型是否支持该档位 → 出来的是要发给后端的档位字符串,或 None。

调用关系:构造 Responses 请求时会调用它;它内部依赖 supports_service_tier 做合法性检查。

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

ModelPreset::filter_by_auth618–623 ↗
fn filter_by_auth(models: Vec<ModelPreset>, chatgpt_mode: bool) -> Vec<ModelPreset>

作用:根据登录方式过滤模型列表。ChatGPT 模式下显示全部模型;普通 API 模式下只显示支持 API 的模型。

数据流:进去的是模型预设列表和 chatgpt_mode 标记 → 它逐个保留符合当前认证方式的模型 → 出来的是过滤后的列表。

调用关系:构建可用模型和测试期望可见模型时会调用它,是模型选择器显示哪些模型的第一道筛选。

调用图:被 2 处调用(expected_visible_models, build_available_models)。

ModelPreset::mark_default_by_picker_visibility628–637 ↗
fn mark_default_by_picker_visibility(models: &mut [ModelPreset])

作用:重新标出唯一的默认模型。规则是:优先选第一个会出现在选择器里的模型;如果一个都不显示,就选列表第一个。

数据流:进去的是可修改的 ModelPreset 列表 → 它先把所有 is_default 清成 false,再找第一个 show_in_picker 的模型设为 true;找不到就把第一个模型设为 true → 出来没有新对象,但列表里的默认标记被改了。

调用关系:构建可用模型列表后会调用它,保证最终只有一个默认模型;它使用迭代器和 first_mut 修改原列表。

调用图:被 3 处调用(expected_visible_models, list_models_uses_chatgpt_remote_catalog_as_source_of_truth, build_available_models);外部调用 2 个(first_mut, iter_mut)。

tests::test_model647–688 ↗
fn test_model(spec: Option<ModelMessages>) -> ModelInfo

作用:给测试快速造一个完整的 ModelInfo 样品。这样每个测试只需要改自己关心的字段,不用重复写一大坨默认值。

数据流:进去的是可选 ModelMessages → 它填入测试模型的固定名称、能力、截断策略、默认输入类型等 → 出来的是一个可用于测试的 ModelInfo。

调用关系:大量测试都会调用它;它内部用 TruncationPolicyConfig::bytes 和 default_input_modalities 保持默认值和正式代码一致。

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

tests::personality_variables690–696 ↗
fn personality_variables() -> ModelInstructionsVariables

作用:给测试准备一套完整的性格文案:默认、友好、务实。用于验证性格模板替换是否正常。

数据流:没有输入 → 它创建三个字段都存在的 ModelInstructionsVariables → 出来的是测试用变量对象。

调用关系:多个性格相关测试会调用它,避免每个测试重复写同样的文案。

tests::reasoning_effort_accepts_known_and_custom_values699–721 ↗
fn reasoning_effort_accepts_known_and_custom_values()

作用:确认推理强度既能识别已知值,也能保留未来新增的自定义值。这样协议不会因为新档位出现就坏掉。

数据流:进去的是测试里写死的 high 和 max → 它解析、反序列化、序列化并转字符串 → 最后断言结果符合预期。

调用关系:它验证 ReasoningEffort::from_str、deserialize、serialize 和 Display 这些路径的兼容性。

调用图:外部调用 3 个(assert_eq!, Custom, to_string)。

tests::reasoning_effort_rejects_empty_values724–729 ↗
fn reasoning_effort_rejects_empty_values()

作用:确认空字符串不能当作推理强度。空值没有实际含义,应该尽早报错。

数据流:进去的是空字符串 → 它尝试解析成 ReasoningEffort → 出来应当是指定错误信息,测试用断言确认。

调用关系:它专门覆盖 ReasoningEffort::from_str 的错误分支。

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

tests::reasoning_effort_json_schema_is_an_open_string732–752 ↗
fn reasoning_effort_json_schema_is_an_open_string()

作用:确认生成的 JSON Schema 把推理强度描述成“非空字符串”,而不是封死成固定几个枚举值。

数据流:进去的是默认 SchemaGenerator → 它调用 ReasoningEffort::json_schema 得到 schema → 用断言和期望结构比较。

调用关系:它保护 json_schema 的兼容性设计,防止以后误改成只允许旧值。

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

tests::get_model_instructions_uses_template_when_placeholder_present755–764 ↗
fn get_model_instructions_uses_template_when_placeholder_present()

作用:确认有模板和占位符时,最终指令会把占位符替换成所选性格文案。

数据流:进去的是带模板的测试模型和 Friendly 性格 → 它调用 get_model_instructions → 出来应是 Hello friendly,测试用断言确认。

调用关系:它通过 test_model 和 personality_variables 搭数据,验证 ModelInfo::get_model_instructions 的正常模板路径。

调用图:外部调用 3 个(assert_eq!, personality_variables, test_model)。

tests::get_model_instructions_always_strips_placeholder767–817 ↗
fn get_model_instructions_always_strips_placeholder()

作用:确认只要使用模板,占位符都会被替换掉;即使某个性格文案缺失,也不会把 {{ personality }} 原样漏给模型。

数据流:进去的是几组缺部分变量的模板模型和不同性格选择 → 它多次调用 get_model_instructions → 出来应是替换后的文字或空位置,测试逐项断言。

调用关系:它覆盖 ModelInfo::get_model_instructions 在变量不完整时的行为,保证模板里的占位符不会泄漏。

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

tests::get_model_instructions_falls_back_when_template_is_missing820–833 ↗
fn get_model_instructions_falls_back_when_template_is_missing()

作用:确认没有指令模板时,即使用户选择了性格,也会退回基础指令。这样模型至少能收到可用指令。

数据流:进去的是没有 instructions_template 的测试模型和 Friendly 性格 → 它调用 get_model_instructions → 出来应是 base。

调用关系:它验证 ModelInfo::get_model_instructions 的回退路径,也间接说明性格功能依赖模板存在。

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

tests::get_personality_message_returns_default_when_personality_is_none836–842 ↗
fn get_personality_message_returns_default_when_personality_is_none()

作用:确认用户没有明确选择性格时,会使用默认性格文案。

数据流:进去的是完整 personality_variables 和 None 选择 → 它调用 get_personality_message → 出来应是 default。

调用关系:它验证 ModelInstructionsVariables::get_personality_message 的默认分支。

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

tests::get_personality_message845–907 ↗
fn get_personality_message()

作用:全面检查性格文案选择规则:友好取友好,务实取务实,None 性格取空字符串,没选择取默认。

数据流:进去的是多组完整或缺字段的变量对象 → 它分别用不同 Personality 调用 get_personality_message → 出来是对应 Some 字符串或 None,测试逐项断言。

调用关系:它直接保护 ModelInstructionsVariables::get_personality_message 的所有关键分支。

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

tests::model_info_defaults_availability_nux_to_none_when_omitted910–950 ↗
fn model_info_defaults_availability_nux_to_none_when_omitted()

作用:确认老的模型 JSON 少写一些新字段时,ModelInfo 仍能成功读入,并补上安全默认值。

数据流:进去的是一段缺少若干新字段的 JSON → 它反序列化成 ModelInfo → 出来后检查 availability_nux、web_search_tool_type、tool_mode 等默认值是否正确。

调用关系:它验证 ModelInfo 上各种 serde default 设置,保证协议向后兼容。

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

tests::model_info_deserializes_known_tool_mode953–966 ↗
fn model_info_deserializes_known_tool_mode()

作用:确认已知的 tool_mode 字符串能正常读成 ToolMode。tool_mode 表示模型使用工具时的工作模式。

数据流:进去的是 test_model 转成的 JSON,并插入 code_mode_only → 它反序列化成 ModelInfo → 出来应包含 Some(ToolMode::CodeModeOnly)。

调用关系:它验证 deserialize_optional_model_selector 对已知值的正常路径。

调用图:外部调用 4 个(assert_eq!, test_model, String, to_value)。

tests::model_info_treats_unknown_tool_mode_as_omitted969–987 ↗
fn model_info_treats_unknown_tool_mode_as_omitted()

作用:确认未知的 tool_mode 不会导致反序列化失败,而是当作没填。这样未来服务端新增模式时,旧客户端还能运行。

数据流:进去的是插入 future_tool_mode 的模型 JSON → 它反序列化后得到 tool_mode 为 None,再序列化回 JSON → 最后确认字段不会再输出。

调用关系:它验证 deserialize_optional_model_selector 对未知值的容错路径,以及 skip_serializing_if 的效果。

调用图:外部调用 5 个(assert!, assert_eq!, test_model, String, to_value)。

tests::model_info_treats_unknown_multi_agent_version_as_omitted990–1003 ↗
fn model_info_treats_unknown_multi_agent_version_as_omitted()

作用:确认未知的 multi_agent_version 也会被当作没填,而不是让整个模型信息读取失败。

数据流:进去的是插入 future_multi_agent_version 的模型 JSON → 它反序列化成 ModelInfo → 出来应是 multi_agent_version 为 None。

调用关系:它和未知 tool_mode 测试一样,保护 deserialize_optional_model_selector 在另一个字段上的兼容行为。

调用图:外部调用 4 个(assert_eq!, test_model, String, to_value)。

tests::resolved_context_window_prefers_context_window1006–1014 ↗
fn resolved_context_window_prefers_context_window()

作用:确认同时有 context_window 和 max_context_window 时,优先使用 context_window。

数据流:进去的是两个窗口字段都填了的测试模型 → 它调用 resolved_context_window → 出来应是 context_window 的值 273000。

调用关系:它验证 ModelInfo::resolved_context_window 的优先级规则。

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

tests::resolved_context_window_falls_back_to_max_context_window1017–1026 ↗
fn resolved_context_window_falls_back_to_max_context_window()

作用:确认没有 context_window 时,会退而使用 max_context_window,并且自动压缩阈值按 90% 计算。

数据流:进去的是只填 max_context_window 的测试模型 → 它调用 resolved_context_window 和 auto_compact_token_limit → 出来分别应是 400000 和 360000。

调用关系:它同时覆盖 ModelInfo::resolved_context_window 的回退路径和 auto_compact_token_limit 的默认计算。

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

tests::model_preset_preserves_availability_nux1029–1051 ↗
fn model_preset_preserves_availability_nux()

作用:确认 ModelInfo 转 ModelPreset 时,不会丢掉模型可用性提示,并且旧的 fast 档位和默认服务档位能保留下来。

数据流:进去的是带 availability_nux、additional_speed_tiers 和 default_service_tier 的 ModelInfo → 它调用 ModelPreset::from → 出来后检查提示、快速模式和默认档位。

调用关系:它验证 ModelPreset::from 和 ModelPreset::supports_fast_mode 对这些字段的处理。

调用图:调用 1 个内部函数(from);外部调用 5 个(new, assert!, assert_eq!, test_model, vec!)。

tests::model_preset_supports_fast_mode_from_service_tiers1054–1065 ↗
fn model_preset_supports_fast_mode_from_service_tiers()

作用:确认新的 service_tiers 字段里写了 fast 时,ModelPreset 能识别快速模式。

数据流:进去的是 service_tiers 含 fast 的测试模型 → 它转成 ModelPreset 并调用 supports_fast_mode → 出来应为 true。

调用关系:它保护 ModelPreset::supports_fast_mode 的新字段路径。

调用图:调用 1 个内部函数(from);外部调用 3 个(assert!, test_model, vec!)。

tests::service_tier_for_request_omits_explicit_default_tier1068–1083 ↗
fn service_tier_for_request_omits_explicit_default_tier()

作用:确认用户显式选择“默认档位”时,请求里不会真的带一个 default 字符串。默认就是不额外指定。

数据流:进去的是支持 fast 的模型和 SERVICE_TIER_DEFAULT_REQUEST_VALUE → 它调用 service_tier_for_request → 出来应是 None。

调用关系:它验证 ModelInfo::service_tier_for_request 对默认档位特殊值的过滤。

调用图:外部调用 3 个(assert_eq!, test_model, vec!)。

tests::service_tier_for_request_filters_unsupported_tiers1086–1106 ↗
fn service_tier_for_request_filters_unsupported_tiers()

作用:确认请求服务档位时,只允许模型支持的档位通过,不支持的会被丢掉。

数据流:进去的是支持 fast 的模型,分别传 fast、unsupported 和 None → 它调用 service_tier_for_request → 出来分别是 Some(fast)、None、None。

调用关系:它验证 ModelInfo::service_tier_for_request 和 supports_service_tier 的配合。

调用图:外部调用 3 个(assert_eq!, test_model, vec!)。

tests::service_tier_for_request_does_not_apply_catalog_default1109–1121 ↗
fn service_tier_for_request_does_not_apply_catalog_default()

作用:确认模型清单里的默认服务档位不会自动塞进每个请求。只有用户明确选择了支持的非默认档位,才会发送。

数据流:进去的是有 default_service_tier 的模型,但请求参数是 None → 它调用 service_tier_for_request → 出来应是 None。

调用关系:它保护请求构造规则,防止把展示用的目录默认值误当成用户请求选项。

调用图:外部调用 3 个(assert_eq!, test_model, vec!)。

protocol/src/parse_command.rs源码 ↗
data_modelcross-cutting

这个文件本身不执行命令,也不读取文件。它像一张标准表格,规定“解析后的命令”应该长什么样。核心是 ParsedCommand 这个枚举,也就是“几选一”的数据类型:命令可能是 Read,表示要读某个文件;可能是 ListFiles,表示要列出文件;可能是 Search,表示要搜索内容;也可能是 Unknown,表示系统没看懂。每一类都会保留原始命令 cmd,方便追踪和调试。Read 还带有文件名和路径,路径用 PathBuf 表示,也就是适合操作系统使用的文件路径。这个类型同时支持序列化和反序列化,也就是可以在程序内部数据和 JSON 这类传输格式之间来回转换;还支持生成 JSON Schema 和 TypeScript 类型,方便前后端或不同模块使用同一套“命令格式说明书”。没有这个文件,各处就可能用不同格式描述命令,容易对不上。

protocol/src/user_input.rs源码 ↗
data_modelcross-cutting

用户输入不一定只是普通文字。有时文字里还夹着图片占位符,有时用户直接上传图片,有时选择了一个技能文件,或者在输入框里点选了某个应用、插件之类的“提及”。这个文件就像一张统一的收货单,把这些不同东西都归到 UserInput 这个枚举里。枚举可以理解成“几种可能情况里选一种”。TextElement 用来标记文字中的特殊片段,比如一段文字其实代表一个图片占位符;它记录的是字节范围,也就是在 UTF-8 字符串底层字节里的起止位置,不是肉眼看到的第几个字。ByteRange 就是这个起止位置的小结构。文件里还设了 MAX_USER_INPUT_TEXT_CHARS,防止一条用户消息太长,把模型可用的上下文空间挤爆。整体上,这个文件主要是协议层的数据约定:让输入可以被序列化、保存、恢复,也能生成 TypeScript 和 JSON Schema,方便不同语言的代码安全地交换数据。

函数细节6
TextElement::new63–68 ↗
fn new(byte_range: ByteRange, placeholder: Option<String>) -> Self

作用:创建一个新的文字特殊片段标记。调用者给它一个位置范围,再可选地给一个显示用占位文字,它就把两者打包成 TextElement。

数据流:进去的是一个 ByteRange,也就是这段特殊内容在原文字里的起止字节位置,以及一个可能为空的 placeholder。函数不做复杂判断,只是把这些值放进新的 TextElement。出来的是一个完整的 TextElement,不会改动外部数据。

调用关系:当别的代码发现用户文字里有一段需要特殊对待的内容时,会用它来造出标记。这个标记之后会跟随 UserInput::Text 一起保存、传输或恢复。

TextElement::map_range75–83 ↗
fn map_range(&self, map: F) -> Self

作用:把一个 TextElement 的位置范围换算成新范围,同时保留原来的占位文字。常见场景是文字被拼接、裁剪或移动后,特殊片段的位置也要跟着改。

数据流:进去的是当前 TextElement 和一个映射函数,这个映射函数知道旧的 ByteRange 应该变成什么新的 ByteRange。函数把旧范围交给映射函数,拿到新范围,再复制原来的 placeholder。出来的是一个新的 TextElement,原来的对象不被修改。

调用关系:它通常在上层代码重排或改写文本时被调用,用来保持“特殊片段仍然指向正确位置”。它把具体怎么换算范围的决定交给调用者,自己只负责安全地重新打包。

TextElement::set_placeholder85–87 ↗
fn set_placeholder(&mut self, placeholder: Option<String>)

作用:修改这个文字特殊片段的显示占位文字。比如原来没有显示名,后来界面知道该显示“图片”或某个更友好的名字,就可以用它补上。

数据流:进去的是一个可变的 TextElement 和一个新的 placeholder,placeholder 可以是文字,也可以是空。函数把对象内部原来的 placeholder 替换掉。出来没有单独返回值,但这个 TextElement 本身已经被改了。

调用关系:它会被需要更新界面显示信息的代码使用。它只改显示用的占位文字,不改 byte_range,所以不会改变这个特殊片段在原文字里的位置。

TextElement::_placeholder_for_conversion_only95–97 ↗
fn _placeholder_for_conversion_only(&self) -> Option<&str>

作用:只在协议类型之间转换时取出内部保存的占位文字。它刻意不去原始 text 里兜底查找,因为转换场景下通常拿不到那段完整文字。

数据流:进去的是一个 TextElement。函数读取它内部的 placeholder,如果有就返回对这段文字的引用,如果没有就返回空。它不读取外部文本,也不修改任何东西。

调用关系:这是一个偏内部用途的辅助函数,文档也提醒普通代码不要优先用它。一般读取展示文字应该用 TextElement::placeholder,因为那个函数还能在 placeholder 缺失时从原文范围里取内容。

TextElement::placeholder99–103 ↗
fn placeholder(&'a self, text: &'a str) -> Option<&'a str>

作用:拿到这个特殊片段应该显示的文字。它会先用明确保存的 placeholder;如果没有,就尝试从原始 text 的对应字节范围里切出一段来当显示内容。

数据流:进去的是 TextElement 和它所属的完整 text。函数先看 TextElement 里有没有 placeholder,有就直接返回;没有的话,就用 byte_range.start 到 byte_range.end 去 text 里取子串。出来的是可能存在的一段字符串引用;如果范围不合法或没有内容,就返回空。

调用关系:界面展示、历史恢复或其他需要显示特殊片段名字的代码会用它。它比 _placeholder_for_conversion_only 更适合日常使用,因为它有“没有专门占位文字就从原文拿”的兜底行为。

ByteRange::from115–120 ↗
fn from(range: std::ops::Range<usize>) -> Self

作用:把 Rust 标准的范围写法转换成这个协议自己的 ByteRange。这样调用者可以用常见的 start..end 写法,再交给协议结构保存。

数据流:进去的是一个 std::ops::Range<usize>,也就是包含 start 和 end 的半开区间,start 包含在内,end 不包含在内。函数把 range.start 放进 ByteRange.start,把 range.end 放进 ByteRange.end。出来的是一个 ByteRange,不做额外校验。

调用关系:当代码已经有标准范围值,却需要填入 TextElement 的 byte_range 时,会用到这个转换。它是小型适配器,让普通 Rust 范围和本文件定义的协议数据能顺畅衔接。

protocol/src/items.rs源码 ↗
data_modelcross-cutting

可以把一轮对话想成一张流水账:用户说了什么,助手说了什么,中间搜了什么、看了什么图片、改了哪些文件、调用了哪个工具,都要按统一格式记下来。这个文件就是这张流水账的“账本格式”。核心是 TurnItem,它像一个总盒子,里面能装不同种类的条目。每种条目保存自己需要的信息,比如 FileChangeItem 记录文件改动,McpToolCallItem 记录外部工具调用,ReasoningItem 记录助手的思考摘要。文件里还有一批转换函数,把这些新条目翻译成旧版 EventMsg 事件;这样前端、日志或旧接口还没完全迁移时,也能继续工作。另一个重点是 hook prompt:它把钩子程序给模型的提示包装成 XML 文本,再从模型消息里解析回来,保证特殊字符和来源编号不会丢。

函数细节31
ContextCompactionItem::new229–233 ↗
fn new() -> Self

作用:创建一个新的“上下文压缩”记录。上下文压缩就是把太长的对话历史整理变短,方便后续继续对话。

数据流:进去不需要参数 → 它生成一个新的随机 id → 出来一个带 id 的 ContextCompactionItem,表示发生了一次压缩。

调用关系:压缩任务运行时会用它来留下记录,比如本地压缩和远程压缩流程都会创建这个条目;它的 id 后续可用于在一轮对话流水账里识别这次压缩。

调用图:被 3 处调用(run_compact_task_inner_impl, run_remote_compact_task_inner_impl, run_remote_compact_task_inner_impl);外部调用 1 个(new_v4)。

ContextCompactionItem::as_legacy_event235–237 ↗
fn as_legacy_event(&self) -> EventMsg

作用:把上下文压缩条目翻译成旧版事件。这样旧的事件消费者也能知道“对话历史已经被压缩过”。

数据流:进去是当前 ContextCompactionItem → 它不需要额外字段,只生成一个 ContextCompacted 事件 → 出来一个 EventMsg。

调用关系:当 TurnItem::as_legacy_events 遇到上下文压缩条目时,会调用它,把新账本里的记录转成老系统认识的事件。

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

ContextCompactionItem::default241–243 ↗
fn default() -> Self

作用:提供默认创建方式。别人只要说“给我一个默认的上下文压缩条目”,它就按 new 的方式生成。

数据流:进去不需要参数 → 它调用 ContextCompactionItem::new → 出来一个带新随机 id 的条目。

调用关系:这是 Rust 的 Default 约定,方便通用代码或测试在不知道细节时也能创建这个类型。

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

UserMessageItem::new247–253 ↗
fn new(content: &[UserInput]) -> Self

作用:创建一条用户消息记录。用户消息可能不只是文字,也可能包含图片或本地图片。

数据流:进去是一组 UserInput 输入片段 → 它复制这些片段,并生成新的随机 id,client_id 先留空 → 出来一个 UserMessageItem。

调用关系:解析用户输入、记录用户提示、测试默认事件等流程都会用它;它是用户开口之后进入 TurnItem 流水账的入口之一。

调用图:被 7 处调用(parse_user_message, inspect_pending_input, record_user_prompt_and_emit_turn_item, item_completed_event_defaults_missing_completed_at_ms, item_started_event_from_non_web_search_emits_no_legacy_events, item_started_event_requires_started_at_ms, user_message_item_legacy_event_preserves_image_details);外部调用 2 个(to_vec, new_v4)。

UserMessageItem::as_legacy_event255–267 ↗
fn as_legacy_event(&self) -> EventMsg

作用:把新的用户消息格式翻译成旧版用户消息事件。旧版事件需要把文字、图片链接、本地图片和文字标记分开放好。

数据流:进去是 UserMessageItem → 它分别取出拼接后的文字、图片地址、图片清晰度、本地图片路径、本地图片清晰度和文字元素位置 → 出来一个 UserMessage 类型的 EventMsg。

调用关系:TurnItem::as_legacy_events 遇到用户消息时会调用它;它再把具体拆分工作交给 message、image_urls、image_details、local_image_paths、local_image_details 和 text_elements。

调用图:调用 6 个内部函数(image_details, image_urls, local_image_details, local_image_paths, message, text_elements);外部调用 1 个(UserMessage)。

UserMessageItem::message269–278 ↗
fn message(&self) -> String

作用:从用户输入里只拿文字部分,并拼成一整段消息。图片等非文字内容在这里会被跳过。

数据流:进去是 UserMessageItem 的 content 列表 → 它逐个看输入片段,文字就取出,非文字就当空字符串 → 出来一段连续文本。

调用关系:UserMessageItem::as_legacy_event 用它来填旧版事件里的 message 字段,因为旧格式只把文字放在这个字段里。

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

UserMessageItem::text_elements280–306 ↗
fn text_elements(&self) -> Vec<TextElement>

作用:整理文字里的特殊标记位置。比如某段文字里有占位符,它要把位置换算到拼接后的整段消息里。

数据流:进去是多个用户输入片段 → 它只处理文字片段,把每个文字元素的字节范围加上前面文字的长度偏移 → 出来一组位置已经对齐整段消息的 TextElement。

调用关系:UserMessageItem::as_legacy_event 会调用它。它解决的是“多段文字合并后,原来每段内部的位置还能不能对得上”的问题。

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

UserMessageItem::image_urls308–316 ↗
fn image_urls(&self) -> Vec<String>

作用:从用户消息里挑出网络图片地址。文字和本地图片不会出现在结果里。

数据流:进去是 content 列表 → 它筛选 Image 输入并复制 image_url → 出来一个图片网址列表。

调用关系:UserMessageItem::as_legacy_event 用它来填旧版事件里的 images 字段,让旧界面仍能显示用户附带的网络图片。

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

UserMessageItem::image_details318–328 ↗
fn image_details(&self) -> Vec<Option<ImageDetail>>

作用:取出网络图片的清晰度要求。清晰度可能为空,表示使用默认值。

数据流:进去是 content 列表 → 它筛选网络图片的 detail 字段,再去掉末尾连续的默认空值 → 出来一个清晰度列表。

调用关系:UserMessageItem::as_legacy_event 会调用它;它内部交给 trim_trailing_default_image_details 做收尾清理,保持旧版数据更简洁。

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

UserMessageItem::local_image_paths330–338 ↗
fn local_image_paths(&self) -> Vec<std::path::PathBuf>

作用:从用户消息里挑出本地图片文件路径。它只关心存在机器上的图片。

数据流:进去是 content 列表 → 它筛选 LocalImage 输入并复制路径 → 出来一个本地图片路径列表。

调用关系:UserMessageItem::as_legacy_event 用它来填旧版事件里的 local_images 字段。

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

UserMessageItem::local_image_details340–350 ↗
fn local_image_details(&self) -> Vec<Option<ImageDetail>>

作用:取出本地图片的清晰度要求。末尾没特别指定的默认值会被省掉。

数据流:进去是 content 列表 → 它筛选本地图片的 detail 字段,再清掉末尾连续的 None → 出来一个本地图片清晰度列表。

调用关系:UserMessageItem::as_legacy_event 会调用它;清理动作由 trim_trailing_default_image_details 完成。

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

trim_trailing_default_image_details353–360 ↗
fn trim_trailing_default_image_details(
    mut details: Vec<Option<ImageDetail>>,
) -> Vec<Option<ImageDetail>>

作用:把图片清晰度列表末尾那些“没指定,也就是默认”的项删掉。这样旧格式不用带一串没意义的空值。

数据流:进去是 Vec<Option<ImageDetail>>,也就是一串可能为空的清晰度设置 → 它从末尾开始,只要最后一个是 None 就弹掉 → 出来更短但含义相同的列表。

调用关系:网络图片和本地图片的 detail 整理都会用它;它是一个小工具函数,专门让旧版事件的字段保持紧凑。

调用图:被 2 处调用(image_details, local_image_details);外部调用 1 个(matches!)。

HookPromptItem::from_fragments363–370 ↗
fn from_fragments(id: Option<&String>, fragments: Vec<HookPromptFragment>) -> Self

作用:用多个 hook prompt 片段组装成一个 HookPromptItem。hook prompt 是外部钩子程序插入给模型看的提示。

数据流:进去是可选 id 和一组片段 → 有 id 就沿用,没有就生成新的随机 id → 出来一个完整 HookPromptItem。

调用关系:解析可见 hook prompt 和 parse_hook_prompt_message 都会用它;它把零散片段收成一个能放进 TurnItem 的条目。

调用图:被 2 处调用(parse_visible_hook_prompt_message, parse_hook_prompt_message)。

HookPromptFragment::from_single_hook374–379 ↗
fn from_single_hook(text: impl Into<String>, hook_run_id: impl Into<String>) -> Self

作用:快速创建一个来自单次钩子运行的提示片段。它把提示文字和这次钩子运行的编号绑在一起。

数据流:进去是 text 和 hook_run_id,二者可以是能转成字符串的值 → 它转换成 String → 出来一个 HookPromptFragment。

调用关系:测试和构造 hook prompt 时会用它,省得手工填写结构体字段。

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

build_hook_prompt_message382–403 ↗
fn build_hook_prompt_message(fragments: &[HookPromptFragment]) -> Option<ResponseItem>

作用:把 hook prompt 片段打包成模型能接收的用户消息。它会把每个片段序列化成 XML,避免文字里的特殊符号和来源编号混在一起。

数据流:进去是一组 HookPromptFragment → 它丢掉 hook_run_id 为空的片段,把其余片段变成 InputText 内容 → 如果没有有效内容就返回 None,否则返回一个 role 为 user 的 ResponseItem::Message。

调用关系:运行一轮对话、回放响应、以及相关测试都会用它;它在把钩子提示送进模型前做包装,后面 parse_hook_prompt_message 可以再拆回来。

调用图:被 6 处调用(rebuilds_hook_prompt_items_from_rollout_response_items, test_hook_prompt_raw_response_emits_item_completed, detects_hook_prompt_fragment_and_roundtrips_escaping, parses_hook_prompt_message_as_distinct_turn_item, run_turn, hook_prompt_roundtrips_multiple_fragments);外部调用 2 个(iter, new_v4)。

parse_hook_prompt_message405–424 ↗
fn parse_hook_prompt_message(
    id: Option<&String>,
    content: &[ContentItem],
) -> Option<HookPromptItem>

作用:从模型消息内容里识别并还原 hook prompt。只有所有内容都是可解析的 hook prompt 文本时,它才会成功。

数据流:进去是可选 id 和一组 ContentItem → 它要求每项都是 InputText,并逐项解析成 HookPromptFragment → 如果片段为空或有一项不符合,就返回 None;否则返回 HookPromptItem。

调用关系:处理模型返回项、决定是否发出 hook prompt 完成事件、以及测试往返转换时会用它;成功后它交给 HookPromptItem::from_fragments 组装最终条目。

调用图:调用 1 个内部函数(from_fragments);被 3 处调用(handle_response_item, maybe_emit_hook_prompt_item_completed, hook_prompt_roundtrips_multiple_fragments);外部调用 1 个(iter)。

parse_hook_prompt_fragment426–434 ↗
fn parse_hook_prompt_fragment(text: &str) -> Option<HookPromptFragment>

作用:解析单段 hook prompt XML 文本。它从 XML 里拿出提示正文和 hook_run_id。

数据流:进去是一段文本 → 它先去掉首尾空白,再按 hook_prompt XML 格式解析 → 如果 XML 不合法或 hook_run_id 为空就返回 None,否则返回 HookPromptFragment。

调用关系:上下文用户片段识别、可见 hook prompt 解析、回放 hook prompt 文本和兼容旧格式测试都会用它;它是识别 hook prompt 的最小解析器。

调用图:被 4 处调用(is_contextual_user_fragment, parse_visible_hook_prompt_message, rollout_hook_prompt_texts, hook_prompt_parses_legacy_single_hook_run_id)。

serialize_hook_prompt_fragment436–445 ↗
fn serialize_hook_prompt_fragment(text: &str, hook_run_id: &str) -> Option<String>

作用:把单个 hook prompt 片段写成 XML 文本。这样提示正文和 hook_run_id 能一起安全传递。

数据流:进去是提示 text 和 hook_run_id → 如果 hook_run_id 为空就返回 None,否则尝试生成 hook_prompt XML 字符串 → 成功则返回 XML 文本,失败返回 None。

调用关系:build_hook_prompt_message 在打包 hook prompt 时依赖它;它和 parse_hook_prompt_fragment 一写一读,保证片段可以往返。

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

AgentMessageItem::new448–455 ↗
fn new(content: &[AgentMessageContent]) -> Self

作用:创建一条助手消息记录。这里的助手消息目前主要是文本内容。

数据流:进去是一组 AgentMessageContent → 它复制内容,生成随机 id,并把 phase 和 memory_citation 先设为空 → 出来一个 AgentMessageItem。

调用关系:当系统需要把助手输出放进 TurnItem 流水账时会用它;后续可通过 as_legacy_events 转成旧版助手消息事件。

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

AgentMessageItem::as_legacy_events457–468 ↗
fn as_legacy_events(&self) -> Vec<EventMsg>

作用:把助手消息拆成旧版助手消息事件。每段文本内容都会变成一个事件。

数据流:进去是 AgentMessageItem → 它遍历 content,把 Text 内容的文字、阶段信息 phase、记忆引用 memory_citation 放进 AgentMessageEvent → 出来一组 EventMsg。

调用关系:TurnItem::as_legacy_events 遇到助手消息时会调用它;这让旧界面仍能逐段显示助手输出。

ReasoningItem::as_legacy_events472–491 ↗
fn as_legacy_events(&self, show_raw_agent_reasoning: bool) -> Vec<EventMsg>

作用:把助手的推理记录翻译成旧版事件。默认只输出摘要,是否输出原始推理内容由开关控制。

数据流:进去是 ReasoningItem 和 show_raw_agent_reasoning 布尔值 → 它先把每条 summary_text 变成 AgentReasoning 事件;如果开关为真,再把 raw_content 变成 AgentReasoningRawContent 事件 → 出来一组 EventMsg。

调用关系:TurnItem::as_legacy_events 遇到 Reasoning 条目时会调用它;这个开关保护了原始推理内容,不会无意中总是显示。

调用图:外部调用 3 个(new, AgentReasoning, AgentReasoningRawContent)。

WebSearchItem::as_legacy_event495–501 ↗
fn as_legacy_event(&self) -> EventMsg

作用:把一次网页搜索结果记录转成旧版网页搜索结束事件。

数据流:进去是 WebSearchItem → 它复制搜索 id、查询词和搜索动作 → 出来一个 WebSearchEnd 类型的 EventMsg。

调用关系:TurnItem::as_legacy_events 遇到 WebSearch 条目时会调用它,让旧的事件流知道一次搜索已经结束。

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

ImageGenerationItem::as_legacy_event505–513 ↗
fn as_legacy_event(&self) -> EventMsg

作用:把图片生成记录转成旧版图片生成结束事件。它会带上状态、结果、可能修订过的提示词和保存路径。

数据流:进去是 ImageGenerationItem → 它复制 call_id、status、revised_prompt、result、saved_path → 出来一个 ImageGenerationEnd 类型的 EventMsg。

调用关系:TurnItem::as_legacy_events 遇到 ImageGeneration 条目时会调用它;旧界面可据此显示图片生成完成情况。

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

FileChangeItem::as_legacy_begin_event517–524 ↗
fn as_legacy_begin_event(&self, turn_id: String) -> EventMsg

作用:把文件改动条目转成“开始应用补丁”的旧版事件。补丁可以理解成一包准备写入文件的修改。

数据流:进去是 FileChangeItem 和 turn_id → 它复制改动内容、调用 id、轮次 id,并把 auto_approved 缺省当作 false → 出来一个 PatchApplyBegin 事件。

调用关系:需要通知旧系统“文件修改开始了”时会用它;它和 as_legacy_end_event 分别对应开始和结束两个时间点。

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

FileChangeItem::as_legacy_end_event526–537 ↗
fn as_legacy_end_event(&self, turn_id: String) -> Option<EventMsg>

作用:把文件改动条目转成“补丁应用结束”的旧版事件。只有已经有状态时才会生成事件。

数据流:进去是 FileChangeItem 和 turn_id → 如果 status 为空就返回 None;否则复制输出、错误、改动内容,并根据 status 是否 Completed 判断 success → 出来一个 PatchApplyEnd 事件。

调用关系:TurnItem::as_legacy_events 对文件改动通常只发结束事件;如果文件改动还没结束,这个函数会返回 None,避免旧系统误以为已经完成。

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

McpToolCallItem::as_legacy_begin_event541–552 ↗
fn as_legacy_begin_event(&self) -> EventMsg

作用:把 MCP 工具调用转成旧版“工具开始调用”事件。MCP 可以理解成模型调用外部工具的一套协议。

数据流:进去是 McpToolCallItem → 它复制服务器名、工具名、参数、资源地址和插件 id;如果参数是 JSON null,就不放参数 → 出来一个 McpToolCallBegin 事件。

调用关系:当一次外部工具调用刚开始时会用它;它让旧事件流能展示“正在调用哪个工具”。

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

McpToolCallItem::as_legacy_end_event554–573 ↗
fn as_legacy_end_event(&self) -> Option<EventMsg>

作用:把 MCP 工具调用转成旧版“工具调用结束”事件。它会区分成功结果和错误信息。

数据流:进去是 McpToolCallItem → 如果有 result 就当成功,如果没有 result 但有 error 就当失败,如果两者都没有就返回 None;同时还要求 duration 存在,否则也返回 None → 出来一个 McpToolCallEnd 事件。

调用关系:TurnItem::as_legacy_events 遇到 McpToolCall 条目时会调用它;这个函数只在信息足够完整时才发结束事件,避免生成半截事件。

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

TurnItem::id577–592 ↗
fn id(&self) -> String

作用:拿到任意一种 TurnItem 的 id。调用者不用先关心它到底是用户消息、工具调用还是文件改动。

数据流:进去是一个 TurnItem → 它按具体变体取出内部 item 的 id 并复制 → 出来一个 String。

调用关系:这是 TurnItem 总盒子的统一取号口;需要排序、更新、查找某个条目时可以用它,而不用写一堆类型判断。

TurnItem::as_legacy_events594–617 ↗
fn as_legacy_events(&self, show_raw_agent_reasoning: bool) -> Vec<EventMsg>

作用:把任意 TurnItem 转成旧版事件列表。它是新条目格式通往旧事件系统的总转换口。

数据流:进去是 TurnItem 和是否显示原始推理的开关 → 它按条目种类分别转换:用户、助手、搜索、图片生成、文件改动、MCP、推理、上下文压缩会生成事件;hook prompt、计划、睡眠等有些条目不会生成旧事件 → 出来一组 EventMsg。

调用关系:旧系统需要消费新流水账时会调用它;它再把具体工作分派给各个条目的 as_legacy_event 或 as_legacy_events 函数。

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

tests::hook_prompt_roundtrips_multiple_fragments626–639 ↗
fn hook_prompt_roundtrips_multiple_fragments()

作用:测试多个 hook prompt 片段能不能先打包成消息,再完整解析回来。它防止 XML 转换把文字或 hook_run_id 弄丢。

数据流:进去是测试里手写的两个 HookPromptFragment → 它调用 build_hook_prompt_message 生成消息,再取出 content 调用 parse_hook_prompt_message → 最后断言解析出的 fragments 和原始值完全一样。

调用关系:这是 hook prompt 打包和解析的往返测试;它同时覆盖 build_hook_prompt_message 和 parse_hook_prompt_message 的配合。

调用图:调用 2 个内部函数(build_hook_prompt_message, parse_hook_prompt_message);外部调用 3 个(assert_eq!, panic!, vec!)。

tests::hook_prompt_parses_legacy_single_hook_run_id642–655 ↗
fn hook_prompt_parses_legacy_single_hook_run_id()

作用:测试旧格式的单个 hook prompt XML 还能被解析。这样老数据或老接口不会因为新结构而坏掉。

数据流:进去是一段写死的 XML 字符串 → 它调用 parse_hook_prompt_fragment → 最后断言得到的 text 和 hook_run_id 符合预期。

调用关系:这是兼容性测试,专门盯住 parse_hook_prompt_fragment;如果以后改 XML 解析方式,这个测试能提醒开发者别破坏旧格式。

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

protocol/src/protocol.rs源码 ↗
data_modelcross-cutting

可以把这个文件理解成项目里的“协议说明书加小工具箱”。它不主要负责做某个具体业务,而是规定很多消息长什么样、怎么从旧格式升级到新格式、怎么转成字符串、怎么判断权限和状态。比如沙盒策略会说明哪些目录能写,事件会兼容旧版显示方式,Token 用量会累计模型消耗,多代理通信会把一个代理发给另一个代理的消息包装成模型能读的格式。它还很注意向后兼容:旧历史记录里只有老字段时,也会尽量转换成现在的权限配置。因为这些类型贯穿整个系统,所以这里的小判断一旦错了,可能影响会话恢复、权限安全、用量统计和界面显示。 可以把这个文件理解成系统里的“统一表格和翻译手册”。一边是新版本内部使用的事件和权限模型,比如文件系统沙盒、联网限制、图片生成、网页搜索、MCP 工具调用;另一边是旧版事件格式和 JSON 数据。这个文件保证它们互相转换时不丢信息、不误解权限,也能兼容少字段的老数据。本段里的函数主要是测试:它们用临时目录、假事件和 JSON 样本,检查文件访问规则是否算对,事件开始/结束是否能翻译成旧事件,缺失字段是否按预期报错或给默认值,以及二进制数据是否用 Base64 这种安全文本格式传输。

函数细节159
TurnEnvironmentSelections::new126–134 ↗
fn new(
        legacy_fallback_cwd: AbsolutePathBuf,
        environments: Vec<TurnEnvironmentSelection>,
    ) -> Self

作用:创建一份“本轮运行环境选择”。它把一个旧版备用工作目录和一组明确选择的环境放在一起,方便后面统一传递。

数据流:输入是备用目录和环境列表 → 函数不做额外计算,只把它们装进结构体 → 输出一个新的 TurnEnvironmentSelections。

调用关系:会话创建、内部启动、恢复覆盖检查等流程会用它来生成环境选择对象,后续代码再按这个对象决定本轮在哪些目录或环境里运行。

调用图:被 30 处调用(collect_resume_override_mismatches_includes_service_tier, build_environment_override, run_review_on_session, spawn_internal, absolute_cwd_update_with_turn_environment_is_allowed, empty_turn_environments_clear_primary_environment, environment_settings_preserve_explicit_primary_cwd, make_session_and_context, make_session_and_context_with_auth_config_home_and_rx, make_session_configuration_for_tests (+15 more))。

GitSha::new143–145 ↗
fn new(sha: &str) -> Self

作用:把一段 Git 提交号文字包装成 GitSha 类型。这样代码里能清楚区分“普通字符串”和“Git 提交哈希”。

数据流:输入是一段提交号字符串 → 复制成 owned String → 输出 GitSha。

调用关系:收集 Git 信息、保存线程元数据、测试序列化时会调用它,让 Git 提交号以统一类型在系统中流动。

调用图:被 8 处调用(thread_list_includes_git_info, thread_metadata_update_can_clear_stored_git_fields, test_git_info_serialization, stored_thread, branch_remote_and_distance, collect_git_info, get_head_commit_hash, backfill_sessions_preserves_existing_git_branch_and_fills_missing_git_fields)。

RealtimeVoice::wire_name244–266 ↗
fn wire_name(self) -> &'static str

作用:把实时语音枚举值转成网络或配置里使用的小写名字。比如内部叫 Alloy,对外传输时叫 alloy。

数据流:输入是一个语音选项 → 按固定表查它的文本名 → 输出静态字符串。

调用关系:这是语音类型对外显示或传输时的翻译表,避免各处自己拼写语音名字。

RealtimeVoicesList::builtin280–308 ↗
fn builtin() -> Self

作用:给系统提供一份内置可用的实时语音清单,并指定每一代语音的默认声音。没有它,列出语音或校验默认语音时就没有标准答案。

数据流:没有输入 → 创建 v1、v2 两组语音列表并填入默认值 → 输出 RealtimeVoicesList。

调用关系:列出实时语音、选择默认语音、校验用户指定声音时都会用这份内置清单。

调用图:被 4 处调用(thread_realtime_list_voices, default_realtime_voice, validate_realtime_voice, realtime_conversation_list_voices);外部调用 1 个(vec!)。

Op::from666–674 ↗
fn from(value: Vec<UserInput>) -> Self

作用:把一组用户输入项直接变成一个“用户输入操作”。这是一个省事转换,让调用方不用手动填一堆默认字段。

数据流:输入是 Vec<UserInput> → 放进 Op::UserInput,同时把 JSON 输出格式、客户端元数据、额外上下文和线程设置设为默认 → 输出 Op。

调用关系:当代码只关心“用户说了这些内容”时,可以通过这个转换快速构造操作,后续调度逻辑再按 Op 类型处理。

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

InterAgentCommunication::new694–710 ↗
fn new(
        author: AgentPath,
        recipient: AgentPath,
        other_recipients: Vec<AgentPath>,
        content: String,
        trigger_turn: bool,
    ) -> Self

作用:创建一条普通的代理间消息。代理就是系统里的子助手;这个函数说明谁发、发给谁、内容是什么、是否要触发对方开始工作。

数据流:输入是发送者、接收者、其他接收者、明文内容和触发标记 → 设置加密内容和元数据为空 → 输出通信对象。

调用关系:多代理流程、子代理完成转发、内部消息排队等地方会用它创建普通消息,然后再记录或发送给模型。

调用图:被 27 处调用(maybe_start_completion_watcher, ensure_v2_agent_loaded_reloads_registered_unloaded_agent, multi_agent_v2_completion_queues_message_for_direct_parent, send_inter_agent_communication_without_turn_queues_message_without_triggering_turn, spawn_agent_can_fork_parent_thread_history_with_sanitized_items, spawn_agent_fork_last_n_turns_keeps_only_recent_turns, inter_agent_assistant_msg, forward_child_completion_to_parent, make_mail, inter_agent_assistant_message (+15 more))。

InterAgentCommunication::new_encrypted712–728 ↗
fn new_encrypted(
        author: AgentPath,
        recipient: AgentPath,
        other_recipients: Vec<AgentPath>,
        encrypted_content: String,
        trigger_turn: bool,
    ) -> Self

作用:创建一条加密的代理间消息。它不把正文放在普通内容里,而是放在 encrypted_content 字段里。

数据流:输入是发送者、接收者、其他接收者、加密文本和触发标记 → 明文内容留空,加密内容填入 → 输出通信对象。

调用关系:从工具消息转换、记忆序列化、加密消息入队等流程会用它;之后 to_model_input_item 会把它包装成模型能识别的加密消息格式。

调用图:被 4 处调用(encrypted_inter_agent_communication_clears_existing_last_task_message, communication_from_tool_message, serializes_inter_agent_communications_for_memory, queued_encrypted_inter_agent_communication_renders_message_envelope);外部调用 1 个(new)。

InterAgentCommunication::to_response_input_item730–738 ↗
fn to_response_input_item(&self) -> ResponseInputItem

作用:把代理间消息变成一条可放进响应输入里的消息。它会把整个通信对象序列化成 JSON 文本。

数据流:输入是当前通信对象 → 转成 JSON 字符串,放进 assistant 角色的输出文本里,并标记为 commentary 阶段 → 输出 ResponseInputItem。

调用关系:用于把代理间通信塞回响应输入流里,同时保留“评论/旁白阶段”的标记,测试会确认这个阶段不会丢。

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

InterAgentCommunication::to_model_input_item740–770 ↗
fn to_model_input_item(&self) -> ResponseItem

作用:把代理间消息变成模型真正读取的输入项。普通消息直接给正文;加密消息会加一个信封,说明消息类型、任务名和发送者。

数据流:输入是通信对象 → 如果有加密内容,就生成说明文本加加密载荷;否则只生成明文内容 → 输出 ResponseItem::AgentMessage。

调用关系:record_inter_agent_communication 会用它把代理消息写进模型上下文,让模型知道哪个代理给哪个代理发了什么。

调用图:被 1 处调用(record_inter_agent_communication);外部调用 2 个(to_string, vec!)。

InterAgentCommunication::is_message_content772–774 ↗
fn is_message_content(content: &[ContentItem]) -> bool

作用:判断一段内容是不是一条代理间通信。它只关心能不能从内容里解析出 InterAgentCommunication。

数据流:输入是内容列表 → 调用 from_message_content 尝试解析 → 输出 true 或 false。

调用关系:识别代理间指令内容时会用它,避免把普通文本误当成代理通信。

调用图:被 1 处调用(is_inter_agent_instruction_content);外部调用 1 个(from_message_content)。

InterAgentCommunication::from_message_content776–783 ↗
fn from_message_content(content: &[ContentItem]) -> Option<Self>

作用:从一段消息内容里尝试还原代理间通信对象。它只接受单条输入文本或单条输出文本,里面必须是 JSON。

数据流:输入是 ContentItem 列表 → 如果正好是一条文本,就按 JSON 解析 → 成功输出通信对象,失败输出 None。

调用关系:触发轮次边界等逻辑会用它判断历史消息里是否藏着一条代理间通信。

调用图:被 1 处调用(is_trigger_turn_boundary);外部调用 1 个(from_str)。

Op::kind787–816 ↗
fn kind(&self) -> &'static str

作用:给每种操作返回一个稳定的文字类别名。日志、调度、统计或前端显示都可以用这个名字识别操作类型。

数据流:输入是一个 Op → 按枚举分支匹配 → 输出类似 user_input、shutdown、review 的字符串。

调用关系:它是操作类型的统一命名表,避免不同模块对同一种操作叫法不一致。

GranularApprovalConfig::allows_sandbox_approval888–890 ↗
fn allows_sandbox_approval(self) -> bool

作用:告诉调用方是否允许沙盒权限审批。沙盒可以理解成限制程序能访问哪些资源的安全围栏。

数据流:输入是配置对象自身 → 读取 sandbox_approval 字段 → 输出布尔值。

调用关系:生成细粒度审批说明时会查这个开关,决定是否提到沙盒审批能力。

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

GranularApprovalConfig::allows_rules_approval892–894 ↗
fn allows_rules_approval(self) -> bool

作用:告诉调用方是否允许规则审批。它只是读取配置里的 rules 开关。

数据流:输入是配置对象自身 → 读取 rules 字段 → 输出 true 或 false。

调用关系:细粒度审批说明会用它决定规则类审批是否可用。

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

GranularApprovalConfig::allows_skill_approval896–898 ↗
fn allows_skill_approval(self) -> bool

作用:告诉调用方是否允许技能审批。技能可以理解成某些额外能力或工具能力。

数据流:输入是配置对象自身 → 读取 skill_approval 字段 → 输出布尔值。

调用关系:生成审批提示时会用它,测试也确认这个结果完全由字段控制。

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

GranularApprovalConfig::allows_request_permissions900–902 ↗
fn allows_request_permissions(self) -> bool

作用:告诉调用方是否允许请求权限。也就是模型或工具能不能主动提出要更多权限。

数据流:输入是配置对象自身 → 读取 request_permissions 字段 → 输出布尔值。

调用关系:审批说明会根据它决定是否启用“请求权限”这类交互。

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

GranularApprovalConfig::allows_mcp_elicitations904–906 ↗
fn allows_mcp_elicitations(self) -> bool

作用:告诉调用方是否允许 MCP elicitation。MCP 是一种工具/服务连接协议,elicitation 指工具向用户追问信息。

数据流:输入是配置对象自身 → 读取 mcp_elicitations 字段 → 输出布尔值。

调用关系:细粒度审批说明和测试都会用它确认 MCP 追问能力是否打开。

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

NetworkAccess::is_enabled922–924 ↗
fn is_enabled(self) -> bool

作用:判断网络访问是不是完全启用。它把枚举值转成简单的是/否。

数据流:输入是 NetworkAccess → 判断是否等于 Enabled → 输出布尔值。

调用关系:沙盒策略判断是否有完整网络访问时会用到这种开关判断。

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

WritableRoot::is_path_writable1000–1018 ↗
fn is_path_writable(&self, path: &Path) -> bool

作用:判断某个路径在一个可写根目录规则下能不能写。它像门卫一样,先看是不是在允许范围内,再排除只读区域和受保护元数据。

数据流:输入是 WritableRoot 和待检查路径 → 检查路径是否在 root 下、是否落入只读子路径、是否碰到受保护名字 → 输出能否写入。

调用关系:沙盒写权限测试和运行时权限判断会用它逐个检查可写根目录。

调用图:调用 1 个内部函数(path_contains_protected_metadata_name);外部调用 1 个(starts_with)。

WritableRoot::path_contains_protected_metadata_name1020–1032 ↗
fn path_contains_protected_metadata_name(&self, path: &Path) -> bool

作用:检查路径相对根目录的第一层名字是不是受保护的元数据名。这样可以阻止写入某些特殊目录或文件。

数据流:输入是路径 → 去掉根目录前缀,取相对路径第一段 → 和 protected_metadata_names 列表比较 → 输出是否命中。

调用关系:只由 is_path_writable 调用,是写权限判断里的最后一道特殊保护。

调用图:被 1 处调用(is_path_writable);外部调用 1 个(strip_prefix)。

SandboxPolicy::from_str1038–1040 ↗
fn from_str(s: &str) -> Result<Self, Self::Err>

作用:把 JSON 字符串解析成 SandboxPolicy。SandboxPolicy 是沙盒权限策略,描述文件和网络能访问到什么程度。

数据流:输入是字符串 → 交给 serde_json 解析 → 成功得到策略,失败返回错误。

调用关系:用于从配置、历史或命令参数里的文本恢复沙盒策略。

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

FileSystemSandboxPolicy::from_str1046–1048 ↗
fn from_str(s: &str) -> Result<Self, Self::Err>

作用:把 JSON 字符串解析成文件系统沙盒策略。文件系统沙盒策略专门描述磁盘读写权限。

数据流:输入是字符串 → 用 JSON 反序列化 → 输出策略或错误。

调用关系:当文件权限策略以字符串形式保存或传输时,用它还原成可判断的结构。

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

NetworkSandboxPolicy::from_str1054–1056 ↗
fn from_str(s: &str) -> Result<Self, Self::Err>

作用:把 JSON 字符串解析成网络沙盒策略。网络沙盒策略说明能不能访问网络。

数据流:输入是字符串 → 用 JSON 解析 → 输出网络策略或错误。

调用关系:用于配置加载或协议解析时恢复网络权限设置。

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

SandboxPolicy::new_read_only_policy1061–1065 ↗
fn new_read_only_policy() -> Self

作用:创建一个默认只读沙盒策略。只读意思是可以读磁盘,但不能写,网络默认关闭。

数据流:没有输入 → 构造 ReadOnly 且 network_access 为 false → 输出 SandboxPolicy。

调用关系:测试和默认安全模式会用它快速得到保守策略。

SandboxPolicy::new_workspace_write_policy1070–1077 ↗
fn new_workspace_write_policy() -> Self

作用:创建一个默认“工作区可写”的沙盒策略。它允许之后把工作目录等地方加入可写范围,但网络默认关闭。

数据流:没有输入 → 构造 WorkspaceWrite,空可写根列表,并关闭网络及临时目录排除项 → 输出策略。

调用关系:需要在项目目录内写文件、但不想开放全盘写入时,会从这个默认策略开始。

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

SandboxPolicy::has_full_disk_read_access1079–1081 ↗
fn has_full_disk_read_access(&self) -> bool

作用:报告这个旧式沙盒策略是否有完整磁盘读取能力。当前实现总是返回 true。

数据流:输入是策略自身 → 不区分分支 → 输出 true。

调用关系:测试语义一致性和权限展示时会调用它;写权限和网络权限则由其他函数细分判断。

SandboxPolicy::has_full_disk_write_access1083–1090 ↗
fn has_full_disk_write_access(&self) -> bool

作用:判断策略是否允许完整磁盘写入。危险全权限和外部沙盒被视为全写,其余不是。

数据流:输入是 SandboxPolicy → 匹配策略类型 → 输出是否有全盘写权限。

调用关系:写权限测试会先问它;如果不是全盘写,再去检查具体可写根目录。

SandboxPolicy::has_full_network_access1092–1099 ↗
fn has_full_network_access(&self) -> bool

作用:判断策略是否允许完整网络访问。不同沙盒类型的网络字段存法不同,这里统一成一个答案。

数据流:输入是策略 → 按类型读取网络开关或 NetworkAccess → 输出布尔值。

调用关系:测试、权限展示和策略比较会用它判断网络是否开放。

SandboxPolicy::get_writable_roots_with_cwd1104–1192 ↗
fn get_writable_roots_with_cwd(&self, cwd: &Path) -> Vec<WritableRoot>

作用:算出在当前工作目录下哪些根目录可以写。它会把配置里的目录、当前目录、临时目录等合并成一份可写清单。

数据流:输入是沙盒策略和当前目录 cwd → 如果是 WorkspaceWrite,就收集显式可写根、cwd、/tmp、TMPDIR,并为每个根生成只读子路径保护 → 输出 WritableRoot 列表;其他策略输出空列表。

调用关系:写权限判断和测试辅助函数会用它取得可写范围,再交给 WritableRoot::is_path_writable 判断具体路径。

调用图:调用 1 个内部函数(from_absolute_path);外部调用 5 个(from, new, cfg!, error!, var_os)。

EventMsg::from1636–1638 ↗
fn from(event: SubAgentActivityEvent) -> Self

作用:把 SubAgentActivityEvent 包成通用事件 EventMsg。这样子代理活动能和其他事件走同一条事件管道。

数据流:输入是子代理活动事件 → 放入 EventMsg::SubAgentActivity → 输出 EventMsg。

调用关系:事件系统需要统一消息类型时会用这个转换,调用方不用手动写枚举包装。

调用图:外部调用 11 个(CollabAgentInteractionBegin, CollabAgentInteractionEnd, CollabAgentSpawnBegin, CollabAgentSpawnEnd, CollabCloseBegin, CollabCloseEnd, CollabResumeBegin, CollabResumeEnd, CollabWaitingBegin, CollabWaitingEnd (+1 more))。

CodexErrorInfo::affects_turn_status1711–1728 ↗
fn affects_turn_status(&self) -> bool

作用:判断某种错误会不会影响当前轮次状态。有些错误只是辅助操作失败,不应该把整轮对话标成失败。

数据流:输入是错误信息 → 按错误种类分类 → 输出是否影响轮次状态。

调用关系:ErrorEvent::affects_turn_status 会进一步调用它,让错误处理流程知道是否要改变用户看到的轮次状态。

ItemStartedEvent::as_legacy_events1745–1760 ↗
fn as_legacy_events(&self, _: bool) -> Vec<EventMsg>

作用:把新版“项目开始”事件转换成旧版事件。旧客户端还认识 WebSearchBegin、ImageGenerationBegin 这类旧事件名。

数据流:输入是项目开始事件 → 根据项目类型生成对应旧事件列表,无法或不需要转换的返回空列表 → 输出 Vec<EventMsg>。

调用关系:EventMsg::as_legacy_events 会调用它,帮助新版事件兼容旧显示逻辑。

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

default_item_completed_at_ms1775–1777 ↗
fn default_item_completed_at_ms() -> i64

作用:给完成时间提供默认值 0。它主要用于反序列化时缺字段也能有个值。

数据流:没有输入 → 返回整数 0 → 不改动任何状态。

调用关系:作为默认值函数服务于事件数据模型,让旧数据没有 completed_at_ms 时也能解析。

ItemCompletedEvent::as_legacy_events1784–1792 ↗
fn as_legacy_events(&self, show_raw_agent_reasoning: bool) -> Vec<EventMsg>

作用:把新版“项目完成”事件转换成旧版事件。文件变更有专门的结束事件,其他项目交给自身转换。

数据流:输入是完成事件和是否显示原始推理的开关 → 文件变更转成旧结束事件,其他 TurnItem 调自己的转换 → 输出旧 EventMsg 列表。

调用关系:EventMsg::as_legacy_events 会调用它,用来兼容老客户端或老日志格式。

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

AgentMessageContentDeltaEvent::as_legacy_events1804–1806 ↗
fn as_legacy_events(&self, _: bool) -> Vec<EventMsg>

作用:处理代理消息内容增量转旧事件的问题。当前它不产生任何旧事件。

数据流:输入是增量事件 → 直接返回空列表 → 没有状态变化。

调用关系:EventMsg::as_legacy_events 会调用它,但它明确表示这种新事件不映射到旧事件。

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

ReasoningContentDeltaEvent::as_legacy_events1829–1831 ↗
fn as_legacy_events(&self, _: bool) -> Vec<EventMsg>

作用:处理推理内容增量转旧事件的问题。当前没有旧版对应事件,所以返回空。

数据流:输入是推理增量事件 → 返回空 Vec → 不改动数据。

调用关系:由 EventMsg::as_legacy_events 调用,保持统一转换接口。

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

ReasoningRawContentDeltaEvent::as_legacy_events1846–1848 ↗
fn as_legacy_events(&self, _: bool) -> Vec<EventMsg>

作用:处理原始推理内容增量转旧事件的问题。当前也不生成旧事件。

数据流:输入是原始推理增量事件 → 返回空列表 → 无副作用。

调用关系:EventMsg::as_legacy_events 会统一调用各类事件的旧格式转换,这里给出空结果。

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

EventMsg::as_legacy_events1852–1867 ↗
fn as_legacy_events(&self, show_raw_agent_reasoning: bool) -> Vec<EventMsg>

作用:把某些新版事件翻译成旧版事件列表。这样旧界面或旧日志读取器还能理解一部分新事件。

数据流:输入是 EventMsg 和显示原始推理的开关 → 如果是支持转换的新版事件,就转交对应事件方法;否则返回空 → 输出旧事件列表。

调用关系:它是兼容层入口,负责分发到 ItemStartedEvent、ItemCompletedEvent 等具体转换函数。

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

ErrorEvent::affects_turn_status1886–1890 ↗
fn affects_turn_status(&self) -> bool

作用:判断这个错误事件是否会影响当前轮次状态。如果没有更具体的错误信息,默认认为会影响。

数据流:输入是 ErrorEvent → 如果有 codex_error_info 就问它,否则返回 true → 输出布尔值。

调用关系:错误处理 handle_error 会用它决定是否把当前轮次标成错误或中断状态。

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

TokenUsageInfo::new_or_append2018–2042 ↗
fn new_or_append(
        info: &Option<TokenUsageInfo>,
        last: &Option<TokenUsage>,
        model_context_window: Option<i64>,
    ) -> Option<Self>

作用:创建或更新一份 Token 用量汇总。Token 可以理解成模型读写文字时的计费小单位。

数据流:输入是已有汇总、最近一次用量和可选上下文窗口大小 → 没有任何用量就返回 None;否则复制或新建汇总,追加最近用量,并更新窗口大小 → 输出新的汇总。

调用关系:新建用量信息、更新用量信息和相关测试会调用它,append_last_usage 负责真正累加数字。

调用图:被 4 处调用(new, update_token_info, token_usage_info_new_or_append_preserves_context_window_when_not_provided, token_usage_info_new_or_append_updates_context_window_when_provided);外部调用 1 个(default)。

TokenUsageInfo::append_last_usage2044–2047 ↗
fn append_last_usage(&mut self, last: &TokenUsage)

作用:把最近一次 Token 用量加到总用量里,并记录“最近一次”是多少。

数据流:输入是可变汇总和 last 用量 → total_token_usage 累加 last,last_token_usage 替换成 last 的副本 → 原对象被更新。

调用关系:TokenUsageInfo::new_or_append 会调用它完成追加;它再调用 TokenUsage::add_assign 做字段级累加。

调用图:调用 1 个内部函数(add_assign);外部调用 1 个(clone)。

TokenUsageInfo::fill_to_context_window2049–2062 ↗
fn fill_to_context_window(&mut self, context_window: i64)

作用:把用量填满到指定上下文窗口大小。上下文窗口就是模型一次能容纳的最大 Token 数。

数据流:输入是窗口大小 → 计算距离当前总量还差多少,把总量设为窗口大小,把最近用量设为差值 → 更新当前对象。

调用关系:full_context_window 会用它快速构造“窗口已满”的用量信息。

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

TokenUsageInfo::full_context_window2064–2072 ↗
fn full_context_window(context_window: i64) -> Self

作用:创建一份表示上下文窗口已经用满的 Token 用量信息。

数据流:输入是窗口大小 → 先建空汇总并记录窗口 → 调用 fill_to_context_window 填满 → 输出 TokenUsageInfo。

调用关系:set_token_usage_full 会用它把状态标记成上下文已满。

调用图:被 1 处调用(set_token_usage_full);外部调用 1 个(default)。

RateLimitReachedType::from_str2107–2116 ↗
fn from_str(value: &str) -> Result<Self, Self::Err>

作用:把服务端返回的限额类型字符串转成内部枚举。这样代码不用到处比较原始字符串。

数据流:输入是字符串 → 匹配已知限额类型 → 成功输出对应枚举,未知则返回错误文字。

调用关系:解析限额错误时会用它,把 rate_limit_reached 等 wire 名字转换成内部可判断的类型。

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

TokenUsage::is_zero2150–2152 ↗
fn is_zero(&self) -> bool

作用:判断这份 Token 用量是否为零。通常用来决定要不要显示用量摘要。

数据流:输入是 TokenUsage → 检查 total_tokens 是否等于 0 → 输出布尔值。

调用关系:session_summary 会用它避免展示空用量。

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

TokenUsage::cached_input2154–2156 ↗
fn cached_input(&self) -> i64

作用:返回缓存命中的输入 Token 数,并保证不会是负数。缓存输入通常成本更低或另行统计。

数据流:输入是 TokenUsage → 读取 cached_input_tokens,和 0 取较大值 → 输出非负整数。

调用关系:指标上报和非缓存输入计算会用它,确保异常负数不会污染统计。

调用图:被 3 处调用(emit_guardian_token_usage_histograms, emit_token_usage_metrics, non_cached_input)。

TokenUsage::non_cached_input2158–2160 ↗
fn non_cached_input(&self) -> i64

作用:计算没有走缓存的输入 Token 数。它从总输入里减掉缓存输入,并保证结果不为负。

数据流:输入是 TokenUsage → 调用 cached_input 得到缓存量 → input_tokens 减缓存量并取非负 → 输出整数。

调用关系:指标上报、总量展示和 blended_total 会用它。

调用图:调用 1 个内部函数(cached_input);被 3 处调用(emit_guardian_token_usage_histograms, blended_total, new)。

TokenUsage::blended_total2163–2165 ↗
fn blended_total(&self) -> i64

作用:计算用于展示或计量的混合总 Token:非缓存输入加输出。它不把缓存输入算进这个总数。

数据流:输入是 TokenUsage → 取 non_cached_input 加非负输出 Token → 输出非负总数。

调用关系:FinalOutput::fmt 会用它显示总用量,新建指标也会参考它。

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

TokenUsage::tokens_in_context_window2167–2169 ↗
fn tokens_in_context_window(&self) -> i64

作用:返回当前上下文窗口里占用的 Token 数。这里直接使用 total_tokens。

数据流:输入是 TokenUsage → 读取 total_tokens → 输出整数。

调用关系:percent_of_context_window_remaining 会调用它计算剩余百分比。

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

TokenUsage::percent_of_context_window_remaining2181–2192 ↗
fn percent_of_context_window_remaining(&self, context_window: i64) -> i64

作用:计算上下文窗口还剩百分之多少。它会扣掉一个基础保留量,避免小窗口下算出奇怪比例。

数据流:输入是上下文窗口大小 → 若窗口不大于基线则返回 0;否则用已用 Token 算剩余比例,限制在 0 到 100 并四舍五入 → 输出百分比整数。

调用关系:用于向用户或系统展示上下文还够不够用,内部调用 tokens_in_context_window。

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

TokenUsage::add_assign2195–2201 ↗
fn add_assign(&mut self, other: &TokenUsage)

作用:把另一份 Token 用量逐项加到当前用量上。它是最基础的累计器。

数据流:输入是当前可变用量和另一份用量 → 输入、缓存输入、输出、推理输出、总量分别相加 → 当前对象被更新。

调用关系:TokenUsageInfo::append_last_usage 会调用它完成总用量累加。

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

FinalOutput::from2210–2212 ↗
fn from(token_usage: TokenUsage) -> Self

作用:把 TokenUsage 包成最终输出摘要对象。这样最终结果可以带上用量信息。

数据流:输入是 TokenUsage → 放入 FinalOutput 的 token_usage 字段 → 输出 FinalOutput。

调用关系:用于类型转换,让需要 FinalOutput 的地方可以直接接收 TokenUsage。

FinalOutput::fmt2216–2242 ↗
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result

作用:把最终 Token 用量格式化成人能看的文字。它会显示总量、输入、缓存输入、输出和推理输出。

数据流:输入是 FinalOutput 和格式化器 → 读取 token_usage,调用相关计算函数并加千分位分隔 → 写出一行说明文字。

调用关系:当最终输出被打印或转成字符串时会走这里;它依赖 TokenUsage 的 blended_total、non_cached_input、cached_input 等方法。

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

McpToolCallEndEvent::is_success2369–2374 ↗
fn is_success(&self) -> bool

作用:判断一次 MCP 工具调用是否成功。MCP 是连接外部工具的协议,这里还会看工具结果自己有没有标记错误。

数据流:输入是工具调用结束事件 → 如果 result 是 Ok 且 is_error 不是 true,则成功;Err 一律失败 → 输出布尔值。

调用关系:handle_mcp_tool_call_end 会用它决定工具调用结束后该按成功还是失败处理。

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

InitialHistory::scan_rollout_items2432–2438 ↗
fn scan_rollout_items(&self, mut predicate: impl FnMut(&RolloutItem) -> bool) -> bool

作用:在初始历史记录里查找是否有某类记录项。rollout items 可以理解成保存下来的会话流水账。

数据流:输入是历史对象和一个判断函数 → 新会话或清空历史直接 false;恢复或分叉历史逐项检查 → 输出是否有任意项命中。

调用关系:initial_history_has_prior_user_turns 会用它判断历史里是否已有用户轮次。

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

InitialHistory::forked_from_id2440–2454 ↗
fn forked_from_id(&self) -> Option<ThreadId>

作用:找出这段初始历史是从哪个线程分叉来的。分叉就像从旧对话复制一份继续聊。

数据流:输入是 InitialHistory → 新建或清空返回 None;恢复历史查 SessionMeta 的 forked_from_id;分叉历史查原 SessionMeta 的 id → 输出线程 ID 或 None。

调用关系:fork_thread_with_initial_history 会用它记录新线程和来源线程的关系。

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

InitialHistory::session_cwd2456–2462 ↗
fn session_cwd(&self) -> Option<PathBuf>

作用:从历史记录里找出会话当时的工作目录。工作目录就是相对路径默认从哪里开始算。

数据流:输入是 InitialHistory → 新建或清空返回 None;恢复或分叉则扫描 SessionMeta → 输出 PathBuf 或 None。

调用关系:它把具体扫描工作交给 session_cwd_from_items。

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

InitialHistory::get_rollout_items2464–2470 ↗
fn get_rollout_items(&self) -> Vec<RolloutItem>

作用:取出初始历史里的完整流水账条目。没有历史时返回空列表。

数据流:输入是 InitialHistory → 新建或清空给空 Vec;恢复或分叉克隆保存的条目 → 输出 Vec<RolloutItem>。

调用关系:加载恢复线程、快照轮次状态、截断用户消息前历史时会用它取得可操作的历史列表。

调用图:被 3 处调用(load_thread_from_resume_source_or_send_internal, snapshot_turn_state, truncate_before_nth_user_message);外部调用 1 个(new)。

InitialHistory::get_event_msgs2472–2495 ↗
fn get_event_msgs(&self) -> Option<Vec<EventMsg>>

作用:从初始历史里抽出事件消息。它只挑 RolloutItem::EventMsg,忽略其他记录。

数据流:输入是 InitialHistory → 新建或清空返回 None;恢复或分叉扫描条目并克隆事件 → 输出事件列表或 None。

调用关系:创建会话对象时会用它恢复历史事件,让界面或状态能接上过去。

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

InitialHistory::get_base_instructions2497–2512 ↗
fn get_base_instructions(&self) -> Option<BaseInstructions>

作用:从历史记录里找出基础指令。基础指令就是模型一开始遵守的总规则。

数据流:输入是 InitialHistory → 新建或清空返回 None;恢复或分叉扫描 SessionMeta → 输出 BaseInstructions 或 None。

调用关系:恢复或分叉会话时可用它继承原来的基础规则。

InitialHistory::get_dynamic_tools2514–2528 ↗
fn get_dynamic_tools(&self) -> Option<Vec<DynamicToolSpec>>

作用:从历史里找出当时启用的动态工具配置。动态工具是运行时提供给模型的工具清单。

数据流:输入是 InitialHistory → 新建或清空返回 None;恢复或分叉扫描 SessionMeta 的 dynamic_tools → 输出工具列表或 None。

调用关系:恢复会话时可用它保持工具环境一致。

InitialHistory::get_multi_agent_version2530–2540 ↗
fn get_multi_agent_version(&self) -> Option<MultiAgentVersion>

作用:从历史里找出多代理系统版本。多代理版本决定子代理通信和线程行为用哪套规则。

数据流:输入是 InitialHistory → 新建或清空返回 None;恢复或分叉把条目交给 multi_agent_version_from_items → 输出版本或 None。

调用关系:resolve_multi_agent_version 会用它决定当前线程应采用哪个多代理版本。

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

InitialHistory::get_resumed_session_sources2542–2545 ↗
fn get_resumed_session_sources(&self) -> Option<(SessionSource, Option<ThreadSource>)>

作用:获取恢复会话的来源信息,包括会话来源和可选线程来源。

数据流:输入是 InitialHistory → 先找恢复历史里的 SessionMeta → 取 source 和 thread_source 的副本 → 输出二元组或 None。

调用关系:resume_thread_with_history 会用它在恢复线程时保留来源标记。

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

InitialHistory::get_resumed_thread_source2547–2550 ↗
fn get_resumed_thread_source(&self) -> Option<ThreadSource>

作用:获取恢复会话里的线程来源。线程来源说明线程是用户创建、子代理创建,还是某个功能创建。

数据流:输入是 InitialHistory → 查恢复 SessionMeta → 取 thread_source → 输出 ThreadSource 或 None。

调用关系:它复用 get_resumed_session_meta,只对恢复历史有效。

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

InitialHistory::get_resumed_parent_thread_id2552–2555 ↗
fn get_resumed_parent_thread_id(&self) -> Option<ThreadId>

作用:获取恢复会话记录里的父线程 ID。只有有父子线程关系时才会有值。

数据流:输入是 InitialHistory → 查恢复 SessionMeta → 取 parent_thread_id → 输出 ThreadId 或 None。

调用关系:它和其他恢复来源读取函数一样,依赖 get_resumed_session_meta。

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

InitialHistory::get_resumed_session_meta2557–2567 ↗
fn get_resumed_session_meta(&self) -> Option<&SessionMeta>

作用:在恢复历史中找到 SessionMeta。SessionMeta 是会话的身份证,存 ID、来源、目录等元信息。

数据流:输入是 InitialHistory → 只有 Resumed 会扫描历史条目,找到 SessionMeta 后返回引用;其他类型返回 None。

调用关系:get_resumed_session_sources、get_resumed_thread_source、get_resumed_parent_thread_id 都通过它读取恢复元信息。

调用图:被 3 处调用(get_resumed_parent_thread_id, get_resumed_session_sources, get_resumed_thread_source)。

session_cwd_from_items2570–2575 ↗
fn session_cwd_from_items(items: &[RolloutItem]) -> Option<PathBuf>

作用:从一组历史条目里找会话工作目录。它是 InitialHistory::session_cwd 的小助手。

数据流:输入是 RolloutItem 切片 → 逐项查找 SessionMeta → 找到后复制 cwd → 输出路径或 None。

调用关系:InitialHistory::session_cwd 在恢复和分叉历史时会调用它。

调用图:被 1 处调用(session_cwd);外部调用 1 个(iter)。

ThreadSource::as_str2605–2612 ↗
fn as_str(&self) -> &str

作用:把线程来源转成稳定字符串。比如用户线程是 user,子代理线程是 subagent。

数据流:输入是 ThreadSource → 按类型返回固定名字或功能自带名字 → 输出字符串切片。

调用关系:ThreadSource::fmt 会调用它,序列化或显示线程来源时就用这套名字。

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

ThreadSource::fmt2616–2618 ↗
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result

作用:定义 ThreadSource 怎么显示成文字。它直接写入 as_str 的结果。

数据流:输入是 ThreadSource 和格式化器 → 调用 as_str → 把字符串写进格式化器。

调用关系:当 ThreadSource 被 to_string、日志打印或格式化时会调用它。

调用图:调用 1 个内部函数(as_str);外部调用 1 个(write_str)。

ThreadSource::try_from2624–2626 ↗
fn try_from(value: String) -> Result<Self, Self::Error>

作用:把 String 尝试转换成 ThreadSource。它让字符串到线程来源的转换可以走标准 TryFrom 接口。

数据流:输入是 String → 调用 parse → 输出 ThreadSource 或错误。

调用关系:parse 最终会走 ThreadSource::from_str。

String::from2630–2632 ↗
fn from(value: ThreadSource) -> Self

作用:把 ThreadSource 转回 String。它让线程来源可以用标准 From 转换成普通文本。

数据流:输入是 ThreadSource → 调用 to_string → 输出 String。

调用关系:to_string 会经过 ThreadSource::fmt,因此名字和显示逻辑保持一致。

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

ThreadSource::from_str2638–2645 ↗
fn from_str(value: &str) -> Result<Self, Self::Err>

作用:把字符串解析成线程来源。已知名字转成固定类型,其他名字当成功能来源标签。

数据流:输入是字符串 → user、subagent、memory_consolidation 分别映射;其他字符串包装成 Feature → 输出 ThreadSource。

调用关系:ThreadSource::try_from 和反序列化场景会用它,让应用自定义来源标签也能保留下来。

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

SessionSource::fmt2676–2687 ↗
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result

作用:定义会话来源显示成什么文字。会话来源说明这次会话来自 CLI、VSCode、MCP、子代理等。

数据流:输入是 SessionSource 和格式化器 → 按来源写入固定文字,内部和子代理来源会加前缀 → 输出格式化结果。

调用关系:日志、序列化或字符串转换时会用它统一来源名字。

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

SessionSource::from_startup_arg2691–2706 ↗
fn from_startup_arg(value: &str) -> Result<Self, &'static str>

作用:把启动参数里的来源名字解析成 SessionSource。它会去掉空格、转小写,并识别常见别名。

数据流:输入是用户传入字符串 → trim,空字符串报错;转小写后匹配 cli、vscode、exec、mcp 等;未知值作为 Custom → 输出来源或错误。

调用关系:进程启动时会用它记录会话从哪里来,相关测试确认已知值和自定义值的处理。

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

SessionSource::is_internal2708–2710 ↗
fn is_internal(&self) -> bool

作用:判断会话来源是不是内部系统发起的。内部会话不是普通用户入口。

数据流:输入是 SessionSource → 判断是否是 Internal 分支 → 输出布尔值。

调用关系:需要区分用户会话和后台内部会话的地方会用它。

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

SessionSource::is_non_root_agent2712–2717 ↗
fn is_non_root_agent(&self) -> bool

作用:判断会话是否属于非根代理。这里把内部来源和子代理来源都算作非根代理相关。

数据流:输入是 SessionSource → 判断是否是 Internal 或 SubAgent → 输出布尔值。

调用关系:多代理流程可用它区别主会话和派生代理会话。

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

SessionSource::get_nickname2719–2726 ↗
fn get_nickname(&self) -> Option<String>

作用:如果会话来自线程派生的子代理,取出这个代理的昵称。

数据流:输入是 SessionSource → 只有 SubAgent::ThreadSpawn 返回 agent_nickname 副本 → 其他来源返回 None。

调用关系:展示或恢复子代理身份时会用它。

SessionSource::get_agent_role2728–2735 ↗
fn get_agent_role(&self) -> Option<String>

作用:如果会话来自线程派生的子代理,取出这个代理的角色说明。

数据流:输入是 SessionSource → 只有 ThreadSpawn 子代理返回 agent_role 副本 → 其他返回 None。

调用关系:多代理界面或调度逻辑可用它知道子代理承担什么角色。

SessionSource::get_agent_path2737–2744 ↗
fn get_agent_path(&self) -> Option<AgentPath>

作用:如果会话来自线程派生的子代理,取出代理路径。代理路径像组织结构里的地址,用来标明代理位置。

数据流:输入是 SessionSource → 只有 ThreadSpawn 子代理返回 agent_path 副本 → 其他返回 None。

调用关系:代理通信和身份追踪会用这个路径区分不同代理。

SessionSource::restriction_product2746–2756 ↗
fn restriction_product(&self) -> Option<Product>

作用:把会话来源映射到产品限制里的产品名。产品限制用于判断某个功能是否允许在 ChatGPT、Codex、Atlas 等产品中使用。

数据流:输入是 SessionSource → 自定义来源尝试按产品名解析;常见非子代理来源默认 Codex;内部和子代理返回 None → 输出 Product 或 None。

调用关系:matches_product_restriction 会调用它决定当前来源是否满足产品限制。

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

SessionSource::matches_product_restriction2758–2763 ↗
fn matches_product_restriction(&self, products: &[Product]) -> bool

作用:判断当前会话来源是否符合给定产品白名单。空白名单表示不限制。

数据流:输入是 SessionSource 和产品列表 → 如果列表空直接 true;否则先算 restriction_product,再检查是否匹配 → 输出布尔值。

调用关系:功能权限判断会用它过滤只允许某些产品使用的能力。

调用图:调用 1 个内部函数(restriction_product);外部调用 1 个(is_empty)。

SessionSource::parent_thread_id2765–2776 ↗
fn parent_thread_id(&self) -> Option<ThreadId>

作用:如果会话是子代理会话,取出它的父线程 ID。普通来源没有父线程。

数据流:输入是 SessionSource → 子代理来源转交 SubAgentSource::parent_thread_id;其他返回 None。

调用关系:需要追踪父子线程关系时会用它。

SubAgentSource::fmt2780–2794 ↗
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result

作用:定义子代理来源显示成什么文字。线程派生来源会带上父线程 ID 和深度。

数据流:输入是 SubAgentSource 和格式化器 → 按类型写入 review、compact、memory_consolidation、thread_spawn_... 等文字 → 输出格式化结果。

调用关系:SessionSource::fmt 在显示子代理来源时会间接用它。

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

SubAgentSource::kind2798–2806 ↗
fn kind(&self) -> &str

作用:返回子代理来源的大类名字。它比完整显示名更粗,比如线程派生统一叫 thread_spawn。

数据流:输入是 SubAgentSource → 按类型返回固定种类或 Other 的自定义字符串 → 输出 &str。

调用关系:subagent_source_name 会用它生成子代理来源名称。

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

SubAgentSource::parent_thread_id2808–2818 ↗
fn parent_thread_id(&self) -> Option<ThreadId>

作用:从子代理来源里取父线程 ID。只有线程派生的子代理有这个信息。

数据流:输入是 SubAgentSource → ThreadSpawn 返回 parent_thread_id;其他类型返回 None。

调用关系:SessionSource::parent_thread_id 会调用它,把子代理内部细节统一暴露给外层。

InternalSessionSource::fmt2822–2826 ↗
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result

作用:定义内部会话来源显示成什么文字。目前只有 memory_consolidation。

数据流:输入是 InternalSessionSource 和格式化器 → 写入对应固定字符串 → 输出格式化结果。

调用关系:SessionSource::fmt 显示 internal_... 时会用到这个内部来源的字符串形式。

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

multi_agent_version_from_items2829–2852 ↗
fn multi_agent_version_from_items(
    items: &[RolloutItem],
    thread_id: Option<ThreadId>,
) -> Option<MultiAgentVersion>

作用:从历史条目里推断多代理版本。它优先看 SessionMeta,找不到再看 TurnContext。

数据流:输入是历史条目和可选线程 ID → 从后往前找匹配线程的 SessionMeta 版本;没有再从 TurnContext 找 → 输出 MultiAgentVersion 或 None。

调用关系:InitialHistory::get_multi_agent_version 会调用它,恢复或分叉线程时确定多代理规则版本。

调用图:被 1 处调用(get_multi_agent_version);外部调用 1 个(iter)。

SessionMeta::default2911–2931 ↗
fn default() -> Self

作用:创建一份空的会话元信息默认值。它给每个字段一个安全的空值或默认值。

数据流:没有输入 → 填入默认线程 ID、空路径、空字符串、None 字段和默认来源 → 输出 SessionMeta。

调用关系:创建线程、构造测试数据、读取 rollout 摘要时会用它作为起点。

调用图:调用 1 个内部函数(default);被 6 处调用(read_summary_from_rollout_preserves_agent_nickname, read_summary_from_rollout_preserves_forked_from_id, read_summary_from_rollout_returns_empty_preview_when_no_user_message, session_meta_item, session_meta_normalizes_legacy_dynamic_tools, create_thread);外部调用 3 个(new, new, default)。

ResponseItem::from2964–2974 ↗
fn from(value: CompactedItem) -> Self

作用:把压缩后的历史摘要转换成普通 assistant 消息。压缩摘要就是把长历史缩短后留下的一段文字。

数据流:输入是 CompactedItem → 把 message 放进 assistant 的 OutputText 内容 → 输出 ResponseItem::Message。

调用关系:需要把压缩记录重新喂给模型时,这个转换让它看起来像一条普通助手消息。

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

TurnContextItem::permission_profile3029–3044 ↗
fn permission_profile(&self) -> PermissionProfile

作用:取得本轮使用的权限配置。新字段存在就直接用;没有时从旧沙盒策略推导,保证旧历史也能运行。

数据流:输入是 TurnContextItem → 如果已有 permission_profile 就克隆;否则生成文件系统沙盒策略、执行强制级别和网络策略,再合成 PermissionProfile → 输出权限配置。

调用关系:apply_turn_context 和文件系统权限恢复会调用它,是旧沙盒策略迁移到新权限模型的关键入口。

调用图:被 2 处调用(filesystem_from_turn_context_item, apply_turn_context)。

TruncationPolicy::from3055–3060 ↗
fn from(config: crate::openai_models::TruncationPolicyConfig) -> Self

作用:把模型配置里的截断策略转换成协议里的截断策略。截断策略说明太长的内容按字节还是按 Token 裁掉。

数据流:输入是 TruncationPolicyConfig → 根据 mode 选择 Bytes 或 Tokens,并把 limit 转成 usize → 输出 TruncationPolicy。

调用关系:模型配置进入协议层时会用它统一表示截断规则。

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

TruncationPolicy::token_budget3064–3072 ↗
fn token_budget(&self) -> usize

作用:把截断限制换算成大约可用的 Token 数。即使原限制是字节,也能得到模型角度的预算。

数据流:输入是 TruncationPolicy → Tokens 直接返回;Bytes 用工具函数估算 Token 数,转换失败则给最大值 → 输出 usize。

调用关系:模型最大输出 Token 和函数输出截断逻辑会用它。

调用图:被 2 处调用(model_output_max_tokens, truncate_function_output_items_with_policy);外部调用 2 个(approx_tokens_from_byte_count, try_from)。

TruncationPolicy::byte_budget3074–3081 ↗
fn byte_budget(&self) -> usize

作用:把截断限制换算成大约可用的字节数。即使原限制是 Token,也能给文本裁剪函数一个字节预算。

数据流:输入是 TruncationPolicy → Bytes 直接返回;Tokens 用工具函数估算字节数 → 输出 usize。

调用关系:格式化截断文本和工具输出截断时会用它。

调用图:被 3 处调用(formatted_truncate_text, formatted_truncate_text_content_items_with_policy, truncate_function_output_items_with_policy);外部调用 1 个(approx_bytes_for_tokens)。

TruncationPolicy::mul3087–3096 ↗
fn mul(self, multiplier: f64) -> Self::Output

作用:按倍数放大或缩小截断策略的额度。它保持单位不变,只调整数字。

数据流:输入是 TruncationPolicy 和倍率 → Bytes 或 Tokens 的数值乘倍率并向上取整 → 输出新的 TruncationPolicy。

调用关系:作为乘法运算实现,让调用方可以用统一方式调整截断预算。

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

ReviewOutputEvent::default3172–3179 ↗
fn default() -> Self

作用:创建一个空的代码评审输出事件。默认没有发现问题,整体说明为空,信心分为 0。

数据流:没有输入 → findings 为空列表,字符串字段为空,分数为 0.0 → 输出 ReviewOutputEvent。

调用关系:评审流程需要默认值或反序列化缺省值时会用它。

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

McpAuthStatus::fmt3465–3473 ↗
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result

作用:把 MCP 授权状态显示成人能读的文字。比如 NotLoggedIn 会显示成 Not logged in。

数据流:输入是 McpAuthStatus 和格式化器 → 匹配状态到文字 → 写入格式化器。

调用关系:展示 MCP 服务登录状态、日志输出或错误提示时会用它。

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

Product::to_app_platform3493–3499 ↗
fn to_app_platform(self) -> &'static str

作用:把产品枚举转成应用平台名。比如 Chatgpt 对应 chat,Codex 对应 codex。

数据流:输入是 Product → 按产品返回固定平台字符串 → 输出 &'static str。

调用关系:需要把内部产品名传给外部平台参数时会用它。

Product::from_session_source_name3501–3509 ↗
fn from_session_source_name(value: &str) -> Option<Self>

作用:从会话来源名字里识别产品。它只认 chatgpt、codex、atlas。

数据流:输入是字符串 → 去空格并转小写 → 匹配产品名 → 输出 Product 或 None。

调用关系:SessionSource::restriction_product 会调用它,把自定义来源映射到产品限制。

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

Product::matches_product_restriction3511–3513 ↗
fn matches_product_restriction(&self, products: &[Product]) -> bool

作用:判断某个产品是否符合产品限制列表。空列表表示不限制。

数据流:输入是 Product 和产品列表 → 列表为空返回 true,否则检查列表是否包含当前产品 → 输出布尔值。

调用关系:SessionSource::matches_product_restriction 会间接用它完成最终判断。

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

SessionConfiguredEvent::deserialize3658–3725 ↗
fn deserialize(deserializer: D) -> Result<Self, D::Error>

作用:自定义解析会话配置事件,并兼容旧历史格式。旧数据可能只有 sandbox_policy,没有新的 permission_profile,这里会自动补上。

数据流:输入是反序列化器 → 先解析临时 Wire 结构;如果有 permission_profile 就用它,否则用旧 sandbox_policy 和 cwd 推导;再填充缺省 thread_id 等字段 → 输出 SessionConfiguredEvent 或错误。

调用关系:读取持久化 rollout 历史时会走它,是旧会话数据能被新版程序继续打开的重要兼容层。

调用图:调用 1 个内部函数(from_legacy_sandbox_policy_for_cwd);外部调用 2 个(deserialize, missing_field)。

validate_thread_goal_objective3742–3752 ↗
fn validate_thread_goal_objective(value: &str) -> Result<(), String>

作用:检查线程目标描述是否合法。它防止目标为空或太长。

数据流:输入是目标字符串 → 空字符串返回错误;字符数超过上限返回错误;否则返回 Ok → 不修改数据。

调用关系:set_thread_goal 和 handle_create 会在设置或创建目标时调用它,提前拦住不合规输入。

调用图:被 2 处调用(set_thread_goal, handle_create);外部调用 1 个(format!)。

ReviewDecision::to_opaque_string3821–3836 ↗
fn to_opaque_string(&self) -> &'static str

作用:把评审决定转成稳定但不暴露细节的字符串。比如批准、拒绝、超时、带网络策略修改的批准等。

数据流:输入是 ReviewDecision → 按决定种类和网络规则动作匹配 → 输出固定字符串。

调用关系:用于日志、指标或协议字段,让外部只看到统一标签,不需要理解内部结构。

tests::feature_thread_source_serializes_as_its_app_owned_label4142–4151 ↗
fn feature_thread_source_serializes_as_its_app_owned_label() -> Result<()>

作用:测试功能型线程来源能按应用自己的标签序列化和反序列化。也就是说 automation 进去出来还是 automation。

数据流:创建 ThreadSource::Feature("automation") → 转 JSON 并再读回 → 断言 JSON 值和读回对象都符合预期。

调用关系:保护 ThreadSource::from_str、fmt 和 serde 相关行为,避免自定义来源标签被改坏。

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

tests::session_meta_normalizes_legacy_dynamic_tools4154–4204 ↗
fn session_meta_normalizes_legacy_dynamic_tools() -> Result<()>

作用:测试旧格式动态工具能被读成新格式。这样老历史里的工具定义不会因为格式升级而丢失。

数据流:先生成默认 SessionMeta 的 JSON,再塞入旧式 dynamic_tools 数组 → 反序列化成 SessionMeta → 断言变成新的 Namespace 工具结构,并正确处理 defer_loading 默认值。

调用关系:覆盖 SessionMeta 的反序列化兼容逻辑,防止旧 rollout 里的动态工具失效。

调用图:调用 1 个内部函数(default);外部调用 4 个(assert_eq!, json!, from_value, to_value)。

tests::sorted_writable_roots4206–4221 ↗
fn sorted_writable_roots(roots: Vec<WritableRoot>) -> Vec<(PathBuf, Vec<PathBuf>)>

作用:把可写根目录和只读子路径排序,方便测试比较。排序能避免因为列表顺序不同导致测试误报。

数据流:输入 WritableRoot 列表 → 转成 PathBuf 元组,排序每个只读子路径,再排序根列表 → 输出稳定顺序的列表。

调用关系:沙盒策略测试会用它比较 get_writable_roots_with_cwd 的结果。

tests::sandbox_policy_allows_read4223–4225 ↗
fn sandbox_policy_allows_read(policy: &SandboxPolicy, _path: &Path, _cwd: &Path) -> bool

作用:测试辅助函数:判断某个沙盒策略是否允许读。当前直接等同于策略是否有完整磁盘读取权限。

数据流:输入策略、路径和 cwd → 忽略路径和 cwd → 返回 has_full_disk_read_access 的结果。

调用关系:assert_same_sandbox_policy_semantics 会用它比较两个策略的读权限表现。

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

tests::sandbox_policy_allows_write4227–4236 ↗
fn sandbox_policy_allows_write(policy: &SandboxPolicy, path: &Path, cwd: &Path) -> bool

作用:测试辅助函数:判断某路径在某策略下能不能写。它先看是否全盘可写,再看是否落在可写根里。

数据流:输入策略、路径和 cwd → 全盘写则 true;否则取得可写根并逐个调用 is_path_writable → 输出布尔值。

调用关系:assert_same_sandbox_policy_semantics 用它比较预期策略和实际策略的写权限是否一致。

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

tests::session_source_from_startup_arg_maps_known_values4239–4248 ↗
fn session_source_from_startup_arg_maps_known_values()

作用:测试启动来源参数能识别已知值。比如 vscode 应该变成 VSCode,app-server 应该当作 MCP。

数据流:输入固定字符串给 from_startup_arg → unwrap 得到结果 → 用断言比较预期来源。

调用关系:保护 SessionSource::from_startup_arg 对已知别名的映射。

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

tests::inter_agent_communication_response_input_item_preserves_commentary_phase4251–4272 ↗
fn inter_agent_communication_response_input_item_preserves_commentary_phase()

作用:测试代理间通信转成响应输入项时,会保留 commentary 阶段标记。

数据流:构造一条明文代理通信 → 调用 to_response_input_item → 断言输出是 assistant 消息、内容为序列化 JSON、phase 为 Commentary。

调用关系:保护 InterAgentCommunication::to_response_input_item 的协议格式,避免阶段信息丢失。

调用图:调用 1 个内部函数(root);外部调用 2 个(assert_eq!, vec!)。

tests::queued_encrypted_inter_agent_communication_renders_message_envelope4275–4301 ↗
fn queued_encrypted_inter_agent_communication_renders_message_envelope()

作用:测试加密代理消息转给模型时会带正确信封。信封说明消息类型、任务名、发送者,然后再附加加密载荷。

数据流:用 new_encrypted 创建消息 → 调用 to_model_input_item → 断言作者、接收者、说明文本和加密内容都正确。

调用关系:覆盖 InterAgentCommunication::new_encrypted 和 to_model_input_item 的配合。

调用图:调用 2 个内部函数(root, new_encrypted);外部调用 2 个(new, assert_eq!)。

tests::session_source_from_startup_arg_normalizes_custom_values4304–4313 ↗
fn session_source_from_startup_arg_normalizes_custom_values()

作用:测试自定义启动来源会被规范化。比如 Atlas 会变成小写 atlas 并作为 Custom 保存。

数据流:输入 atlas 和带空格大写的 Atlas → 调用 from_startup_arg → 断言都得到 Custom("atlas")。

调用关系:保护启动参数解析中的 trim 和小写化行为。

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

tests::session_source_restriction_product_defaults_non_subagent_sources_to_codex4316–4337 ↗
fn session_source_restriction_product_defaults_non_subagent_sources_to_codex()

作用:测试常见非子代理来源默认归到 Codex 产品。这样产品限制判断有稳定默认值。

数据流:分别检查 Cli、VSCode、Exec、Mcp、Unknown → 调用 restriction_product → 断言都是 Some(Product::Codex)。

调用关系:覆盖 SessionSource::restriction_product 对普通来源的默认策略。

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

tests::session_source_restriction_product_does_not_guess_subagent_products4340–4350 ↗
fn session_source_restriction_product_does_not_guess_subagent_products()

作用:测试子代理和内部会话不会被猜成某个产品。因为它们是派生来源,不该随便套产品身份。

数据流:构造 SubAgent::Review 和 Internal::MemoryConsolidation → 调用 restriction_product → 断言都是 None。

调用关系:保护 SessionSource::restriction_product 对代理类来源的保守行为。

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

tests::session_source_restriction_product_maps_custom_sources_to_products4353–4370 ↗
fn session_source_restriction_product_maps_custom_sources_to_products()

作用:测试自定义来源名能映射到已知产品。chatgpt、ATLAS、codex 可识别,atlas-dev 不识别。

数据流:构造不同 Custom 来源 → 调用 restriction_product → 断言返回对应 Product 或 None。

调用关系:覆盖 Product::from_session_source_name 和 SessionSource::restriction_product 的组合。

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

tests::session_source_matches_product_restriction4373–4388 ↗
fn session_source_matches_product_restriction()

作用:测试会话来源是否符合产品限制列表。它覆盖匹配、不匹配、默认 Codex 和空限制列表。

数据流:构造不同 SessionSource 和产品列表 → 调用 matches_product_restriction → 用断言确认 true 或 false。

调用关系:保护 SessionSource::matches_product_restriction、restriction_product 和 Product::matches_product_restriction 的整体语义。

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

tests::sandbox_policy_probe_paths4390–4403 ↗
fn sandbox_policy_probe_paths(policy: &SandboxPolicy, cwd: &Path) -> Vec<PathBuf>

作用:生成一组用于测试沙盒权限的探测路径。它把 cwd、可写根和只读子路径都收集起来。

数据流:输入策略和 cwd → 从 cwd 开始,追加每个可写根及其只读子路径,排序去重 → 输出路径列表。

调用关系:assert_same_sandbox_policy_semantics 会用这些路径逐个比较读写权限。

调用图:外部调用 2 个(get_writable_roots_with_cwd, vec!)。

tests::assert_same_sandbox_policy_semantics4405–4441 ↗
fn assert_same_sandbox_policy_semantics(
        expected: &SandboxPolicy,
        actual: &SandboxPolicy,
        cwd: &Path,
    )

作用:测试辅助函数:确认两个沙盒策略在实际语义上一样。它不只比结构,还比读写和网络行为。

数据流:输入预期策略、实际策略和 cwd → 比较全盘读、全盘写、网络访问;再收集探测路径,逐个比较读写结果 → 断言失败时指出路径。

调用关系:沙盒策略迁移或反序列化测试会用它确认新旧表示没有改变权限含义。

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

tests::external_sandbox_reports_full_access_flags4444–4456 ↗
fn external_sandbox_reports_full_access_flags()

作用:测试外部沙盒策略的全权限标志。外部沙盒被认为有全盘写权限,但网络是否开放取决于字段。

数据流:构造 restricted 和 enabled 两种 ExternalSandbox → 调用 has_full_disk_write_access 和 has_full_network_access → 用断言确认结果。

调用关系:保护 SandboxPolicy 对 ExternalSandbox 的权限解释。

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

tests::read_only_reports_network_access_flags4459–4467 ↗
fn read_only_reports_network_access_flags()

作用:测试只读沙盒的网络访问标志。只读不代表一定断网,网络由 network_access 字段决定。

数据流:创建默认只读策略和网络开启的只读策略 → 调用 has_full_network_access → 断言默认关闭、显式开启为真。

调用关系:覆盖 SandboxPolicy::new_read_only_policy 和 has_full_network_access。

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

tests::granular_approval_config_mcp_elicitation_flag_is_field_driven4470–4491 ↗
fn granular_approval_config_mcp_elicitation_flag_is_field_driven()

作用:测试 MCP 追问审批开关完全由 mcp_elicitations 字段决定。

数据流:构造字段为 true 和 false 的配置 → 调用 allows_mcp_elicitations → 断言结果分别为真和假。

调用关系:保护 GranularApprovalConfig::allows_mcp_elicitations 的简单字段语义。

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

tests::granular_approval_config_skill_approval_flag_is_field_driven4494–4515 ↗
fn granular_approval_config_skill_approval_flag_is_field_driven()

作用:测试技能审批开关完全由 skill_approval 字段决定。

数据流:构造 skill_approval 为 true 和 false 的配置 → 调用 allows_skill_approval → 断言结果符合字段。

调用关系:保护 GranularApprovalConfig::allows_skill_approval。

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

tests::granular_approval_config_request_permissions_flag_is_field_driven4518–4539 ↗
fn granular_approval_config_request_permissions_flag_is_field_driven()

作用:测试请求权限开关完全由 request_permissions 字段决定。

数据流:构造 request_permissions 为 true 和 false 的配置 → 调用 allows_request_permissions → 断言结果符合字段。

调用关系:保护 GranularApprovalConfig::allows_request_permissions。

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

tests::granular_approval_config_defaults_missing_optional_flags_to_false4542–4560 ↗
fn granular_approval_config_defaults_missing_optional_flags_to_false()

作用:测试细粒度审批配置缺少可选字段时会默认 false。这样旧配置不会意外打开新能力。

数据流:从只包含部分字段的 JSON 解析 GranularApprovalConfig → 检查 skill_approval 和 request_permissions 自动为 false → 断言整体等于预期。

调用关系:保护 GranularApprovalConfig 的反序列化默认值。

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

tests::restricted_file_system_policy_reports_full_access_from_root_entries4563–4582 ↗
fn restricted_file_system_policy_reports_full_access_from_root_entries()

作用:测试受限文件系统策略如果包含根目录规则,会正确报告全盘读写能力。

数据流:构造根目录只读规则 → 断言全盘可读但不可写且不包含平台默认;再构造根目录可写规则 → 断言全盘可读可写。

调用关系:保护 FileSystemSandboxPolicy::restricted 对根目录特殊规则的解释。

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

tests::restricted_file_system_policy_treats_root_with_carveouts_as_scoped_access4585–4636 ↗
fn restricted_file_system_policy_treats_root_with_carveouts_as_scoped_access()

作用:这个测试确认:即使策略里允许写整个磁盘根目录,只要又挖掉了一个禁止访问的小区域,系统就不能把它当成真正的“全盘自由访问”。这样可以避免权限显示得过于宽松。

数据流:进去的是一个临时工作目录、一个“根目录可写”的规则、一个“blocked 子路径禁止”的规则。测试把路径都转成规范的绝对路径后,查看策略算出的可读根、不可读根和可写根。出来的结果必须是:没有全盘读写权限,根目录可读写,但 blocked 被列为只读/不可访问的例外。

调用关系:测试运行器调用它;它主要检验 FileSystemSandboxPolicy::restricted 生成的策略,以及路径解析函数 from_absolute_path、resolve_path_against_base、canonicalize_preserving_symlinks 配合后,权限查询结果是否可靠。

调用图:调用 3 个内部函数(restricted, from_absolute_path, resolve_path_against_base);外部调用 5 个(new, assert!, assert_eq!, canonicalize_preserving_symlinks, vec!)。

tests::restricted_file_system_policy_derives_effective_paths4639–4706 ↗
fn restricted_file_system_policy_derives_effective_paths()

作用:这个测试确认“受限文件系统策略”能把特殊规则翻译成真正生效的目录列表。特别是项目目录可写时,一些敏感目录和被拒绝的目录仍然会被保护起来。

数据流:进去的是临时项目目录,里面创建了 .agents 和 .codex,再加上“最小默认可读”“项目根可写”“secret 禁止”的策略。测试把这些规则展开成实际路径。出来的结果必须显示:只有当前项目根可读写,secret 不可读,并且 secret、.agents、.codex 都出现在可写根下面的只读子路径里。

调用关系:测试运行器调用它;它验证 FileSystemSandboxPolicy::restricted 对 Special 路径的展开逻辑,也间接检查平台默认保护项是否会被 include_platform_defaults 标记出来。

调用图:调用 3 个内部函数(restricted, from_absolute_path, resolve_path_against_base);外部调用 6 个(new, assert!, assert_eq!, canonicalize_preserving_symlinks, create_dir_all, vec!)。

tests::restricted_file_system_policy_treats_read_entries_as_read_only_subpaths4709–4753 ↗
fn restricted_file_system_policy_treats_read_entries_as_read_only_subpaths()

作用:这个测试确认:如果大目录可写,但其中某个子目录只允许读,系统会把这个子目录当成“可写区域里的只读隔间”。这样能防止读权限被外层写权限覆盖。

数据流:进去的是项目根可写、docs 只读、docs/public 又可写三条规则。测试计算最终可写根列表。出来的结果应是:项目根可写,但 .codex 和 docs 是只读子路径;同时 docs/public 单独作为一个可写根出现。

调用关系:测试运行器调用它;它围绕 FileSystemSandboxPolicy::restricted 和 get_writable_roots_with_cwd,验证多条权限规则重叠时的优先级和拆分效果。

调用图:调用 3 个内部函数(restricted, from_absolute_path, resolve_path_against_base);外部调用 5 个(new, assert!, assert_eq!, canonicalize_preserving_symlinks, vec!)。

tests::file_system_policy_rejects_legacy_bridge_for_non_workspace_writes4756–4783 ↗
fn file_system_policy_rejects_legacy_bridge_for_non_workspace_writes()

作用:这个测试确认:新权限策略如果允许写工作区外面的目录,就不能强行转换成旧版沙盒策略。因为旧版表达不清这种情况,硬转会造成权限误判。

数据流:进去的是一个假的工作目录,以及一个工作区外部的可写路径。测试尝试把新文件系统策略转换成旧版 SandboxPolicy。出来的结果必须是错误,并且错误信息说明“工作区根目录外的写权限”不能桥接。

调用关系:测试运行器调用它;它检验 to_legacy_sandbox_policy 这个新旧策略桥接函数在遇到旧模型无法表达的权限时,会拒绝而不是悄悄放行。

调用图:调用 2 个内部函数(restricted, from_absolute_path);外部调用 4 个(new, assert!, cfg!, vec!)。

tests::legacy_sandbox_policy_semantics_survive_split_bridge4786–4822 ↗
fn legacy_sandbox_policy_semantics_survive_split_bridge()

作用:这个测试确认旧版沙盒策略拆成新的文件系统策略和网络策略后,再转回旧版,含义不会变。它是在保护升级兼容性。

数据流:进去的是一组旧版 SandboxPolicy,包括全权限、外部沙盒、只读、工作区可写等情况。每个策略先转换成新的文件系统策略,再结合网络策略转回旧版。出来的结果要和原来的策略语义一致。

调用关系:测试运行器调用它;它串起 from_legacy_sandbox_policy_for_cwd、NetworkSandboxPolicy::from 和 to_legacy_sandbox_policy,最后用 assert_same_sandbox_policy_semantics 检查来回转换没有改变用户实际权限。

调用图:调用 3 个内部函数(from_legacy_sandbox_policy_for_cwd, from, resolve_path_against_base);外部调用 3 个(new, assert_same_sandbox_policy_semantics, vec!)。

tests::item_started_event_from_web_search_emits_begin_event4825–4846 ↗
fn item_started_event_from_web_search_emits_begin_event()

作用:这个测试确认网页搜索开始时,新事件能翻译成旧版的 WebSearchBegin 事件。这样旧界面或旧日志仍然能知道“搜索开始了”。

数据流:进去的是一个 ItemStartedEvent,里面的条目是 WebSearchItem,编号是 search-1。测试调用 as_legacy_events。出来的结果必须只有一个旧事件,并且类型是 WebSearchBegin,call_id 保持为 search-1。

调用关系:测试运行器调用它;它检查 ItemStartedEvent::as_legacy_events 对 TurnItem::WebSearch 的分支,确保新协议事件能交给旧协议消费者。

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

tests::item_started_event_from_non_web_search_emits_no_legacy_events4849–4862 ↗
fn item_started_event_from_non_web_search_emits_no_legacy_events()

作用:这个测试确认普通用户消息开始时,不会凭空生成旧版“开始事件”。因为不是所有新事件都需要对应旧事件。

数据流:进去的是一个包含 UserMessageItem 的 ItemStartedEvent。测试调用 as_legacy_events。出来的结果必须是空列表,表示没有旧事件要发。

调用关系:测试运行器调用它;它和网页搜索等测试形成对照,验证 ItemStartedEvent::as_legacy_events 只对需要兼容的项目类型产出旧事件。

调用图:调用 2 个内部函数(new, new);外部调用 2 个(assert!, UserMessage)。

tests::item_started_event_from_image_generation_emits_begin_event4865–4885 ↗
fn item_started_event_from_image_generation_emits_begin_event()

作用:这个测试确认图片生成任务开始时,会转换成旧版 ImageGenerationBegin 事件。这样旧客户端也能显示“图片生成已开始”。

数据流:进去的是一个图片生成中的 ItemStartedEvent,图片任务 id 是 ig-1。测试调用 as_legacy_events。出来的结果必须是一个 ImageGenerationBegin,并保留 call_id。

调用关系:测试运行器调用它;它验证 ItemStartedEvent::as_legacy_events 对 TurnItem::ImageGeneration 的开始事件兼容转换。

调用图:调用 1 个内部函数(new);外部调用 4 个(new, assert_eq!, ImageGeneration, panic!)。

tests::item_started_event_from_file_change_emits_patch_begin_event4888–4921 ↗
fn item_started_event_from_file_change_emits_patch_begin_event()

作用:这个测试确认文件修改开始时,会生成旧版 PatchApplyBegin 事件。这样旧系统能看到将要改哪些文件,以及是否自动批准。

数据流:进去的是一个 FileChangeItem,表示新增 new.txt,且 auto_approved 为 true。测试转换成旧事件。出来的结果必须是 PatchApplyBegin,里面保留 call_id、turn_id、自动批准标记和变更文件清单。

调用关系:测试运行器调用它;它检查 ItemStartedEvent::as_legacy_events 把新式文件变更条目翻译给旧补丁事件系统的路径。

调用图:调用 1 个内部函数(new);外部调用 5 个(from, assert!, assert_eq!, FileChange, panic!)。

tests::item_started_event_from_mcp_tool_call_emits_begin_event4924–4958 ↗
fn item_started_event_from_mcp_tool_call_emits_begin_event()

作用:这个测试确认 MCP 工具调用开始时,会生成旧版 McpToolCallBegin 事件。MCP 可以理解为外部工具/插件连接协议,这里要保证旧客户端能看到工具名、服务器名和插件信息。

数据流:进去的是一个进行中的 McpToolCallItem,包含 server、tool、参数、资源 URI 和 plugin_id。测试转换成旧事件。出来的结果必须是 McpToolCallBegin,并完整保留这些调用信息。

调用关系:测试运行器调用它;它验证 ItemStartedEvent::as_legacy_events 在 MCP 工具调用开始时,会把新结构打包成旧 EventMsg。

调用图:调用 1 个内部函数(new);外部调用 4 个(assert_eq!, McpToolCall, json!, panic!)。

tests::item_completed_event_from_image_generation_emits_end_event4961–4990 ↗
fn item_completed_event_from_image_generation_emits_end_event()

作用:这个测试确认图片生成完成时,会转换成旧版 ImageGenerationEnd 事件,并且不丢最终结果。结果包括状态、改写后的提示词、图片数据和保存路径。

数据流:进去的是一个 completed 状态的 ImageGenerationItem,带 revised_prompt、Base64 结果和保存路径。测试调用 as_legacy_events。出来的是一个 ImageGenerationEnd,字段都要和原始事件一致。

调用关系:测试运行器调用它;它检查 ItemCompletedEvent::as_legacy_events 对图片生成结束场景的旧协议兼容。

调用图:调用 1 个内部函数(new);外部调用 4 个(assert_eq!, ImageGeneration, test_path_buf, panic!)。

tests::item_completed_event_from_file_change_emits_patch_end_event4993–5028 ↗
fn item_completed_event_from_file_change_emits_patch_end_event()

作用:这个测试确认文件修改完成后,会生成旧版 PatchApplyEnd 事件。旧界面需要靠它知道补丁是否成功、输出了什么、改了哪些文件。

数据流:进去的是一个已完成的 FileChangeItem,包含新增文件、完成状态、stdout 和 stderr。测试转换成旧事件。出来的结果必须是 PatchApplyEnd,显示成功、状态为 Completed,并保留变更清单和输出文本。

调用关系:测试运行器调用它;它验证 ItemCompletedEvent::as_legacy_events 把新式文件变更完成信息交给旧补丁事件消费者。

调用图:调用 1 个内部函数(new);外部调用 6 个(from, new, assert!, assert_eq!, FileChange, panic!)。

tests::item_completed_event_from_mcp_tool_call_emits_end_event5031–5072 ↗
fn item_completed_event_from_mcp_tool_call_emits_end_event()

作用:这个测试确认 MCP 工具调用结束时,会生成旧版 McpToolCallEnd 事件,并能判断调用成功。这样旧客户端能显示工具调用结果和耗时。

数据流:进去的是一个已完成的 McpToolCallItem,带结果内容、非错误标记和 42 毫秒耗时。测试转换成旧事件。出来的是 McpToolCallEnd,保留 server、tool、资源 URI、plugin_id、duration,并且 is_success 为真。

调用关系:测试运行器调用它;它覆盖 ItemCompletedEvent::as_legacy_events 的 MCP 完成分支,并依赖 McpToolCallEnd 事件自己的成功判断。

调用图:调用 1 个内部函数(new);外部调用 7 个(from_millis, assert!, assert_eq!, McpToolCall, json!, panic!, vec!)。

tests::item_started_event_requires_started_at_ms5075–5086 ↗
fn item_started_event_requires_started_at_ms()

作用:这个测试确认 ItemStartedEvent 反序列化时必须带 started_at_ms。也就是“开始时间”不能缺,缺了就视为数据不完整。

数据流:进去的是一个正常的 ItemStartedEvent,先转成 JSON,再手动删除 started_at_ms。测试再把 JSON 转回事件。出来的结果必须是失败,说明这个字段是必填的。

调用关系:测试运行器调用它;它检查 serde_json 对 ItemStartedEvent 的读写规则,保护协议字段要求不被无意放宽。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(assert!, UserMessage, to_value)。

tests::item_completed_event_defaults_missing_completed_at_ms5089–5101 ↗
fn item_completed_event_defaults_missing_completed_at_ms()

作用:这个测试确认 ItemCompletedEvent 缺少 completed_at_ms 时会默认成 0,而不是报错。这样可以兼容老数据里没有完成时间的情况。

数据流:进去的是一个完成事件,转成 JSON 后删除 completed_at_ms。测试再反序列化。出来的是一个事件对象,其中 completed_at_ms 自动变成 0。

调用关系:测试运行器调用它;它验证 ItemCompletedEvent 的 JSON 兼容规则,与 started 事件的必填规则形成区别。

调用图:调用 2 个内部函数(new, new);外部调用 3 个(assert_eq!, UserMessage, to_value)。

tests::rollback_failed_error_does_not_affect_turn_status5103–5109 ↗
fn rollback_failed_error_does_not_affect_turn_status()

作用:这个测试确认“回滚失败”这种错误不会改变当前轮次的状态。因为它是善后失败,不代表原本的对话轮次本身失败。

数据流:进去的是一个 ErrorEvent,错误类型是 ThreadRollbackFailed。测试调用 affects_turn_status。出来必须是 false,表示不影响轮次状态。

调用关系:测试运行器调用它;它验证 ErrorEvent::affects_turn_status 对特定 CodexErrorInfo 的判断规则。

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

tests::active_turn_not_steerable_error_does_not_affect_turn_status5112–5120 ↗
fn active_turn_not_steerable_error_does_not_affect_turn_status()

作用:这个测试确认“当前轮次不能被引导”这类错误不会把轮次标成失败。比如 review 轮次不能被 steer,这是规则限制,不是执行崩了。

数据流:进去的是一个 ErrorEvent,错误信息是 ActiveTurnNotSteerable,轮次类型是 Review。测试询问它是否影响轮次状态。出来必须是 false。

调用关系:测试运行器调用它;它覆盖 ErrorEvent::affects_turn_status 中另一个不应影响主流程状态的错误类型。

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

tests::generic_error_affects_turn_status5123–5129 ↗
fn generic_error_affects_turn_status()

作用:这个测试确认普通错误会影响当前轮次状态。也就是说,除了一些明确豁免的错误,大多数错误都应该让轮次进入错误状态。

数据流:进去的是一个 ErrorEvent,错误类型是 Other。测试调用 affects_turn_status。出来必须是 true。

调用关系:测试运行器调用它;它和前两个错误测试一起,确认 ErrorEvent::affects_turn_status 的默认行为和例外行为。

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

tests::realtime_conversation_started_event_uses_realtime_session_id5132–5145 ↗
fn realtime_conversation_started_event_uses_realtime_session_id()

作用:这个测试确认实时会话开始事件序列化时使用 realtime_session_id 这个字段名。字段名稳定,前后端才能对得上。

数据流:进去的是一个 RealtimeConversationStartedEvent,实时会话 id 是 conv_1,版本是 V2。测试把它转成 JSON。出来的 JSON 必须包含 realtime_session_id 和 version: v2。

调用关系:测试运行器调用它;它检查 RealtimeConversationStartedEvent 的 serde_json 输出格式,避免协议字段名被改坏。

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

tests::realtime_voice_list_is_stable5148–5179 ↗
fn realtime_voice_list_is_stable()

作用:这个测试确认内置实时语音列表和默认语音不会意外变化。因为客户端可能依赖固定的可选声音顺序和默认值。

数据流:进去的是 RealtimeVoicesList::builtin 生成的列表。测试把它和手写的预期 v1、v2 声音列表及默认声音比较。出来必须完全相等。

调用关系:测试运行器调用它;它守住 RealtimeVoicesList::builtin 的公开协议,防止改动声音列表时无意影响用户体验。

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

tests::user_input_text_serializes_empty_text_elements5182–5199 ↗
fn user_input_text_serializes_empty_text_elements() -> Result<()>

作用:这个测试确认文本输入即使没有额外文本元素,也会把 text_elements 序列化成空数组。这样接收方不用猜这个字段是漏了还是确实为空。

数据流:进去的是 UserInput::Text,text 是 hello,text_elements 是空列表。测试转成 JSON。出来的 JSON 必须显式包含 text_elements: []。

调用关系:测试运行器调用它;它验证 UserInput 的 JSON 输出格式,保证空元数据也能稳定传输。

调用图:外部调用 3 个(new, assert_eq!, to_value)。

tests::user_message_event_serializes_empty_metadata_vectors5202–5223 ↗
fn user_message_event_serializes_empty_metadata_vectors() -> Result<()>

作用:这个测试确认用户消息里的空图片列表、空文本元素列表会被写进 JSON。这样前端或日志读取时能看到明确的空数组。

数据流:进去的是一个 UserMessageEvent,message 是 hello,local_images 和 text_elements 是空列表,其他字段默认。测试转成 JSON。出来的 JSON 包含 message、local_images: []、text_elements: []。

调用关系:测试运行器调用它;它检查 UserMessageEvent 的序列化规则,尤其是空向量不会被省略。

调用图:外部调用 4 个(default, new, assert_eq!, to_value)。

tests::user_message_event_deserializes_without_image_detail_fields5226–5245 ↗
fn user_message_event_deserializes_without_image_detail_fields() -> Result<()>

作用:这个测试确认老 JSON 里没有图片清晰度/细节字段时,仍然能读成用户消息。这样旧客户端发来的消息不会因为少字段而坏掉。

数据流:进去的是一段 JSON,含 message、远程图片、本地图片和空 text_elements,但没有 image_details。测试反序列化成 UserMessageEvent。出来的对象要保留图片路径,同时 image_details 和 local_image_details 变成空列表。

调用关系:测试运行器调用它;它验证 UserMessageEvent 的向后兼容读取规则,使用 serde_json::from_value 完成转换。

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

tests::user_message_item_legacy_event_preserves_image_details5248–5281 ↗
fn user_message_item_legacy_event_preserves_image_details()

作用:这个测试确认把新式 UserMessageItem 转成旧版 UserMessage 事件时,不会丢掉图片的 detail 信息。detail 可以理解为图片质量/原图要求。

数据流:进去的是一个用户消息条目,包含两张网络图片和一张本地图片,其中部分带 ImageDetail::Original,还设置了 client_id。测试调用 as_legacy_event。出来的旧事件要保留图片 URL、client_id、本地路径,以及已有的图片 detail。

调用关系:测试运行器调用它;它检查 UserMessageItem::as_legacy_event 这条新旧事件转换路径,确保图片元数据不会在兼容层丢失。

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

tests::turn_aborted_event_deserializes_without_turn_id5284–5301 ↗
fn turn_aborted_event_deserializes_without_turn_id() -> Result<()>

作用:这个测试确认 turn_aborted 事件即使没有 turn_id 也能被读取。这样旧格式或简化格式的中断事件仍然兼容。

数据流:进去的是一段 JSON,类型是 turn_aborted,原因是 interrupted,但没有 turn_id。测试反序列化成 EventMsg。出来必须是 TurnAborted 事件,turn_id 为 None,reason 为 Interrupted。

调用关系:测试运行器调用它;它验证 EventMsg 对 TurnAbortedEvent 的兼容反序列化规则。

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

tests::turn_context_item_deserializes_without_network5304–5317 ↗
fn turn_context_item_deserializes_without_network() -> Result<()>

作用:这个测试确认轮次上下文缺少 network、文件系统新沙盒策略和 comp_hash 时也能读取。这样老上下文数据不会因为新字段缺失而失效。

数据流:进去的是一段 TurnContextItem JSON,包含 cwd、approval_policy、旧 sandbox_policy、model 和 summary。测试反序列化。出来的对象中 network、file_system_sandbox_policy、comp_hash 都应为 None。

调用关系:测试运行器调用它;它检查 TurnContextItem 的向后兼容读取,特别是新增字段缺失时的默认行为。

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

tests::multi_agent_version_uses_newest_present_session_meta_value5320–5350 ↗
fn multi_agent_version_uses_newest_present_session_meta_value() -> Result<()>

作用:这个测试确认多智能体版本会使用最近一个“有值”的会话元信息,而不是被更新但空值的元信息抹掉。这样历史里已有的版本信息不会丢。

数据流:进去的是同一个 thread_id 的两条 SessionMeta:较旧一条有 MultiAgentVersion::V2,较新一条没有版本。测试调用 multi_agent_version_from_items。出来应仍然是 Some(V2)。

调用关系:测试运行器调用它;它验证 multi_agent_version_from_items 扫描 RolloutItem::SessionMeta 时的取值策略。

调用图:调用 1 个内部函数(from_string);外部调用 2 个(default, assert_eq!)。

tests::turn_context_item_serializes_network_when_present5353–5408 ↗
fn turn_context_item_serializes_network_when_present() -> Result<()>

作用:这个测试确认轮次上下文里如果有网络规则和新文件系统沙盒规则,序列化成 JSON 时会完整写出来。这样接收方能准确恢复本轮的安全边界。

数据流:进去的是一个 TurnContextItem,含允许/拒绝域名,以及一个拒绝 glob_pattern 路径的文件系统策略。测试转成 JSON。出来的 JSON 中 network、file_system_sandbox_policy 和 summary 字段都要符合预期格式。

调用关系:测试运行器调用它;它验证 TurnContextItem、TurnContextNetworkItem 和 FileSystemSandboxPolicy::restricted 一起输出协议 JSON 的格式。

调用图:调用 1 个内部函数(restricted);外部调用 4 个(assert_eq!, test_path_buf, to_value, vec!)。

tests::serialize_event5413–5460 ↗
fn serialize_event() -> Result<()>

作用:这个测试确认完整 Event 包着 SessionConfiguredEvent 时,序列化出来的 JSON 字段符合协议。会话配置是启动后告诉客户端“这次会话怎么运行”的关键消息。

数据流:进去的是一个 Event,id 为 1234,消息是 session_configured,包含 session_id、thread_id、模型、审批策略、权限配置、工作目录、推理强度和 rollout 文件路径。测试转成 JSON。出来必须和预期 JSON 完全一致。

调用关系:测试运行器调用它;它覆盖 Event、EventMsg::SessionConfigured、PermissionProfile 和路径序列化等多个协议部件的组合输出。

调用图:调用 3 个内部函数(read_only, from_string, from_string);外部调用 6 个(new, default, assert_eq!, test_path_buf, json!, SessionConfigured)。

tests::deserialize_legacy_session_configured_event_uses_sandbox_policy5463–5480 ↗
fn deserialize_legacy_session_configured_event_uses_sandbox_policy() -> Result<()>

作用:这个测试确认旧版 session_configured JSON 里只有 sandbox_policy 时,也能转换成新的 permission_profile。这样旧日志或旧客户端配置仍能被新代码读懂。

数据流:进去的是一段旧格式 JSON,里面 sandbox_policy 是 read-only,没有直接给 permission_profile。测试反序列化成 SessionConfiguredEvent。出来的 permission_profile 必须是 read_only。

调用关系:测试运行器调用它;它验证 SessionConfiguredEvent 的兼容读取逻辑,会把旧 sandbox_policy 映射到新 PermissionProfile。

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

tests::vec_u8_as_base64_serialization_and_deserialization5483–5498 ↗
fn vec_u8_as_base64_serialization_and_deserialization() -> Result<()>

作用:这个测试确认命令输出的二进制片段会用 Base64 存进 JSON,并且能再读回来。Base64 是把任意字节安全写成普通文本的一种编码。

数据流:进去的是 ExecCommandOutputDeltaEvent,chunk 是字节 [1,2,3,4,5]。测试序列化后得到 chunk: "AQIDBAU=",再反序列化。出来的事件必须和原事件完全相同。

调用关系:测试运行器调用它;它检查 ExecCommandOutputDeltaEvent 中 Vec<u8> 的自定义 JSON 编码和解码规则。

调用图:外部调用 4 个(assert_eq!, from_str, to_string, vec!)。

tests::serialize_mcp_startup_update_event5501–5518 ↗
fn serialize_mcp_startup_update_event() -> Result<()>

作用:这个测试确认 MCP 服务启动过程中的单个更新事件能正确写成 JSON,尤其是失败状态和错误信息。这样客户端能实时显示哪个服务启动失败。

数据流:进去的是一个 Event,消息为 McpStartupUpdate,server 是 srv,状态是 Failed,错误是 boom。测试转成 JSON。出来的 msg.type、server、status.state、status.error 都要匹配预期。

调用关系:测试运行器调用它;它验证 EventMsg::McpStartupUpdate 和 McpStartupStatus::Failed 的序列化格式。

调用图:外部调用 3 个(assert_eq!, McpStartupUpdate, to_value)。

tests::serialize_mcp_startup_complete_event5521–5541 ↗
fn serialize_mcp_startup_complete_event() -> Result<()>

作用:这个测试确认 MCP 服务启动汇总事件能正确写成 JSON。它告诉客户端哪些服务准备好了、哪些失败了、哪些被取消了。

数据流:进去的是 McpStartupCompleteEvent,ready 有 a,failed 有 b 和错误 bad,cancelled 有 c。测试转成 JSON。出来的 JSON 必须分别列出 ready、failed、cancelled,并保留失败原因。

调用关系:测试运行器调用它;它验证 EventMsg::McpStartupComplete 的最终汇总协议格式。

调用图:外部调用 4 个(assert_eq!, McpStartupComplete, to_value, vec!)。

tests::token_usage_info_new_or_append_updates_context_window_when_provided5544–5562 ↗
fn token_usage_info_new_or_append_updates_context_window_when_provided()

作用:这个测试确认追加新的 token 用量时,如果给了新的模型上下文窗口大小,就会更新这个大小。上下文窗口可以理解为模型一次能看多少文字的上限。

数据流:进去的是已有 TokenUsageInfo,窗口是 258400;新的最后一次用量是 10 个 token;同时传入新窗口 128000。测试调用 new_or_append。出来的 info 中 model_context_window 必须变成 128000。

调用关系:测试运行器调用它;它验证 TokenUsageInfo::new_or_append 在合并用量时会优先采用新传入的上下文窗口。

调用图:调用 1 个内部函数(new_or_append);外部调用 2 个(assert_eq!, default)。

tests::token_usage_info_new_or_append_preserves_context_window_when_not_provided5565–5584 ↗
fn token_usage_info_new_or_append_preserves_context_window_when_not_provided()

作用:这个测试确认追加 token 用量时,如果没有给新的上下文窗口,就保留原来的值。这样不会因为一次更新缺字段就把模型能力信息清空。

数据流:进去的是已有 TokenUsageInfo,窗口是 258400;新的最后一次用量是 10 个 token;新窗口参数是 None。测试调用 new_or_append。出来的 info 中 model_context_window 仍然是 258400。

调用关系:测试运行器调用它;它和前一个测试一起验证 TokenUsageInfo::new_or_append 对 model_context_window 的更新和保留规则。

调用图:调用 1 个内部函数(new_or_append);外部调用 2 个(assert_eq!, default)。

插件与工具约定

这些文件定义共享插件标识符、manifest、发现元数据,以及标准化工具定义,用于加载、marketplace 和面向客户端的工具暴露。

plugin/src/plugin_id.rs源码 ↗
data_modelcross-cutting:插件选择、安装、缓存刷新、卸载和统计上报时都会用到

这个文件解决的是“怎样可靠地表示一个插件”的问题。系统里很多地方都要提到同一个插件,比如下载、缓存、卸载、上报统计。如果每处都随便拼字符串,就容易出现空名字、奇怪符号、路径混乱,甚至把缓存目录弄错。这里定义了 PluginId,里面有两个字段:插件名和市场名。它像一张门牌号,既说明住户是谁,也说明属于哪个小区。创建时会检查每一段只能包含英文字母、数字、下划线和短横线,不能空。parse 可以把 name@marketplace 这种字符串拆成结构体,as_key 又能把结构体拼回字符串。错误会用 PluginIdError 返回清楚原因,方便上层告诉用户或写日志。

函数细节4
PluginId::new16–24 ↗
fn new(plugin_name: String, marketplace_name: String) -> Result<Self, PluginIdError>

作用:用已经分开的插件名和市场名创建一个 PluginId。它不会盲目信任输入,而是先检查两段名字是否合法,防止坏格式进入后面的插件缓存和安装流程。

数据流:进去的是两个字符串:plugin_namemarketplace_name。函数先分别交给 validate_plugin_segment 检查,确认它们不为空,并且只含安全字符;如果有问题,就返回带说明的错误;如果都没问题,就把这两个字符串放进新的 PluginId 里返回。

调用关系:它是创建插件身份证的统一入口。插件选择解析、插件缓存刷新、读取市场插件详情,以及许多测试都会调用它;它自己把具体的单段名称检查交给 validate_plugin_segment,从而保证所有来源创建出的插件编号规则一致。

调用图:调用 1 个内部函数(validate_plugin_segment);被 36 处调用(parse_plugin_selection, refresh_curated_plugin_cache, refresh_non_curated_plugin_cache_with_mode, read_plugin_detail_for_marketplace_plugin, refresh_curated_plugin_cache_leaves_api_curated_plugin_when_api_manifest_missing, refresh_curated_plugin_cache_migrates_full_sha_cache_version_to_short_version, refresh_curated_plugin_cache_reinstalls_missing_api_curated_plugin, refresh_curated_plugin_cache_reinstalls_missing_configured_plugin_with_current_short_version, refresh_curated_plugin_cache_removes_cache_for_plugin_removed_from_marketplace, refresh_curated_plugin_cache_replaces_existing_local_version_with_short_sha_version (+15 more))。

PluginId::parse26–43 ↗
fn parse(plugin_key: &str) -> Result<Self, PluginIdError>

作用:把外部传进来的 插件@市场 字符串解析成正式的 PluginId。有人输入命令、配置或已保存的数据时,系统需要靠它把普通文本变成可靠的插件身份证。

数据流:进去的是一个完整字符串 plugin_key。函数从最后一个 @ 处分成插件名和市场名;如果没有 @,或者左右有一边为空,就返回格式错误。拆开后,它再调用 PluginId::new 做字符合法性检查;成功后输出 PluginId,失败时会把原始字符串也写进错误消息,方便定位是哪一条输入出问题。

调用关系:它站在“文本输入”和“内部结构”之间。插件迁移、插件开关事件、卸载响应、加载插件、判断备用插件等流程都会先用它识别插件;它不自己重复所有检查,而是把最终验证交给 PluginId::new

调用图:被 19 处调用(sample_plugin_metadata, extract_plugin_migration_details, emit_plugin_toggle_events, plugin_uninstall_response, parse_plugin_selection, is_tool_suggest_fallback_plugin, installed_plugin_name_for_marketplace, load_plugin, merge_configured_plugins_with_remote_installed, plugin_id (+9 more));外部调用 3 个(new, format!, Invalid)。

PluginId::as_key45–47 ↗
fn as_key(&self) -> String

作用:把一个 PluginId 重新变成标准文本形式 插件@市场。当系统需要保存、传递、卸载或上报插件编号时,会用它生成统一写法。

数据流:进去的是一个已经合法的 PluginId,里面有插件名和市场名。函数用 @ 把这两段拼起来,出来的是一个字符串,比如 foo@bar。它不改动原来的对象,只生成可传给其他模块或写入记录的 key。

调用关系:它是内部结构回到外部字符串的出口。遥测信息生成、从插件编号构造其他对象、卸载插件编号等流程会调用它,确保大家拿到的都是同一种 插件@市场 格式。

调用图:被 3 处调用(from_plugin_id, plugin_telemetry_metadata_from_root, uninstall_plugin_id);外部调用 1 个(format!)。

validate_plugin_segment51–64 ↗
fn validate_plugin_segment(segment: &str, kind: &str) -> Result<(), String>

作用:检查插件编号中的单独一段名字是否合法,比如只检查“插件名”或只检查“市场名”。它的作用像门口保安:不让空名字和带危险符号的名字进入系统。

数据流:进去的是待检查的 segment,以及这段名字的说明 kind,比如 plugin name。函数先看它是不是空的,再逐个字符检查是否都是 ASCII 英文字母、数字、下划线或短横线;如果不合格,就返回一段人能看懂的错误文字;如果合格,就返回成功且不产生新数据。

调用关系:它是底层的名字规则检查器,目前由 PluginId::new 调用。这样无论插件编号来自解析字符串、配置还是其他流程,最后都会经过同一套字符规则,避免各处标准不一样。

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

plugin/src/manifest.rs源码 ↗
data_modelplugin loading / config load

插件不是一堆文件随便放着就能用,系统必须先知道它的名字、版本、技能文件在哪里、应用文件在哪里、有没有钩子脚本,以及界面上该显示什么图标和介绍。这个文件就是把这些信息整理成固定格式。核心类型是 PluginManifest,它代表一份完整清单;PluginManifestPaths 记录清单里指向各种资源的位置;PluginManifestHooks 表示钩子可以是外部路径,也可以是直接写在清单里的内容;PluginManifestInterface 则放给用户和模型看的展示信息,比如显示名、简介、官网、logo、截图等。这里还有一个重要设计:资源位置是泛型 Resource,也就是“资源地址的具体样子暂时不固定”。这样插件刚从本地读入时可以用文件路径,等解析完成后又可以换成更安全、更正式的资源定位方式。

函数细节3
PluginManifestInterface::default53–70 ↗
fn default() -> Self

作用:给插件的界面展示信息准备一份“空白默认值”。如果插件没有写展示名、简介、logo 等内容,系统也能拿到一个结构完整、不会缺胳膊少腿的对象。

数据流:进去时不需要任何输入 → 它创建一个 PluginManifestInterface,把可选文字和图片都设成没有,把列表类字段设成空列表 → 出来的是一份干净的默认界面信息,不会自动填任何插件自己的内容。

调用关系:在测试 environment_descriptor_binds_every_manifest_resource 里会用到它,用来快速造出一份默认界面信息,再只改测试关心的资源字段。它内部只是构造新对象,不把工作交给项目里的其他业务函数。

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

PluginManifest::display_name75–82 ↗
fn display_name(&self) -> &str

作用:决定插件最终给人看的名字。它优先用清单里专门写给界面展示的名字;如果没写、写空了,或者全是空格,就退回用插件的正式 name。

数据流:进去的是一份插件清单 self → 它查看 interface 里的 display_name,先去掉前后空格,再检查是不是空字符串 → 出来的是一个字符串引用,要么是好看的展示名,要么是清单里的基础 name。

调用关系:这是读取插件信息时常用的小判断函数,通常会被界面展示、模型说明或日志这类地方调用。它不需要再调用别的项目函数,只是在清单已有字段里做一次安全选择。

PluginManifest::try_map_resources84–166 ↗
fn try_map_resources(
        self,
        mut map: impl FnMut(Resource) -> Result<Mapped, Error>,
    ) -> Result<PluginManifest<Mapped>, Error>

作用:把清单里所有“资源地址”统一换一种表示方式。比如刚开始资源可能是本地路径,之后要变成带权限边界的定位符;这个函数负责把每个该转换的地方都走一遍,避免漏掉。

数据流:进去的是一份 PluginManifest<Resource>,再加上一个转换函数 map → 它拆开清单,把 skills、mcp_servers、apps、hooks 里的路径、界面里的图标、logo、截图等资源逐个交给 map 转换;内联 hooks 不是路径,所以原样保留 → 如果所有资源都转换成功,出来一份 PluginManifest<Mapped>;如果任何一个资源转换失败,就立刻返回错误,不产出半成品。

调用关系:from_environment 会在把环境里的插件清单解析成正式可用清单时调用它。它负责走遍清单里的每个资源角落,并根据 hooks 的形态重新组成 Paths 或 Inline 这两种情况;真正“怎么转换一个资源”则由调用者传进来的 map 决定。

调用图:被 1 处调用(from_environment);外部调用 2 个(Inline, Paths)。

plugin/src/lib.rs源码 ↗
data_modelcross-cutting

这个文件主要解决“插件信息要用统一格式传来传去”的问题。插件系统里会有很多零件:插件 ID、插件清单、加载结果、资源位置、技能目录、应用连接器、钩子配置、遥测信息等。如果每个地方都自己定义一套,数据很容易对不上。这里就像前台接待处,把内部模块里的重要类型重新导出,同时定义几种常用的数据结构。比如 AppDeclaration 表示插件声明的一个应用,AppConnectorId 是这个应用连接器的编号;PluginCapabilitySummary 用来概括插件有什么能力;PluginHookSource 记录某个钩子配置来自哪个插件文件;PluginTelemetryMetadata 则把插件身份和能力摘要打包,方便统计和分析。它还提供两个小帮手:一个从应用声明里去重取出连接器编号,一个把能力摘要变成可上报的遥测元数据。

函数细节3
app_connector_ids_from_declarations38–49 ↗
fn app_connector_ids_from_declarations(
    app_declarations: impl IntoIterator<Item = &'a AppDeclaration>,
) -> Vec<AppConnectorId>

作用:这个函数从一串应用声明里取出所有应用连接器编号,并自动去掉重复项。有人想知道“这个插件到底用到了哪些不同的外部应用连接器”时,就会用它。

数据流:进去的是一组 AppDeclaration,也就是应用声明,每个声明里都有一个 connector_id。函数先准备一个结果列表和一个“已经见过”的集合,然后逐个检查声明:第一次见到的连接器编号会被放进结果,重复出现的会跳过。出来的是一个 Vec<AppConnectorId>,顺序按第一次出现的顺序保留,不会包含重复编号。

调用关系:它是一个独立的小工具,通常会被需要汇总插件应用能力的代码拿来用。它内部只是新建列表和集合来完成去重,不把工作交给插件加载器或网络层,也不会改动传进来的应用声明。

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

PluginTelemetryMetadata::from_plugin_id81–87 ↗
fn from_plugin_id(plugin_id: &PluginId) -> Self

作用:这个函数用一个插件 ID 快速做出一份最简单的遥测元数据。适合在只知道插件是谁、还不知道它详细能力时使用。

数据流:进去的是一个 PluginId,也就是插件的唯一标识。函数把这个 ID 复制一份,放进 PluginTelemetryMetadata;remote_plugin_id 设为空,表示没有额外的远程插件编号;capability_summary 也设为空,表示还没有能力摘要。出来的是一份可以直接传给统计或日志系统的元数据对象。

调用关系:它会被 installed_plugin_telemetry_metadata 和 plugin_telemetry_metadata_from_root 调用,也就是在根据已安装插件或插件目录生成遥测资料时兜底使用。它不解析清单、不读取文件,只负责把已知的插件 ID 包装成标准格式。

调用图:被 2 处调用(installed_plugin_telemetry_metadata, plugin_telemetry_metadata_from_root);外部调用 1 个(clone)。

PluginCapabilitySummary::telemetry_metadata91–99 ↗
fn telemetry_metadata(&self) -> Option<PluginTelemetryMetadata>

作用:这个函数把“插件能力摘要”转换成“遥测元数据”。换句话说,它把给人和系统看的能力说明,整理成统计系统可以上报的格式。

数据流:进去的是一个 PluginCapabilitySummary,里面有 config_name、显示名、描述、是否有技能、服务器名、应用连接器等信息。函数先尝试把 config_name 解析成合法的 PluginId;如果解析失败,就返回 None,表示不能安全地生成遥测数据;如果解析成功,就把插件 ID 和这份能力摘要一起装进 PluginTelemetryMetadata。出来的是 Option<PluginTelemetryMetadata>:成功时有值,失败时为空。

调用关系:它位于能力汇总和遥测上报之间,像一道格式检查门。它会调用 PluginId::parse 来确认 config_name 真的是合法插件 ID;只有通过这一步,后续统计系统才会拿到带能力摘要的插件遥测信息。

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

tools/src/tool_definition.rs源码 ↗
data_modelcross-cutting

可以把 ToolDefinition 想成一张“工具说明卡”。卡片上写着工具名、给人看的描述、输入需要符合什么格式、输出大概是什么样。这里的 JsonSchema 是 JSON 数据的结构说明,简单说就是告诉别人“传进来的数据应该长什么样”。output_schema 可以没有,因为有些工具可能暂时不想暴露或加载输出说明。defer_loading 就是一个开关,表示这个工具可以先登记名字和输入,真正完整内容以后再补。文件里还给这张卡片提供了两个小改动方法:一个用来换名字,一个用来把工具标记成“延后加载”。这样下游代码不用手动拆开结构体改字段,写起来更安全、更清楚。

函数细节2
ToolDefinition::renamed16–19 ↗
fn renamed(mut self, name: String) -> Self

作用:这个函数用来给已有的工具定义换一个名字。有人想复用同一份工具说明,但在不同场景下叫不同名字时,就会用它。

数据流:进去的是一个已经做好的 ToolDefinition 和一个新的名字 → 它把这张“工具说明卡”上的 name 字段改成新名字,其他内容保持不变 → 出来的是改名后的 ToolDefinition,原来的对象被顺手改完并返回。

调用关系:它是 ToolDefinition 自带的便捷改名步骤。调用它的代码通常是在准备或调整工具清单时使用它;它自己不再把工作交给别的函数,只做一次简单字段替换。

ToolDefinition::into_deferred21–25 ↗
fn into_deferred(mut self) -> Self

作用:这个函数把一个工具定义改成“延后加载”状态。也就是说,先保留最基本的信息,暂时不带输出格式,等以后真正需要时再补齐。

数据流:进去的是一个完整或半完整的 ToolDefinition → 它把 output_schema 清空,并把 defer_loading 设为 true → 出来的是一个被标记为延后加载的 ToolDefinition,提醒后续流程不要现在就指望它有完整输出说明。

调用关系:它通常会在生成或整理工具定义时被调用,用来把某些工具放进“先登记、后加载”的模式。它不调用其他函数,只直接改两个字段,让后面的工具处理流程能根据 defer_loading 这个标记做决定。

tools/src/tool_discovery.rs源码 ↗
domain_logictool discovery and install request handling

系统里有些能力来自连接器,有些来自插件。它们来源不同、字段也不完全一样,但用户看到时都像是“可以安装或启用的工具”。这个文件就像给它们发统一的身份证:不管原来是连接器还是插件,都可以问它的类型、编号、名字、安装地址。它还定义了给前端返回的简化清单,比如名字、描述、是否带技能、关联了哪些服务。一个特别的地方是:如果客户端是 TUI,也就是终端文字界面客户端,这里会把插件从“请求安装插件”的列表里过滤掉,只留下连接器,避免这个客户端看到暂时不该处理的选项。

函数细节7
DiscoverableTool::tool_type38–43 ↗
fn tool_type(&self) -> DiscoverableToolType

作用:告诉别人这个工具到底是哪一类:连接器还是插件。这样后面的界面或安装流程就能按类型显示和处理。

数据流:进去的是一个 DiscoverableTool,也就是已经统一包装过的工具 → 它看里面装的是连接器还是插件 → 出来一个类型标记,值是 connector 或 plugin,不改动原工具。

调用关系:这是 DiscoverableTool 这个统一包装上的基础询问函数。调用图里没有显示具体调用者,但它通常会被需要区分工具类别的展示、筛选或序列化代码使用。

DiscoverableTool::id45–50 ↗
fn id(&self) -> &str

作用:取出这个工具的唯一编号。编号就像身份证号,后续要安装、记录或匹配某个工具时会用到。

数据流:进去的是一个连接器或插件包装成的 DiscoverableTool → 它按实际类型找到内部对象的 id 字段 → 出来一个字符串引用,不复制整份数据,也不改动工具。

调用关系:build_request_plugin_install_meta 会调用它来拿到安装请求需要的工具编号。它自己不再把工作交给别的本文件函数,只是从内部数据里取值。

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

DiscoverableTool::name52–57 ↗
fn name(&self) -> &str

作用:取出这个工具给人看的名字。界面展示、确认安装时,都需要这个更友好的名称。

数据流:进去的是 DiscoverableTool → 它判断里面是连接器还是插件,然后读取对应的 name 字段 → 出来一个名字字符串引用,原数据保持不变。

调用关系:build_request_plugin_install_meta 会调用它,把工具名称放进安装相关的元信息里,方便用户知道自己要装的是什么。

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

DiscoverableTool::install_url59–64 ↗
fn install_url(&self) -> Option<&str>

作用:取出工具的安装链接,但目前只有连接器可能有这个链接,插件这里会返回没有。这样调用方可以安全地判断是否能跳转安装页面。

数据流:进去的是 DiscoverableTool → 如果里面是连接器,就读取它的 install_url;如果是插件,就直接认为没有安装链接 → 出来的是一个可有可无的字符串引用,不改动任何内容。

调用关系:build_request_plugin_install_meta 会调用它来准备安装元信息。它体现了连接器和插件的一个差别:连接器可能带外部安装地址,插件在这个模型里不提供。

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

DiscoverableTool::from74–76 ↗
fn from(value: DiscoverablePluginInfo) -> Self

作用:把原始的连接器信息或插件信息,转换成统一的 DiscoverableTool。这样后面代码就不用总是分别写两套处理逻辑。

数据流:进去的是 AppInfo,也就是连接器信息,或者 DiscoverablePluginInfo,也就是插件信息 → 它把这个值放进 Box(一种把较大对象放到堆上的容器,方便枚举统一保存)里,再标记成 Connector 或 Plugin → 出来一个 DiscoverableTool。

调用关系:这是 Rust 的 From 转换接口,方便别处用标准方式把连接器或插件塞进统一列表。调用图显示它会用到外部的 new、Connector、Plugin 构造动作,本质上是在做包装。

调用图:外部调用 3 个(new, Connector, Plugin)。

filter_request_plugin_install_discoverable_tools_for_client79–91 ↗
fn filter_request_plugin_install_discoverable_tools_for_client(
    discoverable_tools: Vec<DiscoverableTool>,
    app_server_client_name: Option<&str>,
) -> Vec<DiscoverableTool>

作用:按客户端类型过滤可安装工具列表。特别是当客户端是终端文字界面 TUI 时,它会去掉插件,只保留连接器。

数据流:进去的是一整组 DiscoverableTool,以及一个可有可无的客户端名字 → 如果客户端名字不是 codex-tui,就原样返回整组工具;如果是 codex-tui,就逐个检查并丢掉插件 → 出来一组过滤后的工具列表。

调用关系:它位于“准备给某个客户端看的安装列表”这一步。调用图里没有显示具体调用者;它不调用本文件其他函数,而是直接根据工具的枚举类型做筛选,避免 TUI 客户端收到不适合它处理的插件项。

collect_request_plugin_install_entries120–146 ↗
fn collect_request_plugin_install_entries(
    discoverable_tools: &[DiscoverableTool],
) -> Vec<RequestPluginInstallEntry>

作用:把内部使用的工具对象,整理成可以返回给前端或调用方的安装列表条目。它会把连接器和插件都变成同一种输出格式。

数据流:进去的是 DiscoverableTool 切片,也就是一批只读工具 → 它遍历每个工具:连接器会填编号、名字、描述,并把技能和关联服务留空;插件会额外带上是否有技能、关联的 MCP 服务名和应用连接器编号 → 出来一个 RequestPluginInstallEntry 列表,原列表不变。

调用关系:这是生成“可安装工具清单”时的整理步骤。调用图显示它通过 iter 遍历输入列表;它不负责发现工具本身,只负责把已有工具翻译成适合对外发送的结构。

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

core/src/mention_syntax.rs源码 ↗
configcross-cutting

在这套系统里,用户或模型可能会用某种特殊符号来“点名”一个插件文本或工具。这个文件本身不发明这些符号,也不解析内容,只做一件事:把别的包里已经定义好的两个符号重新导出。这样做的好处是,核心模块里的其他代码可以固定从这个位置引用这些符号,不必直接依赖更深层的来源。以后如果符号的真正来源位置变了,只要这里还保持同样出口,很多调用方就不用跟着改。可以把它理解成服务台窗口:东西实际在仓库里,但大家都来窗口领取,路线更清楚,也更稳定。

配置与策略类型

这些文件捕获跨子系统共享的通用配置、执行策略、网络代理和 cloud-task 约定类型。

cloud-tasks-client/src/api.rs源码 ↗
data_modelcross-cutting

这个文件本身不去联网,也不真正创建或应用任务。它主要是在定规矩。比如,一个任务有编号、标题、状态、更新时间、改了多少文件;一次应用补丁后,要说清楚是成功、部分成功还是失败;如果出错,要统一包装成 CloudTaskError,方便上层处理。这里还定义了 CloudBackend 这个 trait(可以理解成“一份能力清单”):任何真正的云端后端,只要实现了列任务、取详情、取 diff、创建任务、预检查应用、正式应用等函数,就能被客户端当成同一种后端来用。文件里很多类型支持 Serialize 和 Deserialize,也就是能和 JSON 之类的数据互相转换,方便通过网络传输。TaskText 则用来装一个任务的文字内容,比如用户最初的提示、助手回复、是哪一轮尝试等。

函数细节1
TaskText::default124–133 ↗
fn default() -> Self

作用:这个函数给 TaskText 准备一个“空白默认值”。当调用方还没有拿到任务文字、轮次编号或尝试状态时,可以先用这个安全的初始版本,避免到处手动填空字段。

数据流:进去时不需要任何输入 → 它把 prompt、turn_id、attempt_placement 这些可选信息设为“没有”,把 messages 和 sibling_turn_ids 设为空列表,把 attempt_status 设为 Unknown(未知)→ 出来一个完整但内容为空的 TaskText,调用方之后可以再往里面填真实数据。

调用关系:它是 TaskText 这个数据结构的默认构造方式。需要一个初始 TaskText 时,Rust 的默认值机制会调用它;它内部只借助 Vec::new 创建空列表,不会访问网络、磁盘或其他后端。

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

config/src/types.rs源码 ↗
configconfig load / startup

这个文件主要放各种配置类型,也就是一堆结构体和枚举。结构体可以理解成表格,规定一组选项有哪些字段;枚举可以理解成单选题,规定某个选项只能在几个固定答案里选。Codex 启动时会读取 config.toml,这里的类型告诉程序怎么把文本配置变成 Rust 里的数据。很多配置先有“用户写的版本”,字段大多是可选的;再变成“实际生效的版本”,把没写的地方补上默认值,并把不合理的数字夹到安全范围内。比如记忆功能、通知方式、遥测 OTEL、插件工具权限、沙箱写入范围、子进程环境变量等,都在这里有定义。这个文件刻意不放复杂业务逻辑,只负责把配置的形状、默认行为和少量转换规则说清楚。

函数细节18
default_enabled58–60 ↗
fn default_enabled() -> bool

作用:给很多“是否启用”的配置项提供默认值:默认开启。这样用户不写配置时,常见功能不会莫名其妙被关掉。

数据流:它不接收任何输入,也不读取外部状态;调用后直接返回 true;结果通常被 serde(把配置文件转成程序数据的工具)用来填补缺失的 enabled 字段。

调用关系:它是很多配置字段背后的默认值小工具。配置反序列化时如果发现用户没写 enabled,就会用它补上“开启”。

SessionPickerViewMode::as_str72–77 ↗
fn as_str(self) -> &'static str

作用:把会话选择器的显示模式变成固定英文字符串,比如 comfortable 或 dense。这样界面显示、日志或保存文本时都有统一写法。

数据流:输入是一个 SessionPickerViewMode 值;它根据具体是哪种模式做匹配;输出对应的静态字符串,不修改任何状态。

调用关系:它被 SessionPickerViewMode::fmt 调用。也就是说,当外部想把这个模式当文字打印时,会先通过这里拿到标准字符串。

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

SessionPickerViewMode::fmt81–83 ↗
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result

作用:让会话选择器显示模式可以被正常打印成文字。比如程序要显示当前模式时,不需要自己再写一套转换规则。

数据流:输入是当前模式和一个格式化输出器;它先调用 SessionPickerViewMode::as_str 得到标准文字,再把这段文字写进输出器;返回格式化是否成功的结果。

调用关系:它是 Rust 的 Display 显示接口实现。外部用格式化、打印、拼字符串时会走到这里,它把具体转换工作交给 as_str。

调用图:调用 1 个内部函数(as_str);外部调用 1 个(write_str)。

AuthKeyringBackendKind::default127–133 ↗
fn default() -> Self

作用:决定认证信息默认用哪种系统钥匙串保存方式。它会按操作系统选择更合适的方案,减少用户手动配置的负担。

数据流:它不接收输入,但会查看编译目标是不是 Windows;如果是 Windows,就返回 Secrets,否则返回 Direct;它不改动任何文件或全局状态。

调用关系:它在认证和远程控制相关流程中经常被间接用到,尤其是测试里验证未授权后恢复、重试、配对等场景。它给这些流程提供一致的默认钥匙串策略。

调用图:被 92 处调用(list_remote_control_clients_recovers_auth_after_unauthorized, list_remote_control_clients_retries_unauthorized_only_once, remote_control_handle_discards_pairing_response_after_auth_change, remote_control_handle_recovers_auth_before_refreshing_pairing, persisted_enable_does_not_follow_auth_to_an_account_without_a_preference, remote_control_start_allows_missing_auth_when_enabled, remote_control_waits_for_account_id_before_enrolling, connect_remote_control_websocket_recovers_after_unauthorized_enrollment, connect_remote_control_websocket_recovers_after_unauthorized_refresh, connect_remote_control_websocket_requires_chatgpt_auth (+15 more));外部调用 1 个(cfg!)。

UriBasedFileOpener::get_scheme172–180 ↗
fn get_scheme(&self) -> Option<&str>

作用:把用户选择的编辑器打开方式转换成 URI 协议名。比如 VS Code 对应 vscode,用来生成能让编辑器打开文件的链接。

数据流:输入是一个文件打开器选项;它匹配 VS Code、Cursor、Windsurf 等不同选择;输出 Some("协议名"),如果用户选 none 就输出 None。

调用关系:它通常会被需要“跳转到文件”的界面或命令使用。上层先看这里有没有可用协议,有的话再拼出打开文件的链接。

ToolSuggestDisabledTool::plugin247–252 ↗
fn plugin(id: impl Into<String>) -> Self

作用:快速创建一个“被禁用的插件工具”记录。调用者只需要给插件 ID,不用手动填写类型字段。

数据流:输入是一个能转成字符串的 ID;它把 ID 转成 String,并把类型固定设为 Plugin;输出一个 ToolSuggestDisabledTool 新对象。

调用关系:它被 disabled_install_request 这类流程使用,用来表达“这个插件不要再建议或安装”。它把构造细节封起来,避免上层写错类型。

调用图:被 1 处调用(disabled_install_request);外部调用 1 个(into)。

ToolSuggestDisabledTool::connector254–259 ↗
fn connector(id: impl Into<String>) -> Self

作用:快速创建一个“被禁用的连接器工具”记录。连接器可以理解成外部服务入口,比如某个集成服务。

数据流:输入是一个能转成字符串的 ID;它把 ID 转成 String,并把类型固定设为 Connector;输出一个 ToolSuggestDisabledTool 新对象。

调用关系:它和 plugin 构造函数类似,也被 disabled_install_request 使用。上层只表达“禁用这个连接器”,具体字段怎么填由这里保证。

调用图:被 1 处调用(disabled_install_request);外部调用 1 个(into)。

ToolSuggestDisabledTool::normalized261–267 ↗
fn normalized(&self) -> Option<Self>

作用:清理一个禁用工具记录里的 ID,去掉前后空格,并丢弃空 ID。这样配置里不小心写了空白内容时,不会留下无效规则。

数据流:输入是当前禁用工具记录;它读取 id,先 trim 去掉前后空格;如果剩下的 ID 为空,输出 None,否则输出一个带清理后 ID 的新记录。

调用关系:它通常放在读取或整理工具建议配置之后使用。上层拿到用户配置后,可以用它过滤掉脏数据,再继续做禁用判断。

MemoriesConfig::default331–346 ↗
fn default() -> Self

作用:给 Codex 的“记忆”功能提供一整套默认生效配置。用户不写 memories 配置时,程序就按这里的安全默认值运行。

数据流:它不接收输入;内部填好是否生成记忆、是否使用记忆、最多处理多少条、时间限制等默认值;输出一个完整的 MemoriesConfig。

调用关系:它被新建总配置的流程和记忆配置测试使用。后续 MemoriesConfig::from 也会先拿它当底板,再把用户写过的字段覆盖上去。

调用图:被 2 处调用(startup_test_memories_config, new_config)。

MemoriesConfig::from350–392 ↗
fn from(toml: MemoriesToml) -> Self

作用:把用户在 config.toml 里写的 memories 配置,变成真正生效的记忆配置。它会补默认值,还会把过大或过小的数字拉回合理范围。

数据流:输入是 MemoriesToml,也就是用户配置的可选字段集合;它先拿 MemoriesConfig::default 作为默认底板,再逐项检查用户有没有写;写了就用用户值,但对数量、天数、百分比等做 clamp(夹在最小值和最大值之间);输出完整的 MemoriesConfig。

调用关系:它在配置加载后被使用,用来从“原始配置”走到“可执行配置”。相关测试会专门检查它是否能把数量下限和限流百分比处理正确。

调用图:被 2 处调用(memories_config_clamps_count_limits_to_nonzero_values, memories_config_clamps_rate_limit_remaining_threshold);外部调用 1 个(default)。

OtelConfig::default573–583 ↗
fn default() -> Self

作用:给 OTEL 遥测配置提供默认值。OTEL 是 OpenTelemetry 的缩写,可以理解成统一收集日志、指标和调用跟踪的标准。

数据流:它不接收输入;创建默认环境 dev,默认不记录用户提示,不启用普通日志和追踪导出器,但指标导出默认用 Statsig;同时创建空的附加属性表;输出完整的 OtelConfig。

调用关系:它被新建总配置的流程使用。用户没写 OTEL 配置时,程序靠它决定哪些遥测默认开、哪些默认关。

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

Notifications::default594–596 ↗
fn default() -> Self

作用:给通知设置提供默认值:通知默认开启。这样用户不配置时,TUI 仍然可以在需要时提醒用户。

数据流:它不接收输入;直接构造 Notifications::Enabled(true);输出这个默认通知配置,不改动外部状态。

调用关系:它会在 TUI 通知设置缺省时被 serde 或 Default 机制使用,是通知配置的兜底答案。

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

NotificationMethod::fmt609–615 ↗
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result

作用:把通知发送方式打印成用户能看懂、配置里也常见的文字,比如 auto、osc9、bel。

数据流:输入是一个通知方式和格式化输出器;它按枚举值选择对应小写字符串;把字符串写入输出器,并返回写入是否成功。

调用关系:当日志、界面或错误信息需要展示通知方式时会走到这里。它保证显示出来的名字和配置里的写法保持一致。

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

NotificationCondition::fmt629–634 ↗
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result

作用:把通知触发条件打印成文字,比如 unfocused 或 always。这样程序展示设置时不会出现内部编码名。

数据流:输入是一个通知条件和格式化输出器;它判断是“终端未聚焦时”还是“总是”;写入对应字符串并返回格式化结果。

调用关系:它服务于显示和日志输出。上层只要把 NotificationCondition 当作可显示对象使用,就会自动调用这里。

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

default_true775–777 ↗
fn default_true() -> bool

作用:给一些布尔配置项提供默认 true,比如动画、工具提示、状态栏颜色等。它的作用是让这些体验型功能默认打开。

数据流:它不接收任何输入;直接返回 true;通常由配置解析器在字段缺失时调用。

调用关系:它是 TUI 配置字段的默认值供应器。配置文件没写相关字段时,界面功能会通过它保持开启。

PluginMcpServerConfig::default865–873 ↗
fn default() -> Self

作用:给插件提供的 MCP 服务器配置一套默认策略。MCP 可以理解成让 Codex 调用外部工具的一种接口协议。

数据流:它不接收输入;生成 enabled 为 true、审批模式为空、工具白名单和黑名单为空、单个工具配置表为空的新配置;输出 PluginMcpServerConfig。

调用关系:当插件 MCP 服务器没有单独配置时会使用它。后续加载插件策略时,会在这个默认配置上叠加用户写的开关和工具规则。

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

SandboxSettings::from920–927 ↗
fn from(sandbox_workspace_write: SandboxWorkspaceWrite) -> Self

作用:把配置里的沙箱写入规则转换成 app-server 协议能理解的沙箱设置。沙箱可以理解成给程序划定“哪些地方能写、能不能联网”的安全边界。

数据流:输入是 SandboxWorkspaceWrite,里面有可写目录、是否允许网络、是否排除临时目录等;它把这些字段搬到 codex_app_server_protocol::SandboxSettings 里,并把几个布尔值包成 Some;输出协议层的 SandboxSettings。

调用关系:它连接了配置层和应用服务器协议层。上层读到用户配置后,通过这里变成可以发送给 app-server 的格式。

ShellEnvironmentPolicy::from950–977 ↗
fn from(toml: ShellEnvironmentPolicyToml) -> Self

作用:把用户写的子进程环境变量策略,转换成真正用于启动命令的环境策略。环境变量就是启动程序时带上的一组名字和值,比如 PATH、HOME。

数据流:输入是 ShellEnvironmentPolicyToml,字段大多可选;它补上默认继承全部环境、默认忽略内置排除规则等值;把 exclude 和 include_only 里的字符串转成大小写不敏感的匹配模式;最后输出 ShellEnvironmentPolicy。

调用关系:它在配置加载后、真正启动 shell 类工具前发挥作用。上层用它决定哪些环境变量带给子进程、哪些要过滤、哪些要强制设置。

execpolicy-legacy/src/exec_call.rs源码 ↗
data_modelcross-cutting

这个文件很小,但很关键。它把“运行一个外部程序”这件事包装成 ExecCall 这个结构体:里面有 program,也就是程序名,比如 cp;还有 args,也就是参数列表,比如源文件和目标文件。这样代码里就不用到处传一堆散乱的字符串,而是传一个完整的“命令记录”。它还能被复制、调试打印、比较是否相同,并且能被序列化(把数据变成可保存或传输的格式)。ExecCall::new 是一个方便的创建入口,把普通字符串切片转换成拥有所有权的 String。Display 实现则决定它被当成文本显示时的样子:先显示程序名,再用空格接上每个参数,就像人在终端里看到的一条命令。

函数细节2
ExecCall::new12–17 ↗
fn new(program: &str, args: &[&str]) -> Self

作用:这个函数用来快速造出一个 ExecCall,也就是一条“准备执行的命令”。调用者只要给程序名和参数列表,它就整理成结构化的数据。

数据流:进去的是一个程序名,比如 "cp",以及一组参数,比如 &["a", "b"]。函数会把这些借来的文本复制成 ExecCall 自己持有的字符串,避免原来的文本失效后数据丢掉。出来的是一个新的 ExecCall,里面完整保存了程序名和参数。

调用关系:它通常在测试里被用来构造期望结果,比如测试 cp、head 等命令解析出来的执行调用是否正确。它不把工作交给别的项目函数,只做一件事:把输入文字整理成 ExecCall。

调用图:被 28 处调用(test_cp_multiple_files, test_cp_no_args, test_cp_one_arg, test_cp_one_file, test_head_invalid_n_as_0, test_head_invalid_n_as_float, test_head_invalid_n_as_negative_int, test_head_invalid_n_as_nonint_float, test_head_no_args, test_head_one_file_no_flags (+15 more))。

ExecCall::fmt21–27 ↗
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result

作用:这个函数决定 ExecCall 被显示成文字时是什么样子。它把结构化的命令对象还原成类似终端里的一行命令,方便日志、报错或调试查看。

数据流:进去的是一个已有的 ExecCall 和一个格式化输出位置。函数先写入程序名,然后依次把每个参数前面加一个空格写出去。出来的是格式化是否成功的结果;它不会修改 ExecCall,只是把它写成文本。

调用关系:当代码使用格式化输出,比如把 ExecCall 放进 println! 或 format! 这类显示场景时,Rust 会自动调用它。它内部只调用标准的 write! 宏,把文本一步步写到目标里。

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

execpolicy-legacy/src/valid_exec.rs源码 ↗
data_modelrequest handling

这份文件像是一张“放行单”的格式说明。前面的策略检查确认某个 exec() 调用,也就是程序启动请求,是允许的之后,就会用这里的 ValidExec 来保存结果:要运行哪个程序,匹配到了哪些普通参数、哪些带值选项、哪些不带值开关。MatchedArg 和 MatchedOpt 在创建时会先让 ArgType 做校验,ArgType 可以理解成“参数应该是什么类型、合不合法”的规则,所以脏数据不会轻易混进放行单里。ValidExec 还带着 system_path,也就是建议优先尝试的安全路径,比如用 /bin/ls,而不是随便从用户 PATH 里找 ls。另一个重要点是 might_write_files,它能粗略判断这条命令有没有可能写文件,方便调用方在执行前做更谨慎的处理。

函数细节6
ValidExec::new21–29 ↗
fn new(program: &str, args: Vec<MatchedArg>, system_path: &[&str]) -> Self

作用:创建一张新的“已通过检查的执行放行单”。调用者给它程序名、已经匹配好的普通参数,以及推荐使用的系统路径,它会整理成 ValidExec。

数据流:进去的是程序名、参数列表、系统路径字符串切片 → 它把程序名和路径都复制成自己持有的字符串,把 flags 和 opts 先设为空列表 → 出来的是一个完整的 ValidExec 初始对象,后面可以继续填入匹配到的标志位和选项。

调用关系:它位于策略检查之后、真正执行之前的整理阶段。函数内部用空向量初始化 flags 和 opts,相当于先准备好两个空篮子,等后续流程把匹配到的开关和选项放进去。

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

ValidExec::might_write_files33–36 ↗
fn might_write_files(&self) -> bool

作用:判断这条已经通过检查的命令有没有可能写文件。它不是执行命令,而是提前看参数类型里有没有“可能改文件”的迹象。

数据流:进去的是一个 ValidExec 对象自身,其中已经有选项和参数 → 它逐个查看 opts 和 args 里的 ArgType,询问这些类型是否可能写文件 → 出来的是 true 或 false,true 表示需要把这条命令当成可能产生文件写入来对待。

调用关系:它通常会被后续更谨慎的执行或审计流程使用。它依赖每个参数类型自己的判断,把零散参数上的风险汇总成整条命令的风险提示。

MatchedArg::new47–54 ↗
fn new(index: usize, r#type: ArgType, value: &str) -> Result<Self>

作用:创建一个已经匹配成功的普通参数记录。它会先检查这个参数值是否符合规定类型,避免把不合规的参数包装成合法结果。

数据流:进去的是参数在命令行里的位置、参数类型 ArgType、实际字符串值 → 它先调用 validate 校验这个值是否合法 → 如果校验通过,就输出 MatchedArg;如果失败,就返回错误,不会生成这条参数记录。

调用关系:它会被 resolve_observed_args_with_patterns 调用,也就是在“把实际看到的命令行参数和策略里的参数模式对上号”时使用。它把校验工作交给 ArgType 的 validate,自己负责把通过校验的信息装成统一的数据结构。

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

MatchedOpt::new69–76 ↗
fn new(name: &str, value: &str, r#type: ArgType) -> Result<Self>

作用:创建一个已经匹配成功的带值选项记录。比如某个选项后面跟了文件名或路径,它会确认这个值符合策略要求。

数据流:进去的是选项名、选项值、这个值应该符合的 ArgType → 它先调用 validate 检查值 → 检查通过后输出 MatchedOpt;检查不通过就返回错误,表示这个选项虽然名字像对了,但值不合规。

调用关系:它会被 check 调用,出现在整体策略检查过程中。check 发现一个选项可能匹配后,会让 MatchedOpt::new 做最后的值校验和打包。

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

MatchedOpt::name78–80 ↗
fn name(&self) -> &str

作用:取出这个已匹配选项的名字。它提供一个安全、简单的读取入口,不需要外部直接关心字段怎么存。

数据流:进去的是一个 MatchedOpt 对象自身 → 它不修改任何内容,只返回内部 name 字符串的引用 → 出来的是选项名,调用者可以用它来比较、显示或继续处理。

调用关系:它是 MatchedOpt 的小工具函数,通常在后续流程需要知道“这个选项叫什么”时使用。它不调用别的业务函数,也不触发校验,只负责读取现有信息。

MatchedFlag::new90–94 ↗
fn new(name: &str) -> Self

作用:创建一个已经匹配成功的标志位记录。标志位是不带值的开关,比如类似 --verbose 这种只表示开或关的东西。

数据流:进去的是标志位名称 → 它把名称复制成自己持有的字符串 → 出来的是 MatchedFlag 对象,表示这个开关已经在命令行里被识别到了。

调用关系:它服务于策略匹配后的结果整理阶段。和 MatchedArg、MatchedOpt 不同,标志位没有额外的值,所以这里不需要调用 validate,只要把名字保存下来即可。

execpolicy/src/decision.rs源码 ↗
data_modelconfig load / policy parsing

这个文件就像门卫手里的三种牌子:绿牌是 Allow,表示命令可以直接运行;黄牌是 Prompt,表示先停一下,要用户明确同意;红牌是 Forbidden,表示不用再问,直接拦住。这样做的好处是,系统里其他地方不需要用零散的字符串来猜意思,而是都使用同一个 Decision 类型,减少写错和理解不一致。它还支持序列化和反序列化,也就是可以方便地和配置文件、网络数据、保存的数据互相转换。Decision::parse 负责把外部读进来的文字,比如 “allow”,变成程序内部可靠的枚举值;如果遇到不认识的文字,就返回一个明确的错误,而不是悄悄当成默认值,这能避免安全策略被误读。

函数细节1
Decision::parse19–26 ↗
fn parse(raw: &str) -> Result<Self>

作用:这个函数把外部传进来的文字决定结果,转换成程序内部使用的 Decision。有人会在读取配置或规则时用它,确保 “allow”“prompt”“forbidden” 这些字面值被准确理解。

数据流:输入是一段字符串 raw。函数检查它是不是 “allow”“prompt” 或 “forbidden”:是的话,就分别变成 Allow、Prompt 或 Forbidden 返回;如果不是,就把这个陌生内容放进 InvalidDecision 错误里返回。它不修改别的东西,只负责把文字翻译成可靠的内部结果,或者报告翻译失败。

调用关系:它会在解析网络规则决定结果时被 parse_network_rule_decision 调用,相当于规则解析流程里的“翻译员”。如果翻译失败,它会交给 InvalidDecision 生成错误,让上层知道这条规则里的决定值不合法。

调用图:被 1 处调用(parse_network_rule_decision);外部调用 1 个(InvalidDecision)。

network-proxy/src/reasons.rs源码 ↗
data_modelcross-cutting

这个文件本身不做判断,也不处理网络请求,它像一本很小的“原因词典”。比如请求被策略拦了,就用“policy_denied”;代理功能被关了,就用“proxy_disabled”;需要中间人解密检查但条件不满足,就用“mitm_required”。把这些文字统一写在一个地方很重要:如果各处代码自己随手写字符串,很容易拼错,或者同一种情况写出好几个不同名字,日志、统计、错误返回就会乱。这里的每个常量都是一个稳定的原因编号,给程序内部、日志系统或上层调用方看,用来说明一次代理行为为什么被拒绝或不能继续。

Skills 与扩展模型

这些文件定义 skills 目录、已加载 skills 及相关选择元数据的共享内存 schema 和面向扩展的 schema。

core-skills/src/model.rs源码 ↗
data_modelskill loading, request handling

可以把这个文件看成技能系统的“档案夹”。每个技能都有名字、说明、声明它的 SKILLS.md 文件路径、适用范围、界面信息、依赖工具和使用政策。加载技能时,不只会得到技能列表,还会得到错误、被禁用的路径、每个技能该从哪个文件系统读取等配套信息。这里的 SkillLoadOutcome 就像一次扫描后的总清单:哪些技能找到了,哪些坏了,哪些被关掉了。HostLoadedSkills 则把这份清单包起来,方便后续真正读取技能正文。文件里还特别注意“产品限制”:有些技能只给某些产品用,filter_skill_load_outcome_for_product 会把不合适的技能和相关索引一起删掉,避免后面误用。

函数细节15
SkillMetadata::allows_implicit_invocation29–34 ↗
fn allows_implicit_invocation(&self) -> bool

作用:判断这个技能是否允许被系统“自动叫出来用”。如果技能没有明确说不允许,就默认允许。

数据流:输入是一个技能档案里的 policy 字段,也就是技能自己的使用规则。函数查看里面的 allow_implicit_invocation;如果写了 true 或 false 就照办,如果没写或者没有规则,就返回 true。它不改任何数据,只给出一个是或否。

调用关系:它是后面判断“能不能自动调用技能”的基础小检查。SkillLoadOutcome::is_skill_allowed_for_implicit_invocation 会调用它,把“技能没被禁用”和“技能允许自动调用”两个条件合在一起看。

SkillMetadata::matches_product_restriction_for_product36–49 ↗
fn matches_product_restriction_for_product(
        &self,
        restriction_product: Option<Product>,
    ) -> bool

作用:判断这个技能是否适合当前指定的产品使用。比如一个技能可能只允许在某个产品里出现。

数据流:输入是当前产品限制 restriction_product,以及技能自己的 policy.products 列表。函数先看技能有没有政策;没有就默认可用。有政策但产品列表为空,也表示不限制。否则,它会检查当前产品是否匹配技能声明的产品限制,最后返回 true 或 false。

调用关系:它主要给按产品过滤技能的流程使用。filter_skill_load_outcome_for_product 会反复调用这个判断,把不适合当前产品的技能从总清单和相关索引里剔除。

SkillLoadOutcome::is_skill_enabled104–106 ↗
fn is_skill_enabled(&self, skill: &SkillMetadata) -> bool

作用:判断某个技能当前是不是启用状态。这里的启用与否,是看它的 SKILLS.md 路径有没有出现在禁用列表里。

数据流:输入是一份加载结果和一个技能。函数读取 outcome 里的 disabled_paths,再拿技能的 path_to_skills_md 去查。如果没在禁用集合里,就返回 true;在里面就返回 false。它只读取状态,不修改状态。

调用关系:它被 SkillLoadOutcome::is_skill_allowed_for_implicit_invocation 调用。也就是说,在判断技能能不能自动用之前,系统会先确认它没有被用户或配置关掉。

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

SkillLoadOutcome::is_skill_allowed_for_implicit_invocation108–110 ↗
fn is_skill_allowed_for_implicit_invocation(&self, skill: &SkillMetadata) -> bool

作用:判断某个技能是否真的可以被系统自动调用。它同时要求技能没有被禁用,并且技能自己的政策允许自动调用。

数据流:输入是一份加载结果和一个技能。函数先通过 is_skill_enabled 看技能有没有被关掉,再通过 SkillMetadata::allows_implicit_invocation 看技能自己是否允许自动触发。两个条件都满足才返回 true。

调用关系:它把两个小判断串起来,供更大的筛选流程使用。SkillLoadOutcome::allowed_skills_for_implicit_invocation 会用它逐个筛选技能,最终拿到可自动调用的技能列表。

调用图:调用 1 个内部函数(is_skill_enabled);外部调用 1 个(allows_implicit_invocation)。

SkillLoadOutcome::allowed_skills_for_implicit_invocation112–118 ↗
fn allowed_skills_for_implicit_invocation(&self) -> Vec<SkillMetadata>

作用:从全部技能里挑出“可以被系统自动调用”的那一批。调用者需要的是一个干净的可用列表,而不是自己再挨个判断。

数据流:输入是加载结果里的 skills 列表和禁用信息。函数遍历每个技能,用 is_skill_allowed_for_implicit_invocation 检查它是否合格;合格的技能会被复制到一个新的列表里并返回。原来的加载结果不会被修改。

调用关系:它被 finalize_skill_outcome 和 build_available_skills 使用。也就是在整理最终技能结果、构建可用技能清单时,它负责把“自动可用”的技能先筛出来。

调用图:被 2 处调用(finalize_skill_outcome, build_available_skills)。

SkillLoadOutcome::skills_with_enabled120–124 ↗
fn skills_with_enabled(&self) -> impl Iterator<Item = (&SkillMetadata, bool)>

作用:把每个技能和它当前是否启用配成一对,方便展示或生成目录。这样调用者不只知道有哪些技能,也知道每个是不是被关掉了。

数据流:输入是加载结果里的技能列表和 disabled_paths。函数逐个读取技能,调用 is_skill_enabled 得到 true 或 false,然后产生一个迭代器。这个迭代器每次给出“技能本身 + 是否启用”的组合。

调用关系:它被 catalog_from_outcome 调用。也就是说,当系统要从加载结果生成技能目录时,会用它把技能状态一起带上。

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

SkillLoadOutcome::file_system_for_skill126–132 ↗
fn file_system_for_skill(
        &self,
        skill: &SkillMetadata,
    ) -> Option<Arc<dyn ExecutorFileSystem>>

作用:找到某个技能应该通过哪个文件系统读取。这里的文件系统可以理解为“去哪里、用什么方式拿文件”的接口,不一定总是本机磁盘。

数据流:输入是一份加载结果和一个技能。函数拿技能的 SKILLS.md 路径,到 file_systems_by_skill_path 里查询对应的 ExecutorFileSystem。如果找到了,就返回这个文件系统;找不到就返回空。

调用关系:它把查询工作交给 SkillFileSystemsByPath::get。HostLoadedSkills::read_skill_text 会间接依赖这个结果,决定读取技能正文时走加载时提供的文件系统,还是退回到本地文件系统。

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

HostLoadedSkills::new143–145 ↗
fn new(outcome: Arc<SkillLoadOutcome>) -> Self

作用:创建一个 HostLoadedSkills 包装对象,把一次技能加载结果保存起来,供这一轮运行继续使用。

数据流:输入是一个 Arc<SkillLoadOutcome>。Arc 可以简单理解成“多人共享同一份数据的安全指针”,不用复制整份结果。函数把它放进 HostLoadedSkills 里,然后返回新对象。

调用关系:它被 spawn_review_thread、make_turn_context、skill_loading_and_reads_use_the_supplied_executor_file_system、installed_extension_uses_host_loaded_skills 使用。也就是说,无论是创建一轮对话上下文、启动评审线程,还是测试宿主提供的文件系统,都会先用它把加载结果包好。

调用图:被 4 处调用(spawn_review_thread, make_turn_context, skill_loading_and_reads_use_the_supplied_executor_file_system, installed_extension_uses_host_loaded_skills)。

HostLoadedSkills::outcome147–149 ↗
fn outcome(&self) -> &SkillLoadOutcome

作用:取出 HostLoadedSkills 里面保存的技能加载结果,供外部查看。它只是借给别人看,不转移所有权。

数据流:输入是 HostLoadedSkills 自己。函数读取内部的 Arc<SkillLoadOutcome>,返回一个普通引用。调用者能看到技能、错误、禁用路径等信息,但这个函数本身不修改任何内容。

调用关系:它是 HostLoadedSkills 对外暴露加载结果的窗口。需要查看本轮技能清单的代码会通过它拿到 SkillLoadOutcome,而不必知道 HostLoadedSkills 内部怎么存。

HostLoadedSkills::read_skill_text151–158 ↗
async fn read_skill_text(&self, skill: &SkillMetadata) -> io::Result<String>

作用:读取某个技能的 SKILLS.md 正文内容。它会尽量使用当初加载该技能时对应的文件系统,避免读错地方。

数据流:输入是 HostLoadedSkills 和一个技能。函数先找这个技能对应的文件系统;如果没有记录,就使用 LOCAL_FS,也就是本机文件系统。然后把绝对路径转成 PathUri 这种文件路径标识,调用文件系统读取文本,最后异步返回字符串或读文件错误。

调用关系:它调用 PathUri::from_abs_path 把路径变成文件系统接口能识别的格式。它处在“已经知道要用哪个技能,现在要拿到技能正文”的阶段,是技能加载结果真正被消费时的重要入口。

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

SkillFileSystemsByPath::new167–171 ↗
fn new(values: HashMap<AbsolutePathBuf, Arc<dyn ExecutorFileSystem>>) -> Self

作用:创建一张“技能路径到文件系统”的对照表。这样后面看到某个 SKILLS.md 路径,就知道该从哪里读它。

数据流:输入是一个 HashMap,里面每条记录都是“技能文件路径 → 文件系统”。函数把这张表放进 Arc 里共享,然后返回 SkillFileSystemsByPath。它不检查内容,只负责包装成统一类型。

调用关系:它被 load_skills_from_roots 调用。也就是技能从多个根目录加载出来时,加载流程会顺手建立这张对照表,供后续读取技能正文使用。

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

SkillFileSystemsByPath::get173–175 ↗
fn get(&self, path: &AbsolutePathBuf) -> Option<Arc<dyn ExecutorFileSystem>>

作用:根据技能文件路径,查出对应的文件系统。查到时会返回一份共享引用,方便调用者使用。

数据流:输入是 SkillFileSystemsByPath 和一个绝对路径。函数在内部表里查这个路径;如果有,就克隆 Arc 指针返回。这里的克隆不是复制文件系统本身,而是多一个共享使用者。查不到就返回空。

调用关系:它被 SkillLoadOutcome::file_system_for_skill 调用。外部通常不会直接碰这张表,而是通过加载结果来查某个技能的文件系统。

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

SkillFileSystemsByPath::retain_paths177–185 ↗
fn retain_paths(&mut self, paths: &HashSet<AbsolutePathBuf>)

作用:把文件系统对照表缩小到只保留指定路径。过滤掉技能后,相关的文件系统记录也要一起删,避免留下过期索引。

数据流:输入是一组要保留的路径。函数遍历现有对照表,只留下路径在这组集合里的记录,并把结果重新放进一个新的 Arc<HashMap>。调用后,这个对象内部保存的表会变小。

调用关系:它服务于产品过滤这类“删掉一部分技能”的流程。filter_skill_load_outcome_for_product 会在保留合适技能后调用它,让文件系统映射和剩下的技能保持一致。

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

SkillFileSystemsByPath::fmt189–193 ↗
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result

作用:定义调试打印 SkillFileSystemsByPath 时显示什么。它不会把所有文件系统细节打印出来,只显示里面有多少条记录。

数据流:输入是要打印的 SkillFileSystemsByPath 和格式化器。函数创建一个调试结构,写入字段 len,也就是内部表的长度,然后完成格式化输出。它不改变原数据。

调用关系:它调用标准调试工具 debug_struct。这个函数通常在日志、调试或测试失败输出时被自动使用,让开发者知道映射表大小,而不会输出复杂或敏感的文件系统对象。

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

filter_skill_load_outcome_for_product196–241 ↗
fn filter_skill_load_outcome_for_product(
    mut outcome: SkillLoadOutcome,
    restriction_product: Option<Product>,
) -> SkillLoadOutcome

作用:按当前产品限制过滤一次技能加载结果。它不只是删技能列表,还会同步清理相关索引,避免后面还能从别的入口找到被过滤掉的技能。

数据流:输入是一份 SkillLoadOutcome 和一个可选的 Product。函数先保留匹配产品限制的技能,再收集剩余技能的路径。接着它清理文件系统映射、技能根目录映射、技能根列表、脚本目录到隐式技能的索引、文档路径到隐式技能的索引。最后返回一份内部数据彼此一致的新加载结果。

调用关系:它使用 SkillMetadata::matches_product_restriction_for_product 这类判断来决定哪些技能留下。它位于“技能已经加载完,但要根据当前产品再收口”的阶段,作用像门卫:不适合这个产品的技能和它的旁路入口都不能放过去。

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

ext/skills/src/catalog.rs源码 ↗
data_modelcross-cutting

可以把这个文件理解成技能系统的“商品清单格式”。技能可能来自主程序、执行环境、编排器,或者以后新增的来源;系统不能随便猜路径、猜来源,所以这里用一批专门的类型把它们包起来。比如 SkillAuthority 表示“谁拥有这个技能”,SkillResourceId 表示“技能里的某个文件或提示词”,SkillCatalogEntry 表示目录里展示给模型或用户看的一个技能条目。SkillCatalog 则像一个购物篮,可以把多个来源的技能合并起来,并自动跳过重复条目。这个文件还定义了读取技能内容、搜索技能内容时返回的数据格式,以及 SkillProviderError 这种统一错误。它本身不去读磁盘、不联网,主要作用是定规矩:其他代码按这些规矩传递技能信息,才能避免把不同来源、不同环境里的技能搞混。

函数细节19
SkillSourceKind::custom20–22 ↗
fn custom(kind: impl Into<String>) -> Self

作用:创建一个自定义的技能来源类型。当前已有 host、executor、orchestrator 这些固定来源;如果以后有新来源,又不适合归到这些类别,就用它。

数据流:进去的是一个能变成字符串的来源名字 → 函数把它转换成真正的字符串 → 出来的是 SkillSourceKind::Custom,代表一个自定义来源类别。

调用关系:它是给扩展点用的小入口。后续需要显示或比较来源时,这个自定义值会和内置来源一样被当成 SkillSourceKind 使用。

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

SkillSourceKind::as_str24–31 ↗
fn as_str(&self) -> &str

作用:把技能来源类型变成一段稳定的文字。这样无论来源是内置的还是自定义的,都能用统一方式显示出来。

数据流:进去的是一个 SkillSourceKind → 函数根据具体种类选出对应文字,比如 host、executor、orchestrator,或者自定义名字 → 出来的是可借用的字符串片段,不会额外复制。

调用关系:它主要服务于 SkillSourceKind::fmt。外部要把来源打印成文字时,会先走 fmt,再由 fmt 调用这里取实际文字。

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

SkillSourceKind::fmt35–37 ↗
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result

作用:让 SkillSourceKind 可以被正常打印成字符串。比如写日志、拼错误信息、展示来源时会用到。

数据流:进去的是一个来源类型和一个格式化输出器 → 它先调用 SkillSourceKind::as_str 拿到文字 → 再把这段文字写进输出器,最后返回格式化是否成功。

调用关系:这是 Rust 的 Display 接口实现。谁需要把 SkillSourceKind 当普通文字显示,都会间接走到这里;它把具体取名字的工作交给 as_str。

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

SkillAuthority::new48–53 ↗
fn new(kind: SkillSourceKind, id: impl Into<String>) -> Self

作用:创建一个“技能归属方”标识。它回答的是:这个技能由哪类来源、哪个具体提供者负责。

数据流:进去的是来源类型 kind 和一个提供者 id → 函数把 id 转成字符串并和 kind 放在一起 → 出来的是 SkillAuthority。

调用关系:很多地方会用它来给技能打上归属标签,比如列技能、从技能生成目录条目、读取技能资源、测试构造条目等。后面去路由读取请求时,就靠这个归属信息知道该找谁。

调用图:被 7 处调用(list, catalog_entry_from_skill, read, catalog_entry_from_resource, from_authority, into_authority, test_entry);外部调用 1 个(into)。

SkillResourceId::new69–74 ↗
fn new(id: impl Into<String>) -> Self

作用:创建一个普通的技能资源标识。资源可以理解成技能包里的一个文件,比如主提示词文件。

数据流:进去的是资源 id → 函数把它转成字符串,并且不绑定任何执行环境路径 → 出来的是 SkillResourceId。

调用关系:从技能或资源生成目录条目、处理请求、测试构造条目时都会用它。它适合那些不需要额外记录环境路径的资源。

调用图:被 4 处调用(catalog_entry_from_skill, catalog_entry_from_resource, handle, test_entry);外部调用 1 个(into)。

SkillResourceId::environment76–88 ↗
fn environment(
        id: impl Into<String>,
        environment_id: impl Into<String>,
        path: AbsolutePathBuf,
    ) -> Self

作用:创建一个绑定到某个执行环境路径的技能资源标识。也就是说,这个资源不只是有名字,还明确知道它在某个环境里的真实位置。

数据流:进去的是资源 id、环境 id、绝对路径 → 函数把 id 和环境 id 转成字符串,并保存路径 → 出来的是带 environment_path 信息的 SkillResourceId。

调用关系:它在从技能生成目录条目时使用,特别适合技能资源属于某个执行环境的情况。后续如果要按环境读取这个资源,可以通过保存的环境 id 和路径找到正确位置。

调用图:被 1 处调用(catalog_entry_from_skill);外部调用 1 个(into)。

SkillResourceId::as_str90–92 ↗
fn as_str(&self) -> &str

作用:取出资源标识里的原始文字。需要展示资源名、拼路径说明或渲染目录时会用到。

数据流:进去的是一个 SkillResourceId → 函数直接借出内部 id 字符串 → 出来的是字符串片段,不会修改任何东西。

调用关系:它是资源 id 的只读出口。比如 SkillCatalogEntry::rendered_path 没有显示路径可用时,就会用主提示词资源的这个文字来展示。

SkillResourceId::environment_path94–98 ↗
fn environment_path(&self) -> Option<(&str, &AbsolutePathBuf)>

作用:查看这个资源是否绑定了执行环境里的真实路径。如果绑定了,就把环境 id 和路径拿出来。

数据流:进去的是一个 SkillResourceId → 函数检查内部有没有 environment_path → 有的话输出环境 id 和绝对路径的引用,没有的话输出空值。

调用关系:这是给同一模块内部读取资源时用的辅助口。它不公开给所有外部代码,是为了避免外部随便依赖或解析路径。

SkillCatalogEntry::new123–142 ↗
fn new(
        id: SkillPackageId,
        authority: SkillAuthority,
        name: impl Into<String>,
        description: impl Into<String>,
        main_prompt: SkillResourceId,
    ) -> Self

作用:创建一个技能目录条目的基本版本。一个条目就是目录里的一项技能,包含名字、说明、归属方和主提示词资源。

数据流:进去的是技能包 id、归属方、名称、描述、主提示词资源 → 函数把名称和描述转成字符串,并填好默认值:启用、提示词可见、没有短描述、没有展示路径、没有依赖 → 出来的是 SkillCatalogEntry。

调用关系:从技能、资源或测试数据生成目录条目时都会先调用它。之后如果需要补短描述、展示路径、依赖,通常会继续调用 with_short_description、with_display_path、with_dependencies 这些链式方法。

调用图:被 4 处调用(catalog_entry_from_skill, catalog_entry_from_skill, catalog_entry_from_resource, test_entry);外部调用 1 个(into)。

SkillCatalogEntry::with_short_description144–147 ↗
fn with_short_description(mut self, short_description: Option<String>) -> Self

作用:给目录条目补上一段短说明。短说明通常用于更紧凑的展示场景。

数据流:进去的是一个已有目录条目和可选短说明 → 函数把 short_description 字段替换成传入值 → 出来的是修改后的同一个条目。

调用关系:它是 SkillCatalogEntry::new 之后的补充步骤。调用者可以像搭积木一样先建基本条目,再决定是否加短说明。

SkillCatalogEntry::with_display_path149–152 ↗
fn with_display_path(mut self, display_path: impl Into<String>) -> Self

作用:给目录条目设置一个专门用于展示的路径。这样展示给人或模型看的路径,不一定非要等于内部资源 id。

数据流:进去的是一个已有目录条目和展示路径 → 函数把路径转成字符串,放进 display_path → 出来的是带展示路径的条目。

调用关系:它通常接在 SkillCatalogEntry::new 后面使用。之后 render_skill_line 渲染技能行时,会通过 SkillCatalogEntry::rendered_path 优先看到这个展示路径。

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

SkillCatalogEntry::with_dependencies154–157 ↗
fn with_dependencies(mut self, dependencies: Option<SkillDependencies>) -> Self

作用:给目录条目补上技能依赖信息。依赖就是这个技能运行或使用时还需要哪些东西。

数据流:进去的是一个已有目录条目和可选依赖信息 → 函数把 dependencies 字段替换成传入值 → 出来的是修改后的条目。

调用关系:它是生成目录条目过程中的可选补充。上游解析到依赖后可以挂到条目上,下游展示或决策时就能看到。

SkillCatalogEntry::disabled159–162 ↗
fn disabled(mut self) -> Self

作用:把一个技能目录条目标记为不可用。它仍然在目录数据里,但 enabled 会变成 false。

数据流:进去的是一个已有目录条目 → 函数把 enabled 改成 false → 出来的是被标记为禁用的条目。

调用关系:它用于构造条目时表达“这个技能存在,但当前不能用”。后续展示或选择技能的代码可以根据 enabled 决定是否让它参与使用。

SkillCatalogEntry::hidden_from_prompt164–167 ↗
fn hidden_from_prompt(mut self) -> Self

作用:把一个技能标记为不出现在提示词里。也就是说,技能可以存在,但不主动展示给模型看。

数据流:进去的是一个已有目录条目 → 函数把 prompt_visible 改成 false → 出来的是提示词中隐藏的条目。

调用关系:它用于控制技能目录对模型的可见性。后续渲染提示词或技能列表时,可以用 prompt_visible 过滤掉这些条目。

SkillCatalogEntry::rendered_path169–173 ↗
fn rendered_path(&self) -> &str

作用:决定目录里应该显示哪条路径。优先显示专门设置的 display_path;如果没有,就显示主提示词资源 id。

数据流:进去的是一个目录条目 → 函数先看 display_path 是否存在 → 有就返回它,没有就返回 main_prompt 的字符串形式。

调用关系:render_skill_line 渲染一行技能说明时会调用它。它把“展示路径该怎么选”的小规则集中在这里,避免渲染代码到处重复判断。

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

SkillCatalog::extend184–189 ↗
fn extend(&mut self, other: SkillCatalog)

作用:把另一个技能目录合并进当前目录。它会带上对方的技能条目和警告信息。

数据流:进去的是当前目录和另一个目录 → 函数逐个取出对方条目,并通过 push_entry 放进当前目录以避免重复 → 然后把对方 warnings 追加进来 → 当前目录被更新,没有单独返回新目录。

调用关系:extend_catalog 这类汇总流程会调用它。它把真正插入条目的细节交给 SkillCatalog::push_entry,这样合并时也能保持去重规则一致。

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

SkillCatalog::push_entry191–201 ↗
fn push_entry(&mut self, entry: SkillCatalogEntry)

作用:往技能目录里放入一个条目,同时避免同一个归属方下的同一个技能包被重复加入。

数据流:进去的是当前目录和一个新条目 → 函数检查已有 entries 里是否已经有相同 authority 和 id 的条目 → 如果有就什么也不做;如果没有,就把新条目追加进去。

调用关系:SkillCatalog::extend 会用它来合并多个目录。它是目录去重的守门员,保证不同来源汇总时不会把同一个技能显示两遍。

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

SkillProviderError::new231–235 ↗
fn new(message: impl Into<String>) -> Self

作用:创建一个统一的技能提供方错误。比如列技能、读技能、搜索技能失败时,都可以用它带上一句人能看懂的错误消息。

数据流:进去的是错误消息 → 函数把消息转成字符串 → 出来的是 SkillProviderError。

调用关系:读取、列出、搜索技能的代码在出错时会调用它。这样错误能统一用 SkillProviderResult 返回,而不是每个地方各造一种不同格式的失败结果。

调用图:被 8 处调用(read, list, read, list, read, read, search, list);外部调用 1 个(into)。

SkillProviderError::fmt239–241 ↗
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result

作用:让 SkillProviderError 可以像普通错误文字一样被打印出来。显示给日志、调试信息或上层错误处理时会用到。

数据流:进去的是错误对象和格式化输出器 → 函数把内部 message 写入输出器 → 出来的是格式化是否成功。

调用关系:这是 Rust 的 Display 接口实现。因为 SkillProviderError 也实现了标准错误接口,外部把它当错误展示时会间接走到这里。

状态与持久化 schema

这些文件定义持久化层和运行时状态管理使用的共享状态、图、记忆处理、线程存储和进程状态模型。

agent-graph-store/src/types.rs源码 ↗
data_modelcross-cutting

这个文件很小,但它给系统里的“父线程生成子线程”这条关系贴了一个状态标签。可以把它想成一张流程图里的箭头:箭头连着父任务和子任务,而这个状态说明子任务现在还能不能继续用。ThreadSpawnEdgeStatus 有两个值:Open 表示子线程还活着,或者还能恢复;Closed 表示从父子关系图的角度看,这条子线程已经结束。它还使用 serde(Rust 里常用的序列化工具,意思是把程序里的数据变成 JSON 等外部格式,或再读回来)来保证保存成文字时是 open、closed 这种 snake_case(小写加下划线)的格式。这样数据库、接口、测试、其他程序看到的都是稳定一致的名字。

函数细节1
tests::thread_spawn_edge_status_serializes_as_snake_case20–41 ↗
fn thread_spawn_edge_status_serializes_as_snake_case()

作用:这个测试确认 ThreadSpawnEdgeStatus 写成 JSON 时会变成 "open" 和 "closed",再从 JSON 读回来时也能还原成正确的状态。它防止以后有人改代码时,不小心把外部格式改坏。

数据流:进去的是两个状态值 Open 和 Closed,以及两段 JSON 字符串 "open"、"closed"。测试先把状态值转成 JSON 字符串,检查结果是不是预期的文字;再把这些文字读回状态值,检查是不是原来的枚举值。出来的结果不是业务数据,而是测试通过或失败;如果格式不对,测试会直接报错。

调用关系:它位于这个类型定义旁边,专门守住这个类型的对外格式。测试过程中它调用 assert_eq! 来比较实际结果和期望结果;平时系统运行不会调用它,但开发者运行测试时会用它来确认序列化规则没有被破坏。

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

core/src/state/mod.rs源码 ↗
orchestrationcross-cutting

这个文件本身不写具体业务动作,更像一本书的目录页。系统运行时会有很多“当前情况”要记住,比如一次会话现在是什么状态、当前这一轮任务在做什么、有没有等待用户批准的请求、后台服务有哪些、额外上下文放在哪里等。如果每个地方都直接去找各自的小文件,代码会很散,也容易引用错。这里先声明几个子模块,比如 session、turn、service,再把里面重要的类型重新导出出去。所谓“导出”,可以理解成把常用工具放到前台货架上,别人不用钻进仓库找。它还用了 pub(crate),意思是这些东西只给当前这个代码包内部使用,不对外部公开,这样能控制边界,避免外部代码依赖太多内部细节。

core/src/unified_exec/process_state.rs源码 ↗
data_modelprocess execution state tracking

当系统启动或控制一个外部进程时,不能只知道“它还在不在跑”,还要知道它是正常退出、带着某个退出码退出,还是因为错误失败了。这个文件里的 ProcessState 就像一张状态卡片,卡片上有三栏:是否已经结束、退出码、失败说明。它还提供两个小方法来生成新的状态:exited 表示进程已经结束,并填上退出码;failed 表示进程失败了,并填上失败原因。这里的做法不是直接改原来的卡片,而是根据旧卡片复制出一张新卡片,再改需要变化的部分。这样上层代码在传递状态时更清楚,也更不容易把旧状态意外弄乱。

函数细节2
ProcessState::exited9–15 ↗
fn exited(&self, exit_code: Option<i32>) -> Self

作用:这个函数用来把一个进程状态标记成“已经退出”。调用者可以把退出码传进来,比如 0 通常表示成功,非 0 常常表示出错;如果没有退出码,也可以传空。

数据流:进去的是当前这份 ProcessState,以及一个可有可无的退出码 → 它新建一份状态,把 has_exited 设为 true,把 exit_code 设成传入的值,同时保留原来的 failure_message → 出来的是一份新的 ProcessState,表示这个进程已经结束了。

调用关系:它是 ProcessState 这个状态卡片上的便捷按钮。外部流程在发现进程结束时会用它生成结束后的状态;它不把工作交给别的函数,只负责把“已退出”这个事实写进新状态里。

ProcessState::failed17–23 ↗
fn failed(&self, message: String) -> Self

作用:这个函数用来把一个进程状态标记成“失败了”。它会保存一段给人看的失败说明,方便后面报错、记录日志或展示原因。

数据流:进去的是当前这份 ProcessState,以及一段失败信息文字 → 它新建一份状态,把 has_exited 设为 true,保留原来的 exit_code,并把 failure_message 换成这段新信息 → 出来的是一份新的 ProcessState,表示这个进程已经以失败状态结束。

调用关系:它也是 ProcessState 的便捷按钮。外部流程在发现进程运行出错、启动失败或异常结束时会用它;它不调用其他函数,只把“失败原因”和“已经结束”这两件事合并成一个新的状态。

state/src/model/graph.rs源码 ↗
data_modelcross-cutting

这个文件目前只放了一个枚举类型。枚举可以理解成“只能从固定几个选项里选一个”的类型。这里的 DirectionalThreadSpawnEdgeStatus 用来标记一条有方向的“线程生成边”的状态:Open 表示这条关系还开着,Closed 表示它已经关上。你可以把它想成一扇门,门开着时还有后续活动或连接,门关上时这段关系已经结束。文件还给这个枚举加了几个自动能力:可以调试打印,可以复制,可以比较是否相等,还可以和 snake_case 风格的文字互相转换,比如 open、closed。这样做的好处是,程序内部用安全的固定选项,和外部保存、读取、展示时用的文字也能保持一致。

state/src/model/memories.rs源码 ↗
data_modelcross-cutting

这份文件像一张任务单模板集合,专门描述系统怎么保存和认领“记忆”相关的后台工作。这里的“stage-1”可以理解成第一步:从某个对话线程里提取原始记忆;“phase-2”可以理解成第二步:把已经提取出的内容再统一整理合并。Stage1Output 记录一次提取完成后的结果,比如它来自哪个线程、源文件什么时候更新、提取出的记忆文本、摘要、工作目录和生成时间。Stage1JobClaimOutcome 和 Phase2JobClaimOutcome 则描述“我想接这个任务”时可能发生的结果:接到了、已经是最新不用做、别人正在做、暂时不能重试、重试次数用完等。这样做的好处是,调度任务的代码不用靠猜字符串或临时约定来判断状态,而是使用这些固定类型,减少误判和重复处理。整个文件没有函数,重点是把后台记忆流程中来回传递的信息说清楚。

state/src/model/mod.rs源码 ↗
data_modelcross-cutting

这个文件本身不写具体业务步骤,也不存数据。它的作用更像一本书的目录页:先声明这里有哪些子模块,比如任务、日志、线程元数据、回填状态等;然后把常用的数据类型重新导出,让项目其他地方可以用统一入口访问它们。比如外部代码需要表示一个代理任务、日志记录、线程目标,或者查询线程列表时,不必关心这些类型分别定义在哪个源文件里。这里还区分了“公开给外部用”的类型和“只给当前 crate 内部用”的类型。crate 可以理解成 Rust 里的一个代码包。这样既方便使用,又避免把内部数据库行结构等细节随便暴露出去。没有这个文件,模型层会更零散,调用方要到处找类型,代码也更容易和内部实现绑死。

thread-store/src/types.rs源码 ↗
data_modelcross-cutting

这里的“线程”可以理解成一次持续的对话或任务记录。这个文件不太做具体存取工作,而是把所有入口和出口的数据形状先定好:比如创建线程需要线程编号、来源、工作目录、模型信息;读取线程会返回标题、预览、时间、Git 信息、历史记录;分页列表会带上下一页游标。它还定义了“补丁”这种更新方式:只传来的字段才改,没传的字段保持原样;有些字段还能明确传空值表示“清掉它”。这点很重要,因为“不想改”和“想删掉”在保存元数据时完全不同。文件里的少量函数主要服务于这种补丁合并和 JSON 序列化。整体上,它是线程存储层的共同语言,数据库、文件存储、API 层都靠它对齐。

函数细节9
optional_option::serialize26–35 ↗
fn serialize(value: &Option<Option<T>>, serializer: S) -> Result<S::Ok, S::Error>

作用:这个函数专门把“双层可选值”写成 JSON。它用来区分字段没出现、字段出现但值是空、字段出现且有具体值这三种情况。

数据流:进去的是一个 Option<Option<T>>:外层 None 表示这个字段没提供,Some(None) 表示要清空,Some(Some(值)) 表示要设置新值。函数把外层 None 写成 JSON 的空字段效果,把里面的值按普通规则写出去。出来的是序列化器能接受的 JSON 表达。

调用关系:它被 serde 序列化机制自动调用,用在 ThreadMetadataPatch 和 GitInfoPatch 里那些需要“可清空”的字段上。它只把具体写 JSON 的活交给外部的 serialize_none 或值自己的 serialize。

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

optional_option::deserialize37–43 ↗
fn deserialize(deserializer: D) -> Result<Option<Option<T>>, D::Error>

作用:这个函数专门从 JSON 读回“双层可选值”。它让系统能看懂:字段出现了但值是 null,就表示用户真的想清掉这个字段。

数据流:进去的是 JSON 反序列化器。它先按 Option<T> 读字段:普通值会变成 Some(值),null 会变成 None;然后再包一层 Some,表示“这个字段确实出现过”。出来的是 Option<Option<T>>,供补丁逻辑判断要不要改。

调用关系:它同样由 serde 自动调用,和 optional_option::serialize 配套使用。没有它,JSON 里的 null 很容易和“字段根本没传”混在一起,补丁更新就会出错。

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

GitInfoPatch::merge464–474 ↗
fn merge(&mut self, next: Self)

作用:这个函数把新的 Git 信息补丁合并到当前补丁里。它只覆盖新补丁里明确出现的字段,没出现的字段不动。

数据流:进去的是当前 GitInfoPatch 和另一个 GitInfoPatch。函数逐个看 sha、branch、origin_url:如果新补丁的某项是 Some,就把当前项替换掉;如果是 None,就保持当前项不变。出来没有新对象,而是直接改动当前补丁。

调用关系:它通常被 ThreadMetadataPatch::merge 在合并嵌套 Git 信息时调用。这样外层元数据补丁和里面的 Git 补丁都遵守同一套规则:没说就不改,说了就覆盖,哪怕说的是清空。

ThreadMetadataPatch::merge561–630 ↗
fn merge(&mut self, next: Self)

作用:这个函数把两份线程元数据补丁合成一份。它解决的是多处更新信息叠加时,哪些字段该保留、哪些字段该覆盖的问题。

数据流:进去的是当前 ThreadMetadataPatch 和下一份 ThreadMetadataPatch。函数逐项检查:新补丁里出现了 name、preview、model、时间、权限等字段,就覆盖当前字段;没出现就保持旧补丁里的设置。遇到 git_info 这种里面还有小字段的补丁时,不是整块粗暴替换,而是调用 GitInfoPatch::merge 继续细合并。结果是当前补丁被更新成“旧补丁加新补丁”的效果。

调用关系:它位于元数据更新流程的核心位置。调用方可以不断收集零散的观察结果或用户修改,再用它合并成最终要写入存储的补丁;遇到 Git 信息时,它把细节交给 GitInfoPatch::merge。

ThreadMetadataPatch::is_empty632–655 ↗
fn is_empty(&self) -> bool

作用:这个函数判断一份线程元数据补丁是不是完全没有内容。调用方可以用它避免做一次没有意义的更新。

数据流:进去的是一份 ThreadMetadataPatch。函数检查每个字段是不是 None,也就是没有提供更新。只要所有字段都没提供,就返回 true;任何一个字段有内容,就返回 false。它不改动任何数据。

调用关系:它常用于接收补丁后先做快速判断:如果为空,就不用继续写数据库或文件。测试 tests::thread_metadata_patch_accepts_missing_fields 会验证空 JSON 读出来后确实被认为是空补丁。

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

tests::thread_metadata_patch_round_trips_optional_clears691–715 ↗
fn thread_metadata_patch_round_trips_optional_clears()

作用:这个测试确认“清空字段”的意思在写成 JSON、再读回来之后不会丢。也就是 Some(None) 必须稳定地表示“请把这个字段清掉”。

数据流:进去的是测试里手写的一份 ThreadMetadataPatch,其中 name、thread_source、agent_nickname、agent_role、agent_path 都设置成要清空。测试把它转成 JSON,检查这些字段都是 null;再从 JSON 读回来,检查仍然是 Some(None)。结果是验证通过或测试失败。

调用关系:它验证 optional_option::serialize 和 optional_option::deserialize 在 ThreadMetadataPatch 上是否配合正确。它不参与正式运行,只在测试阶段保护补丁格式不被改坏。

调用图:外部调用 4 个(default, assert_eq!, from_value, to_value)。

tests::git_info_patch_round_trips_optional_clears718–747 ↗
fn git_info_patch_round_trips_optional_clears()

作用:这个测试确认 Git 信息里的字段也能正确表达“保留不动、设置新值、清空旧值”三种状态。

数据流:进去的是一份带 git_info 的 ThreadMetadataPatch:sha 没提供,branch 设置成 main,origin_url 要清空。测试把它转成 JSON,确认 sha 不出现、branch 是字符串、origin_url 是 null;再读回来,确认三种状态都还在。结果是验证通过或测试失败。

调用关系:它重点保护 GitInfoPatch 上的 optional_option 规则。这样 ThreadMetadataPatch::merge 以后合并 Git 补丁时,才能可靠地区分没改和清空。

调用图:外部调用 4 个(default, assert_eq!, from_value, to_value)。

tests::thread_metadata_patch_accepts_missing_fields750–755 ↗
fn thread_metadata_patch_accepts_missing_fields()

作用:这个测试确认老版本或空的 JSON 补丁也能被接受。这样升级后不会因为缺少新字段就读不进旧数据。

数据流:进去的是一个空 JSON 对象。测试把它读成 ThreadMetadataPatch,然后调用 is_empty 检查它确实没有任何更新内容。结果是验证通过或测试失败。

调用关系:它直接覆盖 ThreadMetadataPatch::is_empty 的一个重要使用场景,也保护 serde 的默认值设置。它确保系统面对缺字段的数据时更兼容。

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

tests::thread_metadata_patch_merge_uses_presence_semantics758–793 ↗
fn thread_metadata_patch_merge_uses_presence_semantics()

作用:这个测试确认补丁合并遵守“字段出现才覆盖”的规则。它尤其检查清空请求和 Git 子补丁合并不会误伤其他字段。

数据流:进去的是一份已有补丁和一份新补丁:新补丁清空 name,不提供 preview,新增 title,并修改 Git 分支、清空 origin_url。测试执行 merge 后,检查 name 被清空,preview 保持旧值,title 被加入,Git 的 sha 保留、branch 更新、origin_url 清空。结果是验证通过或测试失败。

调用关系:它主要验证 ThreadMetadataPatch::merge,并间接验证 GitInfoPatch::merge。这个测试保证以后改合并逻辑时,不会把“没传字段”误当成“删除字段”。

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

面向 UI 的应用类型

这些文件定义终端 UI 层使用的小型但共享的应用事件和启动错误约定。

tui/src/app_event.rs源码 ↗
data_modelcross-cutting

可以把这个文件理解成应用里的“工单分类表”。界面上发生的很多事,比如打开模型选择器、开始文件搜索、安装插件、刷新用量、退出程序,都不会让小部件自己硬改全局状态,而是包装成 AppEvent 交给最外层的 App 去办。这样做的好处是:各个界面零件彼此不乱伸手,流程更清楚,也更不容易在退出、保存配置、后台请求还没结束时出岔子。文件里还定义了一些配套的小类型,比如 ExitMode 用来区分“先正常关机再退出”和“立刻退出”,PluginLocation 用来说明插件来自本地市场还是远程市场,RateLimitRefreshOrigin 用来记住限额刷新是启动时自动查的,还是用户点了 /status 后查的。整体上,它不是执行具体业务的地方,而是规定“有哪些消息可以在应用内部流动,以及每条消息要带哪些信息”。

函数细节1
PluginLocation::into_request_params100–105 ↗
fn into_request_params(self) -> (Option<AbsolutePathBuf>, Option<String>)

作用:这个函数把“插件来源”转换成发请求时需要的两个参数。插件要么来自本地市场路径,要么来自远程市场名字,请求接口需要把这两种情况拆开传。

数据流:进去的是一个 PluginLocation:如果它表示本地来源,里面带一个本地市场路径;如果表示远程来源,里面带一个市场名称。函数检查是哪一种,然后输出一对值:本地路径位置填 Some、远程名字位置填 None,或者反过来。它不改别的状态,只是把更好读的内部表示,换成更适合请求接口使用的参数格式。

调用关系:安装插件时,fetch_plugin_install 会调用它,把用户选中的插件来源整理成请求参数。也就是说,界面和应用内部可以继续用“本地/远程”这种直观说法,而真正发安装请求前,再由这个函数翻译成服务端接口要的形式。

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

tui/src/startup_error.rs源码 ↗
data_modelstartup error handling

程序启动时需要打开一个本地状态数据库。SQLite 是一种把数据库存在本地文件里的小型数据库。如果这个文件坏了、路径被普通文件挡住了,或者初始化失败,程序不能只报一句模糊的“启动失败”。这个文件里的 LocalStateDbStartupError 就像一张故障单:上面写着出问题的数据库路径,也写着详细原因。它还提供几个简单的取值方法,让别的代码可以安全地拿到路径和错误详情。比较重要的一点是,state_db_path 其实只是 database_path 的另一个名字,方便不同语境下的代码用更顺口的叫法,但指向的是同一个数据库文件。

函数细节4
LocalStateDbStartupError::new15–20 ↗
fn new(database_path: PathBuf, detail: String) -> Self

作用:创建一张“本地数据库启动失败”的故障单。调用者把出问题的数据库路径和具体原因交给它,它就封装成一个统一的错误对象。

数据流:进去的是一个数据库文件路径和一段错误说明文字;函数把它们放进 LocalStateDbStartupError 这个结构里;出来的是一个新的错误对象,没有额外修改别的东西。

调用关系:当备份或恢复相关流程需要模拟、记录数据库启动失败时,会用它先造出这个错误对象。后面其他函数再从这个对象里取出路径或详情,决定该提示用户、备份哪个文件,或者判断能不能自动恢复。

调用图:被 2 处调用(backup_backs_up_only_failed_database_file, backup_replaces_blocking_sqlite_home_file)。

LocalStateDbStartupError::database_path22–24 ↗
fn database_path(&self) -> &Path

作用:取出这次启动失败所对应的数据库文件路径。别人需要知道“到底哪个文件出问题了”时会用它。

数据流:进去的是这个错误对象本身;函数读取里面保存的 PathBuf 路径,并把它转换成只读的 Path 引用;出来的是数据库路径,原对象不会被改变。

调用关系:备份新开始所需文件、判断 SQLite 目录位置是否被普通文件挡住等流程会调用它来定位问题文件。state_db_path 也会调用它,因为 state_db_path 只是换了个名字来表达同一个路径。

调用图:被 3 处调用(backup_files_for_fresh_start, sqlite_home_is_blocking_file, state_db_path);外部调用 1 个(as_path)。

LocalStateDbStartupError::state_db_path26–28 ↗
fn state_db_path(&self) -> &Path

作用:用“状态数据库路径”这个名字取出同一个数据库文件路径。它主要是为了让调用代码读起来更符合业务语境。

数据流:进去的是这个错误对象;它不直接碰内部字段,而是转头调用 database_path;出来的是同一个数据库路径引用,不会改动任何数据。

调用关系:它处在一个很薄的转接位置:外部代码如果关心的是“状态数据库”,就可以叫它;它再把实际取路径的工作交给 database_path,避免重复写同样逻辑。

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

LocalStateDbStartupError::detail30–32 ↗
fn detail(&self) -> &str

作用:取出启动失败的具体原因文字。比如用户界面或恢复判断逻辑需要知道失败细节时,会用它。

数据流:进去的是这个错误对象;函数读取里面保存的 detail 字符串,并返回一个只读的字符串片段;出来的是错误详情文字,原对象不会被改变。

调用关系:判断这个错误是否能自动备份并恢复的流程会读取这段详情。也就是说,它帮助后续逻辑从“失败了”进一步判断“这种失败能不能安全处理”。

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