Guides

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-query

useAgent

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:

src/agent.ts
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:

components/chat.tsx
"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

OptionTypeDescription
idstringSession ID for the chat
basePathstringOverride the default /api/agents/{name} path
headersHeadersInit | () => Promise<HeadersInit>Headers sent with every request (e.g. auth tokens)
onData(data: DataEvent) => voidCallback for data events from the stream

Return Value

PropertyTypeDescription
chatUseChatReturnThe useChat return value — messages, sendMessage, status, etc.
agentStatusAgentStatus | nullCurrent 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}/reconnect on page reload
  • Interrupt-if-streaming: When sending a new message while streaming, automatically includes interruptIfStreaming with the last part for clean interruption
  • Status: Captures data-status events from the stream and exposes them as agentStatus

useSessionHistory

Fetches session message history using TanStack Query. Pass sessionId: undefined to skip the fetch.

components/history.tsx
"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.

components/stop-button.tsx
"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 handleRequest on the server
  • You want automatic routing, reconnection, and interruption
  • You want AgentStatus surfaced 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