# 工具

每个智能体运行环境都会运行一个工具模块。beeuni.yaml 列出 Beeuni 工具和项目自有工具，每个智能体的 tools 列表决定哪些工具可以调用。

Canonical page: https://beeuni.ai/docs/zh/project/tools

工具是模型在一轮对话中调用的函数。一个
[会话](/docs/zh/work/sessions)有三种工具，在两种
[运行框架](/docs/zh/work/harnesses)标准运行器和实验运行器上都一样：

| 类型 | 名称 | 实现方 |
| --- | --- | --- |
| 运行框架工具 | `bash`, `read`, `write`, `edit`, `glob`, `grep`, `task`, `question`, … | 运行框架。 |
| Beeuni 工具 | `web_search`, `image_search`, `scrape_webpage`, `memory`, `show` | Beeuni。`beeuni.yaml` 将每个工具列为 `<name>: beeuni:<name>`。没有 `tools:` 键的项目会获得全部五个工具。 |
| 项目工具 | 你声明的任意名称 | 你的项目：一个在 `beeuni.yaml` 中 `tools:` 下声明的模块。 |

Beeuni 工具和项目工具与运行框架无关。沙盒守护进程会为每个工具加载一个模块，并让任一运行框架调用它，名称、参数和输出都相同。实验运行器 在守护进程的进程中调用模块。标准运行器 通过守护进程编写的插件调用模块，该插件会将每次调用转发给守护进程。模块不会导入运行框架包。

<a id="beeuni-tools-in-beeuniyaml"></a>

## beeuni.yaml 中的 Beeuni 工具

新项目会在 `tools:` 下列出五个 Beeuni 工具：

```yaml
tools:
  web_search: beeuni:web_search      # maintained by Beeuni, updated automatically
  image_search: beeuni:image_search
  scrape_webpage: beeuni:scrape_webpage
  memory: beeuni:memory
  show: beeuni:show
  # lookup_order: tools/lookup_order.ts   # your own tool: a module in your repo
```

`beeuni:<name>` 表示由 Beeuni 维护的工具。修复该工具后，所有列出它的项目都会获得修复，项目本身无需更改。

<a id="which-beeuni-tools-a-session-gets"></a>

### 会话会获得哪些 Beeuni 工具

