Tool UI is a React component framework for conversation‑native UIs.
Tools return JSON; Tool UI renders it as inline, narrated, referenceable surfaces.
Radix/shadcn (primitives) → Tool UI (conversation‑native components & schema) → AI SDK / LangGraph / etc. (LLM orchestration)
InferUITools.See UI Guidelines for the full philosophy. Quick summary:
information, decision, control, state, compositeinvocation → output‑pending → interactive → committing → receipt → erroredThe assistant calls a tool, the tool returns JSON matching a schema, and the UI renders inline.
Server side: Define a tool that returns schema-validated JSON.
What this demonstrates:
SerializableLinkPreviewSchema enforces type-safe outputimport { streamText, tool, convertToModelMessages } from "ai";import { openai } from "@ai-sdk/openai";import { z } from "zod";import { SerializableLinkPreviewSchema } from "@/components/tool-ui/link-preview/schema";export async function POST(req: Request) { const { messages } = await req.json(); const result = streamText({ model: openai("gpt-4o"), messages: await convertToModelMessages(messages), tools: { previewLink: tool({ description: "Show a preview card for a URL", inputSchema: z.object({ url: z.url() }), outputSchema: SerializableLinkPreviewSchema, async execute({ url }) { return { id: "link-preview-1", href: url, title: "Example Site", description: "A description of the linked content", image: "https://example.com/image.jpg", }; }, }), }, }); return result.toUIMessageStreamResponse();}Client side: Register the component and let assistant-ui handle rendering.
What this demonstrates:
makeAssistantToolUI connects the tool name to a React componentparseSerializableLinkPreview validates the tool result at runtime and returns typed props<PreviewLinkUI /> registers it (no manual plumbing required)"use client";import { AssistantRuntimeProvider, makeAssistantToolUI,} from "@assistant-ui/react";import { useChatRuntime, AssistantChatTransport,} from "@assistant-ui/react-ai-sdk";import { LinkPreview, parseSerializableLinkPreview,} from "@/components/tool-ui/link-preview";const PreviewLinkUI = makeAssistantToolUI({ toolName: "previewLink", render: ({ result }) => { const preview = parseSerializableLinkPreview(result); return <LinkPreview {...preview} maxWidth="420px" />; },});export default function App() { const runtime = useChatRuntime({ transport: new AssistantChatTransport({ api: "/api/chat" }), }); return ( <AssistantRuntimeProvider runtime={runtime}> <PreviewLinkUI /> {/* your <Thread /> component here */} </AssistantRuntimeProvider> );}Every Tool UI surface is addressable and reconstructable. Here's the common schema structure:
What this shows:
id makes the tool UI referenceable by the assistant ("the link preview above")role declares the primary purpose (information, decision, control, state, or composite)actions with sentence fields let the assistant speak about user choicesreceipt provides durable proof of side effects with timestamps and identifiers{ id: string; // stable identifier for this rendering role: "information"|"decision"|"control"|"state"|"composite"; actions?: Array<{ id: string; label: string; sentence: string }>; // optional receipt after side effects: receipt?: { outcome: "success"|"partial"|"failed"|"cancelled"; summary: string; identifiers?: Record<string, string>; at: string; // ISO timestamp };}