You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
|`chatAccessToken`|`string`| Scoped access token for this run |
103
103
|`clientData`| Typed by `clientDataSchema`| Custom data from the frontend |
104
+
|`writer`|[`ChatWriter`](/ai-chat/reference#chatwriter)| Stream writer for custom chunks |
105
+
106
+
Every lifecycle callback receives a `writer` — a lazy stream writer that lets you send custom `UIMessageChunk` parts (like `data-*` parts) to the frontend without the ceremony of `chat.stream.writer()`. See [ChatWriter](/ai-chat/reference#chatwriter).
104
107
105
108
#### onChatStart
106
109
@@ -145,6 +148,7 @@ Fires at the start of every turn, after message accumulation and `onChatStart` (
145
148
|`continuation`|`boolean`| Whether this run is continuing an existing chat |
146
149
|`preloaded`|`boolean`| Whether this run was preloaded |
147
150
|`clientData`| Typed by `clientDataSchema`| Custom data from the frontend |
151
+
|`writer`|[`ChatWriter`](/ai-chat/reference#chatwriter)| Stream writer for custom chunks |
By persisting in `onTurnStart`, the user's message is saved to your database before the AI starts streaming. If the user refreshes mid-stream, the message is already there.
171
175
</Tip>
172
176
177
+
#### onBeforeTurnComplete
178
+
179
+
Fires after the response is captured but **before** the stream closes. The `writer` can send custom chunks that appear in the current turn — use this for post-processing indicators, compaction progress, or any data the user should see before the turn ends.
returnstreamText({ model: openai("gpt-4o"), messages, abortSignal: signal });
203
+
},
204
+
});
205
+
```
206
+
207
+
Receives the same fields as [`TurnCompleteEvent`](/ai-chat/reference#turncompleteevent), plus a [`writer`](/ai-chat/reference#chatwriter).
208
+
173
209
#### onTurnComplete
174
210
175
-
Fires after each turn completes — after the response is captured, before waiting for the next message. This is the primary hook for persisting the assistant's response.
211
+
Fires after each turn completes — after the response is captured and the stream is closed. This is the primary hook for persisting the assistant's response. Does not include a `writer` since the stream is already closed.
Use `data-*` chunk types (e.g. `data-status`, `data-progress`) for custom data. The AI SDK processes these into `DataUIPart` objects in `message.parts` on the frontend. Writing the same `type` + `id` again updates the existing part instead of creating a new one — useful for live progress.
208
208
</Tip>
209
209
210
+
<Tip>
211
+
Inside lifecycle callbacks (`onPreload`, `onChatStart`, `onTurnStart`, `onBeforeTurnComplete`, `onCompacted`), you can use the `writer` parameter instead of `chat.stream.writer()` — it's simpler and avoids the `execute` + `waitUntilComplete` boilerplate. See [ChatWriter](/ai-chat/reference#chatwriter).
Copy file name to clipboardExpand all lines: docs/ai-chat/reference.mdx
+38-2Lines changed: 38 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -16,8 +16,9 @@ Options for `chat.task()`.
16
16
|`onPreload`|`(event: PreloadEvent) => Promise<void> \| void`| — | Fires on preloaded runs before the first message |
17
17
|`onChatStart`|`(event: ChatStartEvent) => Promise<void> \| void`| — | Fires on turn 0 before `run()`|
18
18
|`onTurnStart`|`(event: TurnStartEvent) => Promise<void> \| void`| — | Fires every turn before `run()`|
19
-
|`onTurnComplete`|`(event: TurnCompleteEvent) => Promise<void> \| void`| — | Fires after each turn completes |
20
-
|`onCompacted`|`(event: CompactedEvent) => Promise<void> \| void`| — | Fires when compaction occurs. See [Compaction](/ai-chat/compaction)|
19
+
|`onBeforeTurnComplete`|`(event: BeforeTurnCompleteEvent) => Promise<void> \| void`| — | Fires after response but before stream closes. Includes `writer`. |
20
+
|`onTurnComplete`|`(event: TurnCompleteEvent) => Promise<void> \| void`| — | Fires after each turn completes (stream closed) |
21
+
|`onCompacted`|`(event: CompactedEvent) => Promise<void> \| void`| — | Fires when compaction occurs. Includes `writer`. See [Compaction](/ai-chat/compaction)|
21
22
|`compaction`|`ChatTaskCompactionOptions`| — | Automatic context compaction. See [Compaction](/ai-chat/compaction)|
22
23
|`pendingMessages`|`PendingMessagesOptions`| — | Mid-execution message injection. See [Pending Messages](/ai-chat/pending-messages)|
23
24
|`prepareMessages`|`(event: PrepareMessagesEvent) => ModelMessage[]`| — | Transform model messages before use (cache breaks, context injection, etc.) |
@@ -57,6 +58,7 @@ Passed to the `onPreload` callback.
57
58
|`runId`|`string`| The Trigger.dev run ID |
58
59
|`chatAccessToken`|`string`| Scoped access token for this run |
59
60
|`clientData`| Typed by `clientDataSchema`| Custom data from the frontend |
61
+
|`writer`|[`ChatWriter`](#chatwriter)| Stream writer for custom chunks. Lazy — no overhead if unused. |
60
62
61
63
## ChatStartEvent
62
64
@@ -72,6 +74,7 @@ Passed to the `onChatStart` callback.
72
74
|`continuation`|`boolean`| Whether this run is continuing an existing chat |
73
75
|`previousRunId`|`string \| undefined`| Previous run ID (only when `continuation` is true) |
74
76
|`preloaded`|`boolean`| Whether this run was preloaded before the first message |
77
+
|`writer`|[`ChatWriter`](#chatwriter)| Stream writer for custom chunks. Lazy — no overhead if unused. |
75
78
76
79
## TurnStartEvent
77
80
@@ -89,6 +92,7 @@ Passed to the `onTurnStart` callback.
89
92
|`continuation`|`boolean`| Whether this run is continuing an existing chat |
90
93
|`previousRunId`|`string \| undefined`| Previous run ID (only when `continuation` is true) |
91
94
|`preloaded`|`boolean`| Whether this run was preloaded |
95
+
|`writer`|[`ChatWriter`](#chatwriter)| Stream writer for custom chunks. Lazy — no overhead if unused. |
92
96
93
97
## TurnCompleteEvent
94
98
@@ -112,6 +116,37 @@ Passed to the `onTurnComplete` callback.
112
116
|`usage`|`LanguageModelUsage \| undefined`| Token usage for this turn |
113
117
|`totalUsage`|`LanguageModelUsage`| Cumulative token usage across all turns |
114
118
119
+
## BeforeTurnCompleteEvent
120
+
121
+
Passed to the `onBeforeTurnComplete` callback. Same fields as `TurnCompleteEvent` plus a `writer`.
122
+
123
+
| Field | Type | Description |
124
+
|-------|------|-------------|
125
+
|_(all TurnCompleteEvent fields)_|| See [TurnCompleteEvent](#turncompleteevent)|
126
+
|`writer`|[`ChatWriter`](#chatwriter)| Stream writer — the stream is still open so chunks appear in the current turn |
127
+
128
+
## ChatWriter
129
+
130
+
A stream writer passed to lifecycle callbacks. Write custom `UIMessageChunk` parts (e.g. `data-*` parts) to the chat stream.
131
+
132
+
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.
133
+
134
+
| Method | Type | Description |
135
+
|--------|------|-------------|
136
+
|`write(part)`|`(part: UIMessageChunk) => void`| Write a single chunk to the chat stream |
137
+
|`merge(stream)`|`(stream: ReadableStream<UIMessageChunk>) => void`| Merge another stream's chunks into the chat stream |
138
+
139
+
```ts
140
+
onTurnStart: async ({ writer }) => {
141
+
// Write a custom data part — render it on the frontend
0 commit comments