| `beeuni.yaml` | 会话获得的工具 |
| --- | --- |
| 根文件或任何[导入的](/docs/zh/project/manifest#imports)文件中都没有 `tools:` 键 | 全部五个 Beeuni 工具。此列表出现前创建的项目仍会获得全部工具。 |
| 有 `tools:` 键，即使它为空（`tools: {}`，或删掉所有行后的 `tools:`） | 仅列出的 Beeuni 工具，可列为 `beeuni:<name>`，也可列为替代该工具的模块。 |

两种情况下都会加载项目自有工具。

<a id="see-what-a-session-gets"></a>

### 查看会话会获得哪些工具

`beeuni tools ls` 会读取 `beeuni.yaml` 及其导入文件，打印会话会获得的每个工具，然后列出项目移除的 Beeuni 工具：

```text
$ beeuni tools ls
NAME            KIND                 SOURCE
web_search      beeuni (overridden)  tools/web_search.ts
scrape_webpage  beeuni               beeuni:scrape_webpage
memory          beeuni               beeuni:memory
show            beeuni               beeuni:show
lookup_order    project              tools/lookup_order.ts

Removed Beeuni tools: image_search
Add `<name>: beeuni:<name>` under `tools:` to get one back.
```

| 类型 | 含义 |
| --- | --- |
| `beeuni` | Beeuni 工具，列为 `beeuni:<name>`（或未设置 `tools:` 键时列出全部工具）。 |
| `beeuni (overridden)` | 带模块路径的 Beeuni 工具名称：改为运行项目模块。 |
| `project` | 项目自有工具。 |

`beeuni tools ls --json` 会打印 `{ manifest, tools_key, tools: [{ name, kind, source }], removed }`。

<a id="remove-a-beeuni-tool"></a>

### 移除 Beeuni 工具

删除 `tools:` 下的对应行。变更请求合并后，项目中的每个智能体都会在两种运行框架的所有新会话中失去该工具。若只想让一个智能体无法使用某工具，请改用[`agents.<name>.tools`](#who-may-use-which-tool)。

移除了工具的项目会话中：

- **标准运行器** 不会向模型提供该工具。
- **实验运行器** 不会向模型提供该工具。调用该工具会失败，并返回 `Tool show not found`（针对 `show`）。
- **守护进程**会以 `404` 和 `no tool named <name> is loaded` 响应 `POST /beeuni/tools/<name>`。
- **仍会加载提及该工具的技能。**例如，`beeuni-memory` 提及了 `memory`。模型读取技能后发现没有此工具，便会使用其他工具：没有 `memory` 时，它会用文件工具读取和编辑 `memory/` 下的文件。

<a id="change-a-beeuni-tool-eject"></a>

### 更改 Beeuni 工具：复制并独立维护

`beeuni tools eject <name>` 会为项目创建一份 Beeuni 工具的副本：

```text
$ beeuni tools eject web_search
  ✓  Ejected web_search
  wrote   tools/web_search.ts (the Beeuni source, now the project's to change)
  set     beeuni.yaml: tools.web_search: tools/web_search.ts

Next:
  1. Edit tools/web_search.ts. Keep the default export: { description, parameters, execute(args, context) }.
  2. beeuni validate
  3. git add tools/web_search.ts beeuni.yaml && git commit -m "Own the web_search tool"
  4. Open a change request: git push origin HEAD && beeuni cr open --title "Own the web_search tool"
Sessions load the copy after the change request merges.
```

- 它会将 Beeuni 源码（与沙盒运行的字节完全相同）写入 `tools/<name>.ts`。该文件只导入 `node:*` 模块，并从 `context.env` 读取 `BEEUNI_API_URL`、`BEEUNI_TOKEN` 和提供商密钥，因此可以直接运行。
- 它会在声明该入口的文件（根文件或导入文件）中将 `beeuni.yaml` 的 `<name>: tools/<name>.ts` 设为对应值。其他所有行和注释都会保留。修改后的行以 `# ejected from beeuni:<name>` 结尾。
- 如果没有 `tools:` 键，它会创建该键，并将其他四个 Beeuni 工具列为 `beeuni:<name>`，因此复制操作不会移除任何工具。
- 如果 `tools/<name>.ts` 已存在，或该工具已指向其他模块，它会拒绝操作。`--force` 会覆盖。
- 如果名称不是 Beeuni 工具，它会拒绝操作。

副本归你所有。Beeuni 不会再更新它。若要恢复使用受维护的工具，请将对应行改回 `<name>: beeuni:<name>` 并删除文件。

<a id="override-without-eject"></a>

### 不复制，直接覆盖

为 Beeuni 工具名称指定模块路径，即可在所有运行框架上替换 Beeuni 工具：

```yaml
tools:
  web_search: integrations/search/web_search.ts
```

<a id="the-web-ui-views"></a>

### Web UI 视图

Web UI 会根据工具名称为每个 Beeuni 工具选择对应视图。`show` 和 `memory` 视图会读取调用参数（`type`、`path`、`url`、`content`、`items`；`command`、`path`）。`web_search`、`image_search` 和 `scrape_webpage` 视图会读取输出 JSON。修改后的副本若保留原名称，就必须保留这些参数和输出结构，否则 UI 会显示原始输出。

<a id="validation"></a>

### 验证

`beeuni validate` 会检查此列表：

- `beeuni:<name>` 中的 `<name>` 必须是 Beeuni 工具，且键名必须与之相同。`web_search: beeuni:image_search` 属于错误：
  ``must be `beeuni:web_search` (the Beeuni tool) or a repo-relative path to a .ts or .js module (e.g. `tools/web_search.ts`) that replaces it.``
- `tools:` 键中未列出任何 Beeuni 工具时会发出警告：
  ``Sessions of this project get no Beeuni tool (web_search, image_search, scrape_webpage, memory, show). Add `<name>: beeuni:<name>` to keep one.``
  这能发现项目添加第一个自有工具时意外移除了原有五个工具的情况。
- 智能体的 `tools` 中提及了项目未加载的 Beeuni 工具时会发出警告：``"show" is a Beeuni tool this project does not load: add `show: beeuni:show` under the top-level `tools`, or remove it here.``

[清单参考](/docs/zh/project/manifest#tools)列出了所有规则。

<a id="write-a-project-tool"></a>

## 编写项目工具

```ts
// tools/lookup_order.ts
export default {
  description: 'Look up an order by id. Returns its status and carrier.',
  parameters: {
    type: 'object',
    properties: {
      id: { type: 'string', description: 'The order id, e.g. "A-1042"' },
      include_items: { type: 'boolean', description: 'Also list the items' },
    },
    required: ['id'],
  },
  async execute(args, context) {
    const response = await fetch(`https://orders.example.com/v1/orders/${args.id}`, {
      headers: { authorization: `Bearer ${context.env.ORDERS_API_KEY}` },
      signal: context.signal,
    })
    if (!response.ok) throw new Error(`orders API answered ${response.status}`)
    return await response.json()
  },
}
```

```yaml
# beeuni.yaml
tools:
  web_search: beeuni:web_search
  # … the other Beeuni tools you keep
  lookup_order: tools/lookup_order.ts
