---
title: "Chats hooks"
description: "React hooks for Aeontel chats."
section: "Libraries"
group: "React hooks"
order: 408
---

## Hooks

### `useChat` `sse`

Chat with any chattable entity. Pass the entity's prefixed id —
`agt_…` for agents or `swm_…` for swarms. The API infers the entity
type from the prefix.

Thin wrapper around AI SDK's `useChat` — wires the Aeontel transport,
otherwise the API is identical to AI SDK's. Returns the AI SDK
`useChat` shape (`messages`, `sendMessage`, `status`, …) plus
`isStreaming` / `isReady` derivations. Manage your own input state
with `useState("")` — matches AI SDK v6 conventions.

**AI SDK v6 message shape — two field-name traps:**

1. `sendMessage({ text })` — **NOT** `sendMessage({ content })`. The
   AI SDK auto-wraps `text` into a `parts: [{ type: "text", text }]`
   array internally. Passing `content` (the OpenAI / Anthropic
   convention) bypasses that wrap, the request reaches the server
   with `parts: undefined`, and Zod validation 500s with
   `"expected array, received undefined" at path [0, "parts"]`.

2. Rendering an incoming message — read `msg.parts`, **NOT**
   `msg.content`. AI SDK v6 messages have a `parts` array (mixed
   text/function-call/function-result segments). `content` does not exist
   on the message shape.

   ```tsx
   msg.parts
     .filter((p) => p.type === "text")
     .map((p) => p.text)
     .join("");
   ```

Both traps share the same root cause: `content` is the standard field
name across OpenAI / Anthropic / older AI SDK versions, so the
intuitive shape is wrong. The error surfaces only at runtime; TS
accepts the wrong shape because the input/output types are broad.

```ts
useChat(id: ChatTargetId, options: UseChatOptions_ = {})
```

```ts
const { messages, sendMessage, isStreaming } = useChat("agt_...");
const [input, setInput] = useState("");

<input
  value={input}
  onChange={(e) => setInput(e.target.value)}
  onKeyDown={(e) => {
    if (e.key === "Enter" && !isStreaming && input.trim()) {
      sendMessage({ text: input });   // ✓ text — NOT content
      setInput("");
    }
  }}
/>

// NOT m.content — that field doesn't exist on AI SDK v6 messages.
{messages.map((m) => (
  <div key={m.id}>
    {m.parts.filter((p) => p.type === "text").map((p) => p.text)}
  </div>
))}
```

### `useKickoffChat` `composite`

Programmatic chat kickoff. Opens a thread between an agent and a
user, optionally seeded with a pre-baked opening message authored
by the agent. Wraps `client.chats.kickoff` and invalidates the
threads list cache on success so any open chat sidebar pops the
new thread immediately.

```ts
useKickoffChat(options?: MutationOpts<KickoffChatResponse, KickoffChatParams>)
```

**Types:** [KickoffChatResponse](/types/kickoff-chat-response) · [KickoffChatParams](/types/kickoff-chat-params)

```ts
const kickoff = useKickoffChat();
kickoff.mutate({
  workspaceId: "wsp_…",
  agentId: "agt_…",
  userId: "usr_…",
  message: "Hi, I noticed your last invoice is due — want help?",
});
```
