chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
// Import the test harness FIRST so the resource catalog is installed
|
||||
import { mockChatAgent } from "../src/v3/test/index.js";
|
||||
|
||||
import type { LanguageModelV3StreamPart } from "@ai-sdk/provider";
|
||||
import { simulateReadableStream, streamText } from "ai";
|
||||
import { MockLanguageModelV3 } from "ai/test";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { chat } from "../src/v3/ai.js";
|
||||
|
||||
function userMessage(text: string, id?: string) {
|
||||
return {
|
||||
id: id ?? `u-${Math.random().toString(36).slice(2)}`,
|
||||
role: "user" as const,
|
||||
parts: [{ type: "text" as const, text }],
|
||||
};
|
||||
}
|
||||
|
||||
function textStream(text: string) {
|
||||
const chunks: LanguageModelV3StreamPart[] = [
|
||||
{ type: "text-start", id: "t1" },
|
||||
{ type: "text-delta", id: "t1", delta: text },
|
||||
{ type: "text-end", id: "t1" },
|
||||
{
|
||||
type: "finish",
|
||||
finishReason: { unified: "stop", raw: "stop" },
|
||||
usage: {
|
||||
inputTokens: { total: 10, noCache: 10, cacheRead: undefined, cacheWrite: undefined },
|
||||
outputTokens: { total: 10, text: 10, reasoning: undefined },
|
||||
},
|
||||
},
|
||||
];
|
||||
return simulateReadableStream({ chunks });
|
||||
}
|
||||
|
||||
/** Capture the rendered system message handed to the provider. */
|
||||
type Captured = { system?: { role: string; content: unknown; providerOptions?: any } };
|
||||
|
||||
function makeModel(capture: Captured) {
|
||||
return new MockLanguageModelV3({
|
||||
doStream: async (opts) => {
|
||||
capture.system = opts.prompt.find((m) => m.role === "system") as Captured["system"];
|
||||
return { stream: textStream("ok") };
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** Poll until the mock model captures the system message (bounded), instead of a fixed sleep. */
|
||||
async function waitForSystemCaptured(capture: Captured, timeoutMs = 1000, intervalMs = 5) {
|
||||
const startedAt = Date.now();
|
||||
while (!capture.system) {
|
||||
if (Date.now() - startedAt > timeoutMs) {
|
||||
throw new Error("Timed out waiting for system message capture");
|
||||
}
|
||||
await new Promise((r) => setTimeout(r, intervalMs));
|
||||
}
|
||||
}
|
||||
|
||||
const SYSTEM = "You are a helpful assistant for tests.";
|
||||
|
||||
describe("chat prompt caching — system providerOptions", () => {
|
||||
it("emits a plain system prompt with no providerOptions by default", async () => {
|
||||
const cap: Captured = {};
|
||||
const model = makeModel(cap);
|
||||
|
||||
const agent = chat.agent({
|
||||
id: "prompt-caching.default",
|
||||
onChatStart: async () => {
|
||||
chat.prompt.set(SYSTEM);
|
||||
},
|
||||
run: async ({ messages, signal }) =>
|
||||
streamText({ model, messages, abortSignal: signal, ...chat.toStreamTextOptions() }),
|
||||
});
|
||||
|
||||
const harness = mockChatAgent(agent, { chatId: "pc-default" });
|
||||
try {
|
||||
await harness.sendMessage(userMessage("hi"));
|
||||
await waitForSystemCaptured(cap);
|
||||
expect(cap.system?.content).toContain("helpful assistant");
|
||||
expect(cap.system?.providerOptions).toBeUndefined();
|
||||
} finally {
|
||||
await harness.close();
|
||||
}
|
||||
});
|
||||
|
||||
it("attaches cacheControl via the toStreamTextOptions sugar", async () => {
|
||||
const cap: Captured = {};
|
||||
const model = makeModel(cap);
|
||||
|
||||
const agent = chat.agent({
|
||||
id: "prompt-caching.sugar",
|
||||
onChatStart: async () => {
|
||||
chat.prompt.set(SYSTEM);
|
||||
},
|
||||
run: async ({ messages, signal }) =>
|
||||
streamText({
|
||||
model,
|
||||
messages,
|
||||
abortSignal: signal,
|
||||
...chat.toStreamTextOptions({ cacheControl: { type: "ephemeral" } }),
|
||||
}),
|
||||
});
|
||||
|
||||
const harness = mockChatAgent(agent, { chatId: "pc-sugar" });
|
||||
try {
|
||||
await harness.sendMessage(userMessage("hi"));
|
||||
await waitForSystemCaptured(cap);
|
||||
expect(cap.system?.content).toContain("helpful assistant");
|
||||
expect(cap.system?.providerOptions?.anthropic?.cacheControl).toEqual({ type: "ephemeral" });
|
||||
} finally {
|
||||
await harness.close();
|
||||
}
|
||||
});
|
||||
|
||||
it("attaches systemProviderOptions verbatim", async () => {
|
||||
const cap: Captured = {};
|
||||
const model = makeModel(cap);
|
||||
|
||||
const agent = chat.agent({
|
||||
id: "prompt-caching.explicit",
|
||||
onChatStart: async () => {
|
||||
chat.prompt.set(SYSTEM);
|
||||
},
|
||||
run: async ({ messages, signal }) =>
|
||||
streamText({
|
||||
model,
|
||||
messages,
|
||||
abortSignal: signal,
|
||||
...chat.toStreamTextOptions({
|
||||
systemProviderOptions: {
|
||||
anthropic: { cacheControl: { type: "ephemeral", ttl: "1h" } },
|
||||
},
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
const harness = mockChatAgent(agent, { chatId: "pc-explicit" });
|
||||
try {
|
||||
await harness.sendMessage(userMessage("hi"));
|
||||
await waitForSystemCaptured(cap);
|
||||
expect(cap.system?.providerOptions?.anthropic?.cacheControl).toEqual({
|
||||
type: "ephemeral",
|
||||
ttl: "1h",
|
||||
});
|
||||
} finally {
|
||||
await harness.close();
|
||||
}
|
||||
});
|
||||
|
||||
it("carries providerOptions set on chat.prompt.set()", async () => {
|
||||
const cap: Captured = {};
|
||||
const model = makeModel(cap);
|
||||
|
||||
const agent = chat.agent({
|
||||
id: "prompt-caching.prompt-set",
|
||||
onChatStart: async () => {
|
||||
chat.prompt.set(SYSTEM, {
|
||||
providerOptions: { anthropic: { cacheControl: { type: "ephemeral" } } },
|
||||
});
|
||||
},
|
||||
run: async ({ messages, signal }) =>
|
||||
streamText({ model, messages, abortSignal: signal, ...chat.toStreamTextOptions() }),
|
||||
});
|
||||
|
||||
const harness = mockChatAgent(agent, { chatId: "pc-prompt-set" });
|
||||
try {
|
||||
await harness.sendMessage(userMessage("hi"));
|
||||
await waitForSystemCaptured(cap);
|
||||
expect(cap.system?.providerOptions?.anthropic?.cacheControl).toEqual({ type: "ephemeral" });
|
||||
} finally {
|
||||
await harness.close();
|
||||
}
|
||||
});
|
||||
|
||||
it("call-site systemProviderOptions overrides chat.prompt.set providerOptions", async () => {
|
||||
const cap: Captured = {};
|
||||
const model = makeModel(cap);
|
||||
|
||||
const agent = chat.agent({
|
||||
id: "prompt-caching.precedence",
|
||||
onChatStart: async () => {
|
||||
chat.prompt.set(SYSTEM, {
|
||||
providerOptions: { anthropic: { cacheControl: { type: "ephemeral" } } },
|
||||
});
|
||||
},
|
||||
run: async ({ messages, signal }) =>
|
||||
streamText({
|
||||
model,
|
||||
messages,
|
||||
abortSignal: signal,
|
||||
...chat.toStreamTextOptions({
|
||||
systemProviderOptions: {
|
||||
anthropic: { cacheControl: { type: "ephemeral", ttl: "1h" } },
|
||||
},
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
const harness = mockChatAgent(agent, { chatId: "pc-precedence" });
|
||||
try {
|
||||
await harness.sendMessage(userMessage("hi"));
|
||||
await waitForSystemCaptured(cap);
|
||||
// The call-site option wins (ttl: "1h"), not the prompt-set default.
|
||||
expect(cap.system?.providerOptions?.anthropic?.cacheControl).toEqual({
|
||||
type: "ephemeral",
|
||||
ttl: "1h",
|
||||
});
|
||||
} finally {
|
||||
await harness.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user