К основному содержанию
скилл CX Codex

tools · assistant-ui — скилл для Claude Code: инструменты assistant-ui

Скилл про то, как объявлять инструменты, которые вызывает модель, и рисовать их вызовы в assistant-ui. Пригодится, когда инструмент не вызывается или его UI не отрисовывается.

Установить в Claude Code · Через Skills CLI (Vercel) Все способы и платформы ↓
npx skills add assistant-ui/skills --skill tools -a claude-code
Открыть на GitHub

Что делает tools

Инструмент в assistant-ui — это именованная возможность модели. Вы объявляете их в toolkit: ключи становятся именами, которые видит модель, а значения несут схему, исполнитель и рендерер. Поддерживаемый путь — файл с директивой "use generative", который компилятор разбивает на серверную сборку (схема плюс бэкенд-исполнители) и клиентскую (схема плюс рендереры плюс браузерные исполнители).

Без компилятора директива не работает. Для Next.js оборачивают конфиг через withAui из @assistant-ui/next, для Vite и TanStack Start добавляют aui() из @assistant-ui/vite в plugins, для Expo и голого React Native оборачивают конфиг Metro через withAui из @assistant-ui/metro. Дальше toolkit монтируют на клиенте через AuiConfig({ tools: Tools({ toolkit }) }) и отдают модели через new AISDKToolkit({ toolkit }).tools({ frontend }).

Вид инструмента выводится из execute и записывается обратно в type — самому type писать не нужно. Компилятор на сборке требует, чтобы у каждого инструмента был execute, у фронтенд-инструмента — render или renderText, у human-инструмента — render. humanTool() и stubTool() не имеют рантайм-реализации и бросают исключение при вызове: значит, файл так и не скомпилировали. Это первое, что стоит проверить при такой ошибке.

Какие задачи решает

  • объявить инструмент, который модель сможет вызвать
  • разделить один файл на серверную и клиентскую сборки
  • отрисовать состояние вызова инструмента в интерфейсе
  • добавить подтверждение перед вызовом инструмента
  • подключить MCP-сервер к toolkit

Примеры запросов агенту

  • Добавь инструмент get_weather в toolkit с параметрами location и unit
  • Почему мой инструмент не вызывается моделью?
  • Отрисуй результат вызова инструмента в компоненте

Как установить tools

1Через Skills CLI (Vercel)

npx skills add assistant-ui/skills --skill tools -a claude-code

2Вручную: положить файлы

~/.claude/skills/tools/SKILL.md

Папка скилла целиком; для одного проекта — .claude/skills/tools/

По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов

Из того же набора13

assistant-ui Помогает проектировать чат-интерфейсы на React: подсказывает архитектуру, выбор пакетов, рантаймов и хуков библиотеки assistant-ui. CX Codex +1 7 200установок streaming Скилл по проводным протоколам и бэкенд-хелперам assistant-ui: помогает собрать свой стриминговый эндпоинт и разобрать формат чанков. CX Codex +1 6 400установок primitives Скилл помогает собирать кастомные интерфейсы чата на базе нестилизованных компонентов @assistant-ui/react. Пригодится при вёрстке тредов, композеров и списков сообщений. CX Codex +1 6 300установок thread-list Скилл для assistant-ui: добавляет боковую панель с несколькими диалогами, переключением, переименованием, архивацией и удалением. CX Codex +1 6 200установок update Обновляет существующее приложение на assistant-ui до актуальных версий AI SDK v7 и assistant-ui 0.15.x, применяя нужные миграции. Пригодится, когда после апдейта посыпались ошибки типов или пропали хуки. CX Codex +1 5 900установок cloud Подключает AssistantCloud к приложению на assistant-ui: история тредов между сессиями, обратная связь по сообщениям и загрузка файлов. CX Codex +1 5 000установок react-mcp Скилл для работы с @assistant-ui/react-mcp: подключает MCP-серверы, выбранные пользователем, прямо в браузере и регистрирует их инструменты в чате. CX Codex +1 4 000установок observability Подключает Langfuse, LangSmith или Helicone к бэкенд-роутам assistant-ui на AI SDK и рисует собранные спаны через экспериментальный @assistant-ui/react-o11y. CX Codex +1 4 000установок

