@trigger.dev/sdk 4.6.4 → 4.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/dist/commonjs/v3/ai.d.ts +5 -2
  2. package/dist/commonjs/v3/ai.js +189 -266
  3. package/dist/commonjs/v3/ai.js.map +1 -1
  4. package/dist/commonjs/v3/chat-client.js +7 -0
  5. package/dist/commonjs/v3/chat-client.js.map +1 -1
  6. package/dist/commonjs/v3/chat-server.d.ts +1 -0
  7. package/dist/commonjs/v3/chat-server.js +8 -0
  8. package/dist/commonjs/v3/chat-server.js.map +1 -1
  9. package/dist/commonjs/v3/chat.d.ts +28 -4
  10. package/dist/commonjs/v3/chat.js +41 -9
  11. package/dist/commonjs/v3/chat.js.map +1 -1
  12. package/dist/commonjs/v3/chatRouteWait.d.ts +21 -0
  13. package/dist/commonjs/v3/chatRouteWait.js +43 -0
  14. package/dist/commonjs/v3/chatRouteWait.js.map +1 -0
  15. package/dist/commonjs/v3/compactionResponse.js +5 -0
  16. package/dist/commonjs/v3/compactionResponse.js.map +1 -1
  17. package/dist/commonjs/v3/concurrency-shared.d.ts +13 -0
  18. package/dist/commonjs/v3/concurrency-shared.js +35 -0
  19. package/dist/commonjs/v3/concurrency-shared.js.map +1 -0
  20. package/dist/commonjs/v3/concurrencyLimits.d.ts +73 -0
  21. package/dist/commonjs/v3/concurrencyLimits.js +166 -0
  22. package/dist/commonjs/v3/concurrencyLimits.js.map +1 -0
  23. package/dist/commonjs/v3/index.d.ts +2 -1
  24. package/dist/commonjs/v3/index.js +3 -1
  25. package/dist/commonjs/v3/index.js.map +1 -1
  26. package/dist/commonjs/v3/managedChatResponse.d.ts +44 -0
  27. package/dist/commonjs/v3/managedChatResponse.js +233 -0
  28. package/dist/commonjs/v3/managedChatResponse.js.map +1 -0
  29. package/dist/commonjs/v3/queues.d.ts +31 -0
  30. package/dist/commonjs/v3/queues.js +31 -0
  31. package/dist/commonjs/v3/queues.js.map +1 -1
  32. package/dist/commonjs/v3/shared.d.ts +18 -1
  33. package/dist/commonjs/v3/shared.js +137 -47
  34. package/dist/commonjs/v3/shared.js.map +1 -1
  35. package/dist/commonjs/v3/steeringContext.d.ts +41 -0
  36. package/dist/commonjs/v3/steeringContext.js +118 -0
  37. package/dist/commonjs/v3/steeringContext.js.map +1 -0
  38. package/dist/commonjs/v3/transcriptStorage.d.ts +4 -1
  39. package/dist/commonjs/v3/transcriptStorage.js +51 -4
  40. package/dist/commonjs/v3/transcriptStorage.js.map +1 -1
  41. package/dist/commonjs/version.js +1 -1
  42. package/dist/esm/v3/ai.d.ts +5 -2
  43. package/dist/esm/v3/ai.js +189 -266
  44. package/dist/esm/v3/ai.js.map +1 -1
  45. package/dist/esm/v3/chat-client.js +7 -0
  46. package/dist/esm/v3/chat-client.js.map +1 -1
  47. package/dist/esm/v3/chat-server.d.ts +1 -0
  48. package/dist/esm/v3/chat-server.js +8 -0
  49. package/dist/esm/v3/chat-server.js.map +1 -1
  50. package/dist/esm/v3/chat.d.ts +28 -4
  51. package/dist/esm/v3/chat.js +41 -9
  52. package/dist/esm/v3/chat.js.map +1 -1
  53. package/dist/esm/v3/chatRouteWait.d.ts +21 -0
  54. package/dist/esm/v3/chatRouteWait.js +40 -0
  55. package/dist/esm/v3/chatRouteWait.js.map +1 -0
  56. package/dist/esm/v3/compactionResponse.js +5 -0
  57. package/dist/esm/v3/compactionResponse.js.map +1 -1
  58. package/dist/esm/v3/concurrency-shared.d.ts +13 -0
  59. package/dist/esm/v3/concurrency-shared.js +31 -0
  60. package/dist/esm/v3/concurrency-shared.js.map +1 -0
  61. package/dist/esm/v3/concurrencyLimits.d.ts +73 -0
  62. package/dist/esm/v3/concurrencyLimits.js +158 -0
  63. package/dist/esm/v3/concurrencyLimits.js.map +1 -0
  64. package/dist/esm/v3/index.d.ts +2 -1
  65. package/dist/esm/v3/index.js +2 -1
  66. package/dist/esm/v3/index.js.map +1 -1
  67. package/dist/esm/v3/managedChatResponse.d.ts +44 -0
  68. package/dist/esm/v3/managedChatResponse.js +228 -0
  69. package/dist/esm/v3/managedChatResponse.js.map +1 -0
  70. package/dist/esm/v3/queues.d.ts +31 -0
  71. package/dist/esm/v3/queues.js +31 -0
  72. package/dist/esm/v3/queues.js.map +1 -1
  73. package/dist/esm/v3/shared.d.ts +18 -1
  74. package/dist/esm/v3/shared.js +136 -47
  75. package/dist/esm/v3/shared.js.map +1 -1
  76. package/dist/esm/v3/steeringContext.d.ts +41 -0
  77. package/dist/esm/v3/steeringContext.js +113 -0
  78. package/dist/esm/v3/steeringContext.js.map +1 -0
  79. package/dist/esm/v3/transcriptStorage.d.ts +4 -1
  80. package/dist/esm/v3/transcriptStorage.js +51 -4
  81. package/dist/esm/v3/transcriptStorage.js.map +1 -1
  82. package/dist/esm/version.js +1 -1
  83. package/docs/ai-chat/client-protocol.mdx +3 -1
  84. package/docs/ai-chat/error-handling.mdx +44 -76
  85. package/docs/ai-chat/fast-starts.mdx +1 -1
  86. package/docs/ai-chat/frontend.mdx +27 -21
  87. package/docs/ai-chat/patterns/branching-conversations.mdx +95 -230
  88. package/docs/ai-chat/patterns/human-in-the-loop.mdx +166 -164
  89. package/docs/ai-chat/patterns/tool-result-auditing.mdx +28 -27
  90. package/docs/ai-chat/patterns/version-upgrades.mdx +4 -4
  91. package/docs/ai-chat/pending-messages.mdx +19 -5
  92. package/docs/ai-chat/quick-start.mdx +26 -20
  93. package/docs/ai-chat/reference.mdx +21 -3
  94. package/docs/ai-chat/sessions.mdx +1 -1
  95. package/docs/ai-chat/testing.mdx +16 -4
  96. package/docs/concurrency.mdx +384 -0
  97. package/docs/config/config-file.mdx +2 -0
  98. package/docs/database-connections.mdx +3 -3
  99. package/docs/deploy-environment-variables.mdx +6 -0
  100. package/docs/deployment/atomic-deployment.mdx +416 -132
  101. package/docs/deployment/overview.mdx +2 -2
  102. package/docs/deployment/preview-branches.mdx +2 -2
  103. package/docs/github-actions.mdx +26 -16
  104. package/docs/github-integration.mdx +2 -2
  105. package/docs/idempotency.mdx +43 -5
  106. package/docs/introduction.mdx +1 -1
  107. package/docs/limits.mdx +16 -6
  108. package/docs/manual-setup.mdx +1 -1
  109. package/docs/observability/query.mdx +25 -0
  110. package/docs/queues.mdx +271 -0
  111. package/docs/reports.mdx +1 -1
  112. package/docs/runs/priority.mdx +2 -25
  113. package/docs/self-hosting/env/webapp.mdx +9 -0
  114. package/docs/self-hosting/kubernetes.mdx +14 -3
  115. package/docs/tasks/overview.mdx +3 -5
  116. package/docs/troubleshooting-alerts.mdx +124 -1
  117. package/docs/troubleshooting.mdx +12 -0
  118. package/docs/vercel-integration.mdx +6 -7
  119. package/docs/versioning.mdx +1 -1
  120. package/docs/writing-tasks-introduction.mdx +2 -1
  121. package/package.json +2 -2
  122. package/docs/deployment/version-skew-protection.mdx +0 -492
  123. package/docs/queue-concurrency.mdx +0 -358
