# Server-Side Chat

> Source: https://trigger.dev/docs/ai-chat/server-chat

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](https://trigger.dev/docs/ai-chat/reference#compatibility)
 and the [AI chat changelog](https://trigger.dev/docs/ai-chat/changelog)
 for details.

`AgentChat` lets you chat with agents from server-side code. It works inside tasks (agent-to-agent), request handlers, webhook processors, and scripts.

    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();
    

[​](https://trigger.dev/docs/ai-chat/server-chat#type-safe-client-data)

Type-safe client data
------------------------------------------------------------------------------------------------

Pass `typeof yourAgent` as a type parameter and `clientData` is automatically typed from the agent’s `withClientData` schema:

    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
    });
    

[​](https://trigger.dev/docs/ai-chat/server-chat#conversation-lifecycle)

Conversation lifecycle
--------------------------------------------------------------------------------------------------

Each `AgentChat` instance represents one conversation. The conversation ID is auto-generated or can be set explicitly:

    // 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}` });
    

### 

[​](https://trigger.dev/docs/ai-chat/server-chat#sending-messages)

Sending messages

`sendMessage()` triggers a new run on the first call, then reuses the same run for subsequent messages via input streams:

    // 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();
    

### 

[​](https://trigger.dev/docs/ai-chat/server-chat#preloading-optional)

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.

    await chat.preload();
    // Agent's onPreload hook fires now, before user types anything
    const stream = await chat.sendMessage("Hello");
    

### 

[​](https://trigger.dev/docs/ai-chat/server-chat#closing)

Closing

Signal the agent to exit its loop gracefully:

    await chat.close();
    

Without `close()`, the agent exits on its own when its idle/suspend timeout expires.

[​](https://trigger.dev/docs/ai-chat/server-chat#reading-responses)

Reading responses
----------------------------------------------------------------------------------------

`sendMessage()` returns a `ChatStream` — a typed wrapper around the response.

### 

[​](https://trigger.dev/docs/ai-chat/server-chat#get-the-full-text)

Get the full text

    const stream = await chat.sendMessage("What is Trigger.dev?");
    const text = await stream.text();
    

### 

[​](https://trigger.dev/docs/ai-chat/server-chat#get-structured-results)

Get structured results

    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)}`);
    }
    

### 

[​](https://trigger.dev/docs/ai-chat/server-chat#stream-chunks-in-real-time)

Stream chunks in real-time

    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}`);
      }
    }
    

[​](https://trigger.dev/docs/ai-chat/server-chat#stateless-request-handlers)

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.

    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 });
    }
    

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.

[​](https://trigger.dev/docs/ai-chat/server-chat#sub-agent-tool-pattern)

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:

    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](https://trigger.dev/docs/ai-chat/patterns/sub-agents)
 for the full pattern including multi-turn conversations, cleanup, and what the frontend sees.

[​](https://trigger.dev/docs/ai-chat/server-chat#additional-methods)

Additional methods
------------------------------------------------------------------------------------------

### 

[​](https://trigger.dev/docs/ai-chat/server-chat#steering)

Steering

Send a message during an active stream without interrupting it:

    await chat.steer("Focus on security issues specifically");
    

### 

[​](https://trigger.dev/docs/ai-chat/server-chat#stop-generation)

Stop generation

Abort the current `streamText` call without ending the run:

    await chat.stop();
    

### 

[​](https://trigger.dev/docs/ai-chat/server-chat#raw-messages)

Raw messages

For full control over the UIMessage shape:

    const rawStream = await chat.sendRaw([\
      {\
        id: "msg-1",\
        role: "user",\
        parts: [\
          { type: "text", text: "Hello" },\
          { type: "file", url: "https://...", mediaType: "image/png" },\
        ],\
      },\
    ]);
    

### 

[​](https://trigger.dev/docs/ai-chat/server-chat#reconnect)

Reconnect

Resume a stream subscription after a disconnect:

    const stream = await chat.reconnect();
    

[​](https://trigger.dev/docs/ai-chat/server-chat#agentchat-options)

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. |

[​](https://trigger.dev/docs/ai-chat/server-chat#chatstream-methods)

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 |

Was this page helpful?

YesNo

[Previous](https://trigger.dev/docs/ai-chat/frontend)
[SessionsA Session is a stateful execution of an agent, with two-way streaming and durable compute. A single Session can have multiple runs associated with it.\
\
Next](https://trigger.dev/docs/ai-chat/sessions)

Ctrl+I

Assistant

Responses are generated using AI and may contain mistakes.
