@effect/ai 0.29.1 → 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/src/Toolkit.ts CHANGED
@@ -45,6 +45,7 @@ import type { Inspectable } from "effect/Inspectable"
45
45
  import { BaseProto as InspectableProto } from "effect/Inspectable"
46
46
  import * as Layer from "effect/Layer"
47
47
  import type { ParseError } from "effect/ParseResult"
48
+ import * as ParseResult from "effect/ParseResult"
48
49
  import { type Pipeable, pipeArguments } from "effect/Pipeable"
49
50
  import * as Predicate from "effect/Predicate"
50
51
  import * as Schema from "effect/Schema"
@@ -226,10 +227,7 @@ export interface WithHandler<in out Tools extends Record<string, Tool.Any>> {
226
227
  */
227
228
  params: Tool.Parameters<Tools[Name]>
228
229
  ) => Effect.Effect<
229
- {
230
- readonly result: Tool.Success<Tools[Name]>
231
- readonly encodedResult: unknown
232
- },
230
+ Tool.HandlerResult<Tools[Name]>,
233
231
  Tool.Failure<Tools[Name]>,
234
232
  Tool.Requirements<Tools[Name]>
235
233
  >
@@ -267,26 +265,24 @@ const Proto = {
267
265
  const schemasCache = new WeakMap<any, {
268
266
  readonly context: Context.Context<never>
269
267
  readonly handler: (params: any) => Effect.Effect<any, any>
270
- readonly encodeSuccess: (u: unknown) => Effect.Effect<unknown, ParseError>
271
- readonly encodeFailure: (u: unknown) => Effect.Effect<unknown, ParseError>
272
- readonly decodeFailure: (u: unknown) => Effect.Effect<Tool.Failure<any>, ParseError>
273
268
  readonly decodeParameters: (u: unknown) => Effect.Effect<Tool.Parameters<any>, ParseError>
269
+ readonly validateResult: (u: unknown) => Effect.Effect<unknown, ParseError>
270
+ readonly encodeResult: (u: unknown) => Effect.Effect<unknown, ParseError>
274
271
  }>()
275
272
  const getSchemas = (tool: Tool.Any) => {
276
273
  let schemas = schemasCache.get(tool)
277
274
  if (Predicate.isUndefined(schemas)) {
278
275
  const handler = context.unsafeMap.get(tool.id)! as Tool.Handler<any>
279
- const encodeSuccess = Schema.encodeUnknown(tool.successSchema) as any
280
- const encodeFailure = Schema.encodeUnknown(tool.failureSchema as any) as any
281
- const decodeFailure = Schema.decodeUnknown(tool.failureSchema as any) as any
282
276
  const decodeParameters = Schema.decodeUnknown(tool.parametersSchema) 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
283
280
  schemas = {
284
281
  context: handler.context,
285
282
  handler: handler.handler,
286
- encodeSuccess,
287
- encodeFailure,
288
- decodeFailure,
289
- decodeParameters
283
+ decodeParameters,
284
+ validateResult,
285
+ encodeResult
290
286
  }
291
287
  schemasCache.set(tool, schemas)
292
288
  }
@@ -317,24 +313,30 @@ const Proto = {
317
313
  cause
318
314
  })
319
315
  )
320
- const result = yield* schemas.handler(decodedParams).pipe(
321
- Effect.mapInputContext((input) => Context.merge(schemas.context, input)),
316
+ const { isFailure, result } = yield* schemas.handler(decodedParams).pipe(
317
+ Effect.map((result) => ({ result, isFailure: false })),
322
318
  Effect.catchAll((error) =>
323
- schemas.decodeFailure(error).pipe(
324
- Effect.mapError((cause) =>
325
- new AiError.MalformedInput({
326
- module: "Toolkit",
327
- method: `${name}.handle`,
328
- description: `Failed to decode tool call failure for tool '${name}'`,
329
- cause
330
- })
331
- ),
332
- Effect.flatMap(Effect.fail)
333
- )
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)),
326
+ Effect.mapInputContext((input) => Context.merge(schemas.context, input)),
327
+ Effect.mapError((cause) =>
328
+ ParseResult.isParseError(cause)
329
+ ? new AiError.MalformedInput({
330
+ module: "Toolkit",
331
+ method: `${name}.handle`,
332
+ description: `Failed to validate tool call result for tool '${name}'`,
333
+ cause
334
+ })
335
+ : cause
334
336
  )
335
337
  )
336
338
  const encodedResult = yield* Effect.mapError(
337
- schemas.encodeSuccess(result),
339
+ schemas.encodeResult(result),
338
340
  (cause) =>
339
341
  new AiError.MalformedInput({
340
342
  module: "Toolkit",
@@ -344,6 +346,7 @@ const Proto = {
344
346
  })
345
347
  )
346
348
  return {
349
+ isFailure,
347
350
  result,
348
351
  encodedResult
349
352
  } satisfies Tool.HandlerResult<any>