1/**
2 * Definitions and helpers for tools that AI models can request during a
3 * workflow.
4 *
5 * A tool names an operation, describes the parameters it accepts, declares
6 * successful and failed results, and can require approval before execution.
7 * This module supports tools defined by the application, tools built into a
8 * provider, and dynamic tools whose schema is known only at runtime. It also
9 * includes the shared types and conversion helpers needed by language-model
10 * requests, tool handlers, and provider integrations.
11 *
12 * @stability unstable
13 * @since 4.0.0
14 */
15import * as Context from "../Context.ts"
16import type * as Effect from "../Effect.ts"
17import { constFalse, constTrue, identity } from "../Function.ts"
18import * as StackTraceLimit from "../internal/stackTraceLimit.ts"
19import type * as JsonSchema from "../JsonSchema.ts"
20import { pipeArguments } from "../Pipeable.ts"
21import * as Predicate from "../Predicate.ts"
22import * as Schema from "../Schema.ts"
23import * as SchemaAST from "../SchemaAST.ts"
24import type * as Struct from "../Struct.ts"
25import type * as Types from "../Types.ts"
26import * as AiError from "./AiError.ts"
27import type { CodecTransformer } from "./LanguageModel.ts"
28import type * as Prompt from "./Prompt.ts"
29
30// =============================================================================
31// Type Ids
32// =============================================================================
33
34/**
35 * Runtime type identifier carried by Effect AI tool values.
36 *
37 * **Details**
38 *
39 * The tool type guards use this marker, together with more specific markers,
40 * to distinguish user-defined, provider-defined, and dynamic tools.
41 *
42 * @stability unstable
43 * @category type IDs
44 * @since 4.0.0
45 */
46export const TypeId: TypeId = "~effect/ai/Tool"
47
48/**
49 * Type-level representation of the Effect AI tool runtime type identifier.
50 *
51 * @stability unstable
52 * @category type IDs
53 * @since 4.0.0
54 */
55export type TypeId = "~effect/ai/Tool"
56
57/**
58 * Runtime type identifier carried by provider-defined tools.
59 *
60 * **Details**
61 *
62 * `isProviderDefined` uses this marker to distinguish tools that are built into
63 * an AI provider from user-defined and dynamic tools.
64 *
65 * @stability unstable
66 * @category type IDs
67 * @since 4.0.0
68 */
69export const ProviderDefinedTypeId: ProviderDefinedTypeId = "~effect/ai/Tool/ProviderDefined"
70
71/**
72 * Type-level representation of the provider-defined tool runtime type
73 * identifier.
74 *
75 * @stability unstable
76 * @category type IDs
77 * @since 4.0.0
78 */
79export type ProviderDefinedTypeId = "~effect/ai/Tool/ProviderDefined"
80
81/**
82 * Runtime type identifier carried by dynamic tools.
83 *
84 * **Details**
85 *
86 * `isDynamic` uses this marker to distinguish tools whose schema may be
87 * provided at runtime from user-defined and provider-defined tools.
88 *
89 * @stability unstable
90 * @category type IDs
91 * @since 4.0.0
92 */
93export const DynamicTypeId: DynamicTypeId = "~effect/ai/Tool/Dynamic"
94
95/**
96 * Type-level representation of the dynamic tool runtime type identifier.
97 *
98 * @stability unstable
99 * @category type IDs
100 * @since 4.0.0
101 */
102export type DynamicTypeId = "~effect/ai/Tool/Dynamic"
103
104// =============================================================================
105// Models
106// =============================================================================
107
108/**
109 * The strategy used for handling errors returned from tool call handler
110 * execution.
111 *
112 * **Details**
113 *
114 * If set to `"error"` (the default), errors that occur during tool call handler
115 * execution will be returned in the error channel of the calling effect.
116 *
117 * If set to `"return"`, errors that occur during tool call handler execution
118 * will be captured and returned as part of the tool call result.
119 *
120 * @stability unstable
121 * @category models
122 * @since 4.0.0
123 */
124export type FailureMode = "error" | "return"
125
126/**
127 * Context provided to the `needsApproval` function when dynamically
128 * determining if a tool requires user approval.
129 *
130 * @stability unstable
131 * @category models
132 * @since 4.0.0
133 */
134export interface NeedsApprovalContext {
135 /**
136 * The unique identifier of the tool call.
137 */
138 readonly toolCallId: string
139 /**
140 * The conversation messages leading up to this tool call.
141 */
142 readonly messages: ReadonlyArray<Prompt.Message>
143}
144
145/**
146 * Function type for dynamically determining if a tool requires approval.
147 *
148 * @stability unstable
149 * @category models
150 * @since 4.0.0
151 */
152export type NeedsApprovalFunction<Params extends Schema.Constraint> = (
153 params: Params["Type"],
154 context: NeedsApprovalContext
155) => boolean | Effect.Effect<boolean>
156
157/**
158 * Specifies whether user approval is required before executing a tool.
159 *
160 * **Details**
161 *
162 * Can be:
163 * - `boolean`: Static approval requirement
164 * - `NeedsApprovalFunction`: Dynamic approval based on parameters/context
165 *
166 * @stability unstable
167 * @category models
168 * @since 4.0.0
169 */
170export type NeedsApproval<Params extends Schema.Constraint> =
171 | boolean
172 | NeedsApprovalFunction<Params>
173
174/**
175 * A user-defined tool that language models can call to perform actions.
176 *
177 * **Details**
178 *
179 * Tools represent actionable capabilities that large language models can invoke
180 * to extend their functionality beyond text generation. Each tool has a defined
181 * schema for parameters, results, and failures.
182 *
183 * **Example** (Defining a weather lookup tool)
184 *
185 * ```ts import.meta.vitest
186 * import { Schema } from "effect"
187 * import { Tool } from "effect/ai"
188 *
189 * // Create a weather lookup tool
190 * const GetWeather = Tool.make("GetWeather", {
191 * description: "Get current weather for a location",
192 * parameters: Schema.Struct({
193 * location: Schema.String,
194 * units: Schema.Literals(["celsius", "fahrenheit"])
195 * }),
196 * success: Schema.Struct({
197 * temperature: Schema.Number,
198 * condition: Schema.String,
199 * humidity: Schema.Number
200 * })
201 * })
202 * const result = [GetWeather.name, GetWeather.failureMode] // => ["GetWeather", "error"]
203 * ```
204 *
205 * @stability unstable
206 * @category models
207 * @since 4.0.0
208 */
209export interface Tool<
210 out Name extends string,
211 out Config extends {
212 readonly parameters: Schema.Constraint
213 readonly success: Schema.Constraint
214 readonly failure: Schema.Constraint
215 readonly failureMode: FailureMode
216 },
217 out Requirements = never
218> {
219 readonly [TypeId]: {
220 readonly _Requirements: Types.Covariant<Requirements>
221 }
222
223 /**
224 * The tool identifier which is used to uniquely identify the tool.
225 */
226 readonly id: string
227
228 /**
229 * The name of the tool.
230 */
231 readonly name: Name
232
233 /**
234 * The optional description of the tool.
235 */
236 readonly description?: string | undefined
237
238 /**
239 * The strategy used for handling errors returned from tool call handler
240 * execution.
241 *
242 * **Details**
243 *
244 * If set to `"error"` (the default), errors that occur during tool call
245 * handler execution will be returned in the error channel of the calling
246 * effect.
247 *
248 * If set to `"return"`, errors that occur during tool call handler execution
249 * will be captured and returned as part of the tool call result.
250 */
251 readonly failureMode: FailureMode
252
253 /**
254 * A `Schema` representing the parameters that a tool must be called with.
255 */
256 readonly parametersSchema: Config["parameters"]
257
258 /**
259 * A `Schema` representing the value that a tool must return when called if
260 * the tool call is successful.
261 */
262 readonly successSchema: Config["success"]
263
264 /**
265 * A `Schema` representing the value that a tool must return when called if
266 * it fails.
267 */
268 readonly failureSchema: Config["failure"]
269
270 /**
271 * A `Context` containing tool annotations which can store metadata about
272 * the tool.
273 */
274 readonly annotations: Context.Context<never>
275
276 /**
277 * Specifies whether user approval is required before executing this tool.
278 *
279 * **Details**
280 *
281 * - If `undefined` or `false`, the tool executes immediately.
282 * - If `true`, the tool always requires approval.
283 * - If a function, it is called with the tool parameters and context to
284 * dynamically determine if approval is needed. The function can return
285 * a boolean or an Effect that resolves to a boolean.
286 */
287 readonly needsApproval?: boolean | NeedsApprovalFunction<any> | undefined
288
289 /**
290 * Set whether user approval is required before executing this tool.
291 */
292 setNeedsApproval(
293 needsApproval: NeedsApproval<Config["parameters"]>
294 ): Tool<Name, Config, Requirements>
295
296 /**
297 * Adds a _request-level_ dependency which must be provided before the tool
298 * call handler can be executed.
299 *
300 * **Details**
301 *
302 * This can be useful when you want to enforce that a particular dependency
303 * **MUST** be provided to each request to the large language model provider
304 * instead of being provided when creating the tool call handler layer.
305 */
306 addDependency<Identifier, Service>(
307 tag: Context.Key<Identifier, Service>
308 ): Tool<Name, Config, Identifier | Requirements>
309
310 /**
311 * Set the schema to use to validate the result of a tool call when successful.
312 */
313 setSuccess<SuccessSchema extends Schema.Constraint>(
314 schema: SuccessSchema
315 ): Tool<
316 Name,
317 {
318 readonly parameters: Config["parameters"]
319 readonly success: SuccessSchema
320 readonly failure: Config["failure"]
321 readonly failureMode: Config["failureMode"]
322 },
323 Requirements
324 >
325
326 /**
327 * Set the schema to use to validate the result of a tool call when it fails.
328 */
329 setFailure<FailureSchema extends Schema.Constraint>(
330 schema: FailureSchema
331 ): Tool<
332 Name,
333 {
334 readonly parameters: Config["parameters"]
335 readonly success: Config["success"]
336 readonly failure: FailureSchema
337 readonly failureMode: Config["failureMode"]
338 },
339 Requirements
340 >
341
342 /**
343 * Set the schema to use to validate the parameters of a tool call.
344 */
345 setParameters<ParametersSchema extends Schema.Constraint>(
346 schema: ParametersSchema
347 ): Tool<
348 Name,
349 {
350 readonly parameters: ParametersSchema
351 readonly success: Config["success"]
352 readonly failure: Config["failure"]
353 readonly failureMode: Config["failureMode"]
354 },
355 Requirements
356 >
357
358 /**
359 * Add an annotation to the tool.
360 */
361 annotate<I, S>(tag: Context.Key<I, S>, value: S): Tool<Name, Config, Requirements>
362
363 /**
364 * Add many annotations to the tool.
365 */
366 annotateMerge<I>(context: Context.Context<I>): Tool<Name, Config, Requirements>
367}
368
369/**
370 * A provider-defined tool is a tool which is built into a large language model
371 * provider (e.g. web search, code execution).
372 *
373 * **Details**
374 *
375 * These tools are executed by the large language model provider rather than
376 * by your application. However, they can optionally require custom handlers
377 * implemented in your application to process provider generated results.
378 *
379 * **Example** (Defining a provider-defined web search tool)
380 *
381 * ```ts import.meta.vitest
382 * import { Schema } from "effect"
383 * import { Tool } from "effect/ai"
384 *
385 * // Define a web search tool provided by OpenAI
386 * const WebSearch = Tool.providerDefined({
387 * id: "openai.web_search",
388 * customName: "OpenAiWebSearch",
389 * providerName: "web_search",
390 * args: Schema.Struct({
391 * query: Schema.String
392 * }),
393 * success: Schema.Struct({
394 * results: Schema.Array(Schema.Struct({
395 * title: Schema.String,
396 * url: Schema.String,
397 * snippet: Schema.String
398 * }))
399 * })
400 * })({ query: "Effect" })
401 * const result = [WebSearch.name, WebSearch.providerName] // => ["OpenAiWebSearch", "web_search"]
402 * ```
403 *
404 * @stability unstable
405 * @category models
406 * @since 4.0.0
407 */
408export interface ProviderDefined<
409 out Identifier extends `${string}.${string}`,
410 out Name extends string,
411 out Config extends {
412 readonly args: Schema.Constraint
413 readonly parameters: Schema.Constraint
414 readonly success: Schema.Constraint
415 readonly failure: Schema.Constraint
416 readonly failureMode: FailureMode
417 },
418 out RequiresHandler extends boolean = false
419> extends
420 Tool<
421 Name,
422 {
423 readonly parameters: Config["parameters"]
424 readonly success: Config["success"]
425 readonly failure: Config["failure"]
426 readonly failureMode: Config["failureMode"]
427 }
428 >
429{
430 readonly [ProviderDefinedTypeId]: typeof ProviderDefinedTypeId
431
432 /**
433 * the identifier which is used to uniquely identify the provider-defined tool.
434 */
435 readonly id: Identifier
436
437 /**
438 * The arguments passed to the provider-defined tool.
439 */
440 readonly args: Config["args"]["Encoded"]
441
442 /**
443 * A `Schema` representing the arguments provided by the end-user which will
444 * be used to configure the behavior of the provider-defined tool.
445 */
446 readonly argsSchema: Config["args"]
447
448 /**
449 * Name of the tool as recognized by the large language model provider.
450 */
451 readonly providerName: string
452
453 /**
454 * If set to `true`, this provider-defined tool will require a user-defined
455 * tool call handler to be provided when converting the `Toolkit` containing
456 * this tool into a `Layer`.
457 */
458 readonly requiresHandler: RequiresHandler
459}
460
461/**
462 * A dynamic tool is a tool where the schema may not be known at compile time.
463 *
464 * **Details**
465 *
466 * Dynamic tools support two modes:
467 * - **Effect Schema mode**: Full type safety with validation (like `Tool.make`)
468 * - **JSON Schema mode**: Raw JSON Schema for the model, handler receives `unknown`
469 *
470 * This enables scenarios such as MCP tools discovered at runtime, user-defined
471 * functions loaded from external sources, or plugin systems.
472 *
473 * **Example** (Defining dynamic tools)
474 *
475 * ```ts import.meta.vitest
476 * import { Schema } from "effect"
477 * import { Tool } from "effect/ai"
478 *
479 * // Dynamic tool with Effect Schema (typed)
480 * const Calculator = Tool.dynamic("Calculator", {
481 * parameters: Schema.Struct({
482 * operation: Schema.Literals(["add", "subtract"]),
483 * a: Schema.Number,
484 * b: Schema.Number
485 * }),
486 * success: Schema.Number
487 * })
488 *
489 * // Dynamic tool with JSON Schema (untyped parameters)
490 * const McpTool = Tool.dynamic("McpTool", {
491 * description: "Tool from MCP server",
492 * parameters: {
493 * type: "object",
494 * properties: { query: { type: "string" } },
495 * required: ["query"]
496 * }
497 * })
498 *
499 * const result = [Calculator.name, McpTool.name] // => ["Calculator", "McpTool"]
500 * ```
501 *
502 * @stability unstable
503 * @category models
504 * @since 4.0.0
505 */
506export interface Dynamic<
507 out Name extends string,
508 out Config extends {
509 readonly parameters: Schema.Constraint | JsonSchema.JsonSchema
510 readonly success: Schema.Constraint
511 readonly failure: Schema.Constraint
512 readonly failureMode: FailureMode
513 },
514 out Requirements = never
515> extends
516 Tool<
517 Name,
518 {
519 readonly parameters: Config["parameters"] extends Schema.Constraint ? Config["parameters"] : typeof Schema.Unknown
520 readonly success: Config["success"]
521 readonly failure: Config["failure"]
522 readonly failureMode: Config["failureMode"]
523 },
524 Requirements
525 >
526{
527 readonly [DynamicTypeId]: typeof DynamicTypeId
528
529 /**
530 * The raw JSON Schema for parameters. Present when `parameters` was provided
531 * as a JSON Schema, `undefined` when an Effect Schema was used.
532 */
533 readonly jsonSchema: Config["parameters"] extends Schema.Constraint ? undefined : JsonSchema.JsonSchema
534}
535
536// =============================================================================
537// Type Guards
538// =============================================================================
539
540/**
541 * Type guard to check if a value is a user-defined tool.
542 *
543 * **Example** (Checking for user-defined tools)
544 *
545 * ```ts import.meta.vitest
546 * import { Schema } from "effect"
547 * import { Tool } from "effect/ai"
548 *
549 * const UserDefinedTool = Tool.make("Calculator", {
550 * description: "Performs basic arithmetic operations",
551 * parameters: Schema.Struct({
552 * operation: Schema.Literals(["add", "subtract", "multiply", "divide"]),
553 * a: Schema.Number,
554 * b: Schema.Number
555 * }),
556 * success: Schema.Number
557 * })
558 *
559 * const ProviderDefinedTool = Tool.providerDefined({
560 * id: "openai.web_search",
561 * customName: "OpenAiWebSearch",
562 * providerName: "web_search",
563 * args: Schema.Struct({
564 * query: Schema.String
565 * }),
566 * success: Schema.Struct({
567 * results: Schema.Array(Schema.Struct({
568 * title: Schema.String,
569 * url: Schema.String,
570 * snippet: Schema.String
571 * }))
572 * })
573 * })
574 *
575 * const result = [Tool.isUserDefined(UserDefinedTool), Tool.isUserDefined(ProviderDefinedTool)] // => [true, false]
576 * ```
577 *
578 * @stability unstable
579 * @category guards
580 * @since 4.0.0
581 */
582export const isUserDefined = (u: unknown): u is Tool<string, any, any> =>
583 Predicate.hasProperty(u, TypeId) && !isProviderDefined(u) && !isDynamic(u)
584
585/**
586 * Type guard to check if a value is a provider-defined tool.
587 *
588 * **Example** (Checking for provider-defined tools)
589 *
590 * ```ts import.meta.vitest
591 * import { Schema } from "effect"
592 * import { Tool } from "effect/ai"
593 *
594 * const UserDefinedTool = Tool.make("Calculator", {
595 * description: "Performs basic arithmetic operations",
596 * parameters: Schema.Struct({
597 * operation: Schema.Literals(["add", "subtract", "multiply", "divide"]),
598 * a: Schema.Number,
599 * b: Schema.Number
600 * }),
601 * success: Schema.Number
602 * })
603 *
604 * const ProviderDefinedTool = Tool.providerDefined({
605 * id: "openai.web_search",
606 * customName: "OpenAiWebSearch",
607 * providerName: "web_search",
608 * args: Schema.Struct({
609 * query: Schema.String
610 * }),
611 * success: Schema.Struct({
612 * results: Schema.Array(Schema.Struct({
613 * title: Schema.String,
614 * url: Schema.String,
615 * snippet: Schema.String
616 * }))
617 * })
618 * })
619 *
620 * const result = [Tool.isProviderDefined(UserDefinedTool), Tool.isProviderDefined(ProviderDefinedTool)] // => [false, false]
621 * ```
622 *
623 * @stability unstable
624 * @category guards
625 * @since 4.0.0
626 */
627export const isProviderDefined = (
628 u: unknown
629): u is ProviderDefined<`${string}.${string}`, string, any> => Predicate.hasProperty(u, ProviderDefinedTypeId)
630
631/**
632 * Type guard to check if a value is a dynamic tool.
633 *
634 * **Example** (Checking for dynamic tools)
635 *
636 * ```ts import.meta.vitest
637 * import { Schema } from "effect"
638 * import { Tool } from "effect/ai"
639 *
640 * const DynamicTool = Tool.dynamic("DynamicTool", {
641 * parameters: { type: "object", properties: {} }
642 * })
643 *
644 * const UserDefinedTool = Tool.make("Calculator", {
645 * parameters: Schema.Struct({ a: Schema.Number, b: Schema.Number }),
646 * success: Schema.Number
647 * })
648 *
649 * const result = [Tool.isDynamic(DynamicTool), Tool.isDynamic(UserDefinedTool)] // => [true, false]
650 * ```
651 *
652 * @stability unstable
653 * @category guards
654 * @since 4.0.0
655 */
656export const isDynamic = (u: unknown): u is Dynamic<string, any> => Predicate.hasProperty(u, DynamicTypeId)
657
658// =============================================================================
659// utility types
660// =============================================================================
661
662/**
663 * A type which represents any `Tool`.
664 *
665 * @stability unstable
666 * @category utility types
667 * @since 4.0.0
668 */
669export interface Any extends
670 Tool<any, {
671 readonly parameters: Schema.Top
672 readonly success: Schema.Top
673 readonly failure: Schema.Top
674 readonly failureMode: FailureMode
675 }, any>
676{}
677
678/**
679 * A type which represents any provider-defined `Tool`.
680 *
681 * @stability unstable
682 * @category utility types
683 * @since 4.0.0
684 */
685export interface AnyProviderDefined extends
686 ProviderDefined<any, any, {
687 readonly args: Schema.Top
688 readonly parameters: Schema.Top
689 readonly success: Schema.Top
690 readonly failure: Schema.Top
691 readonly failureMode: FailureMode
692 }, any>
693{}
694
695/**
696 * A type which represents any dynamic `Tool`.
697 *
698 * @stability unstable
699 * @category utility types
700 * @since 4.0.0
701 */
702export interface AnyDynamic extends
703 Dynamic<any, {
704 readonly parameters: Schema.Top | JsonSchema.JsonSchema
705 readonly success: Schema.Top
706 readonly failure: Schema.Top
707 readonly failureMode: FailureMode
708 }, any>
709{}
710
711// /**
712// * @since 4.0.0
713// * @category utility types
714// */
715// export interface AnyStructSchema extends Schema.Top {
716// readonly fields: Schema.Struct.Fields
717// }
718
719/**
720 * A utility type to extract the `Name` type from an `Tool`.
721 *
722 * @stability unstable
723 * @category utility types
724 * @since 4.0.0
725 */
726export type Name<T> = T extends Tool<
727 infer _Name,
728 infer _Config,
729 infer _Requirements
730> ? _Name
731 : never
732
733/**
734 * A utility type to extract the type of the tool call parameters.
735 *
736 * @stability unstable
737 * @category utility types
738 * @since 4.0.0
739 */
740export type Parameters<T> = T extends Tool<
741 infer _Name,
742 infer _Config,
743 infer _Requirements
744> ? _Config["parameters"]["Type"]
745 : never
746
747/**
748 * A utility type to extract the encoded type of the tool call parameters.
749 *
750 * @stability unstable
751 * @category utility types
752 * @since 4.0.0
753 */
754export type ParametersEncoded<T> = T extends Tool<
755 infer _Name,
756 infer _Config,
757 infer _Requirements
758> ? _Config["parameters"]["Encoded"]
759 : never
760
761/**
762 * A utility type to extract the schema for the parameters which an `Tool`
763 * must be called with.
764 *
765 * @stability unstable
766 * @category utility types
767 * @since 4.0.0
768 */
769export type ParametersSchema<T> = T extends Tool<
770 infer _Name,
771 infer _Config,
772 infer _Requirements
773> ? _Config["parameters"]
774 : never
775
776/**
777 * A utility type to extract the type of the tool call result when it succeeds.
778 *
779 * @stability unstable
780 * @category utility types
781 * @since 4.0.0
782 */
783export type Success<T> = T extends Tool<
784 infer _Name,
785 infer _Config,
786 infer _Requirements
787> ? _Config["success"]["Type"]
788 : never
789
790/**
791 * A utility type to extract the encoded type of the tool call result when
792 * it succeeds.
793 *
794 * @stability unstable
795 * @category utility types
796 * @since 4.0.0
797 */
798export type SuccessEncoded<T> = T extends Tool<
799 infer _Name,
800 infer _Config,
801 infer _Requirements
802> ? _Config["success"]["Encoded"]
803 : never
804
805/**
806 * A utility type to extract the schema for the return type of a tool call when
807 * the tool call succeeds.
808 *
809 * @stability unstable
810 * @category utility types
811 * @since 4.0.0
812 */
813export type SuccessSchema<T> = T extends Tool<
814 infer _Name,
815 infer _Config,
816 infer _Requirements
817> ? _Config["success"]
818 : never
819
820/**
821 * A utility type to extract the type of the tool call result when it fails.
822 *
823 * @stability unstable
824 * @category utility types
825 * @since 4.0.0
826 */
827export type Failure<T> = T extends Tool<
828 infer _Name,
829 infer _Config,
830 infer _Requirements
831> ? _Config["failure"]["Type"]
832 : never
833
834/**
835 * A utility type to extract the encoded type of the tool call result when
836 * it fails.
837 *
838 * @stability unstable
839 * @category utility types
840 * @since 4.0.0
841 */
842export type FailureEncoded<T> = T extends Tool<
843 infer _Name,
844 infer _Config,
845 infer _Requirements
846> ? _Config["failure"]["Encoded"]
847 : never
848
849/**
850 * A tool's failure type, plus {@link ExecutionFailure} in both failure modes
851 * and `AiError` in `"return"` mode.
852 *
853 * @stability unstable
854 * @category utility types
855 * @since 4.0.0
856 */
857export type FailureResult<T> = T extends Tool<
858 infer _Name,
859 infer _Config,
860 infer _Requirements
861> ? _Config["failureMode"] extends "return" ?
862 | _Config["failure"]["Type"]
863 | typeof ExecutionFailure.Type
864 | AiError.AiError
865 : _Config["failure"]["Type"] | typeof ExecutionFailure.Type
866 : never
867
868/**
869 * The encoded version of `FailureResult`.
870 *
871 * @stability unstable
872 * @category utility types
873 * @since 4.0.0
874 */
875export type FailureResultEncoded<T> = T extends Tool<
876 infer _Name,
877 infer _Config,
878 infer _Requirements
879> ? _Config["failureMode"] extends "return" ?
880 | _Config["failure"]["Encoded"]
881 | typeof ExecutionFailure.Encoded
882 | AiError.AiErrorEncoded
883 : _Config["failure"]["Encoded"] | typeof ExecutionFailure.Encoded
884 : never
885
886/**
887 * A tool's success or {@link FailureResult} type.
888 *
889 * @stability unstable
890 * @category utility types
891 * @since 4.0.0
892 */
893export type Result<T> = Success<T> | FailureResult<T>
894
895/**
896 * The encoded form of {@link Result}.
897 *
898 * @stability unstable
899 * @category utility types
900 * @since 4.0.0
901 */
902export type ResultEncoded<T> = SuccessEncoded<T> | FailureResultEncoded<T>
903
904/**
905 * A utility type to extract the requirements of a `Tool` call handler.
906 *
907 * @stability unstable
908 * @category utility types
909 * @since 4.0.0
910 */
911export type HandlerServices<T> = T extends Tool<
912 infer _Name,
913 infer _Config,
914 infer _Requirements
915> ? // Parameters must be decoded when received from a model
916 | _Config["parameters"]["DecodingServices"]
917 // A tool call `result`, whether success or failure, is encoded and returned
918 // as the `encodedResult` along with the `result`
919 | ResultEncodingServices<T>
920 // Per-request requirements
921 | _Requirements
922 : never
923
924/**
925 * A utility type to extract the requirements needed to encode the result of
926 * a `Tool` call.
927 *
928 * @stability unstable
929 * @category utility types
930 * @since 4.0.0
931 */
932export type ResultEncodingServices<T> = T extends Tool<
933 infer _Name,
934 infer _Config,
935 infer _Requirements
936> ? _Config["success"]["EncodingServices"] | _Config["failure"]["EncodingServices"]
937 : never
938
939/**
940 * A utility type to extract the requirements needed to decode the result of
941 * a `Tool` call.
942 *
943 * @stability unstable
944 * @category utility types
945 * @since 4.0.0
946 */
947export type ResultDecodingServices<T> = T extends Tool<
948 infer _Name,
949 infer _Config,
950 infer _Requirements
951> ? _Config["success"]["DecodingServices"] | _Config["failure"]["DecodingServices"]
952 : never
953
954/**
955 * Extracts the services required to encode tool parameters.
956 *
957 * @stability unstable
958 * @category utility types
959 * @since 4.0.0
960 */
961export type ParametersEncodingServices<T> = T extends Tool<
962 infer _Name,
963 infer _Config,
964 infer _Requirements
965> ? _Config["parameters"]["EncodingServices"]
966 : never
967
968/**
969 * Represents an `Tool` that has been implemented within the application.
970 *
971 * @stability unstable
972 * @category models
973 * @since 4.0.0
974 */
975export interface Handler<Name extends string> {
976 readonly _: unique symbol
977 readonly name: Name
978 readonly context: Context.Context<never>
979 readonly handler: (params: any, ctx: any) => Effect.Effect<any, any>
980}
981
982/**
983 * The phase of handling a tool call that produced a failure.
984 *
985 * **Details**
986 *
987 * - `"parameters"`: the tool call arguments failed to decode
988 * - `"handler"`: the tool handler itself failed
989 * - `"result"`: the handler's output failed to validate or encode
990 *
991 * @stability unstable
992 * @category models
993 * @since 4.0.0
994 */
995export type FailureOrigin = "parameters" | "handler" | "result"
996
997/**
998 * Represents the result of calling the handler for a particular `Tool`.
999 *
1000 * @stability unstable
1001 * @category models
1002 * @since 4.0.0
1003 */
1004export interface HandlerResult<Tool extends Any> {
1005 /**
1006 * The result of executing the handler for a particular tool.
1007 */
1008 readonly result: Result<Tool>
1009 /**
1010 * The pre-encoded tool call result of executing the handler for a particular
1011 * tool as a JSON-serializable value. The encoded result can be incorporated
1012 * into subsequent requests to the large language model.
1013 */
1014 readonly encodedResult: unknown
1015 /**
1016 * Whether the result of executing the tool call handler was an error or not.
1017 */
1018 readonly isFailure: boolean
1019 /**
1020 * Which phase of handling the tool call failed. Present when `isFailure` is
1021 * `true`.
1022 */
1023 readonly failureOrigin?: FailureOrigin
1024 /**
1025 * Whether this is a preliminary (intermediate) result or the final result.
1026 * Preliminary results represent progress updates; only the final result
1027 * should be used as the authoritative output.
1028 */
1029 readonly preliminary: boolean
1030}
1031
1032/**
1033 * Tagged union for incremental handler output.
1034 *
1035 * **Details**
1036 *
1037 * When a tool handler returns a `Stream`, each emitted value is tagged as
1038 * either:
1039 * - `Preliminary`: An intermediate result representing progress
1040 * - `Final`: The last result, which is the authoritative output
1041 *
1042 * @stability unstable
1043 * @category models
1044 * @since 4.0.0
1045 */
1046export type HandlerOutput<Success> =
1047 | { readonly _tag: "Preliminary"; readonly value: Success }
1048 | { readonly _tag: "Final"; readonly value: Success }
1049
1050/**
1051 * A utility type which represents the possible errors that can be raised by
1052 * a tool call's handler.
1053 *
1054 * @stability unstable
1055 * @category utility types
1056 * @since 4.0.0
1057 */
1058export type HandlerError<T> = T extends Tool<
1059 infer _Name,
1060 infer _Config,
1061 infer _Requirements
1062> ? _Config["failureMode"] extends "error" ? _Config["failure"]["Type"] | AiError.AiError
1063 : never
1064 : never
1065
1066/**
1067 * A utility type to create a union of `Handler` types for all tools in a
1068 * record.
1069 *
1070 * @stability unstable
1071 * @category utility types
1072 * @since 4.0.0
1073 */
1074export type HandlersFor<Tools extends Record<string, Any>> = {
1075 [Name in keyof Tools]: RequiresHandler<Tools[Name]> extends true ? Handler<Tools[Name]["name"]>
1076 : never
1077}[keyof Tools]
1078
1079/**
1080 * A utility type to determine if the specified tool requires a user-defined
1081 * handler to be implemented.
1082 *
1083 * @stability unstable
1084 * @category utility types
1085 * @since 4.0.0
1086 */
1087export type RequiresHandler<Tool extends Any> = Tool extends ProviderDefined<
1088 infer _Name,
1089 infer _Config,
1090 infer _RequiresHandler
1091> ? _RequiresHandler
1092 : true
1093
1094// =============================================================================
1095// Constructors
1096// =============================================================================
1097
1098// Clones a tool while preserving its prototype (and thus its kind, e.g.
1099// user-defined vs. provider-defined vs. dynamic) and its own properties such
1100// as `id`. Optional `overrides` replace individual fields on the clone.
1101const clone = (self: Any, overrides?: Record<string, unknown>): any =>
1102 Object.assign(Object.create(Object.getPrototypeOf(self)), self, overrides)
1103
1104const Proto = {
1105 [TypeId]: { _Requirements: identity },
1106 pipe() {
1107 return pipeArguments(this, arguments)
1108 },
1109 addDependency(this: Any) {
1110 return clone(this)
1111 },
1112 setParameters(this: Any, parametersSchema: Schema.Constraint) {
1113 return clone(this, { parametersSchema, jsonSchema: undefined })
1114 },
1115 setSuccess(this: Any, successSchema: Schema.Constraint) {
1116 return clone(this, { successSchema })
1117 },
1118 setFailure(this: Any, failureSchema: Schema.Constraint) {
1119 return clone(this, { failureSchema })
1120 },
1121 setNeedsApproval(this: Any, needsApproval: NeedsApproval<any>) {
1122 return clone(this, { needsApproval })
1123 },
1124 annotate<I, S>(this: Any, tag: Context.Key<I, S>, value: S) {
1125 return clone(this, { annotations: Context.add(this.annotations, tag, value) })
1126 },
1127 annotateMerge<I>(this: Any, context: Context.Context<I>) {
1128 return clone(this, { annotations: Context.merge(this.annotations, context) })
1129 }
1130}
1131
1132const ProviderDefinedProto = {
1133 ...Proto,
1134 [ProviderDefinedTypeId]: ProviderDefinedTypeId
1135}
1136
1137const DynamicProto = {
1138 ...Proto,
1139 [DynamicTypeId]: DynamicTypeId
1140}
1141
1142const userDefinedProto = <
1143 const Name extends string,
1144 Parameters extends Schema.Constraint,
1145 Success extends Schema.Constraint,
1146 Failure extends Schema.Constraint,
1147 Mode extends FailureMode
1148>(options: {
1149 readonly name: Name
1150 readonly description?: string | undefined
1151 readonly parametersSchema: Parameters
1152 readonly successSchema: Success
1153 readonly failureSchema: Failure
1154 readonly annotations: Context.Context<never>
1155 readonly failureMode: Mode
1156 readonly needsApproval?: NeedsApproval<Parameters> | undefined
1157}): Tool<
1158 Name,
1159 {
1160 readonly parameters: Parameters
1161 readonly success: Success
1162 readonly failure: Failure
1163 readonly failureMode: Mode
1164 }
1165> => {
1166 const self = Object.assign(Object.create(Proto), options)
1167 self.id = `effect/ai/Tool/${options.name}`
1168 return self
1169}
1170
1171const providerDefinedProto = <
1172 const Identifier extends `${string}.${string}`,
1173 const Name extends string,
1174 Args extends Schema.Constraint,
1175 Parameters extends Schema.Constraint,
1176 Success extends Schema.Constraint,
1177 Failure extends Schema.Constraint,
1178 RequiresHandler extends boolean,
1179 Mode extends FailureMode
1180>(options: {
1181 readonly id: Identifier
1182 readonly name: Name
1183 readonly providerName: string
1184 readonly args: Args["Encoded"]
1185 readonly argsSchema: Args
1186 readonly requiresHandler: RequiresHandler
1187 readonly parametersSchema: Parameters
1188 readonly successSchema: Success
1189 readonly failureSchema: Failure
1190 readonly failureMode: FailureMode
1191}): ProviderDefined<
1192 Identifier,
1193 Name,
1194 {
1195 readonly args: Args
1196 readonly parameters: Parameters
1197 readonly success: Success
1198 readonly failure: Failure
1199 readonly failureMode: Mode
1200 },
1201 RequiresHandler
1202> => Object.assign(Object.create(ProviderDefinedProto), { annotations: Context.empty(), ...options })
1203
1204const dynamicProto = <
1205 const Name extends string,
1206 Parameters extends Schema.Constraint | JsonSchema.JsonSchema,
1207 Success extends Schema.Constraint,
1208 Failure extends Schema.Constraint,
1209 Mode extends FailureMode
1210>(options: {
1211 readonly name: Name
1212 readonly description?: string | undefined
1213 readonly parametersSchema: Parameters
1214 readonly successSchema: Success
1215 readonly failureSchema: Failure
1216 readonly annotations: Context.Context<never>
1217 readonly failureMode: Mode
1218 readonly needsApproval?: NeedsApproval<any> | undefined
1219 readonly jsonSchema: JsonSchema.JsonSchema | undefined
1220}): Dynamic<
1221 Name,
1222 {
1223 readonly parameters: Parameters
1224 readonly success: Success
1225 readonly failure: Failure
1226 readonly failureMode: Mode
1227 }
1228> => {
1229 const self = Object.assign(Object.create(DynamicProto), options)
1230 self.id = `effect/ai/Tool/${options.name}`
1231 return self
1232}
1233
1234/**
1235 * Creates a user-defined tool with the specified name and configuration.
1236 *
1237 * **Details**
1238 *
1239 * This is the primary constructor for creating custom tools that AI models
1240 * can call. The tool definition includes parameter validation, success/failure
1241 * schemas, and optional service dependencies.
1242 *
1243 * If a tool accepts no parameters but still needs an explicit empty object
1244 * schema, use {@link EmptyParams}.
1245 *
1246 * **Example** (Creating a tool without parameters)
1247 *
1248 * ```ts import.meta.vitest
1249 * import { Schema } from "effect"
1250 * import { Tool } from "effect/ai"
1251 *
1252 * // Simple tool with no parameters
1253 * const GetCurrentTime = Tool.make("GetCurrentTime", {
1254 * description: "Returns the current timestamp",
1255 * success: Schema.Number
1256 * })
1257 * GetCurrentTime.name // => "GetCurrentTime"
1258 * ```
1259 *
1260 * @stability unstable
1261 * @category constructors
1262 * @since 4.0.0
1263 */
1264export const make = <
1265 const Name extends string,
1266 Parameters extends Schema.Constraint = typeof EmptyParams,
1267 Success extends Schema.Constraint = typeof Schema.Void,
1268 Failure extends Schema.Constraint = typeof Schema.Never,
1269 Mode extends FailureMode | undefined = undefined,
1270 Dependencies extends Array<Context.Key<any, any> | Context.Key<never, any>> = []
1271>(name: Name, options?: {
1272 /**
1273 * An optional description explaining what the tool does.
1274 */
1275 readonly description?: string | undefined
1276 /**
1277 * Schema defining the parameters this tool accepts.
1278 */
1279 readonly parameters?: Parameters | undefined
1280 /**
1281 * Schema for successful tool execution results.
1282 */
1283 readonly success?: Success | undefined
1284 /**
1285 * Schema for tool execution failures.
1286 */
1287 readonly failure?: Failure | undefined
1288 /**
1289 * The strategy used for handling errors returned from tool call handler
1290 * execution.
1291 *
1292 * **Details**
1293 *
1294 * If set to `"error"` (the default), errors that occur during tool call handler
1295 * execution will be returned in the error channel of the calling effect.
1296 *
1297 * If set to `"return"`, errors that occur during tool call handler execution
1298 * will be captured and returned as part of the tool call result.
1299 */
1300 readonly failureMode?: Mode
1301 /**
1302 * Service dependencies required by the tool handler.
1303 */
1304 readonly dependencies?: Dependencies | undefined
1305 /**
1306 * Specifies whether user approval is required before executing this tool.
1307 *
1308 * **Details**
1309 *
1310 * - If `undefined` or `false`, the tool executes immediately.
1311 * - If `true`, the tool always requires approval.
1312 * - If a function, it is called with the tool parameters and context to
1313 * dynamically determine if approval is needed.
1314 */
1315 readonly needsApproval?: NeedsApproval<Parameters> | undefined
1316}): Tool<
1317 Name,
1318 {
1319 readonly parameters: Parameters
1320 readonly success: Success
1321 readonly failure: Failure
1322 readonly failureMode: Mode extends undefined ? "error" : Mode
1323 },
1324 Context.Service.Identifier<Dependencies[number]>
1325> => {
1326 const successSchema = options?.success ?? Schema.Void
1327 const failureSchema = options?.failure ?? Schema.Never
1328 return userDefinedProto({
1329 name,
1330 description: options?.description,
1331 parametersSchema: options?.parameters ?? EmptyParams,
1332 successSchema,
1333 failureSchema,
1334 failureMode: options?.failureMode ?? "error",
1335 annotations: Context.empty(),
1336 needsApproval: options?.needsApproval as any
1337 }) as any
1338}
1339
1340/**
1341 * Creates a dynamic tool that can accept either an Effect Schema or a raw
1342 * JSON Schema for its parameters.
1343 *
1344 * **When to use**
1345 *
1346 * Use when you do not know a tool schema at compile time, such as MCP tools
1347 * discovered at runtime or tools from external configurations.
1348 *
1349 * **Details**
1350 *
1351 * - When `parameters` is an Effect Schema: full type safety with validation
1352 * - When `parameters` is a JSON Schema: handler receives `unknown`, no validation
1353 *
1354 * **Example** (Creating a dynamic tool)
1355 *
1356 * ```ts import.meta.vitest
1357 * import { Schema } from "effect"
1358 * import { Tool } from "effect/ai"
1359 *
1360 * // With Effect Schema (typed parameters)
1361 * const Calculator = Tool.dynamic("Calculator", {
1362 * parameters: Schema.Struct({
1363 * operation: Schema.Literals(["add", "subtract"]),
1364 * a: Schema.Number,
1365 * b: Schema.Number
1366 * }),
1367 * success: Schema.Number
1368 * })
1369 *
1370 * // With JSON Schema (untyped parameters)
1371 * const McpTool = Tool.dynamic("McpTool", {
1372 * description: "Tool from MCP server",
1373 * parameters: {
1374 * type: "object",
1375 * properties: { query: { type: "string" } },
1376 * required: ["query"]
1377 * }
1378 * })
1379 *
1380 * const result = [Calculator.name, McpTool.name] // => ["Calculator", "McpTool"]
1381 * ```
1382 *
1383 * @stability unstable
1384 * @category constructors
1385 * @since 4.0.0
1386 */
1387export const dynamic: {
1388 <
1389 const Name extends string,
1390 const Options extends {
1391 readonly description?: string | undefined
1392 readonly parameters?: Schema.Constraint | JsonSchema.JsonSchema | undefined
1393 readonly success?: Schema.Constraint | undefined
1394 readonly failure?: Schema.Constraint | undefined
1395 readonly failureMode?: FailureMode | undefined
1396 readonly needsApproval?: NeedsApproval<any> | undefined
1397 }
1398 >(
1399 name: Name,
1400 options?: Options
1401 ): Dynamic<
1402 Name,
1403 {
1404 readonly parameters: Options extends { readonly parameters: infer P } ? P extends Schema.Constraint ? P
1405 : P extends JsonSchema.JsonSchema ? P
1406 : typeof Schema.Unknown
1407 : typeof Schema.Unknown
1408 readonly success: Options extends { readonly success: infer S extends Schema.Constraint } ? S
1409 : typeof Schema.Unknown
1410 readonly failure: Options extends { readonly failure: infer F extends Schema.Constraint } ? F
1411 : typeof Schema.Never
1412 readonly failureMode: Options extends { readonly failureMode: infer M extends FailureMode } ? M : "error"
1413 }
1414 >
1415} = <
1416 const Name extends string,
1417 const Options extends {
1418 readonly description?: string | undefined
1419 readonly parameters?: Schema.Constraint | JsonSchema.JsonSchema | undefined
1420 readonly success?: Schema.Constraint | undefined
1421 readonly failure?: Schema.Constraint | undefined
1422 readonly failureMode?: FailureMode | undefined
1423 readonly needsApproval?: NeedsApproval<any> | undefined
1424 }
1425>(name: Name, options?: Options): any => {
1426 const successSchema = options?.success ?? Schema.Unknown
1427 const failureSchema = options?.failure ?? Schema.Never
1428 const rawParameters = options?.parameters ?? Schema.Unknown
1429 const isEffectSchema = Schema.isSchema(rawParameters)
1430 const parametersSchema = isEffectSchema ? rawParameters : Schema.Unknown
1431 const jsonSchema = isEffectSchema ? undefined : rawParameters as JsonSchema.JsonSchema
1432 return dynamicProto({
1433 name,
1434 description: options?.description,
1435 parametersSchema,
1436 successSchema,
1437 failureSchema,
1438 failureMode: options?.failureMode ?? "error",
1439 annotations: Context.empty(),
1440 needsApproval: options?.needsApproval,
1441 jsonSchema
1442 })
1443}
1444
1445/**
1446 * Creates a provider-defined tool which leverages functionality built into a
1447 * large language model provider (e.g. web search, code execution).
1448 *
1449 * **Details**
1450 *
1451 * These tools are executed by the large language model provider rather than
1452 * by your application. However, they can optionally require custom handlers
1453 * implemented in your application to process provider generated results.
1454 *
1455 * **Example** (Creating a provider-defined tool)
1456 *
1457 * ```ts import.meta.vitest
1458 * import { Schema } from "effect"
1459 * import { Tool } from "effect/ai"
1460 *
1461 * // Web search tool provided by OpenAI
1462 * const WebSearch = Tool.providerDefined({
1463 * id: "openai.web_search",
1464 * customName: "OpenAiWebSearch",
1465 * providerName: "web_search",
1466 * args: Schema.Struct({
1467 * query: Schema.String
1468 * }),
1469 * success: Schema.Struct({
1470 * results: Schema.Array(Schema.Struct({
1471 * title: Schema.String,
1472 * url: Schema.String,
1473 * content: Schema.String
1474 * }))
1475 * })
1476 * })({ query: "Effect" })
1477 * const result = [WebSearch.name, WebSearch.providerName] // => ["OpenAiWebSearch", "web_search"]
1478 * ```
1479 *
1480 * @stability unstable
1481 * @category constructors
1482 * @since 4.0.0
1483 */
1484export const providerDefined = <
1485 const Identifier extends `${string}.${string}`,
1486 const Name extends string,
1487 Args extends Schema.Constraint = typeof Schema.Void,
1488 Parameters extends Schema.Constraint = typeof Schema.Void,
1489 Success extends Schema.Constraint = typeof Schema.Void,
1490 Failure extends Schema.Constraint = typeof Schema.Never,
1491 RequiresHandler extends boolean = false
1492>(options: {
1493 /**
1494 * the identifier which is used to uniquely identify the provider-defined tool.
1495 */
1496 readonly id: Identifier
1497 /**
1498 * Custom name used by the Toolkit to identify this tool.
1499 */
1500 readonly customName: Name
1501 /**
1502 * Provider-specific name given to the tool by the large language model provider.
1503 */
1504 readonly providerName: string
1505 /**
1506 * Schema for user-provided configuration arguments.
1507 */
1508 readonly args?: Args | undefined
1509 /**
1510 * Whether this tool requires a custom handler implementation.
1511 */
1512 readonly requiresHandler?: RequiresHandler | undefined
1513 /**
1514 * Schema for parameters the provider sends when calling the tool.
1515 */
1516 readonly parameters?: Parameters | undefined
1517 /**
1518 * Schema for successful tool execution results.
1519 */
1520 readonly success?: Success | undefined
1521 /**
1522 * Schema for failed tool execution results.
1523 */
1524 readonly failure?: Failure | undefined
1525}) =>
1526<Mode extends FailureMode | undefined = undefined>(
1527 args: RequiresHandler extends true ? Struct.Simplify<
1528 Args["Encoded"] & {
1529 /**
1530 * The strategy used for handling errors returned from tool call handler
1531 * execution.
1532 *
1533 * If set to `"error"` (the default), errors that occur during tool call handler
1534 * execution will be returned in the error channel of the calling effect.
1535 *
1536 * If set to `"return"`, errors that occur during tool call handler execution
1537 * will be captured and returned as part of the tool call result.
1538 */
1539 readonly failureMode?: Mode | undefined
1540 }
1541 >
1542 : Struct.Simplify<Args["Encoded"]>
1543): ProviderDefined<
1544 Identifier,
1545 Name,
1546 {
1547 readonly args: Args
1548 readonly parameters: Parameters
1549 readonly success: Success
1550 readonly failure: Failure
1551 readonly failureMode: Mode extends undefined ? "error" : Mode
1552 },
1553 RequiresHandler
1554> => {
1555 const failureMode = Predicate.isNotUndefined(args) && "failureMode" in args
1556 ? (args as any).failureMode
1557 : undefined
1558 const successSchema = options?.success ?? Schema.Void
1559 const failureSchema = options?.failure ?? Schema.Never
1560 return providerDefinedProto({
1561 id: options.id,
1562 name: options.customName,
1563 providerName: options.providerName,
1564 args: args as any,
1565 argsSchema: (options?.args ?? Schema.Void) as any,
1566 requiresHandler: options.requiresHandler ?? false,
1567 parametersSchema: (options?.parameters ?? Schema.Void) as any,
1568 successSchema,
1569 failureSchema,
1570 failureMode: failureMode ?? "error"
1571 }) as any
1572}
1573
1574// =============================================================================
1575// Utilities
1576// =============================================================================
1577
1578/**
1579 * Maps between a provider-defined tool name and the name given to the tool by
1580 * the Effect AI SDK.
1581 *
1582 * **Details**
1583 *
1584 * The custom names used by the Effect AI SDK are to allow for toolkits which
1585 * contain tools from multiple different providers that would otherwise have
1586 * naming conflicts (i.e. `"web_search"`) to instead use custom names (i.e.
1587 * `"OpenAiWebSearch"`).
1588 *
1589 * @stability unstable
1590 * @category models
1591 * @since 4.0.0
1592 */
1593export class NameMapper<Tools extends ReadonlyArray<Any>> {
1594 readonly #customToProvider: Map<string, string> = new Map()
1595 readonly #providerToCustom: Map<string, string> = new Map()
1596
1597 constructor(tools: Tools) {
1598 for (const tool of tools) {
1599 if (isProviderDefined(tool)) {
1600 this.#customToProvider.set(tool.name, tool.providerName)
1601 this.#providerToCustom.set(tool.providerName, tool.name)
1602 }
1603 }
1604 }
1605
1606 /**
1607 * Returns a list of the user-specified tool names in the name mapper.
1608 */
1609 get customNames(): ReadonlyArray<string> {
1610 return Array.from(this.#customToProvider.keys())
1611 }
1612
1613 /**
1614 * Returns a list of the provider-specified tool names in the name mapper.
1615 */
1616 get providerNames(): ReadonlyArray<string> {
1617 return Array.from(this.#providerToCustom.keys())
1618 }
1619
1620 /**
1621 * Returns the user-specified tool name that corresponds with the provided
1622 * provider-specified tool name.
1623 *
1624 * **Details**
1625 *
1626 * If the provider-specified tool name was not registered with the name mapper,
1627 * then the provider-specified tool name is returned.
1628 */
1629 getCustomName(providerName: string): string {
1630 return this.#providerToCustom.get(providerName) ?? providerName
1631 }
1632
1633 /**
1634 * Returns the provider-specified tool name that corresponds with the provided
1635 * user-specified tool name.
1636 *
1637 * **Details**
1638 *
1639 * If the user-specified tool name was not registered with the name mapper,
1640 * then the user-specified tool name is returned.
1641 */
1642 getProviderName(customName: string): string {
1643 return this.#customToProvider.get(customName) ?? customName
1644 }
1645}
1646
1647/**
1648 * Extracts the description from a tool's metadata.
1649 *
1650 * **Details**
1651 *
1652 * Returns the tool's description if explicitly set, otherwise attempts to
1653 * extract it from the parameter schema's AST annotations.
1654 *
1655 * **Example** (Reading a tool description)
1656 *
1657 * ```ts import.meta.vitest
1658 * import { Tool } from "effect/ai"
1659 *
1660 * const myTool = Tool.make("example", {
1661 * description: "This is an example tool"
1662 * })
1663 *
1664 * const description = Tool.getDescription(myTool)
1665 * description // => "This is an example tool"
1666 * ```
1667 *
1668 * @stability unstable
1669 * @category getters
1670 * @since 4.0.0
1671 */
1672export const getDescription = <Tool extends Any>(tool: Tool): string | undefined => {
1673 if (tool.description !== undefined) {
1674 return tool.description
1675 }
1676 if (Schema.isSchema(tool.parametersSchema)) {
1677 return SchemaAST.resolveDescription(tool.parametersSchema.ast)
1678 }
1679 return undefined
1680}
1681
1682/**
1683 * Generates a JSON Schema for a tool.
1684 *
1685 * **Details**
1686 *
1687 * This function creates a JSON Schema representation that can be used by
1688 * large language models to indicate the structure and type of the parameters
1689 * that a given tool call should receive.
1690 *
1691 * May accept an optional `CodecTransformer` which can be used to transform the
1692 * tool parameter schema so that the resultant JSON schema for the tool call
1693 * parameters are in a format that conforms to any provider-specific constraints.
1694 *
1695 * **Example** (Generating a tool JSON schema)
1696 *
1697 * ```ts import.meta.vitest
1698 * import { Schema } from "effect"
1699 * import { Tool } from "effect/ai"
1700 *
1701 * const weatherTool = Tool.make("get_weather", {
1702 * parameters: Schema.Struct({
1703 * location: Schema.String,
1704 * units: Schema.Literals(["celsius", "fahrenheit"])
1705 * })
1706 * })
1707 *
1708 * const jsonSchema = Tool.getJsonSchema(weatherTool)
1709 * jsonSchema.type // => "object"
1710 * if (typeof jsonSchema.properties === "object" && jsonSchema.properties !== null) {
1711 * Object.keys(jsonSchema.properties) // => ["location", "units"]
1712 * }
1713 * ```
1714 *
1715 * @stability unstable
1716 * @category getters
1717 * @since 4.0.0
1718 */
1719export const getJsonSchema = <Tool extends Any>(tool: Tool, options?: {
1720 readonly transformer?: CodecTransformer
1721}): JsonSchema.JsonSchema => {
1722 if (isDynamic(tool) && tool.jsonSchema !== undefined) {
1723 return tool.jsonSchema
1724 }
1725 return getJsonSchemaFromSchema(tool.parametersSchema, options)
1726}
1727
1728/**
1729 * Generates a JSON Schema from an Effect `Schema`.
1730 *
1731 * **Details**
1732 *
1733 * If a `CodecTransformer` is supplied, the transformed schema's JSON Schema is
1734 * returned. Otherwise, the schema is converted with
1735 * `Schema.toJsonSchemaDocument` and any generated definitions are attached as
1736 * `$defs`.
1737 *
1738 * @stability unstable
1739 * @category converting
1740 * @since 4.0.0
1741 */
1742export const getJsonSchemaFromSchema = <S extends Schema.Constraint>(schema: S, options?: {
1743 readonly transformer?: CodecTransformer
1744}): JsonSchema.JsonSchema => {
1745 return getJsonSchemaFromSchemaWith(schema, Schema.toJsonSchemaDocument, options)
1746}
1747
1748const getJsonSchemaFromSchemaWith = <S extends Schema.Constraint>(
1749 schema: S,
1750 toJsonSchemaDocument: (schema: Schema.Constraint) => JsonSchema.Document<"draft-2020-12">,
1751 options?: { readonly transformer?: CodecTransformer }
1752): JsonSchema.JsonSchema => {
1753 if (Predicate.isNotUndefined(options?.transformer)) {
1754 return options.transformer(schema).jsonSchema
1755 }
1756 const document = toJsonSchemaDocument(schema)
1757 if (Object.keys(document.definitions).length > 0) {
1758 document.schema.$defs = document.definitions
1759 }
1760 return document.schema
1761}
1762
1763// =============================================================================
1764// Annotations
1765// =============================================================================
1766
1767/**
1768 * Annotation for providing a human-readable title for tools.
1769 *
1770 * **Example** (Annotating a tool title)
1771 *
1772 * ```ts import.meta.vitest
1773 * import { Context } from "effect"
1774 * import { Tool } from "effect/ai"
1775 *
1776 * const myTool = Tool.make("calculate_tip")
1777 * .annotate(Tool.Title, "Tip Calculator")
1778 * Context.getUnsafe(myTool.annotations, Tool.Title) // => "Tip Calculator"
1779 * ```
1780 *
1781 * @stability unstable
1782 * @category services
1783 * @since 4.0.0
1784 */
1785export class Title extends Context.Service<Title, string>()("effect/ai/Tool/Title") {}
1786
1787/**
1788 * Annotation for providing tool metadata for MCP.
1789 *
1790 * **Example** (Annotating MCP metadata)
1791 *
1792 * ```ts import.meta.vitest
1793 * import { Context } from "effect"
1794 * import { Tool } from "effect/ai"
1795 *
1796 * const myCalculatorUi = Tool.make("calculator_ui", {})
1797 * .annotate(Tool.Meta, { ui: { resourceUri: "ui://example/calculator-ui" } })
1798 * "ui" in Context.getUnsafe(myCalculatorUi.annotations, Tool.Meta) // => true
1799 * ```
1800 *
1801 * @stability unstable
1802 * @category services
1803 * @since 4.0.0
1804 */
1805export class Meta extends Context.Service<Meta, Record<string, unknown>>()("effect/ai/Tool/Meta") {}
1806
1807/**
1808 * Annotation indicating whether a tool only reads data without making changes.
1809 *
1810 * **Details**
1811 *
1812 * This is emitted as the MCP `readOnlyHint`; unannotated tools default to
1813 * `false`.
1814 *
1815 * **Example** (Marking a tool as read-only)
1816 *
1817 * ```ts import.meta.vitest
1818 * import { Context } from "effect"
1819 * import { Tool } from "effect/ai"
1820 *
1821 * const readOnlyTool = Tool.make("get_user_info")
1822 * .annotate(Tool.Readonly, true)
1823 * Context.get(readOnlyTool.annotations, Tool.Readonly) // => true
1824 * ```
1825 *
1826 * @stability unstable
1827 * @category services
1828 * @since 4.0.0
1829 */
1830export const Readonly = Context.Reference<boolean>("effect/ai/Tool/Readonly", {
1831 defaultValue: constFalse
1832})
1833
1834/**
1835 * Annotation indicating whether a tool may perform destructive operations.
1836 *
1837 * **Details**
1838 *
1839 * This is emitted as the MCP `destructiveHint`; unannotated tools default to
1840 * `true`, so annotate safe tools with `false`.
1841 *
1842 * **Example** (Marking a tool as non-destructive)
1843 *
1844 * ```ts import.meta.vitest
1845 * import { Context } from "effect"
1846 * import { Tool } from "effect/ai"
1847 *
1848 * const safeTool = Tool.make("search_database")
1849 * .annotate(Tool.Destructive, false)
1850 * Context.get(safeTool.annotations, Tool.Destructive) // => false
1851 * ```
1852 *
1853 * @stability unstable
1854 * @category services
1855 * @since 4.0.0
1856 */
1857export const Destructive = Context.Reference<boolean>("effect/ai/Tool/Destructive", {
1858 defaultValue: constTrue
1859})
1860
1861/**
1862 * Annotation indicating whether a tool can be called repeatedly with the same
1863 * parameters without changing the result beyond the first call.
1864 *
1865 * **Details**
1866 *
1867 * This is emitted as the MCP `idempotentHint`; unannotated tools default to
1868 * `false`.
1869 *
1870 * **Example** (Marking a tool as idempotent)
1871 *
1872 * ```ts import.meta.vitest
1873 * import { Context } from "effect"
1874 * import { Tool } from "effect/ai"
1875 *
1876 * const idempotentTool = Tool.make("get_current_time")
1877 * .annotate(Tool.Idempotent, true)
1878 * Context.get(idempotentTool.annotations, Tool.Idempotent) // => true
1879 * ```
1880 *
1881 * @stability unstable
1882 * @category services
1883 * @since 4.0.0
1884 */
1885export const Idempotent = Context.Reference<boolean>("effect/ai/Tool/Idempotent", {
1886 defaultValue: constFalse
1887})
1888
1889/**
1890 * Annotation indicating whether a tool may interact with arbitrary external
1891 * data or systems.
1892 *
1893 * **Details**
1894 *
1895 * This is emitted as the MCP `openWorldHint`; unannotated tools default to
1896 * `true`.
1897 *
1898 * **Example** (Disabling open-world access)
1899 *
1900 * ```ts import.meta.vitest
1901 * import { Context } from "effect"
1902 * import { Tool } from "effect/ai"
1903 *
1904 * const restrictedTool = Tool.make("internal_operation")
1905 * .annotate(Tool.OpenWorld, false)
1906 * Context.get(restrictedTool.annotations, Tool.OpenWorld) // => false
1907 * ```
1908 *
1909 * @stability unstable
1910 * @category services
1911 * @since 4.0.0
1912 */
1913export const OpenWorld = Context.Reference<boolean>("effect/ai/Tool/OpenWorld", {
1914 defaultValue: constTrue
1915})
1916
1917/**
1918 * Annotation controlling whether strict JSON schema mode is enabled for a tool.
1919 *
1920 * **Details**
1921 *
1922 * When `true`, providers that support strict mode will send `strict: true` to
1923 * the model API (e.g. OpenAI's Structured Outputs).
1924 *
1925 * When `false`, strict mode is disabled and `strict: false` is sent.
1926 *
1927 * When `undefined` (default), the provider's global configuration determines
1928 * the behavior (e.g. `Config.strictJsonSchema` for OpenAI).
1929 *
1930 * **Example** (Disabling strict JSON schema mode)
1931 *
1932 * ```ts import.meta.vitest
1933 * import { Tool } from "effect/ai"
1934 *
1935 * const flexibleTool = Tool.make("search")
1936 * .annotate(Tool.Strict, false)
1937 * Tool.getStrictMode(flexibleTool) // => false
1938 * ```
1939 *
1940 * @stability unstable
1941 * @category services
1942 * @since 4.0.0
1943 */
1944export const Strict = Context.Reference<boolean | undefined>("effect/ai/Tool/Strict", {
1945 defaultValue: () => undefined
1946})
1947
1948/**
1949 * Returns the strict mode setting for a tool, or `undefined` if not set.
1950 *
1951 * **When to use**
1952 *
1953 * Use to inspect the per-tool strict JSON Schema override attached through
1954 * `Tool.Strict`.
1955 *
1956 * **Gotchas**
1957 *
1958 * `undefined` means no per-tool override is set. It is distinct from `false`;
1959 * provider or global configuration determines the final behavior.
1960 *
1961 * @see {@link Strict} for the annotation read by this helper
1962 *
1963 * @stability unstable
1964 * @category getters
1965 * @since 4.0.0
1966 */
1967export const getStrictMode = <T extends Any>(tool: T): boolean | undefined => Context.get(tool.annotations, Strict)
1968
1969// Licensed under BSD-3-Clause (below code only)
1970// Code adapted from https://github.com/fastify/secure-json-parse/blob/783fcb1b5434709466759847cec974381939673a/index.js
1971//
1972// Copyright (c) Effectful Technologies, Inc (https://effectful.co)
1973// Copyright (c) 2019 The Fastify Team
1974// Copyright (c) 2019, Sideway Inc, and project contributors
1975// All rights reserved.
1976//
1977// The complete list of contributors can be found at:
1978// - https://github.com/hapijs/bourne/graphs/contributors
1979// - https://github.com/fastify/secure-json-parse/graphs/contributors
1980// - https://github.com/Effect-TS/effect/commits/main/packages/ai/ai/src/Tool.ts
1981//
1982// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1983//
1984// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
1985//
1986// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
1987//
1988// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
1989//
1990// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1991
1992const suspectProtoRx = /"__proto__"\s*:/
1993const suspectConstructorRx = /"constructor"\s*:/
1994
1995function _parse(text: string) {
1996 // Parse normally
1997 const obj = JSON.parse(text)
1998
1999 // Ignore null and non-objects
2000 if (obj === null || typeof obj !== "object") {
2001 return obj
2002 }
2003
2004 if (
2005 suspectProtoRx.test(text) === false &&
2006 suspectConstructorRx.test(text) === false
2007 ) {
2008 return obj
2009 }
2010
2011 // Scan result for proto keys
2012 return filter(obj)
2013}
2014
2015function filter(obj: any) {
2016 let next = [obj]
2017
2018 while (next.length) {
2019 const nodes = next
2020 next = []
2021
2022 for (const node of nodes) {
2023 if (Object.hasOwn(node, "__proto__")) {
2024 throw new SyntaxError("Object contains forbidden prototype property")
2025 }
2026
2027 if (
2028 Object.hasOwn(node, "constructor") &&
2029 Object.hasOwn(node.constructor, "prototype")
2030 ) {
2031 throw new SyntaxError("Object contains forbidden prototype property")
2032 }
2033
2034 for (const key in node) {
2035 const value = node[key]
2036 if (value && typeof value === "object") {
2037 next.push(value)
2038 }
2039 }
2040 }
2041 }
2042 return obj
2043}
2044
2045/**
2046 * Parses JSON text while rejecting prototype-pollution keys.
2047 *
2048 * **When to use**
2049 *
2050 * Use when you need a JSON parser that throws for invalid JSON or unsafe
2051 * object shapes.
2052 *
2053 * **Gotchas**
2054 *
2055 * Invalid JSON throws through `JSON.parse`. Parsed objects containing an own
2056 * `__proto__` property or a dangerous `constructor.prototype` shape throw a
2057 * `SyntaxError`.
2058 *
2059 * @stability unstable
2060 * @category unsafe
2061 * @since 4.0.0
2062 */
2063export const unsafeSecureJsonParse = (text: string): unknown => {
2064 // Performance optimization, see https://github.com/fastify/secure-json-parse/pull/90
2065 const prevLimit = StackTraceLimit.getStackTraceLimit()
2066 StackTraceLimit.setStackTraceLimit(0)
2067 try {
2068 return _parse(text)
2069 } finally {
2070 StackTraceLimit.setStackTraceLimit(prevLimit)
2071 }
2072}
2073
2074/**
2075 * Schema for denied or interrupted tool calls.
2076 *
2077 * @stability unstable
2078 * @category schemas
2079 * @since 4.0.0
2080 */
2081export const ExecutionFailure = Schema.Struct({
2082 type: Schema.Literals(["execution-denied", "execution-interrupted"]),
2083 reason: Schema.String
2084}).annotate({ identifier: "ToolExecutionFailure" })
2085
2086/**
2087 * Returns the failure schema shared by `Toolkit` and `Response`.
2088 *
2089 * **Details**
2090 *
2091 * `AiError` comes first to restore error instances. The user schema precedes
2092 * {@link ExecutionFailure} to preserve user fields.
2093 *
2094 * @stability unstable
2095 * @category schemas
2096 * @since 4.0.0
2097 */
2098export const failureResultSchema = <T extends Any>(
2099 tool: T
2100): Schema.Union<readonly [typeof AiError.AiError, T["failureSchema"], typeof ExecutionFailure]> =>
2101 Schema.Union([AiError.AiError, tool.failureSchema, ExecutionFailure])
2102
2103/**
2104 * Type of the `EmptyParams` schema used for tools with no parameters.
2105 *
2106 * **Details**
2107 *
2108 * It is a record schema with string keys and `never` values, so the generated
2109 * parameter schema accepts an empty object shape with no properties.
2110 *
2111 * @stability unstable
2112 * @category schemas
2113 * @since 4.0.0
2114 */
2115export interface EmptyParams extends Schema.$Record<Schema.String, Schema.Never> {}
2116
2117/**
2118 * Schema for tools that accept no parameters.
2119 *
2120 * **When to use**
2121 *
2122 * Use when you need an explicit no-parameter `parameters` schema for a tool.
2123 *
2124 * **Details**
2125 *
2126 * This is `Schema.Record(Schema.String, Schema.Never)`, representing an empty
2127 * object parameter shape with no additional properties.
2128 *
2129 * @see {@link make} for the tool constructor that defaults omitted parameters to this schema
2130 *
2131 * @stability unstable
2132 * @category schemas
2133 * @since 4.0.0
2134 */
2135export const EmptyParams: EmptyParams = Schema.Record(Schema.String, Schema.Never)
2136
2137/**
2138 * @internal
2139 */
2140export function isEmptyParamsRecord(indexSignature: SchemaAST.IndexSignature): boolean {
2141 return indexSignature.parameter === SchemaAST.string && SchemaAST.isNever(indexSignature.type)
2142}