@effect/ai 0.30.0 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect/ai",
3
- "version": "0.30.0",
3
+ "version": "0.31.0",
4
4
  "description": "Effect modules for working with AI apis",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1010,12 +1010,13 @@ const resolveToolCalls = <Tools extends Record<string, Tool.Any>>(
1010
1010
 
1011
1011
  return Effect.forEach(toolCalls, (toolCall) => {
1012
1012
  return toolkit.handle(toolCall.name, toolCall.params as any).pipe(
1013
- Effect.map(({ encodedResult, result }) =>
1013
+ Effect.map(({ encodedResult, isFailure, result }) =>
1014
1014
  Response.makePart("tool-result", {
1015
1015
  id: toolCall.id,
1016
1016
  name: toolCall.name,
1017
1017
  result,
1018
1018
  encodedResult,
1019
+ isFailure,
1019
1020
  providerExecuted: false,
1020
1021
  ...(toolCall.providerName !== undefined
1021
1022
  ? { providerName: toolCall.providerName }
package/src/Prompt.ts CHANGED
@@ -606,13 +606,11 @@ export const toolCallPart = (params: PartConstructorParams<ToolCallPart>): ToolC
606
606
  * const toolResultPart: Prompt.ToolResultPart = Prompt.makePart("tool-result", {
607
607
  * id: "call_123",
608
608
  * name: "get_weather",
609
+ * isFailure: false,
609
610
  * result: {
610
- * _tag: "Right",
611
- * right: {
612
- * temperature: 22,
613
- * condition: "sunny",
614
- * humidity: 65
615
- * }
611
+ * temperature: 22,
612
+ * condition: "sunny",
613
+ * humidity: 65
616
614
  * }
617
615
  * })
618
616
  * ```
@@ -629,10 +627,14 @@ export interface ToolResultPart extends BasePart<"tool-result", ToolResultPartOp
629
627
  * Name of the tool that was executed.
630
628
  */
631
629
  readonly name: string
630
+ /**
631
+ * Whether or not the result of executing the tool call handler was an error.
632
+ */
633
+ readonly isFailure: boolean
632
634
  /**
633
635
  * The result returned by the tool execution.
634
636
  */
635
- readonly result: Schema.EitherEncoded<unknown, unknown>
637
+ readonly result: unknown
636
638
  }
637
639
 
638
640
  /**
@@ -650,10 +652,14 @@ export interface ToolResultPartEncoded extends BasePartEncoded<"tool-result", To
650
652
  * Name of the tool that was executed.
651
653
  */
652
654
  readonly name: string
655
+ /**
656
+ * Whether or not the result of executing the tool call handler was an error.
657
+ */
658
+ readonly isFailure: boolean
653
659
  /**
654
660
  * The result returned by the tool execution.
655
661
  */
656
- readonly result: Schema.EitherEncoded<unknown, unknown>
662
+ readonly result: unknown
657
663
  }
658
664
 
659
665
  /**
@@ -675,10 +681,8 @@ export const ToolResultPart: Schema.Schema<ToolResultPart, ToolResultPartEncoded
675
681
  type: Schema.Literal("tool-result"),
676
682
  id: Schema.String,
677
683
  name: Schema.String,
678
- result: Schema.encodedSchema(Schema.Either({
679
- left: Schema.Unknown,
680
- right: Schema.Unknown
681
- })),
684
+ isFailure: Schema.Boolean,
685
+ result: Schema.Unknown,
682
686
  options: Schema.optionalWith(ProviderOptions, { default: constEmptyObject })
683
687
  }).pipe(
684
688
  Schema.attachPropertySignature(PartTypeId, PartTypeId),
@@ -1028,9 +1032,10 @@ export const userMessage = (params: MessageConstructorParams<UserMessage>): User
1028
1032
  * Prompt.makePart("tool-result", {
1029
1033
  * id: "call_123",
1030
1034
  * name: "get_weather",
1035
+ * isFailure: false,
1031
1036
  * result: {
1032
- * _tag: "Right",
1033
- * right: { temperature: 72, condition: "sunny" }
1037
+ * temperature: 72,
1038
+ * condition: "sunny"
1034
1039
  * }
1035
1040
  * }),
1036
1041
  * Prompt.makePart("text", {
@@ -1138,15 +1143,13 @@ export const assistantMessage = (params: MessageConstructorParams<AssistantMessa
1138
1143
  * Prompt.makePart("tool-result", {
1139
1144
  * id: "call_123",
1140
1145
  * name: "search_web",
1146
+ * isFailure: false,
1141
1147
  * result: {
1142
- * _tag: "Right",
1143
- * right: {
1144
- * query: "TypeScript best practices",
1145
- * results: [
1146
- * { title: "TypeScript Handbook", url: "https://..." },
1147
- * { title: "Effective TypeScript", url: "https://..." }
1148
- * ]
1149
- * }
1148
+ * query: "TypeScript best practices",
1149
+ * results: [
1150
+ * { title: "TypeScript Handbook", url: "https://..." },
1151
+ * { title: "Effective TypeScript", url: "https://..." }
1152
+ * ]
1150
1153
  * }
1151
1154
  * })
1152
1155
  * ]
@@ -1564,8 +1567,9 @@ const isValidPart = (part: Response.AnyPart): part is ValidResponsePart => {
1564
1567
  * Response.makePart("tool-result", {
1565
1568
  * id: "call_1",
1566
1569
  * name: "get_time",
1567
- * result: Either.right("10:30 AM"),
1568
- * encodedResult: { _tag: "Right", right: "10:30 AM" },
1570
+ * isFailure: false,
1571
+ * result: "10:30 AM",
1572
+ * encodedResult: "10:30 AM",
1569
1573
  * providerExecuted: false
1570
1574
  * })
1571
1575
  * ]
@@ -1650,6 +1654,7 @@ export const fromResponseParts = (parts: ReadonlyArray<Response.AnyPart>): Promp
1650
1654
  toolParts.push(makePart("tool-result", {
1651
1655
  id: part.id,
1652
1656
  name: part.providerName ?? part.name,
1657
+ isFailure: part.isFailure,
1653
1658
  result: part.encodedResult
1654
1659
  }))
1655
1660
  break
package/src/Response.ts CHANGED
@@ -28,7 +28,6 @@
28
28
  */
29
29
  import type * as DateTime from "effect/DateTime"
30
30
  import * as Effect from "effect/Effect"
31
- import type * as Either from "effect/Either"
32
31
  import { constFalse } from "effect/Function"
33
32
  import type * as Option from "effect/Option"
34
33
  import * as ParseResult from "effect/ParseResult"
@@ -1506,6 +1505,75 @@ export const toolCallPart = <const Name extends string, Params>(
1506
1505
  // Tool Call Result Part
1507
1506
  // =============================================================================
1508
1507
 
1508
+ /**
1509
+ * The base fields of a tool result part.
1510
+ *
1511
+ * @since 1.0.0
1512
+ * @category Models
1513
+ */
1514
+ export interface BaseToolResult<Name extends string> extends BasePart<"tool-result", ToolResultPartMetadata> {
1515
+ /**
1516
+ * Unique identifier matching the original tool call.
1517
+ */
1518
+ readonly id: string
1519
+ /**
1520
+ * Name of the tool being called, which corresponds to the name of the tool
1521
+ * in the `Toolkit` included with the request.
1522
+ */
1523
+ readonly name: Name
1524
+ /**
1525
+ * The encoded result for serialization purposes.
1526
+ */
1527
+ readonly encodedResult: unknown
1528
+ /**
1529
+ * Optional provider-specific name for the tool, which can be useful when the
1530
+ * name of the tool in the `Toolkit` and the name of the tool used by the
1531
+ * model are different.
1532
+ *
1533
+ * This is usually happens only with provider-defined tools which require a
1534
+ * user-space handler.
1535
+ */
1536
+ readonly providerName?: string | undefined
1537
+ /**
1538
+ * Whether the tool was executed by the provider (true) or framework (false).
1539
+ */
1540
+ readonly providerExecuted: boolean
1541
+ }
1542
+
1543
+ /**
1544
+ * Represents a successful tool call result.
1545
+ *
1546
+ * @since 1.0.0
1547
+ * @category Models
1548
+ */
1549
+ export interface ToolResultSuccess<Name extends string, Success> extends BaseToolResult<Name> {
1550
+ /**
1551
+ * The decoded success returned by the tool execution.
1552
+ */
1553
+ readonly result: Success
1554
+ /**
1555
+ * Whether or not the result of executing the tool call handler was an error.
1556
+ */
1557
+ readonly isFailure: false
1558
+ }
1559
+
1560
+ /**
1561
+ * Represents a failed tool call result.
1562
+ *
1563
+ * @since 1.0.0
1564
+ * @category Models
1565
+ */
1566
+ export interface ToolResultFailure<Name extends string, Failure> extends BaseToolResult<Name> {
1567
+ /**
1568
+ * The decoded failure returned by the tool execution.
1569
+ */
1570
+ readonly result: Failure
1571
+ /**
1572
+ * Whether or not the result of executing the tool call handler was an error.
1573
+ */
1574
+ readonly isFailure: true
1575
+ }
1576
+
1509
1577
  /**
1510
1578
  * Response part representing the result of a tool call.
1511
1579
  *
@@ -1527,18 +1595,16 @@ export const toolCallPart = <const Name extends string, Params>(
1527
1595
  * > = Response.toolResultPart({
1528
1596
  * id: "call_123",
1529
1597
  * name: "get_weather",
1530
- * result: Either.right({
1598
+ * isFailure: false,
1599
+ * result: {
1531
1600
  * temperature: 22,
1532
1601
  * condition: "sunny",
1533
1602
  * humidity: 65
1534
- * }),
1603
+ * },
1535
1604
  * encodedResult: {
1536
- * _tag: "Right",
1537
- * right: {
1538
- * temperature: 22,
1539
- * condition: "sunny",
1540
- * humidity: 65
1541
- * }
1605
+ * temperature: 22,
1606
+ * condition: "sunny",
1607
+ * humidity: 65
1542
1608
  * },
1543
1609
  * providerExecuted: false
1544
1610
  * })
@@ -1547,40 +1613,9 @@ export const toolCallPart = <const Name extends string, Params>(
1547
1613
  * @since 1.0.0
1548
1614
  * @category Models
1549
1615
  */
1550
- export interface ToolResultPart<Name extends string, Success, Failure>
1551
- extends BasePart<"tool-result", ToolResultPartMetadata>
1552
- {
1553
- /**
1554
- * Unique identifier matching the original tool call.
1555
- */
1556
- readonly id: string
1557
- /**
1558
- * Name of the tool being called, which corresponds to the name of the tool
1559
- * in the `Toolkit` included with the request.
1560
- */
1561
- readonly name: Name
1562
- /**
1563
- * The decoded result returned by the tool execution.
1564
- */
1565
- readonly result: Either.Either<Success, Failure>
1566
- /**
1567
- * The encoded result for serialization purposes.
1568
- */
1569
- readonly encodedResult: Schema.EitherEncoded<unknown, unknown>
1570
- /**
1571
- * Optional provider-specific name for the tool, which can be useful when the
1572
- * name of the tool in the `Toolkit` and the name of the tool used by the
1573
- * model are different.
1574
- *
1575
- * This is usually happens only with provider-defined tools which require a
1576
- * user-space handler.
1577
- */
1578
- readonly providerName?: string | undefined
1579
- /**
1580
- * Whether the tool was executed by the provider (true) or framework (false).
1581
- */
1582
- readonly providerExecuted: boolean
1583
- }
1616
+ export type ToolResultPart<Name extends string, Success, Failure> =
1617
+ | ToolResultSuccess<Name, Success>
1618
+ | ToolResultFailure<Name, Failure>
1584
1619
 
1585
1620
  /**
1586
1621
  * Encoded representation of tool result parts for serialization.
@@ -1601,7 +1636,11 @@ export interface ToolResultPartEncoded extends BasePartEncoded<"tool-result", To
1601
1636
  /**
1602
1637
  * The result returned by the tool execution.
1603
1638
  */
1604
- readonly result: Schema.EitherEncoded<unknown, unknown>
1639
+ readonly result: unknown
1640
+ /**
1641
+ * Whether or not the result of executing the tool call handler was an error.
1642
+ */
1643
+ readonly isFailure: boolean
1605
1644
  /**
1606
1645
  * Optional provider-specific name for the tool, which can be useful when the
1607
1646
  * name of the tool in the `Toolkit` and the name of the tool used by the
@@ -1644,15 +1683,13 @@ export const ToolResultPart = <
1644
1683
  ToolResultPart<Name, Schema.Schema.Type<Success>, Schema.Schema.Type<Failure>>,
1645
1684
  ToolResultPartEncoded
1646
1685
  > => {
1647
- const ResultSchema = Schema.Either({
1648
- left: failure,
1649
- right: success
1650
- })
1651
1686
  const Base = Schema.Struct({
1652
1687
  id: Schema.String,
1653
1688
  type: Schema.Literal("tool-result"),
1654
- providerName: Schema.optional(Schema.String)
1689
+ providerName: Schema.optional(Schema.String),
1690
+ isFailure: Schema.Boolean
1655
1691
  })
1692
+ const ResultSchema = Schema.Union(success, failure)
1656
1693
  const Encoded = Schema.Struct({
1657
1694
  ...Base.fields,
1658
1695
  name: Schema.String,
@@ -1692,11 +1729,9 @@ export const ToolResultPart = <
1692
1729
  encode: Effect.fnUntraced(function*(decoded) {
1693
1730
  const encoded = yield* encodeResult(decoded.result)
1694
1731
  return {
1695
- id: decoded.id,
1696
- type: decoded.type,
1697
- name: decoded.name,
1732
+ ...decoded,
1698
1733
  result: encoded,
1699
- ...(decoded.metadata ? { metadata: decoded.metadata } : {}),
1734
+ ...(decoded.metadata ?? {}),
1700
1735
  ...(decoded.providerName ? { providerName: decoded.providerName } : {}),
1701
1736
  ...(decoded.providerExecuted ? { providerExecuted: true } : {})
1702
1737
  }
@@ -1711,9 +1746,21 @@ export const ToolResultPart = <
1711
1746
  * @since 1.0.0
1712
1747
  * @category Constructors
1713
1748
  */
1714
- export const toolResultPart = <const Name extends string, Success, Failure>(
1715
- params: ConstructorParams<ToolResultPart<Name, Success, Failure>>
1716
- ): ToolResultPart<Name, Success, Failure> => makePart("tool-result", params)
1749
+ export const toolResultPart = <
1750
+ const Params extends ConstructorParams<ToolResultPart<string, any, any>>
1751
+ >(
1752
+ params: Params
1753
+ ): Params extends {
1754
+ readonly name: infer Name extends string
1755
+ readonly isFailure: false
1756
+ readonly result: infer Success
1757
+ } ? ToolResultPart<Name, Success, never>
1758
+ : Params extends {
1759
+ readonly name: infer Name extends string
1760
+ readonly isFailure: true
1761
+ readonly result: infer Failure
1762
+ } ? ToolResultPart<Name, never, Failure>
1763
+ : never => makePart("tool-result", params) as any
1717
1764
 
1718
1765
  // =============================================================================
1719
1766
  // File Part
package/src/Tool.ts CHANGED
@@ -27,7 +27,7 @@
27
27
  * @since 1.0.0
28
28
  */
29
29
  import * as Context from "effect/Context"
30
- import * as Effect from "effect/Effect"
30
+ import type * as Effect from "effect/Effect"
31
31
  import { constFalse, constTrue, identity } from "effect/Function"
32
32
  import * as JsonSchema from "effect/JSONSchema"
33
33
  import * as Option from "effect/Option"
@@ -37,7 +37,7 @@ import * as Predicate from "effect/Predicate"
37
37
  import * as Schema from "effect/Schema"
38
38
  import * as AST from "effect/SchemaAST"
39
39
  import type { Covariant } from "effect/Types"
40
- import * as AiError from "./AiError.js"
40
+ import type * as AiError from "./AiError.js"
41
41
 
42
42
  // =============================================================================
43
43
  // Type Ids
@@ -164,12 +164,6 @@ export interface Tool<
164
164
  */
165
165
  readonly failureSchema: Config["failure"]
166
166
 
167
- /**
168
- * A `Schema` representing the result of a tool call, whether it succeeds or
169
- * fails.
170
- */
171
- readonly resultSchema: Schema.Either<Config["success"], Config["failure"]>
172
-
173
167
  /**
174
168
  * A `Context` object containing tool annotations which can store metadata
175
169
  * about the tool.
@@ -340,13 +334,6 @@ export interface ProviderDefined<
340
334
  * this tool into a `Layer`.
341
335
  */
342
336
  readonly requiresHandler: RequiresHandler
343
-
344
- /**
345
- * Decodes the result received after the provider-defined tool is called.
346
- */
347
- decodeResult(
348
- args: unknown
349
- ): Effect.Effect<Config["success"]["Type"], AiError.AiError>
350
337
  }
351
338
 
352
339
  /**
@@ -509,7 +496,6 @@ export interface Any extends Pipeable {
509
496
  readonly parametersSchema: AnyStructSchema
510
497
  readonly successSchema: Schema.Schema.Any
511
498
  readonly failureSchema: Schema.Schema.All
512
- readonly resultSchema: Schema.Either<any, any>
513
499
  readonly failureMode: FailureMode
514
500
  readonly annotations: Context.Context<never>
515
501
  }
@@ -705,8 +691,8 @@ export type Result<T> = T extends Tool<
705
691
  infer _Name,
706
692
  infer _Config,
707
693
  infer _Requirements
708
- > ? Schema.Either<_Config["success"], _Config["failure"]>["Type"] :
709
- never
694
+ > ? Success<T> | Failure<T>
695
+ : never
710
696
 
711
697
  /**
712
698
  * A utility type to extract the encoded type of the tool call result whether
@@ -719,8 +705,8 @@ export type ResultEncoded<T> = T extends Tool<
719
705
  infer _Name,
720
706
  infer _Config,
721
707
  infer _Requirements
722
- > ? Schema.Either<_Config["success"], _Config["failure"]>["Encoded"] :
723
- never
708
+ > ? SuccessEncoded<T> | FailureEncoded<T>
709
+ : never
724
710
 
725
711
  /**
726
712
  * A utility type to extract the requirements of an `Tool`.
@@ -759,6 +745,10 @@ export interface Handler<Name extends string> {
759
745
  * @category Models
760
746
  */
761
747
  export interface HandlerResult<Tool extends Any> {
748
+ /**
749
+ * Whether the result of executing the tool call handler was an error or not.
750
+ */
751
+ readonly isFailure: boolean
762
752
  /**
763
753
  * The result of executing the handler for a particular tool.
764
754
  */
@@ -768,7 +758,7 @@ export interface HandlerResult<Tool extends Any> {
768
758
  * tool as a JSON-serializable value. The encoded result can be incorporated
769
759
  * into subsequent requests to the large language model.
770
760
  */
771
- readonly encodedResult: Schema.EitherEncoded<unknown, unknown>
761
+ readonly encodedResult: unknown
772
762
  }
773
763
 
774
764
  /**
@@ -782,7 +772,7 @@ export type HandlerError<T> = T extends Tool<
782
772
  infer _Name,
783
773
  infer _Config,
784
774
  infer _Requirements
785
- > ? _Config["failureMode"] extends "error" ? Schema.Schema.Type<_Config["failure"]>
775
+ > ? _Config["failureMode"] extends "error" ? _Config["failure"]["Type"]
786
776
  : never
787
777
  : never
788
778
 
@@ -863,21 +853,7 @@ const Proto = {
863
853
 
864
854
  const ProviderDefinedProto = {
865
855
  ...Proto,
866
- [ProviderDefinedTypeId]: ProviderDefinedTypeId,
867
- decodeResult(this: AnyProviderDefined, result: unknown) {
868
- return Schema.decodeUnknown(this.successSchema)(result).pipe(
869
- Effect.orElse(() => Schema.decodeUnknown(this.failureSchema as any)(result)),
870
- Effect.mapError(
871
- (cause) =>
872
- new AiError.MalformedOutput({
873
- module: "Tool",
874
- method: "ProviderDefined.decodeResult",
875
- description: `Failed to decode the result of provider-defined tool '${this.name}'`,
876
- cause
877
- })
878
- )
879
- )
880
- }
856
+ [ProviderDefinedTypeId]: ProviderDefinedTypeId
881
857
  }
882
858
 
883
859
  const userDefinedProto = <
@@ -892,7 +868,6 @@ const userDefinedProto = <
892
868
  readonly parametersSchema: Parameters
893
869
  readonly successSchema: Success
894
870
  readonly failureSchema: Failure
895
- readonly resultSchema: Schema.Either<Success, Failure>
896
871
  readonly annotations: Context.Context<never>
897
872
  readonly failureMode: Mode
898
873
  }): Tool<
@@ -927,7 +902,6 @@ const providerDefinedProto = <
927
902
  readonly parametersSchema: Parameters
928
903
  readonly successSchema: Success
929
904
  readonly failureSchema: Failure
930
- readonly resultSchema: Schema.Either<Success, Failure>
931
905
  readonly failureMode: FailureMode
932
906
  }): ProviderDefined<
933
907
  Name,
@@ -1022,10 +996,6 @@ export const make = <
1022
996
  > => {
1023
997
  const successSchema = options?.success ?? Schema.Void
1024
998
  const failureSchema = options?.failure ?? Schema.Never
1025
- const resultSchema = Schema.Either({
1026
- left: failureSchema,
1027
- right: successSchema
1028
- })
1029
999
  return userDefinedProto({
1030
1000
  name,
1031
1001
  description: options?.description,
@@ -1034,7 +1004,6 @@ export const make = <
1034
1004
  : constEmptyStruct,
1035
1005
  successSchema,
1036
1006
  failureSchema,
1037
- resultSchema,
1038
1007
  failureMode: options?.failureMode ?? "error",
1039
1008
  annotations: Context.empty()
1040
1009
  }) as any
@@ -1146,10 +1115,6 @@ export const providerDefined = <
1146
1115
  const failureMode = "failureMode" in args ? args.failureMode : undefined
1147
1116
  const successSchema = options?.success ?? Schema.Void
1148
1117
  const failureSchema = options?.failure ?? Schema.Never
1149
- const resultSchema = Schema.Either({
1150
- right: successSchema,
1151
- left: failureSchema
1152
- })
1153
1118
  return providerDefinedProto({
1154
1119
  id: options.id,
1155
1120
  name: options.toolkitName,
@@ -1162,7 +1127,6 @@ export const providerDefined = <
1162
1127
  : constEmptyStruct,
1163
1128
  successSchema,
1164
1129
  failureSchema,
1165
- resultSchema,
1166
1130
  failureMode: failureMode ?? "error"
1167
1131
  }) as any
1168
1132
  }
@@ -1213,10 +1177,6 @@ export const fromTaggedRequest = <S extends AnyTaggedRequestSchema>(
1213
1177
  parametersSchema: schema,
1214
1178
  successSchema: schema.success,
1215
1179
  failureSchema: schema.failure,
1216
- resultSchema: Schema.Either({
1217
- left: schema.failure,
1218
- right: schema.success
1219
- }),
1220
1180
  failureMode: "error",
1221
1181
  annotations: Context.empty()
1222
1182
  }) as any
package/src/Toolkit.ts CHANGED
@@ -40,7 +40,6 @@
40
40
  import * as Context from "effect/Context"
41
41
  import * as Effect from "effect/Effect"
42
42
  import { CommitPrototype } from "effect/Effectable"
43
- import * as Either from "effect/Either"
44
43
  import { identity } from "effect/Function"
45
44
  import type { Inspectable } from "effect/Inspectable"
46
45
  import { BaseProto as InspectableProto } from "effect/Inspectable"
@@ -267,16 +266,17 @@ const Proto = {
267
266
  readonly context: Context.Context<never>
268
267
  readonly handler: (params: any) => Effect.Effect<any, any>
269
268
  readonly decodeParameters: (u: unknown) => Effect.Effect<Tool.Parameters<any>, ParseError>
270
- readonly validateResult: (u: unknown) => Effect.Effect<Either.Either<unknown, unknown>, ParseError>
271
- readonly encodeResult: (u: unknown) => Effect.Effect<Schema.EitherEncoded<unknown, unknown>, ParseError>
269
+ readonly validateResult: (u: unknown) => Effect.Effect<unknown, ParseError>
270
+ readonly encodeResult: (u: unknown) => Effect.Effect<unknown, ParseError>
272
271
  }>()
273
272
  const getSchemas = (tool: Tool.Any) => {
274
273
  let schemas = schemasCache.get(tool)
275
274
  if (Predicate.isUndefined(schemas)) {
276
275
  const handler = context.unsafeMap.get(tool.id)! as Tool.Handler<any>
277
276
  const decodeParameters = Schema.decodeUnknown(tool.parametersSchema) as any
278
- const validateResult = Schema.validate(tool.resultSchema) as any
279
- const encodeResult = Schema.encodeUnknown(tool.resultSchema) as any
277
+ const resultSchema = Schema.Union(tool.successSchema, tool.failureSchema)
278
+ const validateResult = Schema.validate(resultSchema) as any
279
+ const encodeResult = Schema.encodeUnknown(resultSchema) as any
280
280
  schemas = {
281
281
  context: handler.context,
282
282
  handler: handler.handler,
@@ -313,15 +313,16 @@ const Proto = {
313
313
  cause
314
314
  })
315
315
  )
316
- const result = yield* schemas.handler(decodedParams).pipe(
317
- Effect.matchEffect({
318
- onFailure: (error) =>
319
- tool.failureMode === "error"
320
- ? Effect.fail(error)
321
- : Effect.succeed(Either.left(error)),
322
- onSuccess: (value) => Effect.succeed(Either.right(value))
323
- }),
324
- Effect.flatMap((either) => schemas.validateResult(either)),
316
+ const { isFailure, result } = yield* schemas.handler(decodedParams).pipe(
317
+ Effect.map((result) => ({ result, isFailure: false })),
318
+ Effect.catchAll((error) =>
319
+ // If the tool handler failed, check the tool's failure mode to
320
+ // determine how the result should be returned to the end user
321
+ tool.failureMode === "error"
322
+ ? Effect.fail(error)
323
+ : Effect.succeed({ result: error, isFailure: true })
324
+ ),
325
+ Effect.tap(({ result }) => schemas.validateResult(result)),
325
326
  Effect.mapInputContext((input) => Context.merge(schemas.context, input)),
326
327
  Effect.mapError((cause) =>
327
328
  ParseResult.isParseError(cause)
@@ -345,6 +346,7 @@ const Proto = {
345
346
  })
346
347
  )
347
348
  return {
349
+ isFailure,
348
350
  result,
349
351
  encodedResult
350
352
  } satisfies Tool.HandlerResult<any>
@@ -352,7 +354,7 @@ const Proto = {
352
354
  )
353
355
  return {
354
356
  tools,
355
- handle: handle as any
357
+ handle
356
358
  } satisfies WithHandler<Record<string, any>>
357
359
  })
358
360
  },