@@ -10,7 +10,7 @@ Chat agent runs are pinned to the worker version they started on. When you deplo
10
10
 
11
11
  <Note>
12
12
  If your sessions are pinned by [version skew
13
- protection](/deployment/version-skew-protection#chat-sessions), you do not need this page to move a
13
+ protection](/deployment/atomic-deployment#chat-sessions), you do not need this page to move a
14
14
  conversation onto a new deployment. A pinned session follows its pin on its own: when the stored
15
15
  `externalDeploymentId` stops naming the deployment a run is on, the agent hands over at the next
16
16
  turn boundary. Set [`versionSkew: "hold"`](#staying-put) to turn that off for one agent.
@@ -32,7 +32,7 @@ The new run lives on the **same Session** as the old one. `chatId` is the durabl
32
32
 
33
33
  ### What "the latest deployment" means
34
34
 
35
- The handoff clears the session's [external deployment id](/deployment/version-skew-protection#chat-sessions) so the new run can land on the current version — re-applying the pin the agent just rejected would make the upgrade impossible. The cleared pin is persisted on the session, so the next continuation doesn't fall back to it either.
35
+ The handoff clears the session's [external deployment id](/deployment/atomic-deployment#chat-sessions) so the new run can land on the current version — re-applying the pin the agent just rejected would make the upgrade impossible. The cleared pin is persisted on the session, so the next continuation doesn't fall back to it either.
36
36
 
37
37
  To move to a specific deployment rather than to whatever is current, name it:
38
38
 
@@ -220,7 +220,7 @@ Two cases never hand over automatically, whatever `versionSkew` says:
220
220
  turn boundary — never mid-turn. If the pin names a deployment that hasn't landed yet, the successor
221
221
  parks: your messages stay durable, and the transport emits `run-pending-version` with
222
222
  `source: "upgrade"` so you can say so in the UI. See [parked
223
- chats](/deployment/version-skew-protection#chat-sessions).
223
+ chats](/deployment/atomic-deployment#chat-sessions).
224
224
  </Note>
225
225
 
226
226
  ## Custom agents
@@ -254,7 +254,7 @@ Both are graceful exits. [`onRecoveryBoot`](/ai-chat/patterns/recovery-boot) doe
254
254
 
255
255
  ## See also
256
256
 
257
- - [Version skew protection](/deployment/version-skew-protection#chat-sessions) — pin a session to the deployment matching the app build that started it
257
+ - [Version skew protection](/deployment/atomic-deployment#chat-sessions) — pin a session to the deployment matching the app build that started it
258
258
  - [Lifecycle hooks](/ai-chat/lifecycle-hooks) — where `onTurnStart` and `onChatResume` fit in the turn cycle
259
259
  - [Recovery boot](/ai-chat/patterns/recovery-boot) — the sibling hook for mid-stream interruptions (does NOT fire on `requestUpgrade`)
260
260
  - [Database persistence](/ai-chat/patterns/database-persistence) — how continuations interact with session state
@@ -12,7 +12,7 @@ By default (without `pendingMessages`), a message sent while the agent is respon
12
12
 
13
13
  The `pendingMessages` option enables steering instead, injecting user messages between tool-call steps via the AI SDK's `prepareStep`. Messages that arrive during streaming are queued and injected at the next step boundary. A message that is not injected becomes the next turn instead, whether that is because `shouldInject` returned `false` or because there were no more step boundaries (single-step response or final text generation). The backend handles that, so no client-side re-send is involved.
14
14
 
15
- Injection is what needs wiring: the `pendingMessages` options only reach `streamText` if you spread `chat.toStreamTextOptions()` (or pass `prepareStep`). Without that, nothing injects, so every mid-turn message is answered as the next turn. Deferral does not depend on it.
15
+ Use the `streamText` passed to your agent's `run` callback. It wires up pending-message injection automatically. If you import `streamText` directly from `ai`, spread `chat.toStreamTextOptions()` into its options to connect injection.
16
16
 
17
17
  ## How it works
18
18
 
@@ -30,20 +30,32 @@ Add `pendingMessages` to your `chat.agent` configuration:
30
30
 
31
31
  ```ts
32
32
  import { chat } from "@trigger.dev/sdk/ai";
33
- import { stepCountIs } from "ai";
33
+ import { stepCountIs, tool } from "ai";
34
34
  import { anthropic } from "@ai-sdk/anthropic";
35
+ import { z } from "zod";
36
+ import { setTimeout } from "node:timers/promises";
35
37
 
36
38
  export const myChat = chat.agent({
37
39
  id: "my-chat",
38
- registry,
39
40
  pendingMessages: {
40
41
  // Only inject when there are completed steps (tool calls happened)
41
42
  shouldInject: ({ steps }) => steps.length > 0,
42
43
  },
43
44
  run: async ({ messages, signal, streamText }) => {
44
45
  return streamText({
46
+ model: anthropic("claude-sonnet-4-5"),
45
47
  messages,
46
- tools: { /* ... */ },
48
+ tools: {
49
+ inspectDocument: tool({
50
+ description: "Inspect a document before summarizing it.",
51
+ inputSchema: z.object({ topic: z.string() }),
52
+ execute: async ({ topic }, { abortSignal }) => {
53
+ // Leave time to send a steering message in this example.
54
+ await setTimeout(10_000, undefined, { signal: abortSignal });
55
+ return { topic, findings: "The document describes a chat application." };
56
+ },
57
+ }),
58
+ },
47
59
  abortSignal: signal,
48
60
  stopWhen: stepCountIs(15),
49
61
  });
@@ -51,7 +63,9 @@ export const myChat = chat.agent({
51
63
  });
52
64
  ```
53
65
 
54
- The `prepareStep` for injection is automatically included when you spread `chat.toStreamTextOptions()`. If you provide your own `prepareStep` after the spread, it overrides the auto-injected one.
66
+ The managed `streamText` composes your `prepareStep` callback after its own. You can add step-specific settings without disconnecting steering. With the manual `chat.toStreamTextOptions()` spread, a later `prepareStep` property replaces the spread's callback.
67
+
68
+ To try it, ask the agent to inspect a document and summarize it in English. While the tool runs, send a steering message asking for French. The next model step receives that instruction, and the stream includes `data-pending-message-injected`.
55
69
 
56
70
  ### Options
57
71
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  title: "Quick Start"
3
3
  sidebarTitle: "Quick Start"
4
- description: "Get a working AI agent in 3 steps — define an agent, generate a token, and wire up the frontend."
4
+ description: "Define an agent, authorize chat sessions on your server, and stream responses into a React frontend."
5
5
  ---
6
6
 
7
7
  These steps assume you already have a Trigger.dev project with the SDK installed and the CLI authenticated — if you don't, follow [Manual setup](/manual-setup) (or `npx trigger.dev@latest init` in an existing project) first. You should be able to run `pnpm exec trigger dev` from your project root before continuing.
@@ -21,9 +21,6 @@ The chat surface works with Vercel AI SDK **v5, v6, or v7**; install whichever m
21
21
 
22
22
  export const myChat = chat.agent({
23
23
  id: "my-chat",
24
- // `streamText` here is the SDK's, not the one from `ai`: it carries
25
- // compaction, steering, background injection, the system prompt and
26
- // telemetry, so none of them have to be wired up by hand.
27
24
  run: async ({ messages, signal, streamText }) => {
28
25
  return streamText({
29
26
  model: anthropic("claude-sonnet-4-5"),
@@ -36,10 +33,9 @@ The chat surface works with Vercel AI SDK **v5, v6, or v7**; install whichever m
36
33
  ```
37
34
 
38
35
  <Note>
39
- Take `streamText` from `run`'s argument rather than importing it from `ai`. The
40
- imported one drives no `prepareStep`, so compaction, mid-turn steering and
41
- background injection never run, and nothing reports it. Spreading
42
- `chat.toStreamTextOptions()` into the imported one does the same job by hand.
36
+ The `streamText` passed to `run` connects compaction, steering, background
37
+ injection, and telemetry. If you use an imported `streamText` from `ai`,
38
+ spread `chat.toStreamTextOptions()` into its options to connect those features.
43
39
  </Note>
44
40
 
45
41
  <Tip>
@@ -56,15 +52,18 @@ The chat surface works with Vercel AI SDK **v5, v6, or v7**; install whichever m
56
52
 
57
53
  import { auth } from "@trigger.dev/sdk";
58
54
  import { chat } from "@trigger.dev/sdk/ai";
55
+ import { requireChatOwner } from "@/lib/chat-access";
59
56
 
60
- // Creates the Session row + triggers the first run, returns the
61
- // session PAT. Idempotent on (env, chatId) so concurrent calls
62
- // converge to the same session.
63
- export const startChatSession = chat.createStartSessionAction("my-chat");
57
+ const startSession = chat.createStartSessionAction("my-chat");
64
58
 
65
- // Pure mint — fresh session-scoped PAT for an existing session.
66
- // The transport calls this on 401/403 to refresh.
59
+ export async function startChatSession({ chatId }: { chatId: string }) {
60
+ await requireChatOwner(chatId);
61
+ return startSession({ chatId });
62
+ }
63
+
64
+ // The transport calls this on 401/403 to refresh the session token.
67
65
  export async function mintChatAccessToken(chatId: string) {
66
+ await requireChatOwner(chatId);
68
67
  return auth.createPublicToken({
69
68
  scopes: {
70
69
  read: { sessions: chatId },
@@ -75,7 +74,9 @@ The chat surface works with Vercel AI SDK **v5, v6, or v7**; install whichever m
75
74
  }
76
75
  ```
77
76
 
78
- The browser never holds your environment's secret key — both helpers run on your server, where customer-side authorization (per-user, per-plan, etc.) lives alongside any DB writes you want to pair with session creation.
77
+ `requireChatOwner` is your application helper: authenticate the request, load the chat by ID and owner, and throw if it doesn't belong to that user. Create the chat record on your server before rendering the frontend, and pass its ID into `Chat`. Check ownership in both actions, including token refresh.
78
+
79
+ Set `TRIGGER_SECRET_KEY` and your model provider key in the server and worker environments. Keep both keys out of the browser.
79
80
 
80
81
  </Step>
81
82
 
@@ -93,15 +94,14 @@ The chat surface works with Vercel AI SDK **v5, v6, or v7**; install whichever m
93
94
  import type { myChat } from "@/trigger/chat";
94
95
  import { mintChatAccessToken, startChatSession } from "@/app/actions";
95
96
 
96
- export function Chat() {
97
+ export function Chat({ chatId }: { chatId: string }) {
97
98
  const transport = useTriggerChatTransport<typeof myChat>({
98
99
  task: "my-chat",
99
100
  accessToken: ({ chatId }) => mintChatAccessToken(chatId),
100
- startSession: ({ chatId, clientData }) =>
101
- startChatSession({ chatId, clientData }),
101
+ startSession: ({ chatId }) => startChatSession({ chatId }),
102
102
  });
103
103
 
104
- const { messages, sendMessage, stop, status } = useChat({ transport });
104
+ const { messages, sendMessage, stop, status, error } = useChat({ id: chatId, transport });
105
105
  const [input, setInput] = useState("");
106
106
 
107
107
  return (
@@ -115,6 +115,8 @@ The chat surface works with Vercel AI SDK **v5, v6, or v7**; install whichever m
115
115
  </div>
116
116
  ))}
117
117
 
118
+ {error && <p role="alert">{error.message}</p>}
119
+
118
120
  <form
119
121
  onSubmit={(e) => {
120
122
  e.preventDefault();
@@ -129,7 +131,7 @@ The chat surface works with Vercel AI SDK **v5, v6, or v7**; install whichever m
129
131
  onChange={(e) => setInput(e.target.value)}
130
132
  placeholder="Type a message..."
131
133
  />
132
- <button type="submit" disabled={status === "streaming"}>
134
+ <button type="submit" disabled={status === "streaming" || status === "submitted"}>
133
135
  Send
134
136
  </button>
135
137
  {status === "streaming" && (
@@ -146,6 +148,10 @@ The chat surface works with Vercel AI SDK **v5, v6, or v7**; install whichever m
146
148
  </Step>
147
149
  </Steps>
148
150
 
151
+ ## Try it
152
+
153
+ Run your frontend and `pnpm exec trigger dev`, then send a message. You should see an assistant response stream into the page and a run in your project's dashboard. If session creation fails, check ownership and the server's `TRIGGER_SECRET_KEY`. If the run starts but the model fails, check the worker's provider key and run logs.
154
+
149
155
  ## Next steps
150
156
 
151
157
  - [Backend](/ai-chat/backend) — Lifecycle hooks, persistence, session iterator, raw task primitives
@@ -698,7 +698,7 @@ The `onEvent` callback receives a `ChatTransportEvent` (exported from `@trigger.
698
698
  | --- | --- | --- |
699
699
  | `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. |
700
700
  | `message-send-failed` | `messageId?`, `source`, `error`, `status?`, `durationMs`, `partId?`, `bodyBytes?` | A send definitively failed after internal retries. Fires in addition to `useChat`'s `onError`. |
701
- | `run-pending-version` | `source` | The chat's run is parked waiting for the deployment carrying its external deployment id ([version skew protection](/deployment/version-skew-protection#chat-sessions)). Everything already sent is durable and answered once the deployment lands. `source` is `"start"` (learned while starting the session), `"send"` (from a message append, re-emitted on every send while parked) `"head-start"` (from the `headStart` POST, where step 1 still streams from your server and only step 2 waits) or `"upgrade"` (an automatic version handover whose successor is parked on a deployment that has not landed). |
701
+ | `run-pending-version` | `source` | The chat's run is parked waiting for the deployment carrying its external deployment id ([version skew protection](/deployment/atomic-deployment#chat-sessions)). Everything already sent is durable and answered once the deployment lands. `source` is `"start"` (learned while starting the session), `"send"` (from a message append, re-emitted on every send while parked) `"head-start"` (from the `headStart` POST, where step 1 still streams from your server and only step 2 waits) or `"upgrade"` (an automatic version handover whose successor is parked on a deployment that has not landed). |
702
702
  | `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. |
703
703
  | `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. |
704
704
  | `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 cursor the agent can safely resume its input stream from. Treat it as a lower bound: it is held back behind any message still waiting to be handled, so it can be below the sequence of the record this turn answered. Do not use it to decide whether a turn boundary belongs to your own send. |
@@ -836,10 +836,28 @@ See [Stop generation](/ai-chat/frontend#stop-generation) for full details.
836
836
 
837
837
  Send a custom action to the agent, outside `useChat`. Actions wake the agent from suspension and fire `onAction`. An action that returns `chat.turn()` is followed by a turn; its answer arrives on the returned stream, which the caller must read. From a `useChat` app, send actions as requests instead (`sendMessage(undefined, { body: { action } })` or the `useChatActions` hook) so `useChat` renders the answer.
838
838
 
839
- ```ts
840
- transport.sendAction(chatId: string, action: unknown): Promise<ReadableStream<UIMessageChunk>>
839
+ ```typescript
840
+ transport.sendAction(
841
+ chatId: string,
842
+ action: unknown,
843
+ options?: ChatActionOptions
844
+ ): Promise<ReadableStream<UIMessageChunk>>
841
845
  ```
842
846
 
847
+ `ChatActionOptions` and `ChatActionSettlement` are exported from `@trigger.dev/sdk/chat`.
848
+
849
+ | Option | Type | Description |
850
+ | --- | --- | --- |
851
+ | `abortSignal` | `AbortSignal` | Cancel the action's response subscription and send a stop signal for an outstanding turn. |
852
+ | `metadata` | `Record<string, unknown>` | Per-action metadata merged over the transport's `clientData`. |
853
+ | `onSettled` | `(settlement: ChatActionSettlement) => void` | Called at most once when this subscription accepts a turn-complete record confirming the action's input was processed. |
854
+
855
+ `onSettled` receives `{ inputSeq, sessionInEventId, lastEventId? }`: the action's input append sequence, the committed input cursor, and the output cursor of the completion record. It runs before the returned stream closes, or continues in watch mode. Settlement confirms input processing, not application-level success; read the response chunks for the action's result. Synchronous callback exceptions do not interrupt the stream.
856
+
857
+ <Note>
858
+ The callback does not run after cancellation, stream closure without a matching completion, missing or invalid cursors, or an error because a previous stop caused the action's output to be discarded. In that last case, the action's own completion can arrive while its response stream still throws an output-lost error. A missing callback does not prove the action was unprocessed: reconcile persisted state or make the action idempotent before retrying.
859
+ </Note>
860
+
843
861
  For managed `chat.agent()` tasks, the action payload is validated against the agent's `actionSchema` on the backend. Raw `chat.customAgent()` tasks receive it as `unknown` and must validate it themselves.
844
862
 
845
863
  ```tsx
@@ -111,7 +111,7 @@ const { id, runId, publicAccessToken, isCached } = await sessions.start({
111
111
  | `type` | `string` | Free-form discriminator. `chat.agent` uses `"chat.agent"`. |
112
112
  | `externalId` | `string?` | Your stable identity. Cannot start with `session_` (reserved). |
113
113
  | `taskIdentifier` | `string` | Task this session triggers runs against. |
114
- | `triggerConfig` | `SessionTriggerConfig` | Trigger options applied to every run: `tags` (up to 10, same as [run tags](/tags); the chat helpers such as `chat.createStartSessionAction` and `AgentChat` add a `chat:{chatId}` tag themselves, which uses one slot. Direct `sessions.start` callers get all 10 and must add any chat tag themselves), `queue`, `machine`, `maxAttempts`, `maxDuration`, `region`, `idleTimeoutInSeconds`, `basePayload`, and the version pins `lockToVersion` / [`externalDeploymentId`](/deployment/version-skew-protection#chat-sessions). |
114
+ | `triggerConfig` | `SessionTriggerConfig` | Trigger options applied to every run: `tags` (up to 10, same as [run tags](/tags); the chat helpers such as `chat.createStartSessionAction` and `AgentChat` add a `chat:{chatId}` tag themselves, which uses one slot. Direct `sessions.start` callers get all 10 and must add any chat tag themselves), `queue`, `machine`, `maxAttempts`, `maxDuration`, `region`, `idleTimeoutInSeconds`, `basePayload`, and the version pins `lockToVersion` / [`externalDeploymentId`](/deployment/atomic-deployment#chat-sessions). |
115
115
  | `tags` | `string[]?` | Up to 10 tags on the Session row (separate from `triggerConfig.tags`). |
116
116
  | `metadata` | `Record<string, unknown>?` | Arbitrary JSON. |
117
117
  | `expiresAt` | `Date?` | Hard retention deadline. |
@@ -73,11 +73,11 @@ describe("myChatAgent", () => {
73
73
  });
74
74
  ```
75
75
 
76
- The agent reads the mock model from `clientData`:
76
+ This test-only agent reads the mock model from `clientData`. Keep this injection point in your test fixture; select production models on the server.
77
77
 
78
78
  ```ts trigger/my-chat.ts
79
79
  import { chat } from "@trigger.dev/sdk/ai";
80
- import { streamText, type LanguageModel } from "ai";
80
+ import { stepCountIs, type LanguageModel } from "ai";
81
81
  import { z } from "zod";
82
82
 
83
83
  type ClientData = { model: LanguageModel };
@@ -90,7 +90,7 @@ export const myChatAgent = chat
90
90
  })
91
91
  .agent({
92
92
  id: "my-chat",
93
- run: async ({ messages, clientData, signal }) => {
93
+ run: async ({ messages, clientData, signal, streamText }) => {
94
94
  return streamText({
95
95
  model: clientData?.model ?? "openai/gpt-4o-mini",
96
96
  messages,
@@ -159,7 +159,7 @@ export const agent = chat
159
159
  .withClientData({ schema: z.custom<ClientData>() })
160
160
  .agent({
161
161
  id: "agent",
162
- run: async ({ messages, clientData, signal }) => {
162
+ run: async ({ messages, clientData, signal, streamText }) => {
163
163
  return streamText({
164
164
  model: clientData?.model ?? anthropic("claude-haiku-4-5"),
165
165
  messages,
@@ -677,3 +677,15 @@ await runInMockTaskContext(
677
677
  - **Single agent per process.** The resource catalog is process-global; tests within a file are sequential by default. If you parallelize across files, vitest runs each file in its own worker, which avoids registry collisions.
678
678
  - **Time-sensitive hooks.** `onTurnComplete` runs *after* the `turn-complete` chunk is written, so `sendMessage()` resolves before that hook finishes. Add a brief `await new Promise((r) => setTimeout(r, 20))` if you need to assert on hook side-effects.
679
679
  - **No real LLM.** The harness does not call providers — you must inject `MockLanguageModelV3` (or another mock) yourself.
680
+
681
+ ## Check the deployed behavior
682
+
683
+ The harness checks your agent logic without a running platform. Also exercise the transport against a development or staging environment with a real model:
684
+
685
+ - Send a message, reload during a tool call, and check that the conversation resumes without duplicate message IDs.
686
+ - Send a pending message during a slow tool. Test both injection and deferral to the next turn.
687
+ - Interrupt the model stream after some text arrives. Check the error, partial transcript, and a successful next turn.
688
+ - Stop a resumed response and check `stopped` in `onTurnComplete`.
689
+ - Try reading a chat and minting its token as another user. Both requests should fail before calling the SDK.
690
+
691
+ For custom storage, test against your actual adapter. Include pagination boundaries, repeated saves, partial responses, and branch isolation. Development workers don't exercise deployed-worker checkpoint and restore; verify that separately if your application relies on it.