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

> Cách một ứng dụng khai báo các công cụ và cách các trình xử lý của chúng trả về kết quả.

# Tools

Một **tool** là một hành động được đặt tên mà AI có thể gọi, với các đầu vào được gõ — ví dụ: `send_email(to, subject, body)`. Các công cụ được khai báo trong `tools.ts` của một skill và, đối với các công cụ mà ứng dụng của bạn cung cấp, được hỗ trợ bởi một tuyến đường HTTP trong một trong các thành phần của bạn. Trang này là nguồn sự thật cho hợp đồng công cụ; [Skills](/apps/skills) bao gồm nơi `tools.ts` nằm.

## Khai báo một công cụ

Mỗi mục trong `tools.ts` có một `name` an toàn với nhà cung cấp, một `displayName` thân thiện, một `description`, một lược đồ Zod `input`, và một `target` cho biết Kazzle gọi cái gì.

```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` là địa chỉ có thể tái sử dụng cho một công cụ hoặc nút hành động bắt buộc. Một lệnh gọi công cụ AI trực tiếp và một cú nhấp chuột nút có thể sử dụng cùng một đối tượng target, vì vậy cả hai đều chạy cùng một mã.

| Target   | Fields                                                             | Chạy ở đâu                                                                                                                   |
| -------- | ------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
| `app`    | `component`, `path`, `method`, `query` tùy chọn, `headers`, `body` | Một trong các thành phần của riêng bạn. Kazzle phân giải URL thành phần và thêm một mã thông báo nhận dạng ứng dụng được ký. |
| `url`    | `url`, `method`, `query` tùy chọn, `headers`, `body`               | Một điểm cuối bên ngoài. Gốc URL và phương thức là các giá trị tác giả theo nghĩa đen.                                       |
| `kazzle` | `name`                                                             | Một trình xử lý Kazzle phía máy khách được tích hợp sẵn.                                                                     |

Các target HTTP sử dụng các trường yêu cầu giống nhau:

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

Không có giá trị mặc định yêu cầu ngầm. Nếu trình xử lý của bạn sẽ nhận đầu vào công cụ thô dưới dạng JSON, hãy viết `body: '${input}'`.

## References

Kazzle phân giải các tham chiếu trong các trường `query`, `headers`, `body` và `url` của HTTP target trước khi gửi:

* `${input}` — toàn bộ đầu vào công cụ, hoặc toàn bộ đầu vào nút.
* `${input.path}` — một giá trị lồng nhau từ đầu vào.
* `${env.NAME}` — một biến môi trường được đặt tên từ `env.collection` + `env.environment` được khai báo của thành phần ứng dụng sở hữu.

Phân giải là một lần duy nhất. Nếu một tham chiếu bị thiếu hoặc không xác định, công cụ sẽ thất bại thay vì thay thế một giá trị trống. Sử dụng `$${input.name}` khi bạn cần văn bản `${input.name}` theo nghĩa đen.

## Handler request

Đối với một target `app`, hãy thêm tuyến đường phù hợp trong thành phần target. Với `body: '${input}'`, Kazzle gửi đầu vào được gõ dưới dạng phần thân JSON:

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

Các target `app` cũng nhận:

| Header                | Value                                                          |
| --------------------- | -------------------------------------------------------------- |
| `Authorization`       | `Bearer <identity token>` xác định người dùng + cài đặt        |
| `Kazzle-Tool-Context` | `{"source":"thread","threadId":"..."}` hoặc `{"source":"api"}` |

Các target ứng dụng không thể đặt `Authorization` thông qua `headers`; Kazzle sở hữu header đó. Đọc ngữ cảnh với `toolContext(req)` từ `@kazzle/app/tools` khi trình xử lý phải phân nhánh thread so với Tools API.

Một công cụ được khai báo mà không có tuyến đường phù hợp sẽ không làm gì hữu ích — hãy thêm cả hai. `tools.json` không được hỗ trợ; trình biên dịch ứng dụng sẽ thất bại nếu tìm thấy một.

## Handler response

Trả về văn bản thuần túy hoặc JSON với tối đa ba kênh:

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

* **`content`** — kết quả thuần túy mà AI đọc (được cung cấp cho mô hình). Bắt buộc.
* **`markdown`** — tùy chọn. Một bản tóm tắt văn bản phong phú ngắn được hiển thị trong thẻ công cụ (react-markdown; HTML thô bị thoát). Tốt cho một câu, một danh sách nhỏ, một liên kết nội tuyến. Một đường dẫn tương đối trần hiển thị dưới dạng văn bản được định dạng trước chết — các liên kết phải là **tuyệt đối**.
* **`embedUrl`** — tùy chọn. Một URL **tuyệt đối** mà ứng dụng của bạn cung cấp; thẻ hiển thị nó trong một iframe được cách ly. Đây là cách bạn hiển thị một giao diện người dùng thực, toàn chiều rộng — một màn hình kết nối, một bảng điều khiển, một biểu đồ. Ứng dụng của bạn lưu trữ và sở hữu trang, vì vậy nó có thể hoàn toàn tương tác với backend, cookie và OAuth của riêng bạn. Không có gì được viết vào drive.

`embedUrl` thắng `markdown` khi cả hai được đặt. Luôn giữ một `content` có ý nghĩa — đó là những gì AI đọc.

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

## Required action

Một công cụ ứng dụng có thể tạm dừng thread khi nó cần một hành động của người dùng (hoặc thiết bị). Trả về `type: 'action_required'` với tiêu đề thẻ và các nút. Chỉ hợp lệ khi `toolContext(req).source === 'thread'` — qua Tools API (`source: 'api'`), hãy trả về một lỗi miền bình thường thay thế.

```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 lưu thẻ trên lệnh gọi công cụ và tiếp tục thông qua `/chat/resume` sau khi một nút được nhấn. Các máy khách phù hợp thấy các nút tác giả; các máy khách không phù hợp vẫn thấy Skip/Cancel cộng với "Continue on {device}". Một nút không có `target` gửi `input` của nó dưới dạng kết quả công cụ. Một nút có target chạy target đó trước, sau đó sử dụng kết quả target làm kết quả công cụ.

### Rich UI — nhúng một trang được lưu trữ bởi ứng dụng

Khi kết quả của một công cụ là trực quan hoặc tương tác (một màn hình kết nối, một biểu đồ, một bảng điều khiển tóm tắt), hãy cung cấp trang từ một trong các thành phần của bạn và trả về URL **tuyệt đối** của nó dưới dạng `embedUrl`. Xây dựng URL từ URL thành phần được tiêm — không bao giờ là một đường dẫn tương đối.

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

Trang chạy trong một iframe gốc chéo (gốc, cookie và tập lệnh của riêng nó). Để thay đổi kích thước thẻ, hãy đăng chiều cao của nó cho cha mẹ — thẻ lắng nghe điều này và thay đổi kích thước (được giới hạn ở 70% của viewport):

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

Ưu tiên `embedUrl` hơn phát ra các chuỗi HTML: ứng dụng của bạn đã cung cấp các trang, giao diện người dùng vẫn tương tác và được phiên bản với mã của bạn, và không có hiện vật trên mỗi cuộc gọi được viết ở bất kỳ đâu.
