diff --git a/.changeset/great-eyes-buy.md b/.changeset/great-eyes-buy.md new file mode 100644 index 00000000000..10390570805 --- /dev/null +++ b/.changeset/great-eyes-buy.md @@ -0,0 +1,65 @@ +--- +"@effect/ai": patch +--- + +Remove `Either` / `EitherEncoded` from tool call results. + +Specifically, the encoding of tool call results as an `Either` / `EitherEncoded` has been removed and is replaced by encoding the tool call success / failure directly into the `result` property. + +To allow type-safe discrimination between a tool call result which was a success vs. one that was a failure, an `isFailure` property has also been added to the `"tool-result"` part. If `isFailure` is `true`, then the tool call handler result was an error. + +```ts +import * as AnthropicClient from "@effect/ai-anthropic/AnthropicClient" +import * as AnthropicLanguageModel from "@effect/ai-anthropic/AnthropicLanguageModel" +import * as LanguageModel from "@effect/ai/LanguageModel" +import * as Tool from "@effect/ai/Tool" +import * as Toolkit from "@effect/ai/Toolkit" +import * as NodeHttpClient from "@effect/platform-node/NodeHttpClient" +import { Config, Effect, Layer, Schema, Stream } from "effect" + +const Claude = AnthropicLanguageModel.model("claude-4-sonnet-20250514") + +const MyTool = Tool.make("MyTool", { + description: "An example of a tool with success and failure types", + failureMode: "return", // Return errors in the response + parameters: { bar: Schema.Number }, + success: Schema.Number, + failure: Schema.Struct({ reason: Schema.Literal("reason-1", "reason-2") }) +}) + +const MyToolkit = Toolkit.make(MyTool) + +const MyToolkitLayer = MyToolkit.toLayer({ + MyTool: () => Effect.succeed(42) +}) + +const program = LanguageModel.streamText({ + prompt: "Tell me about the meaning of life", + toolkit: MyToolkit +}).pipe( + Stream.runForEach((part) => { + if (part.type === "tool-result" && part.name === "MyTool") { + // The `isFailure` property can be used to discriminate whether the result + // of a tool call is a success or a failure + if (part.isFailure) { + part.result + // ^? { readonly reason: "reason-1" | "reason-2"; } + } else { + part.result + // ^? number + } + } + return Effect.void + }), + Effect.provide(Claude) +) + +const Anthropic = AnthropicClient.layerConfig({ + apiKey: Config.redacted("ANTHROPIC_API_KEY") +}).pipe(Layer.provide(NodeHttpClient.layerUndici)) + +program.pipe( + Effect.provide([Anthropic, MyToolkitLayer]), + Effect.runPromise +) +``` diff --git a/packages/ai/ai/src/LanguageModel.ts b/packages/ai/ai/src/LanguageModel.ts index daf67f59acf..f8052fc16cf 100644 --- a/packages/ai/ai/src/LanguageModel.ts +++ b/packages/ai/ai/src/LanguageModel.ts @@ -1010,12 +1010,13 @@ const resolveToolCalls = >( return Effect.forEach(toolCalls, (toolCall) => { return toolkit.handle(toolCall.name, toolCall.params as any).pipe( - Effect.map(({ encodedResult, result }) => + Effect.map(({ encodedResult, isFailure, result }) => Response.makePart("tool-result", { id: toolCall.id, name: toolCall.name, result, encodedResult, + isFailure, providerExecuted: false, ...(toolCall.providerName !== undefined ? { providerName: toolCall.providerName } diff --git a/packages/ai/ai/src/Prompt.ts b/packages/ai/ai/src/Prompt.ts index 3c773224114..e1e6d105ef8 100644 --- a/packages/ai/ai/src/Prompt.ts +++ b/packages/ai/ai/src/Prompt.ts @@ -606,13 +606,11 @@ export const toolCallPart = (params: PartConstructorParams): ToolC * const toolResultPart: Prompt.ToolResultPart = Prompt.makePart("tool-result", { * id: "call_123", * name: "get_weather", + * isFailure: false, * result: { - * _tag: "Right", - * right: { - * temperature: 22, - * condition: "sunny", - * humidity: 65 - * } + * temperature: 22, + * condition: "sunny", + * humidity: 65 * } * }) * ``` @@ -629,10 +627,14 @@ export interface ToolResultPart extends BasePart<"tool-result", ToolResultPartOp * Name of the tool that was executed. */ readonly name: string + /** + * Whether or not the result of executing the tool call handler was an error. + */ + readonly isFailure: boolean /** * The result returned by the tool execution. */ - readonly result: Schema.EitherEncoded + readonly result: unknown } /** @@ -650,10 +652,14 @@ export interface ToolResultPartEncoded extends BasePartEncoded<"tool-result", To * Name of the tool that was executed. */ readonly name: string + /** + * Whether or not the result of executing the tool call handler was an error. + */ + readonly isFailure: boolean /** * The result returned by the tool execution. */ - readonly result: Schema.EitherEncoded + readonly result: unknown } /** @@ -675,10 +681,8 @@ export const ToolResultPart: Schema.Schema): User * Prompt.makePart("tool-result", { * id: "call_123", * name: "get_weather", + * isFailure: false, * result: { - * _tag: "Right", - * right: { temperature: 72, condition: "sunny" } + * temperature: 72, + * condition: "sunny" * } * }), * Prompt.makePart("text", { @@ -1138,15 +1143,13 @@ export const assistantMessage = (params: MessageConstructorParams { * Response.makePart("tool-result", { * id: "call_1", * name: "get_time", - * result: Either.right("10:30 AM"), - * encodedResult: { _tag: "Right", right: "10:30 AM" }, + * isFailure: false, + * result: "10:30 AM", + * encodedResult: "10:30 AM", * providerExecuted: false * }) * ] @@ -1650,6 +1654,7 @@ export const fromResponseParts = (parts: ReadonlyArray): Promp toolParts.push(makePart("tool-result", { id: part.id, name: part.providerName ?? part.name, + isFailure: part.isFailure, result: part.encodedResult })) break diff --git a/packages/ai/ai/src/Response.ts b/packages/ai/ai/src/Response.ts index b49ef505b23..3d1806dd15c 100644 --- a/packages/ai/ai/src/Response.ts +++ b/packages/ai/ai/src/Response.ts @@ -28,7 +28,6 @@ */ import type * as DateTime from "effect/DateTime" import * as Effect from "effect/Effect" -import type * as Either from "effect/Either" import { constFalse } from "effect/Function" import type * as Option from "effect/Option" import * as ParseResult from "effect/ParseResult" @@ -1506,6 +1505,75 @@ export const toolCallPart = ( // Tool Call Result Part // ============================================================================= +/** + * The base fields of a tool result part. + * + * @since 1.0.0 + * @category Models + */ +export interface BaseToolResult extends BasePart<"tool-result", ToolResultPartMetadata> { + /** + * Unique identifier matching the original tool call. + */ + readonly id: string + /** + * Name of the tool being called, which corresponds to the name of the tool + * in the `Toolkit` included with the request. + */ + readonly name: Name + /** + * The encoded result for serialization purposes. + */ + readonly encodedResult: unknown + /** + * Optional provider-specific name for the tool, which can be useful when the + * name of the tool in the `Toolkit` and the name of the tool used by the + * model are different. + * + * This is usually happens only with provider-defined tools which require a + * user-space handler. + */ + readonly providerName?: string | undefined + /** + * Whether the tool was executed by the provider (true) or framework (false). + */ + readonly providerExecuted: boolean +} + +/** + * Represents a successful tool call result. + * + * @since 1.0.0 + * @category Models + */ +export interface ToolResultSuccess extends BaseToolResult { + /** + * The decoded success returned by the tool execution. + */ + readonly result: Success + /** + * Whether or not the result of executing the tool call handler was an error. + */ + readonly isFailure: false +} + +/** + * Represents a failed tool call result. + * + * @since 1.0.0 + * @category Models + */ +export interface ToolResultFailure extends BaseToolResult { + /** + * The decoded failure returned by the tool execution. + */ + readonly result: Failure + /** + * Whether or not the result of executing the tool call handler was an error. + */ + readonly isFailure: true +} + /** * Response part representing the result of a tool call. * @@ -1527,18 +1595,16 @@ export const toolCallPart = ( * > = Response.toolResultPart({ * id: "call_123", * name: "get_weather", - * result: Either.right({ + * isFailure: false, + * result: { * temperature: 22, * condition: "sunny", * humidity: 65 - * }), + * }, * encodedResult: { - * _tag: "Right", - * right: { - * temperature: 22, - * condition: "sunny", - * humidity: 65 - * } + * temperature: 22, + * condition: "sunny", + * humidity: 65 * }, * providerExecuted: false * }) @@ -1547,40 +1613,9 @@ export const toolCallPart = ( * @since 1.0.0 * @category Models */ -export interface ToolResultPart - extends BasePart<"tool-result", ToolResultPartMetadata> -{ - /** - * Unique identifier matching the original tool call. - */ - readonly id: string - /** - * Name of the tool being called, which corresponds to the name of the tool - * in the `Toolkit` included with the request. - */ - readonly name: Name - /** - * The decoded result returned by the tool execution. - */ - readonly result: Either.Either - /** - * The encoded result for serialization purposes. - */ - readonly encodedResult: Schema.EitherEncoded - /** - * Optional provider-specific name for the tool, which can be useful when the - * name of the tool in the `Toolkit` and the name of the tool used by the - * model are different. - * - * This is usually happens only with provider-defined tools which require a - * user-space handler. - */ - readonly providerName?: string | undefined - /** - * Whether the tool was executed by the provider (true) or framework (false). - */ - readonly providerExecuted: boolean -} +export type ToolResultPart = + | ToolResultSuccess + | ToolResultFailure /** * Encoded representation of tool result parts for serialization. @@ -1601,7 +1636,11 @@ export interface ToolResultPartEncoded extends BasePartEncoded<"tool-result", To /** * The result returned by the tool execution. */ - readonly result: Schema.EitherEncoded + readonly result: unknown + /** + * Whether or not the result of executing the tool call handler was an error. + */ + readonly isFailure: boolean /** * Optional provider-specific name for the tool, which can be useful when the * name of the tool in the `Toolkit` and the name of the tool used by the @@ -1644,15 +1683,13 @@ export const ToolResultPart = < ToolResultPart, Schema.Schema.Type>, ToolResultPartEncoded > => { - const ResultSchema = Schema.Either({ - left: failure, - right: success - }) const Base = Schema.Struct({ id: Schema.String, type: Schema.Literal("tool-result"), - providerName: Schema.optional(Schema.String) + providerName: Schema.optional(Schema.String), + isFailure: Schema.Boolean }) + const ResultSchema = Schema.Union(success, failure) const Encoded = Schema.Struct({ ...Base.fields, name: Schema.String, @@ -1692,11 +1729,9 @@ export const ToolResultPart = < encode: Effect.fnUntraced(function*(decoded) { const encoded = yield* encodeResult(decoded.result) return { - id: decoded.id, - type: decoded.type, - name: decoded.name, + ...decoded, result: encoded, - ...(decoded.metadata ? { metadata: decoded.metadata } : {}), + ...(decoded.metadata ?? {}), ...(decoded.providerName ? { providerName: decoded.providerName } : {}), ...(decoded.providerExecuted ? { providerExecuted: true } : {}) } @@ -1711,9 +1746,21 @@ export const ToolResultPart = < * @since 1.0.0 * @category Constructors */ -export const toolResultPart = ( - params: ConstructorParams> -): ToolResultPart => makePart("tool-result", params) +export const toolResultPart = < + const Params extends ConstructorParams> +>( + params: Params +): Params extends { + readonly name: infer Name extends string + readonly isFailure: false + readonly result: infer Success +} ? ToolResultPart + : Params extends { + readonly name: infer Name extends string + readonly isFailure: true + readonly result: infer Failure + } ? ToolResultPart + : never => makePart("tool-result", params) as any // ============================================================================= // File Part diff --git a/packages/ai/ai/src/Tool.ts b/packages/ai/ai/src/Tool.ts index 18cb14c23e3..3dcb012d5cc 100644 --- a/packages/ai/ai/src/Tool.ts +++ b/packages/ai/ai/src/Tool.ts @@ -27,7 +27,7 @@ * @since 1.0.0 */ import * as Context from "effect/Context" -import * as Effect from "effect/Effect" +import type * as Effect from "effect/Effect" import { constFalse, constTrue, identity } from "effect/Function" import * as JsonSchema from "effect/JSONSchema" import * as Option from "effect/Option" @@ -37,7 +37,7 @@ import * as Predicate from "effect/Predicate" import * as Schema from "effect/Schema" import * as AST from "effect/SchemaAST" import type { Covariant } from "effect/Types" -import * as AiError from "./AiError.js" +import type * as AiError from "./AiError.js" // ============================================================================= // Type Ids @@ -164,12 +164,6 @@ export interface Tool< */ readonly failureSchema: Config["failure"] - /** - * A `Schema` representing the result of a tool call, whether it succeeds or - * fails. - */ - readonly resultSchema: Schema.Either - /** * A `Context` object containing tool annotations which can store metadata * about the tool. @@ -340,13 +334,6 @@ export interface ProviderDefined< * this tool into a `Layer`. */ readonly requiresHandler: RequiresHandler - - /** - * Decodes the result received after the provider-defined tool is called. - */ - decodeResult( - args: unknown - ): Effect.Effect } /** @@ -509,7 +496,6 @@ export interface Any extends Pipeable { readonly parametersSchema: AnyStructSchema readonly successSchema: Schema.Schema.Any readonly failureSchema: Schema.Schema.All - readonly resultSchema: Schema.Either readonly failureMode: FailureMode readonly annotations: Context.Context } @@ -705,8 +691,8 @@ export type Result = T extends Tool< infer _Name, infer _Config, infer _Requirements -> ? Schema.Either<_Config["success"], _Config["failure"]>["Type"] : - never +> ? Success | Failure + : never /** * A utility type to extract the encoded type of the tool call result whether @@ -719,8 +705,8 @@ export type ResultEncoded = T extends Tool< infer _Name, infer _Config, infer _Requirements -> ? Schema.Either<_Config["success"], _Config["failure"]>["Encoded"] : - never +> ? SuccessEncoded | FailureEncoded + : never /** * A utility type to extract the requirements of an `Tool`. @@ -759,6 +745,10 @@ export interface Handler { * @category Models */ export interface HandlerResult { + /** + * Whether the result of executing the tool call handler was an error or not. + */ + readonly isFailure: boolean /** * The result of executing the handler for a particular tool. */ @@ -768,7 +758,7 @@ export interface HandlerResult { * tool as a JSON-serializable value. The encoded result can be incorporated * into subsequent requests to the large language model. */ - readonly encodedResult: Schema.EitherEncoded + readonly encodedResult: unknown } /** @@ -782,7 +772,7 @@ export type HandlerError = T extends Tool< infer _Name, infer _Config, infer _Requirements -> ? _Config["failureMode"] extends "error" ? Schema.Schema.Type<_Config["failure"]> +> ? _Config["failureMode"] extends "error" ? _Config["failure"]["Type"] : never : never @@ -863,21 +853,7 @@ const Proto = { const ProviderDefinedProto = { ...Proto, - [ProviderDefinedTypeId]: ProviderDefinedTypeId, - decodeResult(this: AnyProviderDefined, result: unknown) { - return Schema.decodeUnknown(this.successSchema)(result).pipe( - Effect.orElse(() => Schema.decodeUnknown(this.failureSchema as any)(result)), - Effect.mapError( - (cause) => - new AiError.MalformedOutput({ - module: "Tool", - method: "ProviderDefined.decodeResult", - description: `Failed to decode the result of provider-defined tool '${this.name}'`, - cause - }) - ) - ) - } + [ProviderDefinedTypeId]: ProviderDefinedTypeId } const userDefinedProto = < @@ -892,7 +868,6 @@ const userDefinedProto = < readonly parametersSchema: Parameters readonly successSchema: Success readonly failureSchema: Failure - readonly resultSchema: Schema.Either readonly annotations: Context.Context readonly failureMode: Mode }): Tool< @@ -927,7 +902,6 @@ const providerDefinedProto = < readonly parametersSchema: Parameters readonly successSchema: Success readonly failureSchema: Failure - readonly resultSchema: Schema.Either readonly failureMode: FailureMode }): ProviderDefined< Name, @@ -1022,10 +996,6 @@ export const make = < > => { const successSchema = options?.success ?? Schema.Void const failureSchema = options?.failure ?? Schema.Never - const resultSchema = Schema.Either({ - left: failureSchema, - right: successSchema - }) return userDefinedProto({ name, description: options?.description, @@ -1034,7 +1004,6 @@ export const make = < : constEmptyStruct, successSchema, failureSchema, - resultSchema, failureMode: options?.failureMode ?? "error", annotations: Context.empty() }) as any @@ -1146,10 +1115,6 @@ export const providerDefined = < const failureMode = "failureMode" in args ? args.failureMode : undefined const successSchema = options?.success ?? Schema.Void const failureSchema = options?.failure ?? Schema.Never - const resultSchema = Schema.Either({ - right: successSchema, - left: failureSchema - }) return providerDefinedProto({ id: options.id, name: options.toolkitName, @@ -1162,7 +1127,6 @@ export const providerDefined = < : constEmptyStruct, successSchema, failureSchema, - resultSchema, failureMode: failureMode ?? "error" }) as any } @@ -1213,10 +1177,6 @@ export const fromTaggedRequest = ( parametersSchema: schema, successSchema: schema.success, failureSchema: schema.failure, - resultSchema: Schema.Either({ - left: schema.failure, - right: schema.success - }), failureMode: "error", annotations: Context.empty() }) as any diff --git a/packages/ai/ai/src/Toolkit.ts b/packages/ai/ai/src/Toolkit.ts index 77238f679d7..d164353283a 100644 --- a/packages/ai/ai/src/Toolkit.ts +++ b/packages/ai/ai/src/Toolkit.ts @@ -40,7 +40,6 @@ import * as Context from "effect/Context" import * as Effect from "effect/Effect" import { CommitPrototype } from "effect/Effectable" -import * as Either from "effect/Either" import { identity } from "effect/Function" import type { Inspectable } from "effect/Inspectable" import { BaseProto as InspectableProto } from "effect/Inspectable" @@ -267,16 +266,17 @@ const Proto = { readonly context: Context.Context readonly handler: (params: any) => Effect.Effect readonly decodeParameters: (u: unknown) => Effect.Effect, ParseError> - readonly validateResult: (u: unknown) => Effect.Effect, ParseError> - readonly encodeResult: (u: unknown) => Effect.Effect, ParseError> + readonly validateResult: (u: unknown) => Effect.Effect + readonly encodeResult: (u: unknown) => Effect.Effect }>() const getSchemas = (tool: Tool.Any) => { let schemas = schemasCache.get(tool) if (Predicate.isUndefined(schemas)) { const handler = context.unsafeMap.get(tool.id)! as Tool.Handler const decodeParameters = Schema.decodeUnknown(tool.parametersSchema) as any - const validateResult = Schema.validate(tool.resultSchema) as any - const encodeResult = Schema.encodeUnknown(tool.resultSchema) as any + const resultSchema = Schema.Union(tool.successSchema, tool.failureSchema) + const validateResult = Schema.validate(resultSchema) as any + const encodeResult = Schema.encodeUnknown(resultSchema) as any schemas = { context: handler.context, handler: handler.handler, @@ -313,15 +313,16 @@ const Proto = { cause }) ) - const result = yield* schemas.handler(decodedParams).pipe( - Effect.matchEffect({ - onFailure: (error) => - tool.failureMode === "error" - ? Effect.fail(error) - : Effect.succeed(Either.left(error)), - onSuccess: (value) => Effect.succeed(Either.right(value)) - }), - Effect.flatMap((either) => schemas.validateResult(either)), + const { isFailure, result } = yield* schemas.handler(decodedParams).pipe( + Effect.map((result) => ({ result, isFailure: false })), + Effect.catchAll((error) => + // If the tool handler failed, check the tool's failure mode to + // determine how the result should be returned to the end user + tool.failureMode === "error" + ? Effect.fail(error) + : Effect.succeed({ result: error, isFailure: true }) + ), + Effect.tap(({ result }) => schemas.validateResult(result)), Effect.mapInputContext((input) => Context.merge(schemas.context, input)), Effect.mapError((cause) => ParseResult.isParseError(cause) @@ -345,6 +346,7 @@ const Proto = { }) ) return { + isFailure, result, encodedResult } satisfies Tool.HandlerResult @@ -352,7 +354,7 @@ const Proto = { ) return { tools, - handle: handle as any + handle } satisfies WithHandler> }) }, diff --git a/packages/ai/ai/test/Tool.test.ts b/packages/ai/ai/test/Tool.test.ts index 886dc5a483d..1693a2a8883 100644 --- a/packages/ai/ai/test/Tool.test.ts +++ b/packages/ai/ai/test/Tool.test.ts @@ -4,7 +4,6 @@ import * as Tool from "@effect/ai/Tool" import * as Toolkit from "@effect/ai/Toolkit" import { assert, describe, it } from "@effect/vitest" import * as Effect from "effect/Effect" -import * as Either from "effect/Either" import * as Schema from "effect/Schema" import * as TestUtils from "./utilities.js" @@ -103,9 +102,10 @@ describe("Tool", () => { const expected = Response.makePart("tool-result", { id: toolCallId, + isFailure: false, name: toolName, - result: Either.right(toolResult), - encodedResult: { _tag: "Right", right: toolResult }, + result: toolResult, + encodedResult: toolResult, providerExecuted: false }) @@ -142,8 +142,9 @@ describe("Tool", () => { const expected = Response.makePart("tool-result", { id: toolCallId, name: toolName, - result: Either.left(toolResult), - encodedResult: { _tag: "Left", left: toolResult }, + isFailure: true, + result: toolResult, + encodedResult: toolResult, providerExecuted: false }) @@ -246,7 +247,8 @@ describe("Tool", () => { type: "tool-result", id: toolCallId, name: tool.name, - result: { _tag: "Right", right: toolResult }, + isFailure: false, + result: toolResult, providerName: tool.providerName, providerExecuted: true } @@ -257,8 +259,9 @@ describe("Tool", () => { const expected = Response.makePart("tool-result", { id: toolCallId, name: tool.name, - result: Either.right(toolResult), - encodedResult: { _tag: "Right", right: toolResult }, + isFailure: false, + result: toolResult, + encodedResult: toolResult, providerName: tool.providerName, providerExecuted: true }) @@ -293,8 +296,9 @@ describe("Tool", () => { { type: "tool-result", id: toolCallId, + isFailure: true, name: tool.name, - result: { _tag: "Left", left: toolResult }, + result: toolResult, providerName: tool.providerName, providerExecuted: true } @@ -305,8 +309,9 @@ describe("Tool", () => { const expected = Response.makePart("tool-result", { id: toolCallId, name: tool.name, - result: Either.left(toolResult), - encodedResult: { _tag: "Left", left: toolResult }, + isFailure: true, + result: toolResult, + encodedResult: toolResult, providerName: tool.providerName, providerExecuted: true }) @@ -352,8 +357,9 @@ describe("Tool", () => { const expected = Response.makePart("tool-result", { id: toolCallId, name: tool.name, - result: Either.right(toolResult), - encodedResult: { _tag: "Right", right: toolResult }, + isFailure: false, + result: toolResult, + encodedResult: toolResult, providerName: tool.providerName, providerExecuted: false }) @@ -400,8 +406,9 @@ describe("Tool", () => { const expected = Response.makePart("tool-result", { id: toolCallId, name: tool.name, - result: Either.left(toolResult), - encodedResult: { _tag: "Left", left: toolResult }, + isFailure: true, + result: toolResult, + encodedResult: toolResult, providerName: tool.providerName, providerExecuted: false }) diff --git a/packages/ai/amazon-bedrock/src/AmazonBedrockLanguageModel.ts b/packages/ai/amazon-bedrock/src/AmazonBedrockLanguageModel.ts index a8cf2c4ade0..fef36e59e4b 100644 --- a/packages/ai/amazon-bedrock/src/AmazonBedrockLanguageModel.ts +++ b/packages/ai/amazon-bedrock/src/AmazonBedrockLanguageModel.ts @@ -396,13 +396,10 @@ const prepareMessages: (options: LanguageModel.ProviderOptions) => Effect.Effect case "tool": { for (const part of message.content) { - const result = part.result._tag === "Right" - ? part.result.right - : part.result.left content.push({ toolResult: { toolUseId: part.id, - content: [{ text: JSON.stringify(result) }] + content: [{ text: JSON.stringify(part.result) }] } }) } diff --git a/packages/ai/anthropic/src/AnthropicLanguageModel.ts b/packages/ai/anthropic/src/AnthropicLanguageModel.ts index 37e542421bc..82155f6def7 100644 --- a/packages/ai/anthropic/src/AnthropicLanguageModel.ts +++ b/packages/ai/anthropic/src/AnthropicLanguageModel.ts @@ -568,13 +568,11 @@ const prepareMessages: (options: LanguageModel.ProviderOptions) => Effect.Effect isLastPart ? getCacheControl(message) : undefined ) - const result = part.result._tag === "Right" ? part.result.right : part.result.left - const isError = part.result._tag === "Left" content.push({ type: "tool_result", tool_use_id: part.id, - content: JSON.stringify(result), - is_error: isError, + content: JSON.stringify(part.result), + is_error: part.isFailure, cache_control: cacheControl }) } @@ -673,21 +671,18 @@ const prepareMessages: (options: LanguageModel.ProviderOptions) => Effect.Effect } case "tool-result": { - const result = part.result._tag === "Right" - ? part.result.right - : part.result.left if (part.name === "AnthropicCodeExecution") { content.push({ type: "code_execution_tool_result", tool_use_id: part.id, - content: result as any, + content: part.result as any, cache_control: cacheControl }) } else if (part.name === "AnthropicWebSearch") { content.push({ type: "web_search_tool_result", tool_use_id: part.id, - content: result as any, + content: part.result as any, cache_control: cacheControl }) } else { @@ -826,14 +821,13 @@ const makeResponse: ( } case "bash_code_execution_tool_result": { - const result = part.content.type === "bash_code_execution_result" - ? { _tag: "Right", right: part.content } as const - : { _tag: "Left", left: part.content } as const + const isFailure = part.content.type === "bash_code_execution_tool_result_error" parts.push({ type: "tool-result", id: part.tool_use_id, name: "AnthropicCodeExecution", - result, + isFailure, + result: part.content, providerName: "code_execution", providerExecuted: true }) @@ -841,14 +835,13 @@ const makeResponse: ( } case "code_execution_tool_result": { - const result = part.content.type === "code_execution_result" - ? { _tag: "Right", right: part.content } as const - : { _tag: "Left", left: part.content } as const + const isFailure = part.content.type === "code_execution_tool_result_error" parts.push({ type: "tool-result", id: part.tool_use_id, name: "AnthropicCodeExecution", - result, + isFailure, + result: part.content, providerName: "code_execution", providerExecuted: true }) @@ -856,14 +849,13 @@ const makeResponse: ( } case "text_editor_code_execution_tool_result": { - const result = part.content.type === "text_editor_code_execution_tool_result_error" - ? { _tag: "Left", left: part.content } as const - : { _tag: "Right", right: part.content } as const + const isFailure = part.content.type === "text_editor_code_execution_tool_result_error" parts.push({ type: "tool-result", id: part.tool_use_id, name: "AnthropicCodeExecution", - result, + isFailure, + result: part.content, providerName: "code_execution", providerExecuted: true }) @@ -871,14 +863,13 @@ const makeResponse: ( } case "web_search_tool_result": { - const result = Array.isArray(part.content) - ? { _tag: "Right", right: part.content } as const - : { _tag: "Left", left: part.content } as const + const isFailure = !Array.isArray(part.content) parts.push({ type: "tool-result", id: part.tool_use_id, name: "AnthropicWebSearch", - result, + isFailure, + result: part.content, providerName: "web_search", providerExecuted: true }) @@ -1120,14 +1111,13 @@ const makeStreamResponse: ( case "bash_code_execution_tool_result": { const toolUseId = event.content_block.tool_use_id const content = event.content_block.content - const result = content.type === "bash_code_execution_result" - ? { _tag: "Right", right: content } as const - : { _tag: "Left", left: content } as const + const isFailure = content.type === "bash_code_execution_tool_result_error" parts.push({ type: "tool-result", id: toolUseId, name: "AnthropicCodeExecution", - result, + isFailure, + result: content, providerName: "code_execution", providerExecuted: true }) @@ -1137,14 +1127,13 @@ const makeStreamResponse: ( case "code_execution_tool_result": { const toolUseId = event.content_block.tool_use_id const content = event.content_block.content - const result = content.type === "code_execution_result" - ? { _tag: "Right", right: content } as const - : { _tag: "Left", left: content } as const + const isFailure = content.type === "code_execution_tool_result_error" parts.push({ type: "tool-result", id: toolUseId, name: "AnthropicCodeExecution", - result, + isFailure, + result: content, providerName: "code_execution", providerExecuted: true }) @@ -1154,14 +1143,13 @@ const makeStreamResponse: ( case "text_editor_code_execution_tool_result": { const toolUseId = event.content_block.tool_use_id const content = event.content_block.content - const result = content.type === "text_editor_code_execution_tool_result_error" - ? { _tag: "Left", left: content } as const - : { _tag: "Right", right: content } as const + const isFailure = content.type === "text_editor_code_execution_tool_result_error" parts.push({ type: "tool-result", id: toolUseId, name: "AnthropicCodeExecution", - result, + isFailure, + result: content, providerName: "code_execution", providerExecuted: true }) @@ -1171,14 +1159,13 @@ const makeStreamResponse: ( case "web_search_tool_result": { const toolUseId = event.content_block.tool_use_id const content = event.content_block.content - const result = Array.isArray(content) - ? { _tag: "Right", right: content } as const - : { _tag: "Left", left: content } as const + const isFailure = !Array.isArray(content) parts.push({ type: "tool-result", id: toolUseId, name: "AnthropicWebSearch", - result, + isFailure, + result: content, providerName: "web_search", providerExecuted: true }) diff --git a/packages/ai/google/src/GoogleLanguageModel.ts b/packages/ai/google/src/GoogleLanguageModel.ts index 0c713fee53f..ff0dd61c30a 100644 --- a/packages/ai/google/src/GoogleLanguageModel.ts +++ b/packages/ai/google/src/GoogleLanguageModel.ts @@ -386,14 +386,11 @@ const prepareMessages: ( const parts: Array = [] for (const part of message.content) { - const result = part.result._tag === "Right" - ? part.result.right - : part.result.left parts.push({ functionResponse: { id: part.id, name: part.name, - response: result as any + response: part.result as any } }) } @@ -523,7 +520,8 @@ const makeResponse: (response: Generated.GenerateContentResponse) => Effect.Effe type: "tool-result", id: lastCodeExecutionToolCallId, name: "GoogleCodeExecution", - result: { _tag: "Right", right: part.codeExecutionResult }, + isFailure: false, + result: part.codeExecutionResult, providerName: "code_execution", providerExecuted: true }) @@ -677,7 +675,8 @@ const makeStreamResponse: ( type: "tool-result", id: lastCodeExecutionToolCallId, name: "GoogleCodeExecution", - result: { _tag: "Right", right: part.codeExecutionResult }, + isFailure: false, + result: part.codeExecutionResult, providerName: "code_execution", providerExecuted: true }) diff --git a/packages/ai/openai/src/OpenAiLanguageModel.ts b/packages/ai/openai/src/OpenAiLanguageModel.ts index 8a460573f6d..d6b197a30f3 100644 --- a/packages/ai/openai/src/OpenAiLanguageModel.ts +++ b/packages/ai/openai/src/OpenAiLanguageModel.ts @@ -520,13 +520,10 @@ const prepareMessages: ( case "tool": { for (const part of message.content) { - const result = part.result._tag === "Right" - ? part.result.right - : part.result.left messages.push({ type: "function_call_output", call_id: part.id, - output: JSON.stringify(result) + output: JSON.stringify(part.result) }) } @@ -677,7 +674,8 @@ const makeResponse: ( type: "tool-result", id: part.id, name: "OpenAiCodeInterpreter", - result: { _tag: "Right", right: { outputs: part.outputs } }, + isFailure: false, + result: part.outputs, providerName: "code_interpreter", providerExecuted: true }) @@ -699,13 +697,11 @@ const makeResponse: ( type: "tool-result", id: part.id, name: "OpenAiFileSearch", + isFailure: false, result: { - _tag: "Right", - right: { - status: part.status, - queries: part.queries, - ...(part.results && { results: part.results }) - } + status: part.status, + queries: part.queries, + ...(part.results && { results: part.results }) }, providerName: "file_search", providerExecuted: true @@ -728,7 +724,8 @@ const makeResponse: ( type: "tool-result", id: part.id, name: webSearchTool?.name ?? "OpenAiWebSearch", - result: { _tag: "Right", right: { status: part.status } }, + isFailure: false, + result: { status: part.status }, providerName: webSearchTool?.providerName ?? "web_search", providerExecuted: true }) @@ -979,7 +976,8 @@ const makeStreamResponse: ( type: "tool-result", id: event.item.id, name: "OpenAiCodeInterpreter", - result: { _tag: "Right", right: { outputs: event.item.outputs } }, + isFailure: false, + result: { outputs: event.item.outputs }, providerName: "code_interpreter", providerExecuted: true }) @@ -1009,13 +1007,11 @@ const makeStreamResponse: ( type: "tool-result", id: event.item.id, name: "OpenAiFileSearch", + isFailure: false, result: { - _tag: "Right", - right: { - status: event.item.status, - queries: event.item.queries, - ...(event.item.results && { results: event.item.results }) - } + status: event.item.status, + queries: event.item.queries, + ...(event.item.results && { results: event.item.results }) }, providerName: "file_search", providerExecuted: true @@ -1103,7 +1099,8 @@ const makeStreamResponse: ( type: "tool-result", id: event.item.id, name: "OpenAiWebSearch", - result: { _tag: "Right", right: { status: event.item.status } }, + isFailure: false, + result: { status: event.item.status }, providerName: "web_search", providerExecuted: true }) diff --git a/packages/ai/openrouter/src/OpenRouterLanguageModel.ts b/packages/ai/openrouter/src/OpenRouterLanguageModel.ts index a51a882a117..968cdc9799d 100644 --- a/packages/ai/openrouter/src/OpenRouterLanguageModel.ts +++ b/packages/ai/openrouter/src/OpenRouterLanguageModel.ts @@ -468,13 +468,10 @@ const prepareMessages: (options: LanguageModel.ProviderOptions) => Effect.Effect case "tool": { const cacheControl = getCacheControl(message) for (const part of message.content) { - const result = part.result._tag === "Right" - ? part.result.right - : part.result.left messages.push({ role: "tool", tool_call_id: part.id, - content: JSON.stringify(result), + content: JSON.stringify(part.result), cache_control: cacheControl }) }