# API Reference

> Source: https://trigger.dev/docs/ai-chat/reference

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.

[​](https://trigger.dev/docs/ai-chat/reference#compatibility)

Compatibility
------------------------------------------------------------------------------

| Dependency | Supported | Notes |
| --- | --- | --- |
| `@trigger.dev/sdk` | `>=4.5.0-rc.0` | The chat agent surface lives in this SDK release. Install with `@trigger.dev/sdk@rc`. |
| `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](https://trigger.dev/docs/ai-chat/reference#ai-sdk-7-telemetry)<br> 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.

### 

[​](https://trigger.dev/docs/ai-chat/reference#ai-sdk-7-telemetry)

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`:

    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`.

[​](https://trigger.dev/docs/ai-chat/reference#chatagentoptions)

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](https://trigger.dev/docs/ai-chat/lifecycle-hooks#onboot)<br>. |
| `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](https://trigger.dev/docs/ai-chat/patterns/recovery-boot)<br>. |
| `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](https://trigger.dev/docs/ai-chat/lifecycle-hooks#onchatstart)<br>. |
| `onValidateMessages` | `(event: ValidateMessagesEvent) => UIMessage[] \| Promise<UIMessage[]>` | —   | Validate/transform UIMessages before model conversion. See [onValidateMessages](https://trigger.dev/docs/ai-chat/lifecycle-hooks#onvalidatemessages) |
| `hydrateMessages` | `(event: HydrateMessagesEvent) => UIMessage[] \| Promise<UIMessage[]>` | —   | Load message history from backend, replacing the linear accumulator. See [hydrateMessages](https://trigger.dev/docs/ai-chat/lifecycle-hooks#hydratemessages) |
| `actionSchema` | `TaskSchema` | —   | Schema for validating custom actions sent via `transport.sendAction()`. See [Actions](https://trigger.dev/docs/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](https://trigger.dev/docs/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](https://trigger.dev/docs/ai-chat/compaction) |
| `compaction` | `ChatAgentCompactionOptions` | —   | Automatic context compaction. See [Compaction](https://trigger.dev/docs/ai-chat/compaction) |
| `pendingMessages` | `PendingMessagesOptions` | —   | Mid-execution message injection. See [Pending Messages](https://trigger.dev/docs/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](https://trigger.dev/docs/ai-chat/tools)<br>. |
| `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](https://trigger.dev/docs/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](https://trigger.dev/docs/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](https://trigger.dev/docs/ai-chat/patterns/oom-resilience) |

Plus most standard [TaskOptions](https://trigger.dev/docs/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()`](https://trigger.dev/docs/ai-chat/custom-agents)
 if you need richer retry semantics. Standard hooks use the same parameter shapes as on a normal `task()` (including `ctx`).

[​](https://trigger.dev/docs/ai-chat/reference#task-context-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 }) => ... })`.

**`onValidateMessages`** does not include `ctx` — it fires before message accumulation and is designed for pure validation/transformation of incoming messages.

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.

    import type { TaskRunContext } from "@trigger.dev/sdk";
    // Equivalent alias (same type):
    import type { Context } from "@trigger.dev/sdk";
    

Prefer `import type { TaskRunContext } from "@trigger.dev/sdk"` in application code. Do not depend on `@trigger.dev/core` directly.

[​](https://trigger.dev/docs/ai-chat/reference#chattaskrunpayload)

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](https://trigger.dev/docs/ai-chat/tools)<br>. |
| `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 |

[​](https://trigger.dev/docs/ai-chat/reference#bootevent)

BootEvent
----------------------------------------------------------------------

Passed to the `onBoot` callback.

| Field | Type | Description |
| --- | --- | --- |
| `ctx` | `TaskRunContext` | Full task run context — see [Task context](https://trigger.dev/docs/ai-chat/reference#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 |

[​](https://trigger.dev/docs/ai-chat/reference#recoverybootevent)

RecoveryBootEvent
--------------------------------------------------------------------------------------

Passed to the `onRecoveryBoot` callback. See [Recovery boot](https://trigger.dev/docs/ai-chat/patterns/recovery-boot)
 for the full guide.

| Field | Type | Description |
| --- | --- | --- |
| `ctx` | `TaskRunContext` | Full task run context — see [Task context](https://trigger.dev/docs/ai-chat/reference#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[]`](https://trigger.dev/docs/ai-chat/reference#recoverypendingtoolcall) | Tool calls in `input-available` state extracted from `partialAssistant` |
| `writer` | [`ChatWriter`](https://trigger.dev/docs/ai-chat/reference#chatwriter) | Lazy session.out writer — emit a recovery banner / signal here |

[​](https://trigger.dev/docs/ai-chat/reference#recoverybootresult)

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. |

[​](https://trigger.dev/docs/ai-chat/reference#recoverypendingtoolcall)

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 |

[​](https://trigger.dev/docs/ai-chat/reference#preloadevent)

PreloadEvent
----------------------------------------------------------------------------

Passed to the `onPreload` callback.

| Field | Type | Description |
| --- | --- | --- |
| `ctx` | `TaskRunContext` | Full task run context — see [Task context](https://trigger.dev/docs/ai-chat/reference#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`](https://trigger.dev/docs/ai-chat/reference#chatwriter) | Stream writer for custom chunks. Lazy — no overhead if unused. |

[​](https://trigger.dev/docs/ai-chat/reference#chatstartevent)

ChatStartEvent
--------------------------------------------------------------------------------

Passed to the `onChatStart` callback.

| Field | Type | Description |
| --- | --- | --- |
| `ctx` | `TaskRunContext` | Full task run context — see [Task context](https://trigger.dev/docs/ai-chat/reference#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`](https://trigger.dev/docs/ai-chat/reference#chatwriter) | Stream writer for custom chunks. Lazy — no overhead if unused. |

[​](https://trigger.dev/docs/ai-chat/reference#validatemessagesevent)

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 |

[​](https://trigger.dev/docs/ai-chat/reference#resolvetoolsevent)

ResolveToolsEvent
--------------------------------------------------------------------------------------

Passed to the `tools` function form on `chat.agent`, once per turn, to resolve the tool set for that turn. See [Tools](https://trigger.dev/docs/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 |

[​](https://trigger.dev/docs/ai-chat/reference#hydratemessagesevent)

HydrateMessagesEvent
--------------------------------------------------------------------------------------------

Passed to the `hydrateMessages` callback. See [hydrateMessages](https://trigger.dev/docs/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) |

[​](https://trigger.dev/docs/ai-chat/reference#actionevent)

ActionEvent
--------------------------------------------------------------------------

Passed to the `onAction` callback. See [Actions](https://trigger.dev/docs/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) |

[​](https://trigger.dev/docs/ai-chat/reference#turnstartevent)

TurnStartEvent
--------------------------------------------------------------------------------

Passed to the `onTurnStart` callback.

| Field | Type | Description |
| --- | --- | --- |
| `ctx` | `TaskRunContext` | Full task run context — see [Task context](https://trigger.dev/docs/ai-chat/reference#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`](https://trigger.dev/docs/ai-chat/reference#chatwriter) | Stream writer for custom chunks. Lazy — no overhead if unused. |

[​](https://trigger.dev/docs/ai-chat/reference#turncompleteevent)

TurnCompleteEvent
--------------------------------------------------------------------------------------

Passed to the `onTurnComplete` callback.

| Field | Type | Description |
| --- | --- | --- |
| `ctx` | `TaskRunContext` | Full task run context — see [Task context](https://trigger.dev/docs/ai-chat/reference#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 |

[​](https://trigger.dev/docs/ai-chat/reference#beforeturncompleteevent)

BeforeTurnCompleteEvent
--------------------------------------------------------------------------------------------------

Passed to the `onBeforeTurnComplete` callback. Same fields as `TurnCompleteEvent` (including **`ctx`**) plus a `writer`.

| Field | Type | Description |
| --- | --- | --- |
| _(all TurnCompleteEvent fields)_ |     | See [TurnCompleteEvent](https://trigger.dev/docs/ai-chat/reference#turncompleteevent)<br> (includes `ctx`) |
| `writer` | [`ChatWriter`](https://trigger.dev/docs/ai-chat/reference#chatwriter) | Stream writer — the stream is still open so chunks appear in the current turn |

[​](https://trigger.dev/docs/ai-chat/reference#chatsuspendevent)

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**) |

[​](https://trigger.dev/docs/ai-chat/reference#chatresumeevent)

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**) |

[​](https://trigger.dev/docs/ai-chat/reference#chatwriter)

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 |

    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 } });
    },
    

[​](https://trigger.dev/docs/ai-chat/reference#chatagentcompactionoptions)

ChatAgentCompactionOptions
--------------------------------------------------------------------------------------------------------

Options for the `compaction` field on `chat.agent()`. See [Compaction](https://trigger.dev/docs/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 |

[​](https://trigger.dev/docs/ai-chat/reference#compactmessagesevent)

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 |

[​](https://trigger.dev/docs/ai-chat/reference#compactedevent)

CompactedEvent
--------------------------------------------------------------------------------

Passed to the `onCompacted` callback.

| Field | Type | Description |
| --- | --- | --- |
| `ctx` | `TaskRunContext` | Full task run context — see [Task context](https://trigger.dev/docs/ai-chat/reference#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`](https://trigger.dev/docs/ai-chat/reference#chatwriter) | Stream writer for custom chunks during compaction |

[​](https://trigger.dev/docs/ai-chat/reference#pendingmessagesoptions)

PendingMessagesOptions
------------------------------------------------------------------------------------------------

Options for the `pendingMessages` field. See [Pending Messages](https://trigger.dev/docs/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. |

[​](https://trigger.dev/docs/ai-chat/reference#pendingmessagesbatchevent)

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 |

[​](https://trigger.dev/docs/ai-chat/reference#pendingmessagesinjectedevent)

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 |

[​](https://trigger.dev/docs/ai-chat/reference#usependingmessagesreturn)

UsePendingMessagesReturn
----------------------------------------------------------------------------------------------------

Return value of `usePendingMessages` hook. See [Pending Messages — Frontend](https://trigger.dev/docs/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 |

[​](https://trigger.dev/docs/ai-chat/reference#chatsessionoptions)

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](https://trigger.dev/docs/ai-chat/compaction)<br> — same options as `chat.agent({ compaction })` |
| `pendingMessages` | `PendingMessagesOptions` | `undefined` | Mid-execution [message injection](https://trigger.dev/docs/ai-chat/pending-messages)<br> — same options as `chat.agent({ pendingMessages })` |

[​](https://trigger.dev/docs/ai-chat/reference#chatturn)

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`](https://trigger.dev/docs/ai-chat/fast-starts#handover-with-custom-agents)<br> 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()` |

[​](https://trigger.dev/docs/ai-chat/reference#headstarthandleroptions)

HeadStartHandlerOptions
--------------------------------------------------------------------------------------------------

Options for [`chat.headStart()`](https://trigger.dev/docs/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](https://trigger.dev/docs/ai-chat/fast-starts#head-start)
 for the full guide.

[​](https://trigger.dev/docs/ai-chat/reference#chat-namespace)

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 `UIMessage` |
| `chat.writeTurnComplete(options?)` | Signal the frontend that the current turn is complete |
| `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`](https://trigger.dev/docs/ai-chat/chat-local)<br>) |
| `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`](https://trigger.dev/docs/ai-chat/fast-starts#handover-with-custom-agents)<br> 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](https://trigger.dev/docs/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](https://trigger.dev/docs/ai-chat/types#chatbuilder)<br> with a fixed `UIMessage` subtype. See [Types](https://trigger.dev/docs/ai-chat/types) |
| `chat.withClientData({ schema })` | Returns a [ChatBuilder](https://trigger.dev/docs/ai-chat/types#chatbuilder)<br> with a fixed client data schema. See [Types](https://trigger.dev/docs/ai-chat/types#typed-client-data-with-chatwithclientdata) |

[​](https://trigger.dev/docs/ai-chat/reference#chat-withuimessage)

`chat.withUIMessage`
------------------------------------------------------------------------------------------

Returns a [`ChatBuilder`](https://trigger.dev/docs/ai-chat/types#chatbuilder)
 with a fixed `UIMessage` subtype. Chain `.withClientData()`, hook methods, and `.agent()`.

    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`](https://trigger.dev/docs/ai-chat/reference#inferchatuimessage)
 / typed `data-*` parts / `InferUITools` to line up across backend hooks and `useChat`. Full guide: [Types](https://trigger.dev/docs/ai-chat/types)
.

[​](https://trigger.dev/docs/ai-chat/reference#chat-withclientdata)

`chat.withClientData`
--------------------------------------------------------------------------------------------

Returns a [`ChatBuilder`](https://trigger.dev/docs/ai-chat/types#chatbuilder)
 with a fixed client data schema. All hooks and `run` get typed `clientData` without passing `clientDataSchema` in `.agent()` options.

    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](https://trigger.dev/docs/ai-chat/types#typed-client-data-with-chatwithclientdata)
.

[​](https://trigger.dev/docs/ai-chat/reference#chatwithuimessageconfig)

`ChatWithUIMessageConfig`
----------------------------------------------------------------------------------------------------

| Field | Type | Description |
| --- | --- | --- |
| `streamOptions` | `ChatUIMessageStreamOptions<TUIM>` | Default `toUIMessageStream()` options for agents created via `.agent()` |

[​](https://trigger.dev/docs/ai-chat/reference#inferchatuimessage)

`InferChatUIMessage`
------------------------------------------------------------------------------------------

Type helper: extracts the `UIMessage` subtype from a chat agent’s wire payload.

    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`](https://trigger.dev/docs/ai-chat/types)
. For agents defined with plain `chat.agent()` (no custom generic), this resolves to the base `UIMessage`.

[​](https://trigger.dev/docs/ai-chat/reference#inferchatuimessagefromtools)

`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>>`.

    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>()`](https://trigger.dev/docs/ai-chat/types)
 and reuse it on the client. See [Tools](https://trigger.dev/docs/ai-chat/tools#typing-messages-from-your-tools)
.

[​](https://trigger.dev/docs/ai-chat/reference#ai-helpers-ai-from-@trigger-dev/sdk/ai)

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](https://trigger.dev/docs/tasks/schemaTask#task-backed-ai-tools)<br>. |
| `ai.toolCallId`, `ai.chatContext`, `ai.chatContextOrThrow`, `ai.currentToolOptions` | Supported | Work for any task-backed tool execute path, including `ai.toolExecute`. |

[​](https://trigger.dev/docs/ai-chat/reference#chatuimessagestreamoptions)

ChatUIMessageStreamOptions
--------------------------------------------------------------------------------------------------------

Options for customizing `toUIMessageStream()`. Set as static defaults via `uiMessageStreamOptions` on `chat.agent()`, or override per-turn via `chat.setUIMessageStreamOptions()`. See [Stream options](https://trigger.dev/docs/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. |

[​](https://trigger.dev/docs/ai-chat/reference#triggerchattransport-options)

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](https://trigger.dev/docs/ai-chat/reference#accesstoken-callback)<br>. |
| `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](https://trigger.dev/docs/ai-chat/reference#startsession-callback)<br>. |
| `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](https://trigger.dev/docs/ai-chat/patterns/trusted-edge-signals)<br>). |
| `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](https://trigger.dev/docs/ai-chat/reference#chatsession)<br>. |
| `onSessionChange` | `(chatId, session \| null) => void` | —   | Fires when session state changes. `session` is the full `ChatSession` or `null` when the run ends. |
| `multiTab` | `boolean` | `false` | Enable multi-tab claim coordination via `BroadcastChannel`. See [Frontend → multi-tab](https://trigger.dev/docs/ai-chat/frontend#multi-tab-coordination)<br>. |
| `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`](https://trigger.dev/docs/ai-chat/fast-starts#head-start)<br> 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. |

### 

[​](https://trigger.dev/docs/ai-chat/reference#accesstoken-callback)

`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:

    "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",
      });
    }
    

    const transport = useTriggerChatTransport({
      task: "my-chat",
      accessToken: ({ chatId }) => mintChatAccessToken(chatId),
    });
    

### 

[​](https://trigger.dev/docs/ai-chat/reference#startsession-callback)

`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)`:

    "use server";
    import { chat } from "@trigger.dev/sdk/ai";
    
    export const startChatSession = chat.createStartSessionAction("my-chat");
    

    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.

### 

[​](https://trigger.dev/docs/ai-chat/reference#multitab)

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`.

    const transport = useTriggerChatTransport({
      task: "my-chat",
      accessToken,
      multiTab: true,
    });
    

No-op when `BroadcastChannel` is unavailable (SSR, Node.js). See [Multi-tab coordination](https://trigger.dev/docs/ai-chat/frontend#multi-tab-coordination)
.

### 

[​](https://trigger.dev/docs/ai-chat/reference#trigger-configuration)

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:

    "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.

### 

[​](https://trigger.dev/docs/ai-chat/reference#transport-stopgeneration)

transport.stopGeneration()

Stop the current generation for a chat session. Sends a stop signal to the backend task and closes the active SSE connection.

    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:

    const { stop: aiStop } = useChat({ transport });
    
    const stop = useCallback(() => {
      transport.stopGeneration(chatId);
      aiStop();
    }, [transport, chatId, aiStop]);
    

See [Stop generation](https://trigger.dev/docs/ai-chat/frontend#stop-generation)
 for full details.

### 

[​](https://trigger.dev/docs/ai-chat/reference#transport-sendaction)

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.

    transport.sendAction(chatId: string, action: unknown): Promise<ReadableStream<UIMessageChunk>>
    

The action payload is validated against the agent’s `actionSchema` on the backend.

    // Undo button
    <button onClick={() => transport.sendAction(chatId, { type: "undo" })}>
      Undo
    </button>
    

See [Actions](https://trigger.dev/docs/ai-chat/actions)
 for backend setup and [Sending actions](https://trigger.dev/docs/ai-chat/frontend#sending-actions)
 for frontend usage.

### 

[​](https://trigger.dev/docs/ai-chat/reference#transport-preload)

transport.preload()

Eagerly trigger a run before the first message.

    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](https://trigger.dev/docs/ai-chat/fast-starts#preload)
 for full details.

[​](https://trigger.dev/docs/ai-chat/reference#usetriggerchattransport)

useTriggerChatTransport
--------------------------------------------------------------------------------------------------

React hook that creates and memoizes a `TriggerChatTransport` instance. Import from `@trigger.dev/sdk/chat/react`.

    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.

[​](https://trigger.dev/docs/ai-chat/reference#agentchat-options)

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. |

[​](https://trigger.dev/docs/ai-chat/reference#createstartsessionaction-options)

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`. |

[​](https://trigger.dev/docs/ai-chat/reference#usemultitabchat)

useMultiTabChat
----------------------------------------------------------------------------------

React hook for multi-tab message coordination. Import from `@trigger.dev/sdk/chat/react`.

    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](https://trigger.dev/docs/ai-chat/frontend#multi-tab-coordination)
.

[​](https://trigger.dev/docs/ai-chat/reference#chatsession)

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`](https://trigger.dev/docs/ai-chat/client-protocol#x-session-settled-fast-close-on-idle-reconnects)<br> response header. |

[​](https://trigger.dev/docs/ai-chat/reference#chatinputchunk)

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.

    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](https://trigger.dev/docs/ai-chat/client-protocol#chatinputchunk)
.

[​](https://trigger.dev/docs/ai-chat/reference#session-token-scopes)

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.

[​](https://trigger.dev/docs/ai-chat/reference#related)

Related
------------------------------------------------------------------

*   [Realtime Streams](https://trigger.dev/docs/tasks/streams)
     — How streams work under the hood
*   [Using the Vercel AI SDK](https://trigger.dev/docs/guides/examples/vercel-ai-sdk)
     — Basic AI SDK usage with Trigger.dev
*   [Realtime React Hooks](https://trigger.dev/docs/realtime/react-hooks/overview)
     — Lower-level realtime hooks
*   [Authentication](https://trigger.dev/docs/realtime/auth)
     — Public access tokens and trigger tokens

Was this page helpful?

YesNo

[Previous](https://trigger.dev/docs/ai-chat/patterns/trusted-edge-signals)
[Client ProtocolThe wire protocol for building custom chat transports — how clients communicate with chat agents over Sessions and SSE.\
\
Next](https://trigger.dev/docs/ai-chat/client-protocol)

⌘I

Assistant

Responses are generated using AI and may contain mistakes.
