Move makeAssistantTool, useAssistantTool, makeAssistantToolUI, and useAssistantToolUI registrations to the toolkit API.
The component and hook based tool APIs are deprecated:
makeAssistantTooluseAssistantToolmakeAssistantToolUIuseAssistantToolUI
Use a toolkit registered with Tools({ toolkit }) instead. Toolkits keep the model contract, browser execution, and tool-call rendering in one named map, which avoids duplicate registrations and makes the client/backend split explicit.
Before
import { AssistantRuntimeProvider, makeAssistantTool } from "@assistant-ui/react";
import { useChatRuntime } from "@assistant-ui/react-ai-sdk";
import { z } from "zod";
const WeatherTool = makeAssistantTool({
toolName: "get_weather",
description: "Get the current weather for a city.",
parameters: z.object({
city: z.string(),
}),
execute: async ({ city }) => fetchWeather(city),
render: ({ args, result }) => (
<WeatherCard city={args.city} weather={result} />
),
});
export function App() {
const runtime = useChatRuntime({ api: "/api/chat" });
return (
<AssistantRuntimeProvider runtime={runtime}>
<WeatherTool />
<Thread />
</AssistantRuntimeProvider>
);
}After
"use generative";
import { defineToolkit } from "@assistant-ui/react";
import { z } from "zod";
export default defineToolkit({
get_weather: {
description: "Get the current weather for a city.",
parameters: z.object({
city: z.string(),
}),
execute: async ({ city }) => {
"use client";
return fetchWeather(city);
},
render: ({ args, result }) => (
<WeatherCard city={args.city} weather={result} />
),
},
});"use client";
import {
AssistantRuntimeProvider,
Tools,
useAui,
} from "@assistant-ui/react";
import { useChatRuntime } from "@assistant-ui/react-ai-sdk";
import toolkit from "./toolkit";
export function App() {
const runtime = useChatRuntime({ api: "/api/chat" });
const aui = useAui({
tools: Tools({ toolkit }),
});
return (
<AssistantRuntimeProvider aui={aui} runtime={runtime}>
<Thread />
</AssistantRuntimeProvider>
);
}Mechanical Steps
- Create a
Toolkitobject. - Move each
toolNameinto the toolkit key. - Move
description,parameters,execute,providerOptions,render,renderText, anddisplayonto the toolkit entry. - Register the toolkit once with
useAui({ tools: Tools({ toolkit }) }). - Remove
<Tool />,<ToolUI />,useAssistantTool(...), anduseAssistantToolUI(...)registrations.
UI-Only Tool Renderers
If you used makeAssistantToolUI or useAssistantToolUI for a backend, MCP, or LangGraph tool, move the renderer onto a backend toolkit entry:
// app/toolkit.tsx
"use generative";
import { defineToolkit } from "@assistant-ui/react";
export default defineToolkit({
web_search: {
type: "backend",
render: ({ args, result }) => (
<SearchResults query={args.query} results={result?.results ?? []} />
),
},
});Backend entries do not upload a schema or execute in the browser. They only attach UI for matching tool-call message parts.
For a one-off renderer that should only affect a particular message surface, use MessagePrimitive.Parts inline tool render overrides instead of a global registration.
Dynamic Tools
If a tool needs component state or props, keep the toolkit contract in a "use generative" file and use stubTool() for the executor. The component that owns the state supplies the real executor with useAuiToolOverrides(...):
useAuiToolOverrides is experimental and its API may change.
"use generative";
import { defineToolkit, stubTool } from "@assistant-ui/react";
import { z } from "zod";
export type Task = { id: string; title: string };
export default defineToolkit({
add_task: {
description: "Add a task to the board.",
parameters: z.object({ title: z.string() }),
execute: stubTool(),
renderText: {
running: "Adding task",
complete: "Task added",
},
},
});import { AuiProvider, Tools, useAui, useAuiToolOverrides } from "@assistant-ui/react";
import { useState, type Dispatch, type SetStateAction } from "react";
import type { Task } from "./task-board-toolkit";
import toolkit from "./task-board-toolkit";
function TaskBoard() {
const [tasks, setTasks] = useState<Task[]>([]);
const aui = useAui({
tools: Tools({ toolkit }),
});
return (
<AuiProvider value={aui}>
<TaskBoardToolOverrides setTasks={setTasks} />
<TaskList tasks={tasks} />
</AuiProvider>
);
}
function TaskBoardToolOverrides({
setTasks,
}: {
setTasks: Dispatch<SetStateAction<Task[]>>;
}) {
useAuiToolOverrides({
add_task: {
execute: async ({ title }) => {
setTasks((prev) => [
...prev,
{ id: crypto.randomUUID(), title },
]);
return { ok: true };
},
},
});
return null;
}This keeps the model-facing contract in the toolkit file while the component owns the stateful executor.
Generative Toolkits
For tools authored in a "use generative" file, export a toolkit with defineToolkit(...). The compiler splits backend, frontend, and human tools for you. This is the default shape to use for docs and copy-paste examples:
"use generative";
import { defineToolkit } from "@assistant-ui/react";
import { z } from "zod";
export default defineToolkit({
create_chart: {
description: "Create a chart for the user.",
parameters: z.object({
title: z.string(),
values: z.array(z.number()),
}),
execute: async ({ title, values }) => {
"use client";
return renderChart(title, values);
},
render: ChartTool,
},
});Frontend and human tools produced by the generative compiler already have their schema defaults on the backend, so the client no longer re-uploads those schemas.