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

# Narzędzia

> Jak aplikacja deklaruje narzędzia i jak ich handlery zwracają wyniki.

# Narzędzia

**Narzędzie** to nazwana akcja, którą może wywołać AI, z typizowanymi wejściami — np. `send_email(to, subject, body)`. Narzędzia są deklarowane w pliku `tools.ts` umiejętności i, dla narzędzi, które serwuje Twoja aplikacja, wspierane przez trasę HTTP w jednym z Twoich komponentów. Ta strona jest źródłem prawdy dla kontraktu narzędzia; [Umiejętności](/apps/skills) opisują, gdzie znajduje się `tools.ts`.

## Deklarowanie narzędzia

Każdy wpis w `tools.ts` ma bezpieczną dla dostawcy `name`, przyjazną `displayName`, `description`, schemat Zod `input` i `target`, który określa, co Kazzle wywołuje.

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

## Cele

`target` to wielokrotnie używany adres dla narzędzia lub przycisku wymaganej akcji. Bezpośrednie wywołanie narzędzia AI i kliknięcie przycisku mogą używać tego samego obiektu target, więc oba uruchamiają ten sam kod.

| Cel      | Pola                                                                  | Uruchamia się gdzie                                                                                                  |
| -------- | --------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `app`    | `component`, `path`, `method`, opcjonalnie `query`, `headers`, `body` | W jednym z Twoich komponentów. Kazzle rozwiązuje adres URL komponentu i dodaje podpisany token tożsamości aplikacji. |
| `url`    | `url`, `method`, opcjonalnie `query`, `headers`, `body`               | W zewnętrznym punkcie końcowym. Pochodzenie adresu URL i metoda to dosłowne wartości autora.                         |
| `kazzle` | `name`                                                                | Wbudowany handler po stronie klienta Kazzle.                                                                         |

Cele HTTP używają tych samych pól żądania:

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

Nie ma niejawnych domyślnych ustawień żądania. Jeśli Twój handler powinien otrzymać surowe wejście narzędzia jako JSON, napisz `body: '${input}'`.

## Odwołania

Kazzle rozwiązuje odwołania w polach `query`, `headers`, `body` i `url` celu HTTP przed wysłaniem:

* `${input}` — całe wejście narzędzia lub całe wejście przycisku.
* `${input.path}` — zagnieżdżona wartość z wejścia.
* `${env.NAME}` — nazwana zmienna środowiskowa z deklarowanej `env.collection` + `env.environment` komponentu aplikacji będącego właścicielem.

Rozwiązanie jest jednorazowe. Jeśli odwołanie brakuje lub jest nieznane, narzędzie się nie powiedzie zamiast podstawiać pustą wartość. Użyj `$${input.name}`, gdy potrzebujesz dosłownego tekstu `${input.name}`.

## Żądanie handlera

Dla celu `app` dodaj pasującą trasę w komponencie docelowym. Z `body: '${input}'`, Kazzle wysyła typizowane wejście jako treść JSON:

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

Cele `app` również otrzymują:

| Nagłówek              | Wartość                                                           |
| --------------------- | ----------------------------------------------------------------- |
| `Authorization`       | `Bearer <identity token>` identyfikujący użytkownika + instalację |
| `Kazzle-Tool-Context` | `{"source":"thread","threadId":"..."}` lub `{"source":"api"}`     |

Cele aplikacji nie mogą ustawiać `Authorization` przez `headers`; Kazzle jest właścicielem tego nagłówka. Przeczytaj kontekst za pomocą `toolContext(req)` z `@kazzle/app/tools`, gdy handler musi rozróżniać wątek vs Tools API.

Narzędzie zadeklarowane bez pasującej trasy nic pożytecznego nie robi — dodaj oba razem. `tools.json` nie jest obsługiwany; kompilator aplikacji się nie powiedzie, jeśli go znajdzie.

## Odpowiedź handlera

Zwróć zwykły tekst lub JSON z maksymalnie trzema kanałami:

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

* **`content`** — zwykły wynik, który czyta AI (przekazywany do modelu). Wymagane.
* **`markdown`** — opcjonalnie. Krótkie podsumowanie w formacie bogatego tekstu renderowane na karcie narzędzia (react-markdown; surowy HTML jest zmieniony). Dobre dla zdania, małej listy, linku wbudowanego. Sama ścieżka względna renderuje się jako martwy tekst preformatowany — linki muszą być **bezwzględne**.
* **`embedUrl`** — opcjonalnie. **Bezwzględny** adres URL, który serwuje Twoja aplikacja; karta renderuje go w piaskownicy iframe. W ten sposób pokazujesz rzeczywisty, pełnoszerokoś UI — ekran połączenia, pulpit nawigacyjny, wykres. Twoja aplikacja hostuje i jest właścicielem strony, więc może być w pełni interaktywna względem Twojego własnego backendu, ciasteczek i OAuth. Nic nie jest zapisywane na dysku.

`embedUrl` wygrywa nad `markdown`, gdy oba są ustawione. Zawsze zachowaj znaczący `content` — to to, co czyta 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}` });
}
```

## Wymagana akcja

Narzędzie aplikacji może wstrzymać wątek, gdy potrzebuje akcji użytkownika (lub urządzenia). Zwróć `type: 'action_required'` z tytułem karty i przyciskami. Ważne tylko wtedy, gdy `toolContext(req).source === 'thread'` — przez Tools API (`source: 'api'`), zwróć zamiast tego normalny błąd domeny.

```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 zapisuje kartę na wywołaniu narzędzia i wznawia przez `/chat/resume` po naciśnięciu przycisku. Pasujący klienci widzą przyciski autora; niepassujący klienci nadal widzą Pomiń/Anuluj plus „Kontynuuj na {urządzeniu}". Przycisk bez `target` przesyła swoje `input` jako wynik narzędzia. Przycisk z celem najpierw uruchamia ten cel, a następnie używa wyniku celu jako wyniku narzędzia.

### Bogaty UI — osadź stronę hostowaną przez aplikację

Gdy wynik narzędzia jest wizualny lub interaktywny (ekran połączenia, wykres, pulpit nawigacyjny podsumowania), serwuj stronę z jednego z Twoich komponentów i zwróć jej **bezwzględny** adres URL jako `embedUrl`. Zbuduj adres URL z wstrzykniętego adresu URL komponentu — nigdy ścieżki względnej.

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

Strona uruchamia się w iframe o innym pochodzeniu (jej własne pochodzenie, ciasteczka i skrypty). Aby zmienić rozmiar karty, wyślij jej wysokość do rodzica — karta nasłuchuje tego i zmienia rozmiar (ograniczone do 70% okna przeglądarki):

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

Preferuj `embedUrl` zamiast emitowania ciągów HTML: Twoja aplikacja już serwuje strony, UI pozostaje interaktywny i wersjonowany z Twoim kodem, a żadne artefakty na wywołanie nie są zapisywane nigdzie.
