Codex 系统手册

工具和协议契约架构

stage-18.4.418 个文件

这个阶段像后台的“说明书和表格库”,不直接干活,而是让模型、前端、后端和外部工具说同一种话。它规定工具调用时要带什么信息、接口里长什么样,再把执行结果翻译成系统能继续用的格式。提问用户、申请权限、更新计划、查剩余对话容量、跑命令、目标、技能、网页搜索、MCP 调用,都在这里有固定模板和字段名。这样各零件交接时不靠猜,少传错、少看错,也方便日志、审批和钩子稳定接上。

本阶段的文件18

核心工具契约

这些文件定义共享的负载、调用、规格和错误类型,支撑整个系统中模型可见的工具调用。

core/src/function_tool.rs源码 ↗
data_modelcross-cutting

这个文件只有一行代码:把 codex_tools::FunctionCallError 重新导出。所谓“重新导出”,可以理解成商场服务台帮你指路:东西其实在别的店里,但你从这里也能直接拿到。FunctionCallError 表示调用某个工具函数时可能发生的错误,比如参数不对、工具执行失败等。这样做的好处是,项目里其他代码只需要从 core 这一层拿这个错误类型,不必直接依赖 codex_tools 的内部路径。以后如果真正的错误类型搬家了,也只要改这个文件,使用方不用跟着到处改。

tools/src/tool_payload.rs源码 ↗
data_modelrequest handling

这份文件像是给“工具请求”做了一个统一信封。系统里可能有普通函数工具,参数是一段字符串;也可能有搜索工具,参数是结构化的搜索参数;还可能有自定义工具,输入也是一段文本。ToolPayload 这个枚举(一种“只能从几个选项里选一个”的类型)把这三种情况放在一起,避免每个地方都自己猜“这次到底是哪种工具”。它还提供 log_payload,用来拿出适合写进日志的内容。这里有个小细节:搜索工具真正适合记录的是 query,也就是用户要搜的关键词;普通函数和自定义工具则直接记录原始输入。返回值用了 Cow(一种“能借用就不复制,必须复制才复制”的省内存写法),这样日志读取时尽量少浪费内存。

函数细节1
ToolPayload::log_payload14–20 ↗
fn log_payload(&self) -> Cow<'_, str>

作用:这个函数把不同形状的工具输入,转换成一段适合写日志的文字。有人想查看“这次工具到底拿到了什么输入”时,就可以用它,而不用关心背后是哪一种工具。

数据流:进去的是一个 ToolPayload。它先看这个载荷是哪一类:如果是普通函数工具,就直接拿里面的 arguments;如果是搜索工具,就取出搜索参数里的 query 并复制成日志文字;如果是自定义工具,就直接拿 input。出来的是一段可当作日志内容的文本;普通函数和自定义工具通常只是借用原文,搜索工具会新生成一份 query 文本。

调用关系:它是 ToolPayload 这个统一信封上的取日志内容按钮。调用方在需要记录工具输入时会调用它;函数内部只把文本包装成 Borrowed 或 Owned,意思分别是“借用现有文本”和“生成一份自己的文本”,不再把工作交给项目里的其他函数。

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

tools/src/tool_call.rs源码 ↗
data_modelrequest handling / tool invocation

可以把这个文件理解成工具调用的“任务单”和“随行装备清单”。当系统要让某个工具干活时,不能只给它一句命令,还要告诉它:当前是哪一轮对话、调用编号是什么、工具名字是什么、模型是谁、历史对话有哪些、文件系统该怎么访问,以及工具的具体输入在哪里。ConversationHistory 保存工具能看到的历史回复;ToolEnvironment 描述这一轮里可用的工作目录和文件系统沙箱,沙箱就是一层安全边界,防止乱读乱写;TurnItemEmitter 是主程序给工具的“广播喇叭”,工具可以用它发布网页搜索、图片生成这类用户可见的过程;NoopTurnItemEmitter 则是空喇叭,适合不需要发布过程的场景。ToolCall 是核心结构,集中装下所有这些信息,并提供一个小检查:只有真正是函数调用输入时,才允许取出参数,否则返回致命错误,避免工具拿错类型还继续跑。

函数细节6
ConversationHistory::new22–26 ↗
fn new(items: Vec<ResponseItem>) -> Self

作用:创建一份对话历史快照,给扩展工具查看之前发生过什么。这样工具不是盲干,而是能参考上下文。

数据流:进去的是一串 ResponseItem,也就是之前的模型回复或相关结果;函数把这串内容包进一个可共享的内部容器里;出来的是 ConversationHistory,之后可以被 ToolCall 带给工具使用。

调用关系:它会在 to_extension_call 准备扩展工具调用时被使用,相当于在发任务单前先把“历史记录附件”装好。它本身不再调用别的项目内函数,只做一次简单包装。

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

ConversationHistory::items28–30 ↗
fn items(&self) -> &[ResponseItem]

作用:把保存好的对话历史拿出来给别人只读查看。它不允许调用者直接改历史,避免工具把原始记录弄乱。

数据流:进去的是已有的 ConversationHistory;函数读取里面保存的 ResponseItem 列表引用;出来的是一段只读的历史列表,原数据不会被复制,也不会被修改。

调用关系:当某个工具或调用流程需要查看历史上下文时,会通过这个入口拿到内容。它像档案室的阅览窗口,只让看,不让改。

