assistant-ui/skills

tools

Defines model-callable tools and renders their calls in assistant-ui.

Ver código-fonte
Documento original do Skill

Renderizado do repositório de origem, preservando títulos, exemplos, código, tabelas, links e imagens.

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

The authoring model

1. Add the build plugin

The directive does nothing without a compiler.

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.

2. Write the 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
"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
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 writeInferred kindServer build keepsClient build keeps
plain async () => ...backendschema plus execute, guarded by server-onlyschema plus render
async () => { "use client"; ... }frontendschema onlyschema plus execute plus render or renderText
humanTool()humanschema onlyschema plus render
stubTool()frontend, executor supplied at runtimeschema onlyschema plus render or renderText
providerTool({ ... })providerschema plus provider configschema plus provider config
externalTool()backend, defined elsewhereomittedtype: "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.

FieldTypeNotes
argsTArgsParsed arguments, partial while streaming
argsTextstringRaw, possibly partial JSON
result`TResult \undefined`Present once the call has a result
isError`boolean \undefined`Whether the result represents a failure
statusToolCallMessagePartStatusrunning, complete, incomplete with a reason, or requires-action with `reason: "tool-calls" \"interrupt"`
toolName, toolCallIdstringModel-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
approvalobject `\undefined`Server-side gate: id, approved?, options?, optionId?, resolution?
addResult(result) => voidCompletes a human tool from the UI
resume(payload: unknown) => voidAnswers 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.

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 -- the styled ToolFallback and ToolGroup files and the rest of the catalog
  • generative-ui -- UI the model composes from a vocabulary you ship
  • react-mcp -- MCP servers the end user adds and authenticates in the browser
  • runtime -- useChatRuntime and the AI SDK route the toolkit plugs into
  • copilots -- interactables, the model-editable app state alternative to a stub tool
  • update -- migrating older tool code to toolkits
do mesmo repositório

Mais Skills

Todos os Skills
assistant-ui
Comunidade

assistant-ui

Overview and router for assistant-ui, the React library for building AI chat interfaces from composable primitives and a styled elements catalog. Use for high-level, cross-cutting, or architecture questions: choosing packages, picking a runtime, or understanding the layers (elements, primitives, the aui client with AuiConfig and AuiProvider, the runtime, adapters) and the message model. Covers @assistant-ui/react 0.15.x, the framework-neutral @assistant-ui/ai-sdk integration for AI SDK v7 (useChatRuntime, AssistantChatTransport; @assistant-ui/react-ai-sdk re-exports it), @assistant-ui/core, @assistant-ui/store, assistant-stream, assistant-cloud, the adapters for LangGraph, LangChain, Google ADK, A2A, AG-UI, Eve, OpenCode, and Pi, and the platform bindings @assistant-ui/react-native and @assistant-ui/react-ink; AssistantRuntimeProvider; the primitives ThreadPrimitive, MessagePrimitive, ComposerPrimitive; the hooks useAui, useAuiState, useAuiEvent; and runtime selection across useChatRuntime, useExternalStoreRuntime, useLangGraphRuntime, useLocalRuntime. For a specific area route to a focused sibling instead: setup, elements, primitives, runtime, tools, generative-ui, streaming, cloud, thread-list, copilots, markdown, react-mcp, observability, react-native, ink, or update.

instalações
1
GitHub Stars
26
Atualizado
4 de set.
assistant-ui
Comunidade

cloud

Adds AssistantCloud backed persistence, authorization, and telemetry to assistant-ui apps. Use when wiring cross-session thread and message history, multi-device chat, message feedback, file uploads, or auth: passing cloud to useChatRuntime from @assistant-ui/ai-sdk, AISDKThreads({ cloud }) for AuiConfig hosts, the standalone useCloudChat/useThreads hooks from @assistant-ui/cloud-ai-sdk, or cloud on useLangGraphRuntime. Covers constructing AssistantCloud with authToken (JWT), apiKey plus userId/workspaceId (server-side), or anonymous; direct provider integrations (Clerk, Auth0, Supabase, Firebase) and a backend token endpoint; and the client surface verified against source: cloud.threads.{list,get,create,update,delete}, cloud.threads.messages.{list,create,update,feedback}, cloud.files.{generatePresignedUploadUrl,pdfToImages}, cloud.runs.{stream,report}, cloud.projects.threads, cloud.auth.tokens.create, and cloud.telemetry. Also covers a custom ThreadHistoryAdapter built on CloudMessagePersistence/createFormattedPersistence, run telemetry (beforeReport, sub-agent tracking with wrapSamplingHandler), and the NEXTPUBLICASSISTANTBASEURL/ASSISTANTAPIKEY env vars. Route here for threads that do not persist, 401s against the cloud API, or feedback buttons that do not save. For the sidebar UI itself use thread-list; for the general RemoteThreadListAdapter/ThreadHistoryAdapter contract use runtime.

instalações
1
GitHub Stars
26
Atualizado
4 de set.
assistant-ui
Comunidade

copilots

Grounding an assistant in your app with assistant-ui copilots (@assistant-ui/react). Use when steering assistant behavior with useAssistantInstructions, feeding lazy send-time app state through useAssistantContext({ getContext }), exposing rendered components to the assistant with makeAssistantVisible(Component, { clickable, editable }), giving the model two-way component state through interactables (unstableuseInteractable for app-scoped panels, unstableinteractableTool inside defineToolkit for thread-scoped artifacts, both mounted via AuiConfig({ unstableinteractables: unstableInteractables() })), registering instructions and tools together imperatively with aui.modelContext.register({ getModelContext }), or bridging model context across an iframe boundary with AssistantFrameProvider and useAssistantFrameHost. The legacy Interactables() scope, useAssistantInteractable, and useInteractableState are deprecated since 2026-06-14 and scheduled for removal on or after 2026-09-14; new code uses the unstable API. Reach for this when the assistant should read the current page, click or edit UI, read and update component state through auto-generated update{name} tools, or receive tools and instructions from a sandboxed iframe. For LLM tools and tool-call UI use the tools skill; for runtime and thread state use the runtime skill.

instalações
1
GitHub Stars
26
Atualizado
4 de set.
assistant-ui
Comunidade

elements

Installs and customizes assistant-ui elements, the styled shadcn-style component catalog at assistant-ui.com/elements served from the r.assistant-ui.com registry through npx assistant-ui@latest add . Use when adding a prebuilt chat surface or widget (Thread, ThreadList, AssistantModal, AssistantSidebar, ToolFallback, ToolGroup, MarkdownText, Reasoning, Sources, Attachment, ModelSelector, Voice orb, McpConfig) or one of the 120 standalone elements (approval card, agent plan, code diff, data table, chart, trace waterfall, message queue, composer variants, and so on), choosing between runtime-connected .aui.tsx files and props-driven standalone files, overriding Thread slots through the components prop, editing the copied source under components/assistant-ui/elements/, using the shared surfaces.tsx tokens, or picking the Radix versus Base UI flavor through the style-aware registry URL in components.json. Route here when an import from @/components/assistant-ui/... fails, an element renders unstyled, or the CLI installs the wrong flavor. For unstyled building blocks use primitives; for the CLI scaffold itself use setup.

instalações
1
GitHub Stars
26
Atualizado
4 de set.