> ## Documentation Index
> Fetch the complete documentation index at: https://trigger.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Server-Side Chat

> Use AgentChat to interact with chat agents from server-side code — tasks, webhooks, scripts, or other agents.

<Warning>
  The AI Agents and Prompts surface ships as part of the **v4.5 release candidate**. Install with `@trigger.dev/sdk@rc` (or pin `4.5.0-rc.0` or later) to use these features — they aren't yet on the latest stable, and APIs may still change before the 4.5.0 GA. See [supported AI SDK versions](/ai-chat/reference#compatibility) and the [AI chat changelog](/ai-chat/changelog) for details.
</Warning>

`AgentChat` lets you chat with agents from server-side code. It works inside tasks (agent-to-agent), request handlers, webhook processors, and scripts.

```ts theme={"theme":"css-variables"}
import { AgentChat } from "@trigger.dev/sdk/chat";

const chat = new AgentChat({ agent: "my-agent" });
const stream = await chat.sendMessage("Hello!");
const text = await stream.text();
await chat.close();
```

## Type-safe client data

Pass `typeof yourAgent` as a type parameter and `clientData` is automatically typed from the agent's `withClientData` schema:

```ts theme={"theme":"css-variables"}
import { AgentChat } from "@trigger.dev/sdk/chat";
import type { myAgent } from "./trigger/my-agent";

const chat = new AgentChat<typeof myAgent>({
  agent: "my-agent",
  clientData: { userId: "user_123" }, // ← typed from agent definition
});
```

## Conversation lifecycle

Each `AgentChat` instance represents one conversation. The conversation ID is auto-generated or can be set explicitly:

```ts theme={"theme":"css-variables"}
// Auto-generated ID
const chat = new AgentChat({ agent: "my-agent" });

// Explicit ID — useful for persistence or finding the run later
const chat = new AgentChat({ agent: "my-agent", id: `review-${prNumber}` });
```

### Sending messages

`sendMessage()` triggers a new run on the first call, then reuses the same run for subsequent messages via input streams:

```ts theme={"theme":"css-variables"}
// First message — triggers a new run
const stream1 = await chat.sendMessage("Review PR #42");
const review = await stream1.text();

// Follow-up — same run, agent has full context
const stream2 = await chat.sendMessage("Can you fix the main bug?");
const fix = await stream2.text();
```

### Preloading (optional)

If you want the agent to initialize before the first message (e.g., load data, authenticate), call `preload()`. This is optional — `sendMessage()` triggers the run automatically if needed.

```ts theme={"theme":"css-variables"}
await chat.preload();
// Agent's onPreload hook fires now, before user types anything
const stream = await chat.sendMessage("Hello");
```

### Closing

Signal the agent to exit its loop gracefully:

```ts theme={"theme":"css-variables"}
await chat.close();
```

Without `close()`, the agent exits on its own when its idle/suspend timeout expires.

## Reading responses

`sendMessage()` returns a `ChatStream` — a typed wrapper around the response.

### Get the full text

```ts theme={"theme":"css-variables"}
const stream = await chat.sendMessage("What is Trigger.dev?");
const text = await stream.text();
```

### Get structured results

```ts theme={"theme":"css-variables"}
const stream = await chat.sendMessage("Research this topic");
const { text, toolCalls, toolResults } = await stream.result();

for (const tc of toolCalls) {
  console.log(`Tool: ${tc.toolName}, Input: ${JSON.stringify(tc.input)}`);
}
```

### Stream chunks in real-time

```ts theme={"theme":"css-variables"}
const stream = await chat.sendMessage("Write a report");

for await (const chunk of stream) {
  if (chunk.type === "text-delta") {
    process.stdout.write(chunk.delta);
  }
  if (chunk.type === "tool-input-available") {
    console.log(`Using tool: ${chunk.toolName}`);
  }
}
```

## Stateless request handlers

In a stateless environment (HTTP handler, serverless function), you need to persist and restore the session across requests.

Each chat is backed by a durable Session row that outlives any single run. `AgentChat` exposes the persistable state via `chat.session` (the SSE resume cursor) and surfaces the current run id via the `onTriggered` callback for telemetry / dashboard linking.

```ts theme={"theme":"css-variables"}
import { AgentChat } from "@trigger.dev/sdk/chat";

export async function POST(req: Request) {
  const { chatId, message } = await req.json();
  const saved = await db.sessions.find({ chatId });

  const chat = new AgentChat({
    agent: "my-agent",
    id: chatId,
    // Restore from previous request — `lastEventId` is the SSE resume
    // cursor; the underlying Session is keyed on `chatId` so it's
    // implicit and durable.
    session: saved ? { lastEventId: saved.lastEventId } : undefined,
    // Useful for telemetry / dashboard linking. The `runId` is the
    // current run, which may change across continuations and upgrades.
    onTriggered: async ({ runId }) => {
      await db.sessions.upsert({ chatId, runId });
    },
    // Persist after each turn for stream resumption
    onTurnComplete: async ({ lastEventId }) => {
      await db.sessions.update({ chatId, lastEventId });
    },
  });

  const stream = await chat.sendMessage(message);
  const text = await stream.text();

  return Response.json({ text });
}
```

