opencode-effect-enforcer 0.2.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.
Files changed (118) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +278 -0
  3. package/guidance/effect-first-development.md +1247 -0
  4. package/guidance/post__effect-and-the-near-inexpressible-majesty-of-layers.md +490 -0
  5. package/guidance/post__parse-dont-validate.md +109 -0
  6. package/guidance/progressive-disclosure-guidance.md +38 -0
  7. package/package.json +63 -0
  8. package/patterns/avoid-any.md +37 -0
  9. package/patterns/avoid-data-tagged-error.md +34 -0
  10. package/patterns/avoid-direct-json.md +51 -0
  11. package/patterns/avoid-direct-tag-checks.md +54 -0
  12. package/patterns/avoid-expect-in-if.md +52 -0
  13. package/patterns/avoid-mutable-state.md +70 -0
  14. package/patterns/avoid-native-fetch.md +61 -0
  15. package/patterns/avoid-node-imports.md +86 -0
  16. package/patterns/avoid-non-null-assertion.md +44 -0
  17. package/patterns/avoid-object-type.md +46 -0
  18. package/patterns/avoid-option-getorthrow.md +39 -0
  19. package/patterns/avoid-platform-coupling.md +43 -0
  20. package/patterns/avoid-process-env.md +43 -0
  21. package/patterns/avoid-react-hooks.md +73 -0
  22. package/patterns/avoid-schema-suffix.md +45 -0
  23. package/patterns/avoid-sync-fs.md +68 -0
  24. package/patterns/avoid-try-catch.md +47 -0
  25. package/patterns/avoid-ts-ignore.md +38 -0
  26. package/patterns/avoid-untagged-errors.md +67 -0
  27. package/patterns/avoid-yield-ref.md +46 -0
  28. package/patterns/casting-awareness.md +46 -0
  29. package/patterns/context-tag-extends.md +84 -0
  30. package/patterns/effect-catchall-default.md +61 -0
  31. package/patterns/effect-promise-vs-trypromise.md +47 -0
  32. package/patterns/effect-run-in-body.md +58 -0
  33. package/patterns/imperative-loops.md +76 -0
  34. package/patterns/prefer-arr-sort.md +52 -0
  35. package/patterns/prefer-duration-values.md +56 -0
  36. package/patterns/prefer-effect-fn.md +161 -0
  37. package/patterns/prefer-match-over-switch.md +48 -0
  38. package/patterns/prefer-option-over-null.md +56 -0
  39. package/patterns/prefer-redacted-config.md +70 -0
  40. package/patterns/prefer-schema-class.md +54 -0
  41. package/patterns/require-effect-concurrency.md +83 -0
  42. package/patterns/stream-large-files.md +63 -0
  43. package/patterns/throw-in-effect-gen.md +62 -0
  44. package/patterns/use-clock-service.md +45 -0
  45. package/patterns/use-command-executor-service.md +54 -0
  46. package/patterns/use-console-service.md +54 -0
  47. package/patterns/use-filesystem-service.md +59 -0
  48. package/patterns/use-http-client-service.md +77 -0
  49. package/patterns/use-path-service.md +53 -0
  50. package/patterns/use-random-service.md +45 -0
  51. package/patterns/use-temp-file-scoped.md +66 -0
  52. package/patterns/vm-in-wrong-file.md +51 -0
  53. package/patterns/yield-in-for-loop.md +61 -0
  54. package/skills/effect-ai-chat/SKILL.md +472 -0
  55. package/skills/effect-ai-language-model/SKILL.md +652 -0
  56. package/skills/effect-ai-prompt/SKILL.md +752 -0
  57. package/skills/effect-ai-provider/SKILL.md +668 -0
  58. package/skills/effect-ai-streaming/SKILL.md +418 -0
  59. package/skills/effect-ai-tool/SKILL.md +1132 -0
  60. package/skills/effect-atom-rpc/SKILL.md +488 -0
  61. package/skills/effect-atom-state/SKILL.md +640 -0
  62. package/skills/effect-batching/SKILL.md +614 -0
  63. package/skills/effect-cache/SKILL.md +570 -0
  64. package/skills/effect-cli/SKILL.md +523 -0
  65. package/skills/effect-command-executor/SKILL.md +675 -0
  66. package/skills/effect-concurrency-testing/SKILL.md +612 -0
  67. package/skills/effect-config/SKILL.md +580 -0
  68. package/skills/effect-context-witness/SKILL.md +274 -0
  69. package/skills/effect-domain-modeling/SKILL.md +1212 -0
  70. package/skills/effect-domain-predicates/SKILL.md +867 -0
  71. package/skills/effect-error-handling/SKILL.md +1581 -0
  72. package/skills/effect-fiber/SKILL.md +731 -0
  73. package/skills/effect-filesystem/SKILL.md +624 -0
  74. package/skills/effect-graph/SKILL.md +571 -0
  75. package/skills/effect-http-api/SKILL.md +1760 -0
  76. package/skills/effect-http-client/SKILL.md +989 -0
  77. package/skills/effect-http-server/SKILL.md +920 -0
  78. package/skills/effect-incremental-migration/SKILL.md +362 -0
  79. package/skills/effect-layer-design/SKILL.md +642 -0
  80. package/skills/effect-managed-runtime/SKILL.md +395 -0
  81. package/skills/effect-mcp-server/SKILL.md +608 -0
  82. package/skills/effect-observability/SKILL.md +719 -0
  83. package/skills/effect-optics/SKILL.md +554 -0
  84. package/skills/effect-parallelization/SKILL.md +668 -0
  85. package/skills/effect-path/SKILL.md +296 -0
  86. package/skills/effect-pattern-matching/SKILL.md +914 -0
  87. package/skills/effect-platform-abstraction/SKILL.md +1175 -0
  88. package/skills/effect-platform-layers/SKILL.md +514 -0
  89. package/skills/effect-pubsub-event-bus/SKILL.md +384 -0
  90. package/skills/effect-react-composition/SKILL.md +986 -0
  91. package/skills/effect-react-vm/SKILL.md +675 -0
  92. package/skills/effect-rpc-api/SKILL.md +624 -0
  93. package/skills/effect-rpc-client/SKILL.md +666 -0
  94. package/skills/effect-rpc-cluster/SKILL.md +1623 -0
  95. package/skills/effect-rpc-server/SKILL.md +767 -0
  96. package/skills/effect-scheduling/SKILL.md +124 -0
  97. package/skills/effect-schema-composition/SKILL.md +975 -0
  98. package/skills/effect-schema-v4/SKILL.md +691 -0
  99. package/skills/effect-scope/SKILL.md +682 -0
  100. package/skills/effect-service-implementation/SKILL.md +656 -0
  101. package/skills/effect-socket/SKILL.md +703 -0
  102. package/skills/effect-sql/SKILL.md +781 -0
  103. package/skills/effect-stream/SKILL.md +765 -0
  104. package/skills/effect-testing/SKILL.md +1331 -0
  105. package/skills/effect-typeclass-design/SKILL.md +161 -0
  106. package/skills/effect-wide-events/Article.md +66 -0
  107. package/skills/effect-wide-events/SKILL.md +95 -0
  108. package/skills/effect-workflow/SKILL.md +810 -0
  109. package/src/agent-policy.ts +22 -0
  110. package/src/enforcer.ts +104 -0
  111. package/src/frontmatter.ts +34 -0
  112. package/src/guidance.ts +66 -0
  113. package/src/index.ts +38 -0
  114. package/src/pattern-catalog.ts +115 -0
  115. package/src/pattern-matcher.ts +178 -0
  116. package/src/pattern.ts +97 -0
  117. package/src/skills.ts +29 -0
  118. package/src/write-projection.ts +66 -0