```

默认导出是一个普通对象：

| 字段 | 规则 |
| --- | --- |
| `description` | 非空字符串。模型会读取它，以判断何时调用该工具。 |
| `parameters` | 参数的 [JSON Schema](https://json-schema.org)，`type: 'object'`。模型会原样接收它，因此请在 `required` 中列出必需参数。Beeuni 不会根据此 Schema 验证参数。 |
| `execute(args, context)` | 返回字符串或任意 JSON 值（以缩进 JSON 形式发送）。可以是 `async`。抛出的错误就是工具错误，模型会读取其消息。 |

`context` 会提供：

| 字段 | 内容 |
| --- | --- |
| `sessionId` | 此调用所属的 Beeuni 会话。 |
| `agent` | 发起调用的智能体。 |
| `directory` | 项目检出目录，`/workspace`。相对路径需以该目录为基准解析。 |
| `env` | Box 环境以及该智能体当前可用的[密钥](/docs/zh/project/secrets)。以 `context.env.NAME` 形式读取密钥：Box 启动后更改的密钥位于 `context.env` 中，而不在 `process.env` 中。 |
| `signal` | 一个 `AbortSignal`，当用户停止该轮对话时会中止。将其传给 `fetch`。 |

- **输出大小。**输出超过 50 KB 或 2,000 行时，会完整保存到沙盒临时目录中的文件。模型会收到输出开头、大小和文件路径。请只返回模型所需内容，不要返回原始转储。
- **错误。**抛出错误会结束调用，模型会读取错误消息。请抛出说明需要更改什么的消息。
- **导入。**模块可以导入 `node:*` 模块、Bun API 和相对路径文件。包会从模块旁边或其上级目录中的 `node_modules` 解析。沙盒不会为工具安装任何包，[配置发布](/docs/zh/work/runtime#config-convergence)只包含 Git 跟踪的文件。HTTP API 请使用 `fetch`。TypeScript 无需构建步骤即可运行。
- **加载失败。**无法加载的模块（文件缺失、没有默认导出、`parameters` 不是对象 Schema、导入时出错、包无法解析）会被跳过，其他工具仍会加载。守护进程会按工具记录 `[tools] project tools that did not load` 及失败原因。

<a id="parameters-with-zod"></a>

### 使用 zod 的 `parameters`

zod 4 会生成相同的 JSON Schema：

```ts
import { z } from 'zod'

