Buckets:
| import { describe, expect } from "bun:test" | |
| import { Effect, Schema, Stream } from "effect" | |
| import { | |
| GenerationOptions, | |
| LLM, | |
| LLMEvent, | |
| LLMRequest, | |
| LLMResponse, | |
| ToolChoice, | |
| ToolContent, | |
| ToolOutput, | |
| toDefinitions, | |
| } from "../src" | |
| import { Auth, LLMClient } from "../src/route" | |
| import * as AnthropicMessages from "../src/protocols/anthropic-messages" | |
| import * as OpenAIChat from "../src/protocols/openai-chat" | |
| import * as OpenAIResponses from "../src/protocols/openai-responses" | |
| import { Tool, ToolFailure, type ToolExecuteContext } from "../src/tool" | |
| import { ToolRuntime } from "../src/tool-runtime" | |
| import { it } from "./lib/effect" | |
| import * as TestToolRuntime from "./lib/tool-runtime" | |
| import { dynamicResponse, scriptedResponses } from "./lib/http" | |
| import { deltaChunk, finishChunk, toolCallChunk } from "./lib/openai-chunks" | |
| import { sseEvents } from "./lib/sse" | |
| const model = OpenAIChat.route | |
| .with({ endpoint: { baseURL: "https://api.openai.test/v1/" }, auth: Auth.bearer("test") }) | |
| .model({ id: "gpt-4o-mini" }) | |
| const Json = Schema.fromJsonString(Schema.Unknown) | |
| const decodeJson = Schema.decodeUnknownSync(Json) | |
| const baseRequest = LLM.request({ | |
| id: "req_1", | |
| model, | |
| prompt: "Use the tool.", | |
| }) | |
| const weatherFailureCause = new Error("weather lookup denied") | |
| const get_weather = Tool.make({ | |
| description: "Get current weather for a city.", | |
| parameters: Schema.Struct({ city: Schema.String }), | |
| success: Schema.Struct({ temperature: Schema.Number, condition: Schema.String }), | |
| execute: ({ city }) => | |
| Effect.gen(function* () { | |
| if (city === "FAIL") | |
| return yield* new ToolFailure({ message: `Weather lookup failed for ${city}`, error: weatherFailureCause }) | |
| return { temperature: 22, condition: "sunny" } | |
| }), | |
| }) | |
| const schema_only_weather = Tool.make({ | |
| description: "Get current weather for a city.", | |
| parameters: Schema.Struct({ city: Schema.String }), | |
| success: Schema.Struct({ temperature: Schema.Number, condition: Schema.String }), | |
| }) | |
| describe("LLMClient tools", () => { | |
| it.effect("uses the registered model route when adding runtime tools", () => | |
| Effect.gen(function* () { | |
| const layer = scriptedResponses([ | |
| sseEvents(deltaChunk({ role: "assistant", content: "Done." }), finishChunk("stop")), | |
| ]) | |
| const events = Array.from( | |
| yield* TestToolRuntime.runTools({ request: baseRequest, tools: { get_weather } }).pipe( | |
| Stream.runCollect, | |
| Effect.provide(layer), | |
| ), | |
| ) | |
| expect(LLMResponse.text({ events })).toBe("Done.") | |
| }), | |
| ) | |
| it.effect("sends tool-call history and request options on the follow-up request", () => | |
| Effect.gen(function* () { | |
| const bodies: unknown[] = [] | |
| const responses = [ | |
| sseEvents(toolCallChunk("call_1", "get_weather", '{"city":"Paris"}'), finishChunk("tool_calls")), | |
| sseEvents(deltaChunk({ role: "assistant", content: "It's sunny in Paris." }), finishChunk("stop")), | |
| ] | |
| const layer = dynamicResponse((input) => | |
| Effect.sync(() => { | |
| bodies.push(decodeJson(input.text)) | |
| return input.respond(responses[bodies.length - 1] ?? responses[responses.length - 1], { | |
| headers: { "content-type": "text/event-stream" }, | |
| }) | |
| }), | |
| ) | |
| yield* TestToolRuntime.runTools({ | |
| request: LLMRequest.update(baseRequest, { | |
| generation: GenerationOptions.make({ maxTokens: 50 }), | |
| toolChoice: ToolChoice.make("auto"), | |
| }), | |
| tools: { get_weather }, | |
| }).pipe(Stream.runCollect, Effect.provide(layer)) | |
| const second = bodies[1] | |
| if (!second || typeof second !== "object") throw new Error("Expected second request body") | |
| const messages = Reflect.get(second, "messages") | |
| const tools = Reflect.get(second, "tools") | |
| expect(Reflect.get(second, "max_tokens")).toBe(50) | |
| expect(Reflect.get(second, "tool_choice")).toBe("auto") | |
| expect(tools).toHaveLength(1) | |
| expect( | |
| Array.isArray(messages) | |
| ? messages.map((message) => | |
| message && typeof message === "object" ? Reflect.get(message, "role") : undefined, | |
| ) | |
| : undefined, | |
| ).toEqual(["user", "assistant", "tool"]) | |
| expect(Array.isArray(messages) ? messages[1] : undefined).toMatchObject({ | |
| role: "assistant", | |
| content: null, | |
| tool_calls: [{ id: "call_1", type: "function", function: { name: "get_weather" } }], | |
| }) | |
| expect(Array.isArray(messages) ? messages[2] : undefined).toMatchObject({ | |
| role: "tool", | |
| tool_call_id: "call_1", | |
| content: '{"temperature":22,"condition":"sunny"}', | |
| }) | |
| }), | |
| ) | |
| it.effect("dispatches a tool call, appends results, and resumes streaming", () => | |
| Effect.gen(function* () { | |
| const layer = scriptedResponses([ | |
| sseEvents(toolCallChunk("call_1", "get_weather", '{"city":"Paris"}'), finishChunk("tool_calls")), | |
| sseEvents(deltaChunk({ role: "assistant", content: "It's sunny in Paris." }), finishChunk("stop")), | |
| ]) | |
| const events = Array.from( | |
| yield* TestToolRuntime.runTools({ request: baseRequest, tools: { get_weather } }).pipe( | |
| Stream.runCollect, | |
| Effect.provide(layer), | |
| ), | |
| ) | |
| const result = events.find(LLMEvent.is.toolResult) | |
| expect(result).toMatchObject({ | |
| type: "tool-result", | |
| id: "call_1", | |
| name: "get_weather", | |
| result: { type: "json", value: { temperature: 22, condition: "sunny" } }, | |
| }) | |
| expect(events.at(-1)?.type).toBe("finish") | |
| expect(LLMResponse.text({ events })).toBe("It's sunny in Paris.") | |
| }), | |
| ) | |
| it.effect("projects encoded typed tool success into canonical model content", () => | |
| Effect.gen(function* () { | |
| const calls: unknown[] = [] | |
| const projected = Tool.make({ | |
| description: "Project an encoded success.", | |
| parameters: Schema.Struct({ prefix: Schema.String }), | |
| success: Schema.Struct({ count: Schema.NumberFromString }), | |
| execute: () => Effect.succeed({ count: 2 }), | |
| toModelOutput: (input) => { | |
| calls.push(input) | |
| return [{ type: "text", text: `${input.parameters.prefix}:${input.output.count}` }] | |
| }, | |
| }) | |
| const dispatched = yield* ToolRuntime.dispatch( | |
| { projected }, | |
| LLMEvent.toolCall({ id: "call_projected", name: "projected", input: { prefix: "count" } }), | |
| ) | |
| expect(calls).toEqual([{ callID: "call_projected", parameters: { prefix: "count" }, output: { count: "2" } }]) | |
| expect(dispatched.result).toEqual({ type: "text", value: "count:2" }) | |
| expect(dispatched.output).toEqual({ structured: { count: "2" }, content: [{ type: "text", text: "count:2" }] }) | |
| expect(dispatched.events).toEqual([ | |
| LLMEvent.toolResult({ | |
| id: "call_projected", | |
| name: "projected", | |
| result: { type: "text", value: "count:2" }, | |
| output: { structured: { count: "2" }, content: [{ type: "text", text: "count:2" }] }, | |
| }), | |
| ]) | |
| }), | |
| ) | |
| it.effect("uses the narrow default projection for encoded typed success", () => | |
| Effect.gen(function* () { | |
| const text = Tool.make({ | |
| description: "Return text.", | |
| parameters: Schema.Struct({}), | |
| success: Schema.String, | |
| execute: () => Effect.succeed("hello"), | |
| }) | |
| const json = Tool.make({ | |
| description: "Return JSON.", | |
| parameters: Schema.Struct({}), | |
| success: Schema.Struct({ ok: Schema.Boolean }), | |
| execute: () => Effect.succeed({ ok: true }), | |
| }) | |
| expect( | |
| (yield* ToolRuntime.dispatch({ text }, LLMEvent.toolCall({ id: "call_text", name: "text", input: {} }))).output, | |
| ).toEqual({ structured: "hello", content: [{ type: "text", text: "hello" }] }) | |
| expect( | |
| (yield* ToolRuntime.dispatch({ json }, LLMEvent.toolCall({ id: "call_json", name: "json", input: {} }))).output, | |
| ).toEqual({ structured: { ok: true }, content: [] }) | |
| }), | |
| ) | |
| it.effect("can retain model media while redacting duplicated structured payloads", () => | |
| Effect.gen(function* () { | |
| const image = Tool.make({ | |
| description: "Return an image.", | |
| parameters: Schema.Struct({}), | |
| success: Schema.Struct({ mime: Schema.String, data: Schema.String }), | |
| execute: () => Effect.succeed({ mime: "image/png", data: "AAECAw==" }), | |
| toStructuredOutput: (output) => ({ mime: output.mime }), | |
| toModelOutput: ({ output }) => [ | |
| { type: "file", uri: `data:${output.mime};base64,${output.data}`, mime: output.mime }, | |
| ], | |
| }) | |
| const dispatched = yield* ToolRuntime.dispatch( | |
| { image }, | |
| LLMEvent.toolCall({ id: "call_image", name: "image", input: {} }), | |
| ) | |
| expect(dispatched.output).toEqual({ | |
| structured: { mime: "image/png" }, | |
| content: [{ type: "file", uri: "data:image/png;base64,AAECAw==", mime: "image/png" }], | |
| }) | |
| }), | |
| ) | |
| it.effect("models canonical tool files with URIs", () => | |
| Effect.sync(() => { | |
| const decode = Schema.decodeUnknownSync(ToolContent) | |
| expect(decode({ type: "file", uri: "data:image/png;base64,AAAA", mime: "image/png" })).toEqual({ | |
| type: "file", | |
| uri: "data:image/png;base64,AAAA", | |
| mime: "image/png", | |
| }) | |
| expect(decode({ type: "file", uri: "https://example.test/image.png", mime: "image/png" })).toEqual({ | |
| type: "file", | |
| uri: "https://example.test/image.png", | |
| mime: "image/png", | |
| }) | |
| expect(decode({ type: "file", uri: "file:///tmp/image.png", mime: "image/png" })).toEqual({ | |
| type: "file", | |
| uri: "file:///tmp/image.png", | |
| mime: "image/png", | |
| }) | |
| }), | |
| ) | |
| it.effect("preserves canonical tool file URIs", () => | |
| Effect.sync(() => { | |
| expect( | |
| ToolOutput.toResultValue( | |
| ToolOutput.make({}, [{ type: "file", uri: "data:image/png;base64,AAAA", mime: "image/png" }]), | |
| ), | |
| ).toEqual({ | |
| type: "content", | |
| value: [{ type: "file", uri: "data:image/png;base64,AAAA", mime: "image/png" }], | |
| }) | |
| expect( | |
| ToolOutput.toResultValue( | |
| ToolOutput.make({}, [{ type: "file", uri: "https://example.test/image.png", mime: "image/png" }]), | |
| ), | |
| ).toEqual({ | |
| type: "content", | |
| value: [{ type: "file", uri: "https://example.test/image.png", mime: "image/png" }], | |
| }) | |
| expect( | |
| ToolOutput.toResultValue( | |
| ToolOutput.make({}, [{ type: "file", uri: "file:///tmp/image.png", mime: "image/png" }]), | |
| ), | |
| ).toEqual({ | |
| type: "content", | |
| value: [{ type: "file", uri: "file:///tmp/image.png", mime: "image/png" }], | |
| }) | |
| expect( | |
| ToolOutput.fromResultValue({ | |
| type: "content", | |
| value: [{ type: "file", uri: "https://example.test/image.png", mime: "image/png" }], | |
| }), | |
| ).toEqual({ | |
| structured: {}, | |
| content: [{ type: "file", uri: "https://example.test/image.png", mime: "image/png" }], | |
| }) | |
| }), | |
| ) | |
| it.effect("settles projected URL files as canonical tool results", () => | |
| Effect.gen(function* () { | |
| const remote = Tool.make({ | |
| description: "Return a remote file.", | |
| parameters: Schema.Struct({}), | |
| success: Schema.Struct({ ok: Schema.Boolean }), | |
| execute: () => Effect.succeed({ ok: true }), | |
| toModelOutput: () => [{ type: "file", uri: "https://example.test/image.png", mime: "image/png" }], | |
| }) | |
| const dispatched = yield* ToolRuntime.dispatch( | |
| { remote }, | |
| LLMEvent.toolCall({ id: "call_remote", name: "remote", input: {} }), | |
| ) | |
| expect(dispatched.output).toEqual({ | |
| structured: { ok: true }, | |
| content: [{ type: "file", uri: "https://example.test/image.png", mime: "image/png" }], | |
| }) | |
| expect(dispatched.result).toEqual({ | |
| type: "content", | |
| value: [{ type: "file", uri: "https://example.test/image.png", mime: "image/png" }], | |
| }) | |
| expect(dispatched.events.map((event) => event.type)).toEqual(["tool-result"]) | |
| }), | |
| ) | |
| it.effect("derives typed output schemas and preserves dynamic output schemas", () => | |
| Effect.sync(() => { | |
| const [typed] = toDefinitions({ get_weather }) | |
| const schema = { type: "object", properties: { result: { type: "string" } } } as const | |
| const [dynamic] = toDefinitions({ | |
| dynamic: Tool.make({ description: "Dynamic tool.", jsonSchema: { type: "object" }, outputSchema: schema }), | |
| }) | |
| expect(typed?.outputSchema).toMatchObject({ | |
| type: "object", | |
| properties: { condition: { type: "string" } }, | |
| required: ["temperature", "condition"], | |
| additionalProperties: false, | |
| }) | |
| expect(Reflect.get(Reflect.get(typed?.outputSchema ?? {}, "properties") as object, "temperature")).toBeDefined() | |
| expect(dynamic?.outputSchema).toEqual(schema) | |
| }), | |
| ) | |
| it.effect("preserves content tool results from dynamic tools", () => | |
| Effect.gen(function* () { | |
| const screenshot = Tool.make({ | |
| description: "Capture a screenshot.", | |
| jsonSchema: { type: "object", properties: {} }, | |
| execute: () => | |
| Effect.succeed({ | |
| type: "content" as const, | |
| value: [ | |
| { type: "text" as const, text: "Screenshot captured." }, | |
| { type: "file" as const, uri: "data:image/png;base64,AAAA", mime: "image/png" }, | |
| ], | |
| }), | |
| }) | |
| const events = Array.from( | |
| yield* TestToolRuntime.runTools({ request: baseRequest, tools: { screenshot }, maxSteps: 1 }).pipe( | |
| Stream.runCollect, | |
| Effect.provide( | |
| scriptedResponses([sseEvents(toolCallChunk("call_1", "screenshot", "{}"), finishChunk("tool_calls"))]), | |
| ), | |
| ), | |
| ) | |
| expect(events.find(LLMEvent.is.toolResult)).toMatchObject({ | |
| type: "tool-result", | |
| id: "call_1", | |
| name: "screenshot", | |
| result: { | |
| type: "content", | |
| value: [ | |
| { type: "text", text: "Screenshot captured." }, | |
| { type: "file", uri: "data:image/png;base64,AAAA", mime: "image/png" }, | |
| ], | |
| }, | |
| }) | |
| }), | |
| ) | |
| it.effect("does not mistake dynamic tool output fields for dispatcher state", () => | |
| Effect.gen(function* () { | |
| const callerOwned = { type: "json" as const, value: { ok: true }, events: ["caller-owned"] } | |
| const eventful = Tool.make({ | |
| description: "Return an events field.", | |
| jsonSchema: { type: "object", properties: {} }, | |
| execute: () => Effect.succeed(callerOwned), | |
| }) | |
| const dispatched = yield* ToolRuntime.dispatch( | |
| { eventful }, | |
| LLMEvent.toolCall({ id: "call_1", name: "eventful", input: {} }), | |
| ) | |
| expect(dispatched.result).toEqual(callerOwned) | |
| expect(dispatched.events).toEqual([ | |
| LLMEvent.toolResult({ | |
| id: "call_1", | |
| name: "eventful", | |
| result: callerOwned, | |
| output: { structured: { ok: true }, content: [] }, | |
| }), | |
| ]) | |
| }), | |
| ) | |
| it.effect("executes tool calls for one step without looping by default", () => | |
| Effect.gen(function* () { | |
| const layer = scriptedResponses([ | |
| sseEvents(toolCallChunk("call_1", "get_weather", '{"city":"Paris"}'), finishChunk("tool_calls")), | |
| sseEvents(deltaChunk({ role: "assistant", content: "Should not run." }), finishChunk("stop")), | |
| ]) | |
| const events = Array.from( | |
| yield* TestToolRuntime.runTools({ request: baseRequest, tools: { get_weather }, maxSteps: 1 }).pipe( | |
| Stream.runCollect, | |
| Effect.provide(layer), | |
| ), | |
| ) | |
| expect(events.filter(LLMEvent.is.finish)).toHaveLength(1) | |
| expect(events.find(LLMEvent.is.toolResult)).toMatchObject({ type: "tool-result", id: "call_1" }) | |
| }), | |
| ) | |
| it.effect("passes tool call context to execute", () => | |
| Effect.gen(function* () { | |
| let context: ToolExecuteContext | undefined | |
| const contextual = Tool.make({ | |
| description: "Capture tool context.", | |
| parameters: Schema.Struct({ value: Schema.String }), | |
| success: Schema.Struct({ ok: Schema.Boolean }), | |
| execute: (_params, ctx) => | |
| Effect.sync(() => { | |
| context = ctx | |
| return { ok: true } | |
| }), | |
| }) | |
| const events = Array.from( | |
| yield* TestToolRuntime.runTools({ request: baseRequest, tools: { contextual } }).pipe( | |
| Stream.runCollect, | |
| Effect.provide( | |
| scriptedResponses([ | |
| sseEvents(toolCallChunk("call_ctx", "contextual", '{"value":"x"}'), finishChunk("tool_calls")), | |
| ]), | |
| ), | |
| ), | |
| ) | |
| expect(events.some(LLMEvent.is.toolResult)).toBe(true) | |
| expect(context).toEqual({ id: "call_ctx", name: "contextual" }) | |
| }), | |
| ) | |
| it.effect("can expose tool schemas without executing tool calls", () => | |
| Effect.gen(function* () { | |
| const layer = scriptedResponses([ | |
| sseEvents(toolCallChunk("call_1", "get_weather", '{"city":"Paris"}'), finishChunk("tool_calls")), | |
| ]) | |
| const events = Array.from( | |
| yield* LLMClient.stream( | |
| LLMRequest.update(baseRequest, { tools: toDefinitions({ get_weather: schema_only_weather }) }), | |
| ).pipe(Stream.runCollect, Effect.provide(layer)), | |
| ) | |
| expect(events.find(LLMEvent.is.toolCall)).toMatchObject({ type: "tool-call", id: "call_1" }) | |
| expect(events.find(LLMEvent.is.toolResult)).toBeUndefined() | |
| }), | |
| ) | |
| it.effect("preserves provider metadata when folding streamed assistant content into follow-up history", () => | |
| Effect.gen(function* () { | |
| const bodies: unknown[] = [] | |
| const layer = dynamicResponse((input) => | |
| Effect.sync(() => { | |
| bodies.push(decodeJson(input.text)) | |
| return input.respond( | |
| bodies.length === 1 | |
| ? sseEvents( | |
| { type: "message_start", message: { usage: { input_tokens: 5 } } }, | |
| { type: "content_block_start", index: 0, content_block: { type: "thinking", thinking: "" } }, | |
| { type: "content_block_delta", index: 0, delta: { type: "thinking_delta", thinking: "thinking" } }, | |
| { type: "content_block_delta", index: 0, delta: { type: "signature_delta", signature: "sig_1" } }, | |
| { type: "content_block_stop", index: 0 }, | |
| { | |
| type: "content_block_start", | |
| index: 1, | |
| content_block: { type: "tool_use", id: "call_1", name: "get_weather" }, | |
| }, | |
| { | |
| type: "content_block_delta", | |
| index: 1, | |
| delta: { type: "input_json_delta", partial_json: '{"city":"Paris"}' }, | |
| }, | |
| { type: "content_block_stop", index: 1 }, | |
| { type: "message_delta", delta: { stop_reason: "tool_use" }, usage: { output_tokens: 5 } }, | |
| ) | |
| : sseEvents( | |
| { type: "message_start", message: { usage: { input_tokens: 5 } } }, | |
| { type: "content_block_start", index: 0, content_block: { type: "text", text: "" } }, | |
| { type: "content_block_delta", index: 0, delta: { type: "text_delta", text: "Done." } }, | |
| { type: "content_block_stop", index: 0 }, | |
| { type: "message_delta", delta: { stop_reason: "end_turn" }, usage: { output_tokens: 1 } }, | |
| ), | |
| { headers: { "content-type": "text/event-stream" } }, | |
| ) | |
| }), | |
| ) | |
| yield* TestToolRuntime.runTools({ | |
| request: LLM.updateRequest(baseRequest, { | |
| model: AnthropicMessages.route | |
| .with({ auth: Auth.header("x-api-key", "test") }) | |
| .model({ id: "claude-sonnet-4-5" }), | |
| }), | |
| tools: { get_weather }, | |
| }).pipe(Stream.runCollect, Effect.provide(layer)) | |
| expect(bodies[1]).toMatchObject({ | |
| messages: [ | |
| { role: "user" }, | |
| { | |
| role: "assistant", | |
| content: [ | |
| { type: "thinking", thinking: "thinking", signature: "sig_1" }, | |
| { type: "tool_use", id: "call_1", name: "get_weather", input: { city: "Paris" } }, | |
| ], | |
| }, | |
| { role: "user", content: [{ type: "tool_result", tool_use_id: "call_1" }] }, | |
| ], | |
| }) | |
| }), | |
| ) | |
| it.effect("replays encrypted OpenAI reasoning items with tool outputs", () => | |
| Effect.gen(function* () { | |
| const bodies: unknown[] = [] | |
| const layer = dynamicResponse((input) => | |
| Effect.sync(() => { | |
| bodies.push(decodeJson(input.text)) | |
| return input.respond( | |
| bodies.length === 1 | |
| ? sseEvents( | |
| { | |
| type: "response.output_item.added", | |
| item: { type: "reasoning", id: "rs_1", encrypted_content: null }, | |
| }, | |
| { type: "response.reasoning_summary_part.added", item_id: "rs_1", summary_index: 0 }, | |
| { type: "response.reasoning_summary_part.done", item_id: "rs_1", summary_index: 0 }, | |
| { | |
| type: "response.output_item.done", | |
| item: { type: "reasoning", id: "rs_1", encrypted_content: "encrypted-state" }, | |
| }, | |
| { | |
| type: "response.output_item.added", | |
| item: { | |
| type: "function_call", | |
| id: "item_1", | |
| call_id: "call_1", | |
| name: "get_weather", | |
| arguments: "", | |
| }, | |
| }, | |
| { type: "response.function_call_arguments.delta", item_id: "item_1", delta: '{"city":"Paris"}' }, | |
| { | |
| type: "response.output_item.done", | |
| item: { | |
| type: "function_call", | |
| id: "item_1", | |
| call_id: "call_1", | |
| name: "get_weather", | |
| arguments: '{"city":"Paris"}', | |
| }, | |
| }, | |
| { type: "response.completed", response: {} }, | |
| ) | |
| : sseEvents( | |
| { type: "response.output_text.delta", item_id: "msg_1", delta: "Done." }, | |
| { type: "response.completed", response: {} }, | |
| ), | |
| { headers: { "content-type": "text/event-stream" } }, | |
| ) | |
| }), | |
| ) | |
| yield* TestToolRuntime.runTools({ | |
| request: LLM.request({ | |
| model: OpenAIResponses.route | |
| .with({ endpoint: { baseURL: "https://api.openai.test/v1/" }, auth: Auth.bearer("test") }) | |
| .model({ id: "gpt-5.5" }), | |
| prompt: "Use the tool.", | |
| providerOptions: { openai: { store: false, include: ["reasoning.encrypted_content"] } }, | |
| }), | |
| tools: { get_weather }, | |
| }).pipe(Stream.runCollect, Effect.provide(layer)) | |
| expect(bodies[1]).toMatchObject({ | |
| include: ["reasoning.encrypted_content"], | |
| input: [ | |
| { role: "user" }, | |
| { type: "reasoning", id: "rs_1", summary: [], encrypted_content: "encrypted-state" }, | |
| { type: "function_call", call_id: "call_1", name: "get_weather" }, | |
| { type: "function_call_output", call_id: "call_1" }, | |
| ], | |
| }) | |
| }), | |
| ) | |
| it.effect("emits tool-error for unknown tools so the model can self-correct", () => | |
| Effect.gen(function* () { | |
| const layer = scriptedResponses([ | |
| sseEvents(toolCallChunk("call_1", "missing_tool", "{}"), finishChunk("tool_calls")), | |
| sseEvents(deltaChunk({ role: "assistant", content: "Sorry." }), finishChunk("stop")), | |
| ]) | |
| const events = Array.from( | |
| yield* TestToolRuntime.runTools({ request: baseRequest, tools: { get_weather } }).pipe( | |
| Stream.runCollect, | |
| Effect.provide(layer), | |
| ), | |
| ) | |
| const toolError = events.find(LLMEvent.is.toolError) | |
| expect(toolError).toMatchObject({ type: "tool-error", id: "call_1", name: "missing_tool" }) | |
| expect(toolError?.message).toContain("Unknown tool") | |
| expect(events.find(LLMEvent.is.toolResult)).toMatchObject({ | |
| type: "tool-result", | |
| id: "call_1", | |
| name: "missing_tool", | |
| result: { type: "error", value: "Unknown tool: missing_tool" }, | |
| }) | |
| }), | |
| ) | |
| it.effect("emits tool-error when the LLM input fails the parameters schema", () => | |
| Effect.gen(function* () { | |
| const layer = scriptedResponses([ | |
| sseEvents(toolCallChunk("call_1", "get_weather", '{"city":42}'), finishChunk("tool_calls")), | |
| sseEvents(deltaChunk({ role: "assistant", content: "Done." }), finishChunk("stop")), | |
| ]) | |
| const events = Array.from( | |
| yield* TestToolRuntime.runTools({ request: baseRequest, tools: { get_weather } }).pipe( | |
| Stream.runCollect, | |
| Effect.provide(layer), | |
| ), | |
| ) | |
| const toolError = events.find(LLMEvent.is.toolError) | |
| expect(toolError).toMatchObject({ type: "tool-error", id: "call_1", name: "get_weather" }) | |
| expect(toolError?.message).toContain("Invalid tool input") | |
| }), | |
| ) | |
| it.effect("emits tool-error when the handler returns a ToolFailure", () => | |
| Effect.gen(function* () { | |
| const layer = scriptedResponses([ | |
| sseEvents(toolCallChunk("call_1", "get_weather", '{"city":"FAIL"}'), finishChunk("tool_calls")), | |
| sseEvents(deltaChunk({ role: "assistant", content: "Sorry." }), finishChunk("stop")), | |
| ]) | |
| const events = Array.from( | |
| yield* TestToolRuntime.runTools({ request: baseRequest, tools: { get_weather } }).pipe( | |
| Stream.runCollect, | |
| Effect.provide(layer), | |
| ), | |
| ) | |
| const toolError = events.find(LLMEvent.is.toolError) | |
| expect(toolError).toMatchObject({ type: "tool-error", id: "call_1", name: "get_weather" }) | |
| expect(toolError?.message).toBe("Weather lookup failed for FAIL") | |
| expect(toolError?.error).toBe(weatherFailureCause) | |
| }), | |
| ) | |
| it.effect("stops when the model finishes without requesting more tools", () => | |
| Effect.gen(function* () { | |
| const layer = scriptedResponses([ | |
| sseEvents(deltaChunk({ role: "assistant", content: "Done." }), finishChunk("stop")), | |
| ]) | |
| const events = Array.from( | |
| yield* TestToolRuntime.runTools({ request: baseRequest, tools: { get_weather } }).pipe( | |
| Stream.runCollect, | |
| Effect.provide(layer), | |
| ), | |
| ) | |
| expect(events.map((event) => event.type)).toEqual([ | |
| "step-start", | |
| "text-start", | |
| "text-delta", | |
| "text-end", | |
| "step-finish", | |
| "finish", | |
| ]) | |
| expect(LLMResponse.text({ events })).toBe("Done.") | |
| }), | |
| ) | |
| it.effect("respects maxSteps and stops the loop", () => | |
| Effect.gen(function* () { | |
| // Every script entry asks for another tool call. With maxSteps: 2 the | |
| // runtime should run at most two model rounds and then exit even though | |
| // the model still wants to keep going. | |
| const toolCallStep = sseEvents( | |
| toolCallChunk("call_x", "get_weather", '{"city":"Paris"}'), | |
| finishChunk("tool_calls"), | |
| ) | |
| const layer = scriptedResponses([toolCallStep, toolCallStep, toolCallStep]) | |
| const events = Array.from( | |
| yield* TestToolRuntime.runTools({ request: baseRequest, tools: { get_weather }, maxSteps: 2 }).pipe( | |
| Stream.runCollect, | |
| Effect.provide(layer), | |
| ), | |
| ) | |
| expect(events.filter(LLMEvent.is.finish)).toHaveLength(1) | |
| expect(events.filter(LLMEvent.is.stepStart).map((event) => event.index)).toEqual([0, 1]) | |
| expect(events.filter(LLMEvent.is.stepFinish).map((event) => event.index)).toEqual([0, 1]) | |
| }), | |
| ) | |
| it.effect("does not dispatch provider-executed tool calls", () => | |
| Effect.gen(function* () { | |
| let streams = 0 | |
| const layer = dynamicResponse((input) => | |
| Effect.sync(() => { | |
| streams++ | |
| return input.respond( | |
| sseEvents( | |
| { type: "message_start", message: { usage: { input_tokens: 5 } } }, | |
| { | |
| type: "content_block_start", | |
| index: 0, | |
| content_block: { type: "server_tool_use", id: "srvtoolu_abc", name: "web_search" }, | |
| }, | |
| { | |
| type: "content_block_delta", | |
| index: 0, | |
| delta: { type: "input_json_delta", partial_json: '{"query":"x"}' }, | |
| }, | |
| { type: "content_block_stop", index: 0 }, | |
| { | |
| type: "content_block_start", | |
| index: 1, | |
| content_block: { | |
| type: "web_search_tool_result", | |
| tool_use_id: "srvtoolu_abc", | |
| content: [{ type: "web_search_result", url: "https://example.com", title: "Example" }], | |
| }, | |
| }, | |
| { type: "content_block_stop", index: 1 }, | |
| { type: "content_block_start", index: 2, content_block: { type: "text", text: "" } }, | |
| { type: "content_block_delta", index: 2, delta: { type: "text_delta", text: "Done." } }, | |
| { type: "content_block_stop", index: 2 }, | |
| { type: "message_delta", delta: { stop_reason: "end_turn" }, usage: { output_tokens: 8 } }, | |
| ), | |
| { headers: { "content-type": "text/event-stream" } }, | |
| ) | |
| }), | |
| ) | |
| const events = Array.from( | |
| yield* TestToolRuntime.runTools({ | |
| request: LLM.updateRequest(baseRequest, { | |
| model: AnthropicMessages.route | |
| .with({ auth: Auth.header("x-api-key", "test") }) | |
| .model({ id: "claude-sonnet-4-5" }), | |
| }), | |
| tools: {}, | |
| }).pipe(Stream.runCollect, Effect.provide(layer)), | |
| ) | |
| expect(streams).toBe(1) | |
| expect(events.find(LLMEvent.is.toolError)).toBeUndefined() | |
| expect(events.filter(LLMEvent.is.toolCall)).toEqual([ | |
| { | |
| type: "tool-call", | |
| id: "srvtoolu_abc", | |
| name: "web_search", | |
| input: { query: "x" }, | |
| providerExecuted: true, | |
| }, | |
| ]) | |
| expect(LLMResponse.text({ events })).toBe("Done.") | |
| }), | |
| ) | |
| it.effect("dispatches multiple tool calls in one step concurrently", () => | |
| Effect.gen(function* () { | |
| const layer = scriptedResponses([ | |
| sseEvents( | |
| deltaChunk({ | |
| role: "assistant", | |
| tool_calls: [ | |
| { index: 0, id: "c1", function: { name: "get_weather", arguments: '{"city":"Paris"}' } }, | |
| { index: 1, id: "c2", function: { name: "get_weather", arguments: '{"city":"Tokyo"}' } }, | |
| ], | |
| }), | |
| finishChunk("tool_calls"), | |
| ), | |
| sseEvents(deltaChunk({ role: "assistant", content: "Both done." }), finishChunk("stop")), | |
| ]) | |
| const events = Array.from( | |
| yield* TestToolRuntime.runTools({ request: baseRequest, tools: { get_weather } }).pipe( | |
| Stream.runCollect, | |
| Effect.provide(layer), | |
| ), | |
| ) | |
| const results = events.filter(LLMEvent.is.toolResult) | |
| expect(results).toHaveLength(2) | |
| expect(results.map((event) => event.id).toSorted()).toEqual(["c1", "c2"]) | |
| }), | |
| ) | |
| }) | |
Xet Storage Details
- Size:
- 31.6 kB
- Xet hash:
- 4faa431b347113928bd03353e2cdfe195e4c1296ff9cf2609602b01e58018227
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.