@@ -0,0 +1,1132 @@
1
+ ---
2
+ name: effect-ai-tool
3
+ description: Define and implement AI tools using Effect AI's Tool and Toolkit APIs. Use when building LLM integrations with type-safe tool definitions, parameter validation, and handler implementations. Covers user-defined tools, provider-defined tools, and toolkit composition.
4
+ ---
5
+
6
+ # Effect AI Tool Skill
7
+
8
+ Use this skill when implementing tools for AI language models using the Effect AI library. This covers tool definition, parameter schemas, success/failure handling, and toolkit composition.
9
+
10
+ ## Effect AI Documentation Access
11
+
12
+ For comprehensive Effect AI documentation, view the Effect v4 repository at `packages/ai/`
13
+
14
+ Reference this for:
15
+
16
+ - Tool.make, Tool.dynamic, and Tool.providerDefined APIs
17
+ - Toolkit.make for composing multiple tools
18
+ - Schema.Struct(...) for Tool.make parameters
19
+ - Handler implementation patterns
20
+ - OpenAI provider-defined tools via `@effect/ai-openai/OpenAiTool`
21
+
22
+ ## Core Concepts
23
+
24
+ ### Tool Anatomy
25
+
26
+ ```typescript
27
+ Effect<A, E, R>
28
+ ↓
29
+ Tool<Name, Config, Requirements>
30
+
31
+ Config := {
32
+ parameters: Schema.Struct<Fields>
33
+ success: Schema<A>
34
+ failure: Schema<E>
35
+ failureMode: "error" | "return"
36
+ }
37
+ ```
38
+
39
+ ### Toolkit Flow
40
+
41
+ ```typescript
42
+ Tool₁, Tool₂, Tool₃
43
+ ↓ Toolkit.make
44
+ Toolkit<{tool1: Tool₁, tool2: Tool₂, tool3: Tool₃}>
45
+ ↓ .toLayer(handlers)
46
+ Layer<Handlers>
47
+ ↓ Effect.provide
48
+ Effect with tool execution capability
49
+ ```
50
+
51
+ ## Production Harness Pattern: Effectful Local Tool Wrappers
52
+
53
+ `Tool.make` is the core user-defined Effect AI tool API. Effect AI also has `Tool.dynamic` for runtime-discovered tools and `Tool.providerDefined` for native provider capabilities. In larger coding-agent harnesses, it is common to wrap local tool definitions in an effectful layer so it can capture services once and keep a single `Effect.runPromise` bridge at the outer async framework boundary.
54
+
55
+ This wrapper is local to your harness, not part of Effect AI itself.
56
+
57
+ ```typescript
58
+ import { Effect } from 'effect';
59
+
60
+ export const ReadTool = defineEffect(
61
+ 'read',
62
+ Effect.gen(function* () {
63
+ const fs = yield* AppFileSystem.Service;
64
+
65
+ const run = Effect.fn('ReadTool.execute')(function* (params: Params) {
66
+ return yield* fs.readFileString(params.path);
67
+ });
68
+
69
+ return {
70
+ description: 'Read a file',
71
+ async execute(params: Params) {
72
+ return Effect.runPromise(run(params));
73
+ }
74
+ };
75
+ })
76
+ );
77
+ ```
78
+
79
+ Use this pattern when the surrounding framework wants an async callback surface but your real implementation should stay in Effect.
80
+
81
+ ## Creating User-Defined Tools
82
+
83
+ ### Basic Tool Definition
84
+
85
+ ```typescript
86
+ import * as Tool from 'effect/unstable/ai/Tool';
87
+ import * as Schema from 'effect/Schema';
88
+
89
+ const GetCurrentTime = Tool.make('GetCurrentTime', {
90
+ description: 'Returns the current timestamp in milliseconds',
91
+ success: Schema.Number
92
+ });
93
+
94
+ const result = Tool.Success<typeof GetCurrentTime>;
95
+ ```
96
+
97
+ **Key Pattern: Tool.make**
98
+
99
+ - First parameter: tool name (string literal)
100
+ - Second parameter: configuration object
101
+ - No parameters field = empty parameters `{}`
102
+ - Default success: `Schema.Void`
103
+ - Default failure: `Schema.Never`
104
+
105
+ ### Tool from Domain Schema
106
+
107
+ If you have an existing domain schema you want to use as a tool, create the tool with `Tool.make` and reference the schema directly:
108
+
109
+ ```typescript
110
+ import * as Tool from 'effect/unstable/ai/Tool';
111
+ import * as Schema from 'effect/Schema';
112
+
113
+ const UserResult = Schema.Struct({
114
+ id: Schema.String,
115
+ name: Schema.String
116
+ });
117
+
118
+ const GetUserTool = Tool.make('GetUser', {
119
+ description: 'Retrieve user information by ID',
120
+ parameters: Schema.Struct({
121
+ userId: Schema.String
122
+ }),
123
+ success: UserResult
124
+ });
125
+
126
+ type Params = Tool.Parameters<typeof GetUserTool>;
127
+ type ParamsEncoded = Tool.ParametersEncoded<typeof GetUserTool>;
128
+ type Success = Tool.Success<typeof GetUserTool>;
129
+ ```
130
+
131
+ **Key Pattern: Tool.make with domain schemas**
132
+
133
+ - Use `Tool.make` for user-defined tools
134
+ - Reference existing domain schemas in `parameters`, `success`, and `failure` fields
135
+ - Tool name is the first argument (string literal)
136
+ - Use `Tool.dynamic` for runtime-discovered tools and `Tool.providerDefined` / `OpenAiTool` for provider-native tools
137
+ - There is no `Tool.fromTaggedRequest` in v4
138
+
139
+ ### Tool with Parameters
140
+
141
+ ```typescript
142
+ import * as Tool from 'effect/unstable/ai/Tool';
143
+ import { Schema } from 'effect';
144
+
145
+ const GetWeather = Tool.make('GetWeather', {
146
+ description: 'Get weather information for a location',
147
+ parameters: Schema.Struct({
148
+ location: Schema.String,
149
+ units: Schema.optional(Schema.Literals(['celsius', 'fahrenheit']))
150
+ }),
151
+ success: Schema.Struct({
152
+ temperature: Schema.Number,
153
+ condition: Schema.String,
154
+ humidity: Schema.Number
155
+ })
156
+ });
157
+
158
+ type Params = Tool.Parameters<typeof GetWeather>;
159
+ type Success = Tool.Success<typeof GetWeather>;
160
+ ```
161
+
162
+ **Key Pattern: parameters**
163
+
164
+ - `Tool.make` parameters must be an Effect `Schema.Top`
165
+ - Wrap field records with `Schema.Struct({...})`; `Tool.make` does not wrap raw field objects automatically
166
+ - `Tool.dynamic` can use either typed Effect schemas or raw JSON Schema discovered at runtime
167
+ - Use `Schema.optional()` for optional parameters
168
+
169
+ ### Tool with Failure Handling
170
+
171
+ ```typescript
172
+ import * as Tool from 'effect/unstable/ai/Tool';
173
+ import { Schema } from 'effect';
174
+
175
+ class UserNotFound extends Schema.TaggedError<UserNotFound>()(
176
+ 'UserNotFound',
177
+ {
178
+ userId: Schema.String
179
+ }
180
+ ) {}
181
+
182
+ class DatabaseError extends Schema.TaggedError<DatabaseError>()(
183
+ 'DatabaseError',
184
+ {
185
+ message: Schema.String
186
+ }
187
+ ) {}
188
+
189
+ const FindUser = Tool.make('FindUser', {
190
+ description: 'Find user by ID',
191
+ parameters: Schema.Struct({
192
+ userId: Schema.String
193
+ }),
194
+ success: Schema.Struct({
195
+ id: Schema.String,
196
+ name: Schema.String,
197
+ email: Schema.String
198
+ }),
199
+ failure: Schema.Union([
200
+ Schema.instanceOf(UserNotFound),
201
+ Schema.instanceOf(DatabaseError)
202
+ ]),
203
+ failureMode: 'error'
204
+ });
205
+
206
+ type Error = Tool.Failure<typeof FindUser>;
207
+ ```
208
+
209
+ **Key Pattern: failureMode**
210
+
211
+ - `"error"` (default): Failures go to Effect error channel
212
+ - `"return"`: Failures returned as tool result (captured, not thrown)
213
+
214
+ ### Tool with Service Dependencies
215
+
216
+ ```typescript
217
+ import * as Tool from 'effect/unstable/ai/Tool';
218
+ import * as Context from 'effect/Context';
219
+ import { Schema } from 'effect';
220
+
221
+ class Database extends Context.Service<
222
+ Database,
223
+ {
224
+ readonly query: (sql: string) => Effect.Effect<unknown>;
225
+ }
226
+ >()('Database') {}
227
+
228
+ const QueryDatabase = Tool.make('QueryDatabase', {
229
+ description: 'Execute a database query',
230
+ parameters: Schema.Struct({
231
+ sql: Schema.String
232
+ }),
233
+ success: Schema.Unknown,
234
+ dependencies: [Database]
235
+ });
236
+
237
+ type Requirements = Tool.Requirements<typeof QueryDatabase>;
238
+ ```
239
+
240
+ **Key Pattern: dependencies**
241
+
242
+ - Array of service tags
243
+ - Requirements extracted at type level
244
+ - Must be provided when creating handlers
245
+
246
+ ## Creating Toolkits
247
+
248
+ ### Basic Toolkit
249
+
250
+ ```typescript
251
+ import * as Toolkit from 'effect/unstable/ai/Toolkit';
252
+ import * as Tool from 'effect/unstable/ai/Tool';
253
+ import { Effect, Schema } from 'effect';
254
+
255
+ const GetCurrentTime = Tool.make('GetCurrentTime', {
256
+ description: 'Get the current timestamp',
257
+ success: Schema.Number
258
+ });
259
+
260
+ const GetWeather = Tool.make('GetWeather', {
261
+ description: 'Get weather for a location',
262
+ parameters: Schema.Struct({
263
+ location: Schema.String
264
+ }),
265
+ success: Schema.Struct({
266
+ temperature: Schema.Number,
267
+ condition: Schema.String
268
+ })
269
+ });
270
+
271
+ const MyToolkit = Toolkit.make(GetCurrentTime, GetWeather);
272
+
273
+ type Tools = Toolkit.Tools<typeof MyToolkit>;
274
+ ```
275
+
276
+ **Key Pattern: Toolkit.make**
277
+
278
+ - Accepts variadic tool arguments
279
+ - Returns `Toolkit<Record<Name, Tool>>`
280
+ - Tools indexed by their name property
281
+
282
+ ### Implementing Tool Handlers
283
+
284
+ ```typescript
285
+ import { Effect } from 'effect';
286
+
287
+ const MyToolkitLayer = MyToolkit.toLayer({
288
+ GetCurrentTime: () => Effect.succeed(Date.now()),
289
+
290
+ GetWeather: ({ location }) =>
291
+ Effect.gen(function* () {
292
+ const data = yield* fetchWeatherData(location);
293
+ return {
294
+ temperature: data.temp,
295
+ condition: data.conditions
296
+ };
297
+ })
298
+ });
299
+
300
+ declare const fetchWeatherData: (location: string) => Effect.Effect<{
301
+ readonly temp: number;
302
+ readonly conditions: string;
303
+ }>;
304
+ ```
305
+
306
+ **Key Pattern: toLayer**
307
+
308
+ - Object mapping tool names to handler functions
309
+ - Handler signature: `(params, context) => Effect<Success, Failure, Requirements>`; `context.toolCallId` exposes the provider call ID when available, and `context.preliminary(...)` emits progress updates
310
+ - Handler parameters are decoded `Tool.Parameters<T>` values
311
+ - Returns `Layer<Handlers>`
312
+
313
+ ### Alternative: Handlers as Context
314
+
315
+ ```typescript
316
+ import { Effect } from 'effect';
317
+
318
+ const program = Effect.gen(function* () {
319
+ const handlers = yield* MyToolkit.toHandlers({
320
+ GetCurrentTime: () => Effect.succeed(Date.now()),
321
+
322
+ GetWeather: ({ location }) =>
323
+ Effect.gen(function* () {
324
+ const data = yield* fetchWeatherData(location);
325
+ return {
326
+ temperature: data.temp,
327
+ condition: data.conditions
328
+ };
329
+ })
330
+ });
331
+
332
+ const result = yield* Effect.provide(myEffect, handlers);
333
+ return result;
334
+ });
335
+
336
+ declare const fetchWeatherData: (location: string) => Effect.Effect<{
337
+ readonly temp: number;
338
+ readonly conditions: string;
339
+ }>;
340
+
341
+ declare const myEffect: Effect.Effect<unknown, never, Handlers>;
342
+ ```
343
+
344
+ **Key Pattern: toHandlers**
345
+
346
+ - Similar to toLayer but returns a `Context` instead of Layer
347
+ - Use when you need direct handler context (not Layer composition)
348
+ - Returns `Effect<Context<Handlers>>`
349
+ - Provide directly to effects that require handlers
350
+
351
+ ### Providing Dependencies to Handlers
352
+
353
+ ```typescript
354
+ import * as Context from 'effect/Context';
355
+ import { Effect } from 'effect';
356
+
357
+ interface WeatherData {
358
+ readonly temperature: number;
359
+ readonly condition: string;
360
+ }
361
+
362
+ class WeatherService extends Context.Service<
363
+ WeatherService,
364
+ {
365
+ readonly fetch: (location: string) => Effect.Effect<WeatherData>;
366
+ }
367
+ >()('WeatherService') {}
368
+
369
+ const GetWeatherWithDeps = Tool.make('GetWeather', {
370
+ parameters: Schema.Struct({
371
+ location: Schema.String
372
+ }),
373
+ success: Schema.Struct({
374
+ temperature: Schema.Number,
375
+ condition: Schema.String
376
+ }),
377
+ dependencies: [WeatherService]
378
+ });
379
+
380
+ const toolkit = Toolkit.make(GetWeatherWithDeps);
381
+
382
+ const toolkitLayer = toolkit.toLayer({
383
+ GetWeather: ({ location }) =>
384
+ Effect.gen(function* () {
385
+ const service = yield* WeatherService;
386
+ const data = yield* service.fetch(location);
387
+ return {
388
+ temperature: data.temperature,
389
+ condition: data.condition
390
+ };
391
+ })
392
+ });
393
+
394
+ const program = Effect.gen(function* () {
395
+ const handlers = yield* toolkitLayer;
396
+ const resultStream = yield* handlers.handle('GetWeather', { location: 'NYC' });
397
+ return resultStream;
398
+ }).pipe(Effect.provide(WeatherServiceLive));
399
+
400
+ declare const WeatherServiceLive: Layer<WeatherService>;
401
+ ```
402
+
403
+ **Key Pattern: Handler Context**
404
+
405
+ - Handlers run with injected dependencies
406
+ - Access via `yield* Tag` in Effect.gen
407
+ - Dependencies must be provided to final effect
408
+
409
+ ## Dynamic Tools
410
+
411
+ Use `Tool.dynamic` when tool schemas are discovered at runtime, such as from MCP or external configuration. If `parameters` is an Effect `Schema`, handlers receive typed decoded params; if `parameters` is raw JSON Schema, handler params are `unknown`.
412
+
413
+ ```typescript
414
+ const TypedDynamic = Tool.dynamic('runtimeSearch', {
415
+ description: 'Search with a runtime-provided typed schema',
416
+ parameters: Schema.Struct({ query: Schema.String }),
417
+ success: Schema.Array(Schema.String)
418
+ });
419
+
420
+ type TypedParams = Tool.Parameters<typeof TypedDynamic>; // { query: string }
421
+
422
+ const UntypedDynamic = Tool.dynamic('mcpTool', {
423
+ description: 'Runtime MCP tool with raw JSON Schema',
424
+ parameters: {
425
+ type: 'object',
426
+ properties: { query: { type: 'string' } },
427
+ required: ['query']
428
+ }
429
+ });
430
+
431
+ type UntypedParams = Tool.Parameters<typeof UntypedDynamic>; // unknown
432
+ ```
433
+
434
+ ## Registry-Owned Tool Resolution
435
+
436
+ In production agent harnesses, resolve tools through a registry service instead of scattering direct imports across prompt/session code.
437
+
438
+ The registry is the right place to:
439
+
440
+ - initialize effect-built tools once
441
+ - compute dynamic descriptions from permissions, config, or available subagents
442
+ - expose stable named handles for orchestration and tests
443
+
444
+ ```typescript
445
+ export namespace ToolRegistry {
446
+ export interface Interface {
447
+ readonly named: {
448
+ task: LocalToolInfo;
449
+ read: LocalToolInfo;
450
+ };
451
+ }
452
+ }
453
+
454
+ const registry = yield* ToolRegistry.Service;
455
+ const task = yield* Effect.promise(() => registry.named.task.init());
456
+ ```
457
+
458
+ Keep static descriptions on the tool for invariant behavior. Put runtime-specific descriptions and policy shaping in the registry layer.
459
+
460
+ ### Merging Toolkits
461
+
462
+ ```typescript
463
+ import * as Toolkit from 'effect/unstable/ai/Toolkit';
464
+
465
+ const mathToolkit = Toolkit.make(
466
+ Tool.make('add', {
467
+ parameters: Schema.Struct({ a: Schema.Number, b: Schema.Number }),
468
+ success: Schema.Number
469
+ }),
470
+ Tool.make('subtract', {
471
+ parameters: Schema.Struct({ a: Schema.Number, b: Schema.Number }),
472
+ success: Schema.Number
473
+ })
474
+ );
475
+
476
+ const utilityToolkit = Toolkit.make(
477
+ Tool.make('getCurrentTime', { success: Schema.Number }),
478
+ Tool.make('generateUUID', { success: Schema.String })
479
+ );
480
+
481
+ const combined = Toolkit.merge(mathToolkit, utilityToolkit);
482
+
483
+ type AllTools = Toolkit.Tools<typeof combined>;
484
+ ```
485
+
486
+ **Key Pattern: Toolkit.merge**
487
+
488
+ - Combines multiple toolkits into one
489
+ - Later toolkits override earlier ones on name collision
490
+ - Type-safe union of all tools
491
+ - **Note**: In v4, the preferred pattern is `Toolkit.make(tool1, tool2, ...)` — passing all tools to the constructor rather than merging separate toolkits. Use `Toolkit.make` with all tools when possible.
492
+
493
+ ## Provider-Defined Tools
494
+
495
+ `Tool.providerDefined` models provider-native capabilities. For OpenAI, prefer the curated `OpenAiTool` constructors from `@effect/ai-openai` (`WebSearch`, `CodeInterpreter`, `FileSearch`, `ImageGeneration`, `Mcp`, plus handler-required local tools such as `Shell`, `LocalShell`, and `ApplyPatch`).
496
+
497
+ ### Basic Provider Tool
498
+
499
+ ```typescript
500
+ import * as Tool from 'effect/unstable/ai/Tool';
501
+ import { Schema } from 'effect';
502
+
503
+ const AnthropicBash = Tool.providerDefined({
504
+ id: 'anthropic.bash',
505
+ customName: 'Bash',
506
+ providerName: 'bash_20241022',
507
+ args: Schema.Struct({
508
+ command: Schema.String
509
+ })
510
+ });
511
+
512
+ const bashTool = AnthropicBash({ command: 'ls -la' });
513
+
514
+ type ToolType = typeof bashTool;
515
+ ```
516
+
517
+ **Key Pattern: Tool.providerDefined**
518
+
519
+ - Returns a function that accepts args
520
+ - `id`: Unique identifier `<provider>.<tool-name>`
521
+ - `customName`: Name used by Effect AI / Toolkit to identify this tool
522
+ - `providerName`: Name recognized by the AI provider
523
+ - `args`: Schema for provider-specific configuration arguments
524
+ - `requiresHandler`: Set to `true` only when your application must execute/process provider tool calls locally
525
+
526
+ ### OpenAI Provider Tools
527
+
528
+ ```typescript
529
+ import { OpenAiTool } from '@effect/ai-openai';
530
+
531
+ const nativeTools = Toolkit.make(
532
+ OpenAiTool.WebSearch({}),
533
+ OpenAiTool.FileSearch({ vector_store_ids: ['vs_123'] }),
534
+ OpenAiTool.ImageGeneration({}),
535
+ OpenAiTool.Mcp({
536
+ server_label: 'docs',
537
+ server_url: 'https://mcp.example.com/mcp'
538
+ })
539
+ );
540
+ ```
541
+
542
+ `OpenAiTool.Mcp` uses the canonical custom name `OpenAiMcp`. Treat handler-required local OpenAI tools (`Shell`, `LocalShell`, `ApplyPatch`) as privileged operations: provide handlers only behind sandboxing, authorization, and audit policy.
543
+
544
+ ### Provider Tool with Handler
545
+
546
+ ```typescript
547
+ import * as Tool from 'effect/unstable/ai/Tool';
548
+ import { Schema } from 'effect';
549
+
550
+ const WebSearch = Tool.providerDefined({
551
+ id: 'custom.web_search',
552
+ customName: 'WebSearch',
553
+ providerName: 'web_search',
554
+ args: Schema.Struct({
555
+ maxResults: Schema.Number
556
+ }),
557
+ requiresHandler: true,
558
+ parameters: Schema.Struct({
559
+ query: Schema.String
560
+ }),
561
+ success: Schema.Struct({
562
+ results: Schema.Array(
563
+ Schema.Struct({
564
+ title: Schema.String,
565
+ url: Schema.String,
566
+ snippet: Schema.String
567
+ })
568
+ )
569
+ })
570
+ });
571
+
572
+ const searchTool = WebSearch({ maxResults: 10, failureMode: 'return' });
573
+
574
+ const toolkit = Toolkit.make(searchTool);
575
+
576
+ const toolkitLayer = toolkit.toLayer({
577
+ WebSearch: ({ query }) =>
578
+ Effect.gen(function* () {
579
+ const results = yield* performSearch(query);
580
+ return { results };
581
+ })
582
+ });
583
+
584
+ declare const performSearch: (query: string) => Effect.Effect<
585
+ Array<{
586
+ readonly title: string;
587
+ readonly url: string;
588
+ readonly snippet: string;
589
+ }>
590
+ >;
591
+ ```
592
+
593
+ **Key Pattern: requiresHandler**
594
+
595
+ - `false` (default): Provider executes tool completely
596
+ - `true`: Your handler processes provider results
597
+ - Handler receives `parameters` from provider
598
+
599
+ ## Tool Result Flow
600
+
601
+ ### Understanding ToolCallPart and ToolResultPart
602
+
603
+ ```typescript
604
+ import * as Prompt from 'effect/unstable/ai/Prompt';
605
+
606
+ const toolCallPart = Prompt.makePart('tool-call', {
607
+ id: 'call_123',
608
+ name: 'GetWeather',
609
+ params: { location: 'NYC' },
610
+ providerExecuted: false
611
+ });
612
+
613
+ const toolResultPart = Prompt.makePart('tool-result', {
614
+ id: 'call_123',
615
+ name: 'GetWeather',
616
+ result: {
617
+ temperature: 72,
618
+ condition: 'sunny'
619
+ },
620
+ isFailure: false
621
+ });
622
+ ```
623
+
624
+ **Key Pattern: Tool Call Flow**
625
+
626
+ 1. LLM generates `ToolCallPart` in response
627
+ 2. Your code runs `yield* toolkit.handle(...)` to get a `Stream` of handler results
628
+ 3. Preliminary results can be emitted for progress; the final result is authoritative
629
+ 4. Create a `ToolResultPart` from the final handler result for manual loops (LanguageModel does this automatically when tool resolution is enabled)
630
+ 5. Send the tool result back to the LLM
631
+
632
+ ### Approval Flow
633
+
634
+ `Tool.make` and `Tool.dynamic` support `needsApproval`. When approval is needed, automatic tool resolution emits a `tool-approval-request` instead of executing the handler. The caller appends a `Prompt.toolApprovalResponsePart` in a tool message and calls the model again.
635
+
636
+ ```typescript
637
+ const DeleteFile = Tool.make('DeleteFile', {
638
+ parameters: Schema.Struct({ path: Schema.String }),
639
+ success: Schema.Void,
640
+ needsApproval: true
641
+ });
642
+
643
+ // Replace an existing tool's static or dynamic approval policy.
644
+ const DeleteFileWithoutApproval = DeleteFile.setNeedsApproval(false);
645
+
646
+ const DeleteOutsideWorkspace = DeleteFile.setNeedsApproval(
647
+ ({ path }, { toolCallId, messages }) =>
648
+ path.startsWith('/tmp/') ||
649
+ (toolCallId.length > 0 && messages.length === 0)
650
+ );
651
+
652
+ const approvalResponse = Prompt.toolApprovalResponsePart({
653
+ approvalId: 'approval_123',
654
+ approved: false,
655
+ reason: 'User denied deletion'
656
+ });
657
+ ```
658
+
659
+ Approved calls execute on the next model call; denied calls are converted to failed tool results with `{ type: 'execution-denied', reason }`.
660
+
661
+ `setNeedsApproval` returns a cloned tool with the replacement policy. A dynamic policy receives decoded parameters plus `{ toolCallId, messages }` and may return either `boolean` or `Effect<boolean>`.
662
+
663
+ ### Executing Tool Handlers
664
+
665
+ ```typescript
666
+ import { Effect, Stream } from 'effect';
667
+
668
+ const program = Effect.gen(function* () {
669
+ const toolkit = yield* MyToolkitLayer;
670
+
671
+ const resultStream = yield* toolkit.handle('GetWeather', {
672
+ location: 'San Francisco'
673
+ });
674
+
675
+ yield* resultStream.pipe(
676
+ Stream.runForEach((result) =>
677
+ Effect.gen(function* () {
678
+ yield* Effect.log(result.preliminary ? 'progress' : 'final');
679
+ yield* Effect.log(result.isFailure);
680
+ yield* Effect.log(result.result);
681
+ yield* Effect.log(result.encodedResult);
682
+ })
683
+ )
684
+ );
685
+ });
686
+
687
+ interface HandlerResult<T> {
688
+ readonly isFailure: boolean;
689
+ readonly result: Result<T>;
690
+ readonly encodedResult: unknown;
691
+ readonly preliminary: boolean;
692
+ }
693
+
694
+ type Result<T> = Tool.Success<T> | Tool.Failure<T>;
695
+ ```
696
+
697
+ Handlers can emit progress before the final result with `context.preliminary(...)`:
698
+
699
+ ```typescript
700
+ const toolkitLayer = LongRunningToolkit.toLayer({
701
+ LongTask: (params, context) =>
702
+ Effect.gen(function* () {
703
+ yield* Effect.logDebug('handling tool call').pipe(
704
+ Effect.annotateLogs({ toolCallId: context.toolCallId })
705
+ );
706
+ yield* context.preliminary({ status: 'started' });
707
+ const result = yield* runLongTask(params);
708
+ return { status: 'done', result };
709
+ })
710
+ });
711
+ ```
712
+
713
+ **Key Pattern: toolkit.handle**
714
+
715
+ - `toolkit.handle(name, params, toolCallId?)` accepts `Tool.ParametersEncoded<Tool>` and returns `Effect<Stream<HandlerResult<Tool>>>`
716
+ - `handle` decodes the encoded input with the tool's parameter schema before invoking the handler; the handler still receives `Tool.Parameters<Tool>`
717
+ - The optional call ID is forwarded to the handler as `context.toolCallId`
718
+ - `preliminary: true`: progress update; do not persist as final history
719
+ - `preliminary: false`: final result to send/persist
720
+ - `isFailure`: Whether handler failed
721
+ - `result`: Typed success or failure value
722
+ - `encodedResult`: JSON-serializable for LLM
723
+
724
+ The encoded/decoded distinction matters for transforming schemas:
725
+
726
+ ```typescript
727
+ const Repeat = Tool.make('Repeat', {
728
+ parameters: Schema.Struct({ times: Schema.NumberFromString }),
729
+ success: Schema.Number
730
+ });
731
+
732
+ const RepeatToolkit = Toolkit.make(Repeat);
733
+ const RepeatLive = RepeatToolkit.toLayer({
734
+ Repeat: ({ times }) => Effect.succeed(times + 1) // times is number
735
+ });
736
+
737
+ const program = Effect.gen(function* () {
738
+ const handlers = yield* RepeatToolkit;
739
+ return yield* handlers.handle('Repeat', { times: '3' }); // encoded input is string
740
+ }).pipe(Effect.provide(RepeatLive));
741
+ ```
742
+
743
+ Passing the decoded `{ times: 3 }` directly to `handle` is now a type error. Use the encoded boundary type and let `handle` perform the schema decode.
744
+
745
+ ## Advanced Patterns
746
+
747
+ ### Tool Annotations
748
+
749
+ ```typescript
750
+ import * as Tool from 'effect/unstable/ai/Tool';
751
+ import { Schema } from 'effect';
752
+
753
+ const ReadOnlyQuery = Tool.make('query', {
754
+ parameters: Schema.Struct({ sql: Schema.String }),
755
+ success: Schema.Unknown
756
+ })
757
+ .annotate(Tool.Readonly, true)
758
+ .annotate(Tool.Destructive, false)
759
+ .annotate(Tool.Idempotent, true);
760
+ ```
761
+
762
+ **Available Annotations and Defaults:**
763
+
764
+ - `Tool.Readonly`: Tool only reads data; default `false`
765
+ - `Tool.Destructive`: Tool performs destructive operations; default `true`
766
+ - `Tool.Idempotent`: Safe to call multiple times; default `false`
767
+ - `Tool.OpenWorld`: Can handle arbitrary external data; default `true`
768
+ - `Tool.Strict`: OpenAI strict JSON Schema override; default `undefined` so provider/config decides
769
+ - `Tool.Title`: Human-readable title
770
+
771
+ MCP emits the first four annotations as tool hints. They are hints, not authorization decisions. OpenAI function tools use `Tool.Strict` or `OpenAiLanguageModel.Config.strictJsonSchema` for strict mode.
772
+
773
+ ### JSON Schema Generation
774
+
775
+ ```typescript
776
+ import * as Tool from 'effect/unstable/ai/Tool';
777
+
778
+ const tool = Tool.make('example', {
779
+ parameters: Schema.Struct({
780
+ name: Schema.String,
781
+ age: Schema.optional(Schema.Number)
782
+ })
783
+ });
784
+
785
+ const jsonSchema = Tool.getJsonSchema(tool);
786
+ ```
787
+
788
+ **Output:**
789
+
790
+ ```json
791
+ {
792
+ "type": "object",
793
+ "properties": {
794
+ "name": { "type": "string" },
795
+ "age": { "type": "number" }
796
+ },
797
+ "required": ["name"],
798
+ "additionalProperties": false
799
+ }
800
+ ```
801
+
802
+ ### Tool Guards
803
+
804
+ ```typescript
805
+ import * as Tool from 'effect/unstable/ai/Tool';
806
+
807
+ const userTool = Tool.make('example');
808
+ const providerTool = Tool.providerDefined({
809
+ id: 'provider.tool',
810
+ customName: 'Example',
811
+ providerName: 'example',
812
+ args: Schema.Struct({})
813
+ })({});
814
+
815
+ Tool.isUserDefined(userTool);
816
+ Tool.isProviderDefined(providerTool);
817
+ ```
818
+
819
+ ### Dynamic Tool Selection
820
+
821
+ ```typescript
822
+ import { Effect, Match } from 'effect';
823
+
824
+ const executeTool = (toolName: string, params: unknown) =>
825
+ Effect.gen(function* () {
826
+ const toolkit = yield* MyToolkitLayer;
827
+
828
+ const handler = Match.value(toolName).pipe(
829
+ Match.when('GetWeather', () =>
830
+ toolkit.handle('GetWeather', params)
831
+ ),
832
+ Match.when('GetCurrentTime', () =>
833
+ toolkit.handle('GetCurrentTime', params)
834
+ ),
835
+ Match.orElse(() =>
836
+ Effect.fail(new Error(`Unknown tool: ${toolName}`))
837
+ )
838
+ );
839
+
840
+ return yield* handler;
841
+ });
842
+ ```
843
+
844
+ ## Complete Example
845
+
846
+ ```typescript
847
+ import * as Tool from 'effect/unstable/ai/Tool';
848
+ import * as Toolkit from 'effect/unstable/ai/Toolkit';
849
+ import { Effect, Schema, Layer, Stream } from 'effect';
850
+
851
+ class UserNotFound extends Schema.TaggedError<UserNotFound>()(
852
+ 'UserNotFound',
853
+ {
854
+ userId: Schema.String
855
+ }
856
+ ) {}
857
+
858
+ class Database extends Context.Service<
859
+ Database,
860
+ {
861
+ readonly query: (sql: string) => Effect.Effect<unknown>;
862
+ }
863
+ >()('Database') {}
864
+
865
+ const GetUser = Tool.make('GetUser', {
866
+ description: 'Retrieve user information by ID',
867
+ parameters: Schema.Struct({
868
+ userId: Schema.String
869
+ }),
870
+ success: Schema.Struct({
871
+ id: Schema.String,
872
+ name: Schema.String,
873
+ email: Schema.String
874
+ }),
875
+ failure: Schema.instanceOf(UserNotFound),
876
+ failureMode: 'error',
877
+ dependencies: [Database]
878
+ });
879
+
880
+ const CreateUser = Tool.make('CreateUser', {
881
+ description: 'Create a new user',
882
+ parameters: Schema.Struct({
883
+ name: Schema.String,
884
+ email: Schema.String
885
+ }),
886
+ success: Schema.Struct({
887
+ id: Schema.String,
888
+ name: Schema.String,
889
+ email: Schema.String
890
+ }),
891
+ dependencies: [Database]
892
+ });
893
+
894
+ const GetCurrentTime = Tool.make('GetCurrentTime', {
895
+ description: 'Get the current Unix timestamp',
896
+ success: Schema.Number
897
+ });
898
+
899
+ const UserToolkit = Toolkit.make(GetUser, CreateUser, GetCurrentTime);
900
+
901
+ const UserToolkitLive = UserToolkit.toLayer({
902
+ GetUser: ({ userId }) =>
903
+ Effect.gen(function* () {
904
+ const db = yield* Database;
905
+ const user = yield* db.query(
906
+ `SELECT * FROM users WHERE id = ?`,
907
+ userId
908
+ );
909
+
910
+ if (!user) {
911
+ return yield* Effect.fail(new UserNotFound({ userId }));
912
+ }
913
+
914
+ return user as { id: string; name: string; email: string };
915
+ }),
916
+
917
+ CreateUser: ({ name, email }) =>
918
+ Effect.gen(function* () {
919
+ const db = yield* Database;
920
+ const id = crypto.randomUUID();
921
+
922
+ yield* db.query(
923
+ `INSERT INTO users (id, name, email) VALUES (?, ?, ?)`,
924
+ id,
925
+ name,
926
+ email
927
+ );
928
+
929
+ return { id, name, email };
930
+ }),
931
+
932
+ GetCurrentTime: () => Effect.succeed(Date.now())
933
+ });
934
+
935
+ const DatabaseLive = Layer.succeed(Database, {
936
+ query: (sql: string, ...params: ReadonlyArray<unknown>) =>
937
+ Effect.logInfo(`Query: ${sql}`).pipe(Effect.as({}))
938
+ });
939
+
940
+ const program = Effect.gen(function* () {
941
+ const toolkit = yield* UserToolkitLive;
942
+
943
+ const createStream = yield* toolkit.handle('CreateUser', {
944
+ name: 'Alice',
945
+ email: 'alice@example.com'
946
+ });
947
+
948
+ yield* createStream.pipe(
949
+ Stream.runForEach((result) => Effect.log('Created user:', result.result))
950
+ );
951
+
952
+ const getStream = yield* toolkit.handle('GetUser', {
953
+ userId: 'user-123'
954
+ });
955
+
956
+ yield* getStream.pipe(
957
+ Stream.runForEach((result) => Effect.log('Retrieved user:', result.result))
958
+ );
959
+
960
+ const timeStream = yield* toolkit.handle('GetCurrentTime', {});
961
+
962
+ yield* timeStream.pipe(
963
+ Stream.runForEach((result) => Effect.log('Current time:', result.result))
964
+ );
965
+ }).pipe(Effect.provide(DatabaseLive));
966
+ ```
967
+
968
+ ## Import Patterns
969
+
970
+ **CRITICAL**: Always use namespace imports:
971
+
972
+ ```typescript
973
+ import * as Tool from 'effect/unstable/ai/Tool';
974
+ import * as Toolkit from 'effect/unstable/ai/Toolkit';
975
+ import * as Prompt from 'effect/unstable/ai/Prompt';
976
+ import { Schema, Effect, Data, Context, Layer } from 'effect';
977
+
978
+ const myTool = Tool.make('example');
979
+ const myToolkit = Toolkit.make(myTool);
980
+ ```
981
+
982
+ **NEVER** do this:
983
+
984
+ ```typescript
985
+ import { make } from 'effect/unstable/ai/Tool';
986
+ import { make as makeToolkit } from 'effect/unstable/ai/Toolkit';
987
+ ```
988
+
989
+ ## Quality Checklist
990
+
991
+ ### Mandatory - Every Tool
992
+
993
+ - [ ] Tool name is descriptive and unique
994
+ - [ ] Description explains what the tool does
995
+ - [ ] Parameters use `Schema.Struct(...)` or another `Schema.Top` schema (not raw field objects)
996
+ - [ ] Success schema matches handler return type
997
+ - [ ] Failure schema includes all tagged errors
998
+ - [ ] failureMode matches recovery strategy
999
+ - [ ] Dependencies declared if accessing services
1000
+ - [ ] Handler implements correct signature
1001
+ - [ ] Type signatures use Tool.Parameters, Tool.Success, Tool.Failure
1002
+
1003
+ ### Conditional - Include When Appropriate
1004
+
1005
+ - [ ] Tool.Readonly annotation for read-only tools
1006
+ - [ ] Tool.Destructive annotation for mutating operations
1007
+ - [ ] Tool.Idempotent annotation for safe retries
1008
+ - [ ] Custom annotations via Tool.annotate
1009
+ - [ ] Provider-defined tools for native provider features
1010
+ - [ ] Toolkit.make with all tools (preferred in v4) or Toolkit.merge for combining tool collections
1011
+ - [ ] Error handling with catchTag in handlers
1012
+
1013
+ ## Common Patterns
1014
+
1015
+ ### Validation in Handlers
1016
+
1017
+ ```typescript
1018
+ const ValidatedTool = Tool.make('validate', {
1019
+ parameters: Schema.Struct({
1020
+ input: Schema.String
1021
+ }),
1022
+ success: Schema.Struct({
1023
+ valid: Schema.Boolean,
1024
+ errors: Schema.Array(Schema.String)
1025
+ })
1026
+ });
1027
+
1028
+ const toolkit = Toolkit.make(ValidatedTool);
1029
+
1030
+ const toolkitLayer = toolkit.toLayer({
1031
+ validate: ({ input }) =>
1032
+ Effect.gen(function* () {
1033
+ const errors: Array<string> = [];
1034
+
1035
+ if (input.length < 3) {
1036
+ errors.push('Input too short');
1037
+ }
1038
+
1039
+ if (!/^[a-z]+$/.test(input)) {
1040
+ errors.push('Input must be lowercase letters');
1041
+ }
1042
+
1043
+ return {
1044
+ valid: errors.length === 0,
1045
+ errors
1046
+ };
1047
+ })
1048
+ });
1049
+ ```
1050
+
1051
+ ### Async Operations in Handlers
1052
+
1053
+ ```typescript
1054
+ const FetchTool = Tool.make('fetch', {
1055
+ parameters: Schema.Struct({
1056
+ url: Schema.String
1057
+ }),
1058
+ success: Schema.String
1059
+ });
1060
+
1061
+ const toolkit = Toolkit.make(FetchTool);
1062
+
1063
+ const toolkitLayer = toolkit.toLayer({
1064
+ fetch: ({ url }) =>
1065
+ Effect.tryPromise({
1066
+ try: () => fetch(url).then((r) => r.text()),
1067
+ catch: (error) => new Error(`Fetch failed: ${error}`)
1068
+ })
1069
+ });
1070
+ ```
1071
+
1072
+ ### Conditional Tool Execution
1073
+
1074
+ ```typescript
1075
+ const ConditionalTool = Tool.make('process', {
1076
+ parameters: Schema.Struct({
1077
+ mode: Schema.Literals(['fast', 'thorough'])
1078
+ }),
1079
+ success: Schema.String
1080
+ });
1081
+
1082
+ const toolkit = Toolkit.make(ConditionalTool);
1083
+
1084
+ const toolkitLayer = toolkit.toLayer({
1085
+ process: ({ mode }) =>
1086
+ mode === 'fast'
1087
+ ? Effect.succeed('Fast result')
1088
+ : Effect.gen(function* () {
1089
+ yield* Effect.sleep('1 second');
1090
+ return 'Thorough result';
1091
+ })
1092
+ });
1093
+ ```
1094
+
1095
+ ## When to Use This Skill
1096
+
1097
+ - Building LLM integrations with tool calling
1098
+ - Creating type-safe AI agent capabilities
1099
+ - Implementing function calling for Claude/OpenAI
1100
+ - Defining validated tool parameters and results
1101
+ - Composing multiple tools into toolkits
1102
+ - Managing tool handler dependencies
1103
+ - Integrating provider-native tools (bash, web search)
1104
+
1105
+ ## Key Principles Summary
1106
+
1107
+ 1. **Tool.make** - Define user tools with parameters, success, and failure schemas
1108
+ 2. **Tool.dynamic** - Define runtime-discovered tools; raw JSON Schema params are `unknown`
1109
+ 3. **Tool.providerDefined / OpenAiTool** - Use provider-native tools with `customName`
1110
+ 4. **Parameters** - `Tool.make` takes `Schema.Top` schemas such as `Schema.Struct(...)`; raw JSON Schema belongs with `Tool.dynamic`
1111
+ 5. **Toolkit.make** - Compose multiple tools together
1112
+ 6. **toLayer** - Implement handlers returning Layer
1113
+ 7. **toHandlers** - Implement handlers returning Context
1114
+ 8. **toolkit.handle** - Execute tools and consume a Stream of preliminary/final results
1115
+ 9. **ParametersEncoded** - Pass encoded schema input to `handle`; handlers receive decoded `Parameters`
1116
+ 10. **HandlerResult** - Access typed result, encoded JSON, failure flag, and preliminary flag
1117
+ 11. **needsApproval** - Produces approval requests until caller supplies approval responses
1118
+ 12. **setNeedsApproval** - Clone a tool with a replacement static or effectful approval policy
1119
+ 13. **toolCallId** - Pass the call ID through `toolkit.handle` and read it from handler context
1120
+ 14. **failureMode** - Control error vs return failure strategy
1121
+ 15. **dependencies** - Declare service requirements
1122
+ 16. **Namespace imports** - Always `import * as Tool`
1123
+ 17. **Prompt.makePart** - Create tool-call, tool-result, and approval parts with params
1124
+
1125
+ Your tool implementations should be type-safe, validated, and provide excellent developer experience with full schema support.
1126
+
1127
+ ## Related Skills
1128
+
1129
+ - effect-ai-language-model - Using tools with generateText/streamText
1130
+ - effect-ai-prompt - Tool call/result message integration
1131
+ - effect-ai-streaming - Processing tool call streams
1132
+ - effect-ai-provider - Provider-defined tools