export default {
  description: 'Look up an order by id.',
  parameters: z.toJSONSchema(z.object({ id: z.string().describe('The order id') })),
  async execute(args) {
    return `order ${args.id}: shipped`
  },
}
```

只有在会话运行的检出目录中，`node_modules` 内能解析到 `zod` 时，此模块才会加载。否则会因 `Cannot find package 'zod'` 而失败。
若要用 zod 编写 Schema 并发布普通 JSON Schema，请先打印一次结果，再将输出粘贴到 `parameters`：

```bash
bun -e "import { z } from 'zod'; console.log(JSON.stringify(z.toJSONSchema(z.object({ id: z.string() })), null, 2))"
```

<a id="example-call-an-http-api-with-a-project-secret"></a>

### 示例：使用项目密钥调用 HTTP API

将密钥存储为项目密钥。该命令从标准输入读取值：

```bash
beeuni secrets set WEATHER_API_KEY=-
```

将密钥授予会调用该工具的智能体。入门模板中的 `beeuni` 智能体拥有 `secrets: all`。受限范围的智能体需要将它列出：`secrets: [WEATHER_API_KEY]`。

编写模块：

```ts
// tools/weather_now.ts
export default {
  description: 'Current weather for a city: temperature in °C and conditions.',
  parameters: {
    type: 'object',
    properties: { city: { type: 'string', description: 'City name, e.g. "Berlin"' } },
    required: ['city'],
  },
  async execute(args, context) {
    const key = context.env.WEATHER_API_KEY
    if (!key) throw new Error('WEATHER_API_KEY is not set: run `beeuni secrets set WEATHER_API_KEY=-` and grant it to this agent.')
    const url = `https://api.weather.example.com/v1/current?city=${encodeURIComponent(args.city)}`
    const response = await fetch(url, { headers: { authorization: `Bearer ${key}` }, signal: context.signal })
    if (!response.ok) throw new Error(`weather API answered ${response.status}: ${await response.text()}`)
    const data = await response.json()
    return { city: args.city, temperature_c: data.temp_c, conditions: data.conditions }
  },
}
```

声明模块：

```yaml
tools:
  # … the Beeuni tools you keep
  weather_now: tools/weather_now.ts
```

<a id="example-override-websearch"></a>

### 示例：覆盖 `web_search`

`beeuni tools eject web_search` 会写入 `tools/web_search.ts` 并设置 `web_search: tools/web_search.ts`。然后修改副本。例如，若只保留来自某个站点的结果，请在发送请求前，将该站点添加到 `execute` 中的每个查询：

```ts
// tools/web_search.ts, inside execute()
const queries = String(args.query ?? '').split('|||').map((query) => query.trim()).filter(Boolean)
  .map((query) => `site:docs.example.com ${query}`)
```

请保留名称、参数和输出 JSON，这样提及 `web_search` 的技能以及 Web UI 视图仍能正常工作。

<a id="test-a-tool"></a>

### 测试工具

`beeuni validate` 会检查名称、路径、文件缺失情况以及 `beeuni:` 值。`beeuni tools ls` 会显示会话会获得哪些工具。

然后提交、推送并创建变更请求。合并后，启动会话并使用真实参数请求一次工具调用：

```bash
beeuni sessions new --wait --prompt "Call the weather_now tool once with city Berlin and print its output."
```

未能加载的工具不会提供给模型。抛出错误的工具会返回错误消息。`beeuni sessions log <id>` 会打印会话消息，其中包含每次工具调用及其状态。

<a id="declare-tools"></a>

## 声明工具

`tools:` 将每个工具名称映射到其模块；Beeuni 工具名称也可以映射到 `beeuni:<name>`。模块的文件夹位置由你决定。

```yaml
tools:
  web_search: beeuni:web_search
  memory: beeuni:memory
  lookup_order: tools/lookup_order.ts
  crm_note: integrations/crm/note.ts
