Commit 7fb8310
feat(chat): hand run() a streamText with the managed options already applied (#4884)
## Summary
Every `run()` had to spread `chat.toStreamTextOptions()`, and leaving it
out dropped six things with no error: the managed prompt and its cache
control, the registry-resolved model, the prompt's sampling config,
telemetry, the skill tools, and the `prepareStep` that delivers
steering, compaction and injected context.
Before:
```ts /trigger/chat.ts
import { chat } from "@trigger.dev/sdk/ai";
import { streamText, stepCountIs } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
export const myChat = chat.agent({
id: "my-chat",
tools: { myTool },
run: async ({ messages, tools, signal }) =>
streamText({
...chat.toStreamTextOptions({ registry, tools }),
model: anthropic("claude-sonnet-4-5"),
system: "You are a helpful assistant.",
messages,
abortSignal: signal,
stopWhen: stepCountIs(15),
}),
});
```
After:
```ts /trigger/chat.ts
import { chat } from "@trigger.dev/sdk/ai";
import { stepCountIs } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
export const myChat = chat.agent({
id: "my-chat",
system: "You are a helpful assistant.",
registry,
tools: { myTool },
run: async ({ messages, tools, signal, streamText }) =>
streamText({
model: anthropic("claude-sonnet-4-5"),
messages,
tools,
abortSignal: signal,
stopWhen: stepCountIs(15),
}),
});
```
`streamText` comes from `run`'s argument and shadows the one imported
from `ai`, so the correct call is now the shorter one and the managed
options cannot be lost by omission. `chat.toStreamTextOptions()` is
unchanged and still supported, and is still the only option in a custom
agent.
## What changes when your options collide with the managed ones
Spread order decides the outcome today, and losing is silent:
```ts
streamText({ ...chat.toStreamTextOptions(), tools: myTools }) // skill tools dropped
streamText({ ...chat.toStreamTextOptions(), prepareStep: mine }) // steering, compaction and injection off
```
The managed `streamText` merges instead. `tools` are passed into the
helper so skill tools survive, and a `prepareStep` you pass runs after
the managed one rather than replacing it. Everything else you name is
left alone and wins, telemetry included.
`system` is the exception: it can be set on `chat.agent({ system })`,
through `chat.prompt.set()`, or at the call site, but only in one of
them. Two at once throws and names the one that already owns it. No
shape merges two system values across every supported AI SDK version,
since v5 rejects an array of blocks and a structured block carries the
provider options that make prompt caching work.
## chat.headStart and chat.startHeadStart
`buildStreamTextOptions` supplies `messages`, `stopWhen: stepCountIs(1)`
and `abortSignal`. Step 1 belongs to the route handler and step 2 onward
to the agent, so re-setting `stopWhen` after a spread hands over a
stream that has already run past step 1.
Before:
```ts
import { streamText, stepCountIs } from "ai";
export const POST = chat.headStart({
agentId: "my-chat",
run: async ({ chat: helper }) =>
streamText({
...helper.toStreamTextOptions({ tools: headStartTools }),
model: anthropic("claude-sonnet-4-6"),
system: "You are a helpful assistant.",
}),
});
```
After:
```ts
export const POST = chat.headStart({
agentId: "my-chat",
run: async ({ streamText }) =>
streamText({
model: anthropic("claude-sonnet-4-6"),
system: "You are a helpful assistant.",
tools: headStartTools,
}),
});
```
Passing `messages`, `prompt`, `stopWhen` or `abortSignal` to that
`streamText` is a type error, with a runtime throw behind it for
JavaScript callers. `tools` is yours to pass. The old shape only warned
in prose.
## Also in here
- `chat.agent()` takes `system`, `registry`, `cacheControl` and
`systemProviderOptions`, so a managed prompt's model and its cache
breakpoint no longer have to be passed at the call site.
- `ChatStreamText` is exported for typing a loop factored out of `run`.
The signature is taken from the AI SDK's own declaration:
```ts
import type { streamText as aiStreamTextSignature } from "ai";
type AiStreamTextFn = typeof aiStreamTextSignature;
```
The peer range spans `ai` v5, v6 and v7, whose options differ. `typeof`
resolves to whichever version is installed, so generics and tool
inference are the caller's own and a v8 option needs no change here.
**Actions.** `onAction` no longer receives `streamText` or `tools`: an
action is a state edit, and one that returns `chat.turn()` (added in
#4816) is followed by `run()`, which already has both. The action docs
on this branch describe that model. `chat.toStreamTextOptions()` now
also applies `chat.agent`'s `system`, `registry`, `cacheControl` and
`systemProviderOptions`, so the spread form is equivalent to the
`streamText` handed to `run()`, as the docs say; previously an agent's
system prompt was silently dropped on that path. Those options are
published on every boot, including for a `hydrateMessages` agent, which
skips the snapshot boot block where they were first set.
## Verification
Typecheck and the full suite pass on both `ai@6.0.116` and `ai@7.0.66`.
The option merge is a pure function so the merged object can be asserted
directly, which is how `experimental_telemetry` being dropped was
caught: most `streamText` options never reach the provider, so a test
that observes the model cannot see them.
Run end to end against a deployed agent with every `run` rewritten to
the new form and no spread anywhere: steering, undo across a cold boot,
and regenerate all still pass, a caller's own `prepareStep` runs while
managed steering still fires inside the turn, and consecutive injections
arrive one per turn. The handover-owned options are pinned by
`@ts-expect-error` assertions in a typechecked test rather than only by
the runtime throw.
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>1 parent bffc7cc commit 7fb8310
27 files changed
Lines changed: 1460 additions & 252 deletions
File tree
- .changeset
- docs/ai-chat
- patterns
- packages
- cli-v3/skills/trigger-authoring-chat-agent
- trigger-sdk
- skills
- trigger-authoring-chat-agent
- trigger-chat-agent-advanced
- src
- imports
- v3
- test
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
44 | 44 | | |
45 | 45 | | |
46 | 46 | | |
47 | | - | |
| 47 | + | |
48 | 48 | | |
49 | 49 | | |
50 | 50 | | |
51 | 51 | | |
52 | 52 | | |
53 | 53 | | |
54 | 54 | | |
55 | | - | |
| 55 | + | |
56 | 56 | | |
57 | | - | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
58 | 60 | | |
59 | 61 | | |
60 | | - | |
61 | | - | |
62 | | - | |
63 | | - | |
64 | | - | |
65 | | - | |
66 | | - | |
67 | | - | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
68 | 76 | | |
69 | | - | |
70 | 77 | | |
71 | 78 | | |
72 | 79 | | |
73 | | - | |
| 80 | + | |
74 | 81 | | |
75 | 82 | | |
76 | 83 | | |
77 | | - | |
| 84 | + | |
78 | 85 | | |
79 | | - | |
| 86 | + | |
80 | 87 | | |
81 | | - | |
| 88 | + | |
82 | 89 | | |
83 | 90 | | |
84 | | - | |
| 91 | + | |
85 | 92 | | |
86 | 93 | | |
87 | 94 | | |
88 | 95 | | |
89 | | - | |
90 | 96 | | |
91 | 97 | | |
92 | | - | |
93 | | - | |
94 | | - | |
95 | | - | |
96 | | - | |
| 98 | + | |
| 99 | + | |
97 | 100 | | |
98 | 101 | | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
99 | 105 | | |
100 | 106 | | |
101 | | - | |
102 | | - | |
103 | | - | |
104 | | - | |
105 | 107 | | |
106 | 108 | | |
107 | 109 | | |
108 | 110 | | |
109 | 111 | | |
110 | | - | |
| 112 | + | |
111 | 113 | | |
112 | 114 | | |
113 | 115 | | |
114 | | - | |
| 116 | + | |
115 | 117 | | |
116 | 118 | | |
117 | 119 | | |
| |||
135 | 137 | | |
136 | 138 | | |
137 | 139 | | |
138 | | - | |
| 140 | + | |
139 | 141 | | |
140 | 142 | | |
141 | 143 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
21 | | - | |
| 21 | + | |
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
| |||
36 | 36 | | |
37 | 37 | | |
38 | 38 | | |
39 | | - | |
| 39 | + | |
40 | 40 | | |
41 | | - | |
| 41 | + | |
42 | 42 | | |
43 | 43 | | |
44 | 44 | | |
| |||
0 commit comments