app-server 协议 schema 与传输契约
这一阶段像系统的“通话说明书”,属于幕后支撑,不直接干业务活,却规定客户端、前端、服务器该怎么说话。JSON-RPC 文件先定好请求、通知、回复、错误的统一信封;common、v1、v2 各文件把账号、线程、对话、文件、权限、插件、远程控制等消息做成固定表格;辅助和转换文件负责处理空值、旧版兼容和审批格式;导出工具把这些规则打印成 TypeScript 和 schema;传输与错误文件保证消息发出去、错回来时也按同一套规矩。
协议门面与传输基础
这些文件建立 crate 级入口点,以及所有版本化 schema 都基于的共享 JSON-RPC 和协议脚手架。
app-server-protocol/src/jsonrpc_lite.rs源码 ↗
这个文件像是在给通信双方定一张“表格模板”:发请求时要带什么字段,回结果时要长什么样,出错时又该怎么写。JSON-RPC 是一种常见的用 JSON 传命令和结果的协议;这里说是“lite”,因为代码注释说明它并不严格要求 JSON-RPC 2.0 里的 “jsonrpc”: “2.0” 字段。文件里最重要的是几种数据形状:JSONRPCRequest 表示需要答复的请求,JSONRPCNotification 表示不用答复的通知,JSONRPCResponse 表示成功结果,JSONRPCError 表示失败结果。RequestId 是请求编号,可以是字符串也可以是整数,就像取号机的号码牌,用来把“这次回复”对应回“之前哪次请求”。这些类型都支持序列化和反序列化,也就是能在程序对象和 JSON 文本之间来回转换;同时还带有 JSON Schema 和 TypeScript 类型生成能力,方便别的语言或前端也按同一套格式使用。
RequestId::fmt24–29 ↗
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
作用:这个函数把请求编号变成普通文字,方便打印日志、拼接错误信息,或者显示给人看。因为请求编号可能是字符串,也可能是整数,所以它会按实际类型用合适方式写出来。
数据流:进去的是一个 RequestId 和一个用于写文字的输出位置。函数先看这个编号到底是 String 还是 Integer;如果是字符串,就原样写进去;如果是整数,就把数字转成文字再写进去。出来的是一次格式化是否成功的结果,它不会改变请求编号本身。
调用关系:它属于 RequestId 的显示功能,在有人用格式化、打印或转字符串方式展示请求编号时会被自动调用。它内部只把具体写入动作交给标准的写字符串或写格式化内容能力,不参与网络传输,也不决定消息含义。
app-server-protocol/src/lib.rs源码 ↗
这个文件本身不写具体业务步骤,更像一家店的前台。真正的东西放在 experimental_api、export、jsonrpc_lite、protocol、schema_fixtures 这些内部模块里;这里负责把它们挂进来,并把常用内容重新导出。重新导出的意思是:外部使用者可以直接从这个库拿到 ApplyPatchApprovalParams、InitializeParams、generate_ts 这类类型或函数,不必知道它们原来藏在哪个子文件里。这样做很重要,因为协议库通常会被服务器、客户端、代码生成工具、测试工具一起使用。如果没有这个统一出口,大家就会依赖内部路径,文件一改名就容易大面积坏掉。它还特意导出了生成 JSON Schema、TypeScript 类型和 schema fixture 的工具,说明这个库不只定义通信格式,也帮助把这些格式变成文档、类型文件和测试样本。
app-server-protocol/src/protocol/mod.rs源码 ↗
这个文件解决的是“协议代码怎么组织、怎么暴露”的问题。协议可以理解成前后端或不同服务之间约好的说话格式:消息长什么样、事件怎么表示、历史记录怎么传等。如果没有这个目录文件,其他代码就很难按统一入口找到这些协议零件。这里把 common、event_mapping、item_builders、thread_history、v1、v2 等模块公开出来,表示外部可以使用它们;同时把 mappers 和 serde_helpers 设为内部模块,说明它们更像后台工具,不直接给外面调用。一个直观类比是:它是协议文件夹门口的索引牌,哪些房间对外开放、哪些房间只给工作人员用,都在这里标清楚。
app-server-protocol/src/protocol/serde_helpers.rs源码 ↗
程序在和外部交换数据时,常要把 Rust 里的数据结构和 JSON 之类的文本格式互相转换。这个过程叫序列化和反序列化,简单说就是“打包发出去”和“拆包读回来”。这个文件专门处理几个普通转换容易搞混的细节。第一个细节是路径:有些输入可能给了一个空字符串路径,这通常不是真正的文件路径,所以这里把它当成“没有路径”。第二个细节是双层 Option,也就是既要知道“这个字段根本没给”,又要知道“这个字段明确给了 null”,还要能保存真正的值。它像一张表单:没填、填了空、填了内容,三者含义不同。这里没有复杂业务,只是把这些规则集中放好,避免各处重复写,也避免协议数据被误解。
deserialize_empty_path_as_none8–14 ↗
fn deserialize_empty_path_as_none(deserializer: D) -> Result<Option<PathBuf>, D::Error>
作用:这个函数在读取外部传来的路径时,把空路径当成“没有路径”。这样后面的代码就不用把空字符串误认为一个真实文件位置。
数据流:进去的是一个反序列化器,也就是负责从外部数据里读字段的工具。它先按“可有可无的路径”读出一个 Option<PathBuf>;如果读到的是空路径,就过滤掉,变成 None;如果是正常路径,就保留下来。出来的是一个可能有路径、也可能没有路径的结果。
调用关系:它处在协议数据进入程序的入口附近,通常会被 serde 在读取某个路径字段时自动调用。它把真正读取数据的活交给外部的 deserialize,然后自己只补上一条规则:空路径不算有效路径。
调用图:外部调用 1 个(deserialize)。
deserialize_double_option16–22 ↗
fn deserialize_double_option(deserializer: D) -> Result<Option<Option<T>>, D::Error>
作用:这个函数用来读取双层 Option,让程序能分清“字段没出现”“字段出现但值是空”和“字段有具体值”。这在更新设置、打补丁这类场景很重要,因为三种情况可能代表完全不同的意思。
数据流:进去的是一个反序列化器,以及目标值的类型 T。它不自己手写解析规则,而是交给 serde_with 提供的 double_option 读取逻辑。出来的是 Option<Option<T>>:外层表示字段有没有出现,内层表示出现后是不是有值。
调用关系:它是协议字段反序列化时的适配层。serde 在需要读取这种特殊字段时会用到它;它再把具体解析工作交给 serde_with::rust::double_option::deserialize,确保项目里统一使用同一套含义。
调用图:外部调用 1 个(deserialize)。
serialize_double_option24–33 ↗
fn serialize_double_option(
value: &Option<Option<T>>,
serializer: S,
) -> Result<S::Ok, S::Error>
作用:这个函数用来把双层 Option 写回外部数据,同时保留“没出现、null、有值”之间的区别。没有它,发送出去的数据可能会把这些含义混在一起。
数据流:进去的是一个 Option<Option<T>> 和一个序列化器,也就是负责把数据写成外部格式的工具。它把这个双层可选值交给 serde_with 的 double_option 写出逻辑。出来的是序列化后的结果;如果写出失败,也会返回相应错误。
调用关系:它在协议数据离开程序、被打包发送或保存时使用。serde 会在写某些特殊字段时调用它;它把实际写出格式的工作交给 serde_with::rust::double_option::serialize,自己负责把项目里的字段含义接到这个通用工具上。
调用图:外部调用 1 个(serialize)。
app-server-protocol/src/protocol/common.rs源码 ↗
这个文件像一份通信菜单和字典。它先定义登录认证方式,比如 API Key、ChatGPT 登录、Bedrock 密钥等;再用宏(一种帮程序自动展开重复代码的工具)批量生成客户端请求、服务器请求、响应和通知的类型。每条消息都有固定的 method 名字、参数类型、返回类型,还能导出 TypeScript 类型和 JSON Schema(给别的语言或工具看的数据格式说明)。它还给一些请求标上“实验性”,方便系统决定是否允许使用。另一个重要点是 serialization_scope,也就是给某些请求分组排队:比如同一个线程的操作不要互相踩踏,配置写入也不要同时乱改。文件底部有大量测试,专门确认消息序列化后的 JSON 长相没有变,避免协议升级时悄悄破坏兼容性。
AuthMode::has_chatgpt_account53–58 ↗
fn has_chatgpt_account(self) -> bool
作用:判断某种登录方式背后是不是一个真实的 ChatGPT 人类账号。别人需要知道账号能力或展示账号状态时会用它。
数据流:进去的是一个 AuthMode 值 → 函数按枚举种类做简单判断 → 出来一个 true 或 false;它不改任何外部数据。
调用关系:这是 AuthMode 这个认证方式枚举上的小工具方法,供账号相关流程直接查询,不把工作再交给别的函数。
AuthMode::uses_codex_backend61–69 ↗
fn uses_codex_backend(self) -> bool
作用:判断某种登录方式是不是走 Codex 后端服务,而不是直接连模型 API。这样调用方能知道后续请求要走哪条后端路线。
数据流:进去的是一个认证方式 → 函数把 ChatGPT、Agent Identity、个人访问令牌等归为走 Codex 后端,把 API Key、Bedrock API Key 归为不走 → 返回布尔值。
调用关系:它是认证模式的分类器,通常被账号、模型或服务选择逻辑读取,用来决定后续网络请求该走 Codex 服务还是直连模型供应商。
ServerRequest::try_from1417–1419 ↗
fn try_from(value: JSONRPCRequest) -> Result<Self, Self::Error>
作用:把一条通用的 JSON-RPC 请求转换成这里定义的强类型 ServerRequest。这样后面的代码不用再手动猜 method 和 params 是什么。
数据流:进去的是 JSONRPCRequest → 先把它转成 serde_json 的通用 JSON 值,再按 ServerRequest 的规则反序列化 → 成功得到具体的服务器请求枚举,失败得到 JSON 解析错误。
调用关系:它接在底层 JSON-RPC 收到消息之后,把“普通 JSON 包裹”变成“程序认识的具体请求”。它把实际解析交给 serde_json::to_value 和 serde_json::from_value。
调用图:外部调用 2 个(from_value, to_value)。
tests::absolute_path_string1692–1695 ↗
fn absolute_path_string(path: &str) -> String
作用:测试里用来快速造一个看起来像绝对路径的字符串,避免每个测试都重复写路径处理代码。
数据流:进去的是一段路径文本 → 先补成以斜杠开头的形式,再交给测试路径工具规范化 → 返回可比较的路径字符串。
调用关系:它只服务于本文件的测试,常被检查 JSON 输出里 path 字段的测试调用,底层依赖 test_path_buf 和格式化字符串。
调用图:外部调用 2 个(test_path_buf, format!)。
tests::absolute_path1697–1700 ↗
fn absolute_path(path: &str) -> AbsolutePathBuf
作用:测试里用来生成 AbsolutePathBuf,也就是“确认是绝对路径”的路径对象。
数据流:进去的是路径文本 → 补成绝对路径样子,再用测试工具变成绝对路径对象 → 返回给请求参数使用。
调用关系:它是测试构造请求参数的小帮手,很多文件系统、线程 cwd、配置相关测试都会用它。
调用图:外部调用 2 个(test_path_buf, format!)。
tests::request_id1702–1705 ↗
fn request_id() -> RequestId
作用:测试里统一生成一个请求编号,省得每个测试重复写同样的 RequestId。
数据流:没有外部输入 → 固定使用数字 1 构造 RequestId::Integer → 返回这个请求 ID。
调用关系:它被序列化范围相关测试反复调用,用来填充 ClientRequest 的 id 字段。
调用图:外部调用 1 个(Integer)。
tests::client_request_serialization_scope_covers_keyed_families1708–1997 ↗
fn client_request_serialization_scope_covers_keyed_families()
作用:确认那些带“同一资源钥匙”的请求会被分到正确的排队范围里,比如同一个线程、同一个配置、同一个进程。
数据流:测试构造很多 ClientRequest → 调用 serialization_scope 看系统给它们分到哪个范围 → 用断言确认结果符合预期;失败表示请求可能会错误并发执行。
调用关系:它覆盖 serialization_scope 这套防踩踏机制的主要分支,调用 request_id、absolute_path 等测试帮手来搭请求。
调用图:外部调用 8 个(default, from, new, absolute_path, request_id, assert_eq!, json!, vec!)。
tests::client_request_serialization_scope_covers_unkeyed_representatives2000–2129 ↗
fn client_request_serialization_scope_covers_unkeyed_representatives()
作用:确认那些不该排队、或只按全局资源排队的请求,serialization_scope 返回正确结果。
数据流:进去没有业务输入 → 测试构造初始化、文件读取、列表读取、远程控制等请求 → 检查有些返回 None,有些返回全局读写范围。
调用关系:它补充上一条测试,专门检查“没有具体线程或进程 ID”的代表性请求,避免系统把可并发的操作误锁住。
调用图:外部调用 7 个(absolute_path, request_id, default, default, default, assert_eq!, vec!)。
tests::serialize_get_conversation_summary2132–2150 ↗
fn serialize_get_conversation_summary() -> Result<()>
作用:确认旧版获取会话摘要请求转成 JSON 时字段名和结构没变。
数据流:构造一个带 conversationId 的 ClientRequest → 序列化成 JSON → 和手写的期望 JSON 比较。
调用关系:这是协议兼容性测试,依赖 ThreadId::from_string 构造会话 ID,保护老客户端仍能按原格式通信。
调用图:调用 1 个内部函数(from_string);外部调用 2 个(Integer, assert_eq!)。
tests::serialize_initialize_with_opt_out_notification_methods2153–2196 ↗
fn serialize_initialize_with_opt_out_notification_methods() -> Result<()>
作用:确认初始化请求能正确带上客户端能力,以及想屏蔽哪些通知方法。
数据流:构造 Initialize 请求,里面包含客户端信息和能力开关 → 序列化成 JSON → 检查 method、id、capabilities、optOutNotificationMethods 都正确。
调用关系:它测试客户端启动握手消息的输出格式,确保新增能力字段不会破坏初始化协议。
调用图:外部调用 3 个(Integer, assert_eq!, vec!)。
tests::deserialize_initialize_with_opt_out_notification_methods2199–2242 ↗
fn deserialize_initialize_with_opt_out_notification_methods() -> Result<()>
作用:确认服务器收到初始化 JSON 后,能还原成正确的 ClientRequest。
数据流:进去是一段手写 JSON → serde_json 反序列化成 ClientRequest → 和预期的 Rust 数据结构比较。
调用关系:它和序列化初始化测试成对存在,一个看“写出去”,一个看“读进来”,共同保护握手协议。
调用图:外部调用 3 个(assert_eq!, json!, from_value)。
tests::conversation_id_serializes_as_plain_string2245–2253 ↗
fn conversation_id_serializes_as_plain_string() -> Result<()>
作用:确认 ThreadId 这种会话编号写成 JSON 时就是普通字符串,而不是复杂对象。
数据流:构造一个 ThreadId → 转成 JSON → 检查结果等于纯字符串。
调用关系:它保护底层 ID 格式,很多请求和通知都依赖这个简单字符串形式。
调用图:调用 1 个内部函数(from_string);外部调用 1 个(assert_eq!)。
tests::conversation_id_deserializes_from_plain_string2256–2264 ↗
fn conversation_id_deserializes_from_plain_string() -> Result<()>
作用:确认普通字符串能被读回 ThreadId。
数据流:进去是 JSON 字符串 → 反序列化成 ThreadId → 和用同一字符串构造出的 ID 比较。
调用关系:它和 ThreadId 序列化测试配套,保证客户端发来的字符串 ID 能被服务器正确理解。
调用图:外部调用 3 个(assert_eq!, json!, from_value)。
tests::serialize_client_notification2267–2277 ↗
fn serialize_client_notification() -> Result<()>
作用:确认客户端通知 Initialized 发出去时只有 method,没有多余 params。
数据流:构造 ClientNotification::Initialized → 序列化成 JSON → 检查 JSON 只包含 initialized 方法名。
调用关系:它验证 client_notification_definitions 生成的通知格式,保护简单通知不要被加上空参数字段。
调用图:外部调用 1 个(assert_eq!)。
tests::serialize_server_request2280–2324 ↗
fn serialize_server_request() -> Result<()>
作用:确认服务器向客户端请求执行命令审批时,JSON 格式正确。
数据流:构造 ExecCommandApproval 参数和 ServerRequest → 序列化成 JSON → 检查命令、目录、原因、解析命令等字段;还检查 payload 能用 request_id 变回请求。
调用关系:它测试 server_request_definitions 生成的 ServerRequest 和 ServerRequestPayload,确保审批请求能被客户端识别。
调用图:调用 1 个内部函数(from_string);外部调用 5 个(from, ExecCommandApproval, Integer, assert_eq!, vec!)。
tests::serialize_chatgpt_auth_tokens_refresh_request2327–2347 ↗
fn serialize_chatgpt_auth_tokens_refresh_request() -> Result<()>
作用:确认服务器要求客户端刷新 ChatGPT token 时,消息格式正确。
数据流:构造 ChatgptAuthTokensRefresh 请求 → 序列化成 JSON → 检查 method、id、刷新原因和上一个账号 ID。
调用关系:它保护服务器到客户端的账号刷新协议,尤其用于外部宿主提供 ChatGPT token 的场景。
调用图:外部调用 2 个(Integer, assert_eq!)。
tests::serialize_attestation_generate_request2350–2369 ↗
fn serialize_attestation_generate_request() -> Result<()>
作用:确认服务器请求客户端生成 attestation(可理解为证明当前环境可信的凭据)时格式正确。
数据流:构造空参数的 AttestationGenerate 请求 → 序列化检查 JSON → 再确认 payload 加上 id 后能生成同一个请求。
调用关系:它测试服务端请求生成和 payload 包装逻辑,保证按需证明流程能正常发起。
调用图:外部调用 3 个(AttestationGenerate, Integer, assert_eq!)。
tests::serialize_server_response2372–2393 ↗
fn serialize_server_response() -> Result<()>
作用:确认客户端回复服务器审批请求时,响应 JSON 带着正确 method、id 和决定结果。
数据流:构造 ServerResponse::CommandExecutionRequestApproval → 检查 id 和 method 方法返回值 → 序列化后和期望 JSON 比较。
调用关系:它验证 ServerResponse 的基础辅助方法和 JSON 格式,是服务器请求-客户端响应这条链路的一部分。
调用图:外部调用 2 个(Integer, assert_eq!)。
tests::serialize_mcp_server_elicitation_request2396–2450 ↗
fn serialize_mcp_server_elicitation_request() -> Result<()>
作用:确认 MCP 服务器向用户索取输入的请求格式正确。MCP 可以理解为外部工具服务器协议,这里是在让客户端展示一个输入表单。
数据流:先从 JSON 构造一个表单 schema → 组装 McpServerElicitationRequest → 序列化并比较完整 JSON → 还验证 payload 能带 id 生成请求。
调用关系:它覆盖服务器请求客户端交互输入的协议,连接 MCP 工具调用和用户界面审批/填写流程。
调用图:外部调用 5 个(McpServerElicitationRequest, Integer, assert_eq!, json!, from_value)。
tests::serialize_get_account_rate_limits2453–2468 ↗
fn serialize_get_account_rate_limits() -> Result<()>
作用:确认读取账号限额的请求在没有参数时不会输出 params 字段。
数据流:构造 GetAccountRateLimits 请求,params 为 None → 检查 id 和 method → 序列化后比较 JSON。
调用关系:它保护账号限额查询接口的线格式,尤其是“无参数请求”的省略行为。
调用图:外部调用 2 个(Integer, assert_eq!)。
tests::serialize_get_account_token_usage2471–2486 ↗
fn serialize_get_account_token_usage() -> Result<()>
作用:确认读取账号 token 用量的请求格式正确,并且无参数时不写 params。
数据流:构造 GetAccountTokenUsage 请求 → 检查方法名 → 序列化成 JSON 并比对。
调用关系:它和账号限额测试类似,保护账号用量读取接口的外部协议。
调用图:外部调用 2 个(Integer, assert_eq!)。
tests::serialize_client_response2489–2579 ↗
fn serialize_client_response() -> Result<()>
作用:确认服务器返回给客户端的 ThreadStart 响应能按约定写成 JSON。
数据流:构造包含线程信息、模型、目录、沙箱、审批策略等的大响应 → 序列化 → 和完整期望 JSON 比较。
调用关系:它测试 ClientResponse 的 id、method 和序列化输出,保护新线程启动后客户端看到的数据格式。
调用图:外部调用 5 个(new, Integer, absolute_path, assert_eq!, vec!)。
tests::serialize_config_requirements_read2582–2595 ↗
fn serialize_config_requirements_read() -> Result<()>
作用:确认读取配置要求的请求无参数时 JSON 格式正确。
数据流:构造 ConfigRequirementsRead 请求,参数为空 → 序列化 → 检查只有 method 和 id。
调用关系:它保护配置检查相关接口的简单请求格式。
调用图:外部调用 2 个(Integer, assert_eq!)。
tests::serialize_account_login_api_key2598–2617 ↗
fn serialize_account_login_api_key() -> Result<()>
作用:确认用 API Key 登录账号时,请求 JSON 正确包含密钥字段。
数据流:构造 LoginAccount::ApiKey 参数 → 序列化请求 → 检查 type 是 apiKey,apiKey 值在 params 里。
调用关系:它覆盖登录流程的一种分支,确保账号登录接口能区分不同登录方式。
调用图:外部调用 2 个(Integer, assert_eq!)。
tests::serialize_account_login_chatgpt2620–2638 ↗
fn serialize_account_login_chatgpt() -> Result<()>
作用:确认普通 ChatGPT 登录请求在不开启 streamlined 登录时不会多写开关字段。
数据流:构造 Chatgpt 登录参数且开关为 false → 序列化 → 检查 params 里只有 type: chatgpt。
调用关系:它保护 ChatGPT 登录协议的默认简洁格式,避免 false 值造成不必要的线格式变化。
调用图:外部调用 2 个(Integer, assert_eq!)。
tests::serialize_account_login_chatgpt_streamlined2641–2660 ↗
fn serialize_account_login_chatgpt_streamlined() -> Result<()>
作用:确认开启 streamlined ChatGPT 登录时,JSON 会明确带上 codexStreamlinedLogin。
数据流:构造 Chatgpt 登录参数且开关为 true → 序列化 → 检查额外字段存在且为 true。
调用关系:它和普通 ChatGPT 登录测试配套,确保登录体验开关只在需要时出现。
调用图:外部调用 2 个(Integer, assert_eq!)。
tests::serialize_account_login_chatgpt_device_code2663–2679 ↗
fn serialize_account_login_chatgpt_device_code() -> Result<()>
作用:确认使用设备码方式登录 ChatGPT 时,type 字段写对。
数据流:构造 ChatgptDeviceCode 登录请求 → 序列化 → 比对期望 JSON。
调用关系:它覆盖账号登录的设备码分支,确保客户端和服务器对这种登录方式叫法一致。
调用图:外部调用 2 个(Integer, assert_eq!)。
tests::serialize_account_logout2682–2695 ↗
fn serialize_account_logout() -> Result<()>
作用:确认登出请求没有参数时 JSON 格式正确。
数据流:构造 LogoutAccount 请求,params 为 None → 序列化 → 检查只有 method 和 id。
调用关系:它保护账号登出接口的线格式,避免客户端或服务器误以为需要空 params。
调用图:外部调用 2 个(Integer, assert_eq!)。
tests::serialize_account_login_chatgpt_auth_tokens2698–2721 ↗
fn serialize_account_login_chatgpt_auth_tokens() -> Result<()>
作用:确认外部传入 ChatGPT token 的登录方式能正确序列化。
数据流:构造包含 access token、账号 ID、计划类型的登录请求 → 序列化 → 检查字段名是驼峰形式并且值完整。
调用关系:它覆盖 AuthMode 里较特殊的 ChatgptAuthTokens 场景,常用于外部宿主应用托管登录。
调用图:外部调用 2 个(Integer, assert_eq!)。
tests::serialize_get_account2724–2756 ↗
fn serialize_get_account() -> Result<()>
作用:确认读取账号信息时,refreshToken 为 false 会省略,为 true 会写出。
数据流:分别构造 refresh_token=false 和 true 的请求 → 序列化 → 检查一个 params 为空对象,一个包含 refreshToken:true。
调用关系:它保护 GetAccountParams 的默认值省略规则,避免刷新 token 的语义被误传。
调用图:外部调用 2 个(Integer, assert_eq!)。
tests::account_serializes_fields_in_camel_case2759–2804 ↗
fn account_serializes_fields_in_camel_case() -> Result<()>
作用:确认账号对象里的字段写成 JSON 时使用 camelCase,也就是 JavaScript 常见的小驼峰命名。
数据流:构造 API Key、ChatGPT、Amazon Bedrock 两种凭据来源的账号对象 → 分别序列化 → 检查 type、planType、credentialSource 等字段名和值。
调用关系:它测试 v2 账号模型的外部表现,虽然类型不在本文件定义,但这里的协议消息会大量携带它。
调用图:外部调用 1 个(assert_eq!)。
tests::account_defaults_legacy_bedrock_credential_source2807–2817 ↗
fn account_defaults_legacy_bedrock_credential_source() -> Result<()>
作用:确认旧格式的 Bedrock 账号如果没写 credentialSource,会默认当作 AWS 管理的凭据。
数据流:进去是缺少 credentialSource 的 JSON → 反序列化成账号对象 → 检查默认值是 AwsManaged。
调用关系:它保护向后兼容性,让老客户端发来的 Bedrock 账号数据仍能被新代码理解。
调用图:外部调用 1 个(assert_eq!)。
tests::serialize_list_models2820–2838 ↗
fn serialize_list_models() -> Result<()>
作用:确认列出模型的请求会按约定输出分页和 includeHidden 字段。
数据流:构造默认 ModelList 请求 → 序列化 → 检查 limit、cursor、includeHidden 都是 null。
调用关系:它保护模型列表接口的请求格式,让客户端能稳定请求可用模型。
调用图:外部调用 3 个(Integer, default, assert_eq!)。
tests::serialize_model_provider_capabilities_read2841–2855 ↗
fn serialize_model_provider_capabilities_read() -> Result<()>
作用:确认读取模型供应商能力的请求格式正确。
数据流:构造空参数请求 → 序列化 → 检查 method、id 和空 params 对象。
调用关系:它覆盖模型供应商能力接口,帮助客户端知道不同模型后端支持什么功能。
调用图:外部调用 2 个(Integer, assert_eq!)。
tests::serialize_list_collaboration_modes2858–2872 ↗
fn serialize_list_collaboration_modes() -> Result<()>
作用:确认列出协作模式预设的实验接口请求格式正确。
数据流:构造默认 CollaborationModeList 请求 → 序列化 → 检查 params 是空对象。
调用关系:它保护协作模式列表接口,即使该接口标为实验性,也要保持格式明确。
调用图:外部调用 3 个(Integer, default, assert_eq!)。
tests::serialize_list_apps2875–2893 ↗
fn serialize_list_apps() -> Result<()>
作用:确认列出应用的请求会输出分页和 threadId 字段。
数据流:构造默认 AppsList 请求 → 序列化 → 检查 cursor、limit、threadId 都为 null。
调用关系:它测试应用列表 API 的线格式,供客户端展示可用 app。
调用图:外部调用 3 个(Integer, default, assert_eq!)。
tests::serialize_environment_add2896–2916 ↗
fn serialize_environment_add() -> Result<()>
作用:确认添加远程环境的实验请求格式正确。
数据流:构造包含 environmentId 和 execServerUrl 的请求 → 序列化 → 检查字段和值。
调用关系:它覆盖 environment/add 这个实验方法,和实验性标记测试一起保护远程环境功能。
调用图:外部调用 2 个(Integer, assert_eq!)。
tests::serialize_fs_get_metadata2919–2937 ↗
fn serialize_fs_get_metadata() -> Result<()>
作用:确认读取文件元数据请求能正确序列化路径。
数据流:用 absolute_path 构造路径参数 → 序列化 FsGetMetadata 请求 → 比对 path 字符串。
调用关系:它保护远程文件系统接口的路径格式,依赖测试路径帮手生成跨测试环境稳定的路径。
调用图:外部调用 3 个(Integer, absolute_path, assert_eq!)。
tests::serialize_fs_watch2940–2960 ↗
fn serialize_fs_watch() -> Result<()>
作用:确认监听文件变化的请求包含 watchId 和路径。
数据流:构造 FsWatch 请求 → 序列化 → 检查 watchId 和 path 都在 params 中。
调用关系:它测试文件监听协议,和 serialization_scope 里按 watchId 排队的逻辑相互补充。
调用图:外部调用 3 个(Integer, absolute_path, assert_eq!)。
tests::serialize_list_experimental_features2963–2981 ↗
fn serialize_list_experimental_features() -> Result<()>
作用:确认列出实验功能的默认请求格式正确。
数据流:构造默认 ExperimentalFeatureList 请求 → 序列化 → 检查分页字段和 threadId 都是 null。
调用关系:它保护实验功能列表接口,让客户端能稳定展示可开关的实验能力。
调用图:外部调用 3 个(Integer, default, assert_eq!)。
tests::serialize_list_experimental_features_with_thread_id2984–3006 ↗
fn serialize_list_experimental_features_with_thread_id() -> Result<()>
作用:确认列实验功能时,如果带分页和线程 ID,这些字段会正确写入 JSON。
数据流:构造带 cursor、limit、thread_id 的请求 → 序列化 → 比对期望 JSON。
调用关系:它补充默认实验功能列表测试,覆盖带筛选条件的情况。
调用图:外部调用 2 个(Integer, assert_eq!)。
tests::serialize_thread_background_terminals_clean3009–3027 ↗
fn serialize_thread_background_terminals_clean() -> Result<()>
作用:确认清理线程后台终端的实验请求格式正确。
数据流:构造包含 threadId 的请求 → 序列化 → 检查 method 和 params。
调用关系:它保护后台终端清理接口,属于线程相关实验功能的一部分。
调用图:外部调用 2 个(Integer, assert_eq!)。
tests::serialize_thread_background_terminals_list3030–3052 ↗
fn serialize_thread_background_terminals_list() -> Result<()>
作用:确认列出线程后台终端的实验请求包含 threadId 和分页字段。
数据流:构造请求,cursor 和 limit 为空 → 序列化 → 检查它们以 null 形式出现。
调用关系:它覆盖后台终端列表接口,确保客户端分页读取时字段名一致。
调用图:外部调用 2 个(Integer, assert_eq!)。
tests::serialize_thread_background_terminals_terminate3055–3075 ↗
fn serialize_thread_background_terminals_terminate() -> Result<()>
作用:确认终止某个后台终端进程的实验请求格式正确。
数据流:构造包含 threadId 和 processId 的请求 → 序列化 → 比对 JSON。
调用关系:它保护后台终端终止接口,和清理、列表测试一起覆盖后台终端这一组协议。
调用图:外部调用 2 个(Integer, assert_eq!)。
tests::serialize_thread_realtime_start3078–3118 ↗
fn serialize_thread_realtime_start() -> Result<()>
作用:确认启动实时会话的实验请求能正确带上模型、音频输出、提示词、声音等字段。
数据流:构造 ThreadRealtimeStart 请求 → 序列化 → 检查 architecture、threadId、outputModality、prompt、voice 等字段。
调用关系:它保护实时对话启动协议,是后面追加音频、文本、语音等实时接口的入口格式测试。
调用图:外部调用 2 个(Integer, assert_eq!)。
tests::serialize_thread_realtime_start_prompt_default_and_null3121–3233 ↗
fn serialize_thread_realtime_start_prompt_default_and_null() -> Result<()>
作用:确认实时会话启动时,prompt 没传和明确传 null 是两种可区分的情况。
数据流:分别构造 prompt=None 和 prompt=Some(None) 的请求 → 序列化检查一个省略 prompt、一个写出 null → 再从 JSON 反序列化回来确认语义不丢。
调用关系:它专门保护一个容易出错的可选字段语义,避免客户端无法表达“默认提示词”和“明确不要提示词”的区别。
调用图:外部调用 3 个(Integer, assert_eq!, json!)。
tests::serialize_thread_realtime_append_speech3236–3256 ↗
fn serialize_thread_realtime_append_speech() -> Result<()>
作用:确认向实时线程追加一段语音文本的请求格式正确。
数据流:构造包含 threadId 和 text 的请求 → 序列化 → 检查 method 和 params。
调用关系:它覆盖实时对话追加语音这一分支,依赖前面的实时启动协议。
调用图:外部调用 2 个(Integer, assert_eq!)。
tests::serialize_thread_status_changed_notification3259–3278 ↗
fn serialize_thread_status_changed_notification() -> Result<()>
作用:确认服务器通知线程状态变化时 JSON 格式正确。
数据流:构造 ThreadStatusChanged 通知 → 序列化 → 检查 method、threadId 和 status 对象。
调用关系:它测试 ServerNotification 生成的通知格式,客户端靠这种通知更新界面状态。
调用图:外部调用 2 个(ThreadStatusChanged, assert_eq!)。
tests::serialize_thread_realtime_output_audio_delta_notification3281–3311 ↗
fn serialize_thread_realtime_output_audio_delta_notification() -> Result<()>
作用:确认服务器推送实时音频片段时,通知 JSON 包含音频数据和采样信息。
数据流:构造 ThreadRealtimeOutputAudioDelta 通知,含 base64 音频、采样率、声道数等 → 序列化 → 和期望 JSON 比较。
调用关系:它保护实时音频输出通知格式,让客户端能边收到边播放或展示。
调用图:外部调用 2 个(ThreadRealtimeOutputAudioDelta, assert_eq!)。
tests::mock_experimental_method_is_marked_experimental3314–3321 ↗
fn mock_experimental_method_is_marked_experimental()
作用:确认专门用于测试的 mock 实验方法会被实验 API 机制识别出来。
数据流:构造 MockExperimentalMethod 请求 → 调用 experimental_reason → 检查返回对应方法名。
调用关系:它测试实验性标记机制本身,确保宏生成的 ExperimentalApi 实现能识别标注过的请求。
调用图:外部调用 4 个(experimental_reason, Integer, default, assert_eq!)。
tests::environment_add_is_marked_experimental3324–3334 ↗
fn environment_add_is_marked_experimental()
作用:确认 environment/add 请求被标记为实验功能。
数据流:构造 EnvironmentAdd 请求 → 查询 experimental_reason → 检查返回 environment/add。
调用关系:它保护远程环境功能的实验开关,避免未稳定接口被当成正式接口放行。
调用图:外部调用 3 个(experimental_reason, Integer, assert_eq!)。
tests::command_exec_permission_profile_is_marked_experimental3337–3360 ↗
fn command_exec_permission_profile_is_marked_experimental()
作用:确认 command/exec 里只有 permissionProfile 这个字段触发实验标记。
数据流:构造带 permission_profile 的命令执行请求 → 调用 experimental_reason → 检查返回字段级原因 command/exec.permissionProfile。
调用关系:它测试 inspect_params 这种“方法基本稳定,但某些字段仍实验”的机制。
调用图:外部调用 4 个(experimental_reason, Integer, assert_eq!, vec!)。
tests::thread_realtime_start_is_marked_experimental3363–3383 ↗
fn thread_realtime_start_is_marked_experimental()
作用:确认启动实时线程的方法仍被视为实验功能。
数据流:构造 ThreadRealtimeStart 请求 → 调用 experimental_reason → 检查返回 thread/realtime/start。
调用关系:它保护实时会话功能的实验门禁,避免客户端在未允许时使用。
调用图:外部调用 3 个(experimental_reason, Integer, assert_eq!)。
tests::thread_goal_methods_are_not_marked_experimental3386–3421 ↗
fn thread_goal_methods_are_not_marked_experimental()
作用:确认线程目标的设置、读取、清除接口已经不是实验功能。
数据流:分别构造 ThreadGoalSet、ThreadGoalGet、ThreadGoalClear 请求 → 查询 experimental_reason → 都应返回 None。
调用关系:它防止已经稳定的线程目标接口被误加实验限制,影响正常客户端。
调用图:外部调用 2 个(Integer, assert_eq!)。
tests::thread_goal_notifications_are_not_marked_experimental3424–3452 ↗
fn thread_goal_notifications_are_not_marked_experimental()
作用:确认线程目标更新和清除通知不是实验通知。
数据流:构造 ThreadGoalUpdated 和 ThreadGoalCleared 通知 → 查询 experimental_reason → 都检查为 None。
调用关系:它和线程目标方法测试配套,保证请求和通知两边都被当成稳定协议。
调用图:外部调用 3 个(ThreadGoalCleared, ThreadGoalUpdated, assert_eq!)。
tests::thread_settings_updated_notification_is_marked_experimental3455–3486 ↗
fn thread_settings_updated_notification_is_marked_experimental()
作用:确认线程设置更新通知仍受实验功能限制。
数据流:构造包含 cwd、审批策略、沙箱、模型、协作模式等字段的通知 → 查询 experimental_reason → 检查返回 thread/settings/updated。
调用关系:它测试 ServerNotification 上由宏派生出的实验标记,保护设置更新通知不会提前公开。
调用图:外部调用 3 个(ThreadSettingsUpdated, absolute_path, assert_eq!)。
tests::turn_moderation_metadata_notification_is_marked_experimental3489–3501 ↗
fn turn_moderation_metadata_notification_is_marked_experimental()
作用:确认回合审核元数据通知被标记为实验功能。
数据流:构造 TurnModerationMetadata 通知,带 threadId、turnId 和 metadata JSON → 查询 experimental_reason → 检查返回对应原因。
调用关系:它保护 moderation metadata 这类尚未稳定的通知,只在实验开关允许时使用。
调用图:外部调用 3 个(TurnModerationMetadata, assert_eq!, json!)。
tests::thread_realtime_started_notification_is_marked_experimental3504–3513 ↗
fn thread_realtime_started_notification_is_marked_experimental()
作用:确认实时线程启动成功通知被标记为实验功能。
数据流:构造 ThreadRealtimeStarted 通知 → 调用 experimental_reason → 检查返回 thread/realtime/started。
调用关系:它覆盖实时会话通知的实验门禁,和实时请求实验测试相互配套。
调用图:外部调用 3 个(ThreadRealtimeStarted, experimental_reason, assert_eq!)。
tests::thread_realtime_output_audio_delta_notification_is_marked_experimental3516–3531 ↗
fn thread_realtime_output_audio_delta_notification_is_marked_experimental()
作用:确认实时音频输出片段通知被标记为实验功能。
数据流:构造包含音频块的 ThreadRealtimeOutputAudioDelta 通知 → 查询 experimental_reason → 检查返回 thread/realtime/outputAudio/delta。
调用关系:它保护实时音频流通知的实验状态,避免未稳定的流式音频协议被默认开放。
调用图:外部调用 3 个(ThreadRealtimeOutputAudioDelta, experimental_reason, assert_eq!)。
tests::command_execution_request_approval_additional_permissions_is_marked_experimental3534–3564 ↗
fn command_execution_request_approval_additional_permissions_is_marked_experimental()
作用:确认命令执行审批参数里的 additionalPermissions 字段会触发实验标记。
数据流:构造带额外文件系统权限的 CommandExecutionRequestApprovalParams → 调用 experimental_reason → 检查返回字段级实验原因。
调用关系:它测试参数类型自己的 ExperimentalApi 实现,说明不只是整个方法可以实验,某个敏感字段也能单独受控。
调用图:外部调用 3 个(experimental_reason, assert_eq!, vec!)。
app-server-protocol/src/protocol/mappers.rs源码 ↗
这个文件很小,但很关键:它像一个“转接头”,专门把 v1 版本的命令执行请求,转换成 v2 版本能理解的格式。旧版请求里只有命令、超时时间、工作目录、沙箱策略等少量信息;新版请求支持更多能力,比如进程编号、终端模式、是否流式传输入输出、输出大小限制、权限配置等。转换时,旧版有的字段会原样搬过去,比如 command 和 cwd;旧版没有的新版字段,会填上保守默认值,比如不启用 tty、不流式传输、不禁用超时。超时时间会从旧格式转成新版需要的 i64 数字;如果数字太大转不了,就用 60000 毫秒作为兜底值。这样做的好处是,新协议可以继续演进,同时尽量不抛弃老协议使用者。
CommandExecParams::from4–23 ↗
fn from(value: v1::ExecOneOffCommandParams) -> Self
作用:这个函数把一个 v1::ExecOneOffCommandParams 旧版命令参数,变成 v2::CommandExecParams 新版命令参数。别人可以用 Rust 的 From/Into 转换机制调用它,就像把旧插头插进转接头,得到新版接口能用的东西。
数据流:进去的是旧版命令参数,里面包含要执行的 command、可选 timeout_ms、cwd 和 sandbox_policy。函数把这些旧字段搬到新版结构里,同时给新版才有的字段补默认值:进程编号为空,tty 关闭,标准输入输出流式传输关闭,输出上限为空,不禁用输出上限,不禁用超时,环境变量、窗口大小、权限配置也为空。timeout_ms 会尝试转成新版使用的 i64;如果转换失败,就改用 60000。出来的是一个完整的 v2::CommandExecParams,不会修改原来的外部状态。
调用关系:它位于协议版本转换这一步:当系统收到或持有旧版的一次性命令执行参数,但后续流程只想按新版 CommandExecParams 处理时,就会用到它。它主要自己完成字段搬运和默认值填充,只把 sandbox_policy 的细节转换交给对应类型自己的 Into 转换。
app-server-protocol/src/protocol/v1.rs源码 ↗
这个文件本身不做具体动作,主要是定义数据形状。可以把它理解成前后端通信时用的“合同”:客户端初始化时要报自己的名字、版本和能力;服务器要回自己的运行环境和 Codex 主目录;请求会话摘要、登录 API Key、查看 Git diff、申请执行命令或修改文件的批准,都有各自固定的参数和返回结果。这里大量使用 Serialize/Deserialize,意思是这些 Rust 数据可以和 JSON 互相转换,方便通过网络或进程间通信发送;JsonSchema 和 TS 则用来生成 JSON 说明和 TypeScript 类型,让其他语言的客户端也能按同一套规则接入。一个重要点是,很多字段是 Option,也就是“可以没有”,这样协议能兼容不同客户端能力和不同运行场景。
V2 共享核心与对话模型
此组介绍 v2 命名空间、其共享枚举和通用通知,然后介绍构成中央交互协议叙事的线程、轮次、项目和 review 结构。
app-server-protocol/src/protocol/v2/mod.rs源码 ↗
可以把这个文件想成一本书的目录页。真正的内容分散在 account、apps、fs、thread、turn 等许多子模块里,每个模块负责一类协议数据或消息。这里先用 mod 声明告诉 Rust(项目使用的编程语言)这些模块存在,再用 pub use 把它们重新公开出去。这样外部代码只要引用 protocol::v2,就能拿到 v2 协议下的大部分公开类型和工具,而不用关心它们实际放在哪个小文件里。它本身不写业务规则,也不处理网络请求;它的重要性在于统一出口,保证协议版本的结构清楚、引用方便。最后的 tests 只在测试时启用,用来放这个模块相关的测试代码。
app-server-protocol/src/protocol/v2/notification.rs源码 ↗
这个文件像一张“通知表格模板”。app-server 有些事情不是等别人来问才回答,而是需要主动告诉客户端,比如某个功能快废弃了、某个线程里有警告、守护机制发现问题、一次执行出错了,或者某个服务端请求已经处理完了。这里没有真正发送消息的代码,只定义每种通知必须带哪些信息。比如废弃通知有摘要和可选的详细说明;错误通知会带具体错误、是否会自动重试、属于哪个 thread 和 turn。文件里的 serde 用来把这些结构变成 JSON 或从 JSON 读回来,JSON 是常见的数据交换格式;JsonSchema 和 TS 用来生成给其他语言或工具看的类型说明,保证 Rust 服务端和前端/客户端使用同一套字段规则。
app-server-protocol/src/protocol/v2/thread_data.rs源码 ↗
这个文件主要解决“对话历史该长什么样、来源怎么表示、错误怎么带出去”的问题。这里的“线程”可以理解成一段聊天或任务记录,“turn”是一轮用户和系统的交互。文件里定义了 Thread、Turn、TurnError、GitInfo 等结构,告诉接口返回时有哪些字段,比如线程编号、创建时间、工作目录、模型来源、Git 信息、每轮消息列表和失败原因。它还定义 SessionSource 和 ThreadSource,用来说明会话从 CLI、VSCode、app-server、子代理等哪里来。因为底层 codex_protocol 也有自己的类型,这里还写了一组互相转换的函数,相当于在“内部说法”和“对外接口说法”之间做翻译。另一个重要点是序列化:Serde 负责把 Rust 数据变成 JSON,TS 和 JsonSchema 负责给前端生成 TypeScript 类型和 JSON 结构说明,减少前后端理解不一致的风险。
SessionSource::from37–49 ↗
fn from(value: CoreSessionSource) -> Self
作用:把底层协议里的会话来源,翻译成 app-server v2 接口对外使用的会话来源。这样前端不用直接面对内部协议里的所有细节。
数据流:进去的是一个 CoreSessionSource,也就是底层记录的来源 → 函数逐项判断它是 CLI、VSCode、Exec、Mcp、自定义来源、内部来源、子代理还是未知 → 出来的是对应的 SessionSource;其中 Mcp 会被对外叫作 AppServer,Internal 会被隐藏成 Unknown,自定义和子代理会保留原始内容。
调用关系:它通常在服务器把内部线程数据包装成 v2 API 返回值时被用到。遇到 Custom 或 SubAgent 这类带额外内容的来源时,它会交给对应的构造项把内容装进对外类型里。
调用图:外部调用 2 个(Custom, SubAgent)。
CoreSessionSource::from53–63 ↗
fn from(value: SessionSource) -> Self
作用:把 app-server v2 接口使用的会话来源,翻译回底层协议能理解的会话来源。有人从接口层数据继续写入或传给核心协议时会需要它。
数据流:进去的是一个 SessionSource → 函数按来源种类转换成 CoreSessionSource → 出来的是底层协议类型;例如 AppServer 会变回 Mcp,VsCode 会变回 VSCode,自定义来源和子代理信息会一起带回去。
调用关系:它和 SessionSource::from 是一对反向翻译器。接口层需要把用户或 app-server 的数据交回 codex_protocol 时,会走到这里;带内容的 Custom 和 SubAgent 会由对应构造项继续承载。
调用图:外部调用 2 个(Custom, SubAgent)。
ThreadSource::schema_name78–80 ↗
fn schema_name() -> String
作用:告诉 JSON Schema 生成器,这个字段的结构说明应该叫“ThreadSource”。JSON Schema 是一份给机器看的字段说明书,用来校验或生成文档。
数据流:进去不需要业务数据 → 函数固定返回一个名字字符串 → 出来的是“ThreadSource”,供 schema 工具标记这个类型。
调用关系:它属于 JsonSchema 这套生成流程的一部分。当前端或文档工具需要生成接口结构说明时,会先问这个类型叫什么名字,再继续生成具体形状。
ThreadSource::json_schema82–84 ↗
fn json_schema(generator: &mut SchemaGenerator) -> Schema
作用:告诉工具:ThreadSource 在 JSON 里就是一个字符串。这样外部看到的不是复杂枚举对象,而是简单好传的文本。
数据流:进去的是 SchemaGenerator,也就是生成 JSON 说明书的工具 → 函数借用 String 的 schema 生成方式 → 出来的是“字符串类型”的 schema。
调用关系:它在生成接口文档或校验规则时被调用,并把具体工作交给 String::json_schema。这样 ThreadSource 虽然在 Rust 里是枚举,对外仍按字符串处理。
调用图:外部调用 1 个(json_schema)。
ThreadSource::try_from90–92 ↗
fn try_from(value: String) -> Result<Self, Self::Error>
作用:把接口收到的字符串尝试解析成 ThreadSource。它用于反序列化,也就是把 JSON 文本读回 Rust 数据。
数据流:进去的是一个字符串,比如表示用户线程、子代理线程或某个功能产生的线程 → 函数先按底层 CoreThreadSource 的规则解析,再转换成接口层 ThreadSource → 成功时出来的是 ThreadSource,失败时出来的是错误字符串。
调用关系:Serde 读 JSON 时会用到这个入口,因为 ThreadSource 声明了从 String 尝试转换。它依赖底层协议的解析规则,保证接口层和核心层对这些字符串的理解一致。
String::from96–98 ↗
fn from(value: ThreadSource) -> Self
作用:把 ThreadSource 变成要写进 JSON 的字符串。接口返回数据时需要它,让来源信息以简单文本形式出现。
数据流:进去的是一个 ThreadSource → 函数先把它转成 CoreThreadSource,再利用底层类型的字符串转换规则 → 出来的是一个 String,可直接放进 JSON。
调用关系:Serde 写 JSON 时会用到它,因为 ThreadSource 声明了可以转换成 String。它把实际的字符串格式交给底层 CoreThreadSource 处理,避免两边各写一套规则。
调用图:外部调用 1 个(from)。
ThreadSource::from102–109 ↗
fn from(value: CoreThreadSource) -> Self
作用:把底层协议里的线程来源翻译成 app-server v2 的 ThreadSource。这样接口层可以使用自己的类型,同时不丢掉底层记录的信息。
数据流:进去的是 CoreThreadSource → 函数判断它是用户、子代理、某个功能,还是内存整理任务 → 出来的是对应的 ThreadSource;如果是 Feature,会把功能名一起带上。
调用关系:它常被 ThreadSource::try_from 或内部数据转接口数据时使用。遇到 Feature 这种带名字的来源时,它会用 Feature 构造项把名字保存下来。
调用图:外部调用 1 个(Feature)。
CoreThreadSource::from113–120 ↗
fn from(value: ThreadSource) -> Self
作用:把 app-server v2 的 ThreadSource 翻译回底层协议里的 CoreThreadSource。需要把接口层数据交给核心协议时会用它。
数据流:进去的是 ThreadSource → 函数按种类逐个映射成 CoreThreadSource → 出来的是底层协议类型;Feature 会把功能名原样带回底层。
调用关系:它和 ThreadSource::from 是一对反向翻译器。String::from 会先调用这一步,再把底层线程来源变成真正对外输出的字符串。
调用图:外部调用 1 个(Feature)。
app-server-protocol/src/protocol/v2/thread.rs源码 ↗
这里的“线程”可以理解成一次持续的对话或工作会话。这个文件本身不真正去执行任务,而是规定前端、服务器、底层核心之间说话时每个包长什么样:请求里要带哪些字段,响应会返回哪些字段,状态通知会告诉客户端什么。它还覆盖了很多实际场景,比如启动线程、恢复历史、分页查看轮次、更新设置、管理目标、统计 token 用量、清理后台终端等。很多字段带有 JSON、TypeScript 和 schema 标注,意思是同一份定义可以同时用于服务端校验、接口文档和前端类型。少数 from 函数像翻译员,把底层核心库里的结构转换成这个 v2 接口对外暴露的样子,避免内部格式直接漏给客户端。
TurnsPage::from425–431 ↗
fn from(response: ThreadTurnsListResponse) -> Self
作用:把“列出线程轮次”的完整响应,简化成“只包含一页轮次数据”的对象。这样恢复线程时,如果顺便要带一页最近轮次,就能复用同一种分页格式。
数据流:进去的是一个 ThreadTurnsListResponse,里面有轮次列表、下一页游标和反向翻页游标。它不改内容,只把这三个字段原样搬到新的 TurnsPage 里。出来的是一个更通用的分页结果,方便嵌进别的响应。
调用关系:它是一个格式转换小零件。线程恢复响应里的 initial_turns_page 需要的是 TurnsPage,而列轮次接口产出的是 ThreadTurnsListResponse,这个函数就在两种包装之间做无损转换。调用图里没有显示具体上游,但它的存在就是为了让这些分页结果能互相接上。
ThreadGoal::from682–693 ↗
fn from(value: codex_protocol::protocol::ThreadGoal) -> Self
作用:把底层核心库里的线程目标,转换成 v2 API 对外返回的线程目标。这样外部客户端看到的是稳定的接口格式,而不是内部实现格式。
数据流:进去的是核心库的 ThreadGoal,里面有线程 ID、目标文字、状态、token 预算、已用 token、已用时间、创建和更新时间。它把线程 ID 转成字符串,把状态转成 v2 的状态枚举,其余数字和文字照搬。出来的是 v2 的 ThreadGoal。
调用关系:调用图显示它会被 thread_goal_set_inner 使用。也就是说,当服务器内部设置或更新了线程目标后,会先拿到底层核心格式的目标,再通过这个函数翻译成 v2 响应里的 goal 返回给客户端。
调用图:被 1 处调用(thread_goal_set_inner)。
ThreadMemoryMode::as_str814–819 ↗
fn as_str(self) -> &'static str
作用:把线程记忆模式变成普通字符串,比如 enabled 或 disabled。有人需要写日志、拼接口字段、做简单展示时,用字符串更方便。
数据流:进去的是一个 ThreadMemoryMode 枚举值,也就是“开启记忆”或“关闭记忆”。函数根据是哪一种,返回对应的固定英文小写字符串。它不修改任何数据,也不做外部访问。
调用关系:它是记忆模式这个小类型自带的便捷工具。调用图里没有显示具体调用者,但它通常会被需要把枚举展示成文本的代码使用;它不把工作交给别的函数。
ThreadMemoryMode::to_core821–826 ↗
fn to_core(self) -> codex_protocol::protocol::ThreadMemoryMode
作用:把 v2 API 里的记忆模式,转换成底层核心库认识的记忆模式。这样接口层和核心层可以各用各的类型,但含义保持一致。
数据流:进去的是 v2 的 ThreadMemoryMode,值只有开启或关闭。函数按一一对应关系,产出核心库里的 ThreadMemoryMode::Enabled 或 ThreadMemoryMode::Disabled。它不改变原对象,只返回翻译后的值。
调用关系:它位于接口层通往核心层的边界上。客户端发来设置记忆模式的请求时,接口层会拿到 v2 类型;真正交给核心逻辑前,就需要这种转换。调用图里没有列出具体调用者,但它的用途就是做这层翻译。
ThreadTokenUsage::from1299–1305 ↗
fn from(value: CoreTokenUsageInfo) -> Self
作用:把核心库统计出来的 token 用量,转换成 v2 通知里客户端能读懂的格式。token 可以粗略理解成模型处理文字时的计量单位。
数据流:进去的是 CoreTokenUsageInfo,里面有累计用量、最近一次用量,以及模型上下文窗口大小。函数把累计和最近两块分别转成 TokenUsageBreakdown,再把窗口大小带上。出来的是 ThreadTokenUsage,可直接放进线程 token 用量更新通知。
调用关系:调用图显示它会被 send_thread_token_usage_update_to_connection 使用。也就是服务器准备把 token 用量变化推送给某个连接时,会先用这个函数把内部统计结果翻译成 v2 协议对象。它内部依赖每一块用量的转换规则,也就是 TokenUsageBreakdown::from。
调用图:被 1 处调用(send_thread_token_usage_update_to_connection)。
TokenUsageBreakdown::from1325–1333 ↗
fn from(value: CoreTokenUsage) -> Self
作用:把一份核心库里的 token 明细,转换成 v2 API 里的 token 明细。它让“总 token、输入 token、缓存输入、输出、推理输出”这些数字在接口层有统一名字。
数据流:进去的是核心库的 CoreTokenUsage,包含各种 token 计数。函数逐项复制这些数字到 v2 的 TokenUsageBreakdown 字段里。出来的是可以被响应或通知序列化发送的明细对象,原始数据不被修改。
调用关系:它是 token 用量转换里的底层小翻译器。ThreadTokenUsage::from 在组装完整用量信息时,会把累计用量和最近用量都转成这种明细;这个函数就负责每一块明细的字段对齐。
app-server-protocol/src/protocol/v2/turn.rs源码 ↗
用户点发送、继续补充指令、打断正在运行的任务,都会用到这里的类型。这个文件把这些动作需要带什么信息写清楚:比如线程编号、输入内容、工作目录、模型选择、沙盒权限、额外上下文等。它还定义了服务端会发回哪些通知,比如一轮开始了、完成了、计划更新了、文件差异更新了。文件里也有一层“翻译器”:v2 协议用的 UserInput、TextElement、ByteRange,会和核心库里的对应类型互相转换。这样外部接口可以保持稳定、好序列化,内部代码也能继续用自己的核心数据结构。这里大量使用 serde(把结构变成 JSON 或从 JSON 读回来)、JsonSchema(生成 JSON 结构说明)和 TS(生成 TypeScript 类型),目的是让不同语言的客户端不容易传错字段。
ByteRange::from220–225 ↗
fn from(value: CoreByteRange) -> Self
作用:把核心库里的字节范围转换成 v2 协议里的字节范围。字节范围就是一段文本在原始字节里的起点和终点,常用来标记文本中的特殊片段。
数据流:进去的是 CoreByteRange,里面有 start 和 end 两个数字;函数原样取出这两个数字,放进协议层的 ByteRange;出来的是可以发给 v2 客户端或写进 v2 数据结构的范围对象,不改动别的东西。
调用关系:它站在核心数据和外部协议之间做小翻译。凡是内部文本元素要显示到 v2 接口时,都会需要这种从核心格式到协议格式的转换。
CoreByteRange::from229–234 ↗
fn from(value: ByteRange) -> Self
作用:把 v2 协议里的字节范围转换回核心库能用的字节范围。这样客户端传来的文本标记位置,内部引擎才能继续处理。
数据流:进去的是 ByteRange,带着 start 和 end;函数不改含义,只把字段复制到 CoreByteRange;出来的是核心库版本的范围对象。
调用关系:它通常配合 UserInput::into_core 和 TextElement 的转换使用。当请求从 API 入口进入核心逻辑时,这个函数负责把位置标记换成内部格式。
TextElement::new248–253 ↗
fn new(byte_range: ByteRange, placeholder: Option<String>) -> Self
作用:创建一个文本元素。文本元素可以理解为文本里某一小段带特殊含义的区域,比如 UI 需要记住或展示的占位内容。
数据流:进去的是一段 byte_range 和一个可选 placeholder;函数把它们装进新的 TextElement;出来的是一个完整的文本元素对象,没有额外副作用。
调用关系:它是 TextElement 的标准造法。调用图显示它会被 text_elements、expand_pending_pastes 等流程使用,也会被 TextElement::from 在格式转换时使用。
调用图:被 4 处调用(thread_read_returns_summary_without_turns, task_finish_emits_turn_item_lifecycle_for_leftover_pending_user_input, text_elements, expand_pending_pastes)。
TextElement::set_placeholder255–257 ↗
fn set_placeholder(&mut self, placeholder: Option<String>)
作用:修改文本元素的占位说明。占位说明是给人看的提示文字,用来在界面上代表某段特殊内容。
数据流:进去的是一个可选字符串;函数把当前 TextElement 里的 placeholder 换成新值;出来没有返回值,但这个文本元素本身被改了。
调用关系:它给已有文本元素提供一个安全的改名入口。别的流程如果先确定范围、后补展示文字,就会用这种方法更新。
TextElement::placeholder259–261 ↗
fn placeholder(&self) -> Option<&str>
作用:读取文本元素当前的占位说明,但不拿走它。外部代码可以用它来显示提示文字,同时保持原对象不变。
数据流:进去的是对 TextElement 的只读访问;函数查看内部 placeholder;出来的是一个可选的字符串引用,有就给出文字,没有就表示没有占位说明。
调用关系:它是访问私有 placeholder 字段的公开小窗口。这样外部不能随便乱改字段,只能通过 getter 读取、通过 set_placeholder 修改。
TextElement::from265–270 ↗
fn from(value: CoreTextElement) -> Self
作用:把核心库的文本元素转换成 v2 协议的文本元素。它让内部保存的文本标记能安全地出现在 API 返回值里。
数据流:进去的是 CoreTextElement;函数先把内部 byte_range 转成协议 ByteRange,再通过核心对象的 _placeholder_for_conversion_only 取出占位文字;最后调用 TextElement::new 组装成协议对象。
调用关系:它是从核心层走向协议层的翻译步骤。调用关系里它会用到外部的 _placeholder_for_conversion_only 和 new;目的是避免协议层直接依赖核心对象的内部细节。
调用图:外部调用 2 个(_placeholder_for_conversion_only, new)。
CoreTextElement::from274–276 ↗
fn from(value: TextElement) -> Self
作用:把 v2 协议的文本元素转换成核心库的文本元素。客户端传来的文本标记需要经过这一步,内部引擎才认得。
数据流:进去的是 TextElement,包含 byte_range 和 placeholder;函数把 byte_range 转成核心格式,然后调用核心 TextElement 的 new 创建内部对象;出来的是 CoreTextElement。
调用关系:它常在请求进入核心逻辑前发生,尤其是 UserInput::into_core 处理文本输入时。它把 API 边界上的数据交给核心库使用。
调用图:外部调用 1 个(new)。
UserInput::into_core313–330 ↗
fn into_core(self) -> CoreUserInput
作用:把 v2 接口收到的用户输入转换成核心引擎的输入格式。用户输入可能是文字、网络图片、本地图片、技能或提到的文件路径。
数据流:进去的是一个 UserInput;函数根据具体类型分别拆开字段:文字会连同 text_elements 一起转换,图片会把 url 改成核心字段 image_url,本地图片、Skill、Mention 则转交对应字段;出来的是 CoreUserInput,原来的 self 被消费掉。
调用关系:它是请求进入核心执行前的重要关口。turn/start 或 turn/steer 这类请求拿到外部输入后,需要靠它把数据变成核心引擎能理解的样子。
UserInput::from334–352 ↗
fn from(value: CoreUserInput) -> Self
作用:把核心引擎的用户输入转换回 v2 协议格式。这样内部记录或回放的输入,可以按 API 约定发给客户端。
数据流:进去的是 CoreUserInput;函数按类型重新包装成 UserInput:核心的 image_url 会变回协议里的 url,文本元素会逐个转换;如果遇到这里不支持的核心输入类型,会触发 unreachable!,意思是代码认为那种情况不该发生。
调用关系:它是核心层返回协议层时的翻译器。调用关系显示它只在异常分支会用到 unreachable!;正常情况下,它和 UserInput::into_core 方向相反,保持两边格式可互通。
调用图:外部调用 1 个(unreachable!)。
UserInput::text_char_count356–364 ↗
fn text_char_count(&self) -> usize
作用:计算这个用户输入里真正的文字字符数。它常用于统计输入长度,而图片、技能、提及文件这类输入按 0 个文字算。
数据流:进去的是一个 UserInput 的只读引用;如果它是 Text,就按字符而不是字节来数 text 的长度;如果是 Image、LocalImage、Skill 或 Mention,就返回 0;不修改任何数据。
调用关系:它是围绕 UserInput 的小工具函数。需要估算用户发了多少文本时可以直接调用它,而不用每个调用方自己判断输入类型。
TurnPlanStep::from430–435 ↗
fn from(value: CorePlanItemArg) -> Self
作用:把核心库里的计划步骤转换成 v2 协议里的计划步骤。计划步骤就是助手接下来打算做什么、做到哪一步了。
数据流:进去的是 CorePlanItemArg,里面有 step 文本和核心状态;函数复制 step,并把 status 转成协议状态;出来的是 TurnPlanStep,可用于计划更新通知。
调用关系:它服务于 TurnPlanUpdatedNotification 这类对外通知。当核心计划工具产出步骤后,这里负责把它包装成客户端能读懂的 v2 结构。
TurnPlanStepStatus::from439–445 ↗
fn from(value: CorePlanStepStatus) -> Self
作用:把核心库的计划状态转换成 v2 协议的计划状态。状态只有待处理、进行中、已完成三种,对外展示时要保持名称和协议一致。
数据流:进去的是 CorePlanStepStatus;函数用匹配分支把 Pending、InProgress、Completed 一一对应到 TurnPlanStepStatus;出来的是协议层状态值。
调用关系:它通常被 TurnPlanStep::from 调用,是计划步骤转换里的一个小齿轮。核心状态先过它翻译,再随整条计划步骤发给客户端。
app-server-protocol/src/protocol/v2/item.rs源码 ↗
可以把这个文件理解成一套“快递面单格式”。系统内部有自己的数据结构,但发给客户端时,必须包装成稳定、清楚、能转成 JSON 的协议对象。这里定义了 ThreadItem,也就是对话线程里可能出现的各种东西:用户消息、助手回复、命令运行、文件改动、网页搜索、工具调用、子代理活动等。它还定义了很多状态和审批结果,比如命令是完成了、失败了,还是被拒绝了;用户是允许执行一次,还是允许整个会话。文件里大量 From/TryFrom 转换函数,就是把核心系统里的类型翻译成 v2 API 类型,或反过来翻译回去。这样客户端不用懂服务器内部结构,只要按这份协议读写数据即可。它还会导出 TypeScript 类型和 JSON Schema,方便网页端和外部集成方使用同一份“说明书”。
CommandExecutionApprovalDecision::from69–87 ↗
fn from(value: CoreReviewDecision) -> Self
作用:把核心系统里的命令审批结果,翻译成 v2 协议里客户端能看懂的审批结果。比如内部说“Approved”,对外就变成“Accept”。
数据流:进去的是一个核心审批决定 → 函数逐项判断它代表同意、拒绝、取消、会话内同意,还是带策略修改的同意 → 出来的是对应的 CommandExecutionApprovalDecision;如果内部结果带有执行策略或网络策略修改,也会一起转换后带出去。
调用关系:它处在“内部审批系统”和“对外协议”之间,像翻译员一样工作。服务器准备把审批结果发给客户端,或组装可选审批按钮时,会需要这种格式转换。
MemoryCitation::from137–142 ↗
fn from(value: CoreMemoryCitation) -> Self
作用:把核心系统里的记忆引用信息,转成 v2 协议里的记忆引用。记忆引用就是告诉客户端:这段回答参考了哪些历史内容或文件位置。
数据流:进去的是核心 MemoryCitation,里面有引用条目和 rollout/thread 标识 → 函数把每个引用条目继续转换成 v2 的 MemoryCitationEntry,并把 rollout_ids 改名放到 thread_ids → 出来的是客户端能序列化读取的 MemoryCitation。
调用关系:它通常跟助手消息一起出现。ThreadItem::from 在转换 AgentMessage 时,如果发现消息带有 memory_citation,就会借助这种转换把它带到外部协议里。
MemoryCitationEntry::from156–163 ↗
fn from(value: CoreMemoryCitationEntry) -> Self
作用:把一条具体的记忆引用从内部格式转成对外格式。它说明引用来自哪个路径、哪几行,以及备注是什么。
数据流:进去的是核心引用条目 → 函数原样取出 path、line_start、line_end、note → 出来的是 v2 的 MemoryCitationEntry,没有额外副作用。
调用关系:它被 MemoryCitation::from 用来逐条处理引用列表。也就是说,大引用对象负责整体包装,这个函数负责每一条小引用的翻译。
CommandAction::into_core167–188 ↗
fn into_core(self) -> CoreParsedCommand
作用:把 v2 协议里的命令动作说明,转回核心系统使用的命令解析结果。命令动作说明是系统对 shell 命令的粗略理解,比如读文件、列目录、搜索。
数据流:进去的是一个 CommandAction,例如 Read、ListFiles、Search 或 Unknown → 函数根据种类改成核心 ParsedCommand;Read 类型会把绝对路径转换成普通路径缓冲区 → 出来的是核心系统可以继续处理的 CoreParsedCommand。
调用关系:它用于客户端或外层协议把命令动作传回服务器核心时。它和 CommandAction::from_core_with_cwd 是一对方向相反的转换器。
CommandAction::from_core_with_cwd190–207 ↗
fn from_core_with_cwd(value: CoreParsedCommand, cwd: &AbsolutePathBuf) -> Self
作用:把核心系统解析出的命令动作,转成 v2 协议里更适合展示的命令动作。特别是读文件动作,会用当前工作目录补成绝对路径,方便客户端显示。
数据流:进去的是核心 ParsedCommand 和当前工作目录 cwd → 函数按动作类型重新包装;如果是 Read,就调用 join 把相对路径接到 cwd 后面 → 出来的是 CommandAction,供 API 或界面展示。
调用关系:它位于命令解析结果走向客户端的路上。命令执行审批或命令执行条目需要友好展示“这条命令可能在做什么”时,会依赖这种转换。
调用图:调用 1 个内部函数(join)。
ThreadItem::id395–416 ↗
fn id(&self) -> &str
作用:从任意一种 ThreadItem 里取出它的 id。因为 ThreadItem 有很多种形态,但每一种都必须有一个唯一编号。
数据流:进去的是一个线程条目引用 → 函数不关心它是用户消息、命令执行、文件改动还是工具调用,只匹配出共同的 id 字段 → 出来的是这个 id 的字符串引用,不修改原数据。
调用关系:它是操作线程条目时的小工具。调用方不用分别判断二十多种条目类型,只要调用这个函数就能拿到统一标识。
AutoReviewDecisionSource::from440–444 ↗
fn from(value: CoreGuardianAssessmentDecisionSource) -> Self
作用:把核心系统里“自动审批最终决定来自哪里”的标记,翻译成 v2 协议标记。当前只有 Agent,也就是由代理判断。
数据流:进去的是核心决策来源 → 函数匹配 Agent → 出来的是 AutoReviewDecisionSource::Agent。
调用关系:它服务于自动审批通知。ItemGuardianApprovalReviewCompletedNotification 需要告诉客户端最终决定来源时,会用这种对外格式。
GuardianRiskLevel::from459–466 ↗
fn from(value: CoreGuardianRiskLevel) -> Self
作用:把核心系统判断出的风险等级,转成 v2 协议里的风险等级。风险等级包括低、中、高、严重。
数据流:进去的是核心 GuardianRiskLevel → 函数逐个等级对应转换 → 出来的是 v2 的 GuardianRiskLevel。
调用关系:它用于 Guardian 自动审批审查流程。服务器把审查结果通知客户端时,需要把内部风险评级翻译成协议字段。
GuardianUserAuthorization::from481–488 ↗
fn from(value: CoreGuardianUserAuthorization) -> Self
作用:把核心系统判断出的用户授权程度,转成 v2 协议里的授权程度。这个字段表达用户是否已经给过足够明确的允许。
数据流:进去的是核心授权等级 Unknown、Low、Medium 或 High → 函数一一映射 → 出来的是 v2 的 GuardianUserAuthorization。
调用关系:它和 GuardianRiskLevel::from 一起出现在自动审批审查结果里,帮助客户端展示“风险多高、用户授权有多充分”。
GuardianCommandSource::from514–519 ↗
fn from(value: CoreGuardianCommandSource) -> Self
作用:把核心系统里的命令来源,转成 v2 协议里的命令来源。它区分命令来自普通 shell,还是统一执行通道。
数据流:进去的是核心 GuardianCommandSource → 函数判断 Shell 或 UnifiedExec → 出来的是 v2 的 GuardianCommandSource。
调用关系:它被 GuardianApprovalReviewAction::from 使用。当核心审批动作涉及命令时,需要先把命令来源翻译成客户端认识的值。
CoreGuardianCommandSource::from523–528 ↗
fn from(value: GuardianCommandSource) -> Self
作用:把 v2 协议里的命令来源,转回核心系统里的命令来源。也就是做 GuardianCommandSource::from 的反向翻译。
数据流:进去的是 v2 的 GuardianCommandSource → 函数判断 Shell 或 UnifiedExec → 出来的是核心 CoreGuardianCommandSource。
调用关系:它被 CoreGuardianAssessmentAction::try_from 间接使用。客户端或协议层传来的审批动作要交回核心系统处理时,需要这个反向转换。
GuardianApprovalReviewAction::from639–696 ↗
fn from(value: CoreGuardianAssessmentAction) -> Self
作用:把核心系统里“这次自动审批正在审什么”翻译成 v2 协议动作。可能审的是命令、execve 调用、改文件、网络访问、MCP 工具调用或权限请求。
数据流:进去的是核心 GuardianAssessmentAction → 函数按动作类型拆开字段;命令来源、网络协议、权限配置等子字段也会转换成 v2 格式 → 出来的是 GuardianApprovalReviewAction,客户端可以据此展示审查对象。
调用关系:它在 Guardian 自动审批通知发出前使用。服务器核心产生审查动作,协议层通过这个函数把它包装成客户端能显示的结构。
CoreGuardianAssessmentAction::try_from702–759 ↗
fn try_from(value: GuardianApprovalReviewAction) -> Result<Self, Self::Error>
作用:把 v2 协议里的自动审批动作,尝试转回核心系统格式。之所以是“尝试”,是因为权限配置转换可能失败,需要返回错误。
数据流:进去的是 GuardianApprovalReviewAction → 函数按动作类型重建核心 GuardianAssessmentAction;网络协议调用 to_core,权限请求调用 try_into,失败时返回 io::Error → 出来的是成功的核心动作,或一个错误。
调用关系:它是 GuardianApprovalReviewAction::from 的反方向。外部协议数据需要交给核心审批系统理解时,会通过它进入内部世界。
WebSearchAction::from783–796 ↗
fn from(value: codex_protocol::models::WebSearchAction) -> Self
作用:把核心模型里的网页搜索动作,翻译成 v2 协议里的网页搜索动作。它能区分搜索、打开网页、在网页内查找,以及未知动作。
数据流:进去的是核心 WebSearchAction → 函数保留 query、queries、url、pattern 等字段并换成 v2 枚举 → 出来的是协议层 WebSearchAction。
调用关系:调用图显示它会被 handle_web_search_end 和 ThreadItem::from 使用。也就是说,网页搜索结束或线程条目转换时,都会靠它把搜索动作整理给客户端。
调用图:被 2 处调用(handle_web_search_end, from)。
ThreadItem::from800–890 ↗
fn from(value: CoreTurnItem) -> Self
作用:把核心系统的一条对话内容,转换成 v2 协议里的 ThreadItem。这是本文件最核心的翻译函数之一。
数据流:进去的是 CoreTurnItem,可能是用户消息、助手消息、计划、推理、网页搜索、图片、文件改动、MCP 工具调用等 → 函数按类型重新组装字段;文件改动会调用 convert_patch_changes,网页搜索和工具状态等会继续调用对应的 from 转换;有些耗时会换算成毫秒 → 出来的是客户端能接收的 ThreadItem。
调用关系:调用图显示它被 handle_item_started 和 handle_item_completed 使用。也就是说,一个条目开始或结束时,服务器会用它把内部事件包装成对外通知。
调用图:调用 3 个内部函数(convert_patch_changes, from, from);被 2 处调用(handle_item_completed, handle_item_started)。
HookPromptFragment::from894–899 ↗
fn from(value: codex_protocol::items::HookPromptFragment) -> Self
作用:把核心系统里的 hook 提示片段,转成 v2 协议里的提示片段。hook 可以理解成系统在某些时机自动插入的一段提示。
数据流:进去的是核心 HookPromptFragment → 函数取出 text 和 hook_run_id → 出来的是协议层 HookPromptFragment。
调用关系:它在 ThreadItem::from 转换 HookPrompt 条目时被使用。一个 hook 提示可能有多个片段,这个函数负责逐片段翻译。
CommandExecutionStatus::from919–925 ↗
fn from(value: &CoreExecCommandStatus) -> Self
作用:把核心系统里的命令执行状态,转换成 v2 协议里的状态。比如完成、失败、被拒绝。
数据流:进去的是核心 CoreExecCommandStatus → 这个函数把值借用后交给另一个 from 实现处理 → 出来的是 CommandExecutionStatus。
调用关系:它是命令执行条目和命令完成通知里的状态翻译器。外层代码不需要知道核心状态的具体定义,只拿到协议状态即可。
调用图:外部调用 1 个(from)。
PatchApplyStatus::from986–992 ↗
fn from(value: &CorePatchApplyStatus) -> Self
作用:把核心系统里的补丁应用状态,转换成 v2 协议里的文件改动状态。补丁应用就是把一组文件修改真正写进去的过程。
数据流:进去的是核心 CorePatchApplyStatus → 这个函数把值借用后交给引用版本的 from 处理 → 出来的是 PatchApplyStatus,例如 Completed、Failed 或 Declined。
调用关系:它被文件改动相关通知使用。ThreadItem::from 在转换 FileChange 时,也会把核心状态转成这里定义的对外状态。
调用图:外部调用 1 个(from)。
McpToolCallStatus::from996–1002 ↗
fn from(value: CoreMcpToolCallStatus) -> Self
作用:把核心系统里的 MCP 工具调用状态,转成 v2 协议里的状态。MCP 是一种让模型调用外部工具或服务的协议。
数据流:进去的是核心 McpToolCallStatus → 函数把 InProgress、Completed、Failed 一一映射 → 出来的是协议层 McpToolCallStatus。
调用关系:调用图显示它被 ThreadItem::from 使用。当 MCP 工具调用条目被发给客户端时,状态会先经过这个函数翻译。
调用图:被 1 处调用(from)。
SubAgentActivityKind::from1042–1048 ↗
fn from(value: CoreSubAgentActivityKind) -> Self
作用:把核心系统里的子代理活动类型,转成 v2 协议里的活动类型。子代理可以理解成主代理派出去协作的小助手。
数据流:进去的是核心 SubAgentActivityKind → 函数把 Started、Interacted、Interrupted 分别转换 → 出来的是协议层 SubAgentActivityKind。
调用关系:它用于子代理活动通知。服务器要告诉客户端某个子代理开始了、互动了或被打断了,就需要这种协议值。
CollabAgentState::from1073–1104 ↗
fn from(value: CoreAgentStatus) -> Self
作用:把核心系统里的协作代理状态,转成 v2 协议里的状态对象。它不仅有状态名,还可能带一段说明文字。
数据流:进去的是核心 AgentStatus → 函数根据状态生成 CollabAgentState;完成状态可能带 message,错误状态会把错误文字放进 message,运行中等状态则没有 message → 出来的是客户端能展示的协作代理状态。
调用关系:调用图显示它被 item_event_to_server_notification 以及多个协作代理结束处理函数使用。协作代理生成、恢复、交互、关闭等流程完成时,都靠它把内部状态说给客户端听。
调用图:被 6 处调用(item_event_to_server_notification, collab_resume_end_maps_to_item_completed_resume_agent, handle_collab_agent_interaction_end, handle_collab_agent_spawn_end, handle_collab_close_end, handle_collab_resume_end)。
CommandExecutionRequestApprovalParams::strip_experimental_fields1365–1370 ↗
fn strip_experimental_fields(&mut self)
作用:在需要兼容旧客户端时,删掉命令审批请求里的实验性字段。实验性字段就是还没完全稳定、客户端不一定认识的字段。
数据流:进去的是一个可修改的 CommandExecutionRequestApprovalParams → 函数把 additional_permissions 设为 None → 出来的还是同一个对象,但这个实验字段已经被清空;其他字段不变。
调用关系:它位于服务器向客户端发送审批请求之前的兼容处理环节。注释里也说明目前是临时硬编码,未来可能会有更通用的实验字段处理办法。
DynamicToolCallOutputContentItem::from1439–1446 ↗
fn from(item: DynamicToolCallOutputContentItem) -> Self
作用:把 v2 协议里的动态工具输出内容,转成核心系统使用的动态工具输出内容。动态工具输出可以是文字,也可以是图片链接。
数据流:进去的是 DynamicToolCallOutputContentItem → 函数判断是 InputText 还是 InputImage,并保留 text 或 image_url → 出来的是 codex_protocol::dynamic_tools 里的对应内容项。
调用关系:它用于客户端或外部工具把动态工具结果回传给核心系统时。协议层收到结果后,通过这个函数交给内部动态工具机制继续处理。
app-server-protocol/src/protocol/v2/review.rs源码 ↗
这个文件像一张“表格说明书”,专门给应用服务器的 v2 协议用。没有它,前端和后端就可能各说各话:前端以为传一个提交号就能审查,后端却不知道字段叫什么、审查要跑在哪个对话里。这里定义了几个数据结构,并通过 serde(把 Rust 数据和 JSON 互相转换的工具)、JsonSchema(生成 JSON 结构说明的工具)和 TS(生成 TypeScript 类型的工具)保证 Rust 后端、接口文档和前端类型保持一致。ReviewStartParams 表示开始审查时要带的参数:原来的线程、审查目标、以及审查是在当前线程里跑还是另开线程跑。ReviewStartResponse 表示服务器开始审查后返回的信息,包括这次交互的 turn 和真正运行审查的线程 id。ReviewTarget 则把“审查什么”分成几类:未提交改动、相对某个基础分支的改动、某个提交、或者自定义文字指令。整体作用就是把代码审查请求讲清楚,避免接口双方猜来猜去。
app-server-protocol/src/protocol/v2/realtime.rs源码 ↗
这个文件基本不做业务动作,而是在规定一套通信合同:客户端想在某个 thread 里启动实时语音/文字会话,要传哪些参数;传音频时音频块长什么样;后端开始、出错、关闭、返回字幕增量、返回音频流时又会发什么消息。它还用 serde 做序列化,也就是把 Rust 里的结构变成网络上传的 JSON;用 JsonSchema 和 TS 生成前端也能看的类型说明,减少前后端理解不一致。这里有个重要小零件是 ThreadRealtimeAudioChunk,它是 app-server-protocol 这一层使用的音频块;核心协议里也有一个几乎一样的 CoreRealtimeAudioFrame,所以文件提供了两个互相转换的函数。可以把它理解成海关表格:同一段音频在内部系统和对外接口之间过关时,字段不变,但要换成对方认识的格式。
ThreadRealtimeAudioChunk::from27–42 ↗
fn from(value: CoreRealtimeAudioFrame) -> Self
作用:这个函数把核心协议里的实时音频帧,转换成这个 v2 接口层使用的音频块。有人需要把内部实时音频数据发给客户端时,就会用它来换一层包装。
数据流:进去的是一个 CoreRealtimeAudioFrame,里面有音频数据、采样率、声道数、每个声道的样本数,以及可选的 item_id。函数把这些字段逐个取出来,不改内容,只放进 ThreadRealtimeAudioChunk。出来的是一个接口层音频块,方便继续序列化成客户端能收到的消息。
调用关系:它处在“核心协议到 app-server v2 协议”的边界上。实时系统内部产生音频帧后,需要通过 v2 协议通知客户端时,可以借这个转换把内部类型交给对外消息结构使用;它本身不再调用别的函数,只做字段搬运。
CoreRealtimeAudioFrame::from46–61 ↗
fn from(value: ThreadRealtimeAudioChunk) -> Self
作用:这个函数把 v2 接口层收到的音频块,转换成核心协议能处理的实时音频帧。客户端上传麦克风音频时,服务端需要把外部格式换成内部格式,就会用它。
数据流:进去的是一个 ThreadRealtimeAudioChunk,里面带着音频字符串、采样率、声道数、可选样本数和可选 item_id。函数逐项拆开这些字段,再原样装进 CoreRealtimeAudioFrame。出来的是核心协议的音频帧,后续可以交给内部实时对话引擎处理。
调用关系:它处在“客户端输入到核心实时系统”的入口边界上。v2 协议层解析到客户端追加的音频后,可以用这个转换把数据交给 codex_protocol 的核心实时逻辑;它不负责检查音频内容,也不启动会话,只负责把同一份数据换成内部认识的类型。
V2 账户与配置接口
这些文件涵盖用户/会话状态、模型和应用、权限和配置,以及周边的管理和功能管理协议 payload。
app-server-protocol/src/protocol/v2/account.rs源码 ↗
这个文件主要解决一个问题:账号信息要在服务端、客户端、网页前端之间来回传,如果大家对字段名字和格式理解不一致,就会登录失败、限额显示错、或者前端解析不了。它把账号类型、登录参数、登录结果、账号会话、额度和速率限制等数据都定义成清楚的结构,并用 serde 做 JSON 编解码,用 TS 导出 TypeScript 类型,让前端也能照同一份规则写。文件里还有一些“翻译器”,把核心系统里的账号和限额数据,转成 v2 API 对外公开的样子。比如内部的限额百分比可能是小数,这里会转成客户端更好显示的整数。整体上,它不是执行登录流程的地方,而是规定登录流程中“信封里该装什么”。
default_bedrock_credential_source38–40 ↗
fn default_bedrock_credential_source() -> AmazonBedrockCredentialSource
作用:当客户端声明使用 Amazon Bedrock 账号,但没有说明凭证从哪里来时,这个函数给一个默认值。默认选择 AWS 托管的凭证来源,避免字段缺失导致解析失败。
数据流:进去没有参数 → 它直接选定 AmazonBedrockCredentialSource::AwsManaged 这个默认选项 → 出来一个 Amazon Bedrock 凭证来源值,不改动其他东西。
调用关系:它不是业务流程主动调用的工具,更像表单里的默认答案。serde(负责把 JSON 和程序数据互相转换的库)在读取 Amazon Bedrock 账号数据时,如果发现 credential_source 缺失,就会用它补上。
Account::from43–51 ↗
fn from(account: ProviderAccount) -> Self
作用:把核心系统里的账号表示,翻译成 v2 API 对外使用的账号表示。这样内部怎么存账号可以保持独立,外部客户端只看到稳定的协议格式。
数据流:进去一个 ProviderAccount,可能是 API Key、ChatGPT 账号,或 Amazon Bedrock 账号 → 它按账号种类逐一匹配,把邮箱、套餐类型、凭证来源等字段搬到 v2 的 Account 里 → 出来一个可序列化给客户端的 Account。
调用关系:当服务端需要回复“当前账号是什么”这类请求时,会把内部账号对象交给这个转换函数。它不继续调用别的转换器,只做账号外壳的一对一翻译。
RateLimitSnapshot::from422–435 ↗
fn from(value: CoreRateLimitSnapshot) -> Self
作用:把核心系统里的速率限制快照,翻译成 v2 API 返回给客户端的限额快照。客户端靠它知道自己用了多少、什么时候恢复、是否还有额度。
数据流:进去一个内部的 CoreRateLimitSnapshot,里面可能包含主限额窗口、副限额窗口、点数余额、个人花费限制、套餐和触顶原因 → 它保留基本字段,并把里面的子对象分别转成 v2 版本 → 出来一个 RateLimitSnapshot,适合通过 JSON 发给客户端。
调用关系:它是限额信息对外展示前的总翻译器。它会把具体的小零件交给 RateLimitWindow::from、CreditsSnapshot::from、SpendControlLimitSnapshot::from 和 RateLimitReachedType::from 分别处理。
RateLimitReachedType::from450–466 ↗
fn from(value: CoreRateLimitReachedType) -> Self
作用:把核心系统里的“为什么达到限制了”转换成 v2 API 里的说法。这样客户端可以区分是普通速率限制,还是工作区额度或使用上限耗尽。
数据流:进去一个内部的 CoreRateLimitReachedType 枚举值 → 它按具体原因逐项对应到 v2 的同名含义 → 出来一个 RateLimitReachedType,供 API 响应或通知使用。
调用关系:它通常被 RateLimitSnapshot::from 调用,用来处理限额快照里的触顶原因。它只做原因名称的安全映射,不参与计算限额。
CoreRateLimitReachedType::from470–486 ↗
fn from(value: RateLimitReachedType) -> Self
作用:把 v2 API 里的“达到限制原因”反向翻译回核心系统的表示。需要把客户端或协议层的数据交回内部逻辑时会用到。
数据流:进去一个 v2 的 RateLimitReachedType → 它根据具体枚举项,转换成核心系统里的 CoreRateLimitReachedType → 出来一个内部可识别的触顶原因值。
调用关系:它和 RateLimitReachedType::from 是一正一反的翻译。前者用于对外输出,这个用于把协议层数据带回内部代码,保证两边原因分类一致。
RateLimitWindow::from501–507 ↗
fn from(value: CoreRateLimitWindow) -> Self
作用:把一个内部限额时间窗口转换成客户端能看懂的窗口信息。它说明当前窗口用了百分之多少、窗口多长、什么时候重置。
数据流:进去一个 CoreRateLimitWindow,里面有使用百分比、窗口分钟数和重置时间 → 它把使用百分比四舍五入成整数,并原样带上时长和重置时间 → 出来一个 v2 的 RateLimitWindow。
调用关系:它是 RateLimitSnapshot::from 的子步骤。总快照里如果有主窗口或副窗口,就会交给它转换成对外格式。
CreditsSnapshot::from520–526 ↗
fn from(value: CoreCreditsSnapshot) -> Self
作用:把内部的额度余额信息转换成 v2 API 的额度信息。客户端用它判断账号是否还有可用额度、是否是无限额度、余额显示什么。
数据流:进去一个 CoreCreditsSnapshot,包含是否有额度、是否无限、余额文本 → 它把这些字段直接搬到 v2 的 CreditsSnapshot → 出来一个可发给客户端的额度快照。
调用关系:它通常由 RateLimitSnapshot::from 在转换限额快照时调用。它只负责额度这一小块,不判断额度是否足够。
SpendControlLimitSnapshot::from541–548 ↗
fn from(value: CoreSpendControlLimitSnapshot) -> Self
作用:把内部的个人或工作区花费控制信息转换成 v2 API 的格式。客户端可以据此显示总上限、已用量、剩余比例和重置时间。
数据流:进去一个 CoreSpendControlLimitSnapshot,包含上限、已用、剩余百分比和重置时间 → 它把这些值放进 v2 的 SpendControlLimitSnapshot → 出来一个适合展示和传输的花费控制快照。
调用关系:它是 RateLimitSnapshot::from 的一个配套转换器。总限额快照里如果带有个人限制信息,就会交给它处理。
app-server-protocol/src/protocol/v2/model.rs源码 ↗
这个文件像一张“点菜单和通知单”的模板。服务器和客户端都要知道:一个模型有哪些字段,比如名字、说明、是否隐藏、支持哪些推理强度、能不能收图片、有没有服务档位;请求模型列表时可以传什么参数;返回结果里分页游标该放哪里。它还定义了模型被系统自动改用另一个模型时的通知,以及某些模型使用前需要验证时的通知。文件里的结构体都带了序列化(把程序里的数据变成可传输的 JSON)和反序列化(把 JSON 读回程序数据)的能力,也会生成 JSON Schema(给机器看的格式说明)和 TypeScript 类型(给前端用的类型)。这样前后端不会各说各话,字段名也统一用 camelCase,例如 defaultServiceTier。这里真正的行为很少,主要价值是把协议边界讲清楚,避免接口传错、漏字段或理解不一致。
ModelAvailabilityNux::from63–67 ↗
fn from(value: CoreModelAvailabilityNux) -> Self
作用:这个函数把核心协议里的“模型可用性提示”转换成 v2 接口要返回的格式。简单说,就是把内部版本的小纸条改写成外部接口能看懂的小纸条。
数据流:进去的是一个 CoreModelAvailabilityNux,里面带着提示文字 message;函数取出这段文字,放进当前文件定义的 ModelAvailabilityNux;出来的是一个新的 v2 版本对象,只保留并传递这条提示信息。
调用关系:它是 Rust 的 From 转换接口的一部分,通常在服务器准备把核心层数据包装成 v2 API 响应时自动或手动调用。它不再把工作交给别的函数,只做一次很小的格式搬运,让内部数据和对外协议之间保持隔离。
app-server-protocol/src/protocol/v2/apps.rs源码 ↗
这个文件主要不是做复杂计算,而是在给 v2 协议里的 app 信息立规矩。比如请求应用列表时,可以带分页游标、数量限制、线程 id,也可以要求强制重新拉取;返回时,每个 app 会有名字、描述、图标、安装地址、分类、是否启用等信息。这里还定义了更细的资料,比如品牌信息、审核状态、截图、版本信息,以及列表变化通知。它使用序列化(把程序里的结构变成可传输的 JSON)和反序列化(把 JSON 读回结构)来保证网络传输时不乱套,也会导出给 TypeScript 使用,让前端能拿到同样的类型说明。少量逻辑集中在“分类”上:系统会先看品牌里的分类,如果没有,再从元数据的分类列表里找第一个不为空的分类。最后还能把完整的 AppInfo 缩成 AppSummary,方便插件只拿到必要摘要。
AppInfo::category106–120 ↗
fn category(&self) -> Option<String>
作用:这个函数用来给一个 app 找出最合适的分类。它会优先看品牌信息里的分类;如果那里没有可用内容,再去元数据的分类列表里找一个有效分类。
数据流:进去的是一个 AppInfo,也就是某个 app 的完整资料。函数先读取 branding.category,并把空字符串或全是空格的内容过滤掉;如果找不到,再读取 app_metadata.categories,从列表里挑第一个真正有文字的分类。出来的是一个 Option<String>:有分类就返回分类文字,没有就返回空。
调用关系:它是 AppInfo 自己提供的取分类小工具。AppSummary::from 在把完整 app 信息压缩成摘要时会调用它,这样摘要里的 category 字段不用重复写一套挑选规则。它内部把判断“分类是不是空”的小活交给 non_empty_category。
调用图:被 1 处调用(from)。
non_empty_category123–130 ↗
fn non_empty_category(category: Option<&str>) -> Option<String>
作用:这个函数专门判断一个分类文字能不能算数。它会把前后空格去掉,如果剩下什么都没有,就当作没有分类。
数据流:进去的是一个可能不存在的字符串引用。函数先处理“没有传值”的情况;有值时会去掉首尾空白,再检查是否为空。出来的是一个新的 String,表示有效分类;如果原本没有值或只是空白,就返回 None。
调用关系:它是 AppInfo::category 背后的清洁工,负责把脏数据挡掉。这样无论分类来自品牌信息还是元数据列表,都按同一条规则判断,避免把空字符串当成有效分类传出去。
AppSummary::from145–154 ↗
fn from(value: AppInfo) -> Self
作用:这个函数把一份完整的 AppInfo 变成更短的 AppSummary。有人只需要展示 app 的基本摘要时,就不用带上所有品牌、截图、版本等大堆信息。
数据流:进去的是一个 AppInfo,里面有完整 app 资料。函数先调用 value.category() 算出摘要里要用的分类,然后取出 id、name、description、install_url 和 category 这些核心字段。出来的是一个 AppSummary;原来的 AppInfo 会被消费掉,也就是字段被搬进新摘要里。
调用关系:它通常出现在需要把完整应用列表转换成插件响应或轻量展示数据的时候。它把“分类怎么挑”这件事交给 AppInfo::category,自己只负责把完整资料裁剪成摘要格式。
调用图:调用 1 个内部函数(category)。
app-server-protocol/src/protocol/v2/collaboration_mode.rs源码 ↗
这个文件像一张接口菜单的格式说明书。客户端想知道系统里有哪些“协作模式预设”,比如某个预设用什么模式、什么模型、推理强度是多少,就会用到这里定义的数据结构。这里没有真正去查数据,也不负责业务判断;它主要规定请求参数、单个预设的字段、以及返回结果的外形。CollaborationModeListParams 表示列出预设时的请求参数,目前是空的,意思是这个接口暂时不需要额外条件。CollaborationModeMask 表示一个预设的公开信息,它会从核心配置里的同名数据转换而来。CollaborationModeListResponse 则把多个预设装进 data 列表里返回。文件里还带了序列化和类型导出设置:序列化就是把程序里的结构变成网络上传输的 JSON;类型导出则方便 TypeScript 客户端使用同一套字段定义,减少前后端字段对不上的问题。
CollaborationModeMask::from29–36 ↗
fn from(value: CoreCollaborationModeMask) -> Self
作用:这个函数把核心配置里的协作模式预设,转换成接口层要返回给客户端的预设格式。有人会用它,是因为内部数据和对外接口数据虽然很像,但最好隔开,避免内部改动直接影响客户端。
数据流:进去的是一个核心层的 CoreCollaborationModeMask,里面有名称、模式、模型和推理强度。函数把这些字段逐个搬到协议层的 CollaborationModeMask 里,不做额外计算。出来的是一个可以被接口响应使用、也可以被序列化成 JSON 发给客户端的新对象;原来的输入值会被消耗掉。
调用关系:它位于“内部配置数据”和“对外接口响应”之间,像一个翻译员。列出协作模式预设的流程拿到核心层数据后,会通过这个转换把数据变成协议层对象,然后再放进 CollaborationModeListResponse 这类返回结构里交给客户端。
app-server-protocol/src/protocol/v2/permissions.rs源码 ↗
这份文件像一张“权限翻译表”。外部客户端看到的是适合 JSON 和 TypeScript 使用的 v2 格式,比如哪些路径能读、哪些路径能写、能不能联网、沙箱有多严格;服务器内部用的是 codex_protocol 里的核心格式。文件里的结构体和枚举先把这些权限规则定义清楚,再提供 From、TryFrom、to_core 这类转换函数,把外部格式变成内部格式,或反过来。这里特别重要的是文件路径权限:旧接口还有 read/write 两个列表,新接口改成 entries,每条写清楚“哪个路径”和“允许读、写还是拒绝”。代码会兼容旧格式,同时尽量生成新格式。另一个重点是沙箱策略,也就是程序运行时被关在多大的“笼子”里:只读、工作区可写、完全放开等。它还拒绝一些已经废弃的旧字段,防止客户端继续用会误导人的老配置。
NetworkApprovalContext::from47–52 ↗
fn from(value: CoreNetworkApprovalContext) -> Self
作用:把内部的联网审批上下文转成 v2 接口要发给外部的格式。它保留主机名,并把协议也换成 v2 的枚举写法。
数据流:进去的是核心格式的联网审批信息,里面有 host 和 protocol → 函数逐项拷贝,并把 protocol 调用转换逻辑变成 v2 类型 → 出来的是可以序列化给客户端的 NetworkApprovalContext。
调用关系:它处在“内部事件要展示给客户端”的出口位置。核心层产生联网审批信息后,通过这个转换变成协议 v2 能理解的形状。
AdditionalFileSystemPermissions::from73–124 ↗
fn from(value: CoreFileSystemPermissions<AbsolutePathBuf>) -> Self
作用:把内部的文件系统附加权限转成 v2 接口格式。它还会照顾老版本的 read/write 路径列表,同时补出新版本的 entries 列表。
数据流:进去的是核心文件权限,路径是绝对路径格式 → 如果它能表示成旧式 read/write 根目录,就分别生成 read、write 字段,并同时生成每条带访问模式的 entries;否则直接把核心 entries 一条条转成 v2 entries,并保留 glob_scan_max_depth → 出来的是客户端能读懂的 AdditionalFileSystemPermissions。
调用关系:它常用于测试和对外返回权限信息,比如验证旧式根目录能正确填充 entries、验证规范化的 entries 不丢失。它调用核心权限的 legacy_read_write_roots 来判断能否兼容旧格式,并用 with_capacity 预先给 entries 留空间。
调用图:被 2 处调用(additional_file_system_permissions_populates_entries_for_legacy_roots, additional_file_system_permissions_preserves_canonical_entries);外部调用 2 个(legacy_read_write_roots, with_capacity)。
CoreFileSystemPermissions::try_from131–171 ↗
fn try_from(value: AdditionalFileSystemPermissions) -> Result<Self, Self::Error>
作用:把 v2 接口传来的文件系统权限转回内部核心格式。因为路径字符串可能不合法,所以它可能失败并返回输入错误。
数据流:进去的是 AdditionalFileSystemPermissions,可能有新式 entries,也可能只有旧式 read/write → 如果有 entries,就逐条把路径和访问模式转成核心格式;如果没有 entries,就把 read/write 字符串按本机路径规则解析成绝对路径,再交给核心的 from_read_write_roots 组装 → 出来是核心文件权限;如果路径解析失败,就出来一个 io::Error。
调用关系:它位于“客户端请求进入服务器”的入口附近。RequestPermissionProfile、AdditionalPermissionProfile、GrantedPermissionProfile 转核心格式时都会依赖同样的文件权限转换思路。
调用图:外部调用 1 个(from_read_write_roots)。
AdditionalNetworkPermissions::from182–186 ↗
fn from(value: CoreNetworkPermissions) -> Self
作用:把内部的网络权限转成 v2 接口格式。它只关心一个简单问题:网络是否启用。
数据流:进去的是 CoreNetworkPermissions,里面有 enabled → 函数把 enabled 原样放进 v2 结构 → 出来的是 AdditionalNetworkPermissions。
调用关系:它被更大的权限配置转换使用,比如请求权限配置或附加权限配置从核心格式变成对外格式时,会顺手调用它。
CoreNetworkPermissions::from190–194 ↗
fn from(value: AdditionalNetworkPermissions) -> Self
作用:把 v2 接口收到的网络权限转成内部核心格式。它用于服务器真正执行权限判断前的准备工作。
数据流:进去的是 AdditionalNetworkPermissions,里面可能写了 enabled,也可能没写 → 函数把这个值原样放进核心网络权限 → 出来的是 CoreNetworkPermissions。
调用关系:它常被请求权限、附加权限、已授予权限这些更大对象的转换调用,负责其中“网络权限”这一小块。
RequestPermissionProfile::from208–213 ↗
fn from(value: CoreRequestPermissionProfile) -> Self
作用:把内部的“请求用权限配置”转成 v2 接口格式。它把网络和文件系统两部分分别翻译出来。
数据流:进去的是 CoreRequestPermissionProfile,里面的 network 和 file_system 都是可选的 → 有网络权限就转成 AdditionalNetworkPermissions,有文件权限就转成 AdditionalFileSystemPermissions → 出来的是 RequestPermissionProfile。
调用关系:它用于把核心层已有的请求权限展示或发送给 v2 客户端。文件系统部分会继续交给 AdditionalFileSystemPermissions::from。
CoreRequestPermissionProfile::try_from219–227 ↗
fn try_from(value: RequestPermissionProfile) -> Result<Self, Self::Error>
作用:把 v2 客户端提交的请求权限配置转成内部核心格式。它会检查文件路径是否合法,所以可能失败。
数据流:进去的是 RequestPermissionProfile → 网络部分直接转换;文件系统部分如果存在,就尝试转成核心文件权限,并把路径解析错误往外返回 → 出来是 CoreRequestPermissionProfile,或者一个 io::Error。
调用关系:它位于权限请求进入核心系统之前。网络转换交给 CoreNetworkPermissions::from,文件系统转换交给 CoreFileSystemPermissions::try_from。
FileSystemSpecialPath::from258–267 ↗
fn from(value: CoreFileSystemSpecialPath) -> Self
作用:把内部的特殊路径标记转成 v2 接口里的特殊路径标记。特殊路径指的不是普通字符串路径,而是“根目录”“项目根目录”“临时目录”这类约定位置。
数据流:进去的是 CoreFileSystemSpecialPath 的某个变体 → 函数按同名含义逐个匹配,保留 subpath、path 等附带信息 → 出来的是 v2 的 FileSystemSpecialPath。
调用关系:它被 FileSystemPath::from 间接使用。当一条文件权限指向特殊位置时,要先用这个函数把特殊位置翻译给客户端。
CoreFileSystemSpecialPath::from271–280 ↗
fn from(value: FileSystemSpecialPath) -> Self
作用:把 v2 接口里的特殊路径标记转回内部核心格式。它让客户端提交的“项目根目录”“临时目录”等说法能被核心权限系统理解。
数据流:进去的是 FileSystemSpecialPath → 函数按变体逐个转换,附带的 subpath 或 unknown path 原样带过去 → 出来的是 CoreFileSystemSpecialPath。
调用关系:它被 CoreFileSystemPath::try_from 间接使用。只要客户端传的是 Special 类型路径,就会走到这里。
FileSystemPath::from296–306 ↗
fn from(value: CoreFileSystemPath<AbsolutePathBuf>) -> Self
作用:把内部的文件路径规则转成 v2 接口格式。它支持三种路径:真实路径、通配模式、特殊路径。
数据流:进去的是 CoreFileSystemPath,路径内容可能是绝对路径、glob 通配模式,或特殊路径 → 普通绝对路径会转成旧应用路径字符串,通配模式原样保留,特殊路径继续转换成 v2 特殊路径 → 出来的是 FileSystemPath。
调用关系:它被 FileSystemSandboxEntry::from 使用。也就是说,每条沙箱权限记录在发给客户端前,都会先把里面的路径通过这里翻译好。
调用图:调用 1 个内部函数(from_abs_path);外部调用 1 个(into)。
CoreFileSystemPath::try_from313–326 ↗
fn try_from(value: FileSystemPath) -> Result<Self, Self::Error>
作用:把 v2 接口传来的文件路径规则转成内部核心格式。普通路径需要按本机路径习惯解析,所以这一步可能报错。
数据流:进去的是 FileSystemPath → 如果是 Path,就把 LegacyAppPathString 按 native 本机路径规则转成路径 URI,再转成绝对路径;如果是 GlobPattern,就保留模式字符串;如果是 Special,就转成核心特殊路径 → 出来的是 CoreFileSystemPath,或者路径解析错误。
调用关系:它被 CoreFileSystemSandboxEntry::try_from 使用。客户端提交每条文件沙箱规则时,里面的 path 都要先经过它变成核心能执行的形式。
FileSystemSandboxEntry::from339–344 ↗
fn from(value: CoreFileSystemSandboxEntry<AbsolutePathBuf>) -> Self
作用:把内部的一条文件沙箱规则转成 v2 接口格式。一条规则就是“这个路径,用这种访问权限”。
数据流:进去的是核心沙箱条目,包含 path 和 access → path 转成 v2 路径格式,access 转成 v2 访问模式 → 出来的是 FileSystemSandboxEntry。
调用关系:它被 AdditionalFileSystemPermissions::from 用来批量生成 entries。它把单条规则的转换封装起来,避免上层关心路径和访问模式的细节。
CoreFileSystemSandboxEntry::try_from350–355 ↗
fn try_from(value: FileSystemSandboxEntry) -> Result<Self, Self::Error>
作用:把 v2 的一条文件沙箱规则转回内部核心格式。它会解析路径,并把读、写、拒绝这些访问模式换成核心表示。
数据流:进去的是 FileSystemSandboxEntry → path 尝试转换成核心路径,access 调用 to_core 转成核心访问模式 → 出来的是 CoreFileSystemSandboxEntry;如果路径不合法,就返回错误。
调用关系:它被 CoreFileSystemPermissions::try_from 在处理 entries 列表时批量调用,是客户端文件权限落到核心系统前的单条转换关口。
ActivePermissionProfile::new407–412 ↗
fn new(id: impl Into<String>) -> Self
作用:新建一个当前启用的权限配置,只填配置 id,不填父配置。适合只想说明“现在选中了哪个权限档位”的场景。
数据流:进去的是任何能变成字符串的 id → 函数把它转成 String,extends 设为 None → 出来的是 ActivePermissionProfile。
调用关系:它被很多会话和权限状态相关流程使用,例如选择活动权限配置、同步线程设置、嵌入式 turn 权限等。它是创建活动权限快照时最简单的入口。
调用图:被 24 处调用(permission_snapshot_setter_preserves_permission_constraints, session_configuration_apply_rebinds_symbolic_profile_to_updated_workspace_roots, active_profile_selection_uses_profile_id_only, auto_review_mode, override_turn_context_sends_thread_settings_update, permission_settings_sync_updates_active_snapshot_without_rewriting_side_thread, embedded_turn_permissions_select_profile_id_only, embedded_turn_permissions_use_active_profile_selection, remote_turn_permissions_preserve_active_profile_selection, submission_includes_configured_active_permission_profile (+14 more));外部调用 1 个(into)。
ActivePermissionProfile::read_only414–416 ↗
fn read_only() -> Self
作用:生成一个“只读”权限配置。只读意味着默认不允许随便写文件,适合更安全的运行模式。
数据流:没有外部输入 → 函数先让核心层生成只读配置,再把它转成 v2 的 ActivePermissionProfile → 出来的是当前活动的只读权限配置。
调用关系:它在测试、状态显示、线程设置通知等场景会用到,用来快速得到内置的只读权限档位。
调用图:被 4 处调用(inactive_thread_settings_notification_updates_cached_collaboration_mode, thread_settings_for_test, status_permissions_named_read_only_profile_shows_builtin_label, status_permissions_read_only_profile_shows_additional_writable_roots);外部调用 1 个(read_only)。
ActivePermissionProfile::from420–425 ↗
fn from(value: CoreActivePermissionProfile) -> Self
作用:把内部当前启用的权限配置转成 v2 接口格式。它保留配置 id 和可能存在的父配置 id。
数据流:进去的是 CoreActivePermissionProfile → 函数复制 id 和 extends → 出来的是 ActivePermissionProfile。
调用关系:它通常用于把核心会话状态发给客户端,让客户端知道当前实际选中的权限配置。
CoreActivePermissionProfile::from429–434 ↗
fn from(value: ActivePermissionProfile) -> Self
作用:把 v2 的当前权限配置转回内部核心格式。这样外部传入或缓存的选择能被核心系统继续使用。
数据流:进去的是 ActivePermissionProfile → 函数复制 id 和 extends → 出来的是 CoreActivePermissionProfile。
调用关系:它和 ActivePermissionProfile::from 是一对反向翻译,负责在 v2 协议层和核心层之间保持活动配置一致。
AdditionalPermissionProfile::from448–453 ↗
fn from(value: CoreAdditionalPermissionProfile) -> Self
作用:把内部的“附加权限配置”转成 v2 接口格式。附加权限通常是某次命令临时多要一点网络或文件权限。
数据流:进去的是 CoreAdditionalPermissionProfile → network 如果存在就转成 v2 网络权限,file_system 如果存在就转成 v2 文件权限 → 出来的是 AdditionalPermissionProfile。
调用关系:它用于核心层把临时权限需求或权限叠加结果展示给外部。文件系统部分继续依赖 AdditionalFileSystemPermissions::from。
CoreAdditionalPermissionProfile::try_from485–493 ↗
fn try_from(value: GrantedPermissionProfile) -> Result<Self, Self::Error>
作用:把 v2 的附加权限配置转成内部核心格式。它会验证文件路径,因此可能失败。
数据流:进去的是 AdditionalPermissionProfile → 网络部分直接转核心格式;文件系统部分如果有,就尝试解析路径并转核心权限 → 出来的是 CoreAdditionalPermissionProfile,或者路径相关的 io::Error。
调用关系:它用于客户端请求临时加权限时,把外部描述转成核心真正能判断和执行的权限规则。
SandboxPolicy::deserialize576–616 ↗
fn deserialize(deserializer: D) -> Result<Self, D::Error>
作用:自定义读取沙箱策略的 JSON。它既能读当前格式,也会明确拒绝一些已经废弃且不再支持的旧字段。
数据流:进去的是 JSON 反序列化器 → 先读成内部辅助枚举 SandboxPolicyDeserialize;如果发现旧字段 access/readOnlyAccess 是 Restricted,就返回清楚的错误;其他情况按类型组装成正式的 SandboxPolicy → 出来是沙箱策略,或者一条告诉用户该改用 permissionProfile 的错误。
调用关系:它在客户端或配置把沙箱策略传进来时自动触发。它调用 serde 的 deserialize 读 JSON,并用 custom 生成友好的错误信息。
调用图:外部调用 3 个(deserialize, custom, matches!)。
SandboxPolicy::to_core620–650 ↗
fn to_core(&self) -> codex_protocol::protocol::SandboxPolicy
作用:把 v2 沙箱策略转成核心沙箱策略。核心层真正运行命令、限制文件和网络访问时,需要用核心格式。
数据流:进去的是 SandboxPolicy 的引用 → 函数按策略种类匹配:完全访问、只读、外部沙箱、工作区可写;同时转换网络访问枚举并复制可写根目录和排除选项 → 出来的是 codex_protocol::protocol::SandboxPolicy。
调用关系:它被显示权限配置等流程调用。它是 v2 协议描述进入核心运行策略前的翻译口。
调用图:被 1 处调用(display_permission_profile_from_thread_response)。
SandboxPolicy::from654–682 ↗
fn from(value: codex_protocol::protocol::SandboxPolicy) -> Self
作用:把核心沙箱策略转成 v2 接口格式。这样服务器内部的实际沙箱状态可以返回给客户端显示或保存。
数据流:进去的是核心 SandboxPolicy → 函数按策略种类逐一匹配,网络访问从核心枚举转成 v2 枚举,路径和布尔选项原样带出 → 出来的是 v2 的 SandboxPolicy。
调用关系:它被多个沙箱往返测试和会话配置同步流程使用,用来确认核心策略转到 v2 后不会丢信息。
调用图:被 5 处调用(sandbox_policy_round_trips_external_sandbox_network_access, sandbox_policy_round_trips_read_only_network_access, sandbox_policy_round_trips_workspace_write_access, session_configured_external_sandbox_keeps_external_runtime_policy, session_configured_syncs_widget_config_permissions_and_cwd)。
ExecPolicyAmendment::into_core693–695 ↗
fn into_core(self) -> CoreExecPolicyAmendment
作用:把 v2 的执行策略补丁转成核心格式。执行策略补丁这里就是一段命令前缀,用来表示允许或调整某类命令执行规则。
数据流:进去的是 ExecPolicyAmendment,里面有 command 字符串数组 → 函数把 command 交给 CoreExecPolicyAmendment::new → 出来的是核心执行策略补丁。
调用关系:它用于客户端提交执行策略变更时,把外部数组形式变成核心策略对象。
调用图:外部调用 1 个(new)。
ExecPolicyAmendment::from699–703 ↗
fn from(value: CoreExecPolicyAmendment) -> Self
作用:把核心执行策略补丁转成 v2 格式。它把核心对象里的命令前缀取出来,变成普通字符串数组。
数据流:进去的是 CoreExecPolicyAmendment → 函数调用 command 读取命令切片,并复制成 Vec<String> → 出来的是 ExecPolicyAmendment。
调用关系:它被执行策略补丁相关测试使用,例如检查追加补丁后策略和文件是否正确更新,也用于把核心策略变化展示给协议层。
调用图:被 2 处调用(append_execpolicy_amendment_rejects_empty_prefix, append_execpolicy_amendment_updates_policy_and_file);外部调用 1 个(command)。
NetworkPolicyAmendment::into_core721–726 ↗
fn into_core(self) -> CoreNetworkPolicyAmendment
作用:把 v2 的网络策略补丁转成核心格式。它描述的是某个主机应该被允许还是拒绝访问。
数据流:进去的是 NetworkPolicyAmendment,包含 host 和 action → 函数保留 host,并把 action 调用 to_core 转成核心动作 → 出来的是 CoreNetworkPolicyAmendment。
调用关系:它用于客户端批准或拒绝某个网络访问规则后,把决定送进核心网络策略系统。
调用图:外部调用 1 个(to_core)。
NetworkPolicyAmendment::from730–735 ↗
fn from(value: CoreNetworkPolicyAmendment) -> Self
作用:把核心网络策略补丁转成 v2 格式。它让服务器能把内部网络规则变化用客户端熟悉的字段发出去。
数据流:进去的是 CoreNetworkPolicyAmendment → 函数复制 host,并把核心 action 转成 v2 的 NetworkPolicyRuleAction → 出来的是 NetworkPolicyAmendment。
调用关系:它是 NetworkPolicyAmendment::into_core 的反向翻译,服务于核心网络策略和 v2 协议之间的信息同步。
调用图:外部调用 1 个(from)。
app-server-protocol/src/protocol/v2/config.rs源码 ↗
这份文件本身不怎么“干活”,更像一本配置协议的字典。系统里的配置可能来自很多地方:公司管控、系统文件、用户文件、项目里的 .codex 文件夹、命令行临时参数等。这个文件把这些来源、优先级、读配置的结果、写配置的参数、写失败的原因、网络限制、沙箱限制、工具开关、迁移外部 agent 配置等,都定义成 Rust 类型。Rust 类型可以理解成固定格式的表单,哪些格子能填、填什么类型,都写清楚。它还用 serde 做序列化和反序列化,也就是把程序里的数据和 JSON 这类传输格式互相转换;用 JsonSchema 和 TS 生成接口说明和 TypeScript 类型,方便网页端或其他客户端安全地调用。一个重要点是配置是分层的:后面的高优先级层会盖掉前面的低优先级层,就像多张透明胶片叠在一起,最上面的内容最显眼。
ConfigLayerSource::precedence102–119 ↗
fn precedence(&self) -> i16
作用:给某个配置来源算一个优先级数字。数字越大,表示这一层配置越靠上,越能覆盖别的配置。
数据流:进去的是一个配置来源,比如公司 MDM、系统配置、用户配置、项目配置或命令行参数。它根据来源种类返回一个固定数字;如果是用户配置,还会区分普通用户配置和用户选择的 profile 配置。出来的是一个 i16 数字,供后续比较谁覆盖谁使用;它不修改任何数据。
调用关系:它是配置分层排序的基础小工具。ConfigLayerSource::partial_cmp 在比较两个配置来源时会调用它两次,分别拿到两边的优先级,再决定谁在前谁在后。
调用图:被 1 处调用(partial_cmp)。
ConfigLayerSource::partial_cmp125–127 ↗
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering>
作用:让两个配置来源可以按优先级比较大小。这里的“小于”不是数学意义上的好坏,而是表示前者会被后者覆盖。
数据流:进去的是两个 ConfigLayerSource。它分别调用 ConfigLayerSource::precedence 和另一个对象的 precedence,拿到两个优先级数字,再比较这两个数字。出来的是比较结果,比如小于、等于或大于;它不改动配置来源本身。
调用关系:它把 ConfigLayerSource 接进 Rust 的比较机制里。任何需要排序配置层、判断哪层生效的地方,都可以用这个比较规则;具体优先级数字的判断工作交给 ConfigLayerSource::precedence。
调用图:调用 1 个内部函数(precedence);外部调用 1 个(precedence)。
ForcedChatgptWorkspaceIds::into_vec232–237 ↗
fn into_vec(self) -> Vec<String>
作用:把“强制使用的 ChatGPT 工作区 ID”统一变成列表。这样后面的代码不用再关心原来传的是单个 ID 还是多个 ID。
数据流:进去的是 ForcedChatgptWorkspaceIds,可能是 Single,一个字符串;也可能是 Multiple,一组字符串。它如果看到单个字符串,就把它包成只有一个元素的 Vec;如果本来就是多个,就原样拿出那组值。出来的是 Vec<String>,也就是字符串列表;这个函数会消耗传入的值,但不影响别的状态。
调用关系:它服务于向后兼容:老接口可能只给一个工作区 ID,新接口可能给多个。读取或校验登录限制的代码可以先调用它,把两种形状统一成列表,再继续处理。函数内部只用到标准的 vec! 宏来创建单元素列表。
调用图:外部调用 1 个(vec!)。
app-server-protocol/src/protocol/v2/experimental_feature.rs源码 ↗
这个文件本身不做计算,也不发网络请求;它像一张清楚的表格模板,规定了查询和修改实验性功能时,请求里能带什么、响应里会返回什么。所谓实验性功能,就是还在试用、开发、稳定、废弃等不同阶段的开关功能。这里定义了分页查询参数,比如 cursor(分页游标,可以理解成“从上次看到的位置继续”)、limit(一次拿多少条)、thread_id(让服务器按某个已有会话的配置来判断开关是否开启)。它还定义了每个功能的名字、阶段、给用户看的说明、当前是否开启、默认是否开启。最后还有一组用于批量设置开关状态的数据结构,用 BTreeMap 保存“功能名 → 开或关”。这些类型都带有序列化、反序列化、JSON Schema 和 TypeScript 导出能力,意思是同一份定义可以同时服务 Rust 后端、接口文档校验和前端代码。
app-server-protocol/src/protocol/v2/plugin.rs源码 ↗
这个文件本身不去安装插件、扫描文件或联网下载东西,它主要规定“请求里该带什么、响应里会返回什么”。比如列出技能时可以带工作目录和是否强制刷新;插件市场会返回市场名、路径、插件摘要;分享插件会有可见性、分享对象和权限;安装插件会告诉前端是否需要授权。所有结构都支持序列化,也就是能变成 JSON 在接口里传来传去;还会生成 JSON Schema 和 TypeScript 类型,方便前端少猜字段、少写错。文件末尾有几段转换函数,把底层 codex_protocol 里的技能数据,搬成 app-server v2 对外暴露的格式。一个重要点是:这里很多字段是可选的,因为本地插件、远程插件、共享插件能拿到的信息不一样;这让同一套接口能覆盖多种来源,而不会因为缺一个图标或路径就失败。
SkillMetadata::from800–811 ↗
fn from(value: CoreSkillMetadata) -> Self
作用:把底层系统里的技能说明,转换成 v2 接口要返回给客户端的技能说明。有人列出技能或读取插件详情时,需要这种对外格式。
数据流:进去的是一个 CoreSkillMetadata,里面有技能名、描述、短描述、界面信息、依赖、路径和作用范围。它把这些字段逐个搬到新的 SkillMetadata 里;遇到嵌套的界面信息、依赖信息、作用范围时,再交给对应的转换函数处理。出来的是一个 v2 的 SkillMetadata,并且 enabled 会被设成 true,表示这个从核心数据转来的技能默认是启用状态。
调用关系:它站在“核心协议数据”和“app-server v2 API 数据”之间做翻译。转换过程中,它会调用 SkillInterface::from、SkillDependencies::from 和 SkillScope::from,把内部小零件也一起换成 v2 能对外说清楚的形状。
SkillInterface::from815–824 ↗
fn from(value: CoreSkillInterface) -> Self
作用:把底层技能的展示信息转换成 v2 接口的展示信息。这里的展示信息包括显示名、短介绍、品牌颜色、默认提示词和图标路径。
数据流:进去的是 CoreSkillInterface,里面是技能给用户看的外观和提示内容。函数不改含义,只把 display_name、short_description、brand_color、default_prompt、icon_small、icon_large 这些字段原样放进新的 SkillInterface。出来的是 v2 接口可直接返回的 SkillInterface。
调用关系:它通常被 SkillMetadata::from 使用。也就是说,当整个技能元数据被翻译成 v2 格式时,里面那块“界面展示卡片”会交给这个函数单独翻译。
SkillDependencies::from828–836 ↗
fn from(value: CoreSkillDependencies) -> Self
作用:把底层技能依赖的工具清单转换成 v2 接口的依赖清单。依赖可以理解成:这个技能想正常工作,还需要哪些外部工具或服务。
数据流:进去的是 CoreSkillDependencies,里面有一组 tools。函数会逐个拿出工具依赖,把每一项交给 SkillToolDependency::from 转成 v2 格式,然后收集成新的列表。出来的是 SkillDependencies,里面装着转换后的工具依赖数组。
调用关系:它被 SkillMetadata::from 调用,用来处理技能元数据里的 dependencies 字段。它自己又把每个具体工具依赖交给 SkillToolDependency::from,像流水线一样一层层拆开翻译。
SkillToolDependency::from840–849 ↗
fn from(value: CoreSkillToolDependency) -> Self
作用:把一个具体的工具依赖转换成 v2 接口格式。比如技能可能依赖某个命令、某个地址,或某种传输方式。
数据流:进去的是 CoreSkillToolDependency,里面有依赖类型、值、说明、传输方式、命令和 URL。函数把这些字段一一复制到新的 SkillToolDependency;其中 type 在 Rust 里是关键字,所以代码里用 r#type 表示,但对外 JSON 仍叫 type。出来的是单个 v2 工具依赖对象。
调用关系:它是依赖转换里最底层的小翻译员。SkillDependencies::from 会遍历所有工具依赖,并为每一项调用它,最后组成完整的依赖列表。
SkillScope::from853–860 ↗
fn from(value: CoreSkillScope) -> Self
作用:把底层技能的作用范围转换成 v2 接口里的作用范围。作用范围说明这个技能属于用户、仓库、系统还是管理员级别。
数据流:进去的是 CoreSkillScope,可能是 User、Repo、System 或 Admin。函数用一一对应的规则把它换成本文件里的 SkillScope。出来的是 v2 API 使用的 SkillScope 枚举值,含义保持不变。
调用关系:它被 SkillMetadata::from 调用,用来翻译技能元数据里的 scope 字段。这样客户端看到的是 app-server v2 自己定义的枚举,而不是底层协议里的类型。
app-server-protocol/src/protocol/v2/hook.rs源码 ↗
这里的“钩子 Hook”可以理解成系统在某些关键时刻自动触发的小动作,比如工具运行前、用户提交提示词后、会话开始时等。这个文件不负责真正执行钩子,而是负责描述钩子的名字、来源、状态、输出内容,以及开始/完成通知的格式。它像一张统一表格:内部核心模块可能有自己的数据类型,但对外发布时必须按 v2 协议说话,所以这里把核心类型转换成协议类型。文件里还给这些类型加了序列化能力,也就是能变成 JSON;同时生成 JSON Schema 和 TypeScript 类型,方便网页端或其他客户端照着同一份格式开发。一个重要细节是 source 字段有默认值 Unknown,这样旧数据或缺字段的数据也不至于解析失败。
default_hook_source64–66 ↗
fn default_hook_source() -> HookSource
作用:当一条钩子记录没有说明来源时,这个函数给它补上一个安全的默认来源:Unknown,也就是“不知道来自哪里”。这样可以避免旧版本数据少了字段时直接报错。
数据流:它不需要任何输入,也不读取外部信息;被调用时直接生成 HookSource::Unknown;结果是给反序列化过程,也就是把 JSON 读回程序对象时,填上一个默认值。
调用关系:它主要服务于 HookRunSummary 里的 source 字段。serde 这个序列化/反序列化工具在发现输入数据缺少 source 时,会按字段上的标记来调用它,让 HookRunSummary 仍然能被正常创建。
HookOutputEntry::from89–94 ↗
fn from(value: CoreHookOutputEntry) -> Self
作用:这个函数把核心模块里的单条钩子输出,转换成 v2 协议要对外展示的输出格式。比如一条警告、错误、反馈文字,都会经过这里换成客户端能识别的样子。
数据流:输入是一条 CoreHookOutputEntry,里面有输出类型 kind 和文字 text;函数把 kind 转成 v2 的 HookOutputEntryKind,并原样保留 text;输出是一条新的 HookOutputEntry,不会改动原来的核心数据。
调用关系:它是核心数据通往 v2 协议数据的小转换器。HookRunSummary::from 在转换整次钩子运行记录时,会逐条处理 entries,并把每条输出交给这个函数完成格式转换。
HookRunSummary::from119–136 ↗
fn from(value: CoreHookRunSummary) -> Self
作用:这个函数把核心模块里的一整次钩子运行摘要,转换成 v2 协议里的运行摘要。客户端想知道某个钩子什么时候开始、是否成功、输出了什么,最终看到的就是这种格式。
数据流:输入是一份 CoreHookRunSummary,包含编号、事件名、处理器类型、执行方式、作用范围、来源路径、状态、时间、耗时和输出列表;函数把其中的枚举字段逐个转成 v2 版本,把普通字段直接搬过去,并把输出列表里的每一项转成 HookOutputEntry;输出是一份完整的 HookRunSummary,用于后续序列化成 JSON 或放进通知消息。
调用关系:它位在内部核心逻辑和外部协议之间。核心系统生成钩子运行结果后,需要发出 HookStartedNotification 或 HookCompletedNotification 这类通知时,就会先通过这个函数把内部摘要换成对外协议摘要;转换输出列表时,它又会把每条记录交给 HookOutputEntry::from。
app-server-protocol/src/protocol/v2/feedback.rs源码 ↗
当用户提交反馈时,服务器需要知道反馈类型、原因、对应的对话线程、要不要附带日志、额外日志文件在哪里、还有一些标签信息。这个文件没有写具体怎么上传,也不处理网络请求,它只规定数据长什么样。FeedbackUploadParams 是客户端发给服务器的“反馈表单”:classification 表示反馈分类,reason 是可选原因,thread_id 可选,用来说明这条反馈属于哪个会话;include_logs 决定是否带上日志;extra_log_files 可以列出额外日志文件;tags 是一些键值对标签。FeedbackUploadResponse 是服务器回给客户端的结果,目前只包含 thread_id,表示这次反馈关联到哪个线程。文件还通过 serde、JsonSchema、TS 这些工具,让同一份结构能用于 JSON 编码、接口文档和 TypeScript 类型生成,减少两边格式不一致的风险。
app-server-protocol/src/protocol/v2/attestation.rs源码 ↗
这个文件本身不做计算,也不发网络请求,它只是规定两种消息格式。可以把它理解成一张接口表格:调用“生成证明 token”这个功能时,请求参数 AttestationGenerateParams 目前是空的,表示调用方不需要额外提供东西;返回值 AttestationGenerateResponse 里有一个 token 字符串,也就是一段不透明的客户端证明令牌。“不透明”意思是拿到它的人通常只负责传递或提交,不应该自己拆开理解里面内容。文件还用 serde 做序列化和反序列化,也就是把 Rust 数据和 JSON 互相转换;用 JsonSchema 生成接口结构说明;用 TS 导出 TypeScript 类型,方便前端或其他 TypeScript 项目使用同样的定义。字段名统一转成 camelCase,也就是类似 tokenName 这种 JSON 常见写法。
app-server-protocol/src/protocol/v2/environment.rs源码 ↗
这个文件本身不执行操作,而是规定数据长什么样。它定义了两个结构体,也就是两种固定格式的数据。EnvironmentAddParams 是客户端想添加一个环境时要交给服务端的信息:环境的编号 environmentId,以及执行服务器的地址 execServerUrl。EnvironmentAddResponse 是服务端处理完以后返回的结果,目前里面没有字段,意思是只要请求成功就够了,不需要额外带回数据。这里还用到了序列化和反序列化,也就是把程序里的数据和网络上传输的 JSON 文本互相转换;同时生成 JSON Schema 和 TypeScript 类型,让不同语言写的前端、后端、工具都能用同一份“说明书”,减少字段拼错、格式不一致这类问题。
app-server-protocol/src/protocol/v2/remote_control.rs源码 ↗
远程控制功能需要很多来回消息:比如用户要开启远程控制、服务器要通知当前是否已连接、客户端要用配对码绑定、还要查看哪些设备曾经连过。这个文件不真正去连接网络,也不执行远程控制动作,它主要定义这些消息的“包装盒”。每个结构体就是一种包装盒,里面写清楚要带哪些字段,比如服务器名、安装编号、环境编号、连接状态、配对码、设备信息等。这里还用到了序列化(把程序里的数据变成可传输的 JSON 等格式)和 TypeScript 类型导出(让前端也能拿到同一套字段定义),这样后端和前端不会因为字段名写错或含义不一致而出问题。几个可选字段用 Option 表示,也就是“可能有,也可能没有”。连接状态用枚举列出:禁用、连接中、已连接、出错,避免用随便的字符串造成误解。
RemoteControlEnableResponse::from169–182 ↗
fn from(notification: RemoteControlStatusChangedNotification) -> Self
作用:这个函数把一条“远程控制状态已变化”的通知,直接变成“开启远程控制”的返回结果。这样开启操作完成后,可以复用同一份状态信息,不用手动一项项重新拼。
数据流:进去的是一个状态通知,里面有连接状态、服务器名、安装编号和可能存在的环境编号。函数把这些字段拆出来,原样放进一个新的开启响应对象里。出来的是 RemoteControlEnableResponse;原来的通知被消费掉,没有额外修改别的地方。
调用关系:当开启远程控制的流程需要返回结果时会用到它,例如 serve_enable_remote_control_scenario 和 enable 会调用它。它处在“状态已经知道了,把状态整理成 API 返回值”的最后一步,不再把工作交给其他函数。
调用图:被 2 处调用(serve_enable_remote_control_scenario, enable)。
RemoteControlDisableResponse::from186–199 ↗
fn from(notification: RemoteControlStatusChangedNotification) -> Self
作用:这个函数把一条“远程控制状态已变化”的通知,变成“关闭远程控制”的返回结果。它让关闭操作也能用同一套状态数据来回答调用方。
数据流:进去的是一个状态通知,包含当前状态、服务器名、安装编号和可选的环境编号。函数把这些内容取出来,装进一个新的关闭响应对象。出来的是 RemoteControlDisableResponse;它只是转换数据形状,不做网络请求,也不改变远程控制本身。
调用关系:当关闭远程控制流程结束、需要把最新状态返回给调用方时会用到它,例如 disable 和 disable_remote_control_retries_without_params_for_older_servers 会调用它。它的角色是把内部通知翻译成外部接口的关闭响应。
调用图:被 2 处调用(disable_remote_control_retries_without_params_for_older_servers, disable)。
V2 执行与主机集成
此组定义用于命令执行、进程、文件系统访问、MCP 集成,以及 sandbox 专用主机交互的操作性 RPC。
app-server-protocol/src/protocol/v2/command_exec.rs源码 ↗
这个文件本身不真正运行命令,而是定义协议里的数据形状。客户端如果想让服务器在沙箱里跑一条独立命令,就要按这里的 CommandExecParams 填好命令、工作目录、环境变量、超时、输出限制、是否使用终端等信息。沙箱可以理解成一个受控的小房间,用来限制命令能做什么,避免它乱改系统。命令运行完后,CommandExecResponse 描述最终退出码和收集到的标准输出、错误输出。如果是交互式或流式执行,还可以用 processId 继续写入输入、改终端大小、终止进程,并通过 outputDelta 通知一段段收到输出。这里还特别规定:流式输出会用 base64 编码,也就是把原始字节包装成适合放进文本消息里的形式;如果连接断了,相关进程也会被结束,避免后台遗留失控任务。
app-server-protocol/src/protocol/v2/process.rs源码 ↗
这个文件本身不真正启动进程,也不读写终端;它是在定义协议里的数据形状。比如客户端想让服务器运行一个命令,就要用 ProcessSpawnParams 这类结构说明命令、工作目录、环境变量、是否开伪终端等。伪终端(PTY,可以理解成给程序配一个像真实命令行窗口一样的环境)还可以带 rows 和 cols,表示窗口高宽。进程启动后,服务器会用通知告诉客户端输出了哪些内容、是否退出、退出码是多少。为了传二进制内容,这里用 base64(一种把原始字节变成普通文本的编码方式)包装输入输出。文件还把“输出来自 stdout 还是 stderr”这种选择写成枚举,减少写错字符串的风险。它的重要性在于:前后端、不同语言生成的代码、JSON Schema 校验,都能按同一套规则理解 process 相关消息。
app-server-protocol/src/protocol/v2/fs.rs源码 ↗
这个文件像一张文件操作的“表格模板合集”。服务器可能需要替客户端读文件、写文件、建目录、查文件信息、列目录、复制、删除,甚至监听某个文件夹有没有变化。为了让这些操作在网络或进程之间传来传去不出错,这里把每种操作的输入和输出都定义成结构体。比如读文件时,输入是一个绝对路径,输出是用 Base64(一种把二进制内容变成普通文字的编码方式)表示的文件内容;写文件则反过来,收到路径和 Base64 内容。这里还特别使用绝对路径类型,减少“相对路径到底从哪里算起”的误会。每个结构体都支持序列化和反序列化,也就是能在程序对象和可传输文本之间互相转换;还会生成 JSON Schema(给机器检查格式用的说明书)和 TypeScript 类型(给前端或其他 TS 代码用的类型定义)。所以它的重要性在于:它是文件系统协议的合同,真正执行文件操作的代码会按照这份合同收发数据。
app-server-protocol/src/protocol/v2/mcp.rs源码 ↗
这个文件主要不是做具体操作,而是规定 MCP 相关消息长什么样。没有它,前端和后端就可能各说各话:比如工具调用结果里错误字段叫什么、OAuth 登录返回什么、服务器状态怎么分页、MCP 服务器向用户要表单时表单格式怎么写。文件里大量结构体和枚举都带有序列化、JSON Schema、TypeScript 导出能力;简单说,就是同一份 Rust 定义可以变成网络 JSON,也可以变成前端可用的类型说明。它还做几类“翻译”:把核心协议里的工具调用结果、错误、用户确认动作,转换成 v2 API 对外使用的形状;也把 RMCP 库里的动作和结果互相转换。比较重要的是 elicitation,也就是 MCP 服务器向用户“追问信息”:它支持表单模式和 URL 模式,并用严格的 schema 描述输入框、数字、布尔值、单选、多选等,防止收到形状不对的数据。
McpServerToolCallResponse::from146–153 ↗
fn from(result: CoreMcpCallToolResult) -> Self
作用:把核心层的 MCP 工具调用结果,换成 v2 API 返回给客户端的工具调用结果格式。有人调用工具后,需要把内部结果包装成外部看得懂的 JSON 响应时会用它。
数据流:进去的是核心层的工具调用结果,里面有内容块、结构化内容、是否错误、附加元数据。函数不改内容,只把这些字段逐个搬到 McpServerToolCallResponse 里。出来的是适合 v2 接口直接返回的响应对象。
调用关系:它处在核心逻辑和对外协议之间,像翻译员。工具真正执行完以后,外层 API 可以通过 From 或 into 调到它,把核心结果变成网络响应。
McpToolCallResult::from157–163 ↗
fn from(result: CoreMcpCallToolResult) -> Self
作用:把核心层的 MCP 工具调用成功结果,换成更简洁的 v2 工具结果格式。它适合只关心内容本身,而不需要单独携带 is_error 字段的场景。
数据流:进去的是核心工具调用结果。函数取出普通内容、结构化内容和元数据,放进 McpToolCallResult;是否错误这个字段不会进入这个目标类型。出来的是一个只描述工具输出内容的对象。
调用关系:它也是内部结果到外部协议的转换点。和 McpServerToolCallResponse::from 很像,但目标对象少了错误标记,所以用于另一类更精简的消息形状。
McpToolCallError::from167–171 ↗
fn from(error: CoreMcpToolCallError) -> Self
作用:把核心层的 MCP 工具调用错误,换成 v2 API 使用的错误格式。这样外部客户端不用认识核心层自己的错误类型,只看一条错误消息就行。
数据流:进去的是核心错误对象,里面有 message。函数把 message 原样复制到 McpToolCallError。出来的是对外协议里的错误对象,没有额外副作用。
调用关系:它位于错误从核心层流向接口层的路上。工具调用失败时,外层代码可以通过这个转换把内部错误交给客户端。
McpServerElicitationAction::to_core255–261 ↗
fn to_core(self) -> codex_protocol::approvals::ElicitationAction
作用:把 v2 API 里的用户动作,翻译成核心协议认识的用户动作。这里的动作包括接受、拒绝、取消,就像用户在弹窗上点了三个不同按钮。
数据流:进去的是 McpServerElicitationAction,比如 Accept、Decline 或 Cancel。函数按一一对应关系换成核心层的 ElicitationAction。出来的是核心层可以继续处理的动作值。
调用关系:当前端回复 MCP 服务器的追问时,接口层先收到 v2 动作,再用这个函数交给核心层。它不调用别的内部函数,只做明确的枚举映射。
ElicitationAction::from265–271 ↗
fn from(value: McpServerElicitationAction) -> Self
作用:把 v2 API 里的追问处理动作,转换成 RMCP 库使用的动作类型。RMCP 是底层 MCP 实现库,需要它自己的类型。
数据流:进去的是 McpServerElicitationAction。函数看它是接受、拒绝还是取消,然后生成 rmcp::model::ElicitationAction 中对应的值。出来的是 RMCP 可以发送或保存的动作。
调用关系:它服务于 app-server 和 RMCP 库之间的衔接。当代码用 into 把 v2 动作交给 RMCP 时,会走到这里。
McpServerElicitationAction::from275–281 ↗
fn from(value: rmcp::model::ElicitationAction) -> Self
作用:把 RMCP 库里的追问动作,转换回 v2 API 使用的动作类型。这样底层库返回的数据可以继续用统一的对外格式表达。
数据流:进去的是 rmcp::model::ElicitationAction。函数按接受、拒绝、取消三种情况逐个对应,生成 McpServerElicitationAction。出来的是 v2 协议层认识的动作。
调用关系:它和 ElicitationAction::from 是反方向的一对翻译。RMCP 产生结果后,如果要传回 app-server 协议层,就会用这个转换。
McpServerElicitationRequest::try_from650–673 ↗
fn try_from(value: CoreElicitationRequest) -> Result<Self, Self::Error>
作用:把核心层发来的“向用户追问信息”请求,安全地转换成 v2 API 的请求格式。之所以是 try_from,是因为表单 schema 可能不合法,转换可能失败。
数据流:进去的是核心层的 ElicitationRequest,可能是表单模式,也可能是 URL 模式。URL 模式会直接复制元数据、提示语、网址和追问 ID;表单模式会额外调用 serde_json::from_value,把一坨 JSON 检查并解析成严格的 McpElicitationSchema。出来的是成功的 v2 请求;如果表单 schema 形状不对,就出来一个 JSON 解析错误。
调用关系:它是核心追问事件进入 v2 协议层的关口。调用图里测试会用它验证表单请求、URL 请求能转换,也验证非法或 null 的表单 schema 会被拒绝。它唯一明确交出去的活儿是让 serde_json::from_value 做 schema 解析和校验。
调用图:被 4 处调用(mcp_server_elicitation_request_from_core_form_request, mcp_server_elicitation_request_from_core_url_request, mcp_server_elicitation_request_rejects_invalid_core_form_schema, mcp_server_elicitation_request_rejects_null_core_form_schema);外部调用 1 个(from_value)。
CreateElicitationResult::from692–698 ↗
fn from(value: McpServerElicitationRequestResponse) -> Self
作用:把 v2 API 收到的用户追问答复,转换成 RMCP 库的 CreateElicitationResult。这样用户在界面上填完表单或点按钮后,底层 MCP 流程能继续走。
数据流:进去的是 McpServerElicitationRequestResponse,里面有动作、可选内容和可选元数据。函数把动作转成 RMCP 动作,把内容带过去,但把 meta 固定设为 None。出来的是 RMCP 库使用的结果对象。
调用关系:它位于用户回复从 app-server 协议层流向 RMCP 库的路上。里面的动作转换会借助前面定义的动作 From 转换;需要注意的是,输入里的 meta 不会传给 RMCP 结果。
McpServerElicitationRequestResponse::from702–708 ↗
fn from(value: rmcp::model::CreateElicitationResult) -> Self
作用:把 RMCP 库的追问结果,转换成 v2 API 使用的用户答复格式。它用于把底层库结果重新包装成 app-server 对外统一的形状。
数据流:进去的是 rmcp::model::CreateElicitationResult,包含动作、内容和元数据。函数把动作转回 McpServerElicitationAction,把内容复制过来,但 meta 固定设为 None。出来的是 McpServerElicitationRequestResponse。
调用关系:它是 CreateElicitationResult::from 的反向转换。调用图里有测试用它检查 RMCP 结果往返转换是否正常;和另一个方向一样,meta 字段不会被保留下来。
调用图:被 1 处调用(mcp_server_elicitation_response_round_trips_rmcp_result)。
app-server-protocol/src/protocol/v2/windows_sandbox.rs源码 ↗
这个文件本身不执行操作,而是规定一组数据长什么样。它主要服务于 Windows 上的沙盒功能:程序需要知道沙盒是否准备好、是否需要配置、配置是用管理员权限还是普通权限启动,以及配置完成后成功没成功。这里的结构体可以理解成一张张固定格式的通知单或回执单,比如“开始设置沙盒”的请求单、“设置已经完成”的通知单、“当前是否就绪”的查询结果。它还带有序列化能力,意思是这些 Rust 数据可以变成网络或进程间传输用的 JSON;也能生成 JSON Schema 和 TypeScript 类型,让前端或其他语言也能按同一套格式使用。重要的一点是,字段统一改成 camelCase,比如 Rust 里的 sample_paths 对外会叫 samplePaths,这样更符合 JavaScript/TypeScript 世界的习惯。
Schema 导出与实验性过滤
这些文件描述如何分析协议接口中的实验性字段,并在 fixture 支持下将其转换为生成的 schema 和 TypeScript 制品。
app-server-protocol/src/experimental_api.rs源码 ↗
这个文件像是在协议旁边放了一套“试用标签”。有些功能还没完全稳定,只允许明确声明支持实验功能的客户端使用。这里定义了 ExperimentalApi 这个 trait(可以理解成一份约定:任何类型都要能回答“我里面有没有实验性内容”)。如果有,就返回一个简短原因;如果没有,就返回 None。它还定义了 ExperimentalField,用来登记某个类型的某个字段是实验性的,并通过 inventory 这个注册机制把这些字段集中收集起来,方便生成或过滤协议文档时使用。文件还给 Option、Vec、HashMap、BTreeMap 这些常见容器补了检查能力:外层容器本身不重要,重点是一路往里面找,看里面的值有没有实验性内容。测试部分则确认宏生成的检查代码能识别枚举、嵌套字段、列表、字典和可选字段这些常见形状。
experimental_fields25–27 ↗
fn experimental_fields() -> Vec<&'static ExperimentalField>
作用:返回整个协议里已经登记的所有实验性字段。生成协议 schema 或 TypeScript 类型时,可以用它知道哪些字段需要被隐藏、过滤或特别标注。
数据流:进去时不需要参数 → 它从 inventory 这个全局登记表里取出所有 ExperimentalField 条目 → 出来的是一个列表,列表里每一项都指向一个已登记的实验性字段;它不修改数据,只做收集。
调用关系:它是实验字段清单的统一出口。filter_experimental_schema、filter_experimental_ts、filter_experimental_ts_tree 会在过滤不同格式的协议描述时调用它,用同一份清单决定哪些实验内容要被处理。
调用图:被 3 处调用(filter_experimental_schema, filter_experimental_ts, filter_experimental_ts_tree)。
experimental_required_message30–32 ↗
fn experimental_required_message(reason: &str) -> String
作用:把一个实验性功能的原因编号变成给人看的错误消息。这样系统在拒绝请求时,能说明“你用了什么实验功能,所以需要打开 experimentalApi 能力”。
数据流:进去的是一个 reason 字符串,比如某个方法名或字段名 → 它用 format! 把这个原因拼进固定句式里 → 出来的是一段完整错误文字;它不改动外部状态。
调用关系:它是生成提示语的小工具。别的地方只要已经知道触发实验限制的 reason,就可以把这个函数当成统一话术生成器;它内部只把工作交给 Rust 的 format! 宏来拼字符串。
调用图:外部调用 1 个(format!)。
Option::experimental_reason35–37 ↗
fn experimental_reason(&self) -> Option<&'static str>
作用:让“可能有、也可能没有”的值也能参与实验性检查。比如一个可选字段为空时就不算用了实验功能,有值时再检查里面那份值。
数据流:进去的是一个 Option<T>,也就是可能为空的包装盒 → 如果盒子里有值,它继续询问里面的值有没有实验性原因;如果盒子是空的,就直接返回 None → 出来的是里面找到的原因,或者表示稳定的 None。
调用关系:它让嵌套检查可以自然穿过可选字段。由实现了 ExperimentalApi 的外层结构调用时,它会把检查继续交给里面的 T 的 experimental_reason。
Vec::experimental_reason41–43 ↗
fn experimental_reason(&self) -> Option<&'static str>
作用:让列表也能参与实验性检查。只要列表里有任何一个元素用了实验性功能,整个列表就会被视为用了实验性功能。
数据流:进去的是一个 Vec<T> 列表 → 它从前往后查看每个元素,问每个元素有没有实验性原因 → 一旦找到第一个原因就返回;如果全都没有或列表为空,就返回 None。
调用关系:它用于检查数组、集合这类字段。外层结构检查到一个列表字段时,会调用它;它再把检查逐个交给列表元素自己的 experimental_reason。
HashMap::experimental_reason47–49 ↗
fn experimental_reason(&self) -> Option<&'static str>
作用:让哈希表这种“键值对字典”也能参与实验性检查。这里关心的是字典里的值有没有实验性内容,键只是用来查找,不参与判断。
数据流:进去的是一个 HashMap<K, V, S> 字典 → 它遍历字典里的所有值,逐个询问这些值有没有实验性原因 → 找到第一个原因就返回;如果没有任何值触发实验性功能,就返回 None。
调用关系:它用于带名字的集合或映射字段。外层结构遇到 HashMap 字段时调用它,它再把真正的判断交给每个 value 的 experimental_reason。
BTreeMap::experimental_reason53–55 ↗
fn experimental_reason(&self) -> Option<&'static str>
作用:让 BTreeMap 这种按键排序的字典也能参与实验性检查。和 HashMap 一样,它只检查字典里的值是否使用了实验性功能。
数据流:进去的是一个 BTreeMap<K, V> → 它按字典内部顺序遍历所有值,逐个检查值里的实验性原因 → 找到第一个原因就返回;全部稳定则返回 None。
调用关系:它服务于使用有序字典的协议数据。外层结构把检查交给它后,它继续把检查传给每个 value 的 experimental_reason,从而让深层实验字段也能被发现。
tests::derive_supports_all_enum_variant_shapes109–126 ↗
fn derive_supports_all_enum_variant_shapes()
作用:测试自动生成的 ExperimentalApi 代码能不能正确识别不同形状的枚举分支。枚举可以理解成“多选一”的数据,这里确认普通分支、带位置参数的分支、带名字字段的分支都能被标记。
数据流:进去的是几个手写出来的枚举值 → 测试调用 experimental_reason 看它们返回什么 → 预期带 experimental 标记的分支返回对应原因,没标记的稳定分支返回 None;测试本身不产生业务数据,只验证结果是否符合预期。
调用关系:它在测试阶段运行,用 assert_eq! 对比实际结果和期待结果。它验证的是派生宏生成的 ExperimentalApi 实现是否能覆盖各种枚举写法。
调用图:外部调用 1 个(assert_eq!)。
tests::derive_supports_nested_experimental_fields129–140 ↗
fn derive_supports_nested_experimental_fields()
作用:测试嵌套字段里的实验性内容能不能被发现。也就是说,实验标记不只看最外层,藏在里面一层的值也应该能被检查出来。
数据流:进去的是一个包含 Option<EnumVariantShapes> 的结构体样例 → 如果 inner 里放了实验性枚举值,检查应返回对应原因;如果 inner 是 None,检查应返回 None → 输出是测试断言通过或失败。
调用关系:它在测试阶段运行,重点验证 Option::experimental_reason 这一类容器检查能和派生宏生成的结构体检查配合起来,把检查一路传到内部值。
调用图:外部调用 1 个(assert_eq!)。
tests::derive_supports_nested_collections143–159 ↗
fn derive_supports_nested_collections()
作用:测试列表里的实验性元素能不能被发现。实际协议里常有数组字段,这个测试保证数组中只要有一个实验性项,系统就能看出来。
数据流:进去的是一个带 Vec<EnumVariantShapes> 列表的结构体 → 测试先放入稳定元素和实验性元素,期望返回实验性原因;再放入空列表,期望返回 None → 输出是断言结果。
调用关系:它在测试阶段运行,验证 Vec::experimental_reason 和宏生成代码能配合工作:结构体检查列表字段,列表再检查里面每个枚举元素。
调用图:外部调用 1 个(assert_eq!)。
tests::derive_supports_nested_maps162–178 ↗
fn derive_supports_nested_maps()
作用:测试字典里的实验性值能不能被发现。协议数据有时会用名字映射到一组对象,这个测试保证字典里的值不会逃过实验性检查。
数据流:进去的是一个带 HashMap<String, EnumVariantShapes> 的结构体 → 当字典里有一个实验性枚举值时,期望得到它的原因;当字典为空时,期望得到 None → 输出是测试断言是否通过。
调用关系:它在测试阶段运行,验证 HashMap::experimental_reason 和派生宏生成的外层结构检查能串起来,让映射里的值也被检查。
调用图:外部调用 1 个(assert_eq!)。
tests::derive_marks_optional_experimental_fields_when_some181–194 ↗
fn derive_marks_optional_experimental_fields_when_some()
作用:测试一个字段本身被标成实验性时,只有它真的出现了才算使用实验功能。可选字段为空时,不应该误报。
数据流:进去的是一个带 optional_collection 字段的结构体,这个字段是 Option<Vec<...>> → 当字段是 Some,即使里面列表为空,也应返回字段自己的实验原因;当字段是 None,则返回 None → 输出是断言通过或失败。
调用关系:它在测试阶段运行,验证字段级实验标记的行为。这个测试和前面嵌套检查不同:重点不是列表里有没有实验元素,而是这个可选字段一旦出现,本身就代表用了实验功能。
调用图:外部调用 1 个(assert_eq!)。
app-server-protocol/src/export.rs源码 ↗
这个文件主要解决一个很实际的问题:协议类型不能只活在 Rust 代码里,否则前端、Python 生成器、文档和测试都容易各说各话。它会先把 Rust 类型导出成 TypeScript,再把同样的类型导出成 JSON Schema(JSON 数据格式的机器可读说明书)。生成后,它还会做清理:补上“自动生成,请勿手改”的文件头,生成 index.ts 方便统一导入,去掉行尾空格,必要时调用 Prettier 格式化。另一个重点是“实验性 API”的过滤:默认公开版本不会暴露还不稳定的方法、字段和类型,避免外部用户误用。文件里还有一套小型文本扫描器,用来安全地拆 TypeScript 类型内容,不会把字符串、注释或嵌套括号里的符号误当成分隔符。最后,它会把零散 schema 合成总包,并专门生成一个更适合 v2 代码生成器使用的扁平 schema。
GeneratedSchema::namespace67–69 ↗
fn namespace(&self) -> Option<&str>
作用:取出这个生成 schema 所属的命名空间,比如 v2;没有命名空间就返回空。
数据流:进去的是一个 GeneratedSchema 对象 → 它查看内部保存的 namespace 字段 → 出来的是可借用的命名空间字符串,或者没有值。
调用关系:build_schema_bundle 和 collect_namespaced_types 在整理 schema 时会用它判断某个类型该放在根目录还是 v2 这类分区里。
GeneratedSchema::logical_name71–73 ↗
fn logical_name(&self) -> &str
作用:取出 schema 的真实类型名,不带 v2:: 这种前缀。
数据流:进去的是 GeneratedSchema → 它读取 logical_name 字段 → 返回类型的逻辑名字。
调用关系:生成总 schema 包时用它当作 definitions 里的键名,也用来判断某些 v1 类型是否允许进入最终包。
GeneratedSchema::value75–77 ↗
fn value(&self) -> &Value
作用:取出这个 schema 的 JSON 内容本身。
数据流:进去的是 GeneratedSchema → 它读取 value 字段 → 返回一份只读的 JSON 值引用。
调用关系:collect_namespaced_types 会查看里面的 definitions 或 $defs,从而知道命名空间下还有哪些子类型。
generate_types81–85 ↗
fn generate_types(out_dir: &Path, prettier: Option<&Path>) -> Result<()>
作用:一口气生成 TypeScript 类型和 JSON Schema,是外部最省事的总入口。
数据流:进去的是输出目录和可选的 Prettier 路径 → 先生成 TypeScript,再生成 JSON Schema → 成功时写出一批文件,失败时返回错误。
调用关系:它把工作交给 generate_ts 和 generate_json,适合构建脚本或命令行入口直接调用。
调用图:调用 2 个内部函数(generate_json, generate_ts)。
GenerateTsOptions::default96–103 ↗
fn default() -> Self
作用:给 TypeScript 生成过程提供一套默认开关。
数据流:没有输入 → 生成默认选项:生成 index、补文件头、运行 Prettier、不包含实验性 API → 返回 GenerateTsOptions。
调用关系:generate_ts、main 和测试夹具生成代码会用它,除非调用者想手动改某个开关。
调用图:被 3 处调用(main, generate_ts, write_schema_fixtures_with_options)。
generate_ts106–108 ↗
fn generate_ts(out_dir: &Path, prettier: Option<&Path>) -> Result<()>
作用:用默认设置生成 TypeScript 类型文件。
数据流:进去的是输出目录和可选 Prettier 路径 → 套用默认选项 → 调用更完整的 generate_ts_with_options 写出文件。
调用关系:generate_types 会先调用它;它本身只是薄封装,把细活交给 generate_ts_with_options。
调用图:调用 2 个内部函数(default, generate_ts_with_options);被 1 处调用(generate_types)。
generate_ts_with_options110–185 ↗
fn generate_ts_with_options(
out_dir: &Path,
prettier: Option<&Path>,
options: GenerateTsOptions,
) -> Result<()>
作用:真正执行 TypeScript 导出的主流程,可按选项控制是否生成索引、补头、格式化和保留实验性 API。
数据流:进去的是输出目录、Prettier 路径和选项 → 创建目录、导出各种请求/响应/通知类型、过滤实验项、生成 index、补头、格式化、去行尾空格 → 输出目录里得到干净的 .ts 文件。
调用关系:generate_ts 调用它;它串起 ensure_dir、filter_experimental_ts、generate_index_ts、ts_files_in_recursive、trim_trailing_whitespace_in_ts_files 等多个零件。
调用图:调用 5 个内部函数(ensure_dir, filter_experimental_ts, generate_index_ts, trim_trailing_whitespace_in_ts_files, ts_files_in_recursive);被 1 处调用(generate_ts);外部调用 11 个(export_all_to, export_all_to, join, export_all_to, export_all_to, anyhow!, new, export_client_responses, export_server_responses, available_parallelism (+1 more))。
generate_json187–189 ↗
fn generate_json(out_dir: &Path) -> Result<()>
作用:用默认公开模式生成 JSON Schema,不包含实验性 API。
数据流:进去的是输出目录 → 调用 generate_json_with_experimental,并把 experimental_api 设为 false → 输出稳定版 JSON Schema 文件。
调用关系:generate_types 在 TypeScript 之后调用它;更细的开关由 generate_json_with_experimental 处理。
调用图:调用 1 个内部函数(generate_json_with_experimental);被 1 处调用(generate_types)。
generate_internal_json_schema191–195 ↗
fn generate_internal_json_schema(out_dir: &Path) -> Result<()>
作用:生成内部使用的 JSON Schema,目前用于 RolloutLine 这类非公开协议类型。
数据流:进去的是输出目录 → 确保目录存在 → 写出 RolloutLine 的 schema 文件。
调用关系:它走 ensure_dir 和 write_json_schema,和公开协议导出流程相似,但目标是内部数据。
调用图:调用 1 个内部函数(ensure_dir)。
generate_json_with_experimental197–246 ↗
fn generate_json_with_experimental(out_dir: &Path, experimental_api: bool) -> Result<()>
作用:真正执行 JSON Schema 导出的主流程,并可选择是否保留实验性 API。
数据流:进去的是输出目录和实验开关 → 写出基础 JSON-RPC 外壳 schema、各类参数/响应/通知 schema,合成总包,必要时过滤实验内容,再生成 v2 扁平包 → 输出多个 .json schema 文件。
调用关系:generate_json 和相关测试会调用它;它把合包交给 build_schema_bundle,把 v2 扁平化交给 build_flat_v2_schema,把写文件交给 write_pretty_json。
调用图:调用 6 个内部函数(build_flat_v2_schema, build_schema_bundle, ensure_dir, filter_experimental_json_files, filter_experimental_schema, write_pretty_json);被 3 处调用(generate_json, generate_json_filters_experimental_fields_and_methods, generate_json_includes_remote_control_methods_with_experimental_api);外部调用 9 个(join, new, export_client_notification_schemas, export_client_param_schemas, export_client_response_schemas, export_server_notification_schemas, export_server_param_schemas, export_server_response_schemas, vec!)。
filter_experimental_ts248–259 ↗
fn filter_experimental_ts(out_dir: &Path) -> Result<()>
作用:从磁盘上的 TypeScript 输出里删掉默认不公开的实验性方法、字段和类型文件。
数据流:进去的是输出目录 → 读取实验字段和实验方法类型清单 → 改写 ClientRequest.ts、清理带实验字段的类型文件、删除实验类型文件。
调用关系:generate_ts_with_options 在 experimental_api 为 false 时调用它;具体清理交给 filter_client_request_ts、filter_experimental_type_fields_ts 和 remove_generated_type_files。
调用图:调用 5 个内部函数(experimental_fields, experimental_method_types, filter_client_request_ts, filter_experimental_type_fields_ts, remove_generated_type_files);被 1 处调用(generate_ts_with_options)。
filter_experimental_ts_tree261–294 ↗
fn filter_experimental_ts_tree(tree: &mut BTreeMap<PathBuf, String>) -> Result<()>
作用:在内存里的 TypeScript 文件树上做同样的实验性 API 过滤,方便测试夹具不用真的依赖磁盘流程。
数据流:进去的是路径到文件内容的映射 → 修改 ClientRequest.ts 内容、删除实验字段、移除实验类型条目 → 返回同一棵被清理过的树。
调用关系:测试夹具生成流程会调用它;它复用 filter_client_request_ts_contents、filter_experimental_type_fields_ts_contents 和 remove_generated_type_entries。
调用图:调用 5 个内部函数(experimental_fields, experimental_method_types, filter_client_request_ts_contents, filter_experimental_type_fields_ts_contents, remove_generated_type_entries);被 1 处调用(generate_typescript_schema_fixture_subtree_for_tests);外部调用 3 个(new, new, take)。
filter_client_request_ts297–308 ↗
fn filter_client_request_ts(out_dir: &Path, experimental_methods: &[&str]) -> Result<()>
作用:专门清理 ClientRequest.ts 里实验性请求方法对应的联合类型分支。
数据流:进去的是输出目录和实验方法列表 → 如果 ClientRequest.ts 存在就读入、过滤内容、写回 → 文件中不再声明那些实验请求。
调用关系:filter_experimental_ts 调用它;真正的字符串改写由 filter_client_request_ts_contents 完成。
调用图:调用 1 个内部函数(filter_client_request_ts_contents);被 1 处调用(filter_experimental_ts);外部调用 3 个(join, read_to_string, write)。
filter_client_request_ts_contents310–333 ↗
fn filter_client_request_ts_contents(mut content: String, experimental_methods: &[&str]) -> String
作用:在一段 TypeScript 文本里删掉实验性 ClientRequest 分支,并顺手删掉不再用到的 import。
数据流:进去的是文件内容和实验方法名 → 找到 type alias 的主体,按顶层 | 拆开,去掉 method 命中的分支,再清理无用导入 → 返回新文本。
调用关系:磁盘版 filter_client_request_ts 和内存版 filter_experimental_ts_tree 都会用它;它依赖 split_type_alias、split_top_level 和 prune_unused_type_imports。
调用图:调用 3 个内部函数(prune_unused_type_imports, split_top_level, split_type_alias);被 2 处调用(filter_client_request_ts, filter_experimental_ts_tree);外部调用 1 个(format!)。
filter_experimental_type_fields_ts336–362 ↗
fn filter_experimental_type_fields_ts(
out_dir: &Path,
experimental_fields: &[&'static crate::experimental_api::ExperimentalField],
) -> Result<()>
作用:从生成的 TypeScript 类型文件里删掉标记为实验性的字段。
数据流:进去的是输出目录和实验字段清单 → 按类型名把字段分组,遍历所有 .ts 文件,找到对应类型文件后改写 → 文件里只保留稳定字段。
调用关系:filter_experimental_ts 和多个测试会调用它;逐文件处理交给 filter_experimental_fields_in_ts_file。
调用图:调用 2 个内部函数(filter_experimental_fields_in_ts_file, ts_files_in_recursive);被 4 处调用(filter_experimental_ts, experimental_type_fields_ts_filter_handles_generated_command_params_shape, experimental_type_fields_ts_filter_handles_interface_shape, experimental_type_fields_ts_filter_keeps_imports_used_in_intersection_suffix);外部调用 1 个(new)。
filter_experimental_fields_in_ts_file364–373 ↗
fn filter_experimental_fields_in_ts_file(
path: &Path,
experimental_field_names: &HashSet<String>,
) -> Result<()>
作用:读取一个 TypeScript 文件,删掉指定实验字段,再写回。
数据流:进去的是文件路径和字段名集合 → 读文件、调用内容级过滤器、写文件 → 该文件内容被更新。
调用关系:filter_experimental_type_fields_ts 遍历文件时调用它;具体文本判断交给 filter_experimental_type_fields_ts_contents。
调用图:调用 1 个内部函数(filter_experimental_type_fields_ts_contents);被 1 处调用(filter_experimental_type_fields_ts);外部调用 2 个(read_to_string, write)。
filter_experimental_type_fields_ts_contents375–400 ↗
fn filter_experimental_type_fields_ts_contents(
mut content: String,
experimental_field_names: &HashSet<String>,
) -> String
作用:在 TypeScript 类型或接口文本里删掉指定字段,并去掉因此没用的 import。
数据流:进去的是文本和字段名集合 → 找到类型主体的大括号范围,按顶层逗号/分号拆字段,过滤命中字段,重组文本 → 返回清理后的内容。
调用关系:磁盘文件处理和内存文件树处理都会调用它;它依赖 type_body_brace_span、split_top_level_multi 和 prune_unused_type_imports。
调用图:调用 4 个内部函数(prune_unused_type_imports, split_top_level_multi, split_type_alias, type_body_brace_span);被 2 处调用(filter_experimental_fields_in_ts_file, filter_experimental_ts_tree);外部调用 1 个(format!)。
filter_experimental_schema402–409 ↗
fn filter_experimental_schema(bundle: &mut Value) -> Result<()>
作用:从 JSON Schema 包里删掉实验性字段、实验性方法分支和相关类型定义。
数据流:进去的是可修改的 JSON 值 → 找到根 schema 和 definitions 中的实验字段并移除,再删除实验方法变体和类型定义 → 同一个 JSON 值变成稳定版。
调用关系:generate_json_with_experimental 和 filter_experimental_json_files 会调用它;测试也直接用它验证过滤规则。
调用图:调用 5 个内部函数(experimental_fields, filter_experimental_fields_in_definitions, filter_experimental_fields_in_root, prune_experimental_methods, remove_experimental_method_type_definitions);被 4 处调用(filter_experimental_json_files, generate_json_with_experimental, stable_schema_filter_removes_mock_experimental_method, stable_schema_filter_removes_mock_thread_start_field)。
filter_experimental_fields_in_root411–426 ↗
fn filter_experimental_fields_in_root(
schema: &mut Value,
experimental_fields: &[&'static crate::experimental_api::ExperimentalField],
)
作用:如果根 schema 本身就是带实验字段的类型,就从根上删掉这些字段。
数据流:进去的是根 schema 和实验字段列表 → 查看 title 是否匹配类型名 → 匹配时调用 remove_property_from_schema 删除字段和 required 标记。
调用关系:filter_experimental_schema 的第一步会调用它,处理不在 definitions 里的顶层类型。
调用图:调用 1 个内部函数(remove_property_from_schema);被 1 处调用(filter_experimental_schema);外部调用 1 个(get)。
filter_experimental_fields_in_definitions428–437 ↗
fn filter_experimental_fields_in_definitions(
bundle: &mut Value,
experimental_fields: &[&'static crate::experimental_api::ExperimentalField],
)
作用:从 schema 的 definitions 区域里删除实验字段。
数据流:进去的是总 schema 和实验字段列表 → 取出 definitions 对象 → 交给递归版本逐个定义处理。
调用关系:filter_experimental_schema 调用它;实际递归逻辑在 filter_experimental_fields_in_definitions_map。
调用图:调用 1 个内部函数(filter_experimental_fields_in_definitions_map);被 1 处调用(filter_experimental_schema);外部调用 1 个(get_mut)。
filter_experimental_fields_in_definitions_map439–458 ↗
fn filter_experimental_fields_in_definitions_map(
definitions: &mut Map<String, Value>,
experimental_fields: &[&'static crate::experimental_api::ExperimentalField],
)
作用:递归扫描 definitions 映射,找到对应类型后删掉实验字段。
数据流:进去的是 definitions 映射和实验字段列表 → 遇到命名空间就继续往里走,遇到普通 schema 就按类型名匹配字段 → 修改这些 schema。
调用关系:filter_experimental_fields_in_definitions 调用它;它用 is_namespace_map 区分“文件夹”和“真正 schema”,用 remove_property_from_schema 做删除。
调用图:调用 3 个内部函数(definition_matches_type, is_namespace_map, remove_property_from_schema);被 1 处调用(filter_experimental_fields_in_definitions);外部调用 1 个(iter_mut)。
is_namespace_map460–476 ↗
fn is_namespace_map(value: &Value) -> bool
作用:判断一个 JSON 对象是不是命名空间容器,而不是一个真正的 schema。
数据流:进去的是 JSON 值 → 看它有没有 schema 常见字段、有没有 $ 开头字段、里面是不是对象 → 返回真假。
调用关系:过滤实验字段和删除实验类型定义时都会用它,避免把 v2 这种分组误当成类型本身。
调用图:被 2 处调用(filter_experimental_fields_in_definitions_map, remove_experimental_method_type_definitions_map)。
definition_matches_type478–480 ↗
fn definition_matches_type(def_name: &str, type_name: &str) -> bool
作用:判断 definitions 里的名字是否对应某个类型名,兼容带命名空间的名字。
数据流:进去的是定义名和类型名 → 比较是否完全相等,或是否以 ::类型名 结尾 → 返回是否匹配。
调用关系:filter_experimental_fields_in_definitions_map 用它确认哪个 schema 要删字段。
调用图:被 1 处调用(filter_experimental_fields_in_definitions_map);外部调用 1 个(format!)。
remove_property_from_schema482–494 ↗
fn remove_property_from_schema(schema: &mut Value, field_name: &str)
作用:从一个 JSON Schema 里删掉某个属性,并把 required 里的必填声明也一起删掉。
数据流:进去的是 schema 和字段名 → 删除 properties 中的字段,删除 required 数组中的同名项,若里面还有 schema 包裹层就递归处理 → schema 不再要求该字段。
调用关系:实验字段过滤的根处理和 definitions 处理都会调用它。
调用图:被 2 处调用(filter_experimental_fields_in_definitions_map, filter_experimental_fields_in_root);外部调用 1 个(get_mut)。
prune_experimental_methods496–503 ↗
fn prune_experimental_methods(bundle: &mut Value, experimental_methods: &[&str])
作用:从 schema 的联合变体里删掉实验性方法对应的分支。
数据流:进去的是总 schema 和实验方法名列表 → 转成集合 → 递归清理整棵 JSON 树。
调用关系:filter_experimental_schema 调用它;递归工作由 prune_experimental_methods_inner 完成。
调用图:调用 1 个内部函数(prune_experimental_methods_inner);被 1 处调用(filter_experimental_schema)。
prune_experimental_methods_inner505–520 ↗
fn prune_experimental_methods_inner(value: &mut Value, experimental_methods: &HashSet<&str>)
作用:递归走遍 JSON 值,把数组里代表实验方法的项删除。
数据流:进去的是任意 JSON 值和实验方法集合 → 如果是数组就先删除命中项再递归,如果是对象就递归子值 → 原地改好 JSON。
调用关系:prune_experimental_methods 调用它;它依靠 is_experimental_method_variant 判断某个数组项是不是实验方法。
调用图:被 1 处调用(prune_experimental_methods)。
is_experimental_method_variant522–545 ↗
fn is_experimental_method_variant(value: &Value, experimental_methods: &HashSet<&str>) -> bool
作用:判断一个 schema 分支是不是某个实验性方法。
数据流:进去的是 JSON 值和实验方法集合 → 找 properties.method 里的 const 或单值 enum → 如果方法名在集合里就返回 true。
调用关系:prune_experimental_methods_inner 在清理数组分支时用它做判断。
filter_experimental_json_files547–556 ↗
fn filter_experimental_json_files(out_dir: &Path) -> Result<()>
作用:对已经写到磁盘上的所有 JSON Schema 文件再做一次实验性内容清理。
数据流:进去的是输出目录 → 找到所有 .json,读入、过滤、漂亮写回,再删除实验类型文件 → 磁盘上的 JSON 文件变成稳定版。
调用关系:generate_json_with_experimental 在公开模式下调用它;它串起 json_files_in_recursive、read_json_value、filter_experimental_schema 和 remove_generated_type_files。
调用图:调用 6 个内部函数(experimental_method_types, filter_experimental_schema, json_files_in_recursive, read_json_value, remove_generated_type_files, write_pretty_json);被 1 处调用(generate_json_with_experimental)。
experimental_method_types558–564 ↗
fn experimental_method_types() -> HashSet<String>
作用:收集所有实验性方法会用到的参数、响应和依赖类型名。
数据流:没有业务输入 → 从几个常量列表里取类型路径,提取最后的类型名 → 返回类型名集合。
调用关系:TypeScript 和 JSON 过滤流程都会用它决定哪些生成文件或 definitions 应该删除。
调用图:调用 1 个内部函数(collect_experimental_type_names);被 4 处调用(filter_experimental_json_files, filter_experimental_ts, filter_experimental_ts_tree, remove_experimental_method_type_definitions);外部调用 1 个(new)。
collect_experimental_type_names566–577 ↗
fn collect_experimental_type_names(entries: &[&str], out: &mut HashSet<String>)
作用:把一批类型路径里的简单类型名加入集合。
数据流:进去的是字符串列表和输出集合 → 去空白、跳过空项、取 :: 后最后一段 → 修改集合。
调用关系:experimental_method_types 用它分别处理参数类型、响应类型和依赖类型。
调用图:被 1 处调用(experimental_method_types)。
remove_generated_type_files579–600 ↗
fn remove_generated_type_files(
out_dir: &Path,
type_names: &HashSet<String>,
extension: &str,
) -> Result<()>
作用:从输出目录删除指定类型名对应的生成文件。
数据流:进去的是输出目录、类型名集合和扩展名 → 在根目录、v1、v2 下查找这些文件 → 存在就删除。
调用关系:filter_experimental_ts 和 filter_experimental_json_files 用它清掉实验类型的 .ts 或 .json 文件。
调用图:被 2 处调用(filter_experimental_json_files, filter_experimental_ts);外部调用 3 个(join, format!, remove_file)。
remove_generated_type_entries602–617 ↗
fn remove_generated_type_entries(
tree: &mut BTreeMap<PathBuf, String>,
type_names: &HashSet<String>,
extension: &str,
)
作用:从内存文件树里删除指定类型名对应的条目。
数据流:进去的是文件树、类型名集合和扩展名 → 构造根目录/v1/v2 下的路径 → 从映射里移除。
调用关系:filter_experimental_ts_tree 用它在测试夹具的内存版本里模拟删文件。
调用图:被 1 处调用(filter_experimental_ts_tree);外部调用 2 个(from, format!)。
remove_experimental_method_type_definitions619–625 ↗
fn remove_experimental_method_type_definitions(bundle: &mut Value)
作用:从总 schema 的 definitions 里删掉实验性方法专用类型。
数据流:进去的是总 schema → 取实验类型名集合,再找到 definitions → 递归删除匹配的定义。
调用关系:filter_experimental_schema 的最后阶段调用它;递归删除由 remove_experimental_method_type_definitions_map 完成。
调用图:调用 2 个内部函数(experimental_method_types, remove_experimental_method_type_definitions_map);被 1 处调用(filter_experimental_schema);外部调用 1 个(get_mut)。
remove_experimental_method_type_definitions_map627–655 ↗
fn remove_experimental_method_type_definitions_map(
definitions: &mut Map<String, Value>,
experimental_type_names: &HashSet<String>,
)
作用:递归删除 definitions 映射中与实验类型名匹配的项。
数据流:进去的是 definitions 映射和实验类型名集合 → 先收集要删除的键,再删除,再进入命名空间对象继续处理 → 映射被清理。
调用关系:remove_experimental_method_type_definitions 调用它;它用 is_namespace_map 判断是否需要继续深入。
调用图:调用 1 个内部函数(is_namespace_map);被 1 处调用(remove_experimental_method_type_definitions);外部调用 3 个(keys, remove, values_mut)。
prune_unused_type_imports657–674 ↗
fn prune_unused_type_imports(content: String, type_alias_body: &str) -> String
作用:删掉 TypeScript 文件里已经不再被使用的单类型 import。
数据流:进去的是完整文本和还在使用的类型范围 → 逐行检查 import type { X },如果 X 不在使用范围中就跳过该行 → 返回删掉无用导入后的文本。
调用关系:过滤 ClientRequest 分支和过滤实验字段后都调用它,避免留下引用不存在类型的导入。
调用图:调用 1 个内部函数(parse_imported_type_name);被 2 处调用(filter_client_request_ts_contents, filter_experimental_type_fields_ts_contents);外部调用 1 个(new)。
parse_imported_type_name676–685 ↗
fn parse_imported_type_name(line: &str) -> Option<&str>
作用:从一行简单的 TypeScript import 语句里取出导入的类型名。
数据流:进去的是一行文本 → 识别 import type { Name } from 这种格式,排除多导入和别名 → 返回 Name 或空。
调用关系:prune_unused_type_imports 逐行清理导入时用它。
调用图:被 1 处调用(prune_unused_type_imports)。
json_files_in_recursive687–704 ↗
fn json_files_in_recursive(dir: &Path) -> Result<Vec<PathBuf>>
作用:递归找出一个目录下所有 JSON 文件。
数据流:进去的是目录路径 → 用栈遍历子目录 → 返回所有扩展名是 .json 的路径列表。
调用关系:filter_experimental_json_files 用它决定哪些 schema 文件需要二次过滤。
调用图:被 1 处调用(filter_experimental_json_files);外部调用 4 个(new, read_dir, matches!, vec!)。
read_json_value706–710 ↗
fn read_json_value(path: &Path) -> Result<Value>
作用:把一个 JSON 文件读成程序能修改的 JSON 值。
数据流:进去的是文件路径 → 读取文本并用 serde_json 解析 → 返回 JSON 值,读错或解析错会带文件名报错。
调用关系:filter_experimental_json_files 和测试会用它读取生成结果。
调用图:被 2 处调用(filter_experimental_json_files, generate_json_filters_experimental_fields_and_methods);外部调用 2 个(read_to_string, from_str)。
split_type_alias712–722 ↗
fn split_type_alias(content: &str) -> Option<(String, String, String)>
作用:把 TypeScript 的 type alias 粗略拆成等号前、主体、分号后三段。
数据流:进去的是文本 → 找第一个 = 和最后一个 ; → 返回 prefix、body、suffix;找不到合适结构就返回空。
调用关系:过滤 ClientRequest 联合类型和过滤类型字段时用它定位可改写区域。
调用图:被 2 处调用(filter_client_request_ts_contents, filter_experimental_type_fields_ts_contents)。
type_body_brace_span724–739 ↗
fn type_body_brace_span(content: &str) -> Option<(usize, usize)>
作用:找到 TypeScript type 或 interface 主体的大括号位置。
数据流:进去的是文本 → 对 type alias 从 = 后找,对 export interface 从声明后找 → 返回开括号和闭括号的字节位置。
调用关系:filter_experimental_type_fields_ts_contents 用它确认字段列表在哪里。
调用图:调用 1 个内部函数(find_top_level_brace_span);被 1 处调用(filter_experimental_type_fields_ts_contents)。
find_top_level_brace_span741–758 ↗
fn find_top_level_brace_span(input: &str) -> Option<(usize, usize)>
作用:找到最外层的一对大括号,忽略字符串和注释里的括号。
数据流:进去的是文本片段 → 用 ScanState 逐字符记录括号深度和是否在字符串/注释中 → 返回顶层大括号范围。
调用关系:type_body_brace_span 和 extract_method_from_arm 都靠它安全定位对象体。
调用图:被 2 处调用(extract_method_from_arm, type_body_brace_span);外部调用 1 个(default)。
split_top_level760–762 ↗
fn split_top_level(input: &str, delimiter: char) -> Vec<String>
作用:按一个分隔符拆文本,但只拆最外层,不拆嵌套结构里的内容。
数据流:进去的是文本和分隔符 → 转交给 split_top_level_multi → 返回拆好的片段。
调用关系:过滤 ClientRequest 的 | 分支和解析对象字段时会用它。
调用图:调用 1 个内部函数(split_top_level_multi);被 2 处调用(extract_method_from_arm, filter_client_request_ts_contents)。
split_top_level_multi764–783 ↗
fn split_top_level_multi(input: &str, delimiters: &[char]) -> Vec<String>
作用:按多个分隔符拆最外层文本,避免误拆括号、泛型、字符串或注释中的符号。
数据流:进去的是文本和分隔符列表 → 用 ScanState 跟踪状态,在顶层遇到分隔符才切片 → 返回非空片段列表。
调用关系:split_top_level 和 filter_experimental_type_fields_ts_contents 都会调用它。
调用图:被 2 处调用(filter_experimental_type_fields_ts_contents, split_top_level);外部调用 2 个(new, default)。
extract_method_from_arm785–800 ↗
fn extract_method_from_arm(arm: &str) -> Option<String>
作用:从 TypeScript 联合类型的一个分支里读出 method 字段的字符串值。
数据流:进去的是一个分支文本 → 找对象体,拆字段,找到 method,再解析字符串字面量 → 返回方法名或空。
调用关系:filter_client_request_ts_contents 用它判断某个请求分支是否属于实验方法。
调用图:调用 4 个内部函数(find_top_level_brace_span, parse_property, parse_string_literal, split_top_level)。
parse_property802–806 ↗
fn parse_property(input: &str) -> Option<(String, &str)>
作用:解析一个 TypeScript 属性声明,得到属性名和冒号后的内容。
数据流:进去的是字段文本 → 先解析属性名,再找冒号 → 返回名字和类型/值部分。
调用关系:extract_method_from_arm 用它读取对象分支里的 method 字段。
调用图:调用 1 个内部函数(parse_property_name);被 1 处调用(extract_method_from_arm)。
strip_leading_block_comments808–819 ↗
fn strip_leading_block_comments(input: &str) -> &str
作用:去掉字段前面的块注释,方便识别真正的字段名。
数据流:进去的是字段文本 → 循环剥掉开头的 / ... / 注释和空白 → 返回剩余文本切片。
调用关系:filter_experimental_type_fields_ts_contents 在判断字段名之前用它,避免文档注释干扰。
parse_property_name821–855 ↗
fn parse_property_name(input: &str) -> Option<String>
作用:从 TypeScript 属性声明开头解析字段名,支持普通标识符和字符串字段名。
数据流:进去的是字段文本 → 识别 'name':、"name": 或 name?: 这类写法 → 返回字段名或空。
调用关系:parse_property 和实验字段过滤都会用它。
调用图:调用 2 个内部函数(is_ident_char, parse_string_literal);被 1 处调用(parse_property)。
parse_string_literal857–880 ↗
fn parse_string_literal(input: &str) -> Option<(String, usize)>
作用:解析单引号或双引号包住的字符串字面量。
数据流:进去的是文本 → 判断开头是不是引号,处理反斜杠转义,找到结束引号 → 返回字符串内容和消耗长度。
调用关系:extract_method_from_arm 和 parse_property_name 用它读方法名或字符串属性名。
调用图:被 2 处调用(extract_method_from_arm, parse_property_name)。
is_ident_char882–884 ↗
fn is_ident_char(ch: char) -> bool
作用:判断字符能不能作为简单 TypeScript 标识符的一部分。
数据流:进去的是一个字符 → 检查是否是 ASCII 字母数字或下划线 → 返回真假。
调用关系:parse_property_name 用它扫描普通字段名。
调用图:被 1 处调用(parse_property_name)。
ScanState::observe897–963 ↗
fn observe(&mut self, ch: char)
作用:文本扫描时吃进一个字符,更新当前是否在字符串、注释或括号里的状态。
数据流:进去的是一个字符和当前扫描状态 → 根据字符更新转义、注释、字符串定界符和括号深度 → 状态对象被原地更新。
调用关系:find_top_level_brace_span 和 split_top_level_multi 逐字符扫描时依赖它,不然会误判嵌套结构。
ScanState::in_ignored_syntax965–967 ↗
fn in_ignored_syntax(&self) -> bool
作用:告诉调用者当前是否处在字符串或注释里,这些地方的符号不该被当作代码结构。
数据流:进去的是扫描状态 → 查看 string_delim、block_comment、line_comment → 返回是否应忽略当前语法符号。
调用关系:顶层括号查找和顶层分隔符拆分都会先问它,再决定是否处理当前字符。
Depth::is_top_level979–981 ↗
fn is_top_level(&self) -> bool
作用:判断当前是否不在任何括号、方括号、圆括号或尖括号里面。
数据流:进去的是 Depth → 检查四种深度是否全为 0 → 返回是否处于最外层。
调用关系:ScanState 的使用者用它确认某个分隔符或大括号是不是顶层结构。
build_schema_bundle984–1065 ↗
fn build_schema_bundle(schemas: Vec<GeneratedSchema>) -> Result<Value>
作用:把很多单独的 schema 合成一个总 schema 包,并处理命名空间和引用关系。
数据流:进去的是 GeneratedSchema 列表 → 收集命名空间类型,重写 $ref,抽出子 definitions,插入根或命名空间下,补标题注解 → 返回一个带 definitions 的总 JSON Schema。
调用关系:generate_json_with_experimental 用它生成 codex_app_server_protocol.schemas.json;相关测试直接验证它的引用重写行为。
调用图:调用 7 个内部函数(annotate_schema, collect_namespaced_types, insert_into_namespace, namespace_for_definition, rewrite_named_ref_to_namespace, rewrite_refs_to_known_namespaces, rewrite_refs_to_namespace);被 4 处调用(generate_json_with_experimental, build_schema_bundle_rewrites_root_helper_refs_to_namespaced_defs, stable_schema_filter_removes_mock_experimental_method, stable_schema_filter_removes_mock_thread_start_field);外部调用 4 个(new, Object, String, new)。
build_flat_v2_schema1079–1127 ↗
fn build_flat_v2_schema(bundle: &Value) -> Result<Value>
作用:从混合总包里做出一个更适合 v2 代码生成工具使用的扁平 schema。
数据流:进去的是总 schema → 取 definitions.v2 下的类型放到根 definitions,同时加入共享根类型和它们依赖的非 v2 类型,重写 v2 引用并校验 → 返回标题带 V2 的扁平包。
调用关系:generate_json_with_experimental 用它写 v2 专用 schema;测试验证它不会丢掉共享请求和通知。
调用图:调用 5 个内部函数(collect_definition_dependencies, collect_non_v2_refs, ensure_no_ref_prefix, ensure_referenced_definitions_present, rewrite_ref_prefix);被 2 处调用(generate_json_with_experimental, build_flat_v2_schema_keeps_shared_root_schemas_and_dependencies);外部调用 6 个(new, new, Object, String, anyhow!, format!)。
collect_non_v2_refs1129–1133 ↗
fn collect_non_v2_refs(value: &Value) -> HashSet<String>
作用:收集一个 schema 里引用到的非 v2 根 definitions 名字。
数据流:进去的是 JSON 值 → 递归查找 $ref,挑出 #/definitions/xxx 且不是 #/definitions/v2/xxx 的引用 → 返回名字集合。
调用关系:build_flat_v2_schema 和 collect_definition_dependencies 用它找共享类型的依赖。
调用图:调用 1 个内部函数(collect_non_v2_refs_inner);被 2 处调用(build_flat_v2_schema, collect_definition_dependencies);外部调用 1 个(new)。
collect_non_v2_refs_inner1135–1155 ↗
fn collect_non_v2_refs_inner(value: &Value, refs: &mut HashSet<String>)
作用:递归执行非 v2 引用收集的具体遍历。
数据流:进去的是 JSON 值和集合 → 遇到对象就检查 $ref,再继续看子值;遇到数组就逐项处理 → 集合被填充。
调用关系:collect_non_v2_refs 是它的外层包装,负责创建集合。
调用图:被 1 处调用(collect_non_v2_refs)。
collect_definition_dependencies1157–1177 ↗
fn collect_definition_dependencies(
definitions: &Map<String, Value>,
names: HashSet<String>,
) -> HashSet<String>
作用:从一组 definitions 名字出发,找出它们继续引用的所有非 v2 依赖。
数据流:进去的是 definitions 映射和起始名字集合 → 用队列逐个展开引用,避免重复 → 返回完整依赖集合。
调用关系:build_flat_v2_schema 用它把共享根类型需要的辅助类型也带进扁平包。
调用图:调用 1 个内部函数(collect_non_v2_refs);被 1 处调用(build_flat_v2_schema);外部调用 2 个(new, get)。
rewrite_ref_prefix1179–1196 ↗
fn rewrite_ref_prefix(value: &mut Value, prefix: &str, replacement: &str)
作用:把 schema 里的某类 $ref 前缀整体替换成另一个前缀。
数据流:进去的是可修改 JSON、旧前缀和新前缀 → 递归查找字符串 $ref 并替换 → JSON 引用路径被更新。
调用关系:build_flat_v2_schema 用它把 #/definitions/v2/Type 改成 #/definitions/Type。
调用图:被 1 处调用(build_flat_v2_schema)。
ensure_no_ref_prefix1198–1205 ↗
fn ensure_no_ref_prefix(value: &Value, prefix: &str, label: &str) -> Result<()>
作用:确认 schema 里已经没有某种不该出现的引用前缀。
数据流:进去的是 schema、前缀和标签 → 找第一个匹配引用 → 没找到就成功,找到就返回错误说明。
调用关系:build_flat_v2_schema 在重写 v2 引用后调用它,防止留下悬空的命名空间引用。
调用图:调用 1 个内部函数(first_ref_with_prefix);被 1 处调用(build_flat_v2_schema);外部调用 1 个(anyhow!)。
first_ref_with_prefix1207–1223 ↗
fn first_ref_with_prefix(value: &Value, prefix: &str) -> Option<String>
作用:找出 JSON 树里第一个以指定前缀开头的 $ref。
数据流:进去的是 JSON 值和前缀 → 深度优先检查对象和数组 → 返回第一个匹配引用或空。
调用关系:ensure_no_ref_prefix 用它做校验;测试也间接验证扁平 v2 包没有旧前缀。
调用图:被 1 处调用(ensure_no_ref_prefix)。
ensure_referenced_definitions_present1225–1241 ↗
fn ensure_referenced_definitions_present(schema: &Value, label: &str) -> Result<()>
作用:检查 schema 中所有 #/definitions/... 引用都有对应定义。
数据流:进去的是 schema 和标签 → 取 definitions,收集缺失名字 → 没缺失返回成功,有缺失返回列名错误。
调用关系:build_flat_v2_schema 最后调用它,避免生成的包引用了不存在的类型。
调用图:调用 1 个内部函数(collect_missing_definitions);被 1 处调用(build_flat_v2_schema);外部调用 3 个(new, get, anyhow!)。
collect_missing_definitions1243–1269 ↗
fn collect_missing_definitions(
value: &Value,
definitions: &Map<String, Value>,
missing: &mut HashSet<String>,
)
作用:递归查找 schema 中引用了但 definitions 里没有的名字。
数据流:进去的是 JSON 值、definitions 映射和缺失集合 → 遇到 $ref 就取定义名并检查是否存在,再继续遍历子值 → 缺失集合被填充。
调用关系:ensure_referenced_definitions_present 用它完成实际检查。
调用图:被 1 处调用(ensure_referenced_definitions_present);外部调用 1 个(contains_key)。
insert_into_namespace1271–1286 ↗
fn insert_into_namespace(
definitions: &mut Map<String, Value>,
namespace: &str,
name: String,
schema: Value,
) -> Result<()>
作用:把一个 schema 定义插入 definitions 的某个命名空间下。
数据流:进去的是 definitions、命名空间名、类型名和 schema → 找到或创建命名空间对象,再插入定义 → 成功时 definitions 多一个命名空间内条目。
调用关系:build_schema_bundle 在处理 v2 这类命名空间 schema 时调用它;插入冲突检查交给 insert_definition。
调用图:调用 1 个内部函数(insert_definition);被 1 处调用(build_schema_bundle);外部调用 3 个(entry, anyhow!, format!)。
insert_definition1288–1314 ↗
fn insert_definition(
definitions: &mut Map<String, Value>,
name: String,
schema: Value,
location: &str,
) -> Result<()>
作用:向 definitions 映射插入一个定义,并阻止名字冲突。
数据流:进去的是映射、名字、schema 和位置说明 → 如果同名且内容相同就忽略,同名但内容不同就报错,否则插入 → 映射安全更新。
调用关系:insert_into_namespace 用它确保命名空间内不会悄悄覆盖不同 schema。
调用图:被 1 处调用(insert_into_namespace);外部调用 4 个(get, insert, get, anyhow!)。
write_json_schema_with_return1316–1361 ↗
fn write_json_schema_with_return(out_dir: &Path, name: &str) -> Result<GeneratedSchema>
作用:为某个 Rust 类型生成 JSON Schema 文件,并返回后续合包需要的 GeneratedSchema 信息。
数据流:进去的是输出目录和类型名 → 解析命名空间,生成 schema,按规则剔除部分 v1 变体、注解、检查命名冲突,写入对应 .json 文件 → 返回 GeneratedSchema。
调用关系:generate_json_with_experimental 通过多个 emitter 调用它;write_json_schema 也是它的公开包装。
调用图:调用 7 个内部函数(annotate_schema, enforce_numbered_definition_collision_overrides, ensure_dir, split_namespace, strip_v1_client_request_variants_from_json_schema, strip_v1_server_notification_variants_from_json_schema, write_pretty_json);外部调用 4 个(join, format!, schema_for!, to_value)。
enforce_numbered_definition_collision_overrides1363–1370 ↗
fn enforce_numbered_definition_collision_overrides(schema_name: &str, schema: &mut Value)
作用:检查生成 schema 里有没有 Type、Type1 这种可能由重名导致的危险定义。
数据流:进去的是 schema 名和 schema JSON → 查看 definitions 和 $defs → 发现数字后缀碰撞就触发检测报错。
调用关系:write_json_schema_with_return 在写文件前调用它,逼开发者显式重命名冲突类型。
调用图:调用 1 个内部函数(detect_numbered_definition_collisions);被 1 处调用(write_json_schema_with_return);外部调用 1 个(get)。
strip_v1_client_request_variants_from_json_schema1372–1375 ↗
fn strip_v1_client_request_variants_from_json_schema(schema: &mut Value)
作用:从 ClientRequest 的 JSON Schema 里移除只该留在 v1 兼容目录里的旧请求方法。
数据流:进去的是 ClientRequest schema → 准备 v1 方法集合 → 调用通用方法变体删除器。
调用关系:write_json_schema_with_return 在处理 ClientRequest 时调用它,避免总包暴露不该进代码生成的旧变体。
调用图:调用 1 个内部函数(strip_method_variants_from_json_schema);被 1 处调用(write_json_schema_with_return)。
strip_v1_server_notification_variants_from_json_schema1377–1383 ↗
fn strip_v1_server_notification_variants_from_json_schema(schema: &mut Value)
作用:从 ServerNotification 的 JSON Schema 里移除不该进入 JSON 输出的通知方法。
数据流:进去的是 ServerNotification schema → 准备要移除的方法集合 → 调用通用方法变体删除器。
调用关系:write_json_schema_with_return 在处理 ServerNotification 时调用它。
调用图:调用 1 个内部函数(strip_method_variants_from_json_schema);被 1 处调用(write_json_schema_with_return)。
strip_method_variants_from_json_schema1385–1403 ↗
fn strip_method_variants_from_json_schema(schema: &mut Value, methods_to_remove: &HashSet<&str>)
作用:从 schema 的 oneOf 变体中删除指定方法,并清理不再被引用的本地 definitions。
数据流:进去的是 schema 和要删除的方法集合 → 删除 oneOf 中 method 命中的变体,重新计算还可到达的本地定义,只保留这些定义 → schema 更小且无废定义。
调用关系:strip_v1_client_request_variants_from_json_schema 和 strip_v1_server_notification_variants_from_json_schema 都复用它。
调用图:调用 1 个内部函数(reachable_local_definitions);被 2 处调用(strip_v1_client_request_variants_from_json_schema, strip_v1_server_notification_variants_from_json_schema);外部调用 1 个(as_object_mut)。
is_method_variant_in_set1405–1419 ↗
fn is_method_variant_in_set(value: &Value, methods: &HashSet<&str>) -> bool
作用:判断一个 schema 变体的 method 字面量是否在指定集合中。
数据流:进去的是变体 JSON 和方法集合 → 找 properties.method,再读 const 或 enum 字面量 → 返回是否命中。
调用关系:strip_method_variants_from_json_schema 用它决定哪些 oneOf 分支要删。
调用图:调用 1 个内部函数(string_literal)。
reachable_local_definitions1421–1436 ↗
fn reachable_local_definitions(schema: &Value, defs_key: &str) -> HashSet<String>
作用:找出一个 schema 里仍然会被引用到的本地 definitions。
数据流:进去的是 schema 和 definitions 键名 → 先从根部收集引用,再沿引用链继续展开 → 返回可达定义名集合。
调用关系:strip_method_variants_from_json_schema 删除方法分支后用它清掉孤儿定义。
调用图:调用 2 个内部函数(collect_local_definition_refs, collect_local_definition_refs_excluding_maps);被 1 处调用(strip_method_variants_from_json_schema);外部调用 3 个(new, get, new)。
collect_local_definition_refs_excluding_maps1438–1461 ↗
fn collect_local_definition_refs_excluding_maps(
value: &Value,
defs_key: &str,
queue: &mut Vec<String>,
reachable: &mut HashSet<String>,
)
作用:从 schema 根部收集本地定义引用,但跳过 definitions 这些定义仓库本身。
数据流:进去的是 JSON 值、defs 键名、队列和已见集合 → 遍历非 definitions 区域并收集 $ref → 队列获得第一批可达定义。
调用关系:reachable_local_definitions 用它启动可达性分析。
调用图:调用 1 个内部函数(collect_local_definition_ref_here);被 1 处调用(reachable_local_definitions)。
collect_local_definition_refs1463–1483 ↗
fn collect_local_definition_refs(
value: &Value,
defs_key: &str,
queue: &mut Vec<String>,
reachable: &mut HashSet<String>,
)
作用:从某个已可达定义内部继续收集它引用的其他本地定义。
数据流:进去的是 JSON 值、defs 键名、队列和已见集合 → 收集当前节点和所有子节点里的本地 $ref → 新依赖进入队列。
调用关系:reachable_local_definitions 在展开引用链时反复调用它。
调用图:调用 1 个内部函数(collect_local_definition_ref_here);被 1 处调用(reachable_local_definitions)。
collect_local_definition_ref_here1485–1505 ↗
fn collect_local_definition_ref_here(
value: &Value,
defs_key: &str,
queue: &mut Vec<String>,
reachable: &mut HashSet<String>,
)
作用:检查当前 JSON 节点本身是不是一个本地 definitions 引用。
数据流:进去的是 JSON 值、defs 键名、队列和已见集合 → 如果 $ref 形如 #/definitions/Name,就记录 Name 并入队 → 集合和队列可能增加。
调用关系:两个本地引用收集函数都调用它,作为最小判断单元。
调用图:被 2 处调用(collect_local_definition_refs, collect_local_definition_refs_excluding_maps);外部调用 2 个(as_object, format!)。
detect_numbered_definition_collisions1507–1522 ↗
fn detect_numbered_definition_collisions(
schema_name: &str,
defs_key: &str,
defs: &Map<String, Value>,
)
作用:发现 schema 生成器自动加数字后缀造成的潜在类型名碰撞,并直接报错。
数据流:进去的是 schema 名、容器名和 definitions → 遍历键名,若 Type1 这类名字的基础 Type 也存在就 panic → 迫使开发者修正命名。
调用关系:enforce_numbered_definition_collision_overrides 调用它检查 definitions 和 $defs。
调用图:被 1 处调用(enforce_numbered_definition_collision_overrides);外部调用 3 个(contains_key, keys, panic!)。
write_json_schema1524–1529 ↗
fn write_json_schema(out_dir: &Path, name: &str) -> Result<GeneratedSchema>
作用:生成并写出某个 Rust 类型的 JSON Schema,是不关心内部返回细节时用的简便包装。
数据流:进去的是输出目录和类型名 → 调用 write_json_schema_with_return → 返回 GeneratedSchema 或错误。
调用关系:generate_internal_json_schema 会用它写内部类型;实际工作都由 write_json_schema_with_return 完成。
write_pretty_json1531–1536 ↗
fn write_pretty_json(path: PathBuf, value: &impl Serialize) -> Result<()>
作用:把 JSON 值用缩进格式写到文件里,方便人看和版本管理比较。
数据流:进去的是路径和可序列化数据 → 转成漂亮 JSON 字节 → 写入文件。
调用关系:JSON 导出、过滤后重写和单个 schema 写入都调用它。
调用图:被 3 处调用(filter_experimental_json_files, generate_json_with_experimental, write_json_schema_with_return);外部调用 2 个(write, to_vec_pretty)。
split_namespace1539–1542 ↗
fn split_namespace(name: &str) -> (Option<&str>, &str)
作用:把 v2::Type 这种名字拆成命名空间和真实类型名。
数据流:进去的是类型名字符串 → 按第一个 :: 拆分 → 返回可选命名空间和剩余名字。
调用关系:write_json_schema_with_return 用它决定 schema 文件该写到根目录还是 v2 子目录。
调用图:被 1 处调用(write_json_schema_with_return)。
rewrite_refs_to_namespace1546–1568 ↗
fn rewrite_refs_to_namespace(value: &mut Value, ns: &str)
作用:把 schema 内部的本地 definitions 引用改成带命名空间的引用。
数据流:进去的是可修改 JSON 和命名空间名 → 递归查找 #/definitions/X → 改成 #/definitions/ns/X,已带该命名空间的跳过。
调用关系:build_schema_bundle 在处理命名空间 schema 时调用它,确保引用指向最终合包里的正确位置。
调用图:被 1 处调用(build_schema_bundle);外部调用 1 个(format!)。
rewrite_refs_to_known_namespaces1580–1605 ↗
fn rewrite_refs_to_known_namespaces(value: &mut Value, types: &HashMap<String, String>)
作用:把根层共享 schema 里指向某些已知命名空间类型的裸引用改到正确命名空间。
数据流:进去的是 JSON 和类型到命名空间的表 → 递归查看 $ref 的定义名,若该类型属于 v2 等命名空间就重写路径 → 避免引用悬空。
调用关系:build_schema_bundle 处理非命名空间 schema 时调用它。
调用图:调用 1 个内部函数(namespace_for_definition);被 1 处调用(build_schema_bundle);外部调用 2 个(new, format!)。
collect_namespaced_types1607–1627 ↗
fn collect_namespaced_types(schemas: &[GeneratedSchema]) -> HashMap<String, String>
作用:收集哪些类型名属于哪个命名空间。
数据流:进去的是 GeneratedSchema 列表 → 对每个有 namespace 的 schema,记录它自己和内部 definitions/$defs 的名字 → 返回类型名到命名空间的表。
调用关系:build_schema_bundle 先调用它,再据此重写跨命名空间引用。
调用图:被 1 处调用(build_schema_bundle);外部调用 1 个(new)。
namespace_for_definition1629–1641 ↗
fn namespace_for_definition(
name: &str,
types: &'a HashMap<String, String>,
) -> Option<&'a String>
作用:根据定义名查它属于哪个命名空间,也兼容 Type1 这种数字后缀名。
数据流:进去的是定义名和类型命名空间表 → 先直接查,查不到就去掉末尾数字再查 → 返回命名空间或空。
调用关系:build_schema_bundle 和 rewrite_refs_to_known_namespaces 用它判断引用该指向哪里。
调用图:被 2 处调用(build_schema_bundle, rewrite_refs_to_known_namespaces)。
variant_definition_name1643–1683 ↗
fn variant_definition_name(base: &str, variant: &Value) -> Option<String>
作用:给 oneOf/anyOf 里的匿名变体自动起一个稳定、好懂的 title。
数据流:进去的是基础类型名和变体 schema → 看 method、type 或唯一属性/required 字段,转成 PascalCase 后拼成名字 → 返回候选名称。
调用关系:annotate_variant_list 用它给没有 title 的联合分支补名字。
调用图:调用 2 个内部函数(literal_from_property, to_pascal_case);被 1 处调用(annotate_variant_list);外部调用 2 个(get, format!)。
literal_from_property1685–1687 ↗
fn literal_from_property(props: &'a Map<String, Value>, key: &str) -> Option<&'a str>
作用:从 properties 里的某个字段 schema 读取字符串字面量。
数据流:进去的是 properties 映射和字段名 → 取该字段并交给 string_literal → 返回 const 或 enum 里的字符串。
调用关系:variant_definition_name 和 variant_title_collision_key 用它识别 method、type 等判别字段。
调用图:被 2 处调用(variant_definition_name, variant_title_collision_key);外部调用 1 个(get)。
string_literal1689–1697 ↗
fn string_literal(value: &Value) -> Option<&str>
作用:从 schema 里读取一个字符串常量,支持 const 和单值 enum 两种写法。
数据流:进去的是 JSON 值 → 先看 const,再看 enum 的第一个元素 → 返回字符串或空。
调用关系:判断方法变体、设置判别字段 title、生成碰撞信息时都会用它。
调用图:被 3 处调用(is_method_variant_in_set, set_discriminator_titles, variant_title_collision_key);外部调用 1 个(get)。
annotate_schema1699–1709 ↗
fn annotate_schema(value: &mut Value, base: Option<&str>)
作用:递归给 schema 补充对代码生成更友好的 title 信息。
数据流:进去的是可修改 JSON 和可选基础名 → 如果是对象就交给 annotate_object,如果是数组就逐项处理 → schema 中更多匿名结构有了名字。
调用关系:build_schema_bundle 和 write_json_schema_with_return 会调用它;递归过程也会反复回到它。
调用图:调用 1 个内部函数(annotate_object);被 4 处调用(annotate_object, annotate_variant_list, build_schema_bundle, write_json_schema_with_return)。
annotate_object1711–1764 ↗
fn annotate_object(map: &mut Map<String, Value>, base: Option<&str>)
作用:处理一个 schema 对象,给判别字段和联合变体补 title,并继续深入子结构。
数据流:进去的是 JSON 对象和基础名 → 根据 title 给 method/type 等字段补标题,处理 oneOf/anyOf、definitions、properties、items 等 → 对象和子对象都被注解。
调用关系:annotate_schema 遇到对象时调用它;它再把联合列表交给 annotate_variant_list。
调用图:调用 3 个内部函数(annotate_schema, annotate_variant_list, set_discriminator_titles);被 1 处调用(annotate_schema);外部调用 3 个(get, get_mut, iter_mut)。
annotate_variant_list1766–1805 ↗
fn annotate_variant_list(variants: &mut [Value], base: Option<&str>)
作用:给 oneOf 或 anyOf 的每个变体补名字,并检查自动名字是否撞车。
数据流:进去的是变体数组和基础名 → 先收集已有 title,再给无 title 的变体生成候选名,碰撞就 panic,最后设置判别字段标题并递归注解 → 变体更适合代码生成。
调用关系:annotate_object 处理联合类型时调用它;它用 variant_definition_name、variant_title_collision_key 和 set_discriminator_titles。
调用图:调用 5 个内部函数(annotate_schema, set_discriminator_titles, variant_definition_name, variant_title, variant_title_collision_key);被 1 处调用(annotate_object);外部调用 5 个(new, String, iter, iter_mut, panic!)。
variant_title_collision_key1807–1847 ↗
fn variant_title_collision_key(base: &str, generated_name: &str, variant: &Value) -> String
作用:当自动生成的变体名字撞车时,拼出一段足够定位问题的错误说明。
数据流:进去的是基础名、生成名和变体 JSON → 收集 method/type 等判别字段、其他字面量、唯一属性或 required 信息 → 返回一串诊断文本。
调用关系:annotate_variant_list 检测到命名冲突时调用它,然后 panic 提醒开发者修 schema。
调用图:调用 2 个内部函数(literal_from_property, string_literal);被 1 处调用(annotate_variant_list);外部调用 3 个(get, format!, vec!)。
set_discriminator_titles1851–1864 ↗
fn set_discriminator_titles(props: &mut Map<String, Value>, owner: &str)
作用:给 method、type、status 等判别字段的常量 schema 补上 title。
数据流:进去的是 properties 映射和所属类型名 → 找到判别字段且它是字符串字面量、还没有 title → 写入 OwnerMethod 这类标题。
调用关系:annotate_object 和 annotate_variant_list 都调用它,让下游代码生成器能给这些字段起稳定名字。
调用图:调用 2 个内部函数(string_literal, to_pascal_case);被 2 处调用(annotate_object, annotate_variant_list);外部调用 3 个(get_mut, String, format!)。
variant_title1866–1871 ↗
fn variant_title(value: &Value) -> Option<&str>
作用:读取一个 schema 变体已有的 title。
数据流:进去的是 JSON 值 → 如果是对象并有字符串 title 就返回 → 否则返回空。
调用关系:annotate_variant_list 用它收集已有名字和判断是否需要自动命名。
调用图:被 1 处调用(annotate_variant_list);外部调用 1 个(as_object)。
to_pascal_case1873–1892 ↗
fn to_pascal_case(input: &str) -> String
作用:把 method-name 或 method_name 这类字符串转成 PascalCase,比如 MethodName。
数据流:进去的是字符串 → 遇到 _ 或 - 就让下个字符大写,其他字符按规则拼接 → 返回新字符串。
调用关系:variant_definition_name 和 set_discriminator_titles 用它生成类型名风格的标题。
调用图:被 2 处调用(set_discriminator_titles, variant_definition_name);外部调用 1 个(new)。
ensure_dir1894–1897 ↗
fn ensure_dir(dir: &Path) -> Result<()>
作用:确保输出目录存在,不存在就创建。
数据流:进去的是目录路径 → 调用 create_dir_all 创建整条路径 → 成功返回,失败带目录名报错。
调用关系:TypeScript、JSON、内部 schema 和命名空间文件写入前都会调用它。
调用图:被 4 处调用(generate_internal_json_schema, generate_json_with_experimental, generate_ts_with_options, write_json_schema_with_return);外部调用 1 个(create_dir_all)。
rewrite_named_ref_to_namespace1899–1924 ↗
fn rewrite_named_ref_to_namespace(value: &mut Value, ns: &str, name: &str)
作用:只把某个指定类型名的 $ref 改到指定命名空间下。
数据流:进去的是 JSON、命名空间和类型名 → 递归查找 #/definitions/Name 或其子路径 → 改成 #/definitions/ns/Name。
调用关系:build_schema_bundle 在发现某些抽出的子定义实际应放进命名空间时,用它修正原 schema 里的引用。
调用图:被 1 处调用(build_schema_bundle);外部调用 1 个(format!)。
prepend_header_if_missing1926–1946 ↗
ts_files_in1948–1961 ↗
fn ts_files_in(dir: &Path) -> Result<Vec<PathBuf>>
作用:列出某个目录第一层里的 TypeScript 文件。
数据流:进去的是目录路径 → 读取目录项,筛选扩展名 .ts 的普通文件并排序 → 返回路径列表。
调用关系:generate_index_ts 用它决定 index.ts 需要 re-export 哪些类型。
调用图:被 1 处调用(generate_index_ts);外部调用 3 个(new, new, read_dir)。
ts_files_in_recursive1963–1981 ↗
fn ts_files_in_recursive(dir: &Path) -> Result<Vec<PathBuf>>
作用:递归列出一个目录下所有 TypeScript 文件。
数据流:进去的是目录路径 → 用栈遍历子目录,收集 .ts 文件并排序 → 返回完整列表。
调用关系:generate_ts_with_options 用它补头和清理空格;filter_experimental_type_fields_ts 用它找要改写的类型文件。
调用图:被 2 处调用(filter_experimental_type_fields_ts, generate_ts_with_options);外部调用 4 个(new, new, read_dir, vec!)。
trim_trailing_whitespace_in_ts_files1983–1994 ↗
fn trim_trailing_whitespace_in_ts_files(paths: &[PathBuf]) -> Result<()>
作用:清理一批 TypeScript 文件每行末尾多余的空格和制表符。
数据流:进去的是文件路径列表 → 逐个读文件,调用 trim_trailing_line_whitespace,若内容变化就写回 → 文件更干净。
调用关系:generate_ts_with_options 在格式化之后调用它,保证生成结果稳定。
调用图:调用 1 个内部函数(trim_trailing_line_whitespace);被 1 处调用(generate_ts_with_options);外部调用 2 个(read_to_string, write)。
trim_trailing_line_whitespace1996–2007 ↗
fn trim_trailing_line_whitespace(content: &str) -> String
作用:清理一段文本每行结尾的空格和 tab。
数据流:进去的是文本 → 逐行保留换行符,只去掉行尾空白 → 返回新文本。
调用关系:trim_trailing_whitespace_in_ts_files 和测试夹具生成流程都会用它。
调用图:被 2 处调用(trim_trailing_whitespace_in_ts_files, generate_typescript_schema_fixture_subtree_for_tests);外部调用 1 个(with_capacity)。
generate_index_ts2011–2028 ↗
fn generate_index_ts(out_dir: &Path) -> Result<PathBuf>
作用:在输出目录生成 index.ts,让使用者能从一个入口导入所有类型。
数据流:进去的是输出目录 → 收集根目录 .ts 文件,判断 v2 目录是否有类型,生成导出语句并加文件头 → 写出 index.ts。
调用关系:generate_ts_with_options 在需要索引文件时调用它;内容生成由 index_ts_entries 和 generated_index_ts_with_header 完成。
调用图:调用 3 个内部函数(generated_index_ts_with_header, index_ts_entries, ts_files_in);被 1 处调用(generate_ts_with_options);外部调用 2 个(join, create)。
generate_index_ts_tree2030–2061 ↗
fn generate_index_ts_tree(tree: &mut BTreeMap<PathBuf, String>)
作用:在内存文件树中生成根 index.ts 和 v2/index.ts。
数据流:进去的是文件树 → 根据路径筛选根层和 v2 层 TypeScript 文件,生成导出内容 → 插入 index.ts 条目。
调用关系:测试夹具生成流程调用它,和磁盘版 generate_index_ts 保持行为一致。
调用图:调用 1 个内部函数(index_ts_entries);被 1 处调用(generate_typescript_schema_fixture_subtree_for_tests);外部调用 1 个(from)。
generated_index_ts_with_header2063–2068 ↗
fn generated_index_ts_with_header(content: String) -> String
作用:给 index.ts 的导出内容前面加上生成文件头。
数据流:进去的是 index 内容 → 预分配字符串,先放 GENERATED_TS_HEADER,再接原内容 → 返回完整文本。
调用关系:generate_index_ts 写磁盘 index.ts 前调用它。
调用图:被 1 处调用(generate_index_ts);外部调用 1 个(with_capacity)。
index_ts_entries2070–2091 ↗
fn index_ts_entries(paths: &[&Path], has_v2_ts: bool) -> String
作用:根据 TypeScript 文件列表生成一串 export type 语句。
数据流:进去的是路径列表和是否包含 v2 的标记 → 取文件名、跳过 index 和 EventMsg、排序去重,生成导出语句,必要时加 export * as v2 → 返回文本。
调用关系:generate_index_ts 和 generate_index_ts_tree 都用它生成索引内容。
调用图:被 2 处调用(generate_index_ts, generate_index_ts_tree);外部调用 3 个(iter, new, format!)。
tests::generated_ts_optional_nullable_fields_only_in_params2107–2327 ↗
fn generated_ts_optional_nullable_fields_only_in_params() -> Result<()>
作用:测试生成的 TypeScript 是否符合稳定公开规则,尤其是可选且可为 null 的字段只出现在允许的 Params 类型里。
数据流:进去的是测试夹具文件树 → 检查实验项是否已被过滤、index 是否不导出 EventMsg、扫描所有 .ts 中的 undefined 和 ?: T | null → 发现违规就测试失败。
调用关系:这是测试用例,调用 read_schema_fixture_subtree 和 schema_root 来读取已生成夹具,保护生成结果不要悄悄变坏。
调用图:调用 1 个内部函数(read_schema_fixture_subtree);外部调用 9 个(new, new, new, schema_root, assert!, assert_eq!, format!, matches!, from_utf8)。
tests::schema_root2329–2338 ↗
fn schema_root() -> Result<PathBuf>
作用:找到测试用 schema 夹具的根目录。
数据流:没有业务输入 → 通过资源查找定位 schema/typescript/index.ts,再向上取父目录 → 返回 schema 根路径。
调用关系:generated_ts_optional_nullable_fields_only_in_params 用它读取 TypeScript 夹具。
调用图:外部调用 1 个(find_resource!)。
tests::generate_ts_with_experimental_api_retains_experimental_entries2341–2369 ↗
fn generate_ts_with_experimental_api_retains_experimental_entries() -> Result<()>
作用:测试开启实验性 API 时,TypeScript 导出确实保留实验方法和实验字段。
数据流:进去的是测试运行环境 → 直接把若干类型导出成字符串,检查实验方法、参数、响应和字段是否存在 → 不存在则测试失败。
调用关系:它验证默认过滤之外的另一条路径:当 experimental_api 开启时不应删实验内容。
调用图:外部调用 4 个(export_to_string, export_to_string, export_to_string, assert_eq!)。
tests::stable_schema_filter_removes_mock_thread_start_field2372–2395 ↗
fn stable_schema_filter_removes_mock_thread_start_field() -> Result<()>
作用:测试稳定版 JSON Schema 会删掉 ThreadStartParams 里的模拟实验字段。
数据流:进去的是临时目录 → 写 ThreadStartParams schema,合成 bundle,执行实验过滤,再检查字段不存在 → 最后清理临时目录。
调用关系:它直接调用 build_schema_bundle 和 filter_experimental_schema,验证字段过滤逻辑。
调用图:调用 2 个内部函数(build_schema_bundle, filter_experimental_schema);外部调用 6 个(assert_eq!, format!, create_dir, remove_dir_all, temp_dir, vec!)。
tests::build_schema_bundle_rewrites_root_helper_refs_to_namespaced_defs2398–2477 ↗
fn build_schema_bundle_rewrites_root_helper_refs_to_namespaced_defs() -> Result<()>
作用:测试合包时根层辅助 schema 对 v2 类型的引用会被改到正确命名空间。
数据流:进去的是手写的几个 GeneratedSchema → 调用 build_schema_bundle → 检查 ThreadId、MessagePhase、UserInput 等引用路径是否变成 #/definitions/v2/...
调用关系:它保护 build_schema_bundle、rewrite_refs_to_known_namespaces 和相关命名空间逻辑。
调用图:调用 1 个内部函数(build_schema_bundle);外部调用 2 个(assert_eq!, vec!)。
tests::experimental_type_fields_ts_filter_handles_interface_shape2659–2694 ↗
fn experimental_type_fields_ts_filter_handles_interface_shape() -> Result<()>
作用:测试实验字段过滤器能处理 export interface 这种 TypeScript 形状。
数据流:进去的是临时目录中的 CustomParams.ts → 标记 unstableField 为实验字段并过滤 → 读取结果检查 unstableField 被删、稳定字段保留。
调用关系:它调用 filter_experimental_type_fields_ts,保护 interface 分支的解析逻辑。
调用图:调用 1 个内部函数(filter_experimental_type_fields_ts);外部调用 6 个(assert_eq!, format!, create_dir_all, read_to_string, write, temp_dir)。
tests::experimental_type_fields_ts_filter_keeps_imports_used_in_intersection_suffix2697–2738 ↗
fn experimental_type_fields_ts_filter_keeps_imports_used_in_intersection_suffix() -> Result<()>
作用:测试删除实验字段后,交叉类型后半段仍使用的 import 不会被误删。
数据流:进去的是带 import 和交叉类型的 Config.ts → 过滤 unstableField → 检查 JsonValue 和 Keep 的 import 仍然保留。
调用关系:它覆盖 filter_experimental_type_fields_ts_contents 和 prune_unused_type_imports 的配合边界。
调用图:调用 1 个内部函数(filter_experimental_type_fields_ts);外部调用 6 个(assert_eq!, format!, create_dir_all, read_to_string, write, temp_dir)。
tests::experimental_type_fields_ts_filter_handles_generated_command_params_shape2741–2803 ↗
fn experimental_type_fields_ts_filter_handles_generated_command_params_shape() -> Result<()>
作用:测试过滤器能处理真实生成的 CommandExecParams 这种带大量注释和字段的格式。
数据流:进去的是临时 CommandExecParams.ts → 把 permissionProfile 设为实验字段后过滤 → 检查该字段删除,同时 sandboxPolicy 和 import 仍保留。
调用关系:它验证 strip_leading_block_comments、split_top_level_multi 和字段名解析在复杂生成文本上能工作。
调用图:调用 1 个内部函数(filter_experimental_type_fields_ts);外部调用 6 个(assert_eq!, format!, create_dir_all, read_to_string, write, temp_dir)。
tests::stable_schema_filter_removes_mock_experimental_method2806–2818 ↗
fn stable_schema_filter_removes_mock_experimental_method() -> Result<()>
作用:测试稳定版 schema 会删掉模拟实验请求方法。
数据流:进去的是临时目录 → 生成 ClientRequest schema,合包并过滤,转成字符串检查不含 mock/experimentalMethod → 清理目录。
调用关系:它直接验证 prune_experimental_methods 和 remove_experimental_method_type_definitions 等过滤逻辑。
调用图:调用 2 个内部函数(build_schema_bundle, filter_experimental_schema);外部调用 7 个(assert_eq!, format!, create_dir, remove_dir_all, to_string, temp_dir, vec!)。
tests::generate_json_filters_experimental_fields_and_methods2821–2959 ↗
fn generate_json_filters_experimental_fields_and_methods() -> Result<()>
作用:测试完整 JSON 生成流程在默认稳定模式下会过滤实验字段、实验方法和实验类型文件。
数据流:进去的是临时输出目录 → 调用 generate_json_with_experimental(false),读取多个单文件 schema、总包和 v2 扁平包 → 检查实验内容不存在、关键稳定方法存在、v2 引用已扁平。
调用关系:它覆盖 generate_json_with_experimental 的端到端行为,包括写文件、过滤、合包和扁平 v2 schema。
调用图:调用 2 个内部函数(generate_json_with_experimental, read_json_value);外部调用 6 个(assert_eq!, format!, create_dir, read_to_string, remove_dir_all, temp_dir)。
tests::generate_json_includes_remote_control_methods_with_experimental_api2962–2987 ↗
fn generate_json_includes_remote_control_methods_with_experimental_api() -> Result<()>
作用:测试开启实验性 API 时,远程控制相关方法和 schema 文件会被保留下来。
数据流:进去的是临时目录 → 调用 generate_json_with_experimental(true),读取 ClientRequest.json 并检查远程控制方法名,还检查对应 v2 schema 文件存在 → 最后清理目录。
调用关系:它验证 generate_json_with_experimental 的实验开关确实生效,不会误删实验 API。
调用图:调用 1 个内部函数(generate_json_with_experimental);外部调用 6 个(assert!, format!, create_dir, read_to_string, remove_dir_all, temp_dir)。
app-server-protocol/src/schema_fixtures.rs源码 ↗
这个文件主要解决一个很实际的问题:项目里保存了一份 TypeScript 和 JSON 格式的 schema 样本,但代码一改,这些样本就可能过期。它提供了两类能力:一类是把磁盘上的 schema 文件读出来,并做一些标准化,比如 JSON 字段排序、TypeScript 换行统一,这样不同电脑生成的结果也能稳定比较;另一类是重新生成 schema 目录,先清空旧文件,再按当前 Rust 类型生成新的 TypeScript 和 JSON 文件。它还会收集 TypeScript 类型之间的依赖,像顺藤摸瓜一样,把一个类型用到的其它类型也一起导出来。这里的“fixture”就是测试用的固定样本,目的是让测试能发现“生成出来的接口说明书”和仓库里保存的版本是否一致。
read_schema_fixture_tree29–42 ↗
fn read_schema_fixture_tree(schema_root: &Path) -> Result<BTreeMap<PathBuf, Vec<u8>>>
作用:读取整个 schema 样本目录,把 TypeScript 和 JSON 两部分都收集起来。调用者可以用它一次拿到完整的“接口说明书快照”。
数据流:输入是 schema 根目录路径。它进入根目录下的 typescript 和 json 两个子目录,递归读取里面的文件,把路径前面加上 typescript 或 json 作为分类,最后输出一个按路径排序的文件表,内容是字节数据。
调用关系:它是读取完整 fixture 树的入口。具体的递归扫目录和读文件工作交给 collect_files_recursive,自己负责把两个子树合并成一份总清单。
调用图:调用 1 个内部函数(collect_files_recursive);外部调用 3 个(new, join, from)。
read_schema_fixture_subtree44–51 ↗
fn read_schema_fixture_subtree(
schema_root: &Path,
label: &str,
) -> Result<BTreeMap<PathBuf, Vec<u8>>>
作用:只读取 schema 下面某一个指定子目录,比如只读 typescript 或某个测试用目录。它适合测试只关心一小块 schema 的情况。
数据流:输入是 schema 根目录和一个子目录名字。它把两者拼成真正要读取的目录,然后递归读取其中所有文件,输出相对路径到文件内容的表;如果失败,会在错误信息里说明是哪个子树读取失败。
调用关系:它把读取任务交给 collect_files_recursive。调用图里显示 generated_ts_optional_nullable_fields_only_in_params 这个测试会用它来读取指定 fixture 子树。
调用图:调用 1 个内部函数(collect_files_recursive);被 1 处调用(generated_ts_optional_nullable_fields_only_in_params);外部调用 1 个(join)。
generate_typescript_schema_fixture_subtree_for_tests54–80 ↗
fn generate_typescript_schema_fixture_subtree_for_tests() -> Result<BTreeMap<PathBuf, Vec<u8>>>
作用:在测试里临时生成 TypeScript schema 样本,不需要真的写到磁盘。它让测试能拿“刚生成的结果”和仓库里的样本做比较。
数据流:它从几个协议入口类型开始,包括客户端请求、客户端通知、服务端请求、服务端通知。它收集这些类型本身和它们依赖的其它类型,过滤掉实验性接口,补上 index 文件,再去掉每行末尾多余空白,最后输出一个路径到文件字节内容的表。
调用关系:它是测试生成 TypeScript fixture 的总调度。它多次调用 collect_typescript_fixture_file 收集具体类型,又用 visit_typescript_fixture_dependencies 处理响应类型这类间接依赖,最后交给 filter_experimental_ts_tree、generate_index_ts_tree 和 trim_trailing_line_whitespace 做收尾整理。
调用图:调用 4 个内部函数(filter_experimental_ts_tree, generate_index_ts_tree, trim_trailing_line_whitespace, visit_typescript_fixture_dependencies);外部调用 2 个(new, new)。
write_schema_fixtures86–88 ↗
fn write_schema_fixtures(schema_root: &Path, prettier: Option<&Path>) -> Result<()>
作用:用默认选项重新生成 schema/typescript 和 schema/json。它是给开发工具用的简单入口。
数据流:输入是 schema 根目录,以及可选的 prettier 路径。它补上默认配置,然后把真正的重写工作交给 write_schema_fixtures_with_options;输出是成功或错误,不直接返回文件内容。
调用关系:它是 write_schema_fixtures_with_options 的简化包装。工具命令如果不需要特殊选项,就调用这个函数。
调用图:调用 1 个内部函数(write_schema_fixtures_with_options);外部调用 1 个(default)。
write_schema_fixtures_with_options91–113 ↗
fn write_schema_fixtures_with_options(
schema_root: &Path,
prettier: Option<&Path>,
options: SchemaFixtureOptions,
) -> Result<()>
作用:按指定选项重新生成 schema fixture。比如可以决定是否包含实验性 API,也就是还不稳定、可能变化的接口。
数据流:输入是 schema 根目录、可选的 prettier 路径和生成选项。它先定位 typescript 和 json 输出目录,清空并重建它们,然后调用 TypeScript 生成器和 JSON schema 生成器写入新文件;最后只返回成功或错误。
调用关系:它是实际重写 schema 的核心流程。write_schema_fixtures 会调用它;它把清目录交给 ensure_empty_dir,把具体生成交给 crate::generate_ts_with_options 和 crate::generate_json_with_experimental。
调用图:调用 2 个内部函数(default, ensure_empty_dir);被 1 处调用(write_schema_fixtures);外部调用 3 个(join, generate_json_with_experimental, generate_ts_with_options)。
ensure_empty_dir115–122 ↗
fn ensure_empty_dir(dir: &Path) -> Result<()>
作用:保证某个目录是空的、可写的。生成新 schema 前先这样做,可以避免旧文件残留,免得让人误以为旧接口还存在。
数据流:输入是一个目录路径。如果目录已经存在,它先整个删除;然后重新创建这个目录。输出是成功或带上下文的错误,并且磁盘上的目录会变成一个干净的新目录。
调用关系:它被 write_schema_fixtures_with_options 调用,属于生成前的清场步骤。后面的 TypeScript 和 JSON 生成器依赖它准备好的空目录。
调用图:被 1 处调用(write_schema_fixtures_with_options);外部调用 3 个(exists, create_dir_all, remove_dir_all)。
read_file_bytes124–150 ↗
fn read_file_bytes(path: &Path) -> Result<Vec<u8>>
作用:读取单个文件,并把内容整理成适合比较的稳定形式。这样测试不会因为换行、JSON 字段顺序这类小差异误报失败。
数据流:输入是文件路径。它先读出原始字节;如果是 JSON,就解析成 JSON 值,标准化排序后再漂亮地写回字节;如果是 TypeScript,就统一换行,并去掉标准生成头;其它文件则原样返回。
调用关系:它被 collect_files_recursive 在扫到每个文件时调用。JSON 的整理交给 canonicalize_json,TypeScript 的整理主要在本函数内完成。
调用图:调用 1 个内部函数(canonicalize_json);被 1 处调用(collect_files_recursive);外部调用 5 个(extension, from_utf8, from_slice, to_vec_pretty, read)。
canonicalize_json152–208 ↗
fn canonicalize_json(value: &Value) -> Value
作用:把 JSON 变成更稳定、更适合比较的样子。它不会改变 schema 的实际含义,只是尽量减少不同平台生成顺序不同带来的噪音。
数据流:输入是一个 JSON 值。遇到对象时,它按键名排序并递归整理子项;遇到数组时,它先整理每个元素,如果每个元素都能得到安全的排序钥匙,就排序,否则保留原顺序;其它普通值直接复制返回。
调用关系:它被 read_file_bytes 用来整理 JSON 文件。数组是否能排序由 schema_array_item_sort_key 判断,避免把本来有顺序含义的数组乱改。
调用图:调用 1 个内部函数(schema_array_item_sort_key);被 1 处调用(read_file_bytes);外部调用 6 个(with_capacity, Array, Object, clone, with_capacity, to_string)。
schema_array_item_sort_key210–227 ↗
fn schema_array_item_sort_key(item: &Value) -> Option<String>
作用:判断 JSON 数组里的某个元素能不能安全排序,并给它一个排序用的钥匙。它像给每个可排序物品贴标签。
数据流:输入是一个 JSON 数组元素。它会给 null、布尔值、数字、字符串生成稳定标签;如果是对象,只认带 $ref 或 title 的 schema 对象;如果是嵌套数组或不认识的对象,就返回没有排序钥匙。
调用关系:它只服务于 canonicalize_json。canonicalize_json 会要求数组里每个元素都有钥匙,才真的对这个数组排序。
调用图:被 1 处调用(canonicalize_json);外部调用 1 个(format!)。
collect_files_recursive229–268 ↗
fn collect_files_recursive(root: &Path) -> Result<BTreeMap<PathBuf, Vec<u8>>>
作用:递归收集一个目录下的所有普通文件。它是读取 fixture 目录的通用扫地机器人。
数据流:输入是根目录路径。它用一个待访问目录列表逐个扫描子目录,遇到目录就继续深入,遇到普通文件就算出相对路径并读取内容,最后输出相对路径到文件字节的有序表。
调用关系:read_schema_fixture_tree 和 read_schema_fixture_subtree 都靠它真正读取磁盘。它每发现一个文件,就把具体读取和标准化交给 read_file_bytes。
调用图:调用 1 个内部函数(read_file_bytes);被 2 处调用(read_schema_fixture_subtree, read_schema_fixture_tree);外部调用 4 个(new, metadata, read_dir, vec!)。
collect_typescript_fixture_file270–299 ↗
fn collect_typescript_fixture_file(
files: &mut BTreeMap<PathBuf, String>,
seen: &mut HashSet<TypeId>,
) -> Result<()>
作用:为某个 Rust 类型生成对应的 TypeScript 文件,并顺带收集它依赖的其它类型。这里的 Rust 类型可以理解成接口数据结构的源头。
数据流:输入是正在累积的文件表和已见过类型集合。它先问这个类型有没有输出路径;如果已经处理过就跳过;否则导出 TypeScript 字符串,规范化路径和换行后放进文件表,再用访问器继续寻找依赖类型;最后返回成功或生成错误。
调用关系:它是 TypeScript fixture 收集的核心工人。generate_typescript_schema_fixture_subtree_for_tests 会从几个顶层协议类型开始调用它;TypeScriptFixtureCollector::visit 在发现依赖类型时也会反过来调用它。
调用图:调用 1 个内部函数(normalize_relative_fixture_path);外部调用 3 个(export_to_string, output_path, visit_dependencies)。
normalize_relative_fixture_path301–303 ↗
fn normalize_relative_fixture_path(path: &Path) -> PathBuf
作用:把相对路径整理成当前平台可正常使用的 PathBuf。简单说,就是把类型导出的路径变成文件表里统一使用的路径格式。
数据流:输入是一个路径。它拆开路径组件再重新收集成 PathBuf,输出整理后的路径;不读取磁盘,也不改文件内容。
调用关系:它被 collect_typescript_fixture_file 调用,用在把 TypeScript 导出路径写入 fixture 文件表之前。
调用图:被 1 处调用(collect_typescript_fixture_file);外部调用 1 个(components)。
visit_typescript_fixture_dependencies305–320 ↗
fn visit_typescript_fixture_dependencies(
files: &mut BTreeMap<PathBuf, String>,
seen: &mut HashSet<TypeId>,
visit: impl FnOnce(&mut TypeScriptFixtureCollector<'_>),
) -> Result<()>
作用:专门运行一次“依赖访问”。有些类型不是直接导出的文件入口,但响应类型等依赖仍然需要被收集进 fixture。
数据流:输入是文件表、已见过类型集合,以及一段负责访问依赖的函数。它创建 TypeScriptFixtureCollector,把访问器交给那段函数使用;访问过程中如果收集类型出错,就把错误带出来。
调用关系:它被 generate_typescript_schema_fixture_subtree_for_tests 用来收集客户端响应类型和服务端响应类型。真正发现每个具体类型后,会通过 TypeScriptFixtureCollector::visit 继续进入 collect_typescript_fixture_file。
调用图:被 1 处调用(generate_typescript_schema_fixture_subtree_for_tests)。
TypeScriptFixtureCollector::visit329–334 ↗
fn visit(&mut self)
作用:这是 TypeScript 类型访问器的回调函数。每当生成器发现一个依赖类型时,它就尝试把这个类型也导出成 fixture 文件。
数据流:输入是访问到的某个类型。它先看之前是否已经发生错误;如果没有,就调用 collect_typescript_fixture_file 把这个类型加入文件表,并把可能的错误记下来,供外层统一处理。
调用关系:它实现了 ts_rs 的 TypeVisitor,也就是“遇到依赖类型时该怎么办”的规则。collect_typescript_fixture_file 和 visit_typescript_fixture_dependencies 创建的访问器,最终都会通过这个方法继续收集类型依赖。
tests::canonicalize_json_sorts_string_arrays343–347 ↗
fn canonicalize_json_sorts_string_arrays()
作用:测试 JSON 标准化会把字符串数组排序。它防止以后有人改坏这个稳定比较规则。
数据流:输入是一段测试里构造的 JSON 数组 ["b", "a"]。测试调用 canonicalize_json,期待输出变成 ["a", "b"];如果不是,测试失败。
调用关系:它是 canonicalize_json 的单元测试之一。它不参与正式运行,只在测试阶段检查数组排序这个小规则是否还有效。
调用图:外部调用 2 个(assert_eq!, json!)。
tests::canonicalize_json_sorts_schema_ref_arrays350–360 ↗
fn canonicalize_json_sorts_schema_ref_arrays()
作用:测试 JSON 标准化会按 $ref 排序 schema 引用数组。$ref 可以理解成 JSON schema 里“引用另一个定义”的地址。
数据流:输入是两个顺序相反的 $ref 对象。测试调用 canonicalize_json,期待它们按引用地址从 A 到 B 排好;如果结果不一致,测试失败。
调用关系:它是 canonicalize_json 和 schema_array_item_sort_key 配合行为的测试。它只在测试阶段运行,用来保证 schema 引用数组的比较结果稳定。
调用图:外部调用 2 个(assert_eq!, json!)。
传输与服务器约定衔接
最后一组涵盖传输层消息约定、远程控制传输协议、服务器错误整形,以及使用 app-server schema 的下游适配器。
app-server-transport/src/lib.rs源码 ↗
可以把这个文件想成一个服务台。真正干活的代码放在 outgoing_message 和 transport 两个内部模块里,但外部使用者不需要挨个去找它们。这个文件把连接编号、 outgoing message(要发出去的消息)、远程控制启动配置、控制 socket 路径、WebSocket 接收器、标准输入输出连接等常用东西集中转交出去。这样做的好处是:库的使用入口更稳定,内部文件以后怎么拆、怎么改,外部代码通常不用跟着改。它本身不处理网络、不收发消息,也不启动服务;它的重点是整理并公开“这个库允许别人用的工具箱”。
app-server-transport/src/outgoing_message.rs源码 ↗
这个文件像是服务器发信前用的标准信封和排队单。服务器可能要给客户端发四类东西:主动请求、通知、正常结果、错误结果。OutgoingMessage 把这些都包成一种统一的消息,方便后面的传输层一起处理和序列化,也就是转换成能在线路上传的 JSON 数据。ConnectionId 是连接编号,好比每个电话线的号码,用来稳定地区分不同客户端连接。QueuedOutgoingMessage 则是在真正写出去之前放进队列里的条目,里面除了消息本身,还可以带一个一次性通知器 oneshot(一种只能发一次信号的小通道),用来告诉等待的人“这条消息已经写完了”。如果没有这些统一类型,发送代码就要到处判断消息种类,也更容易把回复发错连接或漏掉写入完成的通知。
ConnectionId::fmt16–18 ↗
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
作用:把连接编号变成普通文字,方便写日志、报错或显示给人看。比如编号是 42,就显示成“42”。
数据流:进去的是一个 ConnectionId 和一个文字输出器 → 它取出里面的数字编号 → 把这个数字写成字符串输出,不改变连接编号本身。
调用关系:当代码需要把 ConnectionId 放进格式化文本时会自动用到它,比如日志里打印连接号。它只把活儿交给标准的 write! 写入机制,不参与实际网络发送。
调用图:外部调用 1 个(write!)。
QueuedOutgoingMessage::new52–57 ↗
fn new(message: OutgoingMessage) -> Self
作用:创建一条准备放进发送队列的外发消息。默认不附带“写完后通知我”的回执,适合普通的异步发送场景。
数据流:进去的是一条 OutgoingMessage → 它把这条消息装进 QueuedOutgoingMessage → 出来的是一个队列条目,其中写入完成通知为空,表示没人专门等这次写完。
调用关系:当服务器要把消息塞进外发队列时会用它,例如处理入站消息后需要回复,或测试慢连接、队列满、远程控制连接等场景时。它只是把消息包装好,后续真正排队、路由到哪个连接、以及写到网络上的工作由外层传输流程继续完成。
调用图:被 8 处调用(enqueue_incoming_message, shutdown_cancels_blocked_outbound_forwarding, remote_control_http_mode_enrolls_before_connecting, remote_control_transport_clears_outgoing_buffer_when_backend_acks, remote_control_transport_manages_virtual_clients_and_routes_messages, enqueue_incoming_request_does_not_block_when_writer_queue_is_full, broadcast_does_not_block_on_slow_connection, to_connection_stdio_waits_instead_of_disconnecting_when_writer_queue_is_full)。
app-server-transport/src/transport/remote_control/protocol.rs源码 ↗
远程控制要做两件事:先把本地服务器登记、配对到后端,再通过 WebSocket(一条长期保持的网络通道)来回传消息。这个文件把这些请求、响应、事件、信封都定义清楚。比如登记服务器要带名字、系统、版本和安装 ID;真正通信时,每条消息会带 client_id、stream_id、seq_id,像快递单号一样,方便知道是谁的消息、属于哪条会话、顺序到哪了。它还支持把大消息切成小块发送,并用 Ack(确认回执)告诉对方哪些块已经收到。另一个重要部分是 URL 规范化:用户给一个基础地址,它会补上具体接口路径,并把 https 自动变成 wss、http 自动变成 ws。它还严格限制地址:ChatGPT 正式或 staging 域名必须走 HTTPS;本机 localhost 才允许 HTTP,主要方便开发测试。
RemoteControlPairingStatusRequest::from71–82 ↗
fn from(code: RemoteControlPairingStatusCode) -> Self
作用:把一种配对码包装成查询配对状态时要发送的请求。它的用处是保证普通配对码和手动配对码不会同时乱填,只会填该填的那一个。
数据流:进去的是一个 RemoteControlPairingStatusCode,里面要么是 pairing_code,要么是 manual_pairing_code。函数看它是哪一种,然后生成 RemoteControlPairingStatusRequest:一种字段填上值,另一种字段留空。出来的是可以被序列化成网络请求的状态查询对象。
调用关系:它是配对流程里的小转换器。上层代码拿到某种配对码后,不需要自己记字段名,直接交给这个转换函数,就能得到后端接口期望的请求形状。
StreamId::new_random99–101 ↗
fn new_random() -> Self
作用:生成一个新的消息流 ID。消息流可以理解成一次独立对话或通道的编号,有了它,系统才能把不同会话的消息分开。
数据流:进去没有参数。它调用 uuid::Uuid::now_v7 生成一个几乎不会重复、还带时间顺序特点的 UUID(一种通用唯一编号),再转成字符串,包进 StreamId。出来的是一个新的 StreamId。
调用关系:当系统需要开启新的远程控制消息流时会用它。它把真正生成唯一编号的工作交给外部 UUID 库,自己只负责把结果变成这个协议里专用的 StreamId 类型。
调用图:外部调用 1 个(now_v7)。
ServerEvent::segment_id172–177 ↗
fn segment_id(&self) -> Option<usize>
作用:从服务器事件里取出“这是第几块消息”的编号。只有被切块发送的大消息才有这个编号,普通消息、确认回执和 Pong 都没有。
数据流:进去的是一个 ServerEvent。函数判断事件类型:如果是 ServerMessageChunk,就拿出 segment_id 并返回;如果是普通服务器消息、Ack 或 Pong,就返回空。它不修改任何东西。
调用关系:它服务于消息重连、确认和缓存清理这类流程。发送方或传输层想知道某个服务器事件是不是分块消息时,可以问这个函数,而不用自己拆开枚举判断。
is_allowed_remote_control_chatgpt_host193–201 ↗
fn is_allowed_remote_control_chatgpt_host(host: &Option<Host<&str>>) -> bool
作用:判断一个网址主机名是不是允许用于远程控制的 ChatGPT 域名。它的重点是安全:只认 chatgpt.com、chatgpt-staging.com 以及它们的子域名。
数据流:进去的是 URL 解析出来的 host,也就是网址里的主机部分。函数先确认它是普通域名,不是 IP 或空值;然后检查是否正好等于 chatgpt.com、chatgpt-staging.com,或者以 .chatgpt.com、.chatgpt-staging.com 结尾。出来是 true 或 false。
调用关系:normalize_remote_control_base_url 会调用它来把关远程控制地址。这样像 chatgpt.com.evil.com 这种看起来像但其实不是 ChatGPT 的地址会被拒绝。
调用图:被 1 处调用(normalize_remote_control_base_url)。
is_localhost203–210 ↗
fn is_localhost(host: &Option<Host<&str>>) -> bool
作用:判断一个网址是不是指向本机。localhost 通常用于开发和测试,所以规则会比线上域名宽一点,允许 HTTP。
数据流:进去的是 URL 的 host。函数检查三种情况:域名正好是 localhost,IPv4 地址是回环地址,或者 IPv6 地址是回环地址。符合就返回 true,否则返回 false。
调用关系:normalize_remote_control_base_url 会用它决定是否允许本机地址。它和 ChatGPT 域名检查一起,组成这个文件的 URL 安全门卫。
调用图:被 1 处调用(normalize_remote_control_base_url)。
normalize_remote_control_url212–258 ↗
fn normalize_remote_control_url(
remote_control_url: &str,
) -> io::Result<RemoteControlTarget>
作用:把用户给的远程控制基础地址变成一整套真正要用的接口地址。比如登记、刷新、配对、查询配对状态、WebSocket 通道,都由它统一拼好。
数据流:进去的是一个字符串形式的基础 URL。它先交给 normalize_remote_control_base_url 做格式和安全检查,并确保路径末尾有斜杠;然后在这个基础上拼出 enroll、refresh、pair、pair/status 和 WebSocket 连接地址。最后根据基础地址协议,把 https 改成 wss,把 http 改成 ws。出来的是 RemoteControlTarget,里面装着所有具体 URL;如果地址不合法,就返回 InvalidInput 错误。
调用关系:它是远程控制启动、登记、偏好保存、恢复配置等流程都会用到的入口型工具。load_or_enroll_server、persist_preference、enable、resolve_persisted_preference 等上层流程会先调用它,确认地址安全可用,再继续网络请求。
调用图:调用 1 个内部函数(normalize_remote_control_base_url);被 33 处调用(load_or_enroll_server, persist_preference, enable, resolve_persisted_preference, clearing_persisted_remote_control_enrollment_removes_only_matching_entry, enroll_remote_control_server_parse_failure_includes_response_body, persisted_remote_control_enrollment_round_trips_by_target_and_account, remote_control_enrollment_refreshes_server_token_before_expiry, normalize_remote_control_url_rejects_unsupported_urls, start_remote_control (+15 more))。
normalize_remote_control_base_url260–290 ↗
fn normalize_remote_control_base_url(remote_control_url: &str) -> io::Result<Url>
作用:检查并整理远程控制的基础 URL。它负责回答两个问题:这个地址写得对不对,以及这个地址是否允许连接。
数据流:进去的是用户填写的 URL 字符串。它先用 URL 解析器把字符串变成结构化 URL;如果路径末尾没有斜杠,就补上斜杠,避免后面拼路径时拼错。接着检查协议和主机:ChatGPT 相关域名必须是 HTTPS;localhost 可以是 HTTP 或 HTTPS;其他都拒绝。出来是整理好的 Url,或者一个说明清楚的 InvalidInput 错误。
调用关系:normalize_remote_control_url 会先调用它,再继续拼具体接口;environment_clients_url 也会用它拿到可靠的基础地址。它内部把域名判断交给 is_allowed_remote_control_chatgpt_host 和 is_localhost。
调用图:调用 2 个内部函数(is_allowed_remote_control_chatgpt_host, is_localhost);被 2 处调用(environment_clients_url, normalize_remote_control_url);外部调用 2 个(parse, format!)。
tests::normalize_remote_control_url_accepts_chatgpt_https_urls298–337 ↗
fn normalize_remote_control_url_accepts_chatgpt_https_urls()
作用:测试 ChatGPT 正式域名和 staging 子域名的 HTTPS 地址会被接受,并且能被拼成正确的一组远程控制接口地址。
数据流:进去的是测试里写死的两个合法 URL。测试调用 normalize_remote_control_url 得到结果,再用 assert_eq! 对比预期的 websocket_url、enroll_url、refresh_url、pair_url 和 pair_status_url。测试通过表示这些线上域名规则和拼接规则没有被改坏。
调用关系:它在测试阶段运行,保护 normalize_remote_control_url 的线上域名行为。这个测试不会服务线上请求,但能在开发改代码时及时发现 URL 规则被破坏。
调用图:外部调用 1 个(assert_eq!)。
tests::normalize_remote_control_url_accepts_localhost_urls340–376 ↗
fn normalize_remote_control_url_accepts_localhost_urls()
作用:测试 localhost 的 HTTP 和 HTTPS 地址都会被接受。这个很重要,因为本机开发和调试通常不会使用正式 HTTPS 环境。
数据流:进去的是测试中准备的 localhost 地址,包括 http://localhost:8080 和 https://localhost:8443。测试调用 normalize_remote_control_url,并检查生成的 HTTP 接口地址和 ws/wss WebSocket 地址是否符合预期。
调用关系:它在测试阶段运行,专门守住“localhost 可以用 HTTP”这个开发便利规则。它间接验证了 normalize_remote_control_url 和 normalize_remote_control_base_url 的配合。
调用图:外部调用 1 个(assert_eq!)。
tests::normalize_remote_control_url_rejects_unsupported_urls379–400 ↗
fn normalize_remote_control_url_rejects_unsupported_urls()
作用:测试不该被信任的 URL 会被拒绝。它主要防止安全规则被放松,比如普通 http 的 chatgpt.com、假冒域名、非 localhost 的地址等。
数据流:进去的是一组测试里列出的非法 URL。对每个 URL,测试调用 normalize_remote_control_url,要求它返回错误;然后检查错误类型是 InvalidInput,并且错误文字说明只能使用 ChatGPT HTTPS 地址或 localhost HTTP/HTTPS 地址。
调用关系:它在测试阶段运行,是 URL 安全门禁的回归测试。它直接调用 normalize_remote_control_url,确保底层的域名检查和协议检查一起生效。
调用图:调用 1 个内部函数(normalize_remote_control_url);外部调用 1 个(assert_eq!)。
app-server/src/error_code.rs源码 ↗
这个文件像一本“报错用语表”。服务器和客户端通过 JSON-RPC 通信,JSON-RPC 可以理解成一种用 JSON 传请求和回复的通用规矩。出错时,回复里要有错误码、错误消息等固定字段。这里先定义了几种常见错误码,比如“请求格式不对”“方法不存在”“参数不对”“服务器内部出错”。然后给每种错误提供一个小函数,调用者只要传一句说明文字,就能得到一个标准的 JSONRPCErrorError。真正拼装错误对象的活儿集中在 error 函数里:放入错误码、放入消息,并把附加数据 data 留空。这样做的好处是,其他文件不用记错误码,也不用重复写结构体,减少写错码、漏字段、格式不一致的问题。
invalid_request10–12 ↗
fn invalid_request(message: impl Into<String>) -> JSONRPCErrorError
作用:用来生成“请求本身不合法”的错误,比如客户端发来的 JSON-RPC 请求格式不对。别人用它时,只需要给一句人能看懂的错误说明。
数据流:进去的是一段错误说明文字 → 它把固定错误码 -32600 和这段文字交给通用的 error 函数 → 出来的是一个标准 JSON-RPC 错误对象,里面说明这是“无效请求”。它不改动外部状态。
调用关系:它是很多入口和请求处理流程会用到的报错出口,比如启动、发送控制消息、处理进程写入、监听请求、分发初始化后的客户端请求等。它自己不组装细节,而是把最后一步交给 error,保证格式统一。
调用图:调用 1 个内部函数(error);被 38 处调用(handle_thread_rollback_failed, send_control, start, command_no_longer_running_error, handle_process_write, watch, dispatch_initialized_client_request, config_write_error, map_fs_error, load_plugin_share_config_and_auth (+15 more))。
method_not_found14–16 ↗
fn method_not_found(message: impl Into<String>) -> JSONRPCErrorError
作用:用来生成“客户端请求的方法不存在或不支持”的错误。就像打电话拨了一个不存在的分机号,服务器要明确告诉客户端这个操作找不到。
数据流:进去的是一段解释文字 → 它配上固定错误码 -32601 → 调用 error 生成标准错误对象 → 返回给调用者。它只负责把“方法不存在”这个含义固定下来。
调用关系:它会被一些发现操作不支持的地方使用,比如线程相关列表、核心线程写入错误、不支持的线程存储操作等。它处在具体业务判断之后、错误回复生成之前,中间仍然依赖 error 来统一创建对象。
调用图:调用 1 个内部函数(error);被 3 处调用(thread_turns_items_list, core_thread_write_error, unsupported_thread_store_operation)。
invalid_params18–20 ↗
fn invalid_params(message: impl Into<String>) -> JSONRPCErrorError
作用:用来生成“参数不对”的错误,比如字段缺失、类型不对、终端尺寸不合法、启动进程的参数不合要求等。
数据流:进去的是描述参数哪里不对的文字 → 它使用固定错误码 -32602 → 交给 error 拼成 JSON-RPC 错误对象 → 返回给上层用于回复客户端。它不会修正参数,只负责把问题说清楚。
调用关系:它常出现在已经识别出方法、但检查参数失败的流程里,比如写入、写标准输入、解析终端大小、启动进程等。它把具体检查代码和统一错误格式连接起来,最后仍由 error 完成对象创建。
调用图:调用 1 个内部函数(error);被 5 处调用(write, terminal_size_from_protocol, write_stdin, process_spawn, terminal_size_from_protocol)。
internal_error22–24 ↗
fn internal_error(message: impl Into<String>) -> JSONRPCErrorError
作用:用来生成“服务器自己出问题了”的错误。也就是客户端请求可能没错,但服务器内部执行时遇到了失败。
数据流:进去的是内部失败的说明文字 → 它加上固定错误码 -32603 → 调用 error 生成标准 JSON-RPC 错误对象 → 返回给调用者,用来通知客户端发生了服务器端错误。
调用关系:它被许多比较底层或控制流程使用,比如启动失败、未初始化启动、发送响应失败、中止挂起请求、取消请求、转发客户端错误等。它通常位于异常路径上,把内部失败包装成客户端能理解的 JSON-RPC 错误。
调用图:调用 1 个内部函数(error);被 21 处调用(apply_bespoke_event_handling, start, start_uninitialized, send_response_as, abort_pending_server_requests, cancel_requests_for_thread_cancels_all_thread_requests, notify_client_error_forwards_error_to_waiter, send_error_routes_to_target_connection, map_error, map_fs_error (+11 more))。
error26–32 ↗
fn error(code: i64, message: impl Into<String>) -> JSONRPCErrorError
作用:这是所有错误辅助函数背后的统一“装配工”。它把错误码和错误说明装进 JSONRPCErrorError 这个标准错误结构里。
数据流:进去的是一个数字错误码和一段可转换成字符串的消息 → 它把消息转换成真正的 String,把 code 和 message 填入错误对象,并把 data 设为 None → 出来的是完整的 JSON-RPC 错误结构。它不判断错误类型,只负责标准化打包。
调用关系:它只被 invalid_request、method_not_found、invalid_params、internal_error 这些更具体的函数调用。这样外部代码通常不用直接碰它,而是选择一个更有语义的包装函数;error 则保证所有包装函数产出的错误长得一样。
调用图:被 4 处调用(internal_error, invalid_params, invalid_request, method_not_found);外部调用 1 个(into)。
mcp-server/src/outgoing_message.rs源码 ↗
可以把这个文件想成服务器的“发信窗口”。服务器内部先把要说的话包装成几种固定信封:请求、通知、成功响应、错误响应。OutgoingMessageSender 负责给请求编号,把等待回复的回调先登记起来,再把消息丢进 mpsc 通道(异步队列,像传送带)送给真正写到客户端的那一层。客户端回话时,它再按编号找到对应的人并通知。这里还把 Codex 的事件包装成 MCP 通知,并可附带 _meta 元信息,比如请求编号和 threadId。最后,From 转换会把这些内部信封变成标准 JSON-RPC 2.0 消息,保证客户端能按协议读懂。
OutgoingMessageSender::new34–40 ↗
fn new(sender: mpsc::UnboundedSender<OutgoingMessage>) -> Self
作用:创建一个发消息用的工具对象。调用者给它一条向外发送消息的通道,它会准备好请求编号计数器和“请求编号到回调”的登记表。
数据流:进去的是一个 mpsc 无界发送端,也就是一条可以不断塞消息的异步队列入口。函数把请求编号从 0 开始准备好,再建一个 HashMap 登记表,并用 Mutex(一把异步锁,防止多个任务同时改表)保护它。出来的是一个可以发请求、通知、响应和错误的 OutgoingMessageSender。
调用关系:这是使用这个文件的第一步。后面的 send_request、send_response、send_notification 等方法都依赖它保存的发送通道和回调表。测试里也先调用它,搭一个假的发送通道来检查消息是否被正确送出。
OutgoingMessageSender::send_request42–62 ↗
async fn send_request(
&self,
method: &str,
params: Option<serde_json::Value>,
) -> oneshot::Receiver<Value>
作用:从服务器主动向客户端发一个 JSON-RPC 请求,并给调用者一个“等回信的收件口”。适合服务器问客户端要某个决定或数据,之后还要拿到客户端答复的场景。
数据流:进去的是方法名和可选参数。函数先用原子计数器(可被多个任务安全共享的数字)生成一个新的请求编号,再创建 oneshot 通道(只能传一次结果的小通道),把编号和回调发送端存进登记表。然后它把请求包装成 OutgoingMessage::Request,塞进向外的消息队列。出来的是 oneshot 的接收端,调用者之后可以等这里拿客户端返回的结果。
调用关系:它是“发问并等待答案”的入口。消息被送到外层传输流程后,客户端稍后返回结果;那时通常会由接收端处理逻辑调用 notify_client_response,用同一个请求编号把结果送回这个函数返回的接收端。
OutgoingMessageSender::notify_client_response64–80 ↗
async fn notify_client_response(&self, id: RequestId, result: Value)
作用:客户端对某个服务器请求作答后,用这个函数把答案交给当初等待的人。它靠请求编号找回对应的回调。
数据流:进去的是请求编号和客户端返回的 JSON 值。函数先锁住登记表,按编号把那条等待记录取出来并删除,避免同一个回复被用两次。如果找到了,就通过 oneshot 发送端把结果送给等待者;如果找不到,或者等待者已经不收了,就写一条警告日志。出来没有普通返回值,但会完成一次“把回信交给正确的人”的副作用。
调用关系:它接在客户端响应被解析之后。send_request 负责登记等待者,而这个函数负责销号并通知等待者。两者合起来,就像寄出挂号信和按单号取回回执。
调用图:外部调用 1 个(warn!)。
OutgoingMessageSender::send_response82–97 ↗
async fn send_response(&self, id: RequestId, response: T)
作用:服务器收到客户端请求并处理完后,用这个函数把成功结果回给客户端。它会先把任意 Rust 数据转成 JSON,再按 JSON-RPC 响应格式发出去。
数据流:进去的是原请求编号和一个可序列化的响应对象。函数先调用 serde_json::to_value,把响应变成 JSON 值;如果转换失败,就改走 send_error,告诉客户端服务器内部序列化失败。如果转换成功,它把编号和结果包成 OutgoingMessage::Response,塞进发送队列。出来没有直接返回值,但客户端会收到成功响应或错误响应。
调用关系:它在“处理完客户端请求”之后使用。正常时它直接把 Response 放到通道里;异常时它把活交给 send_error,让错误也按同一套出站消息流程发出。
调用图:调用 1 个内部函数(send_error);外部调用 5 个(internal_error, send, Response, format!, to_value)。
OutgoingMessageSender::send_event_as_notification102–125 ↗
async fn send_event_as_notification(
&self,
event: &Event,
meta: Option<OutgoingNotificationMeta>,
)
作用:把 Codex 内部发生的事件发给客户端,而且是不要求客户端回复的通知。它主要服务 MCP 服务器场景,让客户端能实时知道会话、线程等事件。
数据流:进去的是一个 Event 和可选的通知元信息 meta。函数先把事件转成 JSON;如果带 meta,就尝试把 meta 放进 params._meta,同时把事件字段平铺到 params 里。若这层包装序列化失败,它会记录警告并退回到只发事件本身。最后它构造 method 为 codex/event 的通知,交给 send_notification 发出。
调用关系:它是 Codex 事件通往 MCP 客户端的专用出口。它不直接碰底层通道,而是把通知整理好后交给 send_notification,这样所有通知最后都走同一个发送路径。
调用图:调用 1 个内部函数(send_notification);外部调用 2 个(to_value, warn!)。
OutgoingMessageSender::send_notification127–130 ↗
async fn send_notification(&self, notification: OutgoingNotification)
作用:发送一条不需要回复的通知。适合“我告诉你一件事,但你不用回答”的消息,比如初始化完成或事件广播。
数据流:进去的是一个 OutgoingNotification,里面有方法名和可选参数。函数把它包成 OutgoingMessage::Notification,然后塞进向外的异步消息队列。出来没有直接返回值;发送失败也不会向上传错误。
调用关系:它是通知类消息的统一出口。send_event_as_notification 会先把事件整理成通知,再调用它;其他地方也可以直接用它发送普通服务器通知。后面还会通过 OutgoingJsonRpcMessage::from 转成真正的 JSON-RPC 通知。
调用图:被 1 处调用(send_event_as_notification);外部调用 2 个(send, Notification)。
OutgoingMessageSender::send_error132–135 ↗
async fn send_error(&self, id: RequestId, error: ErrorData)
作用:按 JSON-RPC 的格式给客户端回一个错误。通常用于请求处理失败,或者服务器连成功响应都没法正确序列化的时候。
数据流:进去的是原请求编号和 ErrorData 错误内容。函数把它们包成 OutgoingMessage::Error,再塞进发送队列。出来没有普通返回值,但客户端会收到一条带编号的错误消息,知道是哪次请求失败了。
调用关系:它是错误响应的统一出口。send_response 在序列化成功结果失败时会调用它;其他请求处理逻辑也可以用它把失败原因发回客户端。之后转换层会把它变成 JsonRpcError。
调用图:被 1 处调用(send_response);外部调用 2 个(send, Error)。
OutgoingJsonRpcMessage::from147–176 ↗
fn from(val: OutgoingMessage) -> Self
作用:把本文件内部用的 OutgoingMessage,转换成客户端真正认识的 JSON-RPC 2.0 消息。它相当于把内部信封改写成标准邮局格式。
数据流:进去的是四种内部消息之一:请求、通知、响应或错误。函数按种类分别填上 jsonrpc: 2.0、请求编号、方法名、参数、结果或错误信息,并用 rmcp 提供的 CustomRequest、CustomNotification 等类型包装。出来的是 OutgoingJsonRpcMessage,之后就可以被序列化并发给客户端。
调用关系:它位于发送流程的后半段:OutgoingMessageSender 先把消息放进队列,真正写出前需要调用这个转换,把内部枚举变成协议对象。测试里的序列化检查也重点确认它没有多包一层奇怪字段,而是符合 JSON-RPC 的 method/params 形状。
调用图:外部调用 6 个(new, new, Error, Notification, Request, Response)。
tests::outgoing_request_serializes_as_jsonrpc_request248–267 ↗
fn outgoing_request_serializes_as_jsonrpc_request()
作用:检查内部的请求消息转成 JSON 后,长得像标准 JSON-RPC 请求。这个测试防止协议格式被无意改坏。
数据流:进去的是测试手写的 OutgoingMessage::Request,包含编号 1、方法名 elicitation/create 和参数。测试把它转换成 OutgoingJsonRpcMessage,再序列化成 JSON,然后逐项检查 jsonrpc、id、method、params 是否正确,同时确认没有多出 request 这一层。出来是测试通过或失败。
调用关系:它验证 OutgoingJsonRpcMessage::from 对请求分支的行为。只要转换层或 rmcp 类型的展开方式变了,这个测试就会提醒开发者:客户端可能读不懂请求了。
调用图:外部调用 6 个(Number, Request, assert!, assert_eq!, json!, to_value)。
tests::outgoing_notification_serializes_as_jsonrpc_notification270–287 ↗
fn outgoing_notification_serializes_as_jsonrpc_notification()
作用:检查通知消息转成 JSON 后,符合 JSON-RPC 通知格式。它保证“不需要回复的消息”不会被包装错。
数据流:进去的是一个 OutgoingMessage::Notification,方法名是 notifications/initialized,参数为空。测试转换并序列化后,检查 jsonrpc 和 method 正确,params 是 null,并确认没有多出 notification 这一层。出来是测试结果。
调用关系:它验证 OutgoingJsonRpcMessage::from 对通知分支的行为。和请求测试一起,守住对外协议的基本外观,避免客户端因为字段层级变化而解析失败。
调用图:外部调用 4 个(Notification, assert!, assert_eq!, to_value)。
tests::test_send_event_as_notification290–335 ↗
async fn test_send_event_as_notification() -> Result<()>
作用:检查普通 Codex 事件能被发成 codex/event 通知,并且没有 meta 时参数就是事件本身。它确保事件推送的最基本路径可用。
数据流:测试先建一条假的异步发送队列,再用 OutgoingMessageSender::new 创建发送器。它构造一个会话配置事件,调用 send_event_as_notification。随后从队列另一端取出消息,检查它是 Notification、方法名是 codex/event,并且 params 等于事件序列化后的 JSON。出来是测试通过或失败。
调用关系:它直接覆盖 send_event_as_notification,并间接走到 send_notification 和 new。这个测试说明:事件发送不会真的连网络,而是先进入 OutgoingMessage 队列,方便后续统一处理。
调用图:调用 4 个内部函数(new, read_only, new, new);外部调用 7 个(new, assert_eq!, test_path_buf, panic!, default, SessionConfigured, to_value)。
tests::test_send_event_as_notification_with_meta338–403 ↗
async fn test_send_event_as_notification_with_meta() -> Result<()>
作用:检查事件通知带 requestId 元信息时,_meta 会被正确放进 params。这样客户端能知道这条事件和哪次请求有关。
数据流:测试创建发送器和一个会话配置事件,再准备 meta,其中 request_id 是字符串 123,thread_id 为空。调用 send_event_as_notification 后,它从队列取出通知,检查方法名是 codex/event,并比较 params:里面应有 _meta.requestId,同时事件字段仍然在外层。出来是测试结果。
调用关系:它验证 send_event_as_notification 里“事件加 MCP _meta 包装”的分支。这个测试保护了 MCP 规范要求的元信息形状,避免客户端无法关联事件和请求。
调用图:调用 4 个内部函数(new, read_only, new, new);外部调用 8 个(new, String, assert_eq!, test_path_buf, json!, panic!, default, SessionConfigured)。
tests::test_send_event_as_notification_with_meta_and_thread_id406–472 ↗
async fn test_send_event_as_notification_with_meta_and_thread_id() -> Result<()>
作用:检查事件通知同时带 requestId 和 threadId 时,两者都会正确写进 _meta。在线程复用同一条 MCP 连接时,这很重要。
数据流:测试先生成一个 thread_id,构造会话配置事件,再准备包含 request_id 和 thread_id 的 meta。调用 send_event_as_notification 后,从队列取出通知并核对 params:_meta 里应有 requestId 和 threadId,事件内容也应保持正确。出来是测试通过或失败。
调用关系:它覆盖 send_event_as_notification 最完整的 meta 场景。这个测试说明该文件不只是发事件,还会在多线程共用一条连接时附上足够线索,让客户端分清事件属于哪个线程。
调用图:调用 4 个内部函数(new, read_only, new, new);外部调用 8 个(new, String, assert_eq!, test_path_buf, json!, panic!, default, SessionConfigured)。
tui/src/app_server_approval_conversions.rs源码 ↗
这份文件像一个小翻译员。TUI,也就是终端里的图形界面,大部分时候直接使用 app-server 发来的审批数据;但有两类数据需要换个说法:一类是“用户批准了哪些权限”,另一类是“文件有哪些改动,要怎么显示”。这里的函数会把核心权限请求转换成服务器需要的“已授权权限”格式,也会把一串文件更新记录转换成按文件路径索引的显示模型。比如新增文件、删除文件、修改文件,会分别变成界面能直接展示的 FileChange。这样做的好处是,审批流程里其他地方不用关心协议字段怎么拼、路径怎么放、diff 文本放在哪里。文件底部还有测试,检查常见权限和文件改动是否能被正确转换,尤其是文件系统权限里旧的 read/write 写法能不能变成更统一的 entries 写法。
granted_permission_profile_from_request17–26 ↗
fn granted_permission_profile_from_request(
value: CoreRequestPermissionProfile,
) -> GrantedPermissionProfile
作用:这个函数把“请求权限”的内部格式,变成“已经授予权限”的服务器提交格式。简单说,就是用户同意后,把同意内容整理成能发回 app-server 的表单。
数据流:进去的是 CoreRequestPermissionProfile,也就是核心层已经解析好的权限请求。函数读取里面的网络权限和文件系统权限:网络权限只保留 enabled 这个开关,文件系统权限交给已有的转换规则处理。出来的是 GrantedPermissionProfile,表示可以对外提交的已授权权限;它不改原对象,而是生成一个新对象。
调用关系:它会被 format_requested_permissions_rule 使用,通常发生在审批界面需要把用户同意的权限整理成提交内容时。它自己不做复杂判断,只负责把核心层的权限表达翻译成 app-server 协议里的授权表达。
调用图:被 1 处调用(format_requested_permissions_rule)。
file_update_changes_to_display28–50 ↗
fn file_update_changes_to_display(
changes: Vec<FileUpdateChange>,
) -> HashMap<PathBuf, FileChange>
作用:这个函数把服务器传来的文件改动列表,转换成界面展示文件差异时更好用的结构。它让界面可以按文件路径快速找到这个文件是新增、删除还是修改。
数据流:进去的是一组 FileUpdateChange,每条里面有路径、改动类型和 diff 文本。函数逐条处理:把字符串路径变成 PathBuf,把新增变成 FileChange::Add,把删除变成 FileChange::Delete,把修改变成 FileChange::Update,并保留可能的移动路径。出来的是一个 HashMap,键是文件路径,值是对应的文件改动展示模型。
调用关系:它处在 app-server 数据进入 TUI 显示层的边界上。服务器给的是通用协议格式,界面需要的是 diff_model::FileChange;这个函数负责中间那一步翻译,让后面的展示代码不用理解服务器协议里的 PatchChangeKind。
tests::absolute_path73–75 ↗
fn absolute_path(path: &str) -> AbsolutePathBuf
作用:这是测试里用的小工具,用来快速造一个“绝对路径”对象。绝对路径就是从系统根目录开始写完整的路径,比如 /tmp/write。
数据流:进去的是一个字符串形式的路径。它先把字符串变成普通 PathBuf,再用 try_from 检查并转换成 AbsolutePathBuf;如果传进来的不是绝对路径,测试会直接失败。出来的是测试可以拿来组装权限对象的绝对路径。
调用关系:它只服务于本文件里的测试,尤其是权限转换测试。测试需要多次构造合法的 API 路径,所以用这个小函数避免重复写 PathBuf::from 和 AbsolutePathBuf::try_from。
tests::converts_file_update_changes_to_display78–92 ↗
fn converts_file_update_changes_to_display()
作用:这个测试确认文件改动列表能正确变成界面展示用的数据。它用一个“新增 foo.txt”的例子,检查转换结果是不是按路径存好,并且内容没有丢。
数据流:进去的是测试手写的一条 FileUpdateChange:路径是 foo.txt,类型是新增,diff 内容是 hello 换行。测试调用 file_update_changes_to_display 得到结果,然后用 assert_eq! 比较它是否等于预期的 HashMap。它不产出业务结果,只是在不一致时让测试失败。
调用关系:它直接验证 file_update_changes_to_display 的基本行为。这个测试覆盖最简单但很关键的场景:服务器说新增一个文件,界面模型也必须认成新增文件。
调用图:外部调用 1 个(assert_eq!)。
tests::converts_request_permissions_into_granted_permissions95–137 ↗
fn converts_request_permissions_into_granted_permissions()
作用:这个测试确认普通的权限请求能被转换成正确的已授权权限。重点是检查网络权限,以及文件系统的读写路径能不能被整理成更标准的 entries 列表。
数据流:进去的是测试手写的 RequestPermissionProfile:网络允许,文件系统里有一个只读路径和一个可写路径。测试先用 try_from 把 API 层权限转换成核心层权限,再调用 granted_permission_profile_from_request,最后用 assert_eq! 比较结果。预期结果里既保留 read/write 字段,也生成 entries,明确标出每个路径是读还是写。
调用关系:它验证 granted_permission_profile_from_request 在常见审批场景下是否可靠。这里还间接检查了底层权限转换规则:旧式 read/write 路径列表应该能被规范化成沙盒条目,也就是 FileSystemSandboxEntry。
调用图:外部调用 3 个(try_from, assert_eq!, vec!)。
tests::converts_request_permissions_into_canonical_granted_permissions140–175 ↗
fn converts_request_permissions_into_canonical_granted_permissions()
作用:这个测试确认如果权限请求本来已经是标准的 entries 形式,转换后不会被弄乱。这里用的是一个特殊路径 Root,表示文件系统根目录。
数据流:进去的是测试手写的 RequestPermissionProfile:没有网络权限,文件系统权限直接给 entries,内容是对 Root 的写权限。测试先把它转换成核心层权限,再调用 granted_permission_profile_from_request,最后比较实际输出和预期输出是否完全一样。它的结果是通过或失败,不会改变运行时状态。
调用关系:它补充验证 granted_permission_profile_from_request 的另一个重要情况:输入已经是规范格式时,函数应该原样保留语义,而不是重复加工或丢字段。它和前一个权限测试一起,保证新旧两种文件系统权限表达都能安全通过。
调用图:外部调用 3 个(try_from, assert_eq!, vec!)。