@trigger.dev/sdk 0.0.0-prerelease-20260908122921 → 0.0.0-prerelease-streamfix-20260909094302
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/imports/ai-runtime-cjs.cjs.map +1 -1
- package/dist/commonjs/imports/ai-runtime.js +0 -2
- package/dist/commonjs/v3/ai.d.ts +16 -199
- package/dist/commonjs/v3/ai.js +102 -983
- package/dist/commonjs/v3/ai.js.map +1 -1
- package/dist/commonjs/v3/chat-client.d.ts +2 -3
- package/dist/commonjs/v3/chat-client.js +5 -31
- package/dist/commonjs/v3/chat-client.js.map +1 -1
- package/dist/commonjs/v3/chat-react.d.ts +0 -34
- package/dist/commonjs/v3/chat-react.js +1 -47
- package/dist/commonjs/v3/chat-react.js.map +1 -1
- package/dist/commonjs/v3/chat-server.d.ts +6 -42
- package/dist/commonjs/v3/chat-server.js +7 -52
- package/dist/commonjs/v3/chat-server.js.map +1 -1
- package/dist/commonjs/v3/chat.d.ts +10 -81
- package/dist/commonjs/v3/chat.js +46 -292
- package/dist/commonjs/v3/chat.js.map +1 -1
- package/dist/commonjs/v3/sessions.d.ts +2 -15
- package/dist/commonjs/v3/sessions.js +1 -12
- package/dist/commonjs/v3/sessions.js.map +1 -1
- package/dist/commonjs/v3/shared.js +36 -30
- package/dist/commonjs/v3/shared.js.map +1 -1
- package/dist/commonjs/v3/test/mock-chat-agent.d.ts +0 -43
- package/dist/commonjs/v3/test/mock-chat-agent.js +0 -90
- package/dist/commonjs/v3/test/mock-chat-agent.js.map +1 -1
- package/dist/commonjs/v3/test/test-session-handle.js +0 -6
- package/dist/commonjs/v3/test/test-session-handle.js.map +1 -1
- package/dist/commonjs/version.js +1 -1
- package/dist/esm/imports/ai-runtime.d.ts +2 -2
- package/dist/esm/imports/ai-runtime.js +2 -2
- package/dist/esm/imports/ai-runtime.js.map +1 -1
- package/dist/esm/v3/ai.d.ts +16 -199
- package/dist/esm/v3/ai.js +103 -984
- package/dist/esm/v3/ai.js.map +1 -1
- package/dist/esm/v3/chat-client.d.ts +2 -3
- package/dist/esm/v3/chat-client.js +5 -31
- package/dist/esm/v3/chat-client.js.map +1 -1
- package/dist/esm/v3/chat-react.d.ts +0 -34
- package/dist/esm/v3/chat-react.js +1 -46
- package/dist/esm/v3/chat-react.js.map +1 -1
- package/dist/esm/v3/chat-server.d.ts +6 -42
- package/dist/esm/v3/chat-server.js +8 -53
- package/dist/esm/v3/chat-server.js.map +1 -1
- package/dist/esm/v3/chat.d.ts +10 -81
- package/dist/esm/v3/chat.js +47 -293
- package/dist/esm/v3/chat.js.map +1 -1
- package/dist/esm/v3/sessions.d.ts +2 -15
- package/dist/esm/v3/sessions.js +1 -11
- package/dist/esm/v3/sessions.js.map +1 -1
- package/dist/esm/v3/shared.js +23 -17
- package/dist/esm/v3/shared.js.map +1 -1
- package/dist/esm/v3/test/mock-chat-agent.d.ts +0 -43
- package/dist/esm/v3/test/mock-chat-agent.js +2 -92
- package/dist/esm/v3/test/mock-chat-agent.js.map +1 -1
- package/dist/esm/v3/test/test-session-handle.js +0 -6
- package/dist/esm/v3/test/test-session-handle.js.map +1 -1
- package/dist/esm/version.js +1 -1
- package/docs/ai-chat/actions.mdx +23 -55
- package/docs/ai-chat/anatomy.mdx +3 -3
- package/docs/ai-chat/backend.mdx +48 -125
- package/docs/ai-chat/background-injection.mdx +19 -67
- package/docs/ai-chat/client-protocol.mdx +4 -5
- package/docs/ai-chat/compaction.mdx +7 -11
- package/docs/ai-chat/custom-agents.mdx +0 -23
- package/docs/ai-chat/fast-starts.mdx +20 -27
- package/docs/ai-chat/frontend.mdx +14 -17
- package/docs/ai-chat/migrating-from-a-route-handler.mdx +14 -16
- package/docs/ai-chat/patterns/skills.mdx +10 -7
- package/docs/ai-chat/patterns/version-upgrades.mdx +6 -79
- package/docs/ai-chat/pending-messages.mdx +3 -3
- package/docs/ai-chat/prompt-caching.mdx +25 -23
- package/docs/ai-chat/quick-start.mdx +11 -11
- package/docs/ai-chat/reference.mdx +5 -12
- package/docs/ai-chat/sessions.mdx +1 -6
- package/docs/ai-chat/testing.mdx +1 -2
- package/docs/ai-chat/tools.mdx +13 -18
- package/docs/ai-chat/upgrade-guide.mdx +2 -2
- package/docs/apikeys.mdx +45 -27
- package/docs/deployment/overview.mdx +8 -4
- package/docs/deployment/preview-branches.mdx +4 -4
- package/docs/deployment/version-skew-protection.mdx +0 -62
- package/docs/manual-setup.mdx +7 -7
- package/docs/mcp-tools.mdx +0 -9
- package/docs/quick-start.mdx +3 -3
- package/docs/realtime/auth.mdx +1 -1
- package/docs/self-hosting/security.mdx +0 -5
- package/docs/tasks/scheduled.mdx +0 -24
- package/docs/triggering.mdx +1 -1
- package/package.json +4 -4
- package/skills/trigger-authoring-chat-agent/SKILL.md +27 -38
- package/skills/trigger-chat-agent-advanced/SKILL.md +12 -31
- package/dist/commonjs/v3/chatVersionSkew.d.ts +0 -12
- package/dist/commonjs/v3/chatVersionSkew.js +0 -30
- package/dist/commonjs/v3/chatVersionSkew.js.map +0 -1
- package/dist/commonjs/v3/externalDeploymentId.d.ts +0 -23
- package/dist/commonjs/v3/externalDeploymentId.js +0 -43
- package/dist/commonjs/v3/externalDeploymentId.js.map +0 -1
- package/dist/esm/v3/chatVersionSkew.d.ts +0 -12
- package/dist/esm/v3/chatVersionSkew.js +0 -27
- package/dist/esm/v3/chatVersionSkew.js.map +0 -1
- package/dist/esm/v3/externalDeploymentId.d.ts +0 -23
- package/dist/esm/v3/externalDeploymentId.js +0 -38
- package/dist/esm/v3/externalDeploymentId.js.map +0 -1
- package/docs/ai-chat/patterns/native-compaction.mdx +0 -310
- package/docs/reports.mdx +0 -157
- package/docs/troubleshooting-zod.mdx +0 -158
|
@@ -79,7 +79,7 @@ The **body** is loaded on demand via the `loadSkill` tool when the agent decides
|
|
|
79
79
|
```ts trigger/chat.ts
|
|
80
80
|
import { chat } from "@trigger.dev/sdk/ai";
|
|
81
81
|
import { skills } from "@trigger.dev/sdk";
|
|
82
|
-
import { stepCountIs } from "ai";
|
|
82
|
+
import { streamText, stepCountIs } from "ai";
|
|
83
83
|
import { anthropic } from "@ai-sdk/anthropic";
|
|
84
84
|
|
|
85
85
|
const timeUtilsSkill = skills.define({
|
|
@@ -92,11 +92,12 @@ export const agent = chat.agent({
|
|
|
92
92
|
onChatStart: async () => {
|
|
93
93
|
chat.skills.set([await timeUtilsSkill.local()]);
|
|
94
94
|
},
|
|
95
|
-
run: async ({ messages, signal
|
|
95
|
+
run: async ({ messages, signal }) => {
|
|
96
96
|
return streamText({
|
|
97
97
|
model: anthropic("claude-sonnet-4-5"),
|
|
98
98
|
messages,
|
|
99
99
|
abortSignal: signal,
|
|
100
|
+
...chat.toStreamTextOptions(),
|
|
100
101
|
stopWhen: stepCountIs(15),
|
|
101
102
|
});
|
|
102
103
|
},
|
|
@@ -110,7 +111,7 @@ export const agent = chat.agent({
|
|
|
110
111
|
|
|
111
112
|
`skill.local()` reads the bundled `SKILL.md` from disk and returns a `ResolvedSkill` with the parsed frontmatter + body + on-disk path.
|
|
112
113
|
|
|
113
|
-
`chat.skills.set([...])` stores the resolved skills for the current run.
|
|
114
|
+
`chat.skills.set([...])` stores the resolved skills for the current run. `chat.toStreamTextOptions()` spreads them into `streamText` automatically:
|
|
114
115
|
|
|
115
116
|
- The frontmatter `description` lands in the system prompt under "Available skills:".
|
|
116
117
|
- Three tools are added: `loadSkill`, `readFile`, `bash` — scoped per skill.
|
|
@@ -168,10 +169,12 @@ return streamText({
|
|
|
168
169
|
model: anthropic("claude-sonnet-4-5"),
|
|
169
170
|
messages,
|
|
170
171
|
abortSignal: signal,
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
172
|
+
...chat.toStreamTextOptions({
|
|
173
|
+
tools: {
|
|
174
|
+
webFetch, // your tool
|
|
175
|
+
deepResearch, // your tool
|
|
176
|
+
},
|
|
177
|
+
}),
|
|
175
178
|
stopWhen: stepCountIs(15),
|
|
176
179
|
});
|
|
177
180
|
```
|
|
@@ -8,17 +8,6 @@ Chat agent runs are pinned to the worker version they started on. When you deplo
|
|
|
8
8
|
|
|
9
9
|
`chat.requestUpgrade()` is the managed upgrade signal for `chat.agent()` and the `chat.createSession()` iterator. Fully hand-rolled custom agents use `chat.endAndContinue()` between turns to immediately hand the Session to a new run.
|
|
10
10
|
|
|
11
|
-
<Note>
|
|
12
|
-
If your sessions are pinned by [version skew
|
|
13
|
-
protection](/deployment/version-skew-protection#chat-sessions), you do not need this page to move a
|
|
14
|
-
conversation onto a new deployment. A pinned session follows its pin on its own: when the stored
|
|
15
|
-
`externalDeploymentId` stops naming the deployment a run is on, the agent hands over at the next
|
|
16
|
-
turn boundary. Set [`versionSkew: "hold"`](#staying-put) to turn that off for one agent.
|
|
17
|
-
|
|
18
|
-
Read on for the cases that are still yours to decide — leaving a pin for a version nobody named,
|
|
19
|
-
a session that was never pinned, and hand-rolled custom agents.
|
|
20
|
-
</Note>
|
|
21
|
-
|
|
22
11
|
## How it works
|
|
23
12
|
|
|
24
13
|
When `chat.requestUpgrade()` is called in `onTurnStart` or `onValidateMessages`:
|
|
@@ -30,44 +19,22 @@ When `chat.requestUpgrade()` is called in `onTurnStart` or `onValidateMessages`:
|
|
|
30
19
|
|
|
31
20
|
The new run lives on the **same Session** as the old one. `chatId` is the durable identity; only the underlying `currentRunId` rotates. The audit log records the new run with `reason: "upgrade"`.
|
|
32
21
|
|
|
33
|
-
### What "the latest deployment" means
|
|
34
|
-
|
|
35
|
-
The handoff clears the session's [external deployment id](/deployment/version-skew-protection#chat-sessions) so the new run can land on the current version — re-applying the pin the agent just rejected would make the upgrade impossible. The cleared pin is persisted on the session, so the next continuation doesn't fall back to it either.
|
|
36
|
-
|
|
37
|
-
To move to a specific deployment rather than to whatever is current, name it:
|
|
38
|
-
|
|
39
|
-
```ts
|
|
40
|
-
chat.requestUpgrade({ externalDeploymentId: clientData.commitSha });
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
That is usually what you want when the client told you which build it is on: it upgrades to the version the client expects instead of merely to the newest one.
|
|
44
|
-
|
|
45
|
-
<Warning>
|
|
46
|
-
`lockToVersion` is a different thing and is **never** cleared. A session started with an explicit
|
|
47
|
-
`lockToVersion` re-applies it on every run including upgrade handoffs, so `chat.requestUpgrade()`
|
|
48
|
-
cannot escape it — the new run lands on the same version the old one did. Use the external
|
|
49
|
-
deployment id if you want a pin an agent can opt out of.
|
|
50
|
-
</Warning>
|
|
51
|
-
|
|
52
22
|
When called from inside `run()` or `chat.defer()`, the current turn completes normally first and the run exits afterward. The next message triggers the continuation on the same session.
|
|
53
23
|
|
|
54
24
|
```mermaid
|
|
55
25
|
sequenceDiagram
|
|
56
26
|
participant User
|
|
57
27
|
participant Transport
|
|
58
|
-
participant Session as session.in
|
|
59
28
|
participant RunV1 as Run (v1)
|
|
60
29
|
participant RunV2 as Run (v2)
|
|
61
30
|
|
|
62
31
|
User->>Transport: send message
|
|
63
|
-
Transport->>
|
|
64
|
-
Session->>RunV1: input stream
|
|
32
|
+
Transport->>RunV1: input stream
|
|
65
33
|
RunV1->>RunV1: onTurnStart → requestUpgrade()
|
|
66
|
-
RunV1
|
|
67
|
-
RunV1
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
RunV2-->>Transport: response stream (same session.out)
|
|
34
|
+
RunV1-->>Transport: trigger:upgrade-required
|
|
35
|
+
RunV1->>RunV1: exit (run() never called)
|
|
36
|
+
Transport->>RunV2: trigger new run (continuation, same message)
|
|
37
|
+
RunV2-->>Transport: response stream
|
|
71
38
|
Transport-->>User: response (seamless)
|
|
72
39
|
```
|
|
73
40
|
|
|
@@ -136,13 +103,6 @@ This pattern is useful when:
|
|
|
136
103
|
|
|
137
104
|
## Auto-detect from build ID (Next.js / Vercel)
|
|
138
105
|
|
|
139
|
-
<Warning>
|
|
140
|
-
You probably don't need this any more. If your sessions are pinned, following the pin is the
|
|
141
|
-
built-in behaviour and it needs no `clientData` and no `chat.local`. Reach for the recipe below
|
|
142
|
-
only when you want to upgrade on a signal the pin doesn't carry — a frontend build id that moves
|
|
143
|
-
independently of the deployment your app names.
|
|
144
|
-
</Warning>
|
|
145
|
-
|
|
146
106
|
For automatic upgrade on every deploy, pass your platform's build ID via `clientData` instead of a manual version. The agent stores the ID from the first message and upgrades when it changes:
|
|
147
107
|
|
|
148
108
|
```tsx title="app/components/Chat.tsx"
|
|
@@ -191,38 +151,6 @@ export const myChat = chat
|
|
|
191
151
|
|
|
192
152
|
This upgrades on **every** deploy, not just breaking changes. Good for fast-moving projects where you always want the latest code.
|
|
193
153
|
|
|
194
|
-
## Staying put
|
|
195
|
-
|
|
196
|
-
A pinned session follows its pin by default. To keep one agent where it is — a long tool chain you
|
|
197
|
-
don't want interrupted, or a conversation you'd rather move on your own terms — set `versionSkew`:
|
|
198
|
-
|
|
199
|
-
```ts
|
|
200
|
-
export const myChat = chat.agent({
|
|
201
|
-
id: "my-chat",
|
|
202
|
-
versionSkew: "hold",
|
|
203
|
-
run: async ({ messages, signal }) => { ... },
|
|
204
|
-
});
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
`"hold"` only stops the automatic handoff. `chat.requestUpgrade()` still works, so you can keep the
|
|
208
|
-
decision and still get the seamless swap.
|
|
209
|
-
|
|
210
|
-
Two cases never hand over automatically, whatever `versionSkew` says:
|
|
211
|
-
|
|
212
|
-
- **A session with no pin.** There is nothing to compare against, and an unpinned session already
|
|
213
|
-
lands on the current version every time it starts a run.
|
|
214
|
-
- **A session using `lockToVersion`.** That pin outranks the external deployment id and
|
|
215
|
-
`chat.requestUpgrade()` cannot escape it, so handing over would land on the same version and
|
|
216
|
-
repeat.
|
|
217
|
-
|
|
218
|
-
<Note>
|
|
219
|
-
Following the pin costs one session read per turn on pinned chats, and the handoff happens at a
|
|
220
|
-
turn boundary — never mid-turn. If the pin names a deployment that hasn't landed yet, the successor
|
|
221
|
-
parks: your messages stay durable, and the transport emits `run-pending-version` with
|
|
222
|
-
`source: "upgrade"` so you can say so in the UI. See [parked
|
|
223
|
-
chats](/deployment/version-skew-protection#chat-sessions).
|
|
224
|
-
</Note>
|
|
225
|
-
|
|
226
154
|
## Custom agents
|
|
227
155
|
|
|
228
156
|
Use `chat.requestUpgrade()` with `chat.agent()`. With `chat.createSession()`, call `chat.requestUpgrade()`, then advance the iterator once more so it can exit normally. For an immediate handoff, close the iterator before calling `chat.endAndContinue()`. In a fully hand-rolled `chat.customAgent()` task, detach input listeners, persist the completed turn, write its boundary, then call `chat.endAndContinue()` and return immediately:
|
|
@@ -238,7 +166,7 @@ await chat.endAndContinue();
|
|
|
238
166
|
return;
|
|
239
167
|
```
|
|
240
168
|
|
|
241
|
-
The continuation uses the same durable Session and receives `.in` records that the old run has not consumed. It starts on the latest deployed task version
|
|
169
|
+
The continuation uses the same durable Session and receives `.in` records that the old run has not consumed. It starts on the latest deployed task version unless the Session's trigger configuration sets `lockToVersion`.
|
|
242
170
|
|
|
243
171
|
If input has been dispatched to the old run but should be processed by the continuation, detach the old listeners and skip the final `chat.writeTurnComplete()`. A turn-complete boundary acknowledges the latest input dispatched to the old run, so writing one after that dispatch would cause the continuation to resume past the input.
|
|
244
172
|
|
|
@@ -254,7 +182,6 @@ Both are graceful exits. [`onRecoveryBoot`](/ai-chat/patterns/recovery-boot) doe
|
|
|
254
182
|
|
|
255
183
|
## See also
|
|
256
184
|
|
|
257
|
-
- [Version skew protection](/deployment/version-skew-protection#chat-sessions) — pin a session to the deployment matching the app build that started it
|
|
258
185
|
- [Lifecycle hooks](/ai-chat/lifecycle-hooks) — where `onTurnStart` and `onChatResume` fit in the turn cycle
|
|
259
186
|
- [Recovery boot](/ai-chat/patterns/recovery-boot) — the sibling hook for mid-stream interruptions (does NOT fire on `requestUpgrade`)
|
|
260
187
|
- [Database persistence](/ai-chat/patterns/database-persistence) — how continuations interact with session state
|
|
@@ -30,18 +30,18 @@ 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 { streamText, stepCountIs } from "ai";
|
|
34
34
|
import { anthropic } from "@ai-sdk/anthropic";
|
|
35
35
|
|
|
36
36
|
export const myChat = chat.agent({
|
|
37
37
|
id: "my-chat",
|
|
38
|
-
registry,
|
|
39
38
|
pendingMessages: {
|
|
40
39
|
// Only inject when there are completed steps (tool calls happened)
|
|
41
40
|
shouldInject: ({ steps }) => steps.length > 0,
|
|
42
41
|
},
|
|
43
|
-
run: async ({ messages, signal
|
|
42
|
+
run: async ({ messages, signal }) => {
|
|
44
43
|
return streamText({
|
|
44
|
+
...chat.toStreamTextOptions({ registry }),
|
|
45
45
|
messages,
|
|
46
46
|
tools: { /* ... */ },
|
|
47
47
|
abortSignal: signal,
|
|
@@ -16,7 +16,7 @@ A request renders as `tools` → `system` → `messages`. There are three prefix
|
|
|
16
16
|
|
|
17
17
|
| Region | How to cache it | Stability |
|
|
18
18
|
| --- | --- | --- |
|
|
19
|
-
| System prompt (+ tools) | `cacheControl` / `systemProviderOptions` on `chat.
|
|
19
|
+
| System prompt (+ tools) | `cacheControl` / `systemProviderOptions` on `chat.toStreamTextOptions()`, or `providerOptions` on `chat.prompt.set()` | Set once, never changes — the highest-value target |
|
|
20
20
|
| Conversation history | `prepareMessages` adds a breakpoint to the last message | Grows append-only across turns |
|
|
21
21
|
| Tool definitions | Stable as long as your tool set doesn't change between turns | Render at position 0 — changing them invalidates everything |
|
|
22
22
|
|
|
@@ -32,22 +32,23 @@ The system prompt (your `chat.prompt` text plus any skills preamble) is usually
|
|
|
32
32
|
|
|
33
33
|
Three ways to opt in, depending on where you'd rather express it.
|
|
34
34
|
|
|
35
|
-
**`cacheControl`
|
|
35
|
+
**`cacheControl` at the `streamText` call site** — the Anthropic-flavored one-liner:
|
|
36
36
|
|
|
37
37
|
```ts /trigger/chat.ts
|
|
38
38
|
import { chat } from "@trigger.dev/sdk/ai";
|
|
39
|
+
import { streamText } from "ai";
|
|
39
40
|
import { anthropic } from "@ai-sdk/anthropic";
|
|
40
41
|
|
|
41
42
|
export const myChat = chat.agent({
|
|
42
43
|
id: "my-chat",
|
|
43
|
-
cacheControl: { type: "ephemeral" },
|
|
44
44
|
onChatStart: async () => {
|
|
45
45
|
chat.prompt.set(SYSTEM_PROMPT); // a large, stable instruction block
|
|
46
46
|
},
|
|
47
|
-
run: async ({ messages, signal
|
|
47
|
+
run: async ({ messages, signal }) => {
|
|
48
48
|
return streamText({
|
|
49
49
|
model: anthropic("claude-sonnet-4-6"),
|
|
50
50
|
// Caches the system block with a 5-minute breakpoint.
|
|
51
|
+
...chat.toStreamTextOptions({ cacheControl: { type: "ephemeral" } }),
|
|
51
52
|
messages,
|
|
52
53
|
abortSignal: signal,
|
|
53
54
|
});
|
|
@@ -58,19 +59,17 @@ export const myChat = chat.agent({
|
|
|
58
59
|
**`systemProviderOptions`** is the provider-agnostic form — pass the raw `providerOptions` so it composes with any provider:
|
|
59
60
|
|
|
60
61
|
```ts /trigger/chat.ts
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
abortSignal: signal,
|
|
69
|
-
}),
|
|
62
|
+
return streamText({
|
|
63
|
+
model: anthropic("claude-sonnet-4-6"),
|
|
64
|
+
...chat.toStreamTextOptions({
|
|
65
|
+
systemProviderOptions: { anthropic: { cacheControl: { type: "ephemeral" } } },
|
|
66
|
+
}),
|
|
67
|
+
messages,
|
|
68
|
+
abortSignal: signal,
|
|
70
69
|
});
|
|
71
70
|
```
|
|
72
71
|
|
|
73
|
-
**`providerOptions` on `chat.prompt.set()`** co-locates the intent with where the prompt is defined. It carries through to
|
|
72
|
+
**`providerOptions` on `chat.prompt.set()`** co-locates the intent with where the prompt is defined. It carries through to `toStreamTextOptions()` with no call-site change:
|
|
74
73
|
|
|
75
74
|
```ts /trigger/chat.ts
|
|
76
75
|
onChatStart: async () => {
|
|
@@ -78,16 +77,17 @@ onChatStart: async () => {
|
|
|
78
77
|
providerOptions: { anthropic: { cacheControl: { type: "ephemeral" } } },
|
|
79
78
|
});
|
|
80
79
|
},
|
|
81
|
-
run: async ({ messages, signal
|
|
80
|
+
run: async ({ messages, signal }) => {
|
|
82
81
|
return streamText({
|
|
83
82
|
model: anthropic("claude-sonnet-4-6"),
|
|
83
|
+
...chat.toStreamTextOptions(), // already cached
|
|
84
84
|
messages,
|
|
85
85
|
abortSignal: signal,
|
|
86
86
|
});
|
|
87
87
|
},
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
-
If more than one is set, the
|
|
90
|
+
If more than one is set, the call-site option wins: `systemProviderOptions` overrides `cacheControl`, and both override `chat.prompt.set`'s `providerOptions`. There's no deep merge — the most specific option replaces the rest.
|
|
91
91
|
|
|
92
92
|
<Note>
|
|
93
93
|
Use the 1-hour cache for prefixes that sit idle longer than 5 minutes between turns: `cacheControl: { type: "ephemeral", ttl: "1h" }`. Writes cost more (2× vs 1.25×), so it pays off only when reads span the longer window.
|
|
@@ -100,7 +100,6 @@ Place a breakpoint on the last message and the entire conversation prefix up to
|
|
|
100
100
|
```ts /trigger/chat.ts
|
|
101
101
|
export const myChat = chat.agent({
|
|
102
102
|
id: "my-chat",
|
|
103
|
-
cacheControl: { type: "ephemeral" },
|
|
104
103
|
prepareMessages: async ({ messages }) => {
|
|
105
104
|
if (messages.length === 0) return messages;
|
|
106
105
|
const last = messages[messages.length - 1];
|
|
@@ -115,9 +114,10 @@ export const myChat = chat.agent({
|
|
|
115
114
|
},
|
|
116
115
|
];
|
|
117
116
|
},
|
|
118
|
-
run: async ({ messages, signal
|
|
117
|
+
run: async ({ messages, signal }) => {
|
|
119
118
|
return streamText({
|
|
120
119
|
model: anthropic("claude-sonnet-4-6"),
|
|
120
|
+
...chat.toStreamTextOptions({ cacheControl: { type: "ephemeral" } }),
|
|
121
121
|
messages,
|
|
122
122
|
abortSignal: signal,
|
|
123
123
|
});
|
|
@@ -149,10 +149,11 @@ Caching is provider-specific, and most providers don't use per-block breakpoints
|
|
|
149
149
|
|
|
150
150
|
```ts /trigger/chat.ts
|
|
151
151
|
// Amazon Bedrock
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
152
|
+
return streamText({
|
|
153
|
+
...chat.toStreamTextOptions({
|
|
154
|
+
systemProviderOptions: { bedrock: { cachePoint: { type: "default" } } },
|
|
155
|
+
}),
|
|
156
|
+
messages,
|
|
156
157
|
});
|
|
157
158
|
```
|
|
158
159
|
|
|
@@ -165,13 +166,14 @@ Usage reporting is normalized. Each provider reports cache tokens under its own
|
|
|
165
166
|
The turn's usage carries cache token counts. `chat.agent` accumulates them across turns and hands them to `run` as `previousTurnUsage` (last turn) and `totalUsage` (whole chat), both `LanguageModelUsage`:
|
|
166
167
|
|
|
167
168
|
```ts /trigger/chat.ts
|
|
168
|
-
run: async ({ messages, signal, previousTurnUsage
|
|
169
|
+
run: async ({ messages, signal, previousTurnUsage }) => {
|
|
169
170
|
// After turn 1, cacheReadTokens should be > 0 on a stable prefix.
|
|
170
171
|
console.log("cache read", previousTurnUsage?.inputTokenDetails?.cacheReadTokens);
|
|
171
172
|
console.log("cache write", previousTurnUsage?.inputTokenDetails?.cacheWriteTokens);
|
|
172
173
|
|
|
173
174
|
return streamText({
|
|
174
175
|
model: anthropic("claude-sonnet-4-6"),
|
|
176
|
+
...chat.toStreamTextOptions({ cacheControl: { type: "ephemeral" } }),
|
|
175
177
|
messages,
|
|
176
178
|
abortSignal: signal,
|
|
177
179
|
});
|
|
@@ -16,16 +16,19 @@ The chat surface works with Vercel AI SDK **v5, v6, or v7**; install whichever m
|
|
|
16
16
|
|
|
17
17
|
```ts trigger/chat.ts
|
|
18
18
|
import { chat } from "@trigger.dev/sdk/ai";
|
|
19
|
-
import { stepCountIs } from "ai";
|
|
19
|
+
import { streamText, stepCountIs } from "ai";
|
|
20
20
|
import { anthropic } from "@ai-sdk/anthropic";
|
|
21
21
|
|
|
22
22
|
export const myChat = chat.agent({
|
|
23
23
|
id: "my-chat",
|
|
24
|
-
|
|
25
|
-
// compaction, steering, background injection, the system prompt and
|
|
26
|
-
// telemetry, so none of them have to be wired up by hand.
|
|
27
|
-
run: async ({ messages, signal, streamText }) => {
|
|
24
|
+
run: async ({ messages, signal }) => {
|
|
28
25
|
return streamText({
|
|
26
|
+
// Spread chat.toStreamTextOptions() FIRST — it wires up
|
|
27
|
+
// prepareStep (compaction, steering, background injection),
|
|
28
|
+
// the system prompt set via chat.prompt(), and telemetry.
|
|
29
|
+
// Skipping this is the single most common cause of subtle
|
|
30
|
+
// bugs (silent broken compaction, missing steering, etc.).
|
|
31
|
+
...chat.toStreamTextOptions(),
|
|
29
32
|
model: anthropic("claude-sonnet-4-5"),
|
|
30
33
|
messages,
|
|
31
34
|
abortSignal: signal,
|
|
@@ -35,12 +38,9 @@ The chat surface works with Vercel AI SDK **v5, v6, or v7**; install whichever m
|
|
|
35
38
|
});
|
|
36
39
|
```
|
|
37
40
|
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
background injection never run, and nothing reports it. Spreading
|
|
42
|
-
`chat.toStreamTextOptions()` into the imported one does the same job by hand.
|
|
43
|
-
</Note>
|
|
41
|
+
<Warning>
|
|
42
|
+
**Always spread `chat.toStreamTextOptions()` into your `streamText` call.** It wires up the `prepareStep` callback that drives compaction, mid-turn steering, and background injection — features that silently no-op if the spread is missing. Spread it **first** so any explicit overrides (e.g. a custom `prepareStep`) win.
|
|
43
|
+
</Warning>
|
|
44
44
|
|
|
45
45
|
<Tip>
|
|
46
46
|
For a **custom** [`UIMessage`](https://sdk.vercel.ai/docs/reference/ai-sdk-core/ui-message) subtype (typed `data-*` parts, tool map, etc.), define the agent with [`chat.withUIMessage<...>().agent({...})`](/ai-chat/types) instead of `chat.agent`.
|
|
@@ -46,16 +46,12 @@ Options for `chat.agent()`.
|
|
|
46
46
|
| `onValidateMessages` | `(event: ValidateMessagesEvent) => UIMessage[] \| Promise<UIMessage[]>` | — | Validate/transform UIMessages before model conversion. See [onValidateMessages](/ai-chat/lifecycle-hooks#onvalidatemessages) |
|
|
47
47
|
| `hydrateMessages` | `(event: HydrateMessagesEvent) => UIMessage[] \| Promise<UIMessage[]>` | — | Load message history from backend, replacing the linear accumulator. See [hydrateMessages](/ai-chat/lifecycle-hooks#hydratemessages) |
|
|
48
48
|
| `actionSchema` | `TaskSchema` | — | Schema for validating custom actions sent via `transport.sendAction()`. See [Actions](/ai-chat/actions) |
|
|
49
|
-
| `onAction` | `(event: ActionEvent) => Promise<
|
|
49
|
+
| `onAction` | `(event: ActionEvent) => Promise<unknown> \| unknown` | — | Handle custom actions. Actions are not turns — only `hydrateMessages` + `onAction` fire. Return a `StreamTextResult` (or `string` / `UIMessage`) for a model response; return `void` for side-effect-only. See [Actions](/ai-chat/actions) |
|
|
50
50
|
| `onTurnStart` | `(event: TurnStartEvent) => Promise<void> \| void` | — | Fires every turn before `run()` |
|
|
51
51
|
| `onBeforeTurnComplete` | `(event: BeforeTurnCompleteEvent) => Promise<void> \| void` | — | Fires after response but before stream closes. Includes `writer`. |
|
|
52
52
|
| `onTurnComplete` | `(event: TurnCompleteEvent) => Promise<void> \| void` | — | Fires after each turn completes (stream closed) |
|
|
53
53
|
| `onCompacted` | `(event: CompactedEvent) => Promise<void> \| void` | — | Fires when compaction occurs. Includes `writer`. See [Compaction](/ai-chat/compaction) |
|
|
54
54
|
| `compaction` | `ChatAgentCompactionOptions` | — | Automatic context compaction. See [Compaction](/ai-chat/compaction) |
|
|
55
|
-
| `registry` | `{ languageModel(id: string): unknown }` | — | A provider registry, so the managed `streamText` can resolve a model set through `chat.prompt.set()` |
|
|
56
|
-
| `system` | `string \| SystemModelMessage` | — | The agent's system prompt. Injected instructions append to it. Set it here, at the `streamText` call site, or through `chat.prompt.set()`, but only in one of them |
|
|
57
|
-
| `cacheControl` | `SystemCacheControl` | — | Mark the system prompt for provider-side caching. See [Prompt caching](/ai-chat/prompt-caching) |
|
|
58
|
-
| `systemProviderOptions` | `ProviderMetadata` | — | Raw provider options for the system block. Takes precedence over `cacheControl` |
|
|
59
55
|
| `pendingMessages` | `PendingMessagesOptions` | — | Mid-execution message injection. See [Pending Messages](/ai-chat/pending-messages) |
|
|
60
56
|
| `prepareMessages` | `(event: PrepareMessagesEvent) => ModelMessage[]` | — | Transform model messages before use (cache breaks, context injection, etc.) |
|
|
61
57
|
| `tools` | `ToolSet \| ((event: ResolveToolsEvent) => ToolSet \| Promise<ToolSet>)` | — | Tools for this agent. Threads each tool's `toModelOutput` through cross-turn history re-conversion, and hands the resolved set back on the run payload. Static set or per-turn function. See [Tools](/ai-chat/tools). |
|
|
@@ -102,10 +98,9 @@ The payload passed to the `run` function.
|
|
|
102
98
|
| `ctx` | `TaskRunContext` | Full task run context — same as `task` `run`’s `{ ctx }` |
|
|
103
99
|
| `messages` | `ModelMessage[]` | Model-ready messages — pass directly to `streamText` |
|
|
104
100
|
| `tools` | `ToolSet` | Resolved tools declared on the agent config (empty object when none). Pass straight to `streamText`. See [Tools](/ai-chat/tools). |
|
|
105
|
-
| `streamText` | `typeof streamText` | The AI SDK's `streamText` with this agent's managed options already applied: the prompt, skill tools, telemetry, and the `prepareStep` that delivers steering, compaction and injected context. Prefer it over importing `streamText` from `ai`. See [The managed streamText](/ai-chat/backend#the-managed-streamtext). |
|
|
106
101
|
| `chatId` | `string` | Your conversation ID (the session's `externalId`) |
|
|
107
102
|
| `sessionId` | `string` | Friendly ID of the backing Session (`session_*`). Use with `sessions.open()` for advanced cases. Always set — every chat.agent run is bound to a Session. |
|
|
108
|
-
| `trigger` | `"submit-message" \| "regenerate-message"
|
|
103
|
+
| `trigger` | `"submit-message" \| "regenerate-message"` | What triggered the request |
|
|
109
104
|
| `messageId` | `string \| undefined` | Message ID (for regenerate) |
|
|
110
105
|
| `clientData` | Typed by `clientDataSchema` | Custom data from the frontend (typed when schema is provided) |
|
|
111
106
|
| `continuation` | `boolean` | Whether this run is continuing an existing chat (previous run ended) |
|
|
@@ -495,9 +490,9 @@ Options for [`chat.headStart()`](/ai-chat/fast-starts#head-start), the warm-serv
|
|
|
495
490
|
| `agentId` | `string` | required | The `chat.agent` / `chat.customAgent` id to hand off to |
|
|
496
491
|
| `run` | `(args: HeadStartRunArgs) => Promise<StreamTextResult>` | required | First-turn callback. Call `streamText` and spread `chat.toStreamTextOptions({ tools })` |
|
|
497
492
|
| `idleTimeoutInSeconds` | `number` | `60` | How long the agent waits for the handover signal |
|
|
498
|
-
| `triggerConfig` | `Partial<SessionTriggerConfig>` | `undefined` | Run options (tags, queue, machine, maxAttempts, maxDuration, region, lockToVersion
|
|
493
|
+
| `triggerConfig` | `Partial<SessionTriggerConfig>` | `undefined` | Run options (tags, queue, machine, maxAttempts, maxDuration, region, lockToVersion) for the auto-triggered handover-prepare run. The `chat:{chatId}` tag is prepended automatically and counts toward the 10-tag limit |
|
|
499
494
|
|
|
500
|
-
`chat.headStart(options)` returns the handler `(req: Request) => Promise<Response>`. The `run` callback receives `HeadStartRunArgs`: `{ messages: UIMessage[], signal: AbortSignal, chat: HeadStartChatHelper }`, where the helper exposes `chat.toStreamTextOptions({ tools })` and a `chat.session` escape hatch
|
|
495
|
+
`chat.headStart(options)` returns the handler `(req: Request) => Promise<Response>`. The `run` callback receives `HeadStartRunArgs`: `{ messages: UIMessage[], signal: AbortSignal, chat: HeadStartChatHelper }`, where the helper exposes `chat.toStreamTextOptions({ tools })` and a `chat.session` escape hatch. See [Head Start](/ai-chat/fast-starts#head-start) for the full guide.
|
|
501
496
|
|
|
502
497
|
## chat namespace
|
|
503
498
|
|
|
@@ -516,7 +511,6 @@ All methods available on the `chat` object from `@trigger.dev/sdk/ai`.
|
|
|
516
511
|
| `chat.createStartSessionAction(taskId, options?)` | Returns a server action that creates a chat Session + triggers the first run + returns a session-scoped PAT. Idempotent on `(env, externalId)`. |
|
|
517
512
|
| `chat.waitForHandover(options)` | Wait for a [`chat.headStart`](/ai-chat/fast-starts#handover-with-custom-agents) handover signal in a custom loop. Returns the signal or `null`. `chat.MessageAccumulator` wraps this as `consumeHandover()` / `applyHandover()` |
|
|
518
513
|
| `chat.requestUpgrade()` | End the current run after this turn so the next message starts on the latest agent version. Server-orchestrated handoff. |
|
|
519
|
-
| `chat.close({ reason })` | End the conversation permanently: close the session row, write a terminal `session-closed` record, and exit without a continuation. Decide it before the turn ends (`onBeforeTurnComplete`, not `onTurnComplete`) so the client sees the closed state on that turn. |
|
|
520
514
|
| `chat.endAndContinue()` | In a hand-rolled custom agent, hand off the Session to a fresh continuation run. Call between turns after detaching input listeners, then return immediately. The promise rejects if the handoff fails. |
|
|
521
515
|
| `chat.setTurnTimeout(duration)` | Override turn timeout at runtime (e.g. `"2h"`) |
|
|
522
516
|
| `chat.setTurnTimeoutInSeconds(seconds)` | Override turn timeout at runtime (in seconds) |
|
|
@@ -665,7 +659,6 @@ The `onEvent` callback receives a `ChatTransportEvent` (exported from `@trigger.
|
|
|
665
659
|
| --- | --- | --- |
|
|
666
660
|
| `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. |
|
|
667
661
|
| `message-send-failed` | `messageId?`, `source`, `error`, `status?`, `durationMs`, `partId?`, `bodyBytes?` | A send definitively failed after internal retries. Fires in addition to `useChat`'s `onError`. |
|
|
668
|
-
| `run-pending-version` | `source` | The chat's run is parked waiting for the deployment carrying its external deployment id ([version skew protection](/deployment/version-skew-protection#chat-sessions)). Everything already sent is durable and answered once the deployment lands. `source` is `"start"` (learned while starting the session), `"send"` (from a message append, re-emitted on every send while parked) `"head-start"` (from the `headStart` POST, where step 1 still streams from your server and only step 2 waits) or `"upgrade"` (an automatic version handover whose successor is parked on a deployment that has not landed). |
|
|
669
662
|
| `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. |
|
|
670
663
|
| `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. |
|
|
671
664
|
| `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. |
|
|
@@ -801,7 +794,7 @@ See [Stop generation](/ai-chat/frontend#stop-generation) for full details.
|
|
|
801
794
|
|
|
802
795
|
### transport.sendAction()
|
|
803
796
|
|
|
804
|
-
Send a custom action to the agent
|
|
797
|
+
Send a custom action to the agent. Actions wake the agent from suspension and fire `onAction`. They are not turns — `run()` and turn lifecycle hooks do not fire. If `onAction` returns a `StreamTextResult`, the response is auto-piped to the frontend.
|
|
805
798
|
|
|
806
799
|
```ts
|
|
807
800
|
transport.sendAction(chatId: string, action: unknown): Promise<ReadableStream<UIMessageChunk>>
|
|
@@ -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`, `
|
|
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`, `idleTimeoutInSeconds`, `basePayload`. |
|
|
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. |
|
|
@@ -146,11 +146,6 @@ Mark a Session as closed. Terminal and idempotent. The optional `reason` is stor
|
|
|
146
146
|
await sessions.close(chatId, { reason: "user signed out" });
|
|
147
147
|
```
|
|
148
148
|
|
|
149
|
-
Closing tells a live run too: the close lands on the session's input channel, so an idle or suspended agent exits its loop on the next wake rather than waiting out its idle timeout. After it lands, appends to `.in` are refused with HTTP 409 and `code: "session_closed"`.
|
|
150
|
-
|
|
151
|
-
To close from inside the agent instead, call [`chat.close()`](/ai-chat/backend#ending-the-conversation). It writes a terminal record to the response stream so the browser learns the reason, then closes the row.
|
|
152
|
-
|
|
153
|
-
|
|
154
149
|
### `sessions.list(options?, requestOptions?)`
|
|
155
150
|
|
|
156
151
|
Cursor-paginated list of Sessions in the current environment. Returns a `CursorPagePromise` you can iterate with `for await`.
|
package/docs/ai-chat/testing.mdx
CHANGED
|
@@ -203,7 +203,7 @@ Equivalent to the frontend's `useChat().regenerate()` — replays a turn with th
|
|
|
203
203
|
|
|
204
204
|
### sendAction
|
|
205
205
|
|
|
206
|
-
Routes a payload through `actionSchema` + `onAction`.
|
|
206
|
+
Routes a payload through `actionSchema` + `onAction`. Actions are not turns: only `hydrateMessages` and `onAction` fire on the agent side — no turn lifecycle hooks, no `run()`. The returned `turn.rawChunks` contains whatever `onAction` produced (a streamed model response if it returned a `StreamTextResult`, otherwise just `trigger:turn-complete`):
|
|
207
207
|
|
|
208
208
|
```ts
|
|
209
209
|
const turn = await harness.sendAction({ type: "undo" });
|
|
@@ -634,7 +634,6 @@ The harness's initial wire payload depends on `mode`:
|
|
|
634
634
|
| `sendHandover({ partialAssistantMessage, isFinal?, messageId? })` | Dispatch a `handover` signal — only meaningful when started with `mode: "handover-prepare"`. The agent picks up partial assistant messages and continues the turn. |
|
|
635
635
|
| `sendHandoverSkip()` | Dispatch a `handover-skip` signal — only meaningful when started with `mode: "handover-prepare"`. The agent exits cleanly without firing turn hooks. |
|
|
636
636
|
| `sendAction(action)` | Route a custom action through `actionSchema` + `onAction`. |
|
|
637
|
-
| `sendPendingMessage(message)` | Append a user message mid-turn without waiting for a turn to complete, so it reaches the running turn as a steering message. Resolves once the record has landed on `session.in`. |
|
|
638
637
|
| `sendStop(message?)` | Fire a stop signal. Does not wait for the turn — the run's `signal.aborted` becomes `true`. |
|
|
639
638
|
| `seedSnapshot(snapshot)` | Pre-seed the snapshot read for the next boot. Effective on the next run boot only. |
|
|
640
639
|
| `seedSessionOutTail(chunks?)` | Pre-seed `session.out` chunks for the next boot's replay. Reduces to settled assistant turns. |
|
package/docs/ai-chat/tools.mdx
CHANGED
|
@@ -8,7 +8,7 @@ description: "Declare tools on chat.agent so toModelOutput survives across turns
|
|
|
8
8
|
|
|
9
9
|
```ts
|
|
10
10
|
import { chat } from "@trigger.dev/sdk/ai";
|
|
11
|
-
import { stepCountIs, tool } from "ai";
|
|
11
|
+
import { streamText, stepCountIs, tool } from "ai";
|
|
12
12
|
import { anthropic } from "@ai-sdk/anthropic";
|
|
13
13
|
import { z } from "zod";
|
|
14
14
|
|
|
@@ -23,9 +23,9 @@ const tools = {
|
|
|
23
23
|
export const myChat = chat.agent({
|
|
24
24
|
id: "my-chat",
|
|
25
25
|
tools, // ← declare here
|
|
26
|
-
run: async ({ messages, tools, signal
|
|
26
|
+
run: async ({ messages, tools, signal }) =>
|
|
27
27
|
streamText({
|
|
28
|
-
tools,
|
|
28
|
+
...chat.toStreamTextOptions({ tools }), // ← the same set, handed back on the payload
|
|
29
29
|
model: anthropic("claude-sonnet-4-5"),
|
|
30
30
|
messages,
|
|
31
31
|
abortSignal: signal,
|
|
@@ -46,15 +46,10 @@ There are three places a tool set shows up. Declare once, reuse:
|
|
|
46
46
|
| Surface | What it's for |
|
|
47
47
|
| --- | --- |
|
|
48
48
|
| `chat.agent({ tools })` | Re-applies `toModelOutput` on prior-turn history; hands the set back typed on the `run()` payload. |
|
|
49
|
-
| `
|
|
50
|
-
| `
|
|
49
|
+
| `chat.toStreamTextOptions({ tools })` | Detects which tool calls need [HITL approval](/ai-chat/patterns/human-in-the-loop) (`needsApproval`) and merges any auto-injected [skill](/ai-chat/patterns/skills) tools. |
|
|
50
|
+
| `streamText({ tools })` | What the model actually calls. `chat.toStreamTextOptions({ tools })` already sets this, so spread it instead of passing `tools` twice. |
|
|
51
51
|
|
|
52
|
-
The canonical pattern: declare `tools` on the config, read them back from the `run()` payload, and pass that
|
|
53
|
-
|
|
54
|
-
```ts
|
|
55
|
-
run: async ({ messages, tools, signal, streamText }) =>
|
|
56
|
-
streamText({ model, messages, tools, abortSignal: signal }),
|
|
57
|
-
```
|
|
52
|
+
The canonical pattern: declare `tools` on the config, read them back from the `run()` payload, and pass that to `chat.toStreamTextOptions({ tools })`. One declaration flows everywhere.
|
|
58
53
|
|
|
59
54
|
<Tip>
|
|
60
55
|
Conversion only reads each tool's `inputSchema` and `toModelOutput`, never `execute`. If you keep heavy `execute` dependencies out of a module (for bundle reasons), you can declare a lightweight schema-only tool map on the config and add the executes where you call `streamText`.
|
|
@@ -85,9 +80,9 @@ const tools = {
|
|
|
85
80
|
export const chartChat = chat.agent({
|
|
86
81
|
id: "chart-chat",
|
|
87
82
|
tools, // ← without this, the image is "remembered" on turn 1 and gone from turn 2
|
|
88
|
-
run: async ({ messages, tools, signal
|
|
83
|
+
run: async ({ messages, tools, signal }) =>
|
|
89
84
|
streamText({
|
|
90
|
-
tools,
|
|
85
|
+
...chat.toStreamTextOptions({ tools }),
|
|
91
86
|
model: anthropic("claude-sonnet-4-5"),
|
|
92
87
|
messages,
|
|
93
88
|
abortSignal: signal,
|
|
@@ -109,9 +104,9 @@ export const myChat = chat
|
|
|
109
104
|
searchDocs,
|
|
110
105
|
...(clientData?.plan === "pro" ? { deepResearch } : {}),
|
|
111
106
|
}),
|
|
112
|
-
run: async ({ messages, tools, signal
|
|
107
|
+
run: async ({ messages, tools, signal }) =>
|
|
113
108
|
streamText({
|
|
114
|
-
tools,
|
|
109
|
+
...chat.toStreamTextOptions({ tools }),
|
|
115
110
|
model: anthropic("claude-sonnet-4-5"),
|
|
116
111
|
messages,
|
|
117
112
|
abortSignal: signal,
|
|
@@ -136,10 +131,10 @@ The resolved set is what lands on the `run()` payload's `tools`.
|
|
|
136
131
|
The `run()` payload's `tools` is typed to whatever you declared, so you can pass it straight through without re-importing the map:
|
|
137
132
|
|
|
138
133
|
```ts
|
|
139
|
-
run: async ({ messages, tools, signal
|
|
134
|
+
run: async ({ messages, tools, signal }) => {
|
|
140
135
|
// `tools` is typed as your tool set, not a broad `ToolSet`
|
|
141
136
|
return streamText({
|
|
142
|
-
tools,
|
|
137
|
+
...chat.toStreamTextOptions({ tools }),
|
|
143
138
|
model: anthropic("claude-sonnet-4-5"),
|
|
144
139
|
messages,
|
|
145
140
|
abortSignal: signal,
|
|
@@ -165,7 +160,7 @@ This is shorthand for `UIMessage<unknown, UIDataTypes, InferUITools<typeof tools
|
|
|
165
160
|
|
|
166
161
|
## Skills
|
|
167
162
|
|
|
168
|
-
[Agent skills](/ai-chat/patterns/skills) are auto-injected as tools (`loadSkill`, `readFile`, `bash`) by
|
|
163
|
+
[Agent skills](/ai-chat/patterns/skills) are auto-injected as tools (`loadSkill`, `readFile`, `bash`) by `chat.toStreamTextOptions()`. They're separate from your config `tools`: declare your own tools on the config (so their `toModelOutput` survives across turns), and let `toStreamTextOptions` merge the skill tools on top at call time. Skill tools don't define `toModelOutput`, so they don't need to be on the config.
|
|
169
164
|
|
|
170
165
|
## Manual turn loops (`chat.customAgent`)
|
|
171
166
|
|
|
@@ -298,8 +298,8 @@ and direct API consumers.
|
|
|
298
298
|
fire at the same lifecycle points.
|
|
299
299
|
- `onAction` is still defined the same way, but its semantics changed
|
|
300
300
|
in the [May 6 prerelease](/ai-chat/changelog) — actions are no longer
|
|
301
|
-
turns
|
|
302
|
-
|
|
301
|
+
turns, and `onAction` returning a `StreamTextResult` produces a model
|
|
302
|
+
response.
|
|
303
303
|
- `chat.customAgent({...})` and the `chat.createSession(payload, ...)`
|
|
304
304
|
helper for building a session loop manually inside a custom agent.
|
|
305
305
|
- `chat.defer` (deferred work) and `chat.history` (imperative history
|