---
title: "API Reference"
sidebarTitle: "API Reference"
description: "Complete API reference for the AI Agents SDK — backend options, events, frontend transport, and hooks."
---

## Compatibility

| Dependency | Supported | Notes |
|---|---|---|
| `@trigger.dev/sdk` | `>=4.5.0` | The chat agent surface lives in this SDK release. Install with `@trigger.dev/sdk@latest`. |
| `ai` (Vercel AI SDK) | `^5.0.0 \|\| ^6.0.0 \|\| >=7.0.0-canary <8` | Declared as a peer. v6 is what we develop against day to day; v5 and v7 work too (v7 is in canary/beta upstream). Your installed `ai` major drives the chat surface's types. |
| `@ai-sdk/otel` | `1.x` (v7 only) | Optional. AI SDK 7 moved model-call span emission out of `ai` core into this adapter. Install it alongside `ai@7` and the SDK auto-registers it, so your model calls show up as spans in the run trace. Not needed on v5/v6, where `ai` core emits spans. See [AI SDK 7 telemetry](#ai-sdk-7-telemetry) below. |
| `@ai-sdk/react` | matches your `ai` major | Pulled in by `useChat`. The transport works with whichever React hook ships in the same major as your `ai` version. |
| `react` | `^18.0 \|\| ^19.0` | Required only if you use `@trigger.dev/sdk/chat/react` (the frontend transport). Server-only consumers can skip React entirely. |
| Node.js | `>=18.20.0` | The SDK's engine constraint. The chat agent itself works on any version the SDK supports. |
| Provider packages (`@ai-sdk/openai`, `@ai-sdk/anthropic`, etc.) | versions that target your `ai` major | Pick a provider package whose `ai` peer matches yours. The chat agent doesn't depend on any specific provider — pass whatever model you want into `streamText`. |

The `ai` peer is **optional** — server-only setups that don't call `streamText` (raw `task()` with chat primitives) can skip the AI SDK entirely.

### AI SDK 7 telemetry

On **AI SDK 7**, model-call spans are emitted by `@ai-sdk/otel` rather than `ai` core. Install it alongside `ai@7`:

```bash
npm install @ai-sdk/otel
```

The SDK registers it once per worker at chat agent boot, so the `experimental_telemetry` config wired up by `chat.toStreamTextOptions()` keeps producing spans in your run trace with no extra setup. On v5 and v6 nothing changes: `ai` core emits the spans and `@ai-sdk/otel` isn't needed.

If you (or a library you import) already register `@ai-sdk/otel` yourself, the SDK detects the existing integration and skips its own registration, so you won't get duplicate spans. To opt out of the auto-registration entirely, set `TRIGGER_AI_SDK_OTEL_AUTOREGISTER=0`.

## ChatAgentOptions

Options for `chat.agent()`.

| Option                        | Type                                                        | Default                        | Description                                                                                         |
| ----------------------------- | ----------------------------------------------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------- |
| `id`                          | `string`                                                    | required                       | Task identifier                                                                                     |
| `run`                         | `(payload: ChatTaskRunPayload) => Promise<unknown>`         | required                       | Handler for each turn                                                                               |
| `clientDataSchema`            | `TaskSchema`                                                | —                              | Schema for validating and typing `clientData`                                                       |
| `onBoot`                      | `(event: BootEvent) => Promise<void> \| void`               | —                              | Fires once per worker process — initial, preloaded, AND reactive continuation. Use for `chat.local` init and per-process resources. See [onBoot](/ai-chat/lifecycle-hooks#onboot). |
| `onRecoveryBoot`              | `(event: RecoveryBootEvent) => Promise<RecoveryBootResult \| void> \| RecoveryBootResult \| void` | — | Fires on a continuation boot when the dead predecessor left recovered state (partial assistant or in-flight users). Override the smart default — drop partial, synthesize tool results, emit a recovery banner. See [Recovery boot](/ai-chat/patterns/recovery-boot). |
| `onPreload`                   | `(event: PreloadEvent) => Promise<void> \| void`            | —                              | Fires on preloaded runs before the first message                                                    |
| `onChatStart`                 | `(event: ChatStartEvent) => Promise<void> \| void`          | —                              | Fires once per chat, on the very first user message. Does NOT fire on continuation runs or OOM-retries — see [onChatStart](/ai-chat/lifecycle-hooks#onchatstart). |
| `onValidateMessages`          | `(event: ValidateMessagesEvent) => UIMessage[] \| Promise<UIMessage[]>` | —                | Validate/transform UIMessages before model conversion. See [onValidateMessages](/ai-chat/lifecycle-hooks#onvalidatemessages) |
| `hydrateMessages`             | `(event: HydrateMessagesEvent) => UIMessage[] \| Promise<UIMessage[]>` | —                 | Load message history from backend, replacing the linear accumulator. See [hydrateMessages](/ai-chat/lifecycle-hooks#hydratemessages) |
| `actionSchema`                | `TaskSchema`                                                | —                              | Schema for validating custom actions sent via `transport.sendAction()`. See [Actions](/ai-chat/actions) |
| `onAction`                    | `(event: ActionEvent) => Promise<unknown> \| unknown`       | —                              | Handle custom actions. Actions are not turns — only `hydrateMessages` + `onAction` fire. Return a `StreamTextResult` (or `string` / `UIMessage`) for a model response; return `void` for side-effect-only. See [Actions](/ai-chat/actions) |
| `onTurnStart`                 | `(event: TurnStartEvent) => Promise<void> \| void`          | —                              | Fires every turn before `run()`                                                                     |
| `onBeforeTurnComplete`        | `(event: BeforeTurnCompleteEvent) => Promise<void> \| void` | —                              | Fires after response but before stream closes. Includes `writer`.                                   |
| `onTurnComplete`              | `(event: TurnCompleteEvent) => Promise<void> \| void`       | —                              | Fires after each turn completes (stream closed)                                                     |
| `onCompacted`                 | `(event: CompactedEvent) => Promise<void> \| void`          | —                              | Fires when compaction occurs. Includes `writer`. See [Compaction](/ai-chat/compaction)              |
| `compaction`                  | `ChatAgentCompactionOptions`                                | —                              | Automatic context compaction. See [Compaction](/ai-chat/compaction)                                 |
| `pendingMessages`             | `PendingMessagesOptions`                                    | —                              | Mid-execution message injection. See [Pending Messages](/ai-chat/pending-messages)                  |
| `prepareMessages`             | `(event: PrepareMessagesEvent) => ModelMessage[]`           | —                              | Transform model messages before use (cache breaks, context injection, etc.)                         |
| `tools`                       | `ToolSet \| ((event: ResolveToolsEvent) => ToolSet \| Promise<ToolSet>)` | —                 | Tools for this agent. Threads each tool's `toModelOutput` through cross-turn history re-conversion, and hands the resolved set back on the run payload. Static set or per-turn function. See [Tools](/ai-chat/tools). |
| `maxTurns`                    | `number`                                                    | `100`                          | Max conversational turns per run                                                                    |
| `turnTimeout`                 | `string`                                                    | `"1h"`                         | How long to wait for next message                                                                   |
| `idleTimeoutInSeconds`        | `number`                                                    | `30`                           | Seconds to stay idle before suspending                                                              |
| `chatAccessTokenTTL`          | `string`                                                    | `"1h"`                         | How long the scoped access token remains valid                                                      |
| `preloadIdleTimeoutInSeconds` | `number`                                                    | Same as `idleTimeoutInSeconds` | Idle timeout after `onPreload` fires                                                                |
| `preloadTimeout`              | `string`                                                    | Same as `turnTimeout`          | Suspend timeout for preloaded runs                                                                  |
| `uiMessageStreamOptions`      | `ChatUIMessageStreamOptions`                                | —                              | Default options for `toUIMessageStream()`. Per-turn override via `chat.setUIMessageStreamOptions()` |
| `onChatSuspend`               | `(event: ChatSuspendEvent) => Promise<void> \| void`        | —                              | Fires right before the run suspends. See [onChatSuspend](/ai-chat/lifecycle-hooks#onchatsuspend--onchatresume) |
| `onChatResume`                | `(event: ChatResumeEvent) => Promise<void> \| void`         | —                              | Fires right after the run resumes from suspension                                                   |
| `exitAfterPreloadIdle`        | `boolean`                                                   | `false`                        | Exit run after preload idle timeout instead of suspending. See [exitAfterPreloadIdle](/ai-chat/lifecycle-hooks#exitafterpreloadidle) |
| `oomMachine`                  | `MachinePresetName`                                         | —                              | Fallback machine when an attempt fails with OOM. Setting it enables a single OOM retry on the larger machine. See [OOM resilience](/ai-chat/patterns/oom-resilience) |

Plus most standard [TaskOptions](/tasks/overview) — `queue`, `machine`, `maxDuration`, **`onWait`**, **`onResume`**, **`onComplete`**, and other lifecycle hooks. Generic `retry` is **not** exposed on `chat.agent`; use `oomMachine` for OOM recovery, or drop down to a raw [`task()`](/ai-chat/custom-agents) if you need richer retry semantics. Standard hooks use the same parameter shapes as on a normal `task()` (including `ctx`).

## Task context (`ctx`)

All **`chat.agent`** lifecycle events (**`onBoot`**, **`onPreload`**, **`onChatStart`**, **`onTurnStart`**, **`onBeforeTurnComplete`**, **`onTurnComplete`**, **`onCompacted`**) and the object passed to **`run`** include **`ctx`**: the same **`TaskRunContext`** shape as the `ctx` in `task({ run: (payload, { ctx }) => ... })`.

<Note>
  **`onValidateMessages`** does not include `ctx` — it fires before message accumulation and is designed for pure validation/transformation of incoming messages.
</Note>

Use **`ctx`** for run metadata, tags, parent links, or any API that needs the full run record. The chat-specific string **`runId`** on events is always **`ctx.run.id`**; both are provided for convenience.

```ts
import type { TaskRunContext } from "@trigger.dev/sdk";
// Equivalent alias (same type):
import type { Context } from "@trigger.dev/sdk";
```

<Note>
  Prefer `import type { TaskRunContext } from "@trigger.dev/sdk"` in application code. Do not depend on `@trigger.dev/core` directly.
</Note>

## ChatTaskRunPayload

The payload passed to the `run` function.

| Field          | Type                                       | Description                                                          |
| -------------- | ------------------------------------------ | -------------------------------------------------------------------- |
| `ctx`          | `TaskRunContext`                           | Full task run context — same as `task` `run`’s `{ ctx }`             |
| `messages`     | `ModelMessage[]`                           | Model-ready messages — pass directly to `streamText`                 |
| `tools`        | `ToolSet`                                  | Resolved tools declared on the agent config (empty object when none). Pass straight to `streamText`. See [Tools](/ai-chat/tools). |
| `chatId`       | `string`                                   | Your conversation ID (the session's `externalId`)                    |
| `sessionId`    | `string`                                   | Friendly ID of the backing Session (`session_*`). Use with `sessions.open()` for advanced cases. Always set — every chat.agent run is bound to a Session. |
| `trigger`      | `"submit-message" \| "regenerate-message"` | What triggered the request                                           |
| `messageId`    | `string \| undefined`                      | Message ID (for regenerate)                                          |
| `clientData`   | Typed by `clientDataSchema`                | Custom data from the frontend (typed when schema is provided)        |
| `continuation` | `boolean`                                  | Whether this run is continuing an existing chat (previous run ended) |
| `signal`       | `AbortSignal`                              | Combined stop + cancel signal                                        |
| `cancelSignal` | `AbortSignal`                              | Cancel-only signal                                                   |
| `stopSignal`   | `AbortSignal`                              | Stop-only signal (per-turn)                                          |
| `previousTurnUsage` | `LanguageModelUsage \| undefined`       | Token usage from the previous turn (undefined on turn 0)        |
| `totalUsage`   | `LanguageModelUsage`                       | Cumulative token usage across completed turns so far              |

## BootEvent

Passed to the `onBoot` callback.

| Field             | Type                        | Description                                                                                          |
| ----------------- | --------------------------- | ---------------------------------------------------------------------------------------------------- |
| `ctx`             | `TaskRunContext`            | Full task run context — see [Task context](#task-context-ctx)                                         |
| `chatId`          | `string`                    | Chat session ID                                                                                      |
| `runId`           | `string`                    | The Trigger.dev run ID for this run boot                                                             |
| `chatAccessToken` | `string`                    | Scoped access token for this run                                                                     |
| `clientData`      | Typed by `clientDataSchema` | Custom data from the frontend                                                                        |
| `continuation`    | `boolean`                   | `true` when this run is taking over from a prior dead run (cancel / crash / `endRun` / OOM retry)    |
| `previousRunId`   | `string \| undefined`       | Public id of the prior run when `continuation` is true                                               |
| `preloaded`       | `boolean`                   | Whether this run was triggered as a preload                                                          |

## RecoveryBootEvent

Passed to the `onRecoveryBoot` callback. See [Recovery boot](/ai-chat/patterns/recovery-boot) for the full guide.

| Field              | Type                                                              | Description                                                                                          |
| ------------------ | ----------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `ctx`              | `TaskRunContext`                                                  | Full task run context — see [Task context](#task-context-ctx)                                         |
| `chatId`           | `string`                                                          | Chat session ID                                                                                      |
| `runId`            | `string`                                                          | The Trigger.dev run ID for this run boot                                                             |
| `previousRunId`    | `string`                                                          | Public id of the prior run that died                                                                 |
| `cause`            | `"cancelled" \| "crashed" \| "unknown"`                           | Best-effort cause. Currently always `"unknown"` — forward-looking, don't branch on it                |
| `settledMessages`  | `TUIMessage[]`                                                    | Chain persisted by the predecessor's last `onTurnComplete`                                            |
| `inFlightUsers`    | `TUIMessage[]`                                                    | User messages on `session.in` past the cursor — the message(s) the predecessor never acknowledged    |
| `partialAssistant` | `TUIMessage \| undefined`                                         | The trailing assistant message whose stream never received `finish`                                  |
| `pendingToolCalls` | [`RecoveryPendingToolCall[]`](#recoverypendingtoolcall)           | Tool calls in `input-available` state extracted from `partialAssistant`                              |
| `writer`           | [`ChatWriter`](#chatwriter)                                       | Lazy session.out writer — emit a recovery banner / signal here                                       |

## RecoveryBootResult

Return value of `onRecoveryBoot`. Every field is optional — omit to accept the smart default.

| Field            | Type                          | Description                                                                                                                                   |
| ---------------- | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `chain`          | `TUIMessage[]`                | Replaces the seed chain. Default: `[...settledMessages, firstInFlightUser, partialAssistant]` when both present; `settledMessages` otherwise. |
| `recoveredTurns` | `TUIMessage[]`                | User messages to dispatch as fresh turns. Default: `inFlightUsers.slice(1)` when smart-default fires; `inFlightUsers` otherwise.              |
| `beforeBoot`     | `() => Promise<void>`         | Runs after the writer flushes and before the first recovered turn fires. Use for blocking persistence work.                                   |

## RecoveryPendingToolCall

| Field         | Type      | Description                                                  |
| ------------- | --------- | ------------------------------------------------------------ |
| `toolCallId`  | `string`  | The AI SDK tool call id                                      |
| `toolName`    | `string`  | The tool name (the `tool-${name}` suffix on the part type)   |
| `input`       | `unknown` | The input the model produced for the call                    |
| `partIndex`   | `number`  | Index into `partialAssistant.parts` for in-place edits       |

## PreloadEvent

Passed to the `onPreload` callback.

| Field             | Type                        | Description                                                    |
| ----------------- | --------------------------- | -------------------------------------------------------------- |
| `ctx`             | `TaskRunContext`            | Full task run context — see [Task context](#task-context-ctx)   |
| `chatId`          | `string`                    | Chat session ID                                                |
| `runId`           | `string`                    | The Trigger.dev run ID                                         |
| `chatAccessToken` | `string`                    | Scoped access token for this run                               |
| `clientData`      | Typed by `clientDataSchema` | Custom data from the frontend                                  |
| `writer`          | [`ChatWriter`](#chatwriter) | Stream writer for custom chunks. Lazy — no overhead if unused. |

## ChatStartEvent

Passed to the `onChatStart` callback.

| Field             | Type                        | Description                                                    |
| ----------------- | --------------------------- | -------------------------------------------------------------- |
| `ctx`             | `TaskRunContext`            | Full task run context — see [Task context](#task-context-ctx)   |
| `chatId`          | `string`                    | Chat session ID                                                |
| `messages`        | `ModelMessage[]`            | Initial model-ready messages                                   |
| `clientData`      | Typed by `clientDataSchema` | Custom data from the frontend                                  |
| `runId`           | `string`                    | The Trigger.dev run ID                                         |
| `chatAccessToken` | `string`                    | Scoped access token for this run                               |
| `continuation`    | `boolean`                   | Whether this run is continuing an existing chat                |
| `previousRunId`   | `string \| undefined`       | Previous run ID (only when `continuation` is true)             |
| `preloaded`       | `boolean`                   | Whether this run was preloaded before the first message        |
| `writer`          | [`ChatWriter`](#chatwriter) | Stream writer for custom chunks. Lazy — no overhead if unused. |

## ValidateMessagesEvent

Passed to the `onValidateMessages` callback.

| Field     | Type                                                            | Description                              |
| --------- | --------------------------------------------------------------- | ---------------------------------------- |
| `messages` | `UIMessage[]`                                                  | Incoming UI messages for this turn       |
| `chatId`  | `string`                                                        | Chat session ID                          |
| `turn`    | `number`                                                        | Turn number (0-indexed)                  |
| `trigger` | `"submit-message" \| "regenerate-message" \| "preload" \| "close"` | The trigger type for this turn        |

## ResolveToolsEvent

Passed to the `tools` function form on `chat.agent`, once per turn, to resolve the tool set for that turn. See [Tools](/ai-chat/tools#static-or-per-turn-tools).

| Field          | Type                        | Description                                       |
| -------------- | --------------------------- | ------------------------------------------------- |
| `chatId`       | `string`                    | Chat session ID                                   |
| `turn`         | `number`                    | Turn number (0-indexed)                           |
| `continuation` | `boolean`                   | Whether this run is continuing an existing chat   |
| `clientData`   | Typed by `clientDataSchema` | Custom data from the frontend                     |

## HydrateMessagesEvent

Passed to the `hydrateMessages` callback. See [hydrateMessages](/ai-chat/lifecycle-hooks#hydratemessages).

| Field              | Type                                                  | Description                                               |
| ------------------ | ----------------------------------------------------- | --------------------------------------------------------- |
| `chatId`           | `string`                                              | Chat session ID                                           |
| `turn`             | `number`                                              | Turn number (0-indexed)                                   |
| `trigger`          | `"submit-message" \| "regenerate-message" \| "action"` | The trigger type for this turn                           |
| `incomingMessages` | `UIMessage[]`                                         | Validated wire messages from the frontend (empty for actions) |
| `previousMessages` | `UIMessage[]`                                         | Accumulated UI messages before this turn (`[]` on turn 0) |
| `clientData`       | Typed by `clientDataSchema`                           | Custom data from the frontend                             |
| `continuation`     | `boolean`                                             | Whether this run is continuing an existing chat           |
| `previousRunId`    | `string \| undefined`                                 | Previous run ID (only when `continuation` is true)        |

## ActionEvent

Passed to the `onAction` callback. See [Actions](/ai-chat/actions).

| Field        | Type                        | Description                                              |
| ------------ | --------------------------- | -------------------------------------------------------- |
| `action`     | Typed by `actionSchema`     | The parsed and validated action payload                  |
| `chatId`     | `string`                    | Chat session ID                                          |
| `turn`       | `number`                    | Turn number (0-indexed)                                  |
| `clientData` | Typed by `clientDataSchema` | Custom data from the frontend                            |
| `uiMessages` | `UIMessage[]`               | Accumulated UI messages (after hydration, if set)        |
| `messages`   | `ModelMessage[]`            | Accumulated model messages (after hydration, if set)     |

## TurnStartEvent

Passed to the `onTurnStart` callback.

| Field             | Type                        | Description                                                    |
| ----------------- | --------------------------- | -------------------------------------------------------------- |
| `ctx`             | `TaskRunContext`            | Full task run context — see [Task context](#task-context-ctx)   |
| `chatId`          | `string`                    | Chat session ID                                                |
| `messages`        | `ModelMessage[]`            | Full accumulated conversation (model format)                   |
| `uiMessages`      | `UIMessage[]`               | Full accumulated conversation (UI format)                      |
| `turn`            | `number`                    | Turn number (0-indexed)                                        |
| `runId`           | `string`                    | The Trigger.dev run ID                                         |
| `chatAccessToken` | `string`                    | Scoped access token for this run                               |
| `clientData`      | Typed by `clientDataSchema` | Custom data from the frontend                                  |
| `continuation`    | `boolean`                   | Whether this run is continuing an existing chat                |
| `previousRunId`   | `string \| undefined`       | Previous run ID (only when `continuation` is true)             |
| `preloaded`       | `boolean`                   | Whether this run was preloaded                                 |
| `writer`          | [`ChatWriter`](#chatwriter) | Stream writer for custom chunks. Lazy — no overhead if unused. |

## TurnCompleteEvent

Passed to the `onTurnComplete` callback.

| Field                | Type                              | Description                                          |
| -------------------- | --------------------------------- | ---------------------------------------------------- |
| `ctx`                | `TaskRunContext`                  | Full task run context — see [Task context](#task-context-ctx) |
| `chatId`             | `string`                          | Chat session ID                                      |
| `messages`           | `ModelMessage[]`                  | Full accumulated conversation (model format)         |
| `uiMessages`         | `UIMessage[]`                     | Full accumulated conversation (UI format)            |
| `newMessages`        | `ModelMessage[]`                  | Only this turn's messages (model format)             |
| `newUIMessages`      | `UIMessage[]`                     | Only this turn's messages (UI format)                |
| `responseMessage`    | `UIMessage \| undefined`          | The assistant's response for this turn               |
| `rawResponseMessage` | `UIMessage \| undefined`          | Raw response before abort cleanup                    |
| `turn`               | `number`                          | Turn number (0-indexed)                              |
| `runId`              | `string`                          | The Trigger.dev run ID                               |
| `chatAccessToken`    | `string`                          | Scoped access token for this run                     |
| `lastEventId`        | `string \| undefined`             | Stream position for resumption                       |
| `stopped`            | `boolean`                         | Whether the user stopped generation during this turn |
| `continuation`       | `boolean`                         | Whether this run is continuing an existing chat      |
| `usage`              | `LanguageModelUsage \| undefined` | Token usage for this turn                            |
| `totalUsage`         | `LanguageModelUsage`              | Cumulative token usage across all turns              |
| `finishReason`       | `FinishReason \| undefined`       | Why the LLM stopped (`"stop"`, `"tool-calls"`, `"error"`, …) |
| `error`              | `unknown`                         | Set when the turn threw; `responseMessage` is then undefined or partial |

## BeforeTurnCompleteEvent

Passed to the `onBeforeTurnComplete` callback. Same fields as `TurnCompleteEvent` (including **`ctx`**) plus a `writer`.

| Field                            | Type                        | Description                                                                   |
| -------------------------------- | --------------------------- | ----------------------------------------------------------------------------- |
| _(all TurnCompleteEvent fields)_ |                             | See [TurnCompleteEvent](#turncompleteevent) (includes `ctx`)                  |
| `writer`                         | [`ChatWriter`](#chatwriter) | Stream writer — the stream is still open so chunks appear in the current turn |

## ChatSuspendEvent

Passed to the `onChatSuspend` callback. A discriminated union on `phase`.

| Field        | Type                        | Description                                              |
| ------------ | --------------------------- | -------------------------------------------------------- |
| `phase`      | `"preload" \| "turn"`       | Whether this is a preload or post-turn suspension        |
| `ctx`        | `TaskRunContext`            | Full task run context                                    |
| `chatId`     | `string`                    | Chat session ID                                          |
| `runId`      | `string`                    | The Trigger.dev run ID                                   |
| `clientData` | Typed by `clientDataSchema` | Custom data from the frontend                            |
| `turn`       | `number`                    | Turn number (**`"turn"` phase only**)                    |
| `messages`   | `ModelMessage[]`            | Accumulated model messages (**`"turn"` phase only**)     |
| `uiMessages` | `UIMessage[]`               | Accumulated UI messages (**`"turn"` phase only**)        |

## ChatResumeEvent

Passed to the `onChatResume` callback. Same discriminated union shape as `ChatSuspendEvent`.

| Field        | Type                        | Description                                              |
| ------------ | --------------------------- | -------------------------------------------------------- |
| `phase`      | `"preload" \| "turn"`       | Whether this is a preload or post-turn resumption        |
| `ctx`        | `TaskRunContext`            | Full task run context                                    |
| `chatId`     | `string`                    | Chat session ID                                          |
| `runId`      | `string`                    | The Trigger.dev run ID                                   |
| `clientData` | Typed by `clientDataSchema` | Custom data from the frontend                            |
| `turn`       | `number`                    | Turn number (**`"turn"` phase only**)                    |
| `messages`   | `ModelMessage[]`            | Accumulated model messages (**`"turn"` phase only**)     |
| `uiMessages` | `UIMessage[]`               | Accumulated UI messages (**`"turn"` phase only**)        |

## ChatWriter

A stream writer passed to lifecycle callbacks. Write custom `UIMessageChunk` parts (e.g. `data-*` parts) to the chat stream.

The writer is lazy — no stream is opened unless you call `write()` or `merge()`, so there's zero overhead for callbacks that don't use it.

| Method          | Type                                               | Description                                        |
| --------------- | -------------------------------------------------- | -------------------------------------------------- |
| `write(part)`   | `(part: UIMessageChunk) => void`                   | Write a single chunk to the chat stream            |
| `merge(stream)` | `(stream: ReadableStream<UIMessageChunk>) => void` | Merge another stream's chunks into the chat stream |

```ts
onTurnStart: async ({ writer }) => {
  // Write a custom data part — render it on the frontend
  writer.write({ type: "data-status", data: { loading: true } });
},
onBeforeTurnComplete: async ({ writer, usage }) => {
  // Stream is still open — these chunks arrive before the turn ends
  writer.write({ type: "data-usage", data: { tokens: usage?.totalTokens } });
},
```

## ChatAgentCompactionOptions

Options for the `compaction` field on `chat.agent()`. See [Compaction](/ai-chat/compaction) for usage guide.

| Option                 | Type                                                                         | Required | Description                                                                  |
| ---------------------- | ---------------------------------------------------------------------------- | -------- | ---------------------------------------------------------------------------- |
| `shouldCompact`        | `(event: ShouldCompactEvent) => boolean \| Promise<boolean>`                 | Yes      | Decide whether to compact. Return `true` to trigger                          |
| `summarize`            | `(event: SummarizeEvent) => Promise<string>`                                 | Yes      | Generate a summary from the current messages                                 |
| `compactUIMessages`    | `(event: CompactMessagesEvent) => UIMessage[] \| Promise<UIMessage[]>`       | No       | Transform UI messages after compaction. Default: preserve all                |
| `compactModelMessages` | `(event: CompactMessagesEvent) => ModelMessage[] \| Promise<ModelMessage[]>` | No       | Transform model messages after compaction. Default: replace all with summary |

## CompactMessagesEvent

Passed to `compactUIMessages` and `compactModelMessages` callbacks.

| Field           | Type                 | Description                                          |
| --------------- | -------------------- | ---------------------------------------------------- |
| `summary`       | `string`             | The generated summary text                           |
| `uiMessages`    | `UIMessage[]`        | Current UI messages (full conversation)              |
| `modelMessages` | `ModelMessage[]`     | Current model messages (full conversation)           |
| `chatId`        | `string`             | Chat session ID                                      |
| `turn`          | `number`             | Current turn (0-indexed)                             |
| `clientData`    | `unknown`            | Custom data from the frontend                        |
| `source`        | `"inner" \| "outer"` | Whether compaction is between steps or between turns |

## CompactedEvent

Passed to the `onCompacted` callback.

| Field          | Type                        | Description                                       |
| -------------- | --------------------------- | ------------------------------------------------- |
| `ctx`          | `TaskRunContext`            | Full task run context — see [Task context](#task-context-ctx) |
| `summary`      | `string`                    | The generated summary text                        |
| `messages`     | `ModelMessage[]`            | Messages that were compacted (pre-compaction)     |
| `messageCount` | `number`                    | Number of messages before compaction              |
| `usage`        | `LanguageModelUsage`        | Token usage from the triggering step/turn         |
| `totalTokens`  | `number \| undefined`       | Total token count that triggered compaction       |
| `inputTokens`  | `number \| undefined`       | Input token count                                 |
| `outputTokens` | `number \| undefined`       | Output token count                                |
| `stepNumber`   | `number`                    | Step number (-1 for outer loop)                   |
| `chatId`       | `string \| undefined`       | Chat session ID                                   |
| `turn`         | `number \| undefined`       | Current turn                                      |
| `writer`       | [`ChatWriter`](#chatwriter) | Stream writer for custom chunks during compaction |

## PendingMessagesOptions

Options for the `pendingMessages` field. See [Pending Messages](/ai-chat/pending-messages) for usage guide.

| Option         | Type                                                                              | Required | Description                                                                               |
| -------------- | --------------------------------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------------- |
| `shouldInject` | `(event: PendingMessagesBatchEvent) => boolean \| Promise<boolean>`               | No       | Decide whether to inject the batch between tool-call steps. If absent, no injection.      |
| `prepare`      | `(event: PendingMessagesBatchEvent) => ModelMessage[] \| Promise<ModelMessage[]>` | No       | Transform the batch before injection. Default: convert each via `convertToModelMessages`. |
| `onReceived`   | `(event: PendingMessageReceivedEvent) => void \| Promise<void>`                   | No       | Called when a message arrives during streaming (per-message).                             |
| `onInjected`   | `(event: PendingMessagesInjectedEvent) => void \| Promise<void>`                  | No       | Called after a batch is injected via prepareStep.                                         |

## PendingMessagesBatchEvent

Passed to `shouldInject` and `prepare` callbacks.

| Field           | Type               | Description                   |
| --------------- | ------------------ | ----------------------------- |
| `messages`      | `UIMessage[]`      | All pending messages (batch)  |
| `modelMessages` | `ModelMessage[]`   | Current conversation          |
| `steps`         | `CompactionStep[]` | Completed steps so far        |
| `stepNumber`    | `number`           | Current step (0-indexed)      |
| `chatId`        | `string`           | Chat session ID               |
| `turn`          | `number`           | Current turn (0-indexed)      |
| `clientData`    | `unknown`          | Custom data from the frontend |

## PendingMessagesInjectedEvent

Passed to `onInjected` callback.

| Field                   | Type             | Description                           |
| ----------------------- | ---------------- | ------------------------------------- |
| `messages`              | `UIMessage[]`    | All injected UI messages              |
| `injectedModelMessages` | `ModelMessage[]` | The model messages that were injected |
| `chatId`                | `string`         | Chat session ID                       |
| `turn`                  | `number`         | Current turn                          |
| `stepNumber`            | `number`         | Step where injection occurred         |

## UsePendingMessagesReturn

Return value of `usePendingMessages` hook. See [Pending Messages — Frontend](/ai-chat/pending-messages#frontend-usependingmessages-hook).

| Property/Method         | Type                                   | Description                                                     |
| ----------------------- | -------------------------------------- | --------------------------------------------------------------- |
| `pending`               | `PendingMessage[]`                     | Current pending messages with mode and injection status         |
| `steer`                 | `(text: string) => void`               | Send a steering message (or normal message when not streaming)  |
| `queue`                 | `(text: string) => void`               | Queue for next turn (or send normally when not streaming)       |
| `promoteToSteering`     | `(id: string) => void`                 | Convert a queued message to steering                            |
| `isInjectionPoint`      | `(part: unknown) => boolean`           | Check if an assistant message part is an injection confirmation |
| `getInjectedMessageIds` | `(part: unknown) => string[]`          | Get message IDs from an injection point                         |
| `getInjectedMessages`   | `(part: unknown) => InjectedMessage[]` | Get messages (id + text) from an injection point                |

## ChatSessionOptions

Options for `chat.createSession()`.

| Option                 | Type                        | Default     | Description                                                                                                  |
| ---------------------- | --------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------ |
| `signal`               | `AbortSignal`               | required    | Run-level cancel signal                                                                                      |
| `idleTimeoutInSeconds` | `number`                    | `30`        | Seconds to stay idle between turns                                                                           |
| `timeout`              | `string`                    | `"1h"`      | Duration string for suspend timeout                                                                          |
| `maxTurns`             | `number`                    | `100`       | Max turns before ending                                                                                      |
| `compaction`           | `ChatAgentCompactionOptions`| `undefined` | Automatic context [compaction](/ai-chat/compaction) — same options as `chat.agent({ compaction })`           |
| `pendingMessages`      | `PendingMessagesOptions`    | `undefined` | Mid-execution [message injection](/ai-chat/pending-messages) — same options as `chat.agent({ pendingMessages })` |

## ChatTurn

Each turn yielded by `chat.createSession()`.

| Field               | Type                              | Description                                                |
| ------------------- | --------------------------------- | ---------------------------------------------------------- |
| `number`            | `number`                          | Turn number (0-indexed)                                    |
| `chatId`            | `string`                          | Chat session ID                                            |
| `trigger`           | `string`                          | What triggered this turn                                   |
| `clientData`        | `unknown`                         | Client data from the transport                             |
| `messages`          | `ModelMessage[]`                  | Full accumulated model messages                            |
| `uiMessages`        | `UIMessage[]`                     | Full accumulated UI messages                               |
| `signal`            | `AbortSignal`                     | Combined stop+cancel signal (fresh each turn)              |
| `stopped`           | `boolean`                         | Whether the user stopped generation this turn              |
| `continuation`      | `boolean`                         | Whether this is a continuation run                         |
| `previousTurnUsage` | `LanguageModelUsage \| undefined` | Token usage from the previous turn (undefined on turn 0)   |
| `totalUsage`        | `LanguageModelUsage`              | Cumulative token usage across all completed turns          |
| `handover`          | `{ isFinal: boolean } \| null`    | The [`chat.headStart`](/ai-chat/fast-starts#handover-with-custom-agents) handover for this turn (turn 0 only); `null` otherwise |

| Method                   | Returns                           | Description                                                                                            |
| ------------------------ | --------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `complete(source?)`      | `Promise<UIMessage \| undefined>` | Pipe, capture, accumulate, cleanup, and signal turn-complete. Call with no source on a final head-start handover (`handover.isFinal`), where the warm partial is already the response |
| `done()`                 | `Promise<void>`                   | Signal turn-complete (when you've piped manually)                                                      |
| `addResponse(response)`  | `Promise<void>`                   | Add response to accumulator manually                                                                   |
| `setMessages(uiMessages)`| `Promise<void>`                   | Replace the accumulated messages (continuation seeding, compaction)                                    |
| `prepareStep()`          | `function \| undefined`           | `prepareStep` callback wiring compaction + injection — pass to `streamText` when not using `chat.toStreamTextOptions()` |

## HeadStartHandlerOptions

Options for [`chat.headStart()`](/ai-chat/fast-starts#head-start), the warm-server first-turn handler (`@trigger.dev/sdk/chat-server`).

| Option                 | Type                                                    | Default     | Description                                                                                          |
| ---------------------- | ------------------------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------- |
| `agentId`              | `string`                                                | required    | The `chat.agent` / `chat.customAgent` id to hand off to                                              |
| `run`                  | `(args: HeadStartRunArgs) => Promise<StreamTextResult>` | required    | First-turn callback. Call `streamText` and spread `chat.toStreamTextOptions({ tools })`              |
| `idleTimeoutInSeconds` | `number`                                                | `60`        | How long the agent waits for the handover signal                                                     |
| `triggerConfig`        | `Partial<SessionTriggerConfig>`                         | `undefined` | Run options (tags, queue, machine, maxAttempts, maxDuration, region, lockToVersion) for the auto-triggered handover-prepare run. The `chat:{chatId}` tag is prepended automatically |

`chat.headStart(options)` returns the handler `(req: Request) => Promise<Response>`. The `run` callback receives `HeadStartRunArgs`: `{ messages: UIMessage[], signal: AbortSignal, chat: HeadStartChatHelper }`, where the helper exposes `chat.toStreamTextOptions({ tools })` and a `chat.session` escape hatch. See [Head Start](/ai-chat/fast-starts#head-start) for the full guide.

## chat namespace

All methods available on the `chat` object from `@trigger.dev/sdk/ai`.

| Method                                      | Description                                                                                                                  |
| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `chat.agent(options)`                       | Create a chat agent                                                                                                          |
| `chat.createSession(payload, options)`      | Create an async iterator for chat turns                                                                                      |
| `chat.pipe(source, options?)`               | Pipe a stream to the frontend (from anywhere inside a task)                                                                  |
| `chat.pipeAndCapture(source, options?)`     | Pipe and capture the response; returns `{ message, status, error }`                                                         |
| `chat.writeTurnComplete(options?)`          | Signal turn complete; returns `{ lastEventId, sessionInEventId }` resume cursors                                            |
| `chat.createStopSignal()`                   | Create a managed stop signal wired to the stop input stream                                                                  |
| `chat.messages`                             | Input stream for incoming messages — use `.waitWithIdleTimeout()`                                                            |
| `chat.local<T>({ id })`                     | Create a per-run typed local (see [`chat.local`](/ai-chat/chat-local))                             |
| `chat.createStartSessionAction(taskId, options?)` | Returns a server action that creates a chat Session + triggers the first run + returns a session-scoped PAT. Idempotent on `(env, externalId)`.   |
| `chat.waitForHandover(options)`             | Wait for a [`chat.headStart`](/ai-chat/fast-starts#handover-with-custom-agents) handover signal in a custom loop. Returns the signal or `null`. `chat.MessageAccumulator` wraps this as `consumeHandover()` / `applyHandover()` |
| `chat.requestUpgrade()`                     | End the current run after this turn so the next message starts on the latest agent version. Server-orchestrated handoff.    |
| `chat.setTurnTimeout(duration)`             | Override turn timeout at runtime (e.g. `"2h"`)                                                                               |
| `chat.setTurnTimeoutInSeconds(seconds)`     | Override turn timeout at runtime (in seconds)                                                                                |
| `chat.setIdleTimeoutInSeconds(seconds)`     | Override idle timeout at runtime                                                                                             |
| `chat.setUIMessageStreamOptions(options)`   | Override `toUIMessageStream()` options for the current turn                                                                  |
| `chat.defer(promise)`                       | Run background work in parallel with streaming, awaited before `onTurnComplete`                                              |
| `chat.isStopped()`                          | Check if the current turn was stopped by the user                                                                            |
| `chat.cleanupAbortedParts(message)`         | Remove incomplete parts from a stopped response message                                                                      |
| `chat.response.write(chunk)`                | Write a data part that streams to the frontend AND persists in `onTurnComplete`'s `responseMessage`                          |
| `chat.stream`                               | Raw chat output stream — use `.writer()`, `.pipe()`, `.append()`, `.read()`. Chunks are NOT accumulated into the response.   |
| `chat.history.all()`                        | Read the current accumulated UI messages (returns a copy). See [chat.history](/ai-chat/backend#chat-history)                  |
| `chat.history.set(messages)`                | Replace all accumulated messages (same as `chat.setMessages()`)                                                              |
| `chat.history.remove(messageId)`            | Remove a specific message by ID                                                                                              |
| `chat.history.rollbackTo(messageId)`        | Keep messages up to and including the given ID (undo/rollback)                                                               |
| `chat.history.replace(messageId, message)`  | Replace a specific message by ID (edit)                                                                                      |
| `chat.history.slice(start, end?)`           | Keep only messages in the given range                                                                                        |
| `chat.MessageAccumulator`                   | Class that accumulates conversation messages across turns                                                                    |
| `chat.withUIMessage(config?)`               | Returns a [ChatBuilder](/ai-chat/types#chatbuilder) with a fixed `UIMessage` subtype. See [Types](/ai-chat/types)            |
| `chat.withClientData({ schema })`           | Returns a [ChatBuilder](/ai-chat/types#chatbuilder) with a fixed client data schema. See [Types](/ai-chat/types#typed-client-data-with-chatwithclientdata) |

## `chat.withUIMessage`

Returns a [`ChatBuilder`](/ai-chat/types#chatbuilder) with a fixed `UIMessage` subtype. Chain `.withClientData()`, hook methods, and `.agent()`.

```ts
chat.withUIMessage<TUIM>(config?: ChatWithUIMessageConfig<TUIM>): ChatBuilder<TUIM>;
```

| Parameter              | Type                               | Description                                                                                                                                           |
| ---------------------- | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `config.streamOptions` | `ChatUIMessageStreamOptions<TUIM>` | Optional defaults for `toUIMessageStream()`. Shallow-merged with `uiMessageStreamOptions` on the inner `.agent({ ... })` (agent wins on key conflicts). |

Use this when you need [`InferChatUIMessage`](#inferchatuimessage) / typed `data-*` parts / `InferUITools` to line up across backend hooks and `useChat`. Full guide: [Types](/ai-chat/types).

## `chat.withClientData`

Returns a [`ChatBuilder`](/ai-chat/types#chatbuilder) with a fixed client data schema. All hooks and `run` get typed `clientData` without passing `clientDataSchema` in `.agent()` options.

```ts
chat.withClientData<TSchema>({ schema: TSchema }): ChatBuilder<UIMessage, TSchema>;
```

| Parameter | Type         | Description                                        |
| --------- | ------------ | -------------------------------------------------- |
| `schema`  | `TaskSchema` | Zod, ArkType, Valibot, or any supported schema lib |

Full guide: [Typed client data](/ai-chat/types#typed-client-data-with-chatwithclientdata).

## `ChatWithUIMessageConfig`

| Field           | Type                               | Description                                                           |
| --------------- | ---------------------------------- | --------------------------------------------------------------------- |
| `streamOptions` | `ChatUIMessageStreamOptions<TUIM>` | Default `toUIMessageStream()` options for agents created via `.agent()` |

## `InferChatUIMessage`

Type helper: extracts the `UIMessage` subtype from a chat agent’s wire payload.

```ts
import type { InferChatUIMessage } from "@trigger.dev/sdk/ai";
// Use the /chat/react re-export when you're already importing other React helpers.

type Msg = InferChatUIMessage<typeof myChat>;
```

Use with `useChat<Msg>({ transport })` when using [`chat.withUIMessage`](/ai-chat/types). For agents defined with plain `chat.agent()` (no custom generic), this resolves to the base `UIMessage`.

## `InferChatUIMessageFromTools`

Type helper: derives the chat `UIMessage` type (with typed `tool-${name}` parts) directly from a tool set. Shorthand for `UIMessage<unknown, UIDataTypes, InferUITools<typeof tools>>`.

```ts
import type { InferChatUIMessageFromTools } from "@trigger.dev/sdk/ai";

const tools = { search, readFile };
type ChatUiMessage = InferChatUIMessageFromTools<typeof tools>;
```

Pin it on the agent with [`chat.withUIMessage<ChatUiMessage>()`](/ai-chat/types) and reuse it on the client. See [Tools](/ai-chat/tools#typing-messages-from-your-tools).

## AI helpers (`ai` from `@trigger.dev/sdk/ai`)

| Export                                                                              | Status         | Description                                                                                                                                                                                |
| ----------------------------------------------------------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `ai.toolExecute(task)`                                                              | **Preferred**  | Returns the `execute` function for AI SDK `tool()`. Runs the task via `triggerAndSubscribe` and attaches tool/chat metadata (same behavior the deprecated wrapper used internally).        |
| `ai.tool(task, options?)`                                                           | **Deprecated** | Wraps `tool()` / `dynamicTool()` and the same execute path. Migrate to `tool({ ..., execute: ai.toolExecute(task) })`. See [Task-backed AI tools](/tasks/schemaTask#task-backed-ai-tools). |
| `ai.toolCallId`, `ai.chatContext`, `ai.chatContextOrThrow`, `ai.currentToolOptions` | Supported      | Work for any task-backed tool execute path, including `ai.toolExecute`.                                                                                                                    |

## ChatUIMessageStreamOptions

Options for customizing `toUIMessageStream()`. Set as static defaults via `uiMessageStreamOptions` on `chat.agent()`, or override per-turn via `chat.setUIMessageStreamOptions()`. See [Stream options](/ai-chat/backend#stream-options) for usage examples.

Derived from the AI SDK's `UIMessageStreamOptions` with `onFinish` and `originalMessages` omitted (managed internally — `onFinish` for response capture, `originalMessages` for cross-turn message ID reuse).

| Option              | Type                              | Default              | Description                                                                                                                         |
| ------------------- | --------------------------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `onError`           | `(error: unknown) => string`      | Raw error message    | Called on LLM errors and tool execution errors. Return a sanitized string — sent as `{ type: "error", errorText }` to the frontend. |
| `sendReasoning`     | `boolean`                         | `true`               | Send reasoning parts to the client                                                                                                  |
| `sendSources`       | `boolean`                         | `false`              | Send source parts to the client                                                                                                     |
| `sendFinish`        | `boolean`                         | `true`               | Send the finish event. Set to `false` when chaining multiple `streamText` calls.                                                    |
| `sendStart`         | `boolean`                         | `true`               | Send the message start event. Set to `false` when chaining.                                                                         |
| `messageMetadata`   | `(options: { part }) => metadata` | —                    | Extract message metadata to send to the client. Called on `start` and `finish` events.                                              |
| `generateMessageId` | `() => string`                    | AI SDK's `generateId` | Custom message ID generator for response messages (e.g. UUID-v7). IDs are shared between frontend and backend via the stream's `start` chunk. |

## TriggerChatTransport options

Options for the frontend transport constructor and `useTriggerChatTransport` hook.

| Option                 | Type                                                                 | Default                     | Description                                                                 |
| ---------------------- | -------------------------------------------------------------------- | --------------------------- | --------------------------------------------------------------------------- |
| `task`                 | `string`                                                             | required                    | Task ID the transport's session is bound to. Threaded into `startSession`'s params. |
| `accessToken`          | `(params: AccessTokenParams) => string \| Promise<string>`           | required                    | Pure refresh — mints a fresh session-scoped PAT. Called on 401/403. See [callback shape](#accesstoken-callback). |
| `startSession`         | `(params: StartSessionParams<TClientData>) => Promise<StartSessionResult>` | optional                    | Creates the chat Session and returns the session-scoped PAT. Called on `transport.preload(chatId)` and lazily on the first `sendMessage` for any chatId without a cached PAT. See [callback shape](#startsession-callback). |
| `baseURL`              | `string \| (ctx: { endpoint: "in" \| "out"; chatId: string }) => string` | `"https://api.trigger.dev"` | API base URL. String form applies to every endpoint; function form lets you pick per endpoint — e.g. route `.in/append` through a trusted edge proxy while keeping `.out` SSE direct (see [Trusted edge signals](/ai-chat/patterns/trusted-edge-signals)). |
| `fetch`                | `(url: string, init: RequestInit, ctx: { endpoint: "in" \| "out"; chatId: string }) => Promise<Response>` | —                           | Per-request fetch override. Invoked for both `.in/append` POSTs and the `.out` SSE GET. Use for header injection (tracing), custom retries, or proxy rewrites beyond what `baseURL` can express. |
| `headers`              | `Record<string, string>`                                             | —                           | Extra headers for API requests                                              |
| `streamTimeoutSeconds` | `number`                                                             | `120`                       | How long to wait for stream data                                            |
| `clientData`           | Typed by `clientDataSchema`                                          | —                           | Default client data merged into per-turn `metadata` and threaded through `startSession`'s params (so the first run's `payload.metadata` matches per-turn `metadata`). Live-updated when the option value changes. |
| `sessions`             | `Record<string, ChatSession>`                                        | —                           | Restore sessions from storage. See [ChatSession](#chatsession).             |
| `onSessionChange`      | `(chatId, session \| null) => void`                                  | —                           | Fires when session state changes. `session` is the full `ChatSession` or `null` when the run ends. |
| `onEvent`              | `(event: ChatTransportEvent) => void`                                | —                           | Observability callback for transport lifecycle events (send outcomes, stream connects, first chunk, turn completion). See [Transport events](#transport-events). Exceptions thrown by the callback are swallowed. |
| `multiTab`             | `boolean`                                                            | `false`                     | Enable multi-tab claim coordination via `BroadcastChannel`. See [Frontend → multi-tab](/ai-chat/frontend#multi-tab-coordination). |
| `watch`                | `boolean`                                                            | `false`                     | Read-only watcher mode — keep the SSE subscription open across `trigger:turn-complete` so a viewer sees turns 2, 3, … through one long-lived stream. |
| `headStart`            | `string`                                                             | —                           | URL of a [`chat.headStart`](/ai-chat/fast-starts#head-start) route handler. When set, the FIRST message of a brand-new chat POSTs to this URL so step 1's LLM call runs in your warm process while the agent run boots in parallel. Subsequent turns bypass it. |

### Transport events

The `onEvent` callback receives a `ChatTransportEvent` (exported from `@trigger.dev/sdk/chat` and `@trigger.dev/sdk/chat/react`) for each lifecycle moment the transport observes. All events carry `chatId` and `timestamp`.

| Event | Extra fields | Fires when |
| --- | --- | --- |
| `message-sent` | `messageId?`, `source`, `durationMs`, `partId?`, `bodyBytes?` | A send was durably acknowledged — a 2xx from the session input stream append (or the `headStart` POST), after any internal token-refresh retries. This means the message is durably written to the stream the agent consumes from, not merely "request accepted". `partId` is the append's idempotency key, also stored on the server-side record. |
| `message-send-failed` | `messageId?`, `source`, `error`, `status?`, `durationMs`, `partId?`, `bodyBytes?` | A send definitively failed after internal retries. Fires in addition to `useChat`'s `onError`. |
| `stream-connected` | `resumed`, `lastEventId?`, `messageId?` | The SSE subscription to the session's output stream started delivering. `resumed: true` when reconnecting from a stored cursor (page reload) rather than following a fresh send. `lastEventId` is the cursor it connected from. |
| `first-chunk` | `chunkType?`, `lastEventId?`, `messageId?`, `sinceSendMs?` | The first response chunk of a turn arrived. `sinceSendMs` is the delta from the last turn-producing send — time to first token without any bookkeeping. |
| `turn-completed` | `lastEventId?`, `sessionInEventId?`, `messageId?`, `sinceSendMs?` | The agent's turn-complete control record arrived — the "finished answering" signal. `sinceSendMs` is the full turn latency; `sessionInEventId` is the agent's committed input-stream cursor. |
| `stream-error` | `error`, `status?` | The output stream failed unrecoverably. |

`source` identifies the send path: `"submit-message"`, `"regenerate-message"`, `"steer"` (`sendPendingMessage`), `"action"` (`sendAction`), `"stop"` (`stopGeneration`), or `"head-start"`.

`messageId` on the response-side events is client-side attribution: the id from the most recent turn-producing send (`submit-message`, `regenerate-message`, or `head-start`) on that chat.

See [Monitoring message delivery](/ai-chat/frontend#monitoring-message-delivery) for the metrics and watchdog patterns these enable.

### `accessToken` callback

The transport invokes `accessToken` whenever it needs a *fresh* session-scoped PAT — initial use after no PAT is cached, or after a 401/403 from any session-PAT-authed request. The callback's job is to **return a token, not to start a run.**

`AccessTokenParams`:

| Field | Type | Description |
| --- | --- | --- |
| `chatId` | `string` | The conversation id. |

Customer implementation typically wraps `auth.createPublicToken` server-side:

```ts
"use server";
import { auth } from "@trigger.dev/sdk";

export async function mintChatAccessToken(chatId: string) {
  return auth.createPublicToken({
    scopes: { read: { sessions: chatId }, write: { sessions: chatId } },
    expirationTime: "1h",
  });
}
```

```ts
const transport = useTriggerChatTransport({
  task: "my-chat",
  accessToken: ({ chatId }) => mintChatAccessToken(chatId),
});
```

### `startSession` callback

The transport invokes `startSession` when it needs to *create* the session — on `transport.preload(chatId)`, and lazily on the first `sendMessage` for any chatId without a cached PAT. Concurrent and repeat calls dedupe via an in-flight promise, and the customer's wrapped helper is idempotent on `(env, externalId)` so two tabs / two `preload` calls converge on the same session.

`StartSessionParams<TClientData>`:

| Field | Type | Description |
| --- | --- | --- |
| `taskId` | `string` | The transport's `task` value. |
| `chatId` | `string` | The conversation id (the session's `externalId`). |
| `clientData` | `TClientData` | The transport's current `clientData` option. Pass through to `triggerConfig.basePayload.metadata` so the first run's `payload.metadata` matches per-turn `metadata`. |

Customer implementation wraps `chat.createStartSessionAction(taskId)`:

```ts
"use server";
import { chat } from "@trigger.dev/sdk/ai";

export const startChatSession = chat.createStartSessionAction("my-chat");
```

```ts
const transport = useTriggerChatTransport({
  task: "my-chat",
  startSession: ({ chatId, clientData }) =>
      startChatSession({ chatId, clientData }),
});
```

`startSession` is optional only when the customer fully manages the session lifecycle externally (e.g. by hydrating `sessions: { [chatId]: ... }` and never calling `preload`). Most customers should provide it.

### multiTab

Enable multi-tab coordination. When `true`, only one browser tab can send messages to a given chatId at a time. Other tabs enter read-only mode with real-time message updates via `BroadcastChannel`.

```ts
const transport = useTriggerChatTransport({
  task: "my-chat",
  accessToken,
  multiTab: true,
});
```

No-op when `BroadcastChannel` is unavailable (SSR, Node.js). See [Multi-tab coordination](/ai-chat/frontend#multi-tab-coordination).

### Trigger configuration

Trigger config (machine, queue, tags, maxAttempts, idleTimeoutInSeconds) lives server-side in `chat.createStartSessionAction(taskId, options?)`. The transport doesn't accept these options directly — pass them when wrapping the action:

```ts
"use server";
import { chat } from "@trigger.dev/sdk/ai";

export const startChatSession = chat.createStartSessionAction("my-chat", {
  triggerConfig: {
    machine: "small-1x",
    queue: "chat-queue",
    tags: ["user:123"],
    maxAttempts: 3,
    idleTimeoutInSeconds: 60,
  },
});
```

A `chat:{chatId}` tag is automatically added to every run.

For per-call values that vary by chatId (e.g. plan-tier-driven machine), accept extra params on the customer's server action and pass them into `chat.createStartSessionAction(...)`'s options at call time.

### transport.stopGeneration()

Stop the current generation for a chat session. Sends a stop signal to the backend task and closes the active SSE connection.

```ts
transport.stopGeneration(chatId: string): Promise<boolean>
```

Returns `true` if the stop signal was sent, `false` if there's no active session. Works for both initial connections and reconnected streams (after page refresh with `resume: true`).

Use alongside `useChat`'s `stop()` for a complete stop experience:

```tsx
const { stop: aiStop } = useChat({ transport });

const stop = useCallback(() => {
  transport.stopGeneration(chatId);
  aiStop();
}, [transport, chatId, aiStop]);
```

See [Stop generation](/ai-chat/frontend#stop-generation) for full details.

### transport.sendAction()

Send a custom action to the agent. Actions wake the agent from suspension and fire `onAction`. They are not turns — `run()` and turn lifecycle hooks do not fire. If `onAction` returns a `StreamTextResult`, the response is auto-piped to the frontend.

```ts
transport.sendAction(chatId: string, action: unknown): Promise<ReadableStream<UIMessageChunk>>
```

The action payload is validated against the agent's `actionSchema` on the backend.

```tsx
// Undo button
<button onClick={() => transport.sendAction(chatId, { type: "undo" })}>
  Undo
</button>
```

See [Actions](/ai-chat/actions) for backend setup and [Sending actions](/ai-chat/frontend#sending-actions) for frontend usage.

### transport.preload()

Eagerly trigger a run before the first message.

```ts
transport.preload(chatId): Promise<void>
```

No-op if a session already exists for this chatId. The preload idle window is set by `preloadIdleTimeoutInSeconds` on the agent, not by this call. See [Preload](/ai-chat/fast-starts#preload) for full details.

## useTriggerChatTransport

React hook that creates and memoizes a `TriggerChatTransport` instance. Import from `@trigger.dev/sdk/chat/react`.

```tsx
import { useTriggerChatTransport } from "@trigger.dev/sdk/chat/react";
import type { myChat } from "@/trigger/chat";

const transport = useTriggerChatTransport<typeof myChat>({
  task: "my-chat",
  accessToken: ({ chatId }) => mintChatAccessToken(chatId),
  startSession: ({ chatId, clientData }) =>
      startChatSession({ chatId, clientData }),
  sessions: savedSessions,
  onSessionChange: handleSessionChange,
});
```

The transport is created once on first render and reused across re-renders. Pass a type parameter for compile-time validation of the task ID.

## AgentChat options

Options for the server-side chat client constructor. Import `AgentChat` from `@trigger.dev/sdk/chat`.

| Option                 | Type                                                                 | Default                                | Description                                                                 |
| ---------------------- | -------------------------------------------------------------------- | -------------------------------------- | --------------------------------------------------------------------------- |
| `agent`                | `string`                                                             | required                               | Task ID of the chat agent to converse with.                                 |
| `id`                   | `string`                                                             | `crypto.randomUUID()`                  | Conversation ID. Used as the Session `externalId` and for tagging runs.     |
| `clientData`           | Typed by `clientDataSchema`                                          | —                                      | Client data included in every request. Same shape as the agent's `clientDataSchema`. |
| `session`              | `ChatSession`                                                        | —                                      | Restore a previous session (pass `lastEventId` to resume SSE).              |
| `triggerConfig`        | `Partial<SessionTriggerConfig>`                                      | —                                      | Default trigger config used when starting a new session (machine, tags, etc.). |
| `streamTimeoutSeconds` | `number`                                                             | `120`                                  | SSE timeout in seconds.                                                     |
| `onTriggered`          | `(event) => void \| Promise<void>`                                   | —                                      | Fires when a new run is triggered for this session.                         |
| `onTurnComplete`       | `(event) => void \| Promise<void>`                                   | —                                      | Fires when a turn completes. Persist `event.lastEventId` for stream resumption. |
| `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. 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>` | —                                      | Per-request fetch override. Invoked for both `.in/append` POSTs and the `.out` SSE GET. Use for header injection, custom retries, or proxy rewrites. |

## createStartSessionAction options

Second argument to `chat.createStartSessionAction(taskId, options?)`. Controls how the server-mediated session-create call reaches the trigger.dev API.

| Option           | Type                                                                                 | Default                       | Description                                                                 |
| ---------------- | ------------------------------------------------------------------------------------ | ----------------------------- | --------------------------------------------------------------------------- |
| `tokenTTL`       | `string \| number \| Date`                                                           | `"1h"`                        | TTL for the session-scoped public access token returned to the browser.     |
| `triggerConfig`  | `Partial<SessionTriggerConfig>`                                                      | —                             | Default trigger config (machine, tags, queue, etc.). Per-call config shallow-merges on top. |
| `baseURL`        | `string \| (ctx: { endpoint: "sessions" \| "auth"; chatId: string }) => string`      | `apiClientManager.baseURL`    | API base URL. `endpoint` is `"sessions"` for `POST /api/v1/sessions` or `"auth"` for `POST /api/v1/auth/jwt/claims` (only fires when `tokenTTL` is set). |
| `fetch`          | `(url: string, init: RequestInit, ctx: { endpoint: "sessions" \| "auth"; chatId: string }) => Promise<Response>` | —                             | Per-request fetch override. Use to route session-create through a trusted edge proxy so `basePayload.metadata` is rewritten before reaching `api.trigger.dev`. |

## useMultiTabChat

React hook for multi-tab message coordination. Import from `@trigger.dev/sdk/chat/react`.

```tsx
import { useMultiTabChat } from "@trigger.dev/sdk/chat/react";

const { isReadOnly } = useMultiTabChat(transport, chatId, messages, setMessages);
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `transport` | `TriggerChatTransport` | Transport instance with `multiTab: true` |
| `chatId` | `string` | The chat session ID |
| `messages` | `UIMessage[]` | Current messages from `useChat` |
| `setMessages` | `(messages) => void` | Message setter from `useChat` |

**Returns:** `{ isReadOnly: boolean }` — `true` when another tab is actively sending to this chatId.

The hook handles:
- Tracking read-only state from the transport's `BroadcastChannel` coordinator
- Broadcasting messages when this tab is the active sender
- Receiving messages from other tabs and updating via `setMessages`

See [Multi-tab coordination](/ai-chat/frontend#multi-tab-coordination).

## ChatSession

Persistable session state for the frontend `TriggerChatTransport` and the server-side `AgentChat`. The underlying Session row is keyed on `chatId` (durable across runs); the persistable shape is just the SSE resume cursor and a refresh token.

| Field | Type | Description |
| --- | --- | --- |
| `publicAccessToken` | `string` | Session-scoped JWT (`read:sessions:{chatId} + write:sessions:{chatId}`). Refreshed automatically on 401/403 via the transport's `accessToken` callback. |
| `lastEventId` | `string \| undefined` | Last SSE event received on `.out`. Used to resume mid-stream after a disconnect. |
| `isStreaming` | `boolean \| undefined` | Optional. If persisted, `reconnectToStream` uses it as a fast-path short-circuit. If omitted, the server decides via the session's [`X-Session-Settled`](/ai-chat/client-protocol#x-session-settled-fast-close-on-idle-reconnects) response header. |

## ChatInputChunk

The wire shape for records sent on `.in`. Consumed by `chat.agent` internally — you typically don't write these yourself; `transport.sendMessage`, `transport.stopGeneration`, and `transport.sendAction` all serialize into this shape.

```ts
type ChatInputChunk<TMessage = UIMessage, TMetadata = unknown> =
  | { kind: "message"; payload: ChatTaskWirePayload<TMessage, TMetadata> }
  | { kind: "stop"; message?: string };
```

| Variant | When | Payload |
| --- | --- | --- |
| `kind: "message"` | New message, action, approval response, or close | `payload` is a full `ChatTaskWirePayload` — its `trigger` field (`"submit-message"` / `"action"` / `"close"`) determines the agent's dispatch |
| `kind: "stop"` | Client aborted the active turn | Optional `message` surfaces in the stop handler |

For the raw wire format, see [Client Protocol — ChatInputChunk](/ai-chat/client-protocol#chatinputchunk).

## Session token scopes

Tokens minted for `TriggerChatTransport` and `AgentChat` are session-scoped — keyed on the chat's `externalId` (the `chatId` you assign).

| Scope | Grants |
| --- | --- |
| `read:sessions:<chatId>` | Subscribe to `.out`, HEAD probe the stream, retrieve the session row |
| `write:sessions:<chatId>` | Append to `.in`, close the session, end-and-continue, update metadata |

Tokens are produced by `auth.createPublicToken({ scopes: { read: { sessions: chatId }, write: { sessions: chatId } } })` (used by the customer's `accessToken` server action) or returned automatically from `chat.createStartSessionAction` / `POST /api/v1/sessions`. Either form authorizes both URL forms (`/sessions/{chatId}/...` and `/sessions/session_*/...`) on every read and write route.

## Related

- [Realtime Streams](/tasks/streams) — How streams work under the hood
- [Using the Vercel AI SDK](/guides/examples/vercel-ai-sdk) — Basic AI SDK usage with Trigger.dev
- [Realtime React Hooks](/realtime/react-hooks/overview) — Lower-level realtime hooks
- [Authentication](/realtime/auth) — Public access tokens and trigger tokens
