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