@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,263 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Server-Side Chat"
|
|
3
|
+
sidebarTitle: "Server-Side Chat"
|
|
4
|
+
description: "Use AgentChat to interact with chat agents from server-side code — tasks, webhooks, scripts, or other agents."
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
import RcBanner from "/snippets/ai-chat-rc-banner.mdx";
|
|
8
|
+
|
|
9
|
+
<RcBanner />
|
|
10
|
+
|
|
11
|
+
`AgentChat` lets you chat with agents from server-side code. It works inside tasks (agent-to-agent), request handlers, webhook processors, and scripts.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { AgentChat } from "@trigger.dev/sdk/chat";
|
|
15
|
+
|
|
16
|
+
const chat = new AgentChat({ agent: "my-agent" });
|
|
17
|
+
const stream = await chat.sendMessage("Hello!");
|
|
18
|
+
const text = await stream.text();
|
|
19
|
+
await chat.close();
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Type-safe client data
|
|
23
|
+
|
|
24
|
+
Pass `typeof yourAgent` as a type parameter and `clientData` is automatically typed from the agent's `withClientData` schema:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { AgentChat } from "@trigger.dev/sdk/chat";
|
|
28
|
+
import type { myAgent } from "./trigger/my-agent";
|
|
29
|
+
|
|
30
|
+
const chat = new AgentChat<typeof myAgent>({
|
|
31
|
+
agent: "my-agent",
|
|
32
|
+
clientData: { userId: "user_123" }, // ← typed from agent definition
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Conversation lifecycle
|
|
37
|
+
|
|
38
|
+
Each `AgentChat` instance represents one conversation. The conversation ID is auto-generated or can be set explicitly:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
// Auto-generated ID
|
|
42
|
+
const chat = new AgentChat({ agent: "my-agent" });
|
|
43
|
+
|
|
44
|
+
// Explicit ID — useful for persistence or finding the run later
|
|
45
|
+
const chat = new AgentChat({ agent: "my-agent", id: `review-${prNumber}` });
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Sending messages
|
|
49
|
+
|
|
50
|
+
`sendMessage()` triggers a new run on the first call, then reuses the same run for subsequent messages via input streams:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
// First message — triggers a new run
|
|
54
|
+
const stream1 = await chat.sendMessage("Review PR #42");
|
|
55
|
+
const review = await stream1.text();
|
|
56
|
+
|
|
57
|
+
// Follow-up — same run, agent has full context
|
|
58
|
+
const stream2 = await chat.sendMessage("Can you fix the main bug?");
|
|
59
|
+
const fix = await stream2.text();
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Preloading (optional)
|
|
63
|
+
|
|
64
|
+
If you want the agent to initialize before the first message (e.g., load data, authenticate), call `preload()`. This is optional — `sendMessage()` triggers the run automatically if needed.
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
await chat.preload();
|
|
68
|
+
// Agent's onPreload hook fires now, before user types anything
|
|
69
|
+
const stream = await chat.sendMessage("Hello");
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Closing
|
|
73
|
+
|
|
74
|
+
Signal the agent to exit its loop gracefully:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
await chat.close();
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Without `close()`, the agent exits on its own when its idle/suspend timeout expires.
|
|
81
|
+
|
|
82
|
+
## Reading responses
|
|
83
|
+
|
|
84
|
+
`sendMessage()` returns a `ChatStream` — a typed wrapper around the response.
|
|
85
|
+
|
|
86
|
+
### Get the full text
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
const stream = await chat.sendMessage("What is Trigger.dev?");
|
|
90
|
+
const text = await stream.text();
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Get structured results
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
const stream = await chat.sendMessage("Research this topic");
|
|
97
|
+
const { text, toolCalls, toolResults } = await stream.result();
|
|
98
|
+
|
|
99
|
+
for (const tc of toolCalls) {
|
|
100
|
+
console.log(`Tool: ${tc.toolName}, Input: ${JSON.stringify(tc.input)}`);
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Stream chunks in real-time
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
const stream = await chat.sendMessage("Write a report");
|
|
108
|
+
|
|
109
|
+
for await (const chunk of stream) {
|
|
110
|
+
if (chunk.type === "text-delta") {
|
|
111
|
+
process.stdout.write(chunk.delta);
|
|
112
|
+
}
|
|
113
|
+
if (chunk.type === "tool-input-available") {
|
|
114
|
+
console.log(`Using tool: ${chunk.toolName}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Stateless request handlers
|
|
120
|
+
|
|
121
|
+
In a stateless environment (HTTP handler, serverless function), you need to persist and restore the session across requests.
|
|
122
|
+
|
|
123
|
+
Each chat is backed by a durable Session row that outlives any single run. `AgentChat` exposes the persistable state via `chat.session` (the SSE resume cursor) and surfaces the current run id via the `onTriggered` callback for telemetry / dashboard linking.
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
import { AgentChat } from "@trigger.dev/sdk/chat";
|
|
127
|
+
|
|
128
|
+
export async function POST(req: Request) {
|
|
129
|
+
const { chatId, message } = await req.json();
|
|
130
|
+
const saved = await db.sessions.find({ chatId });
|
|
131
|
+
|
|
132
|
+
const chat = new AgentChat({
|
|
133
|
+
agent: "my-agent",
|
|
134
|
+
id: chatId,
|
|
135
|
+
// Restore from previous request — `lastEventId` is the SSE resume
|
|
136
|
+
// cursor; the underlying Session is keyed on `chatId` so it's
|
|
137
|
+
// implicit and durable.
|
|
138
|
+
session: saved ? { lastEventId: saved.lastEventId } : undefined,
|
|
139
|
+
// Useful for telemetry / dashboard linking. The `runId` is the
|
|
140
|
+
// current run, which may change across continuations and upgrades.
|
|
141
|
+
onTriggered: async ({ runId }) => {
|
|
142
|
+
await db.sessions.upsert({ chatId, runId });
|
|
143
|
+
},
|
|
144
|
+
// Persist after each turn for stream resumption
|
|
145
|
+
onTurnComplete: async ({ lastEventId }) => {
|
|
146
|
+
await db.sessions.update({ chatId, lastEventId });
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const stream = await chat.sendMessage(message);
|
|
151
|
+
const text = await stream.text();
|
|
152
|
+
|
|
153
|
+
return Response.json({ text });
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
<Info>
|
|
158
|
+
The Session row is the run manager — a chat that was active yesterday
|
|
159
|
+
resumes against the same chatId today, even if the original run has
|
|
160
|
+
long since exited. `AgentChat` (server-side) and `TriggerChatTransport`
|
|
161
|
+
(browser) both rely on this: send a new message and the server
|
|
162
|
+
triggers a fresh continuation run on the same session, carrying the
|
|
163
|
+
conversation forward without losing history or identity.
|
|
164
|
+
</Info>
|
|
165
|
+
|
|
166
|
+
## Sub-agent tool pattern
|
|
167
|
+
|
|
168
|
+
`AgentChat` can be used inside an AI SDK tool to delegate work to a durable sub-agent. The sub-agent's response streams as preliminary tool results:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
import { tool } from "ai";
|
|
172
|
+
import { AgentChat } from "@trigger.dev/sdk/chat";
|
|
173
|
+
import { z } from "zod";
|
|
174
|
+
|
|
175
|
+
const researchTool = tool({
|
|
176
|
+
description: "Delegate research to a specialist agent.",
|
|
177
|
+
inputSchema: z.object({ topic: z.string() }),
|
|
178
|
+
execute: async function* ({ topic }, { abortSignal }) {
|
|
179
|
+
const chat = new AgentChat({ agent: "research-agent" });
|
|
180
|
+
const stream = await chat.sendMessage(topic, { abortSignal });
|
|
181
|
+
yield* stream.messages();
|
|
182
|
+
await chat.close();
|
|
183
|
+
},
|
|
184
|
+
toModelOutput: ({ output: message }) => {
|
|
185
|
+
const lastText = message?.parts?.findLast(
|
|
186
|
+
(p: { type: string }) => p.type === "text"
|
|
187
|
+
) as { text?: string } | undefined;
|
|
188
|
+
return { type: "text", value: lastText?.text ?? "Done." };
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
This supports single-turn delegation, multi-turn LLM-driven conversations with persistent sub-agents, and cross-turn state that survives snapshot/restore.
|
|
194
|
+
|
|
195
|
+
See the [Sub-Agents guide](/ai-chat/patterns/sub-agents) for the full pattern including multi-turn conversations, cleanup, and what the frontend sees.
|
|
196
|
+
|
|
197
|
+
## Additional methods
|
|
198
|
+
|
|
199
|
+
### Steering
|
|
200
|
+
|
|
201
|
+
Send a message during an active stream without interrupting it:
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
await chat.steer("Focus on security issues specifically");
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Stop generation
|
|
208
|
+
|
|
209
|
+
Abort the current `streamText` call without ending the run:
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
await chat.stop();
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Raw messages
|
|
216
|
+
|
|
217
|
+
For full control over the UIMessage shape:
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
const rawStream = await chat.sendRaw([
|
|
221
|
+
{
|
|
222
|
+
id: "msg-1",
|
|
223
|
+
role: "user",
|
|
224
|
+
parts: [
|
|
225
|
+
{ type: "text", text: "Hello" },
|
|
226
|
+
{ type: "file", url: "https://...", mediaType: "image/png" },
|
|
227
|
+
],
|
|
228
|
+
},
|
|
229
|
+
]);
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Reconnect
|
|
233
|
+
|
|
234
|
+
Resume a stream subscription after a disconnect:
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
const stream = await chat.reconnect();
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## AgentChat options
|
|
241
|
+
|
|
242
|
+
| Option | Type | Default | Description |
|
|
243
|
+
|---|---|---|---|
|
|
244
|
+
| `agent` | `string` | required | The agent task ID to trigger |
|
|
245
|
+
| `id` | `string` | `crypto.randomUUID()` | Conversation ID for tagging and correlation |
|
|
246
|
+
| `clientData` | typed from agent | `undefined` | Client data included in every request |
|
|
247
|
+
| `session` | `ChatSession` (`{ lastEventId?: string }`) | `undefined` | Restore a previous session's SSE resume cursor. The Session row itself is keyed on `chatId` (durable) — no other state to thread. |
|
|
248
|
+
| `onTriggered` | `(event) => void` | `undefined` | Called when a new run is created |
|
|
249
|
+
| `onTurnComplete` | `(event) => void` | `undefined` | Called when a turn's stream ends |
|
|
250
|
+
| `streamTimeoutSeconds` | `number` | `120` | SSE timeout in seconds |
|
|
251
|
+
| `triggerConfig` | `SessionTriggerConfig` | `undefined` | Tags, queue, machine, `maxAttempts`, `idleTimeoutInSeconds`, `basePayload` — folded into `sessions.start({...})` |
|
|
252
|
+
| `baseURL` | `string \| (ctx: { endpoint: "in" \| "out"; chatId: string }) => string` | `apiClientManager.baseURL` | API base URL. String form applies to every endpoint; function form picks per endpoint — useful for routing `.in/append` through an edge proxy while keeping `.out` SSE direct. Defaults to whatever `@trigger.dev/sdk` was configured with (typically `TRIGGER_API_URL`). |
|
|
253
|
+
| `fetch` | `(url: string, init: RequestInit, ctx: { endpoint: "in" \| "out"; chatId: string }) => Promise<Response>` | `undefined` | Per-request fetch override. Invoked for both `.in/append` POSTs and the `.out` SSE GET. Use for header injection, custom retries, or proxy rewrites. |
|
|
254
|
+
|
|
255
|
+
## ChatStream methods
|
|
256
|
+
|
|
257
|
+
| Method | Returns | Description |
|
|
258
|
+
|---|---|---|
|
|
259
|
+
| `text()` | `Promise<string>` | Consume stream, return accumulated text |
|
|
260
|
+
| `result()` | `Promise<ChatStreamResult>` | Consume stream, return `{ text, toolCalls, toolResults }` |
|
|
261
|
+
| `messages()` | `AsyncGenerator<UIMessage>` | Yield accumulated UIMessage snapshots (sub-agent pattern) |
|
|
262
|
+
| `[Symbol.asyncIterator]` | `UIMessageChunk` | Iterate over typed stream chunks |
|
|
263
|
+
| `.stream` | `ReadableStream<UIMessageChunk>` | Raw stream for AI SDK utilities |
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Sessions"
|
|
3
|
+
sidebarTitle: "Sessions"
|
|
4
|
+
description: "A Session is a stateful execution of an agent, with two-way streaming and durable compute. A single Session can have multiple runs associated with it."
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
import RcBanner from "/snippets/ai-chat-rc-banner.mdx";
|
|
8
|
+
|
|
9
|
+
<RcBanner />
|
|
10
|
+
|
|
11
|
+
**A Session is a stateful execution of an agent.** It includes two-way streaming and durable compute, and a single Session can have multiple runs associated with it.
|
|
12
|
+
|
|
13
|
+
The **two-way streaming** is a pair of durable streams. The input stream (`.in`) carries incoming user messages to your task. The output stream (`.out`) carries everything the agent produces back to your clients: AI generation parts (text, reasoning, tool calls) and any custom data parts you write.
|
|
14
|
+
|
|
15
|
+
The **durable compute** is the runs that process those streams. A Session is keyed on your stable id (`externalId` — for chat, the `chatId`) and owns its current run: when a run suspends, idles out, or hands off to a new version, the Session starts or swaps to a fresh run and the streams carry on. Clients keep sending and reading against the same id; they never know a run changed underneath.
|
|
16
|
+
|
|
17
|
+
```mermaid
|
|
18
|
+
flowchart LR
|
|
19
|
+
C[Browser / backend clients] -- "user messages" --> IN([Session .in])
|
|
20
|
+
IN --> R["current run<br/>(runs come and go)"]
|
|
21
|
+
R -- "text, reasoning, tool calls,<br/>data parts" --> OUT([Session .out])
|
|
22
|
+
OUT --> C
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`chat.agent` is built on Sessions. You can also use them directly for any pattern that needs durable bi-directional streaming across runs: long-lived agent inboxes, multi-step approval flows, server-to-server pipelines that survive worker restarts.
|
|
26
|
+
|
|
27
|
+
## A minimal example
|
|
28
|
+
|
|
29
|
+
A task that echoes whatever lands on its input stream, and a backend that starts the session, sends a message, and reads the reply:
|
|
30
|
+
|
|
31
|
+
```ts trigger/inbox.ts
|
|
32
|
+
import { task, sessions } from "@trigger.dev/sdk";
|
|
33
|
+
|
|
34
|
+
export const inboxAgent = task({
|
|
35
|
+
id: "inbox-agent",
|
|
36
|
+
run: async (payload: { sessionId: string }) => {
|
|
37
|
+
const session = sessions.open(payload.sessionId);
|
|
38
|
+
|
|
39
|
+
while (true) {
|
|
40
|
+
// Suspends the run (no compute billed) until a record arrives.
|
|
41
|
+
const next = await session.in.wait<{ text: string }>({ timeout: "1h" });
|
|
42
|
+
if (!next.ok) return;
|
|
43
|
+
await session.out.append({ type: "reply", text: `echo: ${next.output.text}` });
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```ts Your backend
|
|
50
|
+
import { sessions } from "@trigger.dev/sdk";
|
|
51
|
+
|
|
52
|
+
// Atomically create the session AND trigger its first run.
|
|
53
|
+
await sessions.start({
|
|
54
|
+
type: "inbox",
|
|
55
|
+
externalId: userId,
|
|
56
|
+
taskIdentifier: "inbox-agent",
|
|
57
|
+
triggerConfig: { basePayload: { sessionId: userId } },
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const session = sessions.open(userId);
|
|
61
|
+
await session.in.send({ text: "hello" });
|
|
62
|
+
|
|
63
|
+
const stream = await session.out.read({ signal: AbortSignal.timeout(30_000) });
|
|
64
|
+
for await (const chunk of stream) {
|
|
65
|
+
console.log(chunk); // { type: "reply", text: "echo: hello" }
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The run can suspend, crash, or be replaced between the `send` and the `read` — the streams are durable, so nothing is lost and the client code doesn't change.
|
|
70
|
+
|
|
71
|
+
## Sessions and runs
|
|
72
|
+
|
|
73
|
+
One Session spans many runs over its lifetime. The Session row tracks `currentRunId`; the runs do the work:
|
|
74
|
+
|
|
75
|
+
- **First run**: created atomically by `sessions.start` (no gap where the session exists but nothing is listening).
|
|
76
|
+
- **Idle suspend**: a run blocked on `in.wait` suspends and frees compute. A new record on `.in` wakes it.
|
|
77
|
+
- **Continuation**: when a run ends (idle timeout, `chat.endRun`, a crash, a version upgrade), the next incoming record triggers a fresh run against the same Session. The new run picks up the streams where the old one left off.
|
|
78
|
+
|
|
79
|
+
This is what makes a Session the durable identity for a conversation: runs are an execution detail, the Session (and its `externalId`) is what your clients address. See [How it works](/ai-chat/how-it-works) for how `chat.agent` drives this loop.
|
|
80
|
+
|
|
81
|
+
## When to reach for Sessions directly
|
|
82
|
+
|
|
83
|
+
`chat.agent` handles 90% of chat-shaped workloads — message accumulation, the turn loop, stop signals, lifecycle hooks. Use the raw `sessions` API when you need any of:
|
|
84
|
+
|
|
85
|
+
- **Non-chat conversational state**: an agent inbox where each "turn" is a webhook event rather than a UI message.
|
|
86
|
+
- **Server-to-server bi-directional streaming** where an external service produces records the task consumes (and vice-versa) over the same durable channel.
|
|
87
|
+
- **A custom turn loop** where the agent abstraction doesn't fit but you still want session-survival across runs.
|
|
88
|
+
|
|
89
|
+
For chat use cases, prefer [`chat.agent`](/ai-chat/backend#chat-agent) or [`chat.createSession`](/ai-chat/backend#chat-createsession).
|
|
90
|
+
|
|
91
|
+
## `sessions` namespace
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
import { sessions } from "@trigger.dev/sdk";
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### `sessions.start(body, requestOptions?)`
|
|
98
|
+
|
|
99
|
+
Atomically create a Session row and trigger its first run. Idempotent on `(env, externalId)` — two concurrent calls with the same `externalId` converge to one session.
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
const { id, runId, publicAccessToken, isCached } = await sessions.start({
|
|
103
|
+
type: "chat.agent",
|
|
104
|
+
externalId: chatId,
|
|
105
|
+
taskIdentifier: "my-chat",
|
|
106
|
+
triggerConfig: {
|
|
107
|
+
tags: [`chat:${chatId}`],
|
|
108
|
+
basePayload: { /* whatever your task's payload shape is */ },
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
| Field | Type | Notes |
|
|
114
|
+
|---|---|---|
|
|
115
|
+
| `type` | `string` | Free-form discriminator. `chat.agent` uses `"chat.agent"`. |
|
|
116
|
+
| `externalId` | `string?` | Your stable identity. Cannot start with `session_` (reserved). |
|
|
117
|
+
| `taskIdentifier` | `string` | Task this session triggers runs against. |
|
|
118
|
+
| `triggerConfig` | `SessionTriggerConfig` | Trigger options applied to every run: `tags`, `queue`, `machine`, `maxAttempts`, `idleTimeoutInSeconds`, `basePayload`. |
|
|
119
|
+
| `tags` | `string[]?` | Up to 10 tags on the Session row (separate from `triggerConfig.tags`). |
|
|
120
|
+
| `metadata` | `Record<string, unknown>?` | Arbitrary JSON. |
|
|
121
|
+
| `expiresAt` | `Date?` | Hard retention deadline. |
|
|
122
|
+
|
|
123
|
+
Returns `CreatedSessionResponseBody`:
|
|
124
|
+
|
|
125
|
+
| Field | Type | Notes |
|
|
126
|
+
|---|---|---|
|
|
127
|
+
| `id` | `string` | Server-assigned `session_*` friendlyId. |
|
|
128
|
+
| `runId` | `string` | The first run created alongside the session. |
|
|
129
|
+
| `publicAccessToken` | `string` | Session-scoped PAT (`read:sessions:{id} + write:sessions:{id}`). |
|
|
130
|
+
| `isCached` | `boolean` | `true` if the session already existed (idempotent upsert). |
|
|
131
|
+
|
|
132
|
+
### `sessions.retrieve(idOrExternalId, requestOptions?)`
|
|
133
|
+
|
|
134
|
+
Retrieve a Session by either its server-assigned `session_*` id or your user-supplied `externalId`. The server disambiguates via the `session_` prefix.
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
const session = await sessions.retrieve(chatId);
|
|
138
|
+
console.log(session.currentRunId, session.tags, session.closedAt);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### `sessions.update(idOrExternalId, body, requestOptions?)`
|
|
142
|
+
|
|
143
|
+
Mutate `tags` or `metadata` on an existing Session. `externalId` is read-only after create: it cannot be changed or cleared (it keys the session's durable streams and token scope), so sending a different value returns `422`.
|
|
144
|
+
|
|
145
|
+
### `sessions.close(idOrExternalId, body?, requestOptions?)`
|
|
146
|
+
|
|
147
|
+
Mark a Session as closed. Terminal and idempotent. The optional `reason` is stored on the row.
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
await sessions.close(chatId, { reason: "user signed out" });
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### `sessions.list(options?, requestOptions?)`
|
|
154
|
+
|
|
155
|
+
Cursor-paginated list of Sessions in the current environment. Returns a `CursorPagePromise` you can iterate with `for await`.
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
for await (const s of sessions.list({
|
|
159
|
+
type: "chat.agent",
|
|
160
|
+
tag: `user:${userId}`,
|
|
161
|
+
status: "ACTIVE",
|
|
162
|
+
limit: 50,
|
|
163
|
+
})) {
|
|
164
|
+
console.log(s.id, s.externalId, s.createdAt);
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
| Filter | Type | Notes |
|
|
169
|
+
|---|---|---|
|
|
170
|
+
| `type` | `string \| string[]` | e.g. `"chat.agent"` |
|
|
171
|
+
| `tag` | `string \| string[]` | Matches `triggerConfig.tags` |
|
|
172
|
+
| `taskIdentifier` | `string \| string[]` | Filter by task |
|
|
173
|
+
| `externalId` | `string` | Exact match |
|
|
174
|
+
| `status` | `"ACTIVE" \| "CLOSED" \| "EXPIRED"` | Lifecycle state |
|
|
175
|
+
| `period` / `from` / `to` | window | Time-range filter |
|
|
176
|
+
| `limit` / `after` / `before` | cursor | Pagination (1–100 per page; default 20) |
|
|
177
|
+
|
|
178
|
+
### `sessions.open(idOrExternalId)`
|
|
179
|
+
|
|
180
|
+
Open a lightweight `SessionHandle` to the realtime channels. Does **not** hit the network — each handle method calls the corresponding endpoint lazily.
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
const session = sessions.open(chatId);
|
|
184
|
+
await session.out.append({ kind: "message", text: "hello" });
|
|
185
|
+
const next = await session.in.once<MyEvent>({ timeoutMs: 30_000 });
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## `SessionHandle`
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
class SessionHandle {
|
|
192
|
+
readonly id: string;
|
|
193
|
+
readonly in: SessionInputChannel;
|
|
194
|
+
readonly out: SessionOutputChannel;
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
The two channels mirror the producer/consumer pair in `streams.define` (out) and `streams.input` (in), but are **session-scoped** rather than run-scoped — they survive across run boundaries.
|
|
199
|
+
|
|
200
|
+
## `session.out` — task → clients
|
|
201
|
+
|
|
202
|
+
The output channel. The task writes; external clients (browser, server action, another task) read via SSE. The underlying HTTP endpoints are documented in [Session channels](/management/sessions/channels) for non-SDK callers.
|
|
203
|
+
|
|
204
|
+
### `out.append(value, options?)`
|
|
205
|
+
|
|
206
|
+
Append a single record. Routes through `writer` internally so SSE consumers see the same parsed-object shape on every event.
|
|
207
|
+
|
|
208
|
+
### `out.pipe(stream, options?)`
|
|
209
|
+
|
|
210
|
+
Pipe an `AsyncIterable` or `ReadableStream` directly to S2 (the durable backing store). Returns `{ stream, waitUntilComplete }`.
|
|
211
|
+
|
|
212
|
+
### `out.writer({ execute, ... })`
|
|
213
|
+
|
|
214
|
+
Imperative writer. `execute({ write, merge })` runs against an in-memory queue whose records are piped to S2.
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
session.out.writer<MyChunk>({
|
|
218
|
+
execute: ({ write }) => {
|
|
219
|
+
write({ type: "text", text: "hi" });
|
|
220
|
+
write({ type: "text", text: " there" });
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### `out.read(options?)`
|
|
226
|
+
|
|
227
|
+
Subscribe to SSE records on `.out`. Returns an async-iterable stream with auto-retry and `Last-Event-ID` resume.
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
const stream = await session.out.read<MyChunk>({
|
|
231
|
+
signal: AbortSignal.timeout(30_000),
|
|
232
|
+
lastEventId: lastSeenSeqNum,
|
|
233
|
+
});
|
|
234
|
+
for await (const chunk of stream) {
|
|
235
|
+
// ...
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### `out.writeControl(subtype, extraHeaders?)`
|
|
240
|
+
|
|
241
|
+
Write a Trigger control record. Carries a `trigger-control` header valued with `subtype` (e.g. `turn-complete`, `upgrade-required`); the body is empty. The SDK transport filters control records out of the consumer-facing chunk stream — readers route them via `onControl` instead.
|
|
242
|
+
|
|
243
|
+
Returns `{ lastEventId }` — useful for trim chains.
|
|
244
|
+
|
|
245
|
+
### `out.trimTo(earliestSeqNum)`
|
|
246
|
+
|
|
247
|
+
Append an S2 `trim` command. Records with `seq_num < earliestSeqNum` are eventually deleted. Idempotent and monotonic. `chat.agent` uses this to keep `session.out` bounded to roughly one turn at steady state.
|
|
248
|
+
|
|
249
|
+
## `session.in` — clients → task
|
|
250
|
+
|
|
251
|
+
The input channel. External clients call `send`; the task consumes via `on` / `once` / `peek` / `wait` / `waitWithIdleTimeout`. The underlying HTTP endpoints are documented in [Session channels](/management/sessions/channels) for non-SDK callers.
|
|
252
|
+
|
|
253
|
+
### `in.send(value, requestOptions?)`
|
|
254
|
+
|
|
255
|
+
Append a single record. Called from outside the task (browser, server action, another task).
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
const session = sessions.open(chatId);
|
|
259
|
+
await session.in.send({ kind: "user-event", payload: { ... } });
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### `in.on(handler)`
|
|
263
|
+
|
|
264
|
+
Register a handler that fires for every record landing on `.in`. Buffered records flush on attach. Returns `{ off }`.
|
|
265
|
+
|
|
266
|
+
### `in.once(options?)`
|
|
267
|
+
|
|
268
|
+
Wait for the next record without suspending the run. `{ ok: true, output }` or `{ ok: false, error }` on timeout. Chain `.unwrap()` to get the data directly.
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
const result = await session.in.once<MyEvent>({ timeoutMs: 5_000 });
|
|
272
|
+
if (result.ok) handle(result.output);
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### `in.peek()`
|
|
276
|
+
|
|
277
|
+
Non-blocking peek at the head of the `.in` buffer.
|
|
278
|
+
|
|
279
|
+
### `in.wait(options?)`
|
|
280
|
+
|
|
281
|
+
Suspend the current run until the next record arrives — frees compute while blocked. Only callable from inside `task.run()`.
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
const next = await session.in.wait<MyEvent>({ timeout: "1h" });
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### `in.waitWithIdleTimeout({ idleTimeoutInSeconds, timeout, ... })`
|
|
288
|
+
|
|
289
|
+
Hybrid: stay warm for `idleTimeoutInSeconds`, then suspend via `wait` if nothing arrives. `chat.agent`'s turn loop uses this to balance responsiveness and cost.
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
const next = await session.in.waitWithIdleTimeout<MyEvent>({
|
|
293
|
+
idleTimeoutInSeconds: 30,
|
|
294
|
+
timeout: "1h",
|
|
295
|
+
onSuspend: () => { /* persist before suspending */ },
|
|
296
|
+
onResume: () => { /* re-hydrate after resume */ },
|
|
297
|
+
});
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### `in.lastDispatchedSeqNum()`
|
|
301
|
+
|
|
302
|
+
The highest S2 `seq_num` this channel has delivered to a consumer. Used by `chat.agent` to persist a resume cursor on each `turn-complete` so the next worker boot subscribes past already-processed records.
|
|
303
|
+
|
|
304
|
+
## Authorization
|
|
305
|
+
|
|
306
|
+
Browser and server-side clients use a session-scoped Public Access Token:
|
|
307
|
+
|
|
308
|
+
```ts
|
|
309
|
+
import { auth } from "@trigger.dev/sdk";
|
|
310
|
+
|
|
311
|
+
const pat = await auth.createPublicToken({
|
|
312
|
+
scopes: {
|
|
313
|
+
read: { sessions: chatId },
|
|
314
|
+
write: { sessions: chatId },
|
|
315
|
+
},
|
|
316
|
+
expirationTime: "1h",
|
|
317
|
+
});
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Tokens authorize **both** URL forms: `/sessions/{externalId}/...` and `/sessions/session_*/...`.
|
|
321
|
+
|
|
322
|
+
For the `chat.agent` transport, `auth.createPublicToken` is wrapped by `accessToken` in `useTriggerChatTransport`; for direct session access from your server, mint a token per request just like any other realtime resource.
|
|
323
|
+
|
|
324
|
+
See [Session scopes](/management/authentication#session-scopes) for exactly what `read:sessions` and `write:sessions` grant, and why updating, closing, and appending to `.out` require a secret key.
|
|
325
|
+
|
|
326
|
+
## See also
|
|
327
|
+
|
|
328
|
+
- [Sessions HTTP API](/management/sessions/create) — The REST endpoints for creating, listing, retrieving, updating, and closing sessions, plus the [channel endpoints](/management/sessions/channels) for non-SDK callers.
|
|
329
|
+
- [Session scopes](/management/authentication#session-scopes) — The public-token scopes that authorize session and channel access.
|
|
330
|
+
- [How it works](/ai-chat/how-it-works) — How `chat.agent` builds on Sessions.
|
|
331
|
+
- [Backend](/ai-chat/backend) — `chat.agent` / `chat.createSession` / raw `task()` with chat primitives.
|
|
332
|
+
- [Client Protocol](/ai-chat/client-protocol) — The wire-level view of `.in/append` and `.out` SSE.
|
|
333
|
+
- [Persistence and replay](/ai-chat/patterns/persistence-and-replay) — How tails are read at boot.
|