Devtools
Devtools shows the messages sent to the model, the tool input, and the tool result for a chat run.
The TanStack AI devtools panel shows you this while the app runs. It is a plugin for TanStack Devtools. This takes about 3 minutes.
1. Install#
npm i -D @tanstack/react-ai-devtools @tanstack/react-devtoolspnpm add -D @tanstack/react-ai-devtools @tanstack/react-devtoolsyarn add -D @tanstack/react-ai-devtools @tanstack/react-devtoolsbun add -d @tanstack/react-ai-devtools @tanstack/react-devtools2. Add the panel#
Render TanStackDevtools with the AI plugin in your app root. Set connectToServerBus: true so the panel also gets events from the server.
React:
import { TanStackDevtools } from '@tanstack/react-devtools'
import { aiDevtoolsPlugin } from '@tanstack/react-ai-devtools'
const App = () => {
return (
<>
<TanStackDevtools
plugins={[aiDevtoolsPlugin()]}
eventBusConfig={{
connectToServerBus: true,
}}
/>
</>
)
}Svelte:
<script lang="ts">
import { aiDevtoolsPlugin } from '@tanstack/svelte-ai-devtools'
import { TanStackDevtools } from '@tanstack/svelte-devtools'
const plugins = [aiDevtoolsPlugin()]
</script>
<TanStackDevtools
{plugins}
eventBusConfig={{
connectToServerBus: true,
}}
/>If you already use other devtools plugins, add aiDevtoolsPlugin() to the same plugins array.
3. Start the server bus (without Vite)#
connectToServerBus: true needs a WebSocket/SSE server on port 4206. With Vite, @tanstack/devtools-vite starts it for you. Skip this step.
With Next.js or another bundler, start ServerEventBus when the server boots. In Next.js, put this in instrumentation.ts:
export async function register() {
if (
process.env['NEXT_RUNTIME'] === 'nodejs' &&
process.env.NODE_ENV === 'development'
) {
const { ServerEventBus } = await import(
'@tanstack/devtools-event-bus/server'
)
const bus = new ServerEventBus()
await bus.start()
}
}4. Open the panel#
Start the app, open the devtools, and open the AI panel. Send a chat message. The panel lists your chat hook, and the timeline fills with the run, its events, and any tool calls.
What you see#
- Hook dashboard: every active TanStack AI hook on the page. This includes chat, structured output, image, video, audio, speech, transcription, and summarize hooks.
- Run timeline: user turns, linked runs, stream events, client snapshots, and server-only events, by threadId and runId.
- Tool calls: the input and output of each tool call.
- Chat state: the live messages and the message history.
- Errors: errors and exceptions in AI calls.
Also there when you need it:
- Tool fixture replay: build a tool payload from the input schema of a tool, and add the result to the chat. See Tool Fixtures.
- Memory inspector: for chats with memoryMiddleware, see what memory recalled and added each turn. See Memory Inspector.
How it works#
Events come from two places:
- The client. The headless client sends the state that the browser can see.
- The server. The server sends details that never reach the client, such as middleware and provider stream events. devtoolsMiddleware runs inside every chat() call and sends these to the server bus. The bus forwards them to the panel.
Each event has a source descriptor and a stable envelope id. The panel uses them to link related events and to skip duplicates.
Advanced#
Hook dashboard#
The panel listens for active TanStack AI clients and shows them in the left sidebar.
- A hook registers when it is created and sends a snapshot at once.
- It sends a new snapshot when the panel opens or asks for state. So the hooks show up even if you open the panel after the app rendered.
Each hook entry shows its type, lifecycle, message count, run count, and the latest linked threadId. Select a hook to open its full timeline.
Chat hooks use a view by turn. A user message wraps every run and event that happened while the model answered that turn. The details view also shows small client and server state snapshots between runs, so you can see what changed.
Naming hooks#
When a page has more than one AI hook, give each one a label with devtools.name. The name changes only the label. Hook type, framework, thread id, and run links still come from the TanStack AI client.
import { fetchServerSentEvents, useChat } from '@tanstack/ai-react'
export function SupportChat() {
const chat = useChat({
threadId: 'support-chat',
connection: fetchServerSentEvents('/api/chat'),
devtools: {
name: 'Support Chat',
},
})
// render your chat UI with `chat.messages`, `chat.sendMessage`, etc.
}The same option works for the generation hooks:
import { fetchServerSentEvents, useGenerateImage } from '@tanstack/ai-react'
export function ImageStudio() {
const image = useGenerateImage({
threadId: 'generation-hooks:useGenerateImage',
connection: fetchServerSentEvents('/api/image'),
devtools: {
name: 'Image Studio',
},
})
// render your image generation UI with `image.generate` and `image.result`
}Tool Fixtures#
When a useChat hook gets tools, the panel lists the tools and their schemas. For inputs that use a Standard Schema, the panel shows a small form, so you can build a tool call payload without writing JSON by hand.
When you apply a fixture, the panel adds the tool call and its result to the real chat messages of that hook. The panel saves fixtures in browser localStorage, under the AI devtools namespace. They are there the next time you open the panel.
Memory Inspector#
When a chat uses memoryMiddleware, the Memory tab of the hook shows what the server memory backend did for that conversation. The tab groups the data by scope (session):
- Operations timeline: the recall for each turn. It shows the query, how many fragments came back, how many characters went into the system prompt, if memory tools were shown to the model, and how long the recall took.
- Stored records and facts: the current contents of the memory store for the scope. This needs an adapter with the optional inspect/listFacts methods. The built-in inMemory() and redis() adapters have them. Adapters without them still show the operations timeline.
Memory runs on the server. Its state gets to the panel over the chat stream, as a CUSTOM event that the client sends again, the same way generation results do.
- The snapshot shows memory as it was at the start of each turn. So the writes of a turn show up in the snapshot of the next turn.
- If you open the panel after a turn, it replays the latest memory state. The tab has data even when you open devtools in the middle of a conversation.