Skip to content

Commit dda8210

Browse files
ericallamclaude
andcommitted
fix(chat): apply agent-level options in chat.toStreamTextOptions
chat.agent({ system, registry, cacheControl, systemProviderOptions }) reached only the bound streamText; the documented spread form ran without them. The options are published for the run and toStreamTextOptions defaults from them, caller options winning. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AxuSksX18bj1yhnLpkcQ6a
1 parent 4b6309c commit dda8210

2 files changed

Lines changed: 76 additions & 0 deletions

File tree

‎packages/trigger-sdk/src/v3/ai.ts‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4811,6 +4811,15 @@ export type ChatStreamText = AiStreamTextFn;
48114811
* version, and dropping one silently is the failure this seam exists to
48124812
* prevent. Injected instructions append to whichever one is in play.
48134813
*/
4814+
/**
4815+
* The agent-level managed options (`registry`, `system`, `cacheControl`,
4816+
* `systemProviderOptions`), published for the run so that
4817+
* `chat.toStreamTextOptions()` applies them too. Without this only the bound
4818+
* `streamText` saw them, and the documented spread form silently ran without
4819+
* the agent's system prompt or model.
4820+
*/
4821+
const chatAgentManagedConfigKey = locals.create<ManagedStreamTextConfig>("chat.agentManagedConfig");
4822+
48144823
type ManagedStreamTextConfig = {
48154824
registry?: ToStreamTextOptionsOptions["registry"];
48164825
system?: ToStreamTextOptionsOptions["system"];
@@ -4928,6 +4937,16 @@ function createBoundStreamText(
49284937
}
49294938

49304939
function toStreamTextOptions(options?: ToStreamTextOptionsOptions): Record<string, unknown> {
4940+
const agentDefaults = locals.get(chatAgentManagedConfigKey);
4941+
if (agentDefaults) {
4942+
options = {
4943+
registry: agentDefaults.registry,
4944+
system: agentDefaults.system,
4945+
cacheControl: agentDefaults.cacheControl,
4946+
systemProviderOptions: agentDefaults.systemProviderOptions,
4947+
...options,
4948+
};
4949+
}
49314950
const prompt = locals.get(chatPromptKey);
49324951
const skills = locals.get(chatSkillsKey);
49334952
const result: Record<string, unknown> = {};
@@ -7493,6 +7512,13 @@ function chatAgent<
74937512
}
74947513
}
74957514

7515+
locals.set(chatAgentManagedConfigKey, {
7516+
registry: promptRegistry,
7517+
system: agentSystem,
7518+
cacheControl: agentCacheControl,
7519+
systemProviderOptions: agentSystemProviderOptions,
7520+
});
7521+
74967522
// Make the seeded UI accumulator visible to `chat.history.*`
74977523
// before any hook (`onChatStart`, `onTurnStart`, etc.) fires.
74987524
locals.set(chatCurrentUIMessagesKey, accumulatedUIMessages);
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { mockChatAgent } from "../src/v3/test/index.js";
2+
import type { LanguageModelV3StreamPart } from "@ai-sdk/provider";
3+
import { simulateReadableStream, streamText } from "ai";
4+
import { MockLanguageModelV3 } from "ai/test";
5+
import { describe, expect, it } from "vitest";
6+
import { chat } from "../src/v3/ai.js";
7+
8+
/**
9+
* The docs call `streamText({ ...chat.toStreamTextOptions() })` equivalent to
10+
* the `streamText` handed to `run()`. That holds only if the agent-level
11+
* options (`system`, `registry`, `cacheControl`, `systemProviderOptions`)
12+
* reach the helper too, not just the bound function.
13+
*/
14+
const USAGE = {
15+
inputTokens: { total: 1, noCache: 1, cacheRead: undefined, cacheWrite: undefined },
16+
outputTokens: { total: 1, text: 1, reasoning: undefined },
17+
};
18+
const textChunks = (text: string): LanguageModelV3StreamPart[] => [
19+
{ type: "text-start", id: "t1" },
20+
{ type: "text-delta", id: "t1", delta: text },
21+
{ type: "text-end", id: "t1" },
22+
{ type: "finish", finishReason: { unified: "stop", raw: "stop" }, usage: USAGE },
23+
];
24+
25+
describe("chat.toStreamTextOptions with agent-level options", () => {
26+
it("carries chat.agent({ system }) in the spread form", async () => {
27+
const prompts: string[] = [];
28+
const model = new MockLanguageModelV3({
29+
doStream: async ({ prompt }) => {
30+
prompts.push(JSON.stringify(prompt));
31+
return {
32+
stream: simulateReadableStream({ chunks: textChunks("ok"), initialDelayInMs: 5 }),
33+
};
34+
},
35+
});
36+
const agent = chat.agent({
37+
id: "spread-form-agent-system",
38+
system: "AGENT-SYSTEM-VIA-SPREAD",
39+
run: async ({ messages, signal }) =>
40+
streamText({ model, messages, abortSignal: signal, ...chat.toStreamTextOptions() }),
41+
});
42+
const harness = mockChatAgent(agent, { chatId: "spread-form-agent-system" });
43+
try {
44+
await harness.sendMessage({ id: "u1", role: "user", parts: [{ type: "text", text: "hi" }] });
45+
expect(prompts.at(-1)!).toContain("AGENT-SYSTEM-VIA-SPREAD");
46+
} finally {
47+
await harness.close();
48+
}
49+
});
50+
});

0 commit comments

Comments
 (0)