@trigger.dev/sdk 4.5.0-rc.5 → 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.
- package/dist/commonjs/v3/ai.d.ts +178 -5
- package/dist/commonjs/v3/ai.js +603 -119
- package/dist/commonjs/v3/ai.js.map +1 -1
- package/dist/commonjs/v3/chat-client.js +3 -0
- package/dist/commonjs/v3/chat-client.js.map +1 -1
- package/dist/commonjs/v3/chat-react.js +10 -7
- package/dist/commonjs/v3/chat-react.js.map +1 -1
- package/dist/commonjs/v3/chat-server.d.ts +8 -0
- package/dist/commonjs/v3/chat-server.js +32 -10
- package/dist/commonjs/v3/chat-server.js.map +1 -1
- package/dist/commonjs/v3/chat-server.test.js +51 -0
- package/dist/commonjs/v3/chat-server.test.js.map +1 -1
- package/dist/commonjs/v3/chat.js +34 -6
- package/dist/commonjs/v3/chat.js.map +1 -1
- package/dist/commonjs/v3/chat.test.js +53 -0
- package/dist/commonjs/v3/chat.test.js.map +1 -1
- package/dist/commonjs/v3/createStartSessionAction.test.js +30 -0
- package/dist/commonjs/v3/createStartSessionAction.test.js.map +1 -1
- package/dist/commonjs/v3/sessions.d.ts +11 -6
- package/dist/commonjs/v3/sessions.js +10 -5
- package/dist/commonjs/v3/sessions.js.map +1 -1
- package/dist/commonjs/v3/test/mock-chat-agent.d.ts +6 -0
- package/dist/commonjs/v3/test/mock-chat-agent.js +1 -0
- package/dist/commonjs/v3/test/mock-chat-agent.js.map +1 -1
- package/dist/commonjs/version.js +1 -1
- package/dist/esm/v3/ai.d.ts +178 -5
- package/dist/esm/v3/ai.js +603 -120
- package/dist/esm/v3/ai.js.map +1 -1
- package/dist/esm/v3/chat-client.js +3 -0
- package/dist/esm/v3/chat-client.js.map +1 -1
- package/dist/esm/v3/chat-react.js +10 -7
- package/dist/esm/v3/chat-react.js.map +1 -1
- package/dist/esm/v3/chat-server.d.ts +8 -0
- package/dist/esm/v3/chat-server.js +32 -10
- package/dist/esm/v3/chat-server.js.map +1 -1
- package/dist/esm/v3/chat-server.test.js +51 -0
- package/dist/esm/v3/chat-server.test.js.map +1 -1
- package/dist/esm/v3/chat.js +34 -6
- package/dist/esm/v3/chat.js.map +1 -1
- package/dist/esm/v3/chat.test.js +53 -0
- package/dist/esm/v3/chat.test.js.map +1 -1
- package/dist/esm/v3/createStartSessionAction.test.js +30 -0
- package/dist/esm/v3/createStartSessionAction.test.js.map +1 -1
- package/dist/esm/v3/sessions.d.ts +11 -6
- package/dist/esm/v3/sessions.js +10 -5
- package/dist/esm/v3/sessions.js.map +1 -1
- package/dist/esm/v3/test/mock-chat-agent.d.ts +6 -0
- package/dist/esm/v3/test/mock-chat-agent.js +1 -0
- package/dist/esm/v3/test/mock-chat-agent.js.map +1 -1
- package/dist/esm/version.js +1 -1
- package/docs/ai/prompts.mdx +430 -0
- package/docs/ai-chat/actions.mdx +115 -0
- package/docs/ai-chat/anatomy.mdx +71 -0
- package/docs/ai-chat/backend.mdx +817 -0
- package/docs/ai-chat/background-injection.mdx +221 -0
- package/docs/ai-chat/changelog.mdx +850 -0
- package/docs/ai-chat/chat-local.mdx +174 -0
- package/docs/ai-chat/client-protocol.mdx +1081 -0
- package/docs/ai-chat/compaction.mdx +411 -0
- package/docs/ai-chat/custom-agents.mdx +364 -0
- package/docs/ai-chat/error-handling.mdx +415 -0
- package/docs/ai-chat/fast-starts.mdx +672 -0
- package/docs/ai-chat/frontend.mdx +580 -0
- package/docs/ai-chat/how-it-works.mdx +230 -0
- package/docs/ai-chat/lifecycle-hooks.mdx +530 -0
- package/docs/ai-chat/mcp.mdx +101 -0
- package/docs/ai-chat/overview.mdx +90 -0
- package/docs/ai-chat/patterns/branching-conversations.mdx +284 -0
- package/docs/ai-chat/patterns/code-sandbox.mdx +126 -0
- package/docs/ai-chat/patterns/database-persistence.mdx +414 -0
- package/docs/ai-chat/patterns/human-in-the-loop.mdx +275 -0
- package/docs/ai-chat/patterns/large-payloads.mdx +169 -0
- package/docs/ai-chat/patterns/oom-resilience.mdx +120 -0
- package/docs/ai-chat/patterns/persistence-and-replay.mdx +211 -0
- package/docs/ai-chat/patterns/recovery-boot.mdx +230 -0
- package/docs/ai-chat/patterns/skills.mdx +221 -0
- package/docs/ai-chat/patterns/sub-agents.mdx +383 -0
- package/docs/ai-chat/patterns/tool-result-auditing.mdx +148 -0
- package/docs/ai-chat/patterns/trusted-edge-signals.mdx +337 -0
- package/docs/ai-chat/patterns/version-upgrades.mdx +172 -0
- package/docs/ai-chat/pending-messages.mdx +343 -0
- package/docs/ai-chat/prompt-caching.mdx +206 -0
- package/docs/ai-chat/quick-start.mdx +161 -0
- package/docs/ai-chat/reference.mdx +909 -0
- package/docs/ai-chat/server-chat.mdx +263 -0
- package/docs/ai-chat/sessions.mdx +333 -0
- package/docs/ai-chat/testing.mdx +682 -0
- package/docs/ai-chat/tools.mdx +191 -0
- package/docs/ai-chat/types.mdx +242 -0
- package/docs/ai-chat/upgrade-guide.mdx +515 -0
- package/docs/apikeys.mdx +54 -0
- package/docs/building-with-ai.mdx +261 -0
- package/docs/bulk-actions.mdx +49 -0
- package/docs/changelog.mdx +6 -0
- package/docs/cli-deploy-commands.mdx +9 -0
- package/docs/cli-dev-commands.mdx +9 -0
- package/docs/cli-dev.mdx +8 -0
- package/docs/cli-init-commands.mdx +58 -0
- package/docs/cli-introduction.mdx +25 -0
- package/docs/cli-list-profiles-commands.mdx +42 -0
- package/docs/cli-login-commands.mdx +33 -0
- package/docs/cli-logout-commands.mdx +33 -0
- package/docs/cli-preview-archive.mdx +59 -0
- package/docs/cli-promote-commands.mdx +9 -0
- package/docs/cli-switch.mdx +43 -0
- package/docs/cli-update-commands.mdx +42 -0
- package/docs/cli-whoami-commands.mdx +33 -0
- package/docs/community.mdx +6 -0
- package/docs/config/config-file.mdx +602 -0
- package/docs/config/extensions/additionalFiles.mdx +38 -0
- package/docs/config/extensions/additionalPackages.mdx +40 -0
- package/docs/config/extensions/aptGet.mdx +34 -0
- package/docs/config/extensions/audioWaveform.mdx +20 -0
- package/docs/config/extensions/custom.mdx +380 -0
- package/docs/config/extensions/emitDecoratorMetadata.mdx +29 -0
- package/docs/config/extensions/esbuildPlugin.mdx +31 -0
- package/docs/config/extensions/ffmpeg.mdx +45 -0
- package/docs/config/extensions/lightpanda.mdx +56 -0
- package/docs/config/extensions/overview.mdx +67 -0
- package/docs/config/extensions/playwright.mdx +195 -0
- package/docs/config/extensions/prismaExtension.mdx +1014 -0
- package/docs/config/extensions/puppeteer.mdx +30 -0
- package/docs/config/extensions/pythonExtension.mdx +182 -0
- package/docs/config/extensions/syncEnvVars.mdx +291 -0
- package/docs/context.mdx +235 -0
- package/docs/database-connections.mdx +213 -0
- package/docs/deploy-environment-variables.mdx +435 -0
- package/docs/deployment/atomic-deployment.mdx +172 -0
- package/docs/deployment/overview.mdx +257 -0
- package/docs/deployment/preview-branches.mdx +224 -0
- package/docs/errors-retrying.mdx +379 -0
- package/docs/github-actions.mdx +222 -0
- package/docs/github-integration.mdx +136 -0
- package/docs/github-repo.mdx +8 -0
- package/docs/help-email.mdx +6 -0
- package/docs/help-slack.mdx +11 -0
- package/docs/hidden-tasks.mdx +56 -0
- package/docs/how-it-works.mdx +454 -0
- package/docs/how-to-reduce-your-spend.mdx +217 -0
- package/docs/idempotency.mdx +504 -0
- package/docs/introduction.mdx +223 -0
- package/docs/limits.mdx +241 -0
- package/docs/logging.mdx +195 -0
- package/docs/machines.mdx +952 -0
- package/docs/manual-setup.mdx +632 -0
- package/docs/mcp-agent-rules.mdx +41 -0
- package/docs/mcp-introduction.mdx +385 -0
- package/docs/mcp-tools.mdx +273 -0
- package/docs/migrating-from-v3.mdx +334 -0
- package/docs/observability/dashboards.mdx +102 -0
- package/docs/observability/query.mdx +585 -0
- package/docs/open-source-contributing.mdx +16 -0
- package/docs/open-source-self-hosting.mdx +541 -0
- package/docs/private-networking/aws-console-setup.mdx +304 -0
- package/docs/private-networking/overview.mdx +144 -0
- package/docs/private-networking/troubleshooting.mdx +78 -0
- package/docs/queue-concurrency.mdx +354 -0
- package/docs/quick-start.mdx +97 -0
- package/docs/realtime/auth.mdx +208 -0
- package/docs/realtime/backend/overview.mdx +45 -0
- package/docs/realtime/backend/streams.mdx +418 -0
- package/docs/realtime/backend/subscribe.mdx +225 -0
- package/docs/realtime/how-it-works.mdx +94 -0
- package/docs/realtime/overview.mdx +63 -0
- package/docs/realtime/react-hooks/overview.mdx +73 -0
- package/docs/realtime/react-hooks/streams.mdx +449 -0
- package/docs/realtime/react-hooks/subscribe.mdx +674 -0
- package/docs/realtime/react-hooks/swr.mdx +87 -0
- package/docs/realtime/react-hooks/triggering.mdx +194 -0
- package/docs/realtime/react-hooks/use-wait-token.mdx +34 -0
- package/docs/realtime/run-object.mdx +174 -0
- package/docs/replaying.mdx +72 -0
- package/docs/request-feature.mdx +6 -0
- package/docs/roadmap.mdx +6 -0
- package/docs/run-tests.mdx +20 -0
- package/docs/run-usage.mdx +113 -0
- package/docs/runs/heartbeats.mdx +38 -0
- package/docs/runs/max-duration.mdx +139 -0
- package/docs/runs/metadata.mdx +734 -0
- package/docs/runs/priority.mdx +31 -0
- package/docs/runs.mdx +396 -0
- package/docs/self-hosting/docker.mdx +458 -0
- package/docs/self-hosting/env/supervisor.mdx +74 -0
- package/docs/self-hosting/env/webapp.mdx +276 -0
- package/docs/self-hosting/kubernetes.mdx +601 -0
- package/docs/self-hosting/overview.mdx +108 -0
- package/docs/skills.mdx +85 -0
- package/docs/tags.mdx +120 -0
- package/docs/tasks/overview.mdx +697 -0
- package/docs/tasks/scheduled.mdx +382 -0
- package/docs/tasks/schemaTask.mdx +413 -0
- package/docs/tasks/streams.mdx +884 -0
- package/docs/triggering.mdx +1320 -0
- package/docs/troubleshooting-alerts.mdx +385 -0
- package/docs/troubleshooting-debugging-in-vscode.mdx +8 -0
- package/docs/troubleshooting-github-issues.mdx +6 -0
- package/docs/troubleshooting-uptime-status.mdx +6 -0
- package/docs/troubleshooting.mdx +398 -0
- package/docs/upgrading-packages.mdx +80 -0
- package/docs/vercel-integration.mdx +207 -0
- package/docs/versioning.mdx +56 -0
- package/docs/video-walkthrough.mdx +23 -0
- package/docs/wait-for-token.mdx +540 -0
- package/docs/wait-for.mdx +42 -0
- package/docs/wait-until.mdx +53 -0
- package/docs/wait.mdx +18 -0
- package/docs/writing-tasks-introduction.mdx +33 -0
- package/package.json +10 -6
- package/skills/trigger-authoring-chat-agent/SKILL.md +296 -0
- package/skills/trigger-authoring-tasks/SKILL.md +254 -0
- package/skills/trigger-chat-agent-advanced/SKILL.md +368 -0
- package/skills/trigger-cost-savings/SKILL.md +116 -0
- 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
|
+
```
|