<Info>
  The Session row is the run manager — a chat that was active yesterday
  resumes against the same chatId today, even if the original run has
  long since exited. `AgentChat` (server-side) and `TriggerChatTransport`
  (browser) both rely on this: send a new message and the server
  triggers a fresh continuation run on the same session, carrying the
  conversation forward without losing history or identity.
</Info>

## Sub-agent tool pattern

`AgentChat` can be used inside an AI SDK tool to delegate work to a durable sub-agent. The sub-agent's response streams as preliminary tool results:

```ts theme={"theme":"css-variables"}
import { tool } from "ai";
import { AgentChat } from "@trigger.dev/sdk/chat";
import { z } from "zod";

const researchTool = tool({
  description: "Delegate research to a specialist agent.",
  inputSchema: z.object({ topic: z.string() }),
  execute: async function* ({ topic }, { abortSignal }) {
    const chat = new AgentChat({ agent: "research-agent" });
    const stream = await chat.sendMessage(topic, { abortSignal });
    yield* stream.messages();
    await chat.close();
  },
  toModelOutput: ({ output: message }) => {
    const lastText = message?.parts?.findLast(
      (p: { type: string }) => p.type === "text"
    ) as { text?: string } | undefined;
    return { type: "text", value: lastText?.text ?? "Done." };
  },
});
```

This supports single-turn delegation, multi-turn LLM-driven conversations with persistent sub-agents, and cross-turn state that survives snapshot/restore.

See the [Sub-Agents guide](/ai-chat/patterns/sub-agents) for the full pattern including multi-turn conversations, cleanup, and what the frontend sees.

## Additional methods

### Steering

Send a message during an active stream without interrupting it:

```ts theme={"theme":"css-variables"}
await chat.steer("Focus on security issues specifically");
```

### Stop generation

Abort the current `streamText` call without ending the run:

```ts theme={"theme":"css-variables"}
await chat.stop();
```

### Raw messages

For full control over the UIMessage shape:

```ts theme={"theme":"css-variables"}
const rawStream = await chat.sendRaw([
  {
    id: "msg-1",
    role: "user",
    parts: [
      { type: "text", text: "Hello" },
      { type: "file", url: "https://...", mediaType: "image/png" },
    ],
  },
]);
```

### Reconnect

Resume a stream subscription after a disconnect:

```ts theme={"theme":"css-variables"}
const stream = await chat.reconnect();
```

## AgentChat options

| Option                 | Type                                                                                                      | Default                    | Description                                                                                                                                                                                                                                                               |
| ---------------------- | --------------------------------------------------------------------------------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `agent`                | `string`                                                                                                  | required                   | The agent task ID to trigger                                                                                                                                                                                                                                              |
| `id`                   | `string`                                                                                                  | `crypto.randomUUID()`      | Conversation ID for tagging and correlation                                                                                                                                                                                                                               |
| `clientData`           | typed from agent                                                                                          | `undefined`                | Client data included in every request                                                                                                                                                                                                                                     |
| `session`              | `ChatSession` (`{ lastEventId?: string }`)                                                                | `undefined`                | Restore a previous session's SSE resume cursor. The Session row itself is keyed on `chatId` (durable) — no other state to thread.                                                                                                                                         |
| `onTriggered`          | `(event) => void`                                                                                         | `undefined`                | Called when a new run is created                                                                                                                                                                                                                                          |
| `onTurnComplete`       | `(event) => void`                                                                                         | `undefined`                | Called when a turn's stream ends                                                                                                                                                                                                                                          |
| `streamTimeoutSeconds` | `number`                                                                                                  | `120`                      | SSE timeout in seconds                                                                                                                                                                                                                                                    |
| `triggerConfig`        | `SessionTriggerConfig`                                                                                    | `undefined`                | Tags, queue, machine, `maxAttempts`, `idleTimeoutInSeconds`, `basePayload` — folded into `sessions.start({...})`                                                                                                                                                          |
| `baseURL`              | `string \| (ctx: { endpoint: "in" \| "out"; chatId: string }) => string`                                  | `apiClientManager.baseURL` | API base URL. String form applies to every endpoint; function form picks per endpoint — useful for routing `.in/append` through an edge proxy while keeping `.out` SSE direct. Defaults to whatever `@trigger.dev/sdk` was configured with (typically `TRIGGER_API_URL`). |
| `fetch`                | `(url: string, init: RequestInit, ctx: { endpoint: "in" \| "out"; chatId: string }) => Promise<Response>` | `undefined`                | Per-request fetch override. Invoked for both `.in/append` POSTs and the `.out` SSE GET. Use for header injection, custom retries, or proxy rewrites.                                                                                                                      |

## ChatStream methods

| Method                   | Returns                          | Description                                               |
| ------------------------ | -------------------------------- | --------------------------------------------------------- |
| `text()`                 | `Promise<string>`                | Consume stream, return accumulated text                   |
| `result()`               | `Promise<ChatStreamResult>`      | Consume stream, return `{ text, toolCalls, toolResults }` |
| `messages()`             | `AsyncGenerator<UIMessage>`      | Yield accumulated UIMessage snapshots (sub-agent pattern) |
| `[Symbol.asyncIterator]` | `UIMessageChunk`                 | Iterate over typed stream chunks                          |
| `.stream`                | `ReadableStream<UIMessageChunk>` | Raw stream for AI SDK utilities                           |