```

- 名称使用 snake_case，最多 64 个字符：`^[a-z][a-z0-9_]{0,63}$`。
- 运行框架工具名称会导致错误。Beeuni 工具名称可以使用 `beeuni:<name>` 或模块路径；该模块会在所有运行框架上替代 Beeuni 工具。
- 路径必须相对于仓库，且以 `.ts`、`.js`、`.mts`、`.mjs`、`.cts` 或 `.cjs` 结尾。
- [`imports:`](/docs/zh/project/manifest#imports) 中列出的文件可以声明工具。在两个文件中声明同一名称会导致错误。导入文件中的 `tools:` 键会视为 `tools:` 键，用于选择规则。

[清单参考](/docs/zh/project/manifest#tools)列出了完整规则。

<a id="who-may-use-which-tool"></a>

## 谁可以使用哪些工具

`agents.<name>.tools` 决定智能体的模型可以看到哪些工具。它对运行框架工具、Beeuni 工具和项目工具一视同仁。

```yaml
agents:
  beeuni:
    tools: all                                    # every tool (the default)
  researcher:
    tools: [read, grep, web_search, lookup_order] # only these
  support:
    tools:
      exclude: [bash, write, edit]                # every tool but these
  greeter:
    tools: none                                    # no tool
```

| 值 | 智能体可获得的工具 |
| --- | --- |
| 省略，或 `all` | 所有工具。 |
| `none` | 没有工具。 |
| 列表 | 仅列出的工具。包含 `*` 的列表无效：`all`。 |
| `{ exclude: [...] }` | 除列出的工具外的所有工具。 |
| `{ bash: false, … }` | 旧版映射形式：`false` 会移除一个工具。 |

- `tools` 决定工具是否可见。智能体的 `permission` 规则仍会决定是否允许、询问或拒绝调用。不在列表中的工具对模型隐藏，若被调用也会遭拒。
- 智能体列表无法添加项目未加载的 Beeuni 工具。请先将其列在顶层 `tools:` 下。
- 使用 `task` 工具启动的子智能体：在实验运行器上，它只能获得自身列表和会话智能体列表都允许的工具。在标准运行器上，它会获得自身列表中的工具：标准运行器 只将智能体规则应用于该智能体。若要阻止智能体委派任务，请勿在其列表中加入 `task`。
- [连接器](/docs/zh/connect/connectors)工具遵循 `agents.<name>.connectors`，而非此列表。
- `beeuni validate` 会对不属于运行框架、Beeuni 或项目工具的名称发出警告，也会对项目未加载的 Beeuni 工具发出警告。`exclude` 列表中的拼写错误不会关闭实际工具。

在标准运行器上，`bash` 还涵盖终端插件的 `pty_*` 工具，并且 `write`、`edit` 和 `apply_patch` 共用一个权限：列出其中一个会显示三个，排除其中一个会隐藏三个。实验运行器 的 `write` 和 `edit` 工具则各自独立。

<a id="when-a-change-takes-effect"></a>

## 更改何时生效

会话会在运行时启动时加载工具。变更请求合并后，更改即会生效：新会话会加载更改；使用[配置发布](/docs/zh/work/runtime#config-convergence)时，正在运行的会话会在下一次收敛时加载更改。在实验运行器上，如果没有正在运行的轮次，对项目工具或 Beeuni 工具列表的更改会原地重启运行时；对话会继续。

<a id="tools-in-harnessesopencodetools"></a>

## `harnesses/opencode/tools/` 中的工具

标准运行器 仍会从其配置目录加载自定义工具，实验运行器 则不会。托管工具推出前创建的项目会在该目录中携带 Beeuni 工具副本（`web_search.ts`、`memory.ts` 等）。

- 在标准运行器上，该文件夹中的工具会覆盖同名托管工具，因此项目会一直使用自己的副本，直到将其删除。请删除 Beeuni 工具的副本：守护进程会在两种运行框架上托管 `beeuni.yaml` 列出的工具。
- 若要让标准运行器工具在两种运行框架上运行，请将其移出该文件夹，将 `tool({ description, args, execute })` 替换为普通对象（其 `parameters` 为 JSON Schema），从 `context.env` 读取密钥，并在 `tools:` 下声明该工具。
