# 其它内置工具 · 计划、提问、权限与搜索

> 除了执行命令、改文件和 MCP，Codex 还有十来个内置工具：update_plan、request_user_input、request_permissions、时钟与上下文预算工具、插件安装建议、view_image、网页搜索、图片生成与 tool_search。它们大多由特性开关或模型目录按需打开，参数与返回格式都写在各自的 spec 文件里；按执行位置可分为纯内核、要等前端回应、服务端托管与扩展贡献四类。

- 作者：David（道雾轩）
- 专栏：Codex 源码解读（https://daiw.net/manual/codex-source.md）
- 最后更新：2026-09-29
- 原文：https://daiw.net/manual/codex-source/builtin-tools
- 转载与引用：请注明出处并附原文链接（https://daiw.net/about/copyright）

# 其它内置工具 · 计划、提问、权限与搜索

前面几篇讲完了“重”的工具。剩下的内置工具体量都不大，却覆盖了 agent 的几种基本需要：跟踪进度、向人提问、申请权限、感知时间与上下文余量、找工具、看图、上网。它们大多在 `add_core_utility_tools`（`codex-rs/core/src/tools/spec_plan.rs:1148`）里登记，`tool_search` 与托管的 `web_search` 在收尾阶段加入，另有几个来自扩展；下面逐个看出现条件、参数与返回。多 agent 协作工具留给[多 agent](https://daiw.net/manual/codex-source/multi-agent)。用户这一侧能碰到的多半只是开关：`web_search` 的几种取值见手册[配置项速查](https://daiw.net/manual/codex/config-reference)，`request_user_input` 所在的 Plan 模式用 `/plan` 或 `Shift+Tab` 切换（见[斜杠命令](https://daiw.net/manual/codex/slash-commands)）。

## 一张总表

| 工具 | 出现条件（0.158.0） | 参数 | 模型收到的结果 |
| --- | --- | --- | --- |
| `update_plan` | `tools.update_plan.enabled`，默认关 | `plan`（`step` + `status`），可选 `explanation` | `Plan updated` |
| `request_user_input` | 默认开，但只在 Plan 模式可用 | `questions`：1 到 3 个问题 | 答案 JSON |
| `request_permissions` | 特性 `request_permissions_tool`，开发中 | `permissions`、`reason`、`environment_id` | 批准的权限 JSON |
| `clock.curr_time` / `clock.sleep` | 模型目录声明 `clock` 或开启时间提醒 | 无 / `duration_ms` | `It is ... UTC.` / 实际等待时长 |
| `get_context_remaining`、`new_context` | 特性 `token_budget`，开发中 | 无 | 剩余 token 数 / 新窗口提示 |
| `list_available_plugins_to_install`、`request_plugin_install` | `tool_suggest` 且有可推荐的插件或连接器 | 插件或连接器 ID、推荐理由 | 安装结果 JSON |
| `view_image` | 特性 `view_image`，默认开，需有执行环境 | `path`，可选 `detail` | 图片内容项 |
| `web_search`（托管） | 提供方支持且 `web_search` 不是 `disabled` | 由服务端定义 | 由服务端执行 |
| `web.run` | 特性 `standalone_web_search` 等，开发中 | `search_query`、`open`、`find` 等命令 | 搜索与网页结果 |
| `image_gen.imagegen` | 特性 `image_generation` 且账号、提供方、模型都满足 | 提示词等 | 生成的图片 |
| `tool_search` | 模型支持工具搜索且有延迟工具 | `query`，可选 `limit`（默认 8） | 可加载的工具定义 |

```mermaid
flowchart LR
  subgraph K[纯内核 当场返回]
    K1[update_plan]
    K2[clock.curr_time]
    K3[get_context_remaining · new_context]
    K4[view_image]
    K5[tool_search]
  end
  subgraph U[要等前端回应]
    U1[request_user_input]
    U2[request_permissions]
    U3[request_plugin_install]
  end
  subgraph H[服务端托管]
    H1[web_search]
  end
  subgraph E[扩展贡献]
    E1[web.run]
    E2[image_gen.imagegen]
  end
```

## update_plan：只发事件的计划工具

计划工具的实现几乎只有一件事：把参数原样变成一个 `PlanUpdate` 事件，交给界面渲染成清单。模型拿到的永远是一句 `Plan updated`。它和 [Plan 模式](https://daiw.net/manual/codex-source/tasks-and-plan-mode)是两回事，在 Plan 模式里调用反而会被拒绝：

```rust
        if turn.mode() == ModeKind::Plan {
            return Err(FunctionCallError::RespondToModel(
                "update_plan is a TODO/checklist tool and is not allowed in Plan mode".to_string(),
            ));
        }

        let args = parse_update_plan_arguments(&arguments)?;
        session
            .send_event(turn.as_ref(), EventMsg::PlanUpdate(args))
            .await;

        Ok(boxed_tool_output(PlanToolOutput))
```

（`codex-rs/core/src/tools/handlers/plan.rs:87`）

每个计划项的 `status` 只能是 `pending`、`in_progress`、`completed`，工具描述要求同一时间最多一项 `in_progress`。`tools.update_plan.enabled` 不写就是关，这是 `resolve_update_plan_enabled` 用 `is_some_and` 定下的默认值。

## 向人要东西：提问、权限与安装

这三个工具的共同点是处理器发出请求后挂起，等前端（终端界面或其它 app-server 客户端）回应，再把回应序列化成 JSON 交还模型。

`request_user_input` 的 `questions` 里每个问题要有 `id`、`header`（不超过 12 个字符）、`question` 与 2 到 3 个 `options`，推荐项放第一个并在标签后加 “(Recommended)”；“Other” 选项由客户端自动补上，模型不用写。处理器的检查依次是：

```rust
        if turn.session_source.is_non_root_agent() {
            return Err(FunctionCallError::RespondToModel(
                "request_user_input can only be used by the root thread".to_string(),
            ));
        }

        let mode = turn.collaboration_mode().mode;
        if let Some(message) = request_user_input_unavailable_message(mode, &self.available_modes) {
            return Err(FunctionCallError::RespondToModel(message));
        }

        let args: RequestUserInputToolArgs = parse_arguments(&arguments)?;
        let args = normalize_request_user_input_tool_args(args)
            .map_err(FunctionCallError::RespondToModel)?;
        let args = RequestUserInputArgs {
            questions: args.questions,
            is_blocking: mode == ModeKind::Plan,
            auto_resolution_ms: None,
        };
```

（`codex-rs/core/src/tools/handlers/request_user_input.rs:67`）

子 agent 不能直接问人；可用的协作模式只有 Plan（`ModeKind::allows_request_user_input` 只认 Plan，默认模式要等开发中的 `default_mode_request_user_input`）；缺选项的问题直接退回。返回的是 `{"answers": {"<问题 id>": {"answers": [...]}}}` 形式的 JSON。`guardian_approval` 特性（默认开）打开时，用户的回答还会作为“已核实的答复”（`VerifiedAnswer`）留存进上下文，供[自动审查](https://daiw.net/manual/codex-source/guardian)参考。模型目录还可以声明一个异步版本 `request_user_input_async`，两者都登记为 `DirectModelOnly`，不进 [Code mode](https://daiw.net/manual/codex-source/code-mode)。

`request_permissions` 让模型在沙箱之内申请额外的读写路径或联网权限，参数里的相对路径按所选环境的工作目录解析，一项都没有就报错。回应里带着用户实际批准的子集和范围（`turn` 或 `session`），工具描述写明批准的权限会自动作用于本轮（或整个会话）后续的 shell 类命令。审批的细节见[审批流程](https://daiw.net/manual/codex-source/approvals-flow)。

插件安装建议由 `tool_suggest`（默认开，还要求 `apps` 与 `plugins`）控制，而且只有在 `built_tools` 找到了可推荐的候选时才登记。有两种呈现：一种是先调 `list_available_plugins_to_install` 列候选、再调 `request_plugin_install` 发请求；另一种是候选已经以 `<recommended_plugins>` 列表的形式放进上下文，只剩后一个工具。工具描述反复叮嘱，只在用户明确点名要用某个插件或连接器、手头又没有能用的工具时才调用，`request_plugin_install` 还要求不要与其它工具并行调用。请求以一次 elicitation 呈现给用户，结果 JSON 里有 `completed`、`user_confirmed` 等字段；目前在 `codex-tui` 里请求安装插件（连接器不受影响）会直接回 `plugin install requests are not available in codex-tui yet`。

## 时间与上下文余量

这两组工具都要靠模型目录或开发中的特性打开。时钟工具放在 `clock` 命名空间下：`curr_time` 返回 `It is 2026-09-29 08:00:00 UTC.` 这样一句话，读时钟失败默认是 `Fatal`（开启 `nonfatal_clock_read_errors` 后降为普通错误）；`sleep` 最长 12 小时，有新输入进来会提前结束。上下文预算工具由 `token_budget` 打开：

```rust
    if features.enabled(Feature::TokenBudget) {
        registry.add_with_exposure(NewContextWindowHandler, ToolExposure::DirectModelOnly);
        registry.add(GetContextRemainingHandler);
    }

    let current_time_reminder_enabled = features.enabled(Feature::CurrentTimeReminder);
    let model_has_clock = context
        .model_info
        .experimental_supported_tools
        .iter()
        .any(|tool| tool == "clock");
    if current_time_reminder_enabled || model_has_clock {
        registry.add(CurrentTimeHandler);
    }
```

（`codex-rs/core/src/tools/spec_plan.rs:1220`）

`get_context_remaining` 回一句 `You have N tokens left in this context window.`；处理器文件叫 `new_context_window.rs`，但工具名是 `new_context`，它只是在会话状态里记一个请求，回复模型“新窗口会在不做摘要的情况下开始”，真正的切换由主循环在下一步处理（见[上下文压缩](https://daiw.net/manual/codex-source/compaction)）。

## 看图与上网

`view_image` 先检查模型支不支持图片输入，不支持就直接退回；然后从执行环境的文件系统读文件、解码校验，作为一个 `InputImage` 内容项（data URL）返回。模型支持原图细节时多一个 `detail` 参数，`high` 是默认的缩放版，`original` 保留原始分辨率。

网页搜索有两套。托管的 `web_search` 是 Responses API 的内置工具，由 OpenAI 服务端执行，Codex 只负责在请求里声明它，`create_web_search_tool` 把配置里的模式翻译成两个开关：

```rust
    let (external_web_access, indexed_web_access) = match options.web_search_mode {
        Some(WebSearchMode::Cached) => (false, None),
        Some(WebSearchMode::Indexed) => (true, Some(true)),
        Some(WebSearchMode::Live) => (true, None),
        Some(WebSearchMode::Disabled) | None => return None,
    };
```

（`codex-rs/core/src/tools/hosted_spec.rs:15`）

`cached` 不允许实时抓取，`indexed` 只抓已被索引的页面，`live` 完全放开；模型目录的 `web_search_tool_type` 为 `TextAndImage` 时还会声明可以返回图片结果。另一套是扩展 `codex-rs/ext/web-search` 提供的 `web.run`，由 Codex 在客户端调用搜索接口，一次调用可以组合 `search_query`、`image_query`、`open`、`click`、`find`、`screenshot` 以及天气、金融、体育等命令；它在 Responses Lite 模型上，或开启了开发中的 `standalone_web_search` 时启用，一旦启用，托管的 `web_search` 就不再声明。图片生成 `image_gen.imagegen` 同样来自扩展（`codex-rs/ext/image-generation`），要求特性开启、非免费账号、提供方支持、模型接受图片输入，并且走 OpenAI 认证。

## tool_search：把延迟工具取回来

`tool_search` 的 spec 类型是 Responses API 的 `tool_search`，`execution` 为 `client`，由 Codex 自己执行。参数只有 `query` 与 `limit`（默认 8，传 0 报错）。处理器用 BM25 在所有延迟工具的检索文本上打分。检索文本默认由工具名（包括把下划线换成空格的写法）、描述和参数 schema 里的字段名与描述拼成，MCP 工具另有一套，还会加上服务器名、工具标题、连接器名与插件名；命中的工具按命名空间合并后返回。返回的定义被专门改过：

```rust
    for tool in &mut namespace.tools {
        match tool {
            ResponsesApiNamespaceTool::Function(tool) => {
                tool.defer_loading = Some(true);
                tool.output_schema = None;
                tool.parameters.mcp_input_schema_max_bytes = None;
            }
            ResponsesApiNamespaceTool::Custom(tool) => {
                tool.defer_loading = Some(true);
            }
        }
    }
```

（`codex-rs/tools/src/tool_search.rs:97`）

每个工具都打上 `defer_loading`，去掉输出 schema，作为 `tool_search_output` 条目写回历史，下一次采样时模型就能直接调用它们。处理器对象按注册表内容缓存（`ToolSearchHandlerCache`），MCP 工具的定义不变时 BM25 索引也不必重建。它的描述里列出了所有来源（MCP 服务器名或连接器名及其说明，总长不超过 512 KiB），开启开发中的 `deferred_tool_world_state` 后，这份清单改由上下文注入。

## 和《从 LLM 到 Coding Agent》对照

[MCP 与工具懒加载](https://daiw.net/manual/llm-to-agent/mcp)里的 `search_tools` 元工具，在 Codex 里就是 `tool_search`，而且把“加载完整 schema”做成了 Responses API 的原生条目。[权限系统](https://daiw.net/manual/llm-to-agent/permissions)讲的是 agent 被动地等审批，`request_permissions` 则让模型主动申请，把“我需要更多权限”变成一次正式的工具调用。`update_plan` 与 `request_user_input` 都属于[工具抽象](https://daiw.net/manual/llm-to-agent/tool-abstraction)里说的“给人看”的那一半：前者只产生界面事件，后者把人拉进回路。

---

上一篇：[MCP 工具调用 · 连接管理与工具目录](https://daiw.net/manual/codex-source/mcp-tool-calls) · 下一篇：[Code mode · 让模型写 JavaScript 编排工具](https://daiw.net/manual/codex-source/code-mode)