Другие версии tools

Похожие

react vercel-labs Скилл помогает собирать React-интерфейсы из JSON-спецификаций через json-render. Нужен для каталогов компонентов и UI, который создаёт ИИ. CX Codex +1 16 300установок emblem-ai EmblemCompany Добавляет в приложение вход через кошелёк, email или соцсети и даёт каждому пользователю кошелёк вместе с аккаунтом. Плюс готовый чат-компонент и отладка работающего приложения. CX Codex +1 8 700установок directives vercel-labs Готовые директивы для json-render: форматирование, математика, строки, i18n. Подключаются в каталог и рендерер за пару строк. CX Codex +1 8 000установок magicpath MagicPathAI Переносит компоненты и дизайн-системы из MagicPath в локальный проект через консольную утилиту. Помогает собирать и править интерфейсы на лету. CX Codex +1 6 300установок react-expert https://github.com/Jeffallan Скилл для разработки на React 18+, Next.js App Router и create-react-app. Генерирует компоненты, хуки, чинит рендер и переводит классовые компоненты на функциональные. CX Codex +1 5 500установок ant-design ant-design Скилл-справочник по antd 6.x, Ant Design Pro 5 и Ant Design X v2. Помогает выбрать компонент, настроить токены и не выдумывать пропсы. CX Codex +1 4 100установок

Рядом в теме «Настройка агента: память, контекст, субагенты»

Все скиллы по теме «Настройка агента: память, контекст, субагенты» →

Вопросы о tools

Как установить tools в Claude?

Выполните npx skills add assistant-ui/skills --skill tools -a claude-code. Скилл появится по пути ~/.claude/skills/tools/SKILL.md.

Что делает скилл tools?

Описывает объявление инструментов в toolkit и отрисовку их вызовов в assistant-ui: виды инструментов, рендереры, шлюзы подтверждения, MCP-серверы и сообщения субагентов.

Почему humanTool() падает в рантайме?

У humanTool() и stubTool() нет рантайм-реализации, они бросают исключение при вызове. Обычно это значит, что файл не прошёл через компилятор.

Исходный текст

SKILL.md · MIT
---
name: tools
description: "Defines model-callable tools and renders their calls in assistant-ui. The authoring model: a \"use generative\" file whose default export is defineToolkit({...}), compiled by withAui from @assistant-ui/next, aui() from @assistant-ui/vite, or withAui from @assistant-ui/metro, mounted with AuiConfig({ tools: Tools({ toolkit }) }), and exposed to the model with new AISDKToolkit({ toolkit }).tools({ frontend }). Covers the tool kinds (backend, \"use client\" frontend, humanTool(), providerTool(), externalTool(), stubTool(), defineMcpToolkit()), render and display, ToolCallMessagePartProps, approval gates, MCP servers, MCP Apps, WebMCP, and sub-agent messages. Reach for it when a tool is never called, a tool UI does not render, a frontend result never reaches the model, humanTool() throws at runtime, or an approval gate cannot be answered. For model-composed UI use generative-ui, for MCP servers users add in the browser use react-mcp, and for the styled ToolFallback and ToolGroup use elements."
license: MIT
---

# assistant-ui Tools

