---
description: "The chat as a useChat-shaped React hook: the conversation as AI SDK UIMessages, the adapter choice and its memory, the adapter's status and models, and the suggestions."
---

# `useAgentChat`

The chat as a `useChat`-shaped React hook: the conversation as AI SDK `UIMessage`s, the adapter choice and its memory, the adapter's status and models, and the suggestions. Use it to render the chat with your own components (shadcn/ui's AI patterns, or anything that reads `UIMessage` parts). [`<AgentChat>`](/docs/reference/chat/agent-chat) is built on it, so a custom UI and the built-in widget agree on every state.

## Import

```ts
import { useAgentChat } from "@webmcp-agents/chat";
import type {
  AgentChatState,
  ToolPhase,
  UseAgentChatOptions,
} from "@webmcp-agents/chat";
```

## Example

A minimal chat: text parts, a tool call that says when it is parked on the page for the person's click, the starter prompts, and Stop and Try again.

```tsx title="components/my-chat.tsx"
import { useAgentChat } from "@webmcp-agents/chat";

import { shopper } from "@/agents";
import { Button } from "@/components/ui/button";

// Under <WebAgentsProvider>, so the agent's tools are on the page.
export function MyChat() {
  const chat = useAgentChat({ agent: shopper });

  return (
    <div className="flex flex-col gap-3">
      {chat.messages.map((message) => (
        <div key={message.id}>
          <p>
            {message.parts
              .map((part) => (part.type === "text" ? part.text : ""))
              .join("")}
          </p>
          {message.parts.map((part) => {
            if (part.type !== "dynamic-tool") return null;

            const phase = chat.toolPhase(part);
            return (
              <p key={part.toolCallId}>
                {phase?.phase === "parked"
                  ? `Confirm on the page: ${phase.prompt}`
                  : `${part.toolName}: ${phase?.phase}`}
              </p>
            );
          })}
        </div>
      ))}

      {chat.suggestions.map((text) => (
        <Button
          key={text}
          variant="outline"
          onClick={() => chat.sendMessage({ text })}
        >
          {text}
        </Button>
      ))}

      {chat.status === "streaming" && (
        <Button onClick={chat.stop}>Stop</Button>
      )}
      {chat.status === "error" && (
        <Button onClick={chat.regenerate}>Try again</Button>
      )}
    </div>
  );
}
```

The `agent-chat` block of the `@webmcp-agents` registry is a complete chat built this way: see [Use your shadcn components](/docs/more/shadcn-chat-blocks).

## Options

`useAgentChat(options?)` takes a `UseAgentChatOptions`; every option is optional.

| Name | Type | Default | Description |
|---|---|---|---|
| `adapter` | `AgentAdapter` | one shared `createCloudAdapter()` | The one adapter that runs the model. With neither `adapter` nor `adapters`, the chat uses the shared cloud adapter (your `/api/chat`) that [`<AgentChat>`](/docs/reference/chat/agent-chat), `agent.invoke` and [`useWebAgent`](/docs/reference/chat/use-web-agent) use. |
| `adapters` | `readonly AgentAdapter[]` | not set | Several adapters (cloud, on-device…) for a switcher. The first is the default; the person's choice is remembered in localStorage (`web-agents:chat:adapter`) while that adapter is still offered. |
| `agent` | `WebAgent` | not set | The page's agent ([`defineWebAgent`](/docs/reference/chat/define-web-agent)): its instructions, the tools it may use and its sub-agents. Without it the model sees every page tool and no instructions. |
| `storageKey` | `string \| false` | `"web-agents:chat"` | sessionStorage key for the conversation, so it survives route changes and reloads. `false` keeps it in memory only. |

## Returns

An `AgentChatState`:

| Name | Type | Description |
|---|---|---|
| `messages` | `UIMessage[]` | The conversation for rendering. `text` and `reasoning` parts carry `state` (`streaming` on the last part while the run streams); tool calls are `dynamic-tool` parts with `input-available` (running or parked), `output-available` or `output-error` (`errorText`; a cancelled call carries `Error [cancelled]: …`). Messages left empty are kept. |
| `status` | `"submitted" \| "streaming" \| "ready" \| "error"` | AI SDK `ChatStatus` names: `submitted` until the reply's first part, then `streaming`, then `ready`; `error` after a failed run. |
| `error` | `SessionError` | The last run's failure: `{ message, code? }`. |
| `sendMessage({ text })` | `(input: { text: string }) => void` | Send a message. Ignored when empty or while streaming. |
| `stop()` | `() => void` | Stop the run; open tool calls become `cancelled`. |
| `regenerate()` | `() => void` | Resume the interrupted turn from its last finished tool call, so nothing is done twice. |
| `clear()` | `() => void` | Stop and start a new conversation. |
| `tools` | `ToolDescriptor[]` | What the model sees right now: the page's tools through the agent's filter, plus its delegate tools. |
| `suggestions` | `string[]` | Starter prompts derived from the page's tools. |
| `adapters` | `readonly AgentAdapter[]` | The adapters on offer. |
| `adapter` | `AgentAdapter` | The one in use. |
| `selectAdapter(id)` | `(id: string) => void` | Switch adapter and remember the choice. |
| `adapterStatus` | `AdapterStatus` | Whether the adapter is ready: see [`AdapterStatus`](/docs/reference/chat/adapter-types#adapterstatus). |
| `models` | `readonly ModelChoice[]` | The current adapter's models, when it offers any (otherwise empty). |
| `selectModel(id)` | `(id: string) => Promise<void>` | Switch the adapter's model. Absent when the adapter has none to choose. |
| `prepare(signal)` | `(signal?: AbortSignal) => Promise<void>` | Start the adapter's opt-in setup, such as a model download; aborting `signal` cancels it. Absent when the adapter needs none. |
| `agent` | `WebAgent \| undefined` | The agent in use. |
| `toolPhase(part)` | `(part: UIMessage["parts"][number]) => ToolPhase \| undefined` | The web-agents state of a tool part: `phase` is `"parked"` (waiting for the person's click on the page, with `prompt` to show), `"running"`, `"done"`, `"error"` or `"cancelled"`; for a `delegate_to_<id>` call, `agent` holds the sub-agent's id and its nested run as `UIMessage`s. `undefined` for parts that are not tool calls. |
| `session` | `AgentSession` | The underlying [`AgentSession`](/docs/reference/chat/use-agent-session), for UIs that render the chat's own message shape. |

## Notes

- The `UIMessage` shape is canonical: nothing web-agents-specific is added to parts. `toolPhase` is the one extra.
- Reasoning parts keep their `providerMetadata` (signed thinking); the hook renders what the session has and never mutates it.
- Create custom adapters once, at module scope: the session and the status hook key on the adapter object.
- Render it under `<WebAgentsProvider>` (or any page with `document.modelContext`); without WebMCP `tools` is empty.

## See also

- [Add the chat](/docs/build/add-the-chat) and [Use your shadcn components](/docs/more/shadcn-chat-blocks)
- [`<AgentChat>`](/docs/reference/chat/agent-chat), the built-in widget
- [`useAgentSession`](/docs/reference/chat/use-agent-session), the same session in the chat's own message shape
- [`useWebAgent`](/docs/reference/chat/use-web-agent), to run an agent with no chat
- [Adapter types](/docs/reference/chat/adapter-types)
