> ## 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.

# Tools

> Bagaimana aplikasi mendeklarasikan tools dan bagaimana handler mereka mengembalikan hasil.

# Tools

Sebuah **tool** adalah tindakan bernama yang dapat dipanggil AI, dengan input yang diketik — misalnya `send_email(to, subject, body)`. Tools dideklarasikan dalam `tools.ts` skill, dan untuk tools yang disajikan aplikasi Anda, didukung oleh rute HTTP di salah satu komponen Anda. Halaman ini adalah sumber kebenaran untuk kontrak tool; [Skills](/apps/skills) mencakup di mana `tools.ts` berada.

## Mendeklarasikan tool

Setiap entri dalam `tools.ts` memiliki `name` yang aman untuk provider, `displayName` yang ramah, `description`, skema Zod `input`, dan `target` yang mengatakan apa yang Kazzle panggil.

```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[];
```

## Targets

`target` adalah alamat yang dapat digunakan kembali untuk tool atau tombol tindakan yang diperlukan. Panggilan tool AI langsung dan klik tombol dapat menggunakan objek target yang sama, sehingga keduanya menjalankan kode yang sama.

| Target   | Fields                                                             | Berjalan di mana                                                                                                                  |
| -------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- |
| `app`    | `component`, `path`, `method`, opsional `query`, `headers`, `body` | Salah satu komponen Anda sendiri. Kazzle menyelesaikan URL komponen dan menambahkan token identitas aplikasi yang ditandatangani. |
| `url`    | `url`, `method`, opsional `query`, `headers`, `body`               | Endpoint eksternal. Asal URL dan metode adalah nilai penulis literal.                                                             |
| `kazzle` | `name`                                                             | Handler Kazzle bawaan sisi klien.                                                                                                 |

Target HTTP menggunakan field permintaan yang sama:

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

Tidak ada default permintaan implisit. Jika handler Anda harus menerima input tool mentah sebagai JSON, tulis `body: '${input}'`.

## Referensi

Kazzle menyelesaikan referensi dalam field HTTP target `query`, `headers`, `body`, dan `url` sebelum pengiriman:

* `${input}` — seluruh input tool, atau seluruh input tombol.
* `${input.path}` — nilai bersarang dari input.
* `${env.NAME}` — variabel lingkungan bernama dari `env.collection` + `env.environment` yang dideklarasikan komponen aplikasi pemilik.

Resolusi adalah satu kali. Jika referensi hilang atau tidak dikenal, tool gagal daripada mengganti nilai kosong. Gunakan `$${input.name}` ketika Anda memerlukan teks literal `${input.name}`.

## Permintaan handler

Untuk target `app`, tambahkan rute yang cocok di komponen target. Dengan `body: '${input}'`, Kazzle mengirim input yang diketik sebagai badan JSON:

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

Target `app` juga menerima:

| Header                | Nilai                                                          |
| --------------------- | -------------------------------------------------------------- |
| `Authorization`       | `Bearer <identity token>` mengidentifikasi pengguna + install  |
| `Kazzle-Tool-Context` | `{"source":"thread","threadId":"..."}` atau `{"source":"api"}` |

Target aplikasi tidak dapat mengatur `Authorization` melalui `headers`; Kazzle memiliki header itu. Baca konteks dengan `toolContext(req)` dari `@kazzle/app/tools` ketika handler harus membedakan thread vs Tools API.

Tool yang dideklarasikan tanpa rute yang cocok tidak melakukan apa pun yang berguna — tambahkan keduanya. `tools.json` tidak didukung; compiler aplikasi gagal jika menemukan satu.

## Respons handler

Kembalikan teks biasa, atau JSON dengan hingga tiga saluran:

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

* **`content`** — hasil biasa yang dibaca AI (diberikan ke model). Diperlukan.
* **`markdown`** — opsional. Ringkasan teks kaya pendek yang dirender di kartu tool (react-markdown; HTML mentah diloloskan). Bagus untuk kalimat, daftar kecil, tautan inline. Jalur relatif telanjang dirender sebagai teks preformatted mati — tautan harus **absolut**.
* **`embedUrl`** — opsional. URL **absolut** yang disajikan aplikasi Anda; kartu merender dalam iframe sandbox. Ini adalah cara Anda menampilkan UI nyata, lebar penuh — layar koneksi, dasbor, bagan. Aplikasi Anda menghosting dan memiliki halaman, sehingga dapat sepenuhnya interaktif terhadap backend, cookie, dan OAuth Anda sendiri. Tidak ada yang ditulis ke drive.

`embedUrl` menang atas `markdown` ketika keduanya diatur. Selalu pertahankan `content` yang bermakna — itulah yang dibaca 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}` });
}
```

## Tindakan yang diperlukan

Tool aplikasi dapat menjeda thread ketika memerlukan tindakan pengguna (atau perangkat). Kembalikan `type: 'action_required'` dengan judul kartu dan tombol. Hanya valid ketika `toolContext(req).source === 'thread'` — melalui Tools API (`source: 'api'`), kembalikan kesalahan domain normal.

```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 menyimpan kartu pada panggilan tool dan melanjutkan melalui `/chat/resume` setelah tombol ditekan. Klien yang cocok melihat tombol penulis; klien yang tidak cocok masih melihat Skip/Cancel plus "Continue on {device}". Tombol tanpa `target` mengirimkan `input` sebagai hasil tool. Tombol dengan target menjalankan target itu terlebih dahulu, kemudian menggunakan hasil target sebagai hasil tool.

### UI Kaya — sematkan halaman yang dihosting aplikasi

Ketika hasil tool bersifat visual atau interaktif (layar koneksi, bagan, dasbor ringkasan), sajikan halaman dari salah satu komponen Anda dan kembalikan URL **absolut** sebagai `embedUrl`. Bangun URL dari URL komponen yang disuntikkan — jangan pernah jalur relatif.

```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}`,
});
```

Halaman berjalan dalam iframe lintas asal (asal, cookie, dan skrip sendiri). Untuk mengukur kartu, posting tingginya ke induk — kartu mendengarkan ini dan mengubah ukuran (dibatasi pada 70% viewport):

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

Lebih suka `embedUrl` daripada memancarkan string HTML: aplikasi Anda sudah melayani halaman, UI tetap interaktif dan diversi dengan kode Anda, dan tidak ada artefak per-panggilan yang ditulis di mana pun.
