> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kazzle.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 工具

> 應用程式如何聲明工具及其處理程式如何返回結果。

# 工具

**工具**是 AI 可以呼叫的具名動作，具有型別化輸入 — 例如 `send_email(to, subject, body)`。工具在技能的 `tools.ts` 中聲明，對於應用程式提供的工具，由其中一個元件中的 HTTP 路由支援。本頁面是工具合約的真實來源；[技能](/apps/skills)涵蓋 `tools.ts` 的位置。

## 聲明工具

`tools.ts` 中的每個項目都有一個提供者安全的 `name`、一個友善的 `displayName`、一個 `description`、一個 Zod `input` 結構描述，以及一個 `target` 來指定 Kazzle 呼叫的內容。

```ts theme={"theme":"material-theme-darker"}
import { z } from 'zod';
import type { KazzleTool } from '@kazzle/app/tools';

export const SaveBookmarkInput = z.object({
  url: z.string().url().describe('URL to save.'),
  title: z.string().optional().describe('Optional human-readable title.'),
}).strict();

const saveBookmarkTarget = {
  type: 'app',
  component: 'api',
  path: '/tools/save-bookmark',
  method: 'POST',
  body: '${input}',
} as const;

export const tools = [
  {
    name: 'save_bookmark',
    displayName: 'Save bookmark',
    description: 'Save a bookmark URL and return a confirmation.',
    input: SaveBookmarkInput,
    target: saveBookmarkTarget,
  },
] as const satisfies readonly KazzleTool[];
```

## 目標

`target` 是工具或必要動作按鈕的可重複使用位址。直接 AI 工具呼叫和按鈕點擊可以使用相同的目標物件，因此兩者都執行相同的程式碼。

| 目標       | 欄位                                                       | 執行位置                                          |
| -------- | -------------------------------------------------------- | --------------------------------------------- |
| `app`    | `component`、`path`、`method`、選用的 `query`、`headers`、`body` | 您自己的其中一個元件。Kazzle 解析元件 URL 並新增已簽署的應用程式身分識別權杖。 |
| `url`    | `url`、`method`、選用的 `query`、`headers`、`body`              | 外部端點。URL 來源和方法是常值作者值。                         |
| `kazzle` | `name`                                                   | 內建用戶端 Kazzle 處理程式。                            |

HTTP 目標使用相同的請求欄位：

```ts theme={"theme":"material-theme-darker"}
type RequestSpec = {
  method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
  query?: Record<string, string>;
  headers?: Record<string, string>;
  body?: unknown;
};
```

沒有隱含的請求預設值。如果您的處理程式應該接收原始工具輸入作為 JSON，請寫入 `body: '${input}'`。

## 參考資料

Kazzle 在分派前解析 HTTP 目標 `query`、`headers`、`body` 和 `url` 欄位中的參考資料：

* `${input}` — 整個工具輸入，或整個按鈕輸入。
* `${input.path}` — 輸入中的巢狀值。
* `${env.NAME}` — 來自擁有應用程式元件的已聲明 `env.collection` + `env.environment` 的具名環境變數。

解析是單次通過。如果參考資料遺失或未知，工具會失敗，而不是替換為空值。當您需要常值 `${input.name}` 文字時，請使用 `$${input.name}`。

## 處理程式請求

對於 `app` 目標，在目標元件中新增相符的路由。使用 `body: '${input}'`，Kazzle 會將型別化輸入作為 JSON 本體傳送：

```json theme={"theme":"material-theme-darker"}
{ "...": "the typed input" }
```

`app` 目標也會接收：

| 標頭                    | 值                                                           |
| --------------------- | ----------------------------------------------------------- |
| `Authorization`       | `Bearer <identity token>` 識別使用者 + 安裝                        |
| `Kazzle-Tool-Context` | `{"source":"thread","threadId":"..."}` 或 `{"source":"api"}` |

應用程式目標無法透過 `headers` 設定 `Authorization`；Kazzle 擁有該標頭。當處理程式必須分支執行緒與工具 API 時，從 `@kazzle/app/tools` 讀取具有 `toolContext(req)` 的內容。

沒有相符路由的已聲明工具沒有用處 — 同時新增兩者。不支援 `tools.json`；如果應用程式編譯器找到一個，它會失敗。

## 處理程式回應

返回純文字或 JSON，最多三個通道：

```json theme={"theme":"material-theme-darker"}
{ "content": "...", "markdown": "...", "embedUrl": "https://..." }
```

* **`content`** — AI 讀取的純文字結果（提供給模型）。必要。
* **`markdown`** — 選用。在工具卡中呈現的簡短富文字摘要（react-markdown；原始 HTML 會被逸出）。適合一個句子、一個小清單、一個內嵌連結。裸露相對路徑呈現為無效預先格式化文字 — 連結必須是**絕對**。
* **`embedUrl`** — 選用。您的應用程式提供的**絕對** URL；卡片在沙箱 iframe 中呈現它。這是您顯示真實、全寬 UI 的方式 — 連線畫面、儀表板、圖表。您的應用程式裝載並擁有該頁面，因此它可以針對您自己的後端、Cookie 和 OAuth 完全互動。沒有任何內容寫入磁碟。

當兩者都設定時，`embedUrl` 優先於 `markdown`。始終保持有意義的 `content` — 那是 AI 讀取的內容。

```ts theme={"theme":"material-theme-darker"}
if (req.method === 'POST' && new URL(req.url).pathname === '/tools/save-bookmark') {
  const input = await req.json();
  // ...do the work...
  return Response.json({ content: `Saved ${input.title}` });
}
```

## 必要動作

應用程式工具可以在需要使用者（或裝置）動作時暫停執行緒。返回 `type: 'action_required'` 搭配卡片標題和按鈕。僅在 `toolContext(req).source === 'thread'` 時有效 — 透過工具 API（`source: 'api'`），改為返回正常網域錯誤。

```ts theme={"theme":"material-theme-darker"}
import { toolContext, isThreadToolInvocation } from '@kazzle/app/tools';

const context = toolContext(req);
if (!isThreadToolInvocation(context)) {
  return Response.json(
    { code: 'connect_required', error: 'Gmail must be connected before this tool can run.' },
    { status: 409 },
  );
}

return Response.json({
  type: 'action_required',
  title: 'Connect Gmail',
  description: 'Connect Gmail before Kazzle can search messages.',
  // Optional: only this device may run the gated buttons
  // assignee: { computerId: '<from computer { list: {} }>' },
  elements: [
    {
      type: 'button',
      id: 'connect',
      label: 'Connect Gmail',
      variant: 'primary',
      input: { scope: 'gmail.readonly' },
      target: {
        type: 'app',
        component: 'api',
        path: '/oauth/start',
        method: 'POST',
        body: '${input}',
      },
    },
  ],
});
```

Kazzle 在工具呼叫上儲存卡片，並在按下按鈕後透過 `/chat/resume` 繼續。相符的用戶端會看到作者按鈕；不相符的用戶端仍會看到略過/取消加上「在 {device} 上繼續」。沒有 `target` 的按鈕會將其 `input` 提交為工具結果。具有目標的按鈕會先執行該目標，然後使用目標結果作為工具結果。

### 豐富 UI — 嵌入應用程式裝載的頁面

當工具的結果是視覺或互動式的（連線畫面、圖表、摘要儀表板）時，從您的其中一個元件提供頁面，並將其**絕對** URL 返回為 `embedUrl`。從注入的元件 URL 建立 URL — 絕不是相對路徑。

```ts theme={"theme":"material-theme-darker"}
// A tool that needs the user to connect their account:
return Response.json({
  content: "Gmail isn't connected yet — the user needs to connect it.",
  embedUrl: `${process.env.KAZZLE_APP_COMPONENT_URL}/connect?token=${identity}`,
});
```

該頁面在跨來源 iframe（其自己的來源、Cookie 和指令碼）中執行。若要調整卡片大小，請將其高度發佈到父項 — 卡片會監聽此項並調整大小（上限為檢視區 70%）：

```ts theme={"theme":"material-theme-darker"}
// inside the embedded page
parent.postMessage({ __kazzle_height: document.body.scrollHeight }, '*');
```

優先使用 `embedUrl` 而不是發出 HTML 字串：您的應用程式已經提供頁面、UI 保持互動式並與您的程式碼版本化，並且沒有每次呼叫的成品寫入任何地方。