NoopTurnItemEmitter::emit_started73–75 ↗
fn emit_started(&'a self, _item: ExtensionTurnItem) -> TurnItemEmissionFuture<'a>

作用:在不需要真正发布“某个可见项目开始了”的场景里,假装发布成功。它的作用是让调用流程不用到处判断“有没有广播喇叭”。

数据流:进去的是一个将要开始的 ExtensionTurnItem,但这个空实现会直接忽略它;函数创建一个立刻完成的异步结果;出来的是一个马上结束、没有副作用的 future,future 可以理解成“稍后完成的任务凭条”。

调用关系:它实现了 TurnItemEmitter 这套接口,所以主流程可以把它当成正常发布器来用。内部只调用 ready 和 pin 来做一个立即完成的异步任务,不会把事件交给真实客户端或持久化系统。

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

NoopTurnItemEmitter::emit_completed77–79 ↗
fn emit_completed(&'a self, _item: ExtensionTurnItem) -> TurnItemEmissionFuture<'a>

作用:在不需要真正发布“某个可见项目完成了”的场景里,假装完成发布。它保证工具流程可以照常调用完成事件,而不会因为没有发布通道出错。

数据流:进去的是一个已经完成的 ExtensionTurnItem,但函数不保存、不发送、不展示;它只生成一个立即完成的异步结果;出来的是一个没有实际动作的完成信号。

调用关系:它和 NoopTurnItemEmitter::emit_started 配套,都是空发布器的一部分。调用方按正常流程通知开始和完成时,它们会安静地接住这些通知,不再转交给任何真实事件管道。

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

ToolCall::fmt96–108 ↗
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result

作用:给 ToolCall 做调试打印,让开发者排查问题时能看到关键字段。它有意不直接打印真实的 turn_item_emitter,而是用占位文字代替,避免输出一堆没意义或不好展示的内部对象。

数据流:进去的是一个 ToolCall 和调试输出器;函数把轮次编号、调用编号、工具名、模型、截断策略、历史、环境数量、输入载荷等整理成调试文本;出来的是格式化结果,同时没有改变 ToolCall 本身。

调用关系:这是 Rust 的 Debug 打印机制会自动用到的函数,比如日志或调试器想显示 ToolCall 时会走这里。它把具体排版工作交给标准库的 debug_struct 辅助工具。

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

ToolCall::function_arguments112–120 ↗
fn function_arguments(&self) -> Result<&str, FunctionCallError>

作用:从工具调用里取出函数参数,并确认这次调用确实是“函数型载荷”。如果载荷类型不对,它会立刻报致命错误,防止后面按错格式解析导致更隐蔽的问题。

数据流:进去的是一个 ToolCall;函数查看 payload,如果是 ToolPayload::Function,就拿出里面的 arguments 字符串引用;如果不是函数载荷,就生成一条包含工具名的错误信息并返回 FunctionCallError::Fatal。

调用关系:工具真正开始解析输入参数前会用它先取参数。它不调用别的项目流程,只在出错时用 format! 拼出清楚的错误说明,并用 Fatal 标记这是不能继续执行的严重不匹配。

调用图:外部调用 2 个(format!, Fatal)。

tools/src/tool_spec.rs源码 ↗
data_modelrequest preparation

可以把这个文件理解成一张“工具申报表”的模板。程序想让模型使用函数、命名空间工具、图片生成、网页搜索等能力时,不能随便把数据塞进请求里,必须按 Responses API 要求的格式写清楚。这里的 ToolSpec 就是统一的工具类型:每一种工具对应一种 JSON 形状。文件还特别处理了网页搜索的过滤条件和用户位置,比如只允许搜哪些网站、用户在哪个国家或城市。最后,create_tools_json_for_responses_api 会把一组 ToolSpec 逐个转成 serde_json::Value,也就是可以直接放进 API 请求体里的 JSON 值。这样其他代码只需要使用清晰的 Rust 类型,不用到处手写容易出错的 JSON 字段名。

函数细节5
ToolSpec::name54–63 ↗
fn name(&self) -> &str

作用:这个函数给任意一种工具取出一个可识别的名字。外部代码需要按名字区分工具时,就不用关心它到底是函数、网页搜索还是图片生成。

数据流:进去的是一个 ToolSpec 工具对象 → 它根据工具的具体种类,取出工具自带的 name,或者给内置工具返回固定名字,比如 web_search → 出来的是一个字符串引用,不新建工具,也不修改原数据。

调用关系:它会被 spec_for_model_request 在准备模型请求时使用。也就是说,请求组装代码需要知道每个工具叫什么,这个函数就像给工具贴标签的地方。

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

ToolSpec::from67–72 ↗
fn from(value: LoadableToolSpec) -> Self

作用:这个函数把 LoadableToolSpec 转成真正要发给 Responses API 的 ToolSpec。它让“可加载的工具定义”和“请求里的工具定义”之间可以自然衔接。

数据流:进去的是一个 LoadableToolSpec,它目前可能是函数工具或命名空间工具 → 函数检查它是哪一类,并包进 ToolSpec 对应的 Function 或 Namespace 变体里 → 出来的是一个 ToolSpec,原来的工具内容被带过去,不额外改写。

调用关系:它是 Rust 的 From 转换实现,通常在代码需要自动把加载阶段的工具变成请求阶段工具时触发。转换时会使用 ToolSpec 的 Function 和 Namespace 这两个外部枚举构造方式,把数据放到正确盒子里。

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

create_tools_json_for_responses_api78–89 ↗
fn create_tools_json_for_responses_api(
    tools: &[ToolSpec],
) -> Result<Vec<Value>, serde_json::Error>

作用:这个函数把一组工具说明转换成 Responses API 能直接接收的 JSON 列表。有人准备发请求时,会用它把 Rust 里的结构化数据变成网络接口需要的格式。

数据流:进去的是 ToolSpec 切片,也就是一串工具说明 → 它新建一个空列表,然后逐个调用 serde_json::to_value,把每个工具按 serde 标注转成 JSON 值 → 成功时出来的是 JSON 列表;如果某个工具无法序列化,就返回 serde_json::Error 错误。

调用关系:它位于“发送请求前的最后整理”阶段。上游代码先准备好 ToolSpec;这个函数负责把它们翻译成 JSON;之后这些 JSON 通常会被放进 Responses API 请求体里。

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

ResponsesApiWebSearchFilters::from98–102 ↗
fn from(filters: ConfigWebSearchFilters) -> Self

作用:这个函数把配置文件里的网页搜索过滤条件,转成 Responses API 要求的过滤条件格式。最主要的是保留 allowed_domains,也就是允许搜索的网站域名列表。

数据流:进去的是 ConfigWebSearchFilters,来自程序配置 → 它取出 allowed_domains 字段,放进 ResponsesApiWebSearchFilters → 出来的是适合序列化进 API 请求的过滤条件对象,不做额外校验或改名。

调用关系:它连接配置层和请求层。配置读取代码可能得到的是 ConfigWebSearchFilters,而构造 WebSearch 工具时需要 ResponsesApiWebSearchFilters,这个转换函数就负责把两边接上。

ResponsesApiWebSearchUserLocation::from120–128 ↗
fn from(user_location: ConfigWebSearchUserLocation) -> Self

作用:这个函数把配置里的用户位置信息,转成网页搜索工具发给 Responses API 的位置格式。这样搜索可以根据国家、地区、城市或时区等信息更贴近用户。

数据流:进去的是 ConfigWebSearchUserLocation,里面可能有位置类型、国家、地区、城市和时区 → 它把这些字段原样搬到 ResponsesApiWebSearchUserLocation 里,其中 type 字段在 Rust 里写作 r#type,是为了避开语言关键字 → 出来的是可序列化到 API 请求里的用户位置对象。

调用关系:它也是配置层到请求层的桥。网页搜索工具需要用户位置时,上游配置先提供 ConfigWebSearchUserLocation,再通过这个函数变成 Responses API 能理解的结构。

core/src/tools/context.rs源码 ↗
domain_logic工具调用结束后、结果回传模型、日志记录、代码模式取结果时

这份文件像一个“工具结果翻译站”。命令执行、补丁应用、MCP 工具、工具搜索、普通函数工具、被取消的工具,都会产生不同形状的结果;这里把它们整理成统一的 ToolOutput(一套“工具输出接口”)。它会做几件重要的事:给模型看的内容要加上耗时、退出码等说明;太长的输出要截短,避免把上下文塞爆;给日志看的预览也要限制字节数和行数,防止日志过大;图片细节还会按当前能力做清理。它还区分普通函数工具和自定义工具,因为两者回给模型的协议格式不一样。可以把它理解成餐厅后厨的出餐口:每个厨师做的菜不同,但端到客人面前前,都要装盘、贴单、控制分量,并记录一眼能看懂的小票。

函数细节39
boxed_tool_output31–36 ↗
fn boxed_tool_output(output: T) -> Box<dyn ToolOutput>

作用:把某个具体工具结果装进一个统一的盒子里,让后面的代码不用关心它到底是哪种工具输出。这样调用方只要按 ToolOutput 这套共同规则使用它。

数据流:输入是一个具体的工具输出对象 → 函数把它放进 Box,也就是一块可统一传递的堆内存包装 → 输出是 Box<dyn ToolOutput>,后续可以当作通用工具结果来处理。

调用关系:很多工具处理函数在完成工作后都会调用它,把自己的专用结果交给统一流程。它本身不处理内容,只负责把结果“装箱”,让日志、响应生成等后续步骤能用同一套入口。

调用图:被 19 处调用(handle_call, handle_call, handle, handle_call, handle_call, handle_call, handle_call, handle_call, handle, handle_call (+9 more));外部调用 1 个(new)。

McpToolOutput::log_preview75–82 ↗
fn log_preview(&self) -> String

作用:给 MCP 工具结果生成一段短日志预览。MCP 是一种让外部工具接入系统的协议,这里要避免把完整大结果直接写进日志。

数据流:它读取 MCP 的完整结果 → 先转成模型会看到的响应内容,如果转不成文本就转成 JSON 字符串 → 再交给 telemetry_preview 截成短预览 → 返回日志用字符串。

调用关系:工具日志系统会通过 ToolOutput 接口调用它。它先依赖 McpToolOutput::response_payload 整理内容,再把最后的裁剪工作交给 telemetry_preview。

调用图:调用 2 个内部函数(response_payload, telemetry_preview)。

McpToolOutput::success_for_logging84–86 ↗
fn success_for_logging(&self) -> bool

作用:告诉日志系统这次 MCP 工具调用算不算成功。这样统计和排查问题时能快速看出失败工具。

数据流:它读取 MCP 返回结果里的成功状态 → 调用结果对象自己的 success 判断 → 输出 true 或 false。

调用关系:这是 ToolOutput 接口的一部分,日志记录阶段会用到。它不改结果,只把 MCP 协议里的状态翻译成统一的成功标记。

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

McpToolOutput::to_response_item88–93 ↗
fn to_response_item(&self, call_id: &str, _payload: &ToolPayload) -> ResponseInputItem

作用:把 MCP 工具结果变成可以塞回对话里的响应项。模型下一步就是读这个响应项来继续推理。

数据流:输入是工具调用编号 call_id 和原始工具参数 → 它调用 response_payload 整理 MCP 输出 → 生成 FunctionCallOutput 这种对话协议对象 → 返回给上层会话流程。

调用关系:工具调用完成后,统一响应生成流程会调用它。它把具体 MCP 结果交给 McpToolOutput::response_payload 做清洗、加耗时、截断,然后包装进协议响应。

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

McpToolOutput::code_mode_result95–99 ↗
fn code_mode_result(&self, _payload: &ToolPayload) -> JsonValue

作用:给代码模式返回原始 MCP 结果的 JSON 版本。代码模式更像程序在读数据,所以这里尽量保留结构化信息。

数据流:它读取 MCP 的 CallToolResult → 尝试序列化成 JSON → 成功就返回 JSON,失败就返回一段说明错误的字符串。

调用关系:代码模式消费工具结果时会走这个接口。它和 to_response_item 不同:给模型对话的是整理后的文本/内容,给代码模式的是更原始的结构化结果。

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

McpToolOutput::post_tool_use_input101–103 ↗
fn post_tool_use_input(&self, _payload: &ToolPayload) -> Option<JsonValue>

作用:把 MCP 工具的输入参数拿出来,供“工具用完后”的钩子或记录系统查看。这样系统知道刚才工具是拿什么参数运行的。

数据流:它读取保存下来的 tool_input → 克隆一份 JSON → 返回 Some(JSON),不会修改原输入。

调用关系:工具完成后的后处理流程会调用它。它不参与主响应生成,只给审计、钩子或遥测补充输入信息。

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

McpToolOutput::post_tool_use_response105–107 ↗
fn post_tool_use_response(&self, _call_id: &str, _payload: &ToolPayload) -> Option<JsonValue>

作用:把 MCP 工具的输出结果转成 JSON,供工具结束后的后处理流程使用。

数据流:它读取 MCP 结果 → 尝试序列化成 JSON → 成功返回 Some(JSON),失败返回 None。

调用关系:后处理流程在工具运行完后可能调用它。它和 post_tool_use_input 配对,一个给输入,一个给输出。

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

McpToolOutput::response_payload111–140 ↗
fn response_payload(&self) -> FunctionCallOutputPayload

作用:把 MCP 原始结果整理成模型真正能看的响应内容。它会补上耗时、清理不支持的图片细节,并按预算截短。

数据流:输入是 MCP 原始结果、耗时、图片支持情况和截断策略 → 先转成通用函数调用输出 → 清理图片细节 → 在开头加上 Wall time 和 Output → 按上下文预算截短 → 输出 FunctionCallOutputPayload。

调用关系:McpToolOutput::log_preview 和 McpToolOutput::to_response_item 都依赖它。它是 MCP 输出进入对话上下文前的核心整理步骤。

调用图:被 2 处调用(log_preview, to_response_item);外部调用 5 个(as_secs_f64, truncate_function_output_payload, sanitize_original_image_detail, format!, as_function_call_output_payload)。

ToolSearchOutput::log_preview149–160 ↗
fn log_preview(&self) -> String

作用:给工具搜索结果生成短日志预览。工具列表可能很长,所以日志里只放安全长度的摘要。

数据流:它读取搜索到的工具列表 → 把每个工具规格转成 JSON,失败时用错误字符串占位 → 拼成 JSON 数组字符串 → 交给 telemetry_preview 截短 → 返回预览。

调用关系:当工具搜索完成并记录日志时会调用它。它负责把结构化工具列表先变成可记录文本,再让 telemetry_preview 控制大小。

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

ToolSearchOutput::success_for_logging162–164 ↗
fn success_for_logging(&self) -> bool

作用:告诉日志系统工具搜索这类输出默认是成功的。因为这个结构只代表已经拿到的搜索结果。

数据流:不读取额外输入 → 直接返回 true → 不修改任何状态。

调用关系:日志流程通过 ToolOutput 接口调用它,用来给工具搜索事件打成功标记。

ToolSearchOutput::to_response_item166–181 ↗
fn to_response_item(&self, call_id: &str, _payload: &ToolPayload) -> ResponseInputItem

作用:把工具搜索结果变成对话协议里的工具搜索输出项,让模型知道现在有哪些工具可用。

数据流:输入是调用编号和工具列表 → 把每个工具规格转成 JSON → 填入状态 completed、执行方 client 等字段 → 返回 ToolSearchOutput 响应项。

调用关系:工具搜索完成后,上层会话流程会调用它。它不走普通函数输出格式,而是生成专门的工具搜索响应格式。

FunctionToolOutput::from_text191–197 ↗
fn from_text(text: String, success: Option<bool>) -> Self

作用:用一段普通文本快速创建函数工具输出。多数工具只需要返回文字,这个函数就是最方便的构造入口。

数据流:输入是一段文本和可选成功标记 → 把文本包成一个 InputText 内容项 → 设置成功状态,后处理响应先留空 → 输出 FunctionToolOutput。

调用关系:很多工具处理代码和测试都会调用它。它创建出来的对象后面会通过 FunctionToolOutput::to_response_item 转成模型能读的协议响应。

调用图:被 25 处调用(custom_tool_calls_should_roundtrip_as_custom_outputs, function_payloads_remain_function_outputs, handle, handle, intercept_apply_patch, to_response_item, handle_call, serialize_function_output, tool_output_response_item, handle_message_string_tool (+15 more));外部调用 1 个(vec!)。

FunctionToolOutput::from_content199–208 ↗
fn from_content(
        content: Vec<FunctionCallOutputContentItem>,
        success: Option<bool>,
    ) -> Self

作用:用一组内容项创建函数工具输出。它适合不只是纯文本的情况,比如已经拆成多段内容。

数据流:输入是内容项列表和可选成功标记 → 直接保存这些内容项 → 设置成功状态,后处理响应为空 → 输出 FunctionToolOutput。

调用关系:运行时代码响应或需要保留内容结构的工具会调用它。之后日志和响应生成会按这些内容项继续处理。

调用图:被 4 处调用(handle_runtime_response, custom_tool_calls_can_derive_text_from_content_items, log_preview_uses_content_items_when_plain_text_is_missing, handle_call)。

FunctionToolOutput::into_text210–212 ↗
fn into_text(self) -> String

作用:把函数工具输出尽量合并成普通文本。需要从结构化内容里提取可读文字时会用它。

数据流:输入是整个 FunctionToolOutput,自身会被消耗掉 → 读取 body 里的内容项并尝试转成文本 → 成功返回文本,失败返回空字符串。

调用关系:它把内容转换工作交给 function_call_output_content_items_to_text。常用于调用方只想要文字、不想继续处理内容结构的场景。

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

FunctionToolOutput::log_preview216–220 ↗
fn log_preview(&self) -> String

作用:给普通函数工具输出生成短日志预览。日志只需要看个大概,不应该塞入超长完整输出。

数据流:它读取 body 内容项 → 尝试合成文本,失败就用空字符串 → 交给 telemetry_preview 按限制截短 → 返回预览字符串。

调用关系:日志系统通过 ToolOutput 接口调用它。它负责把函数工具的内容先变成文本,再复用统一的日志裁剪规则。

调用图:调用 2 个内部函数(telemetry_preview, function_call_output_content_items_to_text)。

FunctionToolOutput::success_for_logging222–224 ↗
fn success_for_logging(&self) -> bool

作用:告诉日志系统函数工具是否成功。如果工具没明确说失败,就默认按成功处理。

数据流:它读取 success 字段 → 如果里面有 true/false 就返回那个值 → 如果没有值就返回 true。

调用关系:日志阶段会调用它。这个默认成功的行为很重要,因为很多旧工具可能不会显式填写成功状态。

FunctionToolOutput::to_response_item226–228 ↗
fn to_response_item(&self, call_id: &str, payload: &ToolPayload) -> ResponseInputItem

作用:把普通函数工具输出包装成模型能接收的响应项。

数据流:输入是调用编号、工具参数和当前输出内容 → 把 body、success 交给 function_tool_response → 返回对应的 ResponseInputItem。

调用关系:上层统一响应流程会调用它。真正区分普通函数工具和自定义工具的细节交给 function_tool_response 处理。

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

FunctionToolOutput::post_tool_use_response230–232 ↗
fn post_tool_use_response(&self, _call_id: &str, _payload: &ToolPayload) -> Option<JsonValue>

作用:提供函数工具结束后要给钩子看的额外响应。如果没有提前设置,就不提供。

数据流:它读取 post_tool_use_response 字段 → 克隆其中的 JSON → 返回 Some(JSON) 或 None。

调用关系:工具后处理流程会调用它。它不会生成模型响应,只是把额外记录或钩子需要的数据交出去。

ApplyPatchToolOutput::from_text240–242 ↗
fn from_text(text: String) -> Self

作用:用一段文字创建“应用补丁”工具的输出。补丁工具通常返回应用结果说明,这里把它存起来。

数据流:输入是一段文本 → 放进 ApplyPatchToolOutput 的 text 字段 → 返回新的补丁工具输出对象。

调用关系:应用补丁工具处理完成后会调用它。后续日志、响应和钩子都会从这个对象里读取补丁结果。

调用图:被 2 处调用(handle_call, post_tool_use_payload_uses_patch_input_and_tool_output)。

ApplyPatchToolOutput::log_preview246–248 ↗
fn log_preview(&self) -> String

作用:给补丁应用结果生成短日志预览。这样能看到补丁大致做了什么,又不会让日志过长。

数据流:它读取 text → 交给 telemetry_preview 按字节数和行数裁剪 → 返回预览文本。

调用关系:日志系统通过 ToolOutput 接口调用它。它复用统一预览规则,和其他工具保持一致。

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

ApplyPatchToolOutput::success_for_logging250–252 ↗
fn success_for_logging(&self) -> bool

作用:告诉日志系统应用补丁输出代表成功。这个类型通常只在补丁已经应用后创建。

数据流:不读取额外数据 → 直接返回 true → 不修改状态。

调用关系:日志阶段会调用它,用来标记补丁工具事件成功。

ApplyPatchToolOutput::to_response_item254–263 ↗
fn to_response_item(&self, call_id: &str, payload: &ToolPayload) -> ResponseInputItem

作用:把补丁应用结果变成模型能读的函数工具响应。模型之后能知道文件修改结果。

数据流:输入是调用编号、工具参数和补丁结果文本 → 把文本包成 InputText 内容项,并标记 success 为 true → 交给 function_tool_response → 返回响应项。

调用关系:补丁工具完成后,统一响应流程会调用它。它把具体格式选择交给 function_tool_response。

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

ApplyPatchToolOutput::post_tool_use_response265–267 ↗
fn post_tool_use_response(&self, _call_id: &str, _payload: &ToolPayload) -> Option<JsonValue>

作用:把补丁工具的文字结果提供给工具结束后的钩子或记录系统。

数据流:它读取 text → 复制成 JSON 字符串 → 返回 Some(JSON 字符串)。

调用关系:工具后处理流程会调用它。这样补丁应用的结果不只给模型看,也能被后续审计或钩子拿到。

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

ApplyPatchToolOutput::code_mode_result269–271 ↗
fn code_mode_result(&self, _payload: &ToolPayload) -> JsonValue

作用:给代码模式返回补丁工具结果。这里返回一个空 JSON 对象,表示没有额外结构化数据要给代码模式。

数据流:不读取补丁文本 → 创建一个空 JSON 对象 → 返回这个对象。

调用关系:代码模式消费补丁工具结果时会调用它。补丁的主要信息已经通过普通响应和后处理传递,这里只给一个占位式结构结果。

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

AbortedToolOutput::log_preview279–281 ↗
fn log_preview(&self) -> String

作用:给被中止的工具调用生成短日志预览。它通常记录为什么没有继续执行。

数据流:它读取 message → 交给 telemetry_preview 裁剪 → 返回日志预览。

调用关系:工具被取消、拒绝或提前停止后,日志系统会调用它。它让失败原因能被看见,但仍受日志长度限制。

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

AbortedToolOutput::success_for_logging283–285 ↗
fn success_for_logging(&self) -> bool

作用:告诉日志系统这个工具调用没有成功。中止不是正常完成,所以返回失败标记。

数据流:不读取额外输入 → 直接返回 false → 不修改状态。

调用关系:日志阶段会调用它,用来把中止事件和正常工具完成区分开。

AbortedToolOutput::to_response_item287–304 ↗
fn to_response_item(&self, call_id: &str, payload: &ToolPayload) -> ResponseInputItem

作用:把被中止的工具调用转换成对话响应,避免模型一直等一个永远不会来的结果。

数据流:输入是调用编号、原工具参数和中止消息 → 如果原来是工具搜索,就返回空工具列表的完成响应 → 否则把中止消息包成函数工具输出 → 返回响应项。

调用关系:取消或中止流程会调用它。它对工具搜索做了特殊处理,因为工具搜索有自己的响应格式;其他工具则交给 function_tool_response 包装。

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

ExecCommandToolOutput::log_preview323–325 ↗
fn log_preview(&self) -> String

作用:给命令执行结果生成短日志预览。命令输出可能非常长,所以日志只保留可控摘要。

数据流:它先调用 response_text 生成带耗时、退出码、输出等信息的完整说明 → 再交给 telemetry_preview 截短 → 返回预览。

调用关系:命令工具完成后,日志系统会通过 ToolOutput 接口调用它。它依赖 ExecCommandToolOutput::response_text 先整理人能读懂的结果。

调用图:调用 2 个内部函数(response_text, telemetry_preview)。

ExecCommandToolOutput::success_for_logging327–329 ↗
fn success_for_logging(&self) -> bool

作用:告诉日志系统命令工具调用本身成功产出了结果。这里不按进程退出码判断失败,因为即使命令退出码非零,也仍然是一次成功记录到的执行结果。

数据流:不读取退出码 → 直接返回 true → 不修改任何状态。

调用关系:日志阶段会调用它。真正的退出码会写进响应文本里,由模型或用户判断命令执行意义上的成败。

ExecCommandToolOutput::to_response_item331–340 ↗
fn to_response_item(&self, call_id: &str, payload: &ToolPayload) -> ResponseInputItem

作用:把命令执行结果变成模型能读的函数工具响应。里面会包含耗时、退出码、会话 ID 和截断后的输出。

数据流:输入是调用编号、工具参数和命令结果 → 调用 response_text 生成说明文本 → 包成 InputText,success 标记为 true → 交给 function_tool_response → 返回响应项。

调用关系:命令执行完成后,统一响应流程会调用它。它把结果文本组织交给 ExecCommandToolOutput::response_text,把协议包装交给 function_tool_response。

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

ExecCommandToolOutput::post_tool_use_id342–348 ↗
fn post_tool_use_id(&self, call_id: &str) -> String

作用:决定工具结束后记录时使用哪个调用编号。某些命令事件有自己的 event_call_id,需要优先用它。

数据流:输入是默认 call_id → 如果 event_call_id 为空就返回默认 call_id → 否则返回 event_call_id 的副本。

调用关系:工具后处理或审计流程会调用它。它保证后续记录能和真正的命令事件对上号。

ExecCommandToolOutput::post_tool_use_input350–354 ↗
fn post_tool_use_input(&self, _payload: &ToolPayload) -> Option<JsonValue>

作用:给工具结束后的钩子提供命令输入。主要用于 hook_command,也就是由钩子触发的命令。

数据流:它读取 hook_command → 如果存在,就生成 {"command": 命令文本} 这样的 JSON → 如果不存在,就返回 None。

调用关系:后处理流程会调用它。它只在有钩子命令时提供输入信息,普通命令不一定从这里补充。

ExecCommandToolOutput::post_tool_use_response356–364 ↗
fn post_tool_use_response(&self, _call_id: &str, _payload: &ToolPayload) -> Option<JsonValue>

作用:给工具结束后的钩子提供命令输出,但只在适合的情况下提供。正在运行的进程或非钩子命令不会在这里返回输出。

数据流:它先检查 process_id 和 hook_command → 如果进程还在运行,或不是钩子命令,就返回 None → 否则计算模型输出 token 上限,截短原始输出 → 返回 JSON 字符串。

调用关系:工具后处理流程会调用它。它依赖 model_output_max_tokens 决定长度,再依赖 truncated_output 生成安全大小的输出。

调用图:调用 2 个内部函数(model_output_max_tokens, truncated_output);外部调用 1 个(String)。

ExecCommandToolOutput::code_mode_result366–396 ↗
fn code_mode_result(&self, _payload: &ToolPayload) -> JsonValue

作用:把命令执行结果整理成代码模式容易读取的 JSON。代码模式需要结构化字段,而不只是人看的文本。

数据流:它读取 chunk_id、耗时、退出码、会话 ID、原 token 数和原始输出 → 根据 max_output_tokens 决定是否截断输出 → 组成结构体 → 序列化成 JSON,失败则返回错误字符串。

调用关系:代码模式读取命令结果时会调用它。它复用 truncated_output 做裁剪,但不会使用 function_tool_response,因为这里不是给普通对话协议的响应。

调用图:调用 1 个内部函数(truncated_output);外部调用 3 个(as_secs_f64, from_utf8_lossy, to_value)。

ExecCommandToolOutput::model_output_max_tokens400–402 ↗
fn model_output_max_tokens(&self) -> usize

作用:算出命令输出最多能给模型多少 token。token 可以粗略理解成模型读文字时的“小块”,太多会挤爆上下文。

数据流:它读取 max_output_tokens 和 truncation_policy → 先把可选上限解析成数字 → 再和截断策略的预算取较小值 → 返回最终 token 上限。

调用关系:ExecCommandToolOutput::post_tool_use_response 和 ExecCommandToolOutput::response_text 都会调用它。它是命令输出截断前的“限额计算器”。

调用图:调用 2 个内部函数(resolve_max_tokens, token_budget);被 2 处调用(post_tool_use_response, response_text)。

ExecCommandToolOutput::truncated_output404–407 ↗
fn truncated_output(&self, max_tokens: usize) -> String

作用:把命令的原始字节输出变成文本,并按指定 token 数截短。这样即使命令打印海量内容,也不会塞满模型上下文。

数据流:输入是最大 token 数 → 读取 raw_output 原始字节,用宽容方式转成 UTF-8 文本 → 按 token 截断策略格式化裁剪 → 返回裁剪后的字符串。

调用关系:代码模式结果、后处理响应和普通响应文本都会调用它。它是命令输出长度控制的实际执行者。

调用图:被 3 处调用(code_mode_result, post_tool_use_response, response_text);外部调用 3 个(from_utf8_lossy, formatted_truncate_text, Tokens)。

ExecCommandToolOutput::response_text409–435 ↗
fn response_text(&self) -> String

作用:把命令结果整理成一段人和模型都容易读的说明文字。它会把元信息和输出放在一起。

数据流:它读取 chunk_id、耗时、退出码、进程 ID、原 token 数和原始输出 → 逐段拼出说明,比如 Wall time、Process exited、Output → 调用 model_output_max_tokens 和 truncated_output 裁剪输出 → 返回完整文本。

调用关系:ExecCommandToolOutput::log_preview 会调用它,响应生成也会间接用它。它负责“写报告”,再由其他函数决定是给日志还是给模型。

调用图:调用 2 个内部函数(model_output_max_tokens, truncated_output);被 1 处调用(log_preview);外部调用 3 个(as_secs_f64, new, format!)。

function_tool_response438–463 ↗
fn function_tool_response(
    call_id: &str,
    payload: &ToolPayload,
    body: Vec<FunctionCallOutputContentItem>,
    success: Option<bool>,
) -> ResponseInputItem

作用:把函数类工具的内容包装成正确的对话协议响应。它还会区分普通函数工具和自定义工具,因为两者回传格式不同。

数据流:输入是 call_id、工具参数、内容项列表和成功标记 → 如果只有一段纯文本,就用更简单的 Text 形式;否则保留内容项形式 → 如果 payload 是自定义工具,返回 CustomToolCallOutput;否则返回 FunctionCallOutput。

调用关系:FunctionToolOutput、ApplyPatchToolOutput、AbortedToolOutput、ExecCommandToolOutput 的 to_response_item 都会调用它。它是这些工具输出进入对话协议前的公共包装器。

调用图:被 4 处调用(to_response_item, to_response_item, to_response_item, to_response_item);外部调用 3 个(matches!, ContentItems, Text)。

telemetry_preview465–503 ↗
fn telemetry_preview(content: &str) -> String

作用:生成安全长度的遥测日志预览。遥测就是系统运行记录;这里防止日志被超长内容撑爆,也避免截断到半个中文字符。

数据流:输入是一整段内容 → 先按最大字节数截,但保证不切坏字符 → 再按最大行数取前几行 → 如果确实截断了,就补上截断提示 → 返回最终预览。

调用关系:各种 ToolOutput 的 log_preview 都会调用它。它是全文件统一的日志裁剪工具,让 MCP、命令、补丁、函数工具等都遵守同一套预览限制。

调用图:被 6 处调用(log_preview, log_preview, log_preview, log_preview, log_preview, log_preview);外部调用 2 个(new, take_bytes_at_char_boundary)。

core/src/tools/code_mode/response_adapter.rs源码 ↗
utilrequest handling

这个文件解决的是“两个模块说话格式不一样”的问题。code mode 会产出一批函数调用结果内容,里面可能有文字,也可能有图片;而外层协议需要的是 codex_protocol 里的对应类型。这里定义了一个小接口 IntoProtocol,意思是“把自己变成协议格式”。主函数会拿到一组 code mode 的内容,逐个转换。图片清晰度也会被一一对应地翻译,比如 Auto、Low、High、Original。一个重要细节是:如果图片没有说明清晰度,这里会自动补上默认清晰度 DEFAULT_IMAGE_DETAIL,避免协议里缺少必要信息。可以把它理解成海关申报表转换:货物没变,只是换成目的地系统认可的表格。

函数细节3
into_function_call_output_content_items10–14 ↗
fn into_function_call_output_content_items(
    items: Vec<codex_code_mode::FunctionCallOutputContentItem>,
) -> Vec<FunctionCallOutputContentItem>

作用:把一整组 code mode 返回的内容,批量转换成协议层使用的内容格式。调用方不用关心每个元素怎么翻译,只要把列表交给它就行。

数据流:进去的是一个列表,里面每一项都是 code mode 内部格式的函数调用输出内容。它会按顺序取出每一项,调用对应的转换方法,把文字项、图片项都变成协议格式。出来的是一个新的列表,内容含义不变,但类型已经换成 codex_protocol 能继续处理的格式。

调用关系:它会在 handle_runtime_response 处理运行时返回结果时被调用,属于返回结果进入外层协议之前的转换步骤。它自己不判断业务含义,而是把每个元素交给 IntoProtocol::into_protocol 这套转换规则来完成。

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

CodeModeImageDetail::into_protocol17–25 ↗
fn into_protocol(self) -> ImageDetail

作用:把 code mode 里的图片清晰度选项,转换成协议层里的图片清晰度选项。它保证两边对 Auto、Low、High、Original 这些选择的理解是一一对应的。

数据流:进去的是一个 code mode 的图片细节值,比如低清、高清或原图。它通过匹配每一种可能的值,换成 codex_protocol 中同名同义的值。出来的是协议层的 ImageDetail,没有额外副作用。

调用关系:它主要服务于 FunctionCallOutputContentItem::into_protocol。当函数调用结果里包含图片,并且图片带有清晰度设置时,内容项转换会请它先把清晰度也翻译成协议格式。

FunctionCallOutputContentItem::into_protocol31–46 ↗
fn into_protocol(self) -> FunctionCallOutputContentItem

作用:把单个函数调用输出内容从 code mode 格式翻译成协议格式。它能处理文字,也能处理图片,并且会给没有清晰度说明的图片补上默认值。

数据流:进去的是一个 code mode 的内容项。如果是文字,它把文字原样放进协议的文字项里;如果是图片,它把图片地址原样带过去,并把图片清晰度转换成协议格式。若图片没有清晰度,它会填入 DEFAULT_IMAGE_DETAIL。出来的是一个协议层的 FunctionCallOutputContentItem。

调用关系:它是批量转换函数 into_function_call_output_content_items 处理每个列表元素时真正干活的地方。遇到图片清晰度时,它会进一步调用 CodeModeImageDetail::into_protocol,把这部分小字段也转换正确。

内置工具 schema

这些文件声明面向模型的 schema 和命名约定,用于内置工具以及与 hook 相关的兼容接口。

core/src/tools/handlers/get_context_remaining_spec.rs源码 ↗
configstartup / tool registration

这个文件本身不去计算剩余 token,它做的是“登记工具”。可以把它想成给一个按钮贴标签和写使用说明:按钮名字是什么、按钮是干什么的、点按钮时需要传什么参数、点完会返回什么格式。这里的工具没有输入参数,因为它只是查询当前状态;返回结果固定是一个对象,里面必须有 tokens_left。这个值可能是整数,也可能是 null,null 表示系统现在拿不到这个信息。这个说明很重要,因为模型或外部调用方要按这份格式来调用和读取结果;如果没有它,系统就不知道这个工具存在,也不知道返回值该怎么检查。

函数细节2
create_get_context_remaining_tool10–19 ↗
fn create_get_context_remaining_tool() -> ToolSpec

作用:创建 get_context_remaining 这个工具的完整说明。别人调用它,是为了把这个工具注册进系统,让模型知道可以询问“当前上下文还剩多少空间”。

数据流:进去时不需要业务输入;它读取固定的工具名和描述文字,创建一个没有参数的 JSON Schema(JSON Schema 是一份规定 JSON 数据长什么样的格式说明),再调用 get_context_remaining_output_schema 生成返回值格式;出来的是一个 ToolSpec,也就是这件工具的登记表,里面写清了名字、用途、输入要求和输出要求。

调用关系:它由上层的 spec 流程调用,通常发生在系统收集所有可用工具的时候。它自己不计算 token,只负责组装工具说明,并把“返回值应该长什么样”这部分交给 get_context_remaining_output_schema 来做。

调用图:调用 2 个内部函数(get_context_remaining_output_schema, object);被 1 处调用(spec);外部调用 2 个(new, Function)。

get_context_remaining_output_schema21–36 ↗
fn get_context_remaining_output_schema() -> Value

作用:生成这个工具返回结果的格式说明。它规定返回值必须有 tokens_left,而且这个字段要么是整数,要么是 null。

数据流:进去不需要参数;它用 json! 宏直接拼出一份 JSON 格式规则:结果是一个对象,有 tokens_left 字段,不能带多余字段;出来的是这份 schema,供工具说明使用。

调用关系:它只被 create_get_context_remaining_tool 调用,是工具登记过程中的一个小零件。上层创建工具说明时需要知道输出长什么样,于是来这里拿这份固定的返回格式。

调用图:被 1 处调用(create_get_context_remaining_tool);外部调用 1 个(json!)。

core/src/tools/handlers/request_user_input_spec.rs源码 ↗
domain_logic工具注册、请求处理

这个文件像一张“点餐单模板”和“收银前检查表”。系统有时需要暂停一下,问用户一到三个短问题,比如让用户在几个方案里选一个。这里先规定工具名、问题格式、选项格式,以及自动等待时间的范围:最短 60 秒,最长 240 秒。创建工具说明时,它会告诉调用方:每个问题必须有 id、标题、问题文字和 2 到 3 个互斥选项。真正调用时,它还会检查每个问题有没有选项;没有就直接报错,避免把一个没法回答的问题丢给用户。它也会把自动等待时间夹到允许范围内,太短或太长都会被改到边界值,并记录警告。另一个重要点是,并不是所有运行模式都允许这个工具,所以文件里也提供了“当前模式不可用”的提示文字。

函数细节5
create_request_user_input_tool12–92 ↗
fn create_request_user_input_tool(description: String) -> ToolSpec

作用:创建“request_user_input”这个工具的正式说明书。别人要把这个工具注册给模型或接口使用时,会用它生成工具名、描述、输入参数格式等信息。

数据流:进去的是一段工具描述文字 → 函数把问题、选项、自动等待时间这些字段拼成 JSON Schema(JSON 数据格式说明,告诉系统哪些字段必须有、是什么类型)→ 出来的是一个 ToolSpec,也就是可注册、可调用的工具规格;它本身不提问,只规定怎么提问才合格。

调用关系:它由 spec 调用,通常发生在系统准备工具清单的时候。它把具体的格式构造工作交给 JsonSchema 的 string、object、array、number 等小工具,最后包装成 ResponsesApiTool,供外部调用流程识别这个工具。

调用图:调用 4 个内部函数(array, number, object, string);被 1 处调用(spec);外部调用 4 个(from, format!, Function, vec!)。

request_user_input_unavailable_message94–106 ↗
fn request_user_input_unavailable_message(
    mode: ModeKind,
    available_modes: &[ModeKind],
) -> Option<String>

作用:判断当前运行模式能不能使用“向用户提问”工具。不能用时,它负责生成一句清楚的提示,告诉调用方这个模式下不可用。

数据流:进去的是当前模式和一组允许使用的模式 → 函数检查当前模式是否在允许列表里 → 如果允许就返回 None,意思是没有问题;如果不允许,就拿到这个模式的显示名称,返回一段“request_user_input 在某某模式不可用”的文字。

调用关系:它由 handle_call 调用,发生在真正处理工具调用前。它像门口保安,先看当前模式有没有权限;如果没有权限,就让调用流程尽早停下并给出原因,而不是继续做后面的参数处理。

调用图:调用 1 个内部函数(display_name);被 1 处调用(handle_call);外部调用 2 个(format!, contains)。

normalize_request_user_input_args108–137 ↗
fn normalize_request_user_input_args(
    mut args: RequestUserInputArgs,
) -> Result<RequestUserInputArgs, String>

作用:清理和校正一次“向用户提问”的实际参数。它确保每个问题都能回答,并把自动等待时间限制在系统支持的范围内。

数据流:进去的是 RequestUserInputArgs,也就是调用方提交的问题列表和可选的自动等待时间 → 函数先检查每个问题是否都有非空选项;有问题就返回错误文字 → 然后把每个问题标记为带有“其他”选项,再把 autoResolutionMs 限制在 60000 到 240000 毫秒之间;如果改动了时间,会写一条警告日志 → 出来的是整理后的参数,或者一个错误。

调用关系:它由 handle_call 调用,通常在模式检查通过之后、真正把问题展示给用户之前执行。它不负责展示界面,只负责把输入变成系统放心使用的样子,避免后续流程遇到空选项或不合理等待时间。

调用图:被 1 处调用(handle_call);外部调用 1 个(warn!)。

request_user_input_tool_description139–144 ↗
fn request_user_input_tool_description(available_modes: &[ModeKind]) -> String

作用:生成给调用方看的工具说明文字。说明里会写清楚这个工具能问几个问题、什么时候该设置自动等待时间,以及在哪些模式下可用。

数据流:进去的是允许使用这个工具的模式列表 → 函数先把这些模式整理成一段好读的文字 → 再把等待时间范围和使用建议拼进完整说明 → 出来的是一整段工具描述字符串。

调用关系:它由 spec 调用,通常和 create_request_user_input_tool 一起用于生成工具规格。它把“可用模式怎么说得像人话”这件事交给 format_allowed_modes,然后自己负责组装完整描述。

调用图:调用 1 个内部函数(format_allowed_modes);被 1 处调用(spec);外部调用 1 个(format!)。

format_allowed_modes146–158 ↗
fn format_allowed_modes(available_modes: &[ModeKind]) -> String

作用:把一组可用模式变成自然一点的英文说明。这样工具描述里不会只出现生硬的列表,而是能写成“某模式”“A 或 B 模式”等更容易读的形式。

数据流:进去的是模式列表 → 函数逐个取出模式的显示名称 → 根据数量不同生成不同文字:没有就是“no modes”,一个就是“某某 mode”,两个就是“A or B mode”,多个就是“modes: A,B,C” → 出来的是一段可直接放进说明里的字符串。

调用关系:它只被 request_user_input_tool_description 调用,是一个小的文字格式化帮手。它不参与工具调用本身,只让工具说明更清楚、更适合给人看。

调用图:被 1 处调用(request_user_input_tool_description);外部调用 2 个(format!, iter)。

core/src/tools/handlers/shell_spec.rs源码 ↗
configstartup / tool registration

这份文件不是真的去执行命令,而是写清楚“执行命令工具”的说明书。系统要让模型调用 shell(命令行外壳,用来运行系统命令)时,必须先告诉模型:命令字段叫什么、工作目录怎么填、要不要申请更高权限、输出会是什么格式。这里用 JSON Schema(JSON 格式的参数规则说明)把这些都描述出来,避免模型乱填参数,也方便客户端检查。它还区分新式的 exec_command、给已运行进程继续输入的 write_stdin、旧式的 shell_command,以及 request_permissions 这种向用户要文件或网络权限的工具。一个重要细节是:Windows 下会额外加入安全提示,提醒不要把危险的删除、移动命令跨 shell 拼来拼去,防止误删文件。

函数细节12
create_exec_command_tool15–19 ↗
fn create_exec_command_tool(options: CommandToolOptions) -> ToolSpec

作用:这是测试环境用的简化入口,用来创建一个 exec_command 工具说明。它默认不带 environment_id,但保留 shell 参数,方便测试确认工具规格是否正确。

数据流:进去的是 CommandToolOptions,也就是是否允许登录 shell、是否启用执行权限审批这些开关 → 它把这些开关连同固定选择传给更通用的创建函数 → 出来的是一份 ToolSpec,表示 exec_command 这个工具该怎么被调用。

调用关系:它自己不拼完整工具内容,而是把活儿交给 create_exec_command_tool_with_environment_id。这样测试和正式流程可以共用同一套核心生成逻辑,减少两边写出不同规则的风险。

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

create_exec_command_tool_with_environment_id21–108 ↗
fn create_exec_command_tool_with_environment_id(
    options: CommandToolOptions,
    include_environment_id: bool,
    include_shell_parameter: bool,
) -> ToolSpec

作用:创建新式 exec_command 工具的完整说明。这个工具用于启动一个命令,并且可以返回输出,或者在命令还没结束时返回一个 session_id,之后还能继续交互。

数据流:进去的是一组开关:权限选项、是否允许指定环境、是否允许指定 shell → 它先准备 cmd、workdir、tty、等待时间、输出长度等参数说明,再按开关补上 shell、login、environment_id 和权限审批参数 → 出来的是一个 ToolSpec,里面包含工具名、说明文字、输入参数规则和统一输出格式。

调用关系:这是生成 exec_command 规格的核心函数。create_exec_command_tool 会调用它;更上层的 spec 流程也会在注册工具时用到它。它会请 create_approval_parameters 生成权限相关字段,并请 unified_exec_output_schema 生成命令输出的固定格式。

调用图:调用 6 个内部函数(create_approval_parameters, unified_exec_output_schema, boolean, number, object, string);被 2 处调用(create_exec_command_tool, spec);外部调用 5 个(from, cfg!, format!, Function, vec!)。

create_write_stdin_tool110–152 ↗
fn create_write_stdin_tool() -> ToolSpec

作用:创建 write_stdin 工具的说明。这个工具用于给一个还在运行的命令继续发送文字,比如给交互式程序输入下一行。

数据流:它没有外部输入 → 它定义 session_id、chars、yield_time_ms、max_output_tokens 这些参数:要写给哪个会话、写什么、等多久、最多收多少输出 → 出来的是一个 ToolSpec,并且使用和 exec_command 一样的输出格式。

调用关系:更上层的 spec 流程会在注册工具时调用它。它依赖 unified_exec_output_schema,因为继续输入后返回的内容,和启动命令后返回的内容属于同一类输出。

调用图:调用 4 个内部函数(unified_exec_output_schema, number, object, string);被 1 处调用(spec);外部调用 3 个(from, Function, vec!)。

create_shell_command_tool154–222 ↗
fn create_shell_command_tool(options: CommandToolOptions) -> ToolSpec

作用:创建旧式 shell_command 工具的说明。它用于一次性运行一段 shell 命令并返回结果,比 exec_command 更简单,不强调长期交互。

数据流:进去的是 CommandToolOptions → 它准备 command、workdir、timeout_ms 等基本参数;如果允许登录 shell,就加 login;如果启用权限审批,就加对应权限字段 → 出来的是一个 ToolSpec。Windows 下还会把 PowerShell 示例和安全规则写进说明里,非 Windows 下则提醒尽量用 workdir,不要随便 cd。

调用关系:更上层的 spec 流程会用它注册 shell_command。它会调用 create_approval_parameters 来补上审批相关参数;在 Windows 上还会调用 windows_shell_guidance,把安全提示合进工具说明。

调用图:调用 5 个内部函数(create_approval_parameters, boolean, number, object, string);被 1 处调用(spec);外部调用 5 个(from, cfg!, format!, Function, vec!)。

create_request_permissions_tool224–254 ↗
fn create_request_permissions_tool(description: String) -> ToolSpec

作用:创建 request_permissions 工具的说明。这个工具让模型在权限不够时,向用户或客户端请求更多文件系统或网络权限。

数据流:进去的是一段工具描述文字 → 它定义 reason、environment_id、permissions 三个参数:为什么要权限、针对哪个环境、具体要哪些权限 → 出来的是一个 ToolSpec,规定 permissions 是必填项。

调用关系:更上层的 spec 流程会在需要提供权限申请能力时调用它。它会调用 permission_profile_schema 来描述“网络权限”和“文件读写权限”该怎么填写。

调用图:调用 3 个内部函数(permission_profile_schema, object, string);被 1 处调用(spec);外部调用 3 个(from, Function, vec!)。

request_permissions_tool_description256–259 ↗
fn request_permissions_tool_description() -> String

作用:返回 request_permissions 工具的标准说明文字。它把什么时候该申请权限、权限会影响哪些后续命令讲清楚。

数据流:它没有输入 → 直接生成一段固定文字,说明可以申请文件系统或网络权限,可以指定环境,也说明相对路径按环境当前目录理解 → 出来的是 String 类型的描述文本。

调用关系:更上层的 spec 流程会拿这段文字交给 create_request_permissions_tool。这样工具说明集中在这里,避免别处复制一份后改漏。

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

unified_exec_output_schema261–293 ↗
fn unified_exec_output_schema() -> Value

作用:定义 exec_command 和 write_stdin 共同使用的输出格式。它告诉调用方:命令输出里可能有哪些字段,哪些字段一定会有。

数据流:它没有输入 → 它生成一个 JSON 对象规则,包含 wall_time_seconds、output、exit_code、session_id、chunk_id 等字段说明,并规定 wall_time_seconds 和 output 必须出现 → 出来的是 serde_json::Value,也就是一份 JSON 结构。

调用关系:create_exec_command_tool_with_environment_id 和 create_write_stdin_tool 都会调用它。这样启动命令和继续写入命令这两个工具返回同一种形状的数据,客户端处理起来更简单。

调用图:被 2 处调用(create_exec_command_tool_with_environment_id, create_write_stdin_tool);外部调用 1 个(json!)。

create_approval_parameters295–341 ↗
fn create_approval_parameters(
    exec_permission_approvals_enabled: bool,
) -> BTreeMap<String, JsonSchema>

作用:生成“这条命令要不要申请更高权限”的参数说明。它让命令工具可以表达:用默认沙箱、请求额外权限,或者完全升级执行。

数据流:进去的是 exec_permission_approvals_enabled,表示是否允许逐条命令申请额外权限 → 它根据这个开关决定 sandbox_permissions 里有哪些可选值,并加入 justification、prefix_rule;如果允许额外权限,还会加入 additional_permissions → 出来的是一组可插入工具参数表的字段。

调用关系:create_exec_command_tool_with_environment_id 和 create_shell_command_tool 都会调用它。它还会调用 permission_profile_schema 来描述 additional_permissions 里网络和文件权限的具体写法。

调用图:调用 4 个内部函数(permission_profile_schema, array, string, string_enum);被 2 处调用(create_exec_command_tool_with_environment_id, create_shell_command_tool);外部调用 3 个(from, json!, vec!)。

permission_profile_schema343–354 ↗
fn permission_profile_schema() -> JsonSchema

作用:定义一份“权限申请清单”的整体格式。它把网络权限和文件系统权限放在同一个对象里,像一张总申请表。

数据流:它没有输入 → 它分别取来 network_permissions_schema 和 file_system_permissions_schema,组合成一个包含 network 和 file_system 的对象规则,并加上一句说明 → 出来的是 JsonSchema。

调用关系:create_approval_parameters 用它描述单条命令想要的额外权限;create_request_permissions_tool 用它描述专门申请权限工具的 permissions 参数。它再往下把细节交给 network_permissions_schema 和 file_system_permissions_schema。

调用图:调用 3 个内部函数(file_system_permissions_schema, network_permissions_schema, object);被 2 处调用(create_approval_parameters, create_request_permissions_tool);外部调用 1 个(from)。

network_permissions_schema356–369 ↗
fn network_permissions_schema() -> JsonSchema

作用:定义网络权限该怎么申请。这里的规则很简单:只需要说明是否启用网络访问。

数据流:它没有输入 → 它生成一个对象规则,里面有 enabled 这个布尔值,也就是 true 或 false → 出来的是带说明文字的 JsonSchema。

调用关系:permission_profile_schema 会调用它,把它作为权限总表里的 network 部分。它只关心网络,不处理文件读写。

调用图:调用 2 个内部函数(boolean, object);被 1 处调用(permission_profile_schema);外部调用 1 个(from)。

file_system_permissions_schema371–400 ↗
fn file_system_permissions_schema() -> JsonSchema

作用:定义文件系统权限该怎么申请。它把“想读哪些绝对路径”和“想写哪些绝对路径”分开写清楚。

数据流:它没有输入 → 它生成一个对象规则,里面有 read 和 write 两个数组字段,每个数组里放路径字符串 → 出来的是带说明文字的 JsonSchema。

调用关系:permission_profile_schema 会调用它,把它作为权限总表里的 file_system 部分。它只描述申请格式,不真正检查路径,也不授予权限。

调用图:调用 3 个内部函数(array, object, string);被 1 处调用(permission_profile_schema);外部调用 1 个(from)。

windows_shell_guidance402–407 ↗
fn windows_shell_guidance() -> &'static str

作用:返回 Windows 上运行命令时的安全提醒。重点是防止因为 shell 混用、路径拼错或后台窗口弹出而造成误删文件或打扰用户。

数据流:它没有输入 → 直接返回一段固定的多行英文说明,提醒使用 PowerShell 原生命令、删除或移动前确认绝对路径、后台进程默认隐藏窗口 → 出来的是静态字符串。

调用关系:Windows 平台下,create_exec_command_tool_with_environment_id 和 create_shell_command_tool 会把这段提示放进工具描述里。这样模型在调用 Windows 命令前能看到额外安全规则。

core/src/tools/hook_names.rs源码 ↗
data_modelrequest handling / hook matching

这里解决的是“同一个工具可能有好几个叫法”的问题。钩子可以理解成程序在做某件事前后,通知外部脚本来检查或记录。发给钩子的正式名字必须稳定,比如真正改文件的工具叫 apply_patch,日志和策略就应该一直看到这个名字。但有些用户的匹配规则可能写的是别的生态里的名字,比如 WriteEdit。这个文件用 HookToolName 把两件事分开:name 是正式写进钩子输入里的名字;matcher_aliases 是只在内部匹配规则时用的别名,不会泄漏给外部脚本。可以把它想成身份证姓名和小名:办正式手续用身份证姓名,熟人查找时也认小名。

函数细节6
HookToolName::new21–26 ↗
fn new(name: impl Into<String>) -> Self

作用:创建一个最普通的钩子工具名,没有任何兼容别名。有人只需要一个稳定名字、不需要额外匹配旧叫法时会用它。

数据流:进去的是一个可转成字符串的名字 → 它把这个名字存进 name,并准备一个空的别名列表 → 出来的是一个 HookToolName,表示“正式名就是这个,没有其他小名”。

调用关系:它是最基础的构造入口。很多权限审批、钩子请求相关流程和测试会用它来造一个明确的工具名;HookToolName::bash 也把创建工作交给它,因为 Bash 没有额外别名。

调用图:被 11 处调用(approve_mode_skips_guardian_in_every_permission_mode, approve_mode_skips_when_annotations_do_not_require_approval, full_access_mode_skips_mcp_tool_approval_for_all_approval_modes, guardian_mode_mcp_denial_returns_rationale_message, guardian_mode_skips_auto_when_annotations_do_not_require_approval, permission_request_hook_allows_mcp_tool_call, permission_request_hook_runs_after_remembered_mcp_approval, permission_request_hook_uses_hook_tool_name_without_metadata, prompt_mode_waits_for_approval_when_annotations_do_not_require_approval, hook_tool_name (+1 more));外部调用 2 个(into, new)。

HookToolName::apply_patch34–39 ↗
fn apply_patch() -> Self

作用:创建代表 apply_patch 的钩子工具名,也就是通过补丁方式改文件的工具名。它额外认 WriteEdit,方便兼容那些按别的工具叫法写好的钩子规则。

数据流:不需要外部输入 → 它固定把正式名设为 apply_patch,再放入两个内部匹配别名 WriteEdit → 出来的是一个既能稳定对外叫 apply_patch、又能被旧匹配规则选中的工具名对象。

调用关系:它会在生成工具使用后的钩子载荷、以及权限请求载荷时被调用。后面的钩子运行流程可以用正式名写入标准输入,同时用别名帮助选择该触发哪些处理器。

调用图:被 2 处调用(post_tool_use_payload, permission_request_payload);外部调用 1 个(vec!)。

HookToolName::spawn_agent46–51 ↗
fn spawn_agent() -> Self

作用:创建代表 spawn_agent 的钩子工具名,也就是启动子代理时使用的名字。它同时兼容 Agent 这个别名。

数据流:不需要外部输入 → 它固定生成正式名 spawn_agent,并添加内部匹配别名 Agent → 出来的是一个用于子代理创建场景的工具名对象。

调用关系:当系统需要把某个函数调用解释成钩子里的工具名时,会用到它。它让外部收到的名字保持 Codex 自己的 spawn_agent,但匹配配置时也能照顾写成 Agent 的规则。

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

HookToolName::bash54–56 ↗
fn bash() -> Self

作用:创建历史上用于类似 shell 命令工具的钩子名字 Bash。它主要是把这个固定名字包装成统一的 HookToolName

数据流:不需要外部输入 → 它把固定字符串 Bash 交给 HookToolName::new → 出来的是一个正式名为 Bash、没有别名的工具名对象。

调用关系:生成命令执行后的钩子载荷时会用到它,包括普通工具使用和统一执行工具的场景。它复用 HookToolName::new,说明 Bash 这个名字本身就是要对外使用的稳定名字。

调用图:被 3 处调用(post_tool_use_payload, post_unified_exec_tool_use_payload, bash);外部调用 1 个(new)。

HookToolName::name59–61 ↗
fn name(&self) -> &str

作用:取出这个工具对外公开的正式名字。钩子进程真正收到的 tool_name 就应该来自这里。

数据流:进去的是一个已经创建好的 HookToolName → 它只读取里面的 name 字段,不改任何东西 → 出来的是正式工具名文本,比如 apply_patchBash

调用关系:运行预工具钩子的流程会调用它,把稳定的工具名放进钩子输入里。它和 matcher_aliases 分工很清楚:这里给外部看正式名,不给外部看兼容别名。

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

HookToolName::matcher_aliases64–66 ↗
fn matcher_aliases(&self) -> &[String]

作用:取出这个工具在内部匹配规则时可接受的额外名字。它让旧配置或相邻工具生态里的叫法仍然能命中同一组钩子。

数据流:进去的是一个 HookToolName → 它只读取内部的别名列表,不改对象 → 出来的是一组别名文本;如果这个工具没有别名,就返回空列表。

调用关系:运行预工具钩子的流程会调用它,用正式名加这些别名一起判断哪些钩子该触发。它不会改变写给钩子进程的载荷,只影响“该不该选中这个钩子”的内部判断。

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

协议负载 schema

这些文件定义工具和审批流程使用的共享协议级请求、响应、元数据和规划负载。

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

这份文件本身不做计算,也没有函数。它像一张统一的表格字段清单,规定了审批相关元数据里该用哪些键和值。比如,一个请求是不是“审批请求”、审批对象是不是“MCP 工具调用”、信息来源是不是“连接器”、工具名和工具参数该放在哪个字段里,都在这里用常量写死。这样做很重要:如果各处代码都自己手打字符串,很容易出现一个地方写成 tool_name,另一个地方写成 toolName,结果系统就读不到数据。把这些名字集中在这里后,其他代码只要引用这些常量,就能保证大家说的是同一种“语言”。可以把它理解成快递单上的标准栏目名:寄件人、收件人、地址都固定,后面的流程才能顺利分拣。

protocol/src/plan_tool.rs源码 ↗
data_modelrequest handling

这个文件本身不做具体动作,它主要定义数据结构。项目里有一个叫 update_plan 的工具,用来展示或更新一组计划步骤,比如“还没做”“正在做”“做完了”。为了避免调用方传错字段、状态写乱、前后端理解不一致,这里把可用的步骤状态和参数格式固定下来。StepStatus 是步骤状态,只允许三种:待处理、进行中、已完成。PlanItemArg 表示计划里的一条:包含步骤文字和它的状态。UpdatePlanArgs 表示一次更新请求:可以带一段解释说明,也必须带一个计划列表。文件还给这些类型加上序列化、反序列化、JSON Schema 和 TypeScript 导出能力;通俗说,就是让 Rust、JSON 和 TypeScript 世界都能按同一份“说明书”读写这些数据。特别要注意,结构体拒绝未知字段,这能尽早发现拼写错误或多传了不该传的内容。

protocol/src/request_permissions.rs源码 ↗
data_modelrequest handling

这个文件像一张标准申请表。程序在沙箱(一种限制环境,防止代码随便联网或读写文件)里运行时,有时需要临时放开一点权限,比如允许访问网络,或者允许读某个目录。这里定义了申请表、审批结果和事件通知的格式。RequestPermissionProfile 放真正要申请的权限:网络权限和文件系统权限,都可以没有。PermissionGrantScope 说明批准范围,是只对当前一轮对话有效,还是整个会话有效。RequestPermissionsArgs 是发起申请时带的内容,比如环境编号、申请理由和权限清单。RequestPermissionsResponse 是审批后的返回。RequestPermissionsEvent 是系统向外通知“有人申请权限了”的事件,里面还带时间、调用编号、当前目录等信息。文件里还做了两个相近权限类型之间的转换,方便不同层复用同一份权限数据。

函数细节3
RequestPermissionProfile::is_empty26–28 ↗
fn is_empty(&self) -> bool

作用:这个函数用来判断一份权限申请是不是空的。也就是说,既没有申请网络权限,也没有申请文件系统权限。

数据流:进去的是一个 RequestPermissionProfile,也就是一份权限申请资料;它检查里面的 network 和 file_system 两栏是不是都没有填;出来的是 true 或 false,true 表示这张申请表什么权限都没要,false 表示至少要了一类权限。

调用关系:它是 RequestPermissionProfile 自己带的小检查工具。其他流程在收到权限申请时,可以先用它判断这次请求有没有实际内容,避免拿一张空申请表继续走审批流程。

AdditionalPermissionProfile::from32–37 ↗
fn from(value: RequestPermissionProfile) -> Self

作用:这个函数把 RequestPermissionProfile 转成 AdditionalPermissionProfile。两者装的都是网络和文件权限,只是用在系统里的不同场景。

数据流:进去的是一份 RequestPermissionProfile;它把其中的 network 和 file_system 原样搬过去;出来的是一份 AdditionalPermissionProfile,内容不变,只是换成了另一个类型的包装。

调用关系:它是 Rust 的 From 转换函数,意思是“这个类型可以从另一个类型自然变来”。当后续模块需要 AdditionalPermissionProfile,而当前手里只有 RequestPermissionProfile 时,就会用这个转换把申请来的权限交给需要额外权限配置的地方。

RequestPermissionProfile::from41–46 ↗
fn from(value: AdditionalPermissionProfile) -> Self

作用:这个函数把 AdditionalPermissionProfile 转回 RequestPermissionProfile。它让已经存在的额外权限配置,也能重新当作一份权限申请资料来使用或传输。

数据流:进去的是一份 AdditionalPermissionProfile;它取出里面的 network 和 file_system;出来的是一份 RequestPermissionProfile,权限内容保持一样,只是换了用于“请求权限”的数据外壳。

调用关系:它和反方向的转换函数配套。系统在不同层之间传递权限数据时,有的地方说“额外权限”,有的地方说“请求权限”,这个函数负责把同一份信息换成请求权限流程能识别的格式。

protocol/src/request_user_input.rs源码 ↗
data_modelrequest handling / cross-cutting

这个文件本身不做提问,也不显示界面,它更像一张标准表格,规定“请求用户输入”这件事该怎么写。比如系统需要问用户一个问题,可以用 RequestUserInputQuestion 写清楚问题编号、标题、正文、是否是“其他”选项、是否是秘密内容。若是选择题,还能带上多个 RequestUserInputQuestionOption,也就是每个选项的文字和说明。RequestUserInputArgs 表示一次提问请求,里面可以有多个问题,还可以设置 autoResolutionMs,也就是多少毫秒后自动处理。用户答完后,RequestUserInputResponse 用问题编号对应答案,方便系统知道每个回答属于哪个问题。RequestUserInputEvent 则用于把这次提问作为事件发出去,并带上 call_id 和 turn_id,方便追踪它属于哪次工具调用、哪一轮对话。这里还用了序列化,意思是这些结构能安全地变成 JSON 发送,也能从 JSON 读回来。

扩展工具 schema

这些文件提供 schema 生成和工具契约定义,用于目标、技能和网页搜索扩展接口。

ext/goal/src/spec.rs源码 ↗
configstartup / tool registration

这个文件像一张“工具说明书”。系统里有一种会被保存下来的线程目标,比如用户明确要求“接下来完成某件事”,还可能带预算。为了让 Responses API 能安全地调用这些能力,这里把三个工具包装成标准格式:get_goal 用来查看当前目标;create_goal 用来在明确要求时创建目标;update_goal 只用来把目标标成完成或真正卡住。文件特别强调:不能把普通任务随便推断成目标,也不能因为难、慢、快没预算了就说完成或卡住。每个函数都会生成一个 ToolSpec,也就是“工具登记表”,里面包含工具名、说明文字、参数格式。参数格式用 JSON Schema(用 JSON 这种通用数据格式描述参数长什么样)来约束,避免调用方乱传字段。

函数细节3
create_get_goal_tool13–23 ↗
fn create_get_goal_tool() -> ToolSpec

作用:创建“查看当前目标”的工具说明。别人调用这个工具时,不需要传参数,只是询问当前线程有没有目标、目标状态、预算和已用量等信息。

数据流:进去时没有业务输入,只读取固定的工具名和说明文字 → 它做出一个空参数的 JSON Schema,表示这个工具不收额外字段 → 出来一个 ToolSpec,交给系统注册,让外部可以按规范调用 get_goal。

调用关系:它通常在工具规格被汇总注册时由 spec 这一层调用。它不继续调用别的业务流程,只是把 ResponsesApiTool 包进 ToolSpec::Function,像把一张说明卡放进工具目录里。

调用图:调用 1 个内部函数(object);被 1 处调用(spec);外部调用 3 个(new, new, Function)。

create_create_goal_tool25–58 ↗
fn create_create_goal_tool() -> ToolSpec

作用:创建“新建目标”的工具说明。它告诉调用方:必须给 objective,也就是具体目标;token_budget 是可选的,而且只有用户或更高层指令明确要求预算时才填。

数据流:进去时没有外部参数,只使用文件里的常量和写死的说明 → 它先列出这个工具允许的参数:objective 是必填字符串,token_budget 是可选整数 → 再生成一个 ToolSpec,里面带着严格的使用提醒:不要从普通任务里自行猜目标,未完成目标存在时不能乱建新的。

调用关系:它由 spec 这一层在准备工具列表时调用。它把参数说明交给 JsonSchema 组装,再包成 ResponsesApiTool,最后变成 ToolSpec::Function,供 Responses API 知道 create_goal 应该怎么被调用。

调用图:调用 3 个内部函数(integer, object, string);被 1 处调用(spec);外部调用 4 个(from, format!, Function, vec!)。

create_update_goal_tool60–94 ↗
fn create_update_goal_tool() -> ToolSpec

作用:创建“更新目标状态”的工具说明。这个工具只允许把目标标为 complete(完成)或 blocked(确实卡住),不能拿来暂停、恢复或改预算。

数据流:进去时没有运行时输入,只读取固定说明 → 它构造一个只接受 status 字段的参数格式,并且 status 只能是 complete 或 blocked → 输出一个 ToolSpec,同时把很多关键规则写进描述里,比如只有目标真的完成才可标完成,同一个阻塞条件连续出现至少三轮且无法推进时才可标卡住。

调用关系:它在工具注册阶段由 spec 这一层调用。它主要依赖 JsonSchema 的枚举能力来限制 status 的取值,然后把这份规则包装成 ToolSpec::Function,让后续真正调用 update_goal 的一方不能随意发明状态。

调用图:调用 2 个内部函数(object, string_enum);被 1 处调用(spec);外部调用 3 个(from, Function, vec!)。

ext/skills/src/tools/schema.rs源码 ↗
utiltool schema generation

技能工具通常需要明确说明:输入长什么样,输出长什么样。这个文件就是专门生成这份说明书的。它借助 schemars,把实现代码里的 Rust 类型自动变成 JSON Schema,然后再把完整 schema 里和工具接口最相关的部分挑出来,比如字段列表、必填字段、类型、额外字段规则等。这里有一个重要区别:输入 schema 不会把可选值额外标成 null,而输出 schema 会允许可选值为 null。可以把它想成给工具做“菜单说明”:输入菜单告诉别人点菜时必须怎么填,输出菜单告诉别人上菜后盘子里可能有哪些东西,也可能有空值。这样工具接口更清楚,也更适合被机器读取和校验。

函数细节3
input_schema_for6–8 ↗
fn input_schema_for() -> Value

作用:这个函数为某个工具的输入类型生成 JSON Schema。别人要调用工具前,可以用它知道输入参数该怎么写。

数据流:进去的是一个 Rust 类型 T,这个类型必须能生成 JsonSchema;函数把“不要额外把可选项标成 null”这个规则交给 schema_for;出来的是一个 JSON 值,里面描述了输入数据允许有哪些字段、哪些字段必填、字段类型是什么。

调用关系:它是给输入参数用的简洁入口,本身不做复杂工作,而是把真正的生成和整理交给 schema_for。需要展示或校验工具输入格式时,会走到这里。

output_schema_for10–12 ↗
fn output_schema_for() -> Value

作用:这个函数为某个工具的输出类型生成 JSON Schema。它让接收工具结果的一方提前知道返回数据大概会是什么样。

数据流:进去的是一个能生成 JsonSchema 的 Rust 类型 T;函数把“可选项可以表现为 null”这个规则交给 schema_for;出来的是一个 JSON 值,用来说明工具输出的字段、类型和可能的空值。

调用关系:它是给输出结果用的入口,和 input_schema_for 共用同一个底层生成函数 schema_for,只是传入的空值规则不同。生成工具说明、暴露工具能力时会用到它。

schema_for14–42 ↗
fn schema_for(option_add_null_type: bool) -> Value

作用:这个函数是真正干活的地方:它把 Rust 类型变成 JSON Schema,并把不需要的外层信息剥掉,只留下工具接口关心的部分。

数据流:进去的是一个 Rust 类型 T,以及一个布尔值 option_add_null_type,表示可选字段要不要额外允许 null;它先用 schemars 的 draft2019_09 规则生成完整 schema,再用 serde_json 转成普通 JSON;接着确认根部是 JSON 对象,然后只取 properties、required、type、additionalProperties、$defs、definitions 这些关键字段;出来的是一份更精简的 JSON 对象。如果生成结果不能序列化会直接报错,如果根部不是对象则认为这是不可能发生的问题。

调用关系:input_schema_for 和 output_schema_for 都把活儿交给它。它内部调用外部库来创建 schema 设置、生成根 schema、转成 JSON 对象;最后自己负责筛选字段,保证交出去的是适合技能工具描述使用的 schema。

调用图:外部调用 5 个(new, draft2019_09, Object, to_value, unreachable!)。

ext/web-search/src/schema.rs源码 ↗
io_transportspec generation

这个文件的作用像是给网页搜索工具写一张“点菜单”:菜单上说明有哪些可选项、哪些必填、每项应该是什么类型。它先根据 codex_api::SearchCommands 这个命令类型自动生成一份 JSON Schema。JSON Schema 可以理解成“检查 JSON 数据合不合规的规则表”。生成时它特意把子规则直接展开,避免引用绕来绕去;同时不把可选值自动写成 null,减少歧义。然后它把生成结果转成普通 JSON,并只挑出工具声明真正需要的几个字段,比如 properties、required、type 等。这样输出的 schema 更干净,更适合交给上层的 spec 使用。如果生成出来的东西不能序列化,程序会直接报错,因为这代表内部定义已经坏了。

函数细节1
commands_schema6–36 ↗
fn commands_schema() -> Value

作用:生成网页搜索命令的 JSON Schema,也就是告诉外部“这个工具接受什么样的 JSON 输入”。调用它的人通常是要发布或组装工具说明的人。

数据流:进去时不需要外部参数,但它会读取 SearchCommands 这个命令类型的结构 → 它用 schemars 生成 JSON Schema,调整生成规则,再把结果转成 serde_json::Value 这种通用 JSON 值 → 最后只保留 properties、required、type、additionalProperties、$defs、definitions 这些关键字段,返回一份精简后的 JSON 对象;如果转换失败会直接 panic,如果结果不是对象则认为不可能发生。

调用关系:它被 spec 调用,用来把“搜索命令的 Rust 类型定义”变成“对外可读的工具参数说明”。内部它把主要工作交给 schemars 的 draft2019_09 生成器和 serde_json 的转换功能,自己负责调参数和裁剪最终输出。

调用图:被 1 处调用(spec);外部调用 6 个(new, draft2019_09, Object, panic!, to_value, unreachable!)。

MCP 工具配置

此文件将 MCP 暴露的 codex 工具负载转换为经过验证的 schema 和核心运行时配置。

mcp-server/src/codex_tool_config.rs源码 ↗
configconfig load / tool registration / request handling

可以把这个文件理解成“Codex 工具的点菜单”。客户端要启动 Codex,会提交 prompt、模型名、工作目录、是否需要人工批准命令、沙箱权限等信息;这个文件先定义这些菜单项长什么样,再把它们翻译成 codex-core 能懂的 Config。这里还会生成 JSON Schema(用 JSON 描述输入格式的说明书),让 MCP 客户端知道哪些字段允许、哪些必填、枚举值有哪些。它也定义了继续对话用的 codex-reply 参数,并兼容旧字段 conversationId。比较重要的是:它明确拒绝未知字段,避免客户端偷偷传入已经移除或拼错的配置;同时把输出固定成 threadId 和 content,方便调用方稳定解析结果。

函数细节11
AskForApproval::from77–84 ↗
fn from(value: CodexToolCallApprovalPolicy) -> Self

作用:把 MCP 工具参数里的“命令是否要批准”选项,转换成 Codex 核心代码真正使用的批准策略。这样外部接口可以有自己的可生成说明书的类型,内部运行仍用核心类型。

数据流:进去的是 CodexToolCallApprovalPolicy,比如 untrusted、on-failure、on-request、never。函数按对应关系翻译:untrusted 变成 UnlessTrusted,on-failure 变成 OnFailure,on-request 变成 OnRequest,never 变成 Never。出来的是 AskForApproval,不改动其他数据。

调用关系:当 CodexToolCallParam::into_config 组装运行配置时,如果用户传了 approval-policy,就会通过这个转换把外部参数接到 Codex 核心配置上。

SandboxMode::from98–104 ↗
fn from(value: CodexToolCallSandboxMode) -> Self

作用:把 MCP 工具参数里的沙箱模式,转换成 Codex 核心代码使用的沙箱模式。沙箱就是给程序划定能读写哪些地方的安全边界。

数据流:进去的是 CodexToolCallSandboxMode,比如 read-only、workspace-write、danger-full-access。函数逐个匹配,把它翻译成 codex_protocol 里的 SandboxMode。出来的是核心配置能识别的沙箱值,不产生额外副作用。

调用关系:CodexToolCallParam::into_config 在处理用户传入的 sandbox 字段时会用到它,让 MCP 层的枚举和核心运行层的枚举顺利对接。

create_tool_for_codex_tool_call_param108–126 ↗
fn create_tool_for_codex_tool_call_param() -> Tool

作用:创建名为 codex 的 MCP 工具定义,告诉客户端:启动一段 Codex 会话时该传什么输入、会收到什么输出。

数据流:它先根据 CodexToolCallParam 自动生成一份 JSON Schema,再用 create_tool_input_schema 精简成 MCP 需要的输入说明,接着用 codex_tool_output_schema 加上固定的输出格式。最后产出一个 Tool 对象,里面包含工具名、标题、说明、输入格式和输出格式。

调用关系:这是服务器注册 codex 工具时会用的构造函数。测试 tests::verify_codex_tool_json_schema 会调用它,确认生成出来的工具说明没有意外变化。它内部把输入说明交给 create_tool_input_schema,把输出说明交给 codex_tool_output_schema。

调用图:调用 2 个内部函数(codex_tool_output_schema, create_tool_input_schema);被 1 处调用(verify_codex_tool_json_schema);外部调用 2 个(draft2019_09, new)。

codex_tool_output_schema128–141 ↗
fn codex_tool_output_schema() -> Arc<JsonObject>

作用:定义 Codex 工具调用成功后返回值的形状:必须有 threadId 和 content。threadId 用来以后继续同一段对话,content 是本次返回的文字内容。

数据流:函数内部写死一段 JSON:类型是对象,包含 threadId 和 content 两个字符串字段,并且两个都必填。然后把这个 JSON 对象包装成可共享的 Arc<JsonObject> 返回;Arc 可以理解成多人共用同一份只读说明书的安全指针。

调用关系:create_tool_for_codex_tool_call_param 和 create_tool_for_codex_tool_call_reply_param 都会调用它,因为“开始对话”和“继续对话”的返回格式相同。

调用图:被 2 处调用(create_tool_for_codex_tool_call_param, create_tool_for_codex_tool_call_reply_param);外部调用 3 个(new, json!, unreachable!)。

CodexToolCallParam::into_config146–190 ↗
async fn into_config(
        self,
        arg0_paths: Arg0DispatchPaths,
    ) -> std::io::Result<(String, Config)>

作用:把客户端传来的启动参数,变成 Codex 真正运行需要的 Config,同时保留最开始的用户 prompt。它是外部请求进入核心运行配置的关键翻译关口。

数据流:进去的是一个 CodexToolCallParam 和当前可执行文件路径信息 Arg0DispatchPaths。函数拆出 prompt、model、cwd、approval-policy、sandbox、额外 config 等字段;把 cwd 变成路径,把批准策略和沙箱模式转成核心类型,把 JSON 形式的覆盖配置转成 TOML 形式;再交给 ConfigBuilder 读取默认配置并套用这些覆盖项。出来的是二元组:原始 prompt 和构建好的 Config;如果配置读取或构建失败,就返回 io 错误。

调用关系:当 MCP 服务器收到 codex 工具调用、准备真正启动 Codex 会话时,会走到这个函数。它不直接运行模型,而是把“用户点的菜单”整理成 codex-core 可以执行的配置包。

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

CodexToolCallReplyParam::get_thread_id211–223 ↗
fn get_thread_id(&self) -> anyhow::Result<ThreadId>

作用:从继续对话的请求里取出会话编号。它优先使用新字段 threadId,也兼容旧字段 conversationId。

数据流:进去的是一个 CodexToolCallReplyParam。函数先看 thread_id 是否存在;有的话就用 ThreadId::from_string 检查并转换。没有 thread_id 时,再看 conversation_id;也没有的话,就返回错误,说明必须提供其中一个。出来的是有效的 ThreadId,或者一条清楚的失败原因。

调用关系:服务器处理 codex-reply 工具调用时,需要先知道用户要继续哪一段对话,这个函数就是取号和校验的入口。它把字符串解析工作交给 ThreadId::from_string。

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

create_tool_for_codex_tool_call_reply_param227–245 ↗
fn create_tool_for_codex_tool_call_reply_param() -> Tool

作用:创建名为 codex-reply 的 MCP 工具定义,告诉客户端:继续一段 Codex 对话时需要传会话编号和新的 prompt。

数据流:它根据 CodexToolCallReplyParam 生成 JSON Schema,再用 create_tool_input_schema 提取 MCP 需要的核心输入字段;然后复用 codex_tool_output_schema 设置统一的输出格式。最后返回一个 Tool 对象,名称是 codex-reply,标题是 Codex Reply。

调用关系:服务器注册“继续对话”工具时会用它。测试 tests::verify_codex_tool_reply_json_schema 会调用它,确认对外暴露的 schema 仍然符合预期。它和 create_tool_for_codex_tool_call_param 使用同一套输出 schema 生成函数。

调用图:调用 2 个内部函数(codex_tool_output_schema, create_tool_input_schema);被 1 处调用(verify_codex_tool_reply_json_schema);外部调用 2 个(draft2019_09, new)。

create_tool_input_schema247–276 ↗
fn create_tool_input_schema(
    schema: schemars::schema::RootSchema,
    panic_message: &str,
) -> Arc<JsonObject>

作用:把 schemars 生成的完整 JSON Schema,整理成 MCP Tool 输入所需的简洁版本。它像是把一份很厚的说明书裁剪成客户端真正要看的几页。

数据流:进去的是 RootSchema 和一段出错时使用的提示文字。函数先把 schema 序列化成 JSON 值,确认它是 JSON 对象;然后只保留 additionalProperties、properties、required、type、$defs、definitions 这些关键字段。出来的是 Arc<JsonObject>,供 Tool 作为 inputSchema 使用;如果 schema 不像对象,会直接 panic,也就是程序员错误而不是普通用户错误。

调用关系:create_tool_for_codex_tool_call_param 和 create_tool_for_codex_tool_call_reply_param 都依赖它来生成输入说明。它位于“自动生成 schema”和“注册 MCP 工具”之间,负责把格式调到 MCP 期望的样子。

调用图:被 2 处调用(create_tool_for_codex_tool_call_param, create_tool_for_codex_tool_call_reply_param);外部调用 4 个(new, new, panic!, to_value)。

tests::verify_codex_tool_json_schema295–376 ↗
fn verify_codex_tool_json_schema()

作用:测试 codex 工具对外公布的 JSON Schema 是否和预期完全一致。这样别人改字段、改枚举、改必填项时,测试会立刻提醒这是一次对外接口变化。

数据流:它调用 create_tool_for_codex_tool_call_param 得到 Tool,再把 Tool 转成 JSON。然后构造一份手写的 expected_tool_json,最后用 assert_eq 比较两者是否完全相同。测试通过表示 codex 工具的输入输出说明没有偏离预期。

调用关系:这是 create_tool_for_codex_tool_call_param 的“可执行文档”。它不参与线上运行,只在测试时检查 MCP 客户端会看到的 codex 工具说明。

调用图:调用 1 个内部函数(create_tool_for_codex_tool_call_param);外部调用 3 个(assert_eq!, json!, to_value)。

tests::codex_tool_call_param_rejects_removed_profile_field379–390 ↗
fn codex_tool_call_param_rejects_removed_profile_field()

作用:测试启动参数会拒绝已经移除的 profile 字段。这样旧配置或拼错字段不会被悄悄忽略,避免用户以为配置生效了其实没有。

数据流:它构造一个包含 prompt 和 profile 的 JSON,然后尝试解析成 CodexToolCallParam,并期待解析失败。接着检查错误信息里确实提到了 unknown field profile。出来的结果是测试通过或失败,不影响运行时数据。

调用关系:这个测试验证 CodexToolCallParam 上 deny_unknown_fields 的行为。它保护的是对外接口的严格性,防止工具调用参数变得含糊。

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

tests::verify_codex_tool_reply_json_schema393–437 ↗
fn verify_codex_tool_reply_json_schema()

作用:测试 codex-reply 工具对外公布的 JSON Schema 是否和预期完全一致。它保证继续对话接口的字段名、必填项和输出格式不会被无意改掉。

数据流:它调用 create_tool_for_codex_tool_call_reply_param 得到 Tool,把它转成 JSON,再和手写的 expected_tool_json 做完全相等比较。若两边一致,说明 threadId、conversationId、prompt 以及输出 schema 都符合预期。

调用关系:这是 create_tool_for_codex_tool_call_reply_param 的测试守门员。它只在测试阶段运行,用来审计 codex-reply 这个 MCP 工具的公开契约。

调用图:调用 1 个内部函数(create_tool_for_codex_tool_call_reply_param);外部调用 3 个(assert_eq!, json!, to_value)。