@trigger.dev/sdk 4.5.0-rc.6 → 4.5.0-rc.7

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 (191) hide show
  1. package/dist/commonjs/v3/ai.d.ts +171 -5
  2. package/dist/commonjs/v3/ai.js +309 -22
  3. package/dist/commonjs/v3/ai.js.map +1 -1
  4. package/dist/commonjs/v3/chat-server.d.ts +8 -0
  5. package/dist/commonjs/v3/chat-server.js +32 -10
  6. package/dist/commonjs/v3/chat-server.js.map +1 -1
  7. package/dist/commonjs/v3/chat-server.test.js +51 -0
  8. package/dist/commonjs/v3/chat-server.test.js.map +1 -1
  9. package/dist/commonjs/v3/createStartSessionAction.test.js +30 -0
  10. package/dist/commonjs/v3/createStartSessionAction.test.js.map +1 -1
  11. package/dist/commonjs/v3/sessions.d.ts +3 -2
  12. package/dist/commonjs/v3/sessions.js +3 -2
  13. package/dist/commonjs/v3/sessions.js.map +1 -1
  14. package/dist/commonjs/version.js +1 -1
  15. package/dist/esm/v3/ai.d.ts +171 -5
  16. package/dist/esm/v3/ai.js +309 -22
  17. package/dist/esm/v3/ai.js.map +1 -1
  18. package/dist/esm/v3/chat-server.d.ts +8 -0
  19. package/dist/esm/v3/chat-server.js +32 -10
  20. package/dist/esm/v3/chat-server.js.map +1 -1
  21. package/dist/esm/v3/chat-server.test.js +51 -0
  22. package/dist/esm/v3/chat-server.test.js.map +1 -1
  23. package/dist/esm/v3/createStartSessionAction.test.js +30 -0
  24. package/dist/esm/v3/createStartSessionAction.test.js.map +1 -1
  25. package/dist/esm/v3/sessions.d.ts +3 -2
  26. package/dist/esm/v3/sessions.js +3 -2
  27. package/dist/esm/v3/sessions.js.map +1 -1
  28. package/dist/esm/version.js +1 -1
  29. package/docs/ai/prompts.mdx +430 -0
  30. package/docs/ai-chat/actions.mdx +115 -0
  31. package/docs/ai-chat/anatomy.mdx +71 -0
  32. package/docs/ai-chat/backend.mdx +817 -0
  33. package/docs/ai-chat/background-injection.mdx +221 -0
  34. package/docs/ai-chat/changelog.mdx +850 -0
  35. package/docs/ai-chat/chat-local.mdx +174 -0
  36. package/docs/ai-chat/client-protocol.mdx +1081 -0
  37. package/docs/ai-chat/compaction.mdx +411 -0
  38. package/docs/ai-chat/custom-agents.mdx +364 -0
  39. package/docs/ai-chat/error-handling.mdx +415 -0
  40. package/docs/ai-chat/fast-starts.mdx +672 -0
  41. package/docs/ai-chat/frontend.mdx +580 -0
  42. package/docs/ai-chat/how-it-works.mdx +230 -0
  43. package/docs/ai-chat/lifecycle-hooks.mdx +530 -0
  44. package/docs/ai-chat/mcp.mdx +101 -0
  45. package/docs/ai-chat/overview.mdx +90 -0
  46. package/docs/ai-chat/patterns/branching-conversations.mdx +284 -0
  47. package/docs/ai-chat/patterns/code-sandbox.mdx +126 -0
  48. package/docs/ai-chat/patterns/database-persistence.mdx +414 -0
  49. package/docs/ai-chat/patterns/human-in-the-loop.mdx +275 -0
  50. package/docs/ai-chat/patterns/large-payloads.mdx +169 -0
  51. package/docs/ai-chat/patterns/oom-resilience.mdx +120 -0
  52. package/docs/ai-chat/patterns/persistence-and-replay.mdx +211 -0
  53. package/docs/ai-chat/patterns/recovery-boot.mdx +230 -0
  54. package/docs/ai-chat/patterns/skills.mdx +221 -0
  55. package/docs/ai-chat/patterns/sub-agents.mdx +383 -0
  56. package/docs/ai-chat/patterns/tool-result-auditing.mdx +148 -0
  57. package/docs/ai-chat/patterns/trusted-edge-signals.mdx +337 -0
  58. package/docs/ai-chat/patterns/version-upgrades.mdx +172 -0
  59. package/docs/ai-chat/pending-messages.mdx +343 -0
  60. package/docs/ai-chat/prompt-caching.mdx +206 -0
  61. package/docs/ai-chat/quick-start.mdx +161 -0
  62. package/docs/ai-chat/reference.mdx +909 -0
  63. package/docs/ai-chat/server-chat.mdx +263 -0
  64. package/docs/ai-chat/sessions.mdx +333 -0
  65. package/docs/ai-chat/testing.mdx +682 -0
  66. package/docs/ai-chat/tools.mdx +191 -0
  67. package/docs/ai-chat/types.mdx +242 -0
  68. package/docs/ai-chat/upgrade-guide.mdx +515 -0
  69. package/docs/apikeys.mdx +54 -0
  70. package/docs/building-with-ai.mdx +261 -0
  71. package/docs/bulk-actions.mdx +49 -0
  72. package/docs/changelog.mdx +6 -0
  73. package/docs/cli-deploy-commands.mdx +9 -0
  74. package/docs/cli-dev-commands.mdx +9 -0
  75. package/docs/cli-dev.mdx +8 -0
  76. package/docs/cli-init-commands.mdx +58 -0
  77. package/docs/cli-introduction.mdx +25 -0
  78. package/docs/cli-list-profiles-commands.mdx +42 -0
  79. package/docs/cli-login-commands.mdx +33 -0
  80. package/docs/cli-logout-commands.mdx +33 -0
  81. package/docs/cli-preview-archive.mdx +59 -0
  82. package/docs/cli-promote-commands.mdx +9 -0
  83. package/docs/cli-switch.mdx +43 -0
  84. package/docs/cli-update-commands.mdx +42 -0
  85. package/docs/cli-whoami-commands.mdx +33 -0
  86. package/docs/community.mdx +6 -0
  87. package/docs/config/config-file.mdx +602 -0
  88. package/docs/config/extensions/additionalFiles.mdx +38 -0
  89. package/docs/config/extensions/additionalPackages.mdx +40 -0
  90. package/docs/config/extensions/aptGet.mdx +34 -0
  91. package/docs/config/extensions/audioWaveform.mdx +20 -0
  92. package/docs/config/extensions/custom.mdx +380 -0
  93. package/docs/config/extensions/emitDecoratorMetadata.mdx +29 -0
  94. package/docs/config/extensions/esbuildPlugin.mdx +31 -0
  95. package/docs/config/extensions/ffmpeg.mdx +45 -0
  96. package/docs/config/extensions/lightpanda.mdx +56 -0
  97. package/docs/config/extensions/overview.mdx +67 -0
  98. package/docs/config/extensions/playwright.mdx +195 -0
  99. package/docs/config/extensions/prismaExtension.mdx +1014 -0
  100. package/docs/config/extensions/puppeteer.mdx +30 -0
  101. package/docs/config/extensions/pythonExtension.mdx +182 -0
  102. package/docs/config/extensions/syncEnvVars.mdx +291 -0
  103. package/docs/context.mdx +235 -0
  104. package/docs/database-connections.mdx +213 -0
  105. package/docs/deploy-environment-variables.mdx +435 -0
  106. package/docs/deployment/atomic-deployment.mdx +172 -0
  107. package/docs/deployment/overview.mdx +257 -0
  108. package/docs/deployment/preview-branches.mdx +224 -0
  109. package/docs/errors-retrying.mdx +379 -0
  110. package/docs/github-actions.mdx +222 -0
  111. package/docs/github-integration.mdx +136 -0
  112. package/docs/github-repo.mdx +8 -0
  113. package/docs/help-email.mdx +6 -0
  114. package/docs/help-slack.mdx +11 -0
  115. package/docs/hidden-tasks.mdx +56 -0
  116. package/docs/how-it-works.mdx +454 -0
  117. package/docs/how-to-reduce-your-spend.mdx +217 -0
  118. package/docs/idempotency.mdx +504 -0
  119. package/docs/introduction.mdx +223 -0
  120. package/docs/limits.mdx +241 -0
  121. package/docs/logging.mdx +195 -0
  122. package/docs/machines.mdx +952 -0
  123. package/docs/manual-setup.mdx +632 -0
  124. package/docs/mcp-agent-rules.mdx +41 -0
  125. package/docs/mcp-introduction.mdx +385 -0
  126. package/docs/mcp-tools.mdx +273 -0
  127. package/docs/migrating-from-v3.mdx +334 -0
  128. package/docs/observability/dashboards.mdx +102 -0
  129. package/docs/observability/query.mdx +585 -0
  130. package/docs/open-source-contributing.mdx +16 -0
  131. package/docs/open-source-self-hosting.mdx +541 -0
  132. package/docs/private-networking/aws-console-setup.mdx +304 -0
  133. package/docs/private-networking/overview.mdx +144 -0
  134. package/docs/private-networking/troubleshooting.mdx +78 -0
  135. package/docs/queue-concurrency.mdx +354 -0
  136. package/docs/quick-start.mdx +97 -0
  137. package/docs/realtime/auth.mdx +208 -0
  138. package/docs/realtime/backend/overview.mdx +45 -0
  139. package/docs/realtime/backend/streams.mdx +418 -0
  140. package/docs/realtime/backend/subscribe.mdx +225 -0
  141. package/docs/realtime/how-it-works.mdx +94 -0
  142. package/docs/realtime/overview.mdx +63 -0
  143. package/docs/realtime/react-hooks/overview.mdx +73 -0
  144. package/docs/realtime/react-hooks/streams.mdx +449 -0
  145. package/docs/realtime/react-hooks/subscribe.mdx +674 -0
  146. package/docs/realtime/react-hooks/swr.mdx +87 -0
  147. package/docs/realtime/react-hooks/triggering.mdx +194 -0
  148. package/docs/realtime/react-hooks/use-wait-token.mdx +34 -0
  149. package/docs/realtime/run-object.mdx +174 -0
  150. package/docs/replaying.mdx +72 -0
  151. package/docs/request-feature.mdx +6 -0
  152. package/docs/roadmap.mdx +6 -0
  153. package/docs/run-tests.mdx +20 -0
  154. package/docs/run-usage.mdx +113 -0
  155. package/docs/runs/heartbeats.mdx +38 -0
  156. package/docs/runs/max-duration.mdx +139 -0
  157. package/docs/runs/metadata.mdx +734 -0
  158. package/docs/runs/priority.mdx +31 -0
  159. package/docs/runs.mdx +396 -0
  160. package/docs/self-hosting/docker.mdx +458 -0
  161. package/docs/self-hosting/env/supervisor.mdx +74 -0
  162. package/docs/self-hosting/env/webapp.mdx +276 -0
  163. package/docs/self-hosting/kubernetes.mdx +601 -0
  164. package/docs/self-hosting/overview.mdx +108 -0
  165. package/docs/skills.mdx +85 -0
  166. package/docs/tags.mdx +120 -0
  167. package/docs/tasks/overview.mdx +697 -0
  168. package/docs/tasks/scheduled.mdx +382 -0
  169. package/docs/tasks/schemaTask.mdx +413 -0
  170. package/docs/tasks/streams.mdx +884 -0
  171. package/docs/triggering.mdx +1320 -0
  172. package/docs/troubleshooting-alerts.mdx +385 -0
  173. package/docs/troubleshooting-debugging-in-vscode.mdx +8 -0
  174. package/docs/troubleshooting-github-issues.mdx +6 -0
  175. package/docs/troubleshooting-uptime-status.mdx +6 -0
  176. package/docs/troubleshooting.mdx +398 -0
  177. package/docs/upgrading-packages.mdx +80 -0
  178. package/docs/vercel-integration.mdx +207 -0
  179. package/docs/versioning.mdx +56 -0
  180. package/docs/video-walkthrough.mdx +23 -0
  181. package/docs/wait-for-token.mdx +540 -0
  182. package/docs/wait-for.mdx +42 -0
  183. package/docs/wait-until.mdx +53 -0
  184. package/docs/wait.mdx +18 -0
  185. package/docs/writing-tasks-introduction.mdx +33 -0
  186. package/package.json +8 -5
  187. package/skills/trigger-authoring-chat-agent/SKILL.md +296 -0
  188. package/skills/trigger-authoring-tasks/SKILL.md +254 -0
  189. package/skills/trigger-chat-agent-advanced/SKILL.md +368 -0
  190. package/skills/trigger-cost-savings/SKILL.md +116 -0
  191. package/skills/trigger-realtime-and-frontend/SKILL.md +276 -0
@@ -0,0 +1,45 @@
1
+ ---
2
+ title: "Subscribe to tasks from your backend"
3
+ sidebarTitle: Overview
4
+ description: "Subscribe to run progress, stream AI output, and react to task status changes from your backend code or other tasks."
5
+ ---
6
+
7
+ import RealtimeExamplesCards from "/snippets/realtime-examples-cards.mdx";
8
+
9
+ **Subscribe to runs from your server-side code or other tasks using async iterators.** Get status updates, metadata changes, and streamed data without polling.
10
+
11
+ ## What's available
12
+
13
+ | Category | What it does | Guide |
14
+ |---|---|---|
15
+ | **Run updates** | Subscribe to run status, metadata, and tag changes | [Run updates](/realtime/backend/subscribe) |
16
+ | **Streaming** | Read AI output, file chunks, or any continuous data from tasks | [Streaming](/realtime/backend/streams) |
17
+
18
+ <Note>
19
+ To learn how to emit streams from your tasks, see [Streaming data from tasks](/tasks/streams).
20
+ </Note>
21
+
22
+ ## Authentication
23
+
24
+ All backend functions support both server-side and client-side authentication:
25
+
26
+ - **Server-side**: Use your API key (automatically handled in tasks)
27
+ - **Client-side**: Generate a Public Access Token with appropriate scopes
28
+
29
+ See our [authentication guide](/realtime/auth) for detailed information on creating and using tokens.
30
+
31
+ ## Quick example
32
+
33
+ Subscribe to a run:
34
+
35
+ ```ts
36
+ import { runs, tasks } from "@trigger.dev/sdk";
37
+
38
+ // Trigger a task
39
+ const handle = await tasks.trigger("my-task", { some: "data" });
40
+
41
+ // Subscribe to real-time updates
42
+ for await (const run of runs.subscribeToRun(handle.id)) {
43
+ console.log(`Run ${run.id} status: ${run.status}`);
44
+ }
45
+ ```
@@ -0,0 +1,418 @@
1
+ ---
2
+ title: "Stream data to your backend (AI, files)"
3
+ sidebarTitle: "Streaming"
4
+ description: "Read AI/LLM output, file chunks, and other streaming data from your Trigger.dev tasks in backend code."
5
+ ---
6
+
7
+ **Read streaming data from your tasks in backend code.** Consume AI completions as they generate, process file chunks, or handle any continuous data your tasks produce.
8
+
9
+ <Note>
10
+ To emit streams from your tasks, see [Streaming data from tasks](/tasks/streams). For React components, see [Streaming in React](/realtime/react-hooks/streams).
11
+ </Note>
12
+
13
+ <Tip>
14
+ Run-scoped streams are the right primitive for ephemeral I/O that lives inside a single run's lifetime. For durable, long-lived channels that outlive a run, see [`chat.agent`](/ai-chat/overview): it's built on a Session row that owns the chat's runs and exposes bidirectional `.in` / `.out` channels addressed by a durable id.
15
+ </Tip>
16
+
17
+ ## Reading streams
18
+
19
+ ### Using defined streams (Recommended)
20
+
21
+ The recommended approach is to use [defined streams](/tasks/streams#defining-typed-streams-recommended) for full type safety:
22
+
23
+ ```ts
24
+ import { streams } from "@trigger.dev/sdk";
25
+ import { aiStream } from "./trigger/streams";
26
+
27
+ async function consumeStream(runId: string) {
28
+ // Read from the defined stream
29
+ const stream = await aiStream.read(runId);
30
+
31
+ let fullText = "";
32
+
33
+ for await (const chunk of stream) {
34
+ console.log("Received chunk:", chunk); // chunk is typed!
35
+ fullText += chunk;
36
+ }
37
+
38
+ console.log("Final text:", fullText);
39
+ }
40
+ ```
41
+
42
+ ### Direct stream reading
43
+
44
+ If you prefer not to use defined streams, you can read directly by specifying the stream key:
45
+
46
+ ```ts
47
+ import { streams } from "@trigger.dev/sdk";
48
+
49
+ async function consumeStream(runId: string) {
50
+ // Read from a stream by key
51
+ const stream = await streams.read<string>(runId, "ai-output");
52
+
53
+ for await (const chunk of stream) {
54
+ console.log("Received chunk:", chunk);
55
+ }
56
+ }
57
+ ```
58
+
59
+ ### Reading from the default stream
60
+
61
+ Every run has a default stream, so you can omit the stream key:
62
+
63
+ ```ts
64
+ import { streams } from "@trigger.dev/sdk";
65
+
66
+ async function consumeDefaultStream(runId: string) {
67
+ // Read from the default stream
68
+ const stream = await streams.read<string>(runId);
69
+
70
+ for await (const chunk of stream) {
71
+ console.log("Received chunk:", chunk);
72
+ }
73
+ }
74
+ ```
75
+
76
+ ## Stream options
77
+
78
+ The `read()` method accepts several options for controlling stream behavior:
79
+
80
+ ### Timeout
81
+
82
+ Set a timeout to stop reading if no data is received within a specified time:
83
+
84
+ ```ts
85
+ import { streams } from "@trigger.dev/sdk";
86
+ import { aiStream } from "./trigger/streams";
87
+
88
+ async function consumeWithTimeout(runId: string) {
89
+ const stream = await aiStream.read(runId, {
90
+ timeoutInSeconds: 120, // Wait up to 2 minutes for data
91
+ });
92
+
93
+ try {
94
+ for await (const chunk of stream) {
95
+ console.log("Received chunk:", chunk);
96
+ }
97
+ } catch (error) {
98
+ if (error.name === "TimeoutError") {
99
+ console.log("Stream timed out");
100
+ }
101
+ }
102
+ }
103
+ ```
104
+
105
+ ### Start index
106
+
107
+ Resume reading from a specific chunk index (useful for reconnection scenarios):
108
+
109
+ ```ts
110
+ import { streams } from "@trigger.dev/sdk";
111
+ import { aiStream } from "./trigger/streams";
112
+
113
+ async function resumeStream(runId: string, lastChunkIndex: number) {
114
+ // Start reading from the chunk after the last one we received
115
+ const stream = await aiStream.read(runId, {
116
+ startIndex: lastChunkIndex + 1,
117
+ });
118
+
119
+ for await (const chunk of stream) {
120
+ console.log("Received chunk:", chunk);
121
+ }
122
+ }
123
+ ```
124
+
125
+ ### Abort signal
126
+
127
+ Use an `AbortSignal` to cancel stream reading:
128
+
129
+ ```ts
130
+ import { streams } from "@trigger.dev/sdk";
131
+ import { aiStream } from "./trigger/streams";
132
+
133
+ async function consumeWithCancellation(runId: string) {
134
+ const controller = new AbortController();
135
+
136
+ // Cancel after 30 seconds
137
+ setTimeout(() => controller.abort(), 30000);
138
+
139
+ const stream = await aiStream.read(runId, {
140
+ signal: controller.signal,
141
+ });
142
+
143
+ try {
144
+ for await (const chunk of stream) {
145
+ console.log("Received chunk:", chunk);
146
+
147
+ // Optionally abort based on content
148
+ if (chunk.includes("STOP")) {
149
+ controller.abort();
150
+ }
151
+ }
152
+ } catch (error) {
153
+ if (error.name === "AbortError") {
154
+ console.log("Stream was cancelled");
155
+ }
156
+ }
157
+ }
158
+ ```
159
+
160
+ ### Combining options
161
+
162
+ You can combine multiple options:
163
+
164
+ ```ts
165
+ import { streams } from "@trigger.dev/sdk";
166
+ import { aiStream } from "./trigger/streams";
167
+
168
+ async function advancedStreamConsumption(runId: string) {
169
+ const controller = new AbortController();
170
+
171
+ const stream = await aiStream.read(runId, {
172
+ timeoutInSeconds: 300, // 5 minute timeout
173
+ startIndex: 0, // Start from the beginning
174
+ signal: controller.signal, // Allow cancellation
175
+ });
176
+
177
+ try {
178
+ for await (const chunk of stream) {
179
+ console.log("Received chunk:", chunk);
180
+ }
181
+ } catch (error) {
182
+ if (error.name === "AbortError") {
183
+ console.log("Stream was cancelled");
184
+ } else if (error.name === "TimeoutError") {
185
+ console.log("Stream timed out");
186
+ } else {
187
+ console.error("Stream error:", error);
188
+ }
189
+ }
190
+ }
191
+ ```
192
+
193
+ ## Practical examples
194
+
195
+ ### Reading AI streaming responses
196
+
197
+ Here's a complete example of consuming an AI stream from your backend:
198
+
199
+ ```ts
200
+ import { streams } from "@trigger.dev/sdk";
201
+ import { aiStream } from "./trigger/streams";
202
+
203
+ async function consumeAIStream(runId: string) {
204
+ const stream = await aiStream.read(runId, {
205
+ timeoutInSeconds: 300, // AI responses can take time
206
+ });
207
+
208
+ let fullResponse = "";
209
+ const chunks: string[] = [];
210
+
211
+ for await (const chunk of stream) {
212
+ chunks.push(chunk);
213
+ fullResponse += chunk;
214
+
215
+ // Process each chunk as it arrives
216
+ console.log("Chunk received:", chunk);
217
+
218
+ // Could send to websocket, SSE, etc.
219
+ // await sendToClient(chunk);
220
+ }
221
+
222
+ console.log("Stream complete!");
223
+ console.log("Total chunks:", chunks.length);
224
+ console.log("Full response:", fullResponse);
225
+
226
+ return { fullResponse, chunks };
227
+ }
228
+ ```
229
+
230
+ ### Reading multiple streams
231
+
232
+ If a task emits multiple streams, you can read them concurrently or sequentially:
233
+
234
+ ```ts
235
+ import { streams } from "@trigger.dev/sdk";
236
+ import { aiStream, progressStream } from "./trigger/streams";
237
+
238
+ async function consumeMultipleStreams(runId: string) {
239
+ // Read streams concurrently
240
+ const [aiData, progressData] = await Promise.all([
241
+ consumeStream(aiStream, runId),
242
+ consumeStream(progressStream, runId),
243
+ ]);
244
+
245
+ return { aiData, progressData };
246
+ }
247
+
248
+ async function consumeStream<T>(
249
+ streamDef: { read: (runId: string) => Promise<AsyncIterableStream<T>> },
250
+ runId: string
251
+ ): Promise<T[]> {
252
+ const stream = await streamDef.read(runId);
253
+ const chunks: T[] = [];
254
+
255
+ for await (const chunk of stream) {
256
+ chunks.push(chunk);
257
+ }
258
+
259
+ return chunks;
260
+ }
261
+ ```
262
+
263
+ ### Piping streams to HTTP responses
264
+
265
+ You can pipe streams directly to HTTP responses for server-sent events (SSE):
266
+
267
+ ```ts
268
+ import { streams } from "@trigger.dev/sdk";
269
+ import { aiStream } from "./trigger/streams";
270
+ import type { NextRequest } from "next/server";
271
+
272
+ export async function GET(request: NextRequest) {
273
+ const runId = request.nextUrl.searchParams.get("runId");
274
+
275
+ if (!runId) {
276
+ return new Response("Missing runId", { status: 400 });
277
+ }
278
+
279
+ const stream = await aiStream.read(runId, {
280
+ timeoutInSeconds: 300,
281
+ });
282
+
283
+ // Create a readable stream for SSE
284
+ const encoder = new TextEncoder();
285
+ const readableStream = new ReadableStream({
286
+ async start(controller) {
287
+ try {
288
+ for await (const chunk of stream) {
289
+ // Format as SSE
290
+ const data = `data: ${JSON.stringify({ chunk })}\n\n`;
291
+ controller.enqueue(encoder.encode(data));
292
+ }
293
+ controller.close();
294
+ } catch (error) {
295
+ controller.error(error);
296
+ }
297
+ },
298
+ });
299
+
300
+ return new Response(readableStream, {
301
+ headers: {
302
+ "Content-Type": "text/event-stream",
303
+ "Cache-Control": "no-cache",
304
+ "Connection": "keep-alive",
305
+ },
306
+ });
307
+ }
308
+ ```
309
+
310
+ ### Implementing retry logic
311
+
312
+ Handle transient errors with retry logic:
313
+
314
+ ```ts
315
+ import { streams } from "@trigger.dev/sdk";
316
+ import { aiStream } from "./trigger/streams";
317
+
318
+ async function consumeStreamWithRetry(
319
+ runId: string,
320
+ maxRetries = 3
321
+ ): Promise<string[]> {
322
+ let lastChunkIndex = 0;
323
+ const allChunks: string[] = [];
324
+ let attempt = 0;
325
+
326
+ while (attempt < maxRetries) {
327
+ try {
328
+ const stream = await aiStream.read(runId, {
329
+ startIndex: lastChunkIndex,
330
+ timeoutInSeconds: 120,
331
+ });
332
+
333
+ for await (const chunk of stream) {
334
+ allChunks.push(chunk);
335
+ lastChunkIndex++;
336
+ }
337
+
338
+ // Success! Break out of retry loop
339
+ break;
340
+ } catch (error) {
341
+ attempt++;
342
+
343
+ if (attempt >= maxRetries) {
344
+ throw new Error(`Failed after ${maxRetries} attempts: ${error.message}`);
345
+ }
346
+
347
+ console.log(`Retry attempt ${attempt} after error:`, error.message);
348
+
349
+ // Wait before retrying (exponential backoff)
350
+ await new Promise((resolve) => setTimeout(resolve, 1000 * Math.pow(2, attempt)));
351
+ }
352
+ }
353
+
354
+ return allChunks;
355
+ }
356
+ ```
357
+
358
+ ### Processing streams in chunks
359
+
360
+ Process streams in batches for efficiency:
361
+
362
+ ```ts
363
+ import { streams } from "@trigger.dev/sdk";
364
+ import { aiStream } from "./trigger/streams";
365
+
366
+ async function processStreamInBatches(runId: string, batchSize = 10) {
367
+ const stream = await aiStream.read(runId);
368
+
369
+ let batch: string[] = [];
370
+
371
+ for await (const chunk of stream) {
372
+ batch.push(chunk);
373
+
374
+ if (batch.length >= batchSize) {
375
+ // Process the batch
376
+ await processBatch(batch);
377
+ batch = [];
378
+ }
379
+ }
380
+
381
+ // Process remaining chunks
382
+ if (batch.length > 0) {
383
+ await processBatch(batch);
384
+ }
385
+ }
386
+
387
+ async function processBatch(chunks: string[]) {
388
+ console.log(`Processing batch of ${chunks.length} chunks`);
389
+ // Do something with the batch
390
+ // e.g., save to database, send to queue, etc.
391
+ }
392
+ ```
393
+
394
+ ## Using with `runs.subscribeToRun()`
395
+
396
+ For more advanced use cases where you need both the run status and streams, you can use the `runs.subscribeToRun()` method with `.withStreams()`:
397
+
398
+ ```ts
399
+ import { runs } from "@trigger.dev/sdk";
400
+ import type { myTask } from "./trigger/myTask";
401
+
402
+ async function subscribeToRunAndStreams(runId: string) {
403
+ for await (const update of runs.subscribeToRun<typeof myTask>(runId).withStreams()) {
404
+ switch (update.type) {
405
+ case "run":
406
+ console.log("Run update:", update.run.status);
407
+ break;
408
+ case "default":
409
+ console.log("Stream chunk:", update.chunk);
410
+ break;
411
+ }
412
+ }
413
+ }
414
+ ```
415
+
416
+ <Note>
417
+ For most use cases, we recommend using `streams.read()` with defined streams for better type safety and clearer code. Use `runs.subscribeToRun().withStreams()` only when you need to track both run status and stream data simultaneously.
418
+ </Note>
@@ -0,0 +1,225 @@
1
+ ---
2
+ title: "Run updates (backend)"
3
+ sidebarTitle: "Run updates"
4
+ description: "Subscribe to run status changes, metadata updates, and tag changes from your backend code using async iterators."
5
+ ---
6
+
7
+ **Subscribe to runs from your backend and get updates whenever status, metadata, or tags change.** Each function returns an async iterator that yields the run object on every change.
8
+
9
+ ## runs.subscribeToRun
10
+
11
+ Subscribes to all changes to a specific run.
12
+
13
+ ```ts Example
14
+ import { runs } from "@trigger.dev/sdk";
15
+
16
+ for await (const run of runs.subscribeToRun("run_1234")) {
17
+ console.log(run);
18
+ }
19
+ ```
20
+
21
+ This function subscribes to all changes to a run. It returns an async iterator that yields the run object whenever the run is updated. The iterator will complete when the run is finished.
22
+
23
+ **Authentication**: This function supports both server-side and client-side authentication. For server-side authentication, use your API key. For client-side authentication, you must generate a public access token with read access to the specific run. See our [authentication guide](/realtime/auth) for details.
24
+
25
+ **Response**: The AsyncIterator yields the [run object](/realtime/run-object).
26
+
27
+ ## runs.subscribeToRunsWithTag
28
+
29
+ Subscribes to all changes to runs with a specific tag.
30
+
31
+ ```ts Example
32
+ import { runs } from "@trigger.dev/sdk";
33
+
34
+ for await (const run of runs.subscribeToRunsWithTag("user:1234")) {
35
+ console.log(run);
36
+ }
37
+ ```
38
+
39
+ This function subscribes to all changes to runs with a specific tag. It returns an async iterator that yields the run object whenever a run with the specified tag is updated. This iterator will never complete, so you must manually break out of the loop when you no longer want to receive updates.
40
+
41
+ **Authentication**: This function supports both server-side and client-side authentication. For server-side authentication, use your API key. For client-side authentication, you must generate a public access token with read access to the specific tag. See our [authentication guide](/realtime/auth) for details.
42
+
43
+ **Response**: The AsyncIterator yields the [run object](/realtime/run-object).
44
+
45
+ ## runs.subscribeToBatch
46
+
47
+ Subscribes to all changes for runs in a batch.
48
+
49
+ ```ts Example
50
+ import { runs } from "@trigger.dev/sdk";
51
+
52
+ for await (const run of runs.subscribeToBatch("batch_1234")) {
53
+ console.log(run);
54
+ }
55
+ ```
56
+
57
+ This function subscribes to all changes for runs in a batch. It returns an async iterator that yields a run object whenever a run in the batch is updated. The iterator does not complete on its own, you must manually `break` the loop when you want to stop listening for updates.
58
+
59
+ **Authentication**: This function supports both server-side and client-side authentication. For server-side authentication, use your API key. For client-side authentication, you must generate a public access token with read access to the specific batch. See our [authentication guide](/realtime/auth) for details.
60
+
61
+ **Response**: The AsyncIterator yields the [run object](/realtime/run-object).
62
+
63
+ ## Type safety
64
+
65
+ You can infer the types of the run's payload and output by passing the type of the task to the subscribe functions:
66
+
67
+ ```ts
68
+ import { runs, tasks } from "@trigger.dev/sdk";
69
+ import type { myTask } from "./trigger/my-task";
70
+
71
+ async function myBackend() {
72
+ const handle = await tasks.trigger("my-task", { some: "data" });
73
+
74
+ for await (const run of runs.subscribeToRun<typeof myTask>(handle.id)) {
75
+ // run.payload and run.output are now typed
76
+ console.log(run.payload.some);
77
+
78
+ if (run.output) {
79
+ console.log(run.output.some);
80
+ }
81
+ }
82
+ }
83
+ ```
84
+
85
+ When using `subscribeToRunsWithTag`, you can pass a union of task types:
86
+
87
+ ```ts
88
+ import { runs } from "@trigger.dev/sdk";
89
+ import type { myTask, myOtherTask } from "./trigger/my-task";
90
+
91
+ for await (const run of runs.subscribeToRunsWithTag<typeof myTask | typeof myOtherTask>("my-tag")) {
92
+ // Narrow down the type based on the taskIdentifier
93
+ switch (run.taskIdentifier) {
94
+ case "my-task": {
95
+ console.log("Run output:", run.output.foo); // Type-safe
96
+ break;
97
+ }
98
+ case "my-other-task": {
99
+ console.log("Run output:", run.output.bar); // Type-safe
100
+ break;
101
+ }
102
+ }
103
+ }
104
+ ```
105
+
106
+ ## Subscribe to metadata updates from your tasks
107
+
108
+ The metadata API allows you to update custom metadata on runs and receive real-time updates when metadata changes. This is useful for tracking progress, storing intermediate results, or adding custom status information that can be monitored in real-time.
109
+
110
+ <Note>
111
+ For frontend applications using React, see our [React hooks metadata
112
+ documentation](/realtime/react-hooks/subscribe#using-metadata-to-show-progress-in-your-ui) for
113
+ consuming metadata updates in your UI.
114
+ </Note>
115
+
116
+ When you update metadata from within a task using `metadata.set()`, `metadata.append()`, or other metadata methods, all subscribers to that run will automatically receive the updated run object containing the new metadata.
117
+
118
+ This makes metadata perfect for:
119
+
120
+ - Progress tracking
121
+ - Status updates
122
+ - Intermediate results
123
+ - Custom notifications
124
+
125
+ Use the metadata API within your task to update metadata in real-time. In this basic example task, we're updating the progress of a task as it processes items.
126
+
127
+ ### How to subscribe to metadata updates
128
+
129
+ This example task updates the progress of a task as it processes items.
130
+
131
+ ```ts
132
+ // Your task code
133
+ import { task, metadata } from "@trigger.dev/sdk";
134
+
135
+ export const progressTask = task({
136
+ id: "progress-task",
137
+ run: async (payload: { items: string[] }) => {
138
+ const total = payload.items.length;
139
+
140
+ for (let i = 0; i < payload.items.length; i++) {
141
+ // Update progress metadata
142
+ metadata.set("progress", {
143
+ current: i + 1,
144
+ total: total,
145
+ percentage: Math.round(((i + 1) / total) * 100),
146
+ currentItem: payload.items[i],
147
+ });
148
+
149
+ // Process the item
150
+ await processItem(payload.items[i]);
151
+ }
152
+
153
+ metadata.set("status", "completed");
154
+ return { processed: total };
155
+ },
156
+ });
157
+
158
+ async function processItem(item: string) {
159
+ // Simulate work
160
+ await new Promise((resolve) => setTimeout(resolve, 1000));
161
+ }
162
+ ```
163
+
164
+ We can now subscribe to the runs and receive real-time metadata updates.
165
+
166
+ ```ts
167
+ // Somewhere in your backend code
168
+ import { runs } from "@trigger.dev/sdk";
169
+ import type { progressTask } from "./trigger/progress-task";
170
+
171
+ async function monitorProgress(runId: string) {
172
+ for await (const run of runs.subscribeToRun<typeof progressTask>(runId)) {
173
+ console.log(`Run ${run.id} status: ${run.status}`);
174
+
175
+ if (run.metadata?.progress) {
176
+ const progress = run.metadata.progress as {
177
+ current: number;
178
+ total: number;
179
+ percentage: number;
180
+ currentItem: string;
181
+ };
182
+
183
+ console.log(`Progress: ${progress.current}/${progress.total} (${progress.percentage}%)`);
184
+ console.log(`Processing: ${progress.currentItem}`);
185
+ }
186
+
187
+ if (run.metadata?.status === "completed") {
188
+ console.log("Task completed!");
189
+ break;
190
+ }
191
+ }
192
+ }
193
+ ```
194
+
195
+ For more information on how to write tasks that use the metadata API, as well as more examples, see our [run metadata docs](/runs/metadata#more-metadata-task-examples).
196
+
197
+ ### Type safety
198
+
199
+ You can get type safety for your metadata by defining types:
200
+
201
+ ```ts
202
+ import { runs } from "@trigger.dev/sdk";
203
+ import type { progressTask } from "./trigger/progress-task";
204
+
205
+ interface ProgressMetadata {
206
+ progress?: {
207
+ current: number;
208
+ total: number;
209
+ percentage: number;
210
+ currentItem: string;
211
+ };
212
+ status?: "running" | "completed" | "failed";
213
+ }
214
+
215
+ async function monitorTypedProgress(runId: string) {
216
+ for await (const run of runs.subscribeToRun<typeof progressTask>(runId)) {
217
+ const metadata = run.metadata as ProgressMetadata;
218
+
219
+ if (metadata?.progress) {
220
+ // Now you have full type safety
221
+ console.log(`Progress: ${metadata.progress.percentage}%`);
222
+ }
223
+ }
224
+ }
225
+ ```