Buckets:
| import { expect } from "bun:test" | |
| import { Effect, Schema } from "effect" | |
| import { | |
| LLM, | |
| LLMEvent, | |
| LLMResponse, | |
| Message, | |
| ToolRuntime, | |
| ToolChoice, | |
| ToolDefinition, | |
| toDefinitions, | |
| type ContentPart, | |
| type FinishReason, | |
| type LLMRequest, | |
| type Model, | |
| } from "../src" | |
| import { LLMClient } from "../src/route" | |
| import { Tool } from "../src/tool" | |
| export const weatherToolName = "get_weather" | |
| // A deterministic system prompt long enough to clear every supported provider's | |
| // minimum cacheable-prefix threshold (Anthropic Haiku 3.5: 2048 tokens; Anthropic | |
| // Opus/Haiku 4.5: 4096 tokens; OpenAI/Gemini/Bedrock: lower). Built by repeating | |
| // a fixed sentence — the cassette replays bit-for-bit, so the exact text matters | |
| // only when re-recording with `RECORD=true`. | |
| export const LARGE_CACHEABLE_SYSTEM = (() => { | |
| const sentence = "You are a concise, factual assistant. Answer precisely and avoid filler. Cite numbers when known. " | |
| // ~100 chars per sentence × 250 repeats ≈ 25,000 chars ≈ 5k+ tokens, safely | |
| // above every provider's threshold. | |
| return sentence.repeat(250) | |
| })() | |
| export const weatherTool = ToolDefinition.make({ | |
| name: weatherToolName, | |
| description: "Get current weather for a city.", | |
| inputSchema: { | |
| type: "object", | |
| properties: { city: { type: "string" } }, | |
| required: ["city"], | |
| additionalProperties: false, | |
| }, | |
| }) | |
| export const weatherRuntimeTool = Tool.make({ | |
| description: weatherTool.description, | |
| parameters: Schema.Struct({ city: Schema.String }), | |
| success: Schema.Struct({ temperature: Schema.Number, condition: Schema.String }), | |
| execute: ({ city }) => | |
| Effect.succeed( | |
| city === "Paris" ? { temperature: 22, condition: "sunny" } : { temperature: 0, condition: "unknown" }, | |
| ), | |
| }) | |
| export const weatherToolLoopRequest = (input: { | |
| readonly id: string | |
| readonly model: Model | |
| readonly system?: string | |
| readonly maxTokens?: number | |
| readonly temperature?: number | false | |
| }) => | |
| LLM.request({ | |
| id: input.id, | |
| model: input.model, | |
| system: input.system ?? "Use the get_weather tool, then answer in one short sentence.", | |
| prompt: "What is the weather in Paris?", | |
| cache: "none", | |
| generation: | |
| input.temperature === false | |
| ? { maxTokens: input.maxTokens ?? 80 } | |
| : { maxTokens: input.maxTokens ?? 80, temperature: input.temperature ?? 0 }, | |
| }) | |
| export const goldenWeatherToolLoopRequest = (input: { | |
| readonly id: string | |
| readonly model: Model | |
| readonly maxTokens?: number | |
| readonly temperature?: number | false | |
| }) => | |
| weatherToolLoopRequest({ | |
| ...input, | |
| system: "Use the get_weather tool exactly once. After the tool result, reply exactly: Paris is sunny.", | |
| }) | |
| const RESTROOM_IMAGE_TEXT = "jiggling restroom prison" | |
| const restroomImage = () => | |
| Effect.promise(() => Bun.file(new URL("./fixtures/media/restroom.png", import.meta.url)).bytes()).pipe( | |
| Effect.map((bytes) => Buffer.from(bytes).toString("base64")), | |
| ) | |
| export const runWeatherToolLoop = (request: LLMRequest) => | |
| Effect.gen(function* () { | |
| const tools = { [weatherToolName]: weatherRuntimeTool } | |
| let next = LLM.updateRequest(request, { tools: toDefinitions(tools) }) | |
| const events: LLMEvent[] = [] | |
| for (let step = 0; step < 10; step++) { | |
| const response = yield* LLMClient.generate(next) | |
| events.push(...response.events.filter((event) => event.type !== "finish")) | |
| const calls = response.events.filter(LLMEvent.is.toolCall).filter((call) => !call.providerExecuted) | |
| if (calls.length === 0) { | |
| const finish = response.events.find(LLMEvent.is.finish) | |
| if (finish) events.push(finish) | |
| return events | |
| } | |
| const dispatched = yield* Effect.forEach(calls, (call) => | |
| ToolRuntime.dispatch(tools, call).pipe(Effect.map((result) => [call, result] as const)), | |
| ) | |
| events.push(...dispatched.flatMap(([, result]) => result.events)) | |
| next = LLM.updateRequest(next, { | |
| messages: [ | |
| ...next.messages, | |
| Message.assistant(assistantContent(response.events)), | |
| ...dispatched.map(([call, result]) => Message.tool({ id: call.id, name: call.name, result: result.result })), | |
| ], | |
| }) | |
| } | |
| throw new Error("Weather tool loop exceeded 10 steps") | |
| }) | |
| const assistantContent = (events: ReadonlyArray<LLMEvent>) => { | |
| const content: ContentPart[] = [] | |
| for (const event of events) { | |
| if (event.type === "text-delta" || event.type === "reasoning-delta") { | |
| const type = event.type === "text-delta" ? "text" : "reasoning" | |
| const last = content.at(-1) | |
| if (last?.type === type) { | |
| content[content.length - 1] = { ...last, text: `${last.text}${event.text}` } | |
| } else { | |
| content.push({ type, text: event.text }) | |
| } | |
| continue | |
| } | |
| if (event.type === "text-end" || event.type === "reasoning-end") { | |
| const type = event.type === "text-end" ? "text" : "reasoning" | |
| const last = content.at(-1) | |
| if (last?.type === type) content[content.length - 1] = { ...last, providerMetadata: event.providerMetadata } | |
| continue | |
| } | |
| if (event.type === "tool-call") content.push(event) | |
| } | |
| return content | |
| } | |
| export const expectFinish = ( | |
| events: ReadonlyArray<LLMEvent>, | |
| reason: Extract<LLMEvent, { readonly type: "finish" }>["reason"], | |
| ) => expect(events.at(-1)).toMatchObject({ type: "finish", reason }) | |
| export const expectWeatherToolCall = (response: LLMResponse) => | |
| expect(response.toolCalls).toMatchObject([ | |
| { type: "tool-call", id: expect.any(String), name: weatherToolName, input: { city: "Paris" } }, | |
| ]) | |
| export const expectWeatherToolLoop = (events: ReadonlyArray<LLMEvent>) => { | |
| const finishes = events.filter(LLMEvent.is.finish) | |
| expect(finishes).toHaveLength(1) | |
| expect(finishes[0]?.reason).toBe("stop") | |
| const stepFinishes = events.filter(LLMEvent.is.stepFinish) | |
| expect(stepFinishes.map((event) => event.reason)).toEqual(["tool-calls", "stop"]) | |
| const toolCalls = events.filter(LLMEvent.is.toolCall) | |
| expect(toolCalls).toHaveLength(1) | |
| expect(toolCalls[0]).toMatchObject({ type: "tool-call", name: weatherToolName, input: { city: "Paris" } }) | |
| const toolResults = events.filter(LLMEvent.is.toolResult) | |
| expect(toolResults).toHaveLength(1) | |
| expect(toolResults[0]).toMatchObject({ | |
| type: "tool-result", | |
| name: weatherToolName, | |
| result: { type: "json", value: { temperature: 22, condition: "sunny" } }, | |
| }) | |
| const output = LLMResponse.text({ events }) | |
| expect(output).toContain("Paris") | |
| expect(output.trim().length).toBeGreaterThan(0) | |
| } | |
| export const expectGoldenWeatherToolLoop = (events: ReadonlyArray<LLMEvent>) => { | |
| expectWeatherToolLoop(events) | |
| expect(LLMResponse.text({ events }).trim()).toMatch(/^Paris is sunny\.?$/) | |
| } | |
| export interface GoldenScenarioContext { | |
| readonly id: string | |
| readonly model: Model | |
| readonly maxTokens?: number | |
| readonly temperature?: number | false | |
| } | |
| const generate = (request: LLMRequest) => LLMClient.generate(request) | |
| const generation = (context: GoldenScenarioContext, maxTokens: number) => | |
| context.temperature === false ? { maxTokens } : { maxTokens, temperature: context.temperature ?? 0 } | |
| const normalizeImageText = (value: string) => | |
| value | |
| .toLowerCase() | |
| .replace(/[^a-z\s]/g, "") | |
| .replace(/\s+/g, " ") | |
| .trim() | |
| const encryptedReasoningOptions = { | |
| openai: { | |
| store: false, | |
| include: ["reasoning.encrypted_content"], | |
| reasoningEffort: "low", | |
| reasoningSummary: "auto", | |
| }, | |
| } as const | |
| type AssistantTextExpectation = string | RegExp | |
| type UserStep = { readonly type: "user"; readonly content: Message.ContentInput } | |
| type AssistantStep = { | |
| readonly type: "assistant" | |
| readonly text?: AssistantTextExpectation | |
| readonly toolCall?: { readonly name: string; readonly input: unknown } | |
| readonly reasoning?: "openai-encrypted" | |
| readonly id?: string | |
| readonly system?: string | |
| readonly maxTokens?: number | |
| readonly finish?: FinishReason | |
| readonly tools?: LLM.RequestInput["tools"] | |
| readonly toolChoice?: LLM.RequestInput["toolChoice"] | |
| readonly providerOptions?: LLMRequest["providerOptions"] | |
| readonly assert?: (response: LLMResponse) => void | |
| } | |
| type ConversationStep = UserStep | AssistantStep | |
| const user = (content: Message.ContentInput): ConversationStep => ({ type: "user", content }) | |
| const assistant = { | |
| expectText: ( | |
| text: AssistantTextExpectation, | |
| options?: Omit<AssistantStep, "type" | "text" | "reasoning" | "toolCall">, | |
| ): ConversationStep => ({ type: "assistant", text, ...options }), | |
| expectToolCall: ( | |
| name: string, | |
| input: unknown, | |
| options?: Omit<AssistantStep, "type" | "text" | "reasoning" | "toolCall" | "finish">, | |
| ): ConversationStep => ({ type: "assistant", toolCall: { name, input }, finish: "tool-calls", ...options }), | |
| expectEncryptedReasoningText: ( | |
| text: AssistantTextExpectation, | |
| options?: Omit<AssistantStep, "type" | "text" | "reasoning" | "toolCall" | "providerOptions">, | |
| ): ConversationStep => ({ | |
| type: "assistant", | |
| text, | |
| reasoning: "openai-encrypted", | |
| providerOptions: encryptedReasoningOptions, | |
| ...options, | |
| }), | |
| } | |
| const assertAssistantText = (actual: string, expected: AssistantTextExpectation) => { | |
| if (typeof expected === "string") { | |
| expect(actual.trim()).toBe(expected) | |
| return | |
| } | |
| expect(actual.trim()).toMatch(expected) | |
| } | |
| const assertAssistantToolCall = (response: LLMResponse, expected: NonNullable<AssistantStep["toolCall"]>) => { | |
| expect(response.toolCalls).toMatchObject([ | |
| { type: "tool-call", id: expect.any(String), name: expected.name, input: expected.input }, | |
| ]) | |
| } | |
| // The generated golden scenarios only model one assistant shape at a time: | |
| // encrypted reasoning + text, text, or tool call. Keep mixed interleavings in | |
| // focused protocol tests where event order can be asserted directly. | |
| const assistantMessageFromResponse = (response: LLMResponse, step: AssistantStep) => { | |
| const content: ContentPart[] = [] | |
| if (step.reasoning === "openai-encrypted") { | |
| const reasoning = response.events.find( | |
| (event): event is Extract<LLMEvent, { readonly type: "reasoning-end" }> => | |
| LLMEvent.is.reasoningEnd(event) && typeof event.providerMetadata?.openai?.itemId === "string", | |
| ) | |
| if (!reasoning) throw new Error("OpenAI Responses did not return reasoning metadata") | |
| expect(reasoning.providerMetadata?.openai?.reasoningEncryptedContent).toEqual(expect.any(String)) | |
| content.push({ type: "reasoning", text: response.reasoning, providerMetadata: reasoning.providerMetadata }) | |
| } | |
| if (response.text.length > 0) content.push({ type: "text", text: response.text }) | |
| content.push(...response.toolCalls) | |
| return Message.assistant(content) | |
| } | |
| const runGeneratedConversation = (context: GoldenScenarioContext, steps: ReadonlyArray<ConversationStep>) => | |
| Effect.gen(function* () { | |
| const messages: Message[] = [] | |
| let generated = 0 | |
| for (const step of steps) { | |
| if (step.type === "user") { | |
| messages.push(Message.user(step.content)) | |
| continue | |
| } | |
| generated += 1 | |
| const response = yield* generate( | |
| LLM.request({ | |
| id: step.id ? `${context.id}_${step.id}` : `${context.id}_${generated}`, | |
| model: context.model, | |
| system: step.system, | |
| cache: "none", | |
| messages, | |
| tools: step.tools, | |
| toolChoice: step.toolChoice, | |
| providerOptions: step.providerOptions, | |
| generation: generation(context, step.maxTokens ?? context.maxTokens ?? 80), | |
| }), | |
| ) | |
| if (step.text !== undefined) assertAssistantText(response.text, step.text) | |
| if (step.toolCall) assertAssistantToolCall(response, step.toolCall) | |
| step.assert?.(response) | |
| expectFinish(response.events, step.finish ?? "stop") | |
| messages.push(assistantMessageFromResponse(response, step)) | |
| } | |
| }) | |
| const runTextScenario = (context: GoldenScenarioContext) => | |
| runGeneratedConversation(context, [ | |
| user("Reply exactly with: Hello!"), | |
| assistant.expectText(/^Hello!?$/, { | |
| system: "You are concise.", | |
| maxTokens: context.maxTokens ?? 40, | |
| providerOptions: | |
| context.model.route.id === "gemini" ? { gemini: { thinkingConfig: { thinkingBudget: 0 } } } : undefined, | |
| }), | |
| ]) | |
| const runToolCallScenario = (context: GoldenScenarioContext) => | |
| runGeneratedConversation(context, [ | |
| user("Call get_weather with city exactly Paris."), | |
| assistant.expectToolCall( | |
| weatherToolName, | |
| { city: "Paris" }, | |
| { | |
| system: "Call tools exactly as requested.", | |
| tools: [weatherTool], | |
| toolChoice: ToolChoice.make(weatherTool), | |
| maxTokens: context.maxTokens ?? 80, | |
| }, | |
| ), | |
| ]) | |
| const runImageScenario = (context: GoldenScenarioContext) => | |
| Effect.gen(function* () { | |
| yield* runGeneratedConversation(context, [ | |
| user([ | |
| { | |
| type: "text", | |
| text: "The image contains exactly three lowercase English words. Read them left to right and reply with only those words.", | |
| }, | |
| { type: "media", mediaType: "image/png", data: yield* restroomImage() }, | |
| ]), | |
| assistant.expectText(/.+/, { | |
| system: "Read images carefully. Reply only with the visible text.", | |
| maxTokens: context.maxTokens ?? 20, | |
| assert: (response) => expect(normalizeImageText(response.text)).toBe(RESTROOM_IMAGE_TEXT), | |
| }), | |
| ]) | |
| }) | |
| // Reproduces a tool-result image round trip: a tool returns image bytes, and | |
| // the next model turn must receive provider-native image content instead of a | |
| // JSON-stringified base64 blob. | |
| const screenshotToolName = "read_screenshot" | |
| const runImageToolResultScenario = (context: GoldenScenarioContext) => | |
| Effect.gen(function* () { | |
| const image = yield* restroomImage() | |
| const response = yield* generate( | |
| LLM.request({ | |
| id: `${context.id}_image_tool_result`, | |
| model: context.model, | |
| system: "Read images carefully. Reply only with the visible text, lowercase, no punctuation.", | |
| cache: "none", | |
| generation: generation(context, context.maxTokens ?? 40), | |
| messages: [ | |
| Message.user("Use the read_screenshot tool, then reply with the words shown."), | |
| Message.assistant([{ type: "tool-call", id: "call_screenshot_1", name: screenshotToolName, input: {} }]), | |
| Message.tool({ | |
| id: "call_screenshot_1", | |
| name: screenshotToolName, | |
| resultType: "content", | |
| result: [ | |
| { type: "text", text: "Image read successfully" }, | |
| { type: "file", uri: `data:image/png;base64,${image}`, mime: "image/png" }, | |
| ], | |
| }), | |
| ], | |
| tools: [ | |
| ToolDefinition.make({ | |
| name: screenshotToolName, | |
| description: "Capture a screenshot of the current screen.", | |
| inputSchema: { type: "object", properties: {}, additionalProperties: false }, | |
| }), | |
| ], | |
| }), | |
| ) | |
| expectFinish(response.events, "stop") | |
| expect(normalizeImageText(response.text)).toBe(RESTROOM_IMAGE_TEXT) | |
| }) | |
| const runReasoningScenario = (context: GoldenScenarioContext) => | |
| runGeneratedConversation(context, [ | |
| user("Think briefly, then reply exactly with: Hello!"), | |
| assistant.expectText(/^Hello!?$/, { | |
| system: "Show concise reasoning when the provider supports visible reasoning summaries.", | |
| providerOptions: { openai: { reasoningEffort: "low", reasoningSummary: "auto" } }, | |
| maxTokens: context.maxTokens ?? 120, | |
| assert: (response) => expect(response.usage?.reasoningTokens ?? 0).toBeGreaterThan(0), | |
| }), | |
| ]) | |
| const runReasoningContinuationScenario = (context: GoldenScenarioContext) => | |
| runGeneratedConversation(context, [ | |
| user("Think briefly, then reply exactly with: Hello!"), | |
| assistant.expectEncryptedReasoningText(/^Hello!?$/, { | |
| id: "first", | |
| system: "Show concise reasoning when the provider supports visible reasoning summaries.", | |
| maxTokens: context.maxTokens ?? 120, | |
| }), | |
| user("Now reply exactly with: Done."), | |
| assistant.expectText(/^Done\.?$/, { id: "second", maxTokens: 40, providerOptions: encryptedReasoningOptions }), | |
| ]) | |
| const runToolLoopScenario = (context: GoldenScenarioContext) => | |
| Effect.gen(function* () { | |
| expectGoldenWeatherToolLoop( | |
| yield* runWeatherToolLoop( | |
| goldenWeatherToolLoopRequest({ | |
| id: context.id, | |
| model: context.model, | |
| maxTokens: context.maxTokens ?? 80, | |
| temperature: context.temperature, | |
| }), | |
| ), | |
| ) | |
| }) | |
| const goldenScenarios = { | |
| text: { title: "streams text", tags: ["text", "golden"], run: runTextScenario }, | |
| "tool-call": { title: "streams tool call", tags: ["tool", "tool-call", "golden"], run: runToolCallScenario }, | |
| "tool-loop": { title: "drives a tool loop", tags: ["tool", "tool-loop", "golden"], run: runToolLoopScenario }, | |
| image: { title: "reads image text", tags: ["media", "image", "vision", "golden"], run: runImageScenario }, | |
| "image-tool-result": { | |
| title: "reads image returned from tool result", | |
| tags: ["media", "image", "vision", "tool", "tool-result", "golden"], | |
| run: runImageToolResultScenario, | |
| }, | |
| reasoning: { title: "uses reasoning", tags: ["reasoning", "golden"], run: runReasoningScenario }, | |
| "reasoning-continuation": { | |
| title: "continues encrypted reasoning", | |
| tags: ["reasoning", "continuation", "encrypted-reasoning", "golden"], | |
| run: runReasoningContinuationScenario, | |
| }, | |
| } as const | |
| export type GoldenScenarioID = keyof typeof goldenScenarios | |
| export const goldenScenarioTitle = (id: GoldenScenarioID) => goldenScenarios[id].title | |
| export const goldenScenarioTags = (id: GoldenScenarioID) => [...goldenScenarios[id].tags] | |
| export const runGoldenScenario = (id: GoldenScenarioID, context: GoldenScenarioContext) => | |
| goldenScenarios[id].run(context) | |
| const usageSummary = (usage: LLMResponse["usage"] | undefined) => { | |
| if (!usage) return undefined | |
| return Object.fromEntries( | |
| [ | |
| ["inputTokens", usage.inputTokens], | |
| ["outputTokens", usage.outputTokens], | |
| ["reasoningTokens", usage.reasoningTokens], | |
| ["cacheReadInputTokens", usage.cacheReadInputTokens], | |
| ["cacheWriteInputTokens", usage.cacheWriteInputTokens], | |
| ["totalTokens", usage.totalTokens], | |
| ].filter((entry) => entry[1] !== undefined), | |
| ) | |
| } | |
| const pushText = (summary: Array<Record<string, unknown>>, type: "text" | "reasoning", value: string) => { | |
| const last = summary.at(-1) | |
| if (last?.type === type) { | |
| last.value = `${typeof last.value === "string" ? last.value : ""}${value}` | |
| return | |
| } | |
| summary.push({ type, value }) | |
| } | |
| export const eventSummary = (events: ReadonlyArray<LLMEvent>) => { | |
| const summary: Array<Record<string, unknown>> = [] | |
| for (const event of events) { | |
| if (event.type === "text-delta") { | |
| pushText(summary, "text", event.text) | |
| continue | |
| } | |
| if (event.type === "reasoning-delta") { | |
| pushText(summary, "reasoning", event.text) | |
| continue | |
| } | |
| if (event.type === "tool-call") { | |
| summary.push({ | |
| type: "tool-call", | |
| name: event.name, | |
| input: event.input, | |
| providerExecuted: event.providerExecuted, | |
| }) | |
| continue | |
| } | |
| if (event.type === "tool-result") { | |
| summary.push({ | |
| type: "tool-result", | |
| name: event.name, | |
| result: event.result, | |
| providerExecuted: event.providerExecuted, | |
| }) | |
| continue | |
| } | |
| if (event.type === "tool-error") { | |
| summary.push({ type: "tool-error", name: event.name, message: event.message }) | |
| continue | |
| } | |
| if (event.type === "finish") { | |
| summary.push({ type: "finish", reason: event.reason, usage: usageSummary(event.usage) }) | |
| } | |
| } | |
| return summary.map((item) => Object.fromEntries(Object.entries(item).filter((entry) => entry[1] !== undefined))) | |
| } | |
Xet Storage Details
- Size:
- 19.9 kB
- Xet hash:
- 0f81cb18c0eb06f4bb4edf40cbb843c977c5ed7c73c9407bf783a85bdcd25f49
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.