> ## 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 ルートでサポートされます。このページはツール契約の信頼できる情報源です。`tools.ts` がどこにあるかについては、[スキル](/apps/skills)を参照してください。

## ツールの宣言

`tools.ts` の各エントリには、プロバイダー安全な `name`、フレンドリーな `displayName`、`description`、Zod の `input` スキーマ、および Kazzle が呼び出すものを指定する `target` があります。

```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` | 自分のコンポーネントの 1 つ。Kazzle はコンポーネント URL を解決し、署名されたアプリ ID トークンを追加します。 |
| `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` はサポートされていません。アプリコンパイラーが見つけた場合は失敗します。

## ハンドラーレスポンス

プレーンテキスト、または最大 3 つのチャネルを持つ JSON を返します：

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

* **`content`** — AI が読むプレーン結果（モデルに供給されます）。必須。
* **`markdown`** — オプション。ツールカードにレンダリングされる短いリッチテキスト概要（react-markdown；生の HTML はエスケープされます）。文、小さなリスト、インラインリンクに適しています。ベアの相対パスはデッドプリフォーマットテキストとしてレンダリングされます。リンクは**絶対**である必要があります。
* **`embedUrl`** — オプション。アプリが提供する**絶対** URL。カードはそれをサンドボックス化された iframe でレンダリングします。これは実際のフルウィッドス UI（接続画面、ダッシュボード、チャート）を表示する方法です。アプリはページをホストして所有するため、独自のバックエンド、クッキー、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` を通じて再開します。一致するクライアントは作成者ボタンを表示します。一致しないクライアントは、スキップ/キャンセルと「{デバイス}で続行」を表示します。`target` のないボタンはその `input` をツール結果として送信します。ターゲット付きボタンはそのターゲットを最初に実行し、ターゲット結果をツール結果として使用します。

### リッチ UI — アプリホストページを埋め込む

ツールの結果がビジュアルまたはインタラクティブ（接続画面、チャート、概要ダッシュボード）の場合、コンポーネントの 1 つからページを提供し、その**絶対** URL を `embedUrl` として返します。注入されたコンポーネント URL から URL を構築します。相対パスは使用しないでください。

```ts theme={"theme":"material-theme-darker"}
// ユーザーがアカウントを接続する必要があるツール：
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（独自のオリジン、クッキー、スクリプト）で実行されます。カードのサイズを変更するには、その高さを親に投稿します。カードはこれをリッスンしてサイズを変更します（ビューポートの 70% でキャップされます）：

```ts theme={"theme":"material-theme-darker"}
// 埋め込まれたページ内
parent.postMessage({ __kazzle_height: document.body.scrollHeight }, '*');
```

HTML 文字列を出力する代わりに `embedUrl` を優先します。アプリは既にページを提供しており、UI はインタラクティブでコードとバージョン管理されたままであり、呼び出しごとのアーティファクトはどこにも書き込まれません。