**Always consult [assistant-ui.com/llms.txt](https://www.assistant-ui.com/llms.txt) for the latest API.**

A tool is a named capability the model can call. In assistant-ui you declare tools in a toolkit, a map whose keys are the tool names the model sees and whose values carry the schema, the executor, and the renderer. The supported authoring path is a `"use generative"` file compiled by a build plugin, which splits one file into a server build (schema plus backend executors) and a client build (schema plus renderers plus browser executors).

## References

- [./references/toolkits.md](./references/toolkits.md) -- authoring a toolkit end to end: kinds, renderers, `toModelOutput`, `providerOptions`, stubs, splitting and merging files, `backendless`
- [./references/tool-ui.md](./references/tool-ui.md) -- rendering states, `useToolArgsStatus`, deferred rendering, streaming args, `ToolFallback` and `ToolGroup`
- [./references/human-in-loop.md](./references/human-in-loop.md) -- human tools, `human()` interrupts, and the full approval gate surface
- [./references/mcp-server.md](./references/mcp-server.md) -- server-side MCP servers and `defineMcpToolkit`
- [./references/mcp-apps.md](./references/mcp-apps.md) -- rendering MCP App `ui://` widgets with `McpAppRenderer`
- [./references/webmcp.md](./references/webmcp.md) -- publishing frontend tools to the browser agent with `unstable_useWebMcpProvider`
- [./references/multi-agent.md](./references/multi-agent.md) -- sub-agent conversations inside a tool call
- [./references/legacy-component-apis.md](./references/legacy-component-apis.md) -- the deprecated `makeAssistantTool` family and how to migrate off it

## The authoring model

### 1. Add the build plugin

The directive does nothing without a compiler.

```ts title="next.config.ts"
import { withAui } from "@assistant-ui/next";

export default withAui({
  /* your Next config */
});
```

Vite and TanStack Start add `aui()` from `@assistant-ui/vite` to `plugins` instead; Expo and bare React Native wrap the Metro config with `withAui` from `@assistant-ui/metro`. All three take an `aui` options object, documented in [toolkits.md](./references/toolkits.md).

### 2. Write the toolkit

```tsx title="app/toolkit.tsx"
"use generative";

import { defineToolkit } from "@assistant-ui/react";
import { z } from "zod";

export default defineToolkit({
  get_weather: {
    description: "Get current weather for a location.",
    parameters: z.object({
      location: z.string().describe("City name or zip code"),
      unit: z.enum(["celsius", "fahrenheit"]).default("celsius"),
    }),
    execute: async ({ location, unit }) => {
      "use client";
      return fetchWeatherAPI(location, unit);
    },
    render: ({ args, result }) =>
      result ? (
        <div>
          {result.temperature} {args.unit}
        </div>
      ) : (
        <div>Fetching weather for {args.location}</div>
      ),
  },
});
```

### 3. Mount it on the client

```tsx title="app/MyRuntimeProvider.tsx"
"use client";

import { AssistantRuntimeProvider, AuiConfig, Tools } from "@assistant-ui/react";
import { useChatRuntime } from "@assistant-ui/ai-sdk";
import toolkit from "./toolkit";

export function MyRuntimeProvider({ children }: { children: React.ReactNode }) {
  const runtime = useChatRuntime();
  const config = AuiConfig({ tools: Tools({ toolkit }) });
  return (
    <AssistantRuntimeProvider runtime={runtime} config={config}>
      {children}
    </AssistantRuntimeProvider>
  );
}
```

To scope a toolkit to part of the tree instead, wrap that subtree in `<AuiProvider extends={aui} config={config}>` with `const aui = useAui()`. `useChatRuntime()` targets `/api/chat` by default.

### 4. Expose it to the model

The same import resolves to the server build inside a route handler.

```ts title="app/api/chat/route.ts"
import { AISDKToolkit } from "@assistant-ui/ai-sdk";
import { streamText, convertToModelMessages } from "ai";
import { openai } from "@ai-sdk/openai";
import toolkit from "../../toolkit";

const aiToolkit = new AISDKToolkit({ toolkit });

export async function POST(req: Request) {
  const { messages, system, tools } = await req.json();

  const result = streamText({
    model: openai("gpt-5.6-luna"),
    system,
    messages: await convertToModelMessages(messages),
    tools: await aiToolkit.tools({ frontend: tools }),
  });

  return result.toUIMessageStreamResponse();
}
```

`AISDKToolkit.tools()` registers every toolkit tool with the model, wires the backend `execute` where the server build carries one, merges the frontend tools the client uploaded in the request body, and opens any MCP servers the toolkit spreads in. A server `execute` wins over an uploaded entry of the same name.

## Tool kinds

The kind is inferred from `execute` and written back as `type`. You never author `type` in a `"use generative"` file.

| `execute` you write | Inferred kind | Server build keeps | Client build keeps |
| --- | --- | --- | --- |
| plain `async () => ...` | backend | schema plus `execute`, guarded by `server-only` | schema plus `render` |
| `async () => { "use client"; ... }` | frontend | schema only | schema plus `execute` plus `render` or `renderText` |
| `humanTool()` | human | schema only | schema plus `render` |
| `stubTool()` | frontend, executor supplied at runtime | schema only | schema plus `render` or `renderText` |
| `providerTool({ ... })` | provider | schema plus provider config | schema plus provider config |
| `externalTool()` | backend, defined elsewhere | omitted | `type: "backend"` plus `render` or `renderText` |

The compiler enforces at build time that every tool declares an `execute`, that a frontend tool declares a `render` or `renderText`, and that a human tool declares a `render`. `humanTool()` and `stubTool()` have no runtime implementation and throw when reached, which means that file was never compiled; `externalTool()` is a compile-time marker in the same way.

## Rendering a tool call

`render` receives the live call as `ToolCallMessagePartProps`.

| Field | Type | Notes |
| --- | --- | --- |
| `args` | `TArgs` | Parsed arguments, partial while streaming |
| `argsText` | `string` | Raw, possibly partial JSON |
| `result` | `TResult \| undefined` | Present once the call has a result |
| `isError` | `boolean \| undefined` | Whether the result represents a failure |
| `status` | `ToolCallMessagePartStatus` | `running`, `complete`, `incomplete` with a `reason`, or `requires-action` with `reason: "tool-calls" \| "interrupt"` |
| `toolName`, `toolCallId` | `string` | Model-visible name and the stable id of this invocation |
| `timing` | `ToolCallTiming \| undefined` | Wall clock start and completion, when tracked |
| `interrupt` | `{ type: "human"; payload: unknown } \| undefined` | A paused `human()` request from a frontend executor |
| `approval` | object `\| undefined` | Server-side gate: `id`, `approved?`, `options?`, `optionId?`, `resolution?` |
| `addResult` | `(result) => void` | Completes a human tool from the UI |
| `resume` | `(payload: unknown) => void` | Answers an `interrupt` |
| `respondToApproval` | `(response: ToolApprovalResponse) => Promise<void>` | Answers an approval gate |

For a one-line status instead of a component, set `renderText` with `running` and `complete` values, each a string or a function of `({ args, result })`. Set `display: "standalone"` on the entry to keep the UI outside the collapsed tool group. Tools with no renderer fall back to the `ToolFallback` element.

## Approval gates

Some runtimes pause on the server and emit an approval request the client must answer before the tool runs. The AI SDK v7 runtime emits one for every tool listed in the call-level `toolApproval` option.

```tsx
import { useState } from "react";
import { defineToolkit, type ToolApprovalResponse } from "@assistant-ui/react";

const toolkit = defineToolkit({
  deploy: {
    type: "backend",
    render: ({ args, approval, respondToApproval, result }) => {
      const [error, setError] = useState<string | null>(null);

      const answer = async (response: ToolApprovalResponse) => {
        setError(null);
        try {
          await respondToApproval(response);
        } catch (failure) {
          setError(failure instanceof Error ? failure.message : String(failure));
        }
      };

      if (approval?.approved === undefined) {
        if (approval?.isAutomatic) return <p>Auto approved by policy</p>;
        return (
          <div>
            <p>Approve deploy to {args.target}?</p>
            <button onClick={() => void answer({ approved: true })}>Approve</button>
            <button onClick={() => void answer({ approved: false, reason: "user denied" })}>
              Deny
            </button>
            {error && <p role="alert">{error}</p>}
          </div>
        );
      }

      if (approval?.approved === false) {
        return <p>Denied{approval.reason ? `: ${approval.reason}` : ""}</p>;
      }
      return result === undefined ? <p>Approved, running</p> : <p>Deployed</p>;
    },
  },
});
```

`approval.approved` has three states. `undefined` means the gate is open and is the only state in which `respondToApproval` is legal. `true` means the decision was recorded as allow and the server is producing the result. `false` means it was recorded as deny; the runtime records an error result and exposes `approval.reason`. `approval.isAutomatic` is `true` when a server-side policy granted the decision rather than the user, so render a badge instead of buttons.

`respondToApproval` returns a promise that resolves once the runtime accepted the response and rejects when it could not be recorded, for example an expired gate or a refused answer. Await it before disabling the controls so a refused response leaves the request retryable. `toolApprovalAcceptsText(approval)` reports whether the request takes a free-form answer, on its own or alongside its options, so a renderer knows whether to offer a text field. The full option, question, and resolution surface is in [human-in-loop.md](./references/human-in-loop.md).

## Human tools

A human tool has no executor: the run pauses until the renderer supplies the result.

```tsx
select_date: {
  description: "Ask the user to select a date.",
  parameters: z.object({ prompt: z.string() }),
  execute: humanTool(),
  render: ({ args, result, addResult }) => {
    if (result) return <p>Selected {result.date}</p>;
    return <DatePicker prompt={args.prompt} onChange={(date) => addResult({ date })} />;
  },
},
```

Call `addResult` exactly once. Use a human tool when the user supplies the tool result itself, and an approval gate when the backend owns the action and only needs permission.

## Common Gotchas

**`humanTool()` or `stubTool()` throws at runtime**
- The file was not processed by the compiler. Add the build plugin and keep `"use generative"` as the file's first line.

**A tool UI never renders**
- The toolkit key must match the model-visible tool name exactly, including any MCP `prefix`.
- The toolkit must be mounted: `const config = AuiConfig({ tools: Tools({ toolkit }) })` passed as `config` on the provider. Pass a stable toolkit from module scope or `useMemo`.

**The model never learns about a frontend or human tool**
- The client build skips uploading those schemas because it assumes your backend imported the same file's server build. With no backend of yours, compile with `aui: { backendless: true }`.

**A frontend tool result never reaches the model**
- Configure the runtime with `sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls` from `ai`, and `lastAssistantMessageIsCompleteWithApprovalResponses` for approval gates.

**`toModelOutput` is ignored on round-tripped results**
- Pass the tool registry to `convertToModelMessages(messages, { tools })` as well as to `streamText`.

**A build warning about tool names**
- A duplicate name means two spread fragments define the same key and object spread keeps the later one; rename one entry. A tool that cannot be `makeTool()` came from an opaque factory call: write it as an inline object, or spread a compiler-visible `defineToolkit(...)` or `defineMcpToolkit(...)` fragment.

**`respondToApproval` rejects**
- It is legal only while `approval.approved` is `undefined`. A `text` answer to a request that declares neither `display: "text"` nor `allowFreeform` throws, as does an unknown `optionId`.

**MCP connections pile up**
- Keep the `AISDKToolkit` at module scope so clients pool across requests, and call `aiToolkit.close()` from `onFinish`.

## Related Skills

- [elements](../elements/SKILL.md) -- the styled `ToolFallback` and `ToolGroup` files and the rest of the catalog
- [generative-ui](../generative-ui/SKILL.md) -- UI the model composes from a vocabulary you ship
- [react-mcp](../react-mcp/SKILL.md) -- MCP servers the end user adds and authenticates in the browser
- [runtime](../runtime/SKILL.md) -- `useChatRuntime` and the AI SDK route the toolkit plugs into
- [copilots](../copilots/SKILL.md) -- interactables, the model-editable app state alternative to a stub tool
- [update](../update/SKILL.md) -- migrating older tool code to toolkits

© assistant-ui · MIT · assistant-ui/skills

Смотрите также

Спонсоры проекта

Компании, которые помогают держать аналитику открытой