React Hooks
Build chat UIs with useAgent, useSessionHistory, and useInterruptSession. Handles streaming, reconnection, and status updates automatically.
The experimental-agent/react package provides React hooks that handle streaming, reconnection, interruption, and status updates out of the box. They pair with handleRequest on the server.
Installation
The hooks require @ai-sdk/react and @tanstack/react-query as peer dependencies:
npm i @ai-sdk/react @tanstack/react-queryuseAgent
useAgent wraps @ai-sdk/react's useChat with an agent-aware transport. It automatically routes messages through your agent's API, handles reconnection on page reload, and surfaces AgentStatus updates.
Export a type alias from your agent file to avoid importing runtime code into client components:
import { agent } from "experimental-agent";
export const myAgent = agent("my-agent", {
model: "anthropic/claude-opus-4.6",
system: "You are a helpful assistant.",
});
export type MyAgent = typeof myAgent;Then use the type in your components:
"use client";
import { useAgent } from "experimental-agent/react";
import type { MyAgent } from "@/agent";
function Chat({ sessionId }: { sessionId: string }) {
const { chat, agentStatus } = useAgent<MyAgent>("my-agent", {
id: sessionId,
});
return (
<div>
{agentStatus && <p>Status: {agentStatus.type}</p>}
{chat.messages.map((message) => (
<div key={message.id}>
<strong>{message.role}:</strong>
{message.parts.map((part, i) =>
part.type === "text" ? <p key={i}>{part.text}</p> : null
)}
</div>
))}
<form
onSubmit={(e) => {
e.preventDefault();
chat.sendMessage({
parts: [{ type: "text", text: "Hello agent" }],
});
}}
>
<button type="submit">Send</button>
</form>
</div>
);
}The first argument is the agent name — it must match the name passed to agent(). The hook uses it to derive the API base path (/api/agents/{name}).
Options
| Option | Type | Description |
|---|---|---|
id | string | Session ID for the chat |
basePath | string | Override the default /api/agents/{name} path |
headers | HeadersInit | () => Promise<HeadersInit> | Headers sent with every request (e.g. auth tokens) |
onData | (data: DataEvent) => void | Callback for data events from the stream |
Return Value
| Property | Type | Description |
|---|---|---|
chat | UseChatReturn | The useChat return value — messages, sendMessage, status, etc. |
agentStatus | AgentStatus | null | Current agent status (thinking, sandbox-setup, needs-approval, etc.) |
With Auth Headers
Pass headers as a static object or an async function for dynamic tokens:
"use client";
import { useAgent } from "experimental-agent/react";
import type { MyAgent } from "@/agent";
function Chat({ sessionId }: { sessionId: string }) {
const { chat } = useAgent<MyAgent>("my-agent", {
id: sessionId,
headers: async () => ({
Authorization: `Bearer ${await getToken()}`,
}),
});
return <div>{/* ... */}</div>;
}What It Handles
- Send: Routes messages to
POST /api/agents/{name}/{sessionId} - Reconnect: Reconnects to
GET /api/agents/{name}/{sessionId}/reconnecton page reload - Interrupt-if-streaming: When sending a new message while streaming, automatically includes
interruptIfStreamingwith the last part for clean interruption - Status: Captures
data-statusevents from the stream and exposes them asagentStatus
useSessionHistory
Fetches session message history using TanStack Query. Pass sessionId: undefined to skip the fetch.
"use client";
import { useSessionHistory } from "experimental-agent/react";
import type { MyAgent } from "@/agent";
function SessionHistory({ sessionId }: { sessionId?: string }) {
const { data, isLoading } = useSessionHistory<MyAgent>({
sessionId,
basePath: "/api/agents/my-agent",
});
if (isLoading) return <p>Loading...</p>;
return (
<ul>
{data?.messages.map((message) => (
<li key={message.id}>
{message.role}: {message.parts.length} parts
</li>
))}
</ul>
);
}The hook includes a built-in QueryClient — no QueryClientProvider needed.
useInterruptSession
Stops a running session. Returns a TanStack Query mutation.
"use client";
import { useInterruptSession } from "experimental-agent/react";
import type { MyAgent } from "@/agent";
function StopButton({ sessionId }: { sessionId: string }) {
const interrupt = useInterruptSession<MyAgent>({
sessionId,
basePath: "/api/agents/my-agent",
});
return (
<button
onClick={() => interrupt.mutate({})}
disabled={interrupt.isPending}
>
Stop
</button>
);
}Custom Base Path
If your server uses a custom base path, pass it to the hooks:
"use client";
import { useAgent } from "experimental-agent/react";
import type { MyAgent } from "@/agent";
function Chat({ sessionId }: { sessionId: string }) {
const { chat } = useAgent<MyAgent>("my-agent", {
id: sessionId,
basePath: "/api/v2/chat",
});
return <div>{/* ... */}</div>;
}This must match the basePath passed to handleRequest on the server.
useAgent vs. useChat
useAgent is a thin wrapper around @ai-sdk/react's useChat. Use useAgent when:
- You're using
handleRequeston the server - You want automatic routing, reconnection, and interruption
- You want
AgentStatussurfaced as a React state
Use useChat directly when you need full control over the transport configuration. See Frontend Integration for the manual useChat setup.
Next Steps
- handleRequest — Server-side HTTP handler
- Frontend Integration — Manual
useChatsetup for full control - Streaming — Reconnection and status handling
- Approvals — Tool part states and resolution flow
Building API Routes
Backend integration patterns for any framework. Send and reconnect streams, load history, resolve approvals, interrupt runs, and manage sessions.
Custom Tools
Deep dive into building custom tools with ToolContext, sandbox execution, auth context, output schemas, and approval integration.