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