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

# Herramientas

> Cómo una app declara herramientas y cómo sus manejadores devuelven resultados.

# Herramientas

Una **herramienta** es una acción nombrada que la IA puede invocar, con entradas tipadas — p. ej. `send_email(to, subject, body)`. Las herramientas se declaran en el archivo `tools.ts` de una skill y, para herramientas que tu app sirve, están respaldadas por una ruta HTTP en uno de tus componentes. Esta página es la fuente de verdad para el contrato de herramientas; [Skills](/apps/skills) cubre dónde vive `tools.ts`.

## Declarar una herramienta

Cada entrada en `tools.ts` tiene un `name` seguro para proveedores, un `displayName` amigable, una `description`, un esquema Zod `input`, y un `target` que indica qué invoca 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[];
```

## Destinos

`target` es la dirección reutilizable para una herramienta o botón de acción requerida. Una llamada directa de herramienta de IA y un clic de botón pueden usar el mismo objeto target, por lo que ambos ejecutan el mismo código.

| Destino  | Campos                                                             | Se ejecuta donde                                                                                                       |
| -------- | ------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- |
| `app`    | `component`, `path`, `method`, `query` opcional, `headers`, `body` | En uno de tus propios componentes. Kazzle resuelve la URL del componente y añade un token de identidad de app firmado. |
| `url`    | `url`, `method`, `query` opcional, `headers`, `body`               | Un endpoint externo. El origen de la URL y el método son valores literales del autor.                                  |
| `kazzle` | `name`                                                             | Un manejador integrado del lado del cliente de Kazzle.                                                                 |

Los destinos HTTP usan los mismos campos de solicitud:

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

No hay valores por defecto implícitos en la solicitud. Si tu manejador debe recibir la entrada de herramienta sin procesar como JSON, escribe `body: '${input}'`.

## Referencias

Kazzle resuelve referencias en los campos `query`, `headers`, `body` y `url` del destino HTTP antes del envío:

* `${input}` — toda la entrada de herramienta, o toda la entrada de botón.
* `${input.path}` — un valor anidado de la entrada.
* `${env.NAME}` — una variable de entorno nombrada de la `env.collection` declarada del componente de app propietario + `env.environment`.

La resolución es de un solo paso. Si falta una referencia o es desconocida, la herramienta falla en lugar de sustituir un valor vacío. Usa `$${input.name}` cuando necesites texto literal `${input.name}`.

## Solicitud del manejador

Para un destino `app`, añade la ruta coincidente en el componente destino. Con `body: '${input}'`, Kazzle envía la entrada tipada como cuerpo JSON:

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

Los destinos `app` también reciben:

| Encabezado            | Valor                                                            |
| --------------------- | ---------------------------------------------------------------- |
| `Authorization`       | `Bearer <identity token>` identificando al usuario + instalación |
| `Kazzle-Tool-Context` | `{"source":"thread","threadId":"..."}` o `{"source":"api"}`      |

Los destinos de app no pueden establecer `Authorization` a través de `headers`; Kazzle es propietario de ese encabezado. Lee el contexto con `toolContext(req)` desde `@kazzle/app/tools` cuando el manejador debe ramificarse entre thread y Tools API.

Una herramienta declarada sin una ruta coincidente no hace nada útil — añade ambas juntas. `tools.json` no es compatible; el compilador de app falla si encuentra uno.

## Respuesta del manejador

Devuelve texto sin formato, o JSON con hasta tres canales:

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

* **`content`** — el resultado sin formato que la IA lee (alimentado al modelo). Requerido.
* **`markdown`** — opcional. Un resumen de texto enriquecido corto renderizado en la tarjeta de herramienta (react-markdown; el HTML sin procesar se escapa). Bueno para una oración, una lista pequeña, un enlace en línea. Una ruta relativa sin procesar se renderiza como texto preformateado muerto — los enlaces deben ser **absolutos**.
* **`embedUrl`** — opcional. Una URL **absoluta** que tu app sirve; la tarjeta la renderiza en un iframe aislado. Así es como muestras una interfaz real y de ancho completo — una pantalla de conexión, un panel, un gráfico. Tu app aloja y es propietaria de la página, por lo que puede ser completamente interactiva contra tu propio backend, cookies y OAuth. Nada se escribe en el drive.

`embedUrl` prevalece sobre `markdown` cuando ambos están establecidos. Siempre mantén un `content` significativo — eso es lo que la IA lee.

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

## Acción requerida

Una herramienta de app puede pausar el thread cuando necesita una acción del usuario (o dispositivo). Devuelve `type: 'action_required'` con un título de tarjeta y botones. Solo válido cuando `toolContext(req).source === 'thread'` — sobre la Tools API (`source: 'api'`), devuelve un error de dominio normal en su lugar.

```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 guarda la tarjeta en la llamada de herramienta y reanuda a través de `/chat/resume` después de que se presiona un botón. Los clientes coincidentes ven los botones del autor; los clientes no coincidentes aún ven Omitir/Cancelar más "Continuar en {dispositivo}". Un botón sin `target` envía su `input` como resultado de herramienta. Un botón con un destino ejecuta ese destino primero, luego usa el resultado del destino como resultado de herramienta.

### Interfaz enriquecida — incrustar una página alojada en app

Cuando el resultado de una herramienta es visual o interactivo (una pantalla de conexión, un gráfico, un panel de resumen), sirve la página desde uno de tus componentes y devuelve su URL **absoluta** como `embedUrl`. Construye la URL a partir de la URL del componente inyectada — nunca una ruta relativa.

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

La página se ejecuta en un iframe de origen cruzado (su propio origen, cookies y scripts). Para dimensionar la tarjeta, publica su altura al padre — la tarjeta escucha esto y se redimensiona (limitado al 70% de la ventana gráfica):

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

Prefiere `embedUrl` sobre emitir cadenas HTML: tu app ya sirve páginas, la interfaz permanece interactiva y versionada con tu código, y ningún artefacto por llamada se escribe en ningún lugar.
