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
- Fix onFinish race condition: await onFinishPromise so capturedResponseMessage is set before accumulation
- Add chat.isStopped() helper accessible from anywhere during a turn
- Add chat.cleanupAbortedParts() to remove incomplete tool/reasoning/text parts on stop
- Auto-cleanup aborted parts before passing to onTurnComplete
- Clean incoming messages from frontend to prevent tool_use without tool_result API errors
- Add stopped and rawResponseMessage fields to TurnCompleteEvent
- Add continuation and previousRunId fields to all lifecycle hooks and run payload
- Add span attributes (chat.id, chat.turn, chat.stopped, chat.continuation, chat.previous_run_id, etc.)
- Add webFetch tool and reasoning model support to ai-chat reference project
- Render reasoning parts in frontend chat component
- Document all new fields in ai-chat guide
Fires once on the first turn (turn 0) before `run()` executes. Use it to create a chat record in your database.
242
242
243
+
The `continuation` field tells you whether this is a brand new chat or a continuation of an existing one (where the previous run timed out or was cancelled):
Use `signal` (the combined signal) in most cases. The separate `stopSignal` and `cancelSignal` are only needed if you want different behavior for stop vs cancel.
680
691
</Tip>
681
692
693
+
### Detecting stop in callbacks
694
+
695
+
The `onTurnComplete` event includes a `stopped` boolean that indicates whether the user stopped generation during that turn:
returnstreamText({ model: openai("gpt-4o"), messages, abortSignal: signal });
708
+
},
709
+
});
710
+
```
711
+
712
+
You can also check stop status from **anywhere** during a turn using `chat.isStopped()`. This is useful inside `streamText`'s `onFinish` callback where the AI SDK's `isAborted` flag can be unreliable (e.g. when using `createUIMessageStream` + `writer.merge()`):
713
+
714
+
```ts
715
+
import { chat } from"@trigger.dev/sdk/ai";
716
+
import { streamText } from"ai";
717
+
718
+
exportconst myChat =chat.task({
719
+
id: "my-chat",
720
+
run: async ({ messages, signal }) => {
721
+
returnstreamText({
722
+
model: openai("gpt-4o"),
723
+
messages,
724
+
abortSignal: signal,
725
+
onFinish: ({ isAborted }) => {
726
+
// isAborted may be false even after stop when using createUIMessageStream
727
+
const wasStopped =isAborted||chat.isStopped();
728
+
if (wasStopped) {
729
+
// handle stop — e.g. log analytics
730
+
}
731
+
},
732
+
});
733
+
},
734
+
});
735
+
```
736
+
737
+
### Cleaning up aborted messages
738
+
739
+
When stop happens mid-stream, the captured response message can contain parts in an incomplete state — tool calls stuck in `partial-call`, reasoning blocks still marked as `streaming`, etc. These can cause UI issues like permanent spinners.
740
+
741
+
`chat.task` automatically cleans up the `responseMessage` when stop is detected before passing it to `onTurnComplete`. If you use `chat.pipe()` manually and capture response messages yourself, use `chat.cleanupAbortedParts()`:
This removes tool invocation parts stuck in `partial-call` state and marks any `streaming` text or reasoning parts as `done`.
748
+
749
+
<Note>
750
+
Stop signal delivery is best-effort. There is a small race window where the model may finish before the stop signal arrives, in which case the turn completes normally with `stopped: false`. This is expected and does not require special handling.
751
+
</Note>
752
+
682
753
## Client data and metadata
683
754
684
755
### Transport-level client data
@@ -982,6 +1053,7 @@ Plus all standard [TaskOptions](/tasks/overview) — `retry`, `queue`, `machine`
982
1053
|`trigger`|`"submit-message" \| "regenerate-message"`| What triggered the request |
983
1054
|`messageId`|`string \| undefined`| Message ID (for regenerate) |
984
1055
|`clientData`| Typed by `clientDataSchema`| Custom data from the frontend (typed when schema is provided) |
1056
+
|`continuation`|`boolean`| Whether this run is continuing an existing chat (previous run ended) |
985
1057
|`signal`|`AbortSignal`| Combined stop + cancel signal |
986
1058
|`cancelSignal`|`AbortSignal`| Cancel-only signal |
987
1059
|`stopSignal`|`AbortSignal`| Stop-only signal (per-turn) |
@@ -1001,6 +1073,8 @@ See [onTurnComplete](#onturncomplete) for the full field reference.
1001
1073
|`chat.setTurnTimeout(duration)`| Override turn timeout at runtime (e.g. `"2h"`) |
1002
1074
|`chat.setTurnTimeoutInSeconds(seconds)`| Override turn timeout at runtime (in seconds) |
1003
1075
|`chat.setWarmTimeoutInSeconds(seconds)`| Override warm timeout at runtime |
1076
+
|`chat.isStopped()`| Check if the current turn was stopped by the user (works anywhere during a turn) |
1077
+
|`chat.cleanupAbortedParts(message)`| Remove incomplete parts from a stopped response message |
0 commit comments