@effect-uai/core 0.9.0 → 0.10.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 (112) hide show
  1. package/dist/{AiError-CrdR1EQi.d.mts → AiError-Bf1pq4BB.d.mts} +8 -3
  2. package/dist/{AiError-CrdR1EQi.d.mts.map → AiError-Bf1pq4BB.d.mts.map} +1 -1
  3. package/dist/{Audio-DDSKCkSe.d.mts → Audio-CR7CSljJ.d.mts} +2 -2
  4. package/dist/{Audio-DDSKCkSe.d.mts.map → Audio-CR7CSljJ.d.mts.map} +1 -1
  5. package/dist/{Image-C_Zjpwhc.d.mts → Image-BZmKfIdq.d.mts} +2 -2
  6. package/dist/{Image-C_Zjpwhc.d.mts.map → Image-BZmKfIdq.d.mts.map} +1 -1
  7. package/dist/{Items-CgKD6Npw.d.mts → Items-BnIduEV2.d.mts} +9 -9
  8. package/dist/{Items-CgKD6Npw.d.mts.map → Items-BnIduEV2.d.mts.map} +1 -1
  9. package/dist/{Loop-B5_cqumi.d.mts → Loop-D8nWpowc.d.mts} +2 -2
  10. package/dist/{Loop-B5_cqumi.d.mts.map → Loop-D8nWpowc.d.mts.map} +1 -1
  11. package/dist/{Media-DOLyaygI.d.mts → Media-D_CpcM1Z.d.mts} +1 -1
  12. package/dist/{Media-DOLyaygI.d.mts.map → Media-D_CpcM1Z.d.mts.map} +1 -1
  13. package/dist/{StructuredFormat-CMJFD3sn.d.mts → StructuredFormat-DHaUJBdd.d.mts} +1 -1
  14. package/dist/{StructuredFormat-CMJFD3sn.d.mts.map → StructuredFormat-DHaUJBdd.d.mts.map} +1 -1
  15. package/dist/{Tool-0XUIT2A2.d.mts → Tool-2lC_XMth.d.mts} +64 -12
  16. package/dist/Tool-2lC_XMth.d.mts.map +1 -0
  17. package/dist/{ToolEvent-DYrAUJGs.d.mts → ToolEvent-cWZ2BnbS.d.mts} +2 -2
  18. package/dist/ToolEvent-cWZ2BnbS.d.mts.map +1 -0
  19. package/dist/{ToolResult-BZd-cEWW.d.mts → ToolResult-CL4hK_9z.d.mts} +10 -4
  20. package/dist/ToolResult-CL4hK_9z.d.mts.map +1 -0
  21. package/dist/{Toolkit-BKC8b8Xo.d.mts → Toolkit-Dr9l_hNy.d.mts} +64 -23
  22. package/dist/Toolkit-Dr9l_hNy.d.mts.map +1 -0
  23. package/dist/browser/Browser.d.mts +190 -0
  24. package/dist/browser/Browser.d.mts.map +1 -0
  25. package/dist/browser/Browser.mjs +34 -0
  26. package/dist/browser/Browser.mjs.map +1 -0
  27. package/dist/browser/BrowserError.d.mts +141 -0
  28. package/dist/browser/BrowserError.d.mts.map +1 -0
  29. package/dist/browser/BrowserError.mjs +89 -0
  30. package/dist/browser/BrowserError.mjs.map +1 -0
  31. package/dist/browser/BrowserTool.d.mts +58 -0
  32. package/dist/browser/BrowserTool.d.mts.map +1 -0
  33. package/dist/browser/BrowserTool.mjs +86 -0
  34. package/dist/browser/BrowserTool.mjs.map +1 -0
  35. package/dist/domain/AiError.d.mts +2 -2
  36. package/dist/domain/AiError.mjs +22 -3
  37. package/dist/domain/AiError.mjs.map +1 -1
  38. package/dist/domain/Audio.d.mts +1 -1
  39. package/dist/domain/Image.d.mts +1 -1
  40. package/dist/domain/Items.d.mts +1 -1
  41. package/dist/domain/Media.d.mts +1 -1
  42. package/dist/domain/Music.d.mts +1 -1
  43. package/dist/domain/Turn.d.mts +3 -3
  44. package/dist/embedding-model/Embedding.d.mts +1 -1
  45. package/dist/embedding-model/EmbeddingModel.d.mts +1 -1
  46. package/dist/index.d.mts +17 -12
  47. package/dist/index.mjs +14 -9
  48. package/dist/language-model/LanguageModel.d.mts +4 -4
  49. package/dist/loop/Loop.d.mts +1 -1
  50. package/dist/music-generator/MusicGenerator.d.mts +2 -2
  51. package/dist/observability/Metrics.d.mts +1 -1
  52. package/dist/retry/Retry.d.mts +1 -1
  53. package/dist/speech-synthesizer/SpeechSynthesizer.d.mts +2 -2
  54. package/dist/structured-format/StructuredFormat.d.mts +1 -1
  55. package/dist/testing/MockMusicGenerator.d.mts +1 -1
  56. package/dist/testing/MockProvider.d.mts +1 -1
  57. package/dist/testing/MockSpeechSynthesizer.d.mts +1 -1
  58. package/dist/tool/Approval.d.mts +19 -7
  59. package/dist/tool/Approval.d.mts.map +1 -1
  60. package/dist/tool/Approval.mjs +18 -11
  61. package/dist/tool/Approval.mjs.map +1 -1
  62. package/dist/tool/Approval.test.mjs +46 -7
  63. package/dist/tool/Approval.test.mjs.map +1 -1
  64. package/dist/tool/HistoryCheck.d.mts +2 -2
  65. package/dist/tool/Tool.d.mts +2 -2
  66. package/dist/tool/Tool.mjs +86 -22
  67. package/dist/tool/Tool.mjs.map +1 -1
  68. package/dist/tool/ToolEvent.d.mts +1 -1
  69. package/dist/tool/ToolEvent.mjs +5 -0
  70. package/dist/tool/ToolEvent.mjs.map +1 -1
  71. package/dist/tool/ToolResult.d.mts +2 -2
  72. package/dist/tool/ToolResult.mjs +15 -6
  73. package/dist/tool/ToolResult.mjs.map +1 -1
  74. package/dist/tool/Toolkit.d.mts +2 -2
  75. package/dist/tool/Toolkit.mjs +62 -22
  76. package/dist/tool/Toolkit.mjs.map +1 -1
  77. package/dist/tool/Toolkit.test.mjs +163 -9
  78. package/dist/tool/Toolkit.test.mjs.map +1 -1
  79. package/dist/transcriber/Transcriber.d.mts +2 -2
  80. package/dist/web-read/WebRead.d.mts +44 -0
  81. package/dist/web-read/WebRead.d.mts.map +1 -0
  82. package/dist/web-read/WebRead.mjs +18 -0
  83. package/dist/web-read/WebRead.mjs.map +1 -0
  84. package/dist/web-read/WebReadTool.d.mts +57 -0
  85. package/dist/web-read/WebReadTool.d.mts.map +1 -0
  86. package/dist/web-read/WebReadTool.mjs +59 -0
  87. package/dist/web-read/WebReadTool.mjs.map +1 -0
  88. package/dist/web-search/WebSearch.d.mts +1 -1
  89. package/dist/web-search/WebSearchTool.d.mts +12 -6
  90. package/dist/web-search/WebSearchTool.d.mts.map +1 -1
  91. package/dist/web-search/WebSearchTool.mjs +10 -5
  92. package/dist/web-search/WebSearchTool.mjs.map +1 -1
  93. package/package.json +21 -1
  94. package/src/browser/Browser.ts +268 -0
  95. package/src/browser/BrowserError.ts +145 -0
  96. package/src/browser/BrowserTool.ts +161 -0
  97. package/src/domain/AiError.ts +27 -1
  98. package/src/index.ts +5 -0
  99. package/src/tool/Approval.test.ts +55 -5
  100. package/src/tool/Approval.ts +72 -27
  101. package/src/tool/Tool.ts +146 -47
  102. package/src/tool/ToolEvent.ts +5 -0
  103. package/src/tool/ToolResult.ts +14 -8
  104. package/src/tool/Toolkit.test.ts +171 -2
  105. package/src/tool/Toolkit.ts +173 -60
  106. package/src/web-read/WebRead.ts +70 -0
  107. package/src/web-read/WebReadTool.ts +96 -0
  108. package/src/web-search/WebSearchTool.ts +11 -5
  109. package/dist/Tool-0XUIT2A2.d.mts.map +0 -1
  110. package/dist/ToolEvent-DYrAUJGs.d.mts.map +0 -1
  111. package/dist/ToolResult-BZd-cEWW.d.mts.map +0 -1
  112. package/dist/Toolkit-BKC8b8Xo.d.mts.map +0 -1
@@ -1,10 +1,10 @@
1
1
  import { n as __exportAll } from "../chunk-uyGKjUfl.mjs";
2
+ import { ToolResult, failed, nonLocalTool, toToolCallOutput, validationError } from "./ToolResult.mjs";
3
+ import { ToolFailed, decodeCallInput, toDescriptors } from "./Tool.mjs";
2
4
  import { appendToHistory } from "../domain/Turn.mjs";
3
- import { toDescriptors } from "./Tool.mjs";
4
5
  import { next, value } from "../loop/Loop.mjs";
5
- import { ToolResult, executionError, failed, nonLocalTool, toToolCallOutput, validationError } from "./ToolResult.mjs";
6
6
  import { ToolEvent, isOutput } from "./ToolEvent.mjs";
7
- import { Array, Data, Effect, Fiber, Function, Match, Option, Queue, Record, Ref, Schema, Stream } from "effect";
7
+ import { Array, Cause, Data, Effect, Fiber, Function, Match, Option, Queue, Record, Ref, Stream } from "effect";
8
8
  //#region src/tool/Toolkit.ts
9
9
  var Toolkit_exports = /* @__PURE__ */ __exportAll({
10
10
  DuplicateToolName: () => DuplicateToolName,
@@ -12,6 +12,7 @@ var Toolkit_exports = /* @__PURE__ */ __exportAll({
12
12
  collectResults: () => collectResults,
13
13
  compose: () => compose,
14
14
  continueWithResults: () => continueWithResults,
15
+ describeFailures: () => describeFailures,
15
16
  descriptors: () => descriptors,
16
17
  fromArray: () => fromArray,
17
18
  make: () => make,
@@ -89,20 +90,46 @@ const compose = (...toolkits) => Effect.gen(function* () {
89
90
  * Wrap every local tool's `run` with a middleware, preserving each tool's
90
91
  * `Input`/`Output` and unioning the middleware's requirement `R2` into the
91
92
  * toolkit's `ToolkitR`. Non-local kinds pass through unchanged. Stack with
92
- * `pipe`:
93
+ * `pipe` - inner-first, so the last `wrap` applied is the outermost and runs
94
+ * first:
93
95
  *
94
96
  * const observed = pipe(toolkit, Toolkit.wrap(logging), Toolkit.wrap(authz))
95
- * Toolkit.run(observed, calls) // R = ToolkitR<typeof toolkit> | <authz R>
97
+ * Toolkit.run(observed, calls) // authz runs, then logging, then the tool
96
98
  *
97
- * A middleware that `Effect.fail`s surfaces as a `ToolResult.Failure` from the
98
- * executor like any run failure; use the `Approval` flow for a distinct
99
- * "denied" verdict.
99
+ * A middleware that fails with `string` / `ToolFailed` is absorbed by the
100
+ * executor into a `ToolResult.Failure`; any other error propagates typed. Use
101
+ * `describeFailures` for the total "everything to the model" case, or the
102
+ * `Approval` flow for a distinct "denied" verdict.
100
103
  */
101
104
  const wrap = Function.dual(2, (toolkit, middleware) => Record.map(toolkit, (tool) => tool._tag === "LocalTool" ? {
102
105
  ...tool,
103
106
  run: middleware(tool.run, tool.name)
104
107
  } : tool));
105
- /** Execute every provided call. Approval/rejection policy belongs upstream. */
108
+ /**
109
+ * Map every local tool's error to a `string`, so `Toolkit.run` absorbs those
110
+ * failures into `ToolResult.Failure` the model reads and adapts to. Pairs
111
+ * with the domain `describe` helpers (`AiError.describe`,
112
+ * `BrowserError.describe`). Non-local kinds pass through.
113
+ *
114
+ * const kit = yield* Toolkit.compose(
115
+ * Toolkit.describeFailures(searchKit, AiError.describe),
116
+ * browserToolkit(session), // failures stay typed
117
+ * )
118
+ */
119
+ const describeFailures = Function.dual(2, (toolkit, describe) => Record.map(toolkit, (tool) => tool._tag === "LocalTool" ? {
120
+ ...tool,
121
+ run: (input, emit) => tool.run(input, emit).pipe(Effect.mapError(describe))
122
+ } : tool));
123
+ /**
124
+ * Execute every provided call. Approval/rejection policy belongs upstream.
125
+ *
126
+ * String and `ToolFailed` failures are absorbed into `ToolResult.Failure`
127
+ * (the model reads and adapts to them); every other tool error propagates
128
+ * typed on the stream, so `Exclude<ToolkitE<T>, string | ToolFailed>` is
129
+ * what a caller catches or lets end the run. If one call in a concurrent
130
+ * batch fails the stream, siblings may go unanswered: a loop that catches
131
+ * and continues must reconcile history first (`HistoryCheck.cancelAllPending`).
132
+ */
106
133
  const run = (toolkit, calls, options) => Stream.fromIterable(calls).pipe(Stream.flatMap((call) => runOne(toolkit, call), { concurrency: options?.concurrency ?? "unbounded" }));
107
134
  const okResult = (call, tool, value) => ToolResult.Ok({
108
135
  call_id: call.call_id,
@@ -110,26 +137,39 @@ const okResult = (call, tool, value) => ToolResult.Ok({
110
137
  value
111
138
  });
112
139
  const outputEvent = (result) => Stream.succeed(ToolEvent.Output({ result }));
113
- const runOne = (toolkit, call) => Match.value(toolkit[call.name]).pipe(Match.when(Match.undefined, () => outputEvent(failed(call, "unknown_tool", `No tool registered with name "${call.name}"`))), Match.tag("LocalTool", (local) => runTool(local, call)), Match.orElse(() => outputEvent(nonLocalTool(call))));
114
- const parseJsonUnknown = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown));
115
- const runTool = (tool, call) => Stream.unwrap(Effect.gen(function* () {
116
- const parsed = yield* parseJsonUnknown(call.arguments).pipe(Effect.mapError(() => "json_parse_error"));
117
- const validated = yield* Effect.tryPromise({
118
- try: () => Promise.resolve(tool.inputSchema["~standard"].validate(parsed)),
119
- catch: () => "validation_threw"
120
- });
121
- if (validated.issues !== void 0) return Stream.succeed(ToolEvent.Output({ result: validationError(call) }));
140
+ const runOne = (toolkit, call) => {
141
+ const tool = Object.hasOwn(toolkit, call.name) ? toolkit[call.name] : void 0;
142
+ return Match.value(tool).pipe(Match.when(Match.undefined, () => outputEvent(failed(call, "unknown_tool", `No tool registered with name "${call.name}"`))), Match.tag("LocalTool", (local) => runTool(local, call)), Match.orElse(() => outputEvent(nonLocalTool(call))));
143
+ };
144
+ const describeIssues = (issues) => issues.map((issue) => {
145
+ const path = (issue.path ?? []).map((seg) => typeof seg === "object" && seg !== null ? String(seg.key) : String(seg)).join(".");
146
+ return path === "" ? issue.message : `${path}: ${issue.message}`;
147
+ }).join("; ").slice(0, 500);
148
+ const runTool = (tool, call) => Stream.unwrap(decodeCallInput(tool, call).pipe(Effect.map(Match.type().pipe(Match.tag("parseError", () => outputEvent(validationError(call, "arguments are not valid JSON"))), Match.tag("invalid", ({ issues }) => outputEvent(validationError(call, describeIssues(issues)))), Match.tag("ok", ({ input }) => runValidated(tool, call, input)), Match.exhaustive))));
149
+ const runValidated = (tool, call, input) => Stream.unwrap(Effect.gen(function* () {
122
150
  const queue = yield* Queue.make({ capacity: tool.emitBufferSize });
123
151
  const emit = (event) => Effect.asVoid(Queue.offer(queue, event));
124
- const fiber = yield* tool.run(validated.value, emit).pipe(Effect.ensuring(Queue.end(queue)), Effect.forkScoped);
152
+ const fiber = yield* tool.run(input, emit).pipe(Effect.ensuring(Queue.end(queue)), Effect.forkScoped);
125
153
  const progress = Stream.fromQueue(queue).pipe(Stream.map((data) => ToolEvent.Progress({
126
154
  call_id: call.call_id,
127
155
  tool: tool.name,
128
156
  data
129
157
  })));
130
- const output = Stream.fromEffect(Fiber.join(fiber).pipe(Effect.map((value) => ToolEvent.Output({ result: okResult(call, tool.name, value) })), Effect.catchCause(() => Effect.succeed(ToolEvent.Output({ result: executionError(call, "Tool execution failed") })))));
158
+ const output = Stream.fromEffect(Fiber.join(fiber).pipe(Effect.matchCauseEffect({
159
+ onSuccess: (value) => Effect.succeed(ToolEvent.Output({ result: okResult(call, tool.name, value) })),
160
+ onFailure: (cause) => routeCause(call, cause)
161
+ })));
131
162
  return progress.pipe(Stream.concat(output));
132
- })).pipe(Stream.catchCause(() => Stream.succeed(ToolEvent.Output({ result: executionError(call, "Tool execution failed") }))));
163
+ }));
164
+ const routeCause = (call, cause) => {
165
+ const only = cause.reasons.length === 1 ? cause.reasons[0] : void 0;
166
+ if (only !== void 0 && Cause.isFailReason(only)) {
167
+ const error = only.error;
168
+ if (typeof error === "string") return Effect.succeed(ToolEvent.Output({ result: failed(call, "tool_failed", error) }));
169
+ if (error instanceof ToolFailed) return Effect.succeed(ToolEvent.Output({ result: failed(call, error.kind ?? "tool_failed", error.message) }));
170
+ }
171
+ return Effect.failCause(cause);
172
+ };
133
173
  /**
134
174
  * Append a completed turn plus tool results to a state's history, converting
135
175
  * the results to wire-format `ToolCallOutput`s. Curried so it slots directly
@@ -178,6 +218,6 @@ const continueWithResults = Function.dual(2, (stream, build) => Stream.unwrap(Ef
178
218
  return tapped.pipe(Stream.concat(continuation));
179
219
  })));
180
220
  //#endregion
181
- export { DuplicateToolName, appendToolResults, collectResults, compose, continueWithResults, descriptors, fromArray, make, makeNamespaced, namespace, run, Toolkit_exports as t, wrap };
221
+ export { DuplicateToolName, appendToolResults, collectResults, compose, continueWithResults, describeFailures, descriptors, fromArray, make, makeNamespaced, namespace, run, Toolkit_exports as t, wrap };
182
222
 
183
223
  //# sourceMappingURL=Toolkit.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Toolkit.mjs","names":["Arr","Loop.value","Loop.next"],"sources":["../../src/tool/Toolkit.ts"],"sourcesContent":["import {\n Array as Arr,\n Cause,\n Data,\n Effect,\n Fiber,\n Function,\n Match,\n Option,\n Queue,\n Record,\n Ref,\n Schema,\n Stream,\n} from \"effect\"\nimport * as Loop from \"../loop/Loop.js\"\nimport type { HistoryItem, ToolCall } from \"../domain/Items.js\"\nimport { appendToHistory, type Turn } from \"../domain/Turn.js\"\nimport {\n type AnyLocalTool,\n type AnyTool,\n type Emit,\n type InteractionTool,\n type ProviderTool,\n type SignalTool,\n type Tool,\n type ToolDescriptor,\n type ToolR,\n toDescriptors,\n} from \"./Tool.js\"\nimport {\n ToolResult,\n executionError,\n failed,\n nonLocalTool,\n toToolCallOutput,\n validationError,\n} from \"./ToolResult.js\"\nimport { ToolEvent } from \"./ToolEvent.js\"\nimport { isOutput } from \"./ToolEvent.js\"\n\n// ---------------------------------------------------------------------------\n// Toolkit: a name-indexed record of tools — \"what the model sees\". The record\n// gives O(1) dispatch and within-toolkit name-uniqueness for free; descriptors\n// are derived, not stored. Build a static one with `make` (compile-time\n// duplicate-name check), a dynamic one with `fromArray` (trusted single source,\n// last-wins), and combine independent ones with `compose` (the application\n// boundary where cross-source names can clash).\n// ---------------------------------------------------------------------------\n\nexport type ToolMap = Record<string, AnyTool>\n\nexport type Toolkit<Tools extends ToolMap = ToolMap> = Tools\n\nconst indexByName = (tools: ReadonlyArray<AnyTool>): ToolMap =>\n Object.fromEntries(tools.map((tool) => [tool.name, tool]))\n\n/**\n * Index literal tools by their `name`. Duplicate names are a compile error\n * (`UniqueTools`). Names are not otherwise checked: a malformed name is a\n * developer typo in a literal that the provider rejects clearly on first use,\n * and there is no single cross-provider name rule to enforce here anyway.\n */\nconst buildStatic = <Tools extends ReadonlyArray<AnyTool>>(\n tools: Tools,\n): Toolkit<{ [T in Tools[number] as T[\"name\"]]: T }> =>\n indexByName(tools) as Toolkit<{ [T in Tools[number] as T[\"name\"]]: T }>\n\nexport const make = <const Tools extends ReadonlyArray<AnyTool>>(\n ...tools: UniqueTools<Tools>\n): Toolkit<{ [T in Tools[number] as T[\"name\"]]: T }> => buildStatic(tools as unknown as Tools)\n\n/**\n * Build a toolkit from a dynamically-sourced array (MCP server, plugin). Names\n * are not validated (MCP legitimately uses dots / >64 chars — sanitize at the\n * provider adapter); same-named tools resolve last-wins. Cross-source clashes\n * are caught by `compose`.\n */\nexport const fromArray = (tools: ReadonlyArray<AnyTool>): Toolkit => indexByName(tools)\n\n/** Render the provider-facing descriptors for a toolkit on demand. */\nexport const descriptors = (toolkit: Toolkit): ReadonlyArray<ToolDescriptor> =>\n toDescriptors(Object.values(toolkit))\n\n/**\n * Union of every tool's `R` requirements in a toolkit. Surfaced by `run` so the\n * loop's stream type carries the services tools need through to `Effect.provide`.\n *\n * The `ToolMap extends T` guard yields `never` for the wide `Toolkit` type (a\n * toolkit passed through an untyped boundary, where the requirements aren't\n * statically known) instead of `any`, which would otherwise poison the\n * resulting stream's `R`. Concrete toolkits from `make` keep their precise R.\n */\nexport type ToolkitR<T extends Toolkit> = ToolMap extends T ? never : ToolR<T[keyof T]>\n\n// ---------------------------------------------------------------------------\n// Uniqueness & composition. `make` rejects duplicate literal names at compile\n// time; `compose` rejects cross-source collisions — at compile time when the\n// names are statically known, and at runtime (`DuplicateToolName`) for dynamic\n// sources whose keys are only `string`. No silent last-wins overwrite, no late\n// provider 400.\n// ---------------------------------------------------------------------------\n\n// --- compile-time duplicate detection --------------------------------------\n\ntype DuplicateName<\n Tools extends ReadonlyArray<AnyTool>,\n Seen extends string = never,\n> = Tools extends readonly [\n infer Head extends AnyTool,\n ...infer Tail extends ReadonlyArray<AnyTool>,\n]\n ? Head[\"name\"] extends Seen\n ? Head[\"name\"]\n : DuplicateName<Tail, Seen | Head[\"name\"]>\n : never\n\ntype DuplicateNameError<Name extends string> = {\n readonly __error: `Duplicate tool name: ${Name}`\n}\n\nexport type NoDuplicateNames<Tools extends ReadonlyArray<AnyTool>> = [\n DuplicateName<Tools>,\n] extends [never]\n ? unknown\n : DuplicateNameError<DuplicateName<Tools>>\n\n/** `Tools` constrained so a repeated literal name is a compile error in `make`. */\nexport type UniqueTools<Tools extends ReadonlyArray<AnyTool>> = Tools & NoDuplicateNames<Tools>\n\n// Cross-toolkit literal-key collision, skipping wide (`string`-keyed, dynamic)\n// toolkits so the compile-time check never false-positives on `fromArray`.\ntype ComposeDuplicateKey<\n Kits extends ReadonlyArray<Toolkit>,\n Seen extends string = never,\n> = Kits extends readonly [infer Head extends Toolkit, ...infer Tail extends ReadonlyArray<Toolkit>]\n ? string extends keyof Head\n ? ComposeDuplicateKey<Tail, Seen>\n : Extract<keyof Head & string, Seen> extends never\n ? ComposeDuplicateKey<Tail, Seen | (keyof Head & string)>\n : Extract<keyof Head & string, Seen>\n : never\n\ntype NoDuplicateComposedKeys<Kits extends ReadonlyArray<Toolkit>> = [\n ComposeDuplicateKey<Kits>,\n] extends [never]\n ? unknown\n : DuplicateNameError<ComposeDuplicateKey<Kits>>\n\ntype UnionToIntersection<U> = (U extends unknown ? (x: U) => void : never) extends (\n x: infer I,\n) => void\n ? I\n : never\n\ntype Composed<Kits extends ReadonlyArray<Toolkit>> =\n UnionToIntersection<Kits[number]> extends infer M ? (M extends ToolMap ? M : ToolMap) : ToolMap\n\n// --- uniqueness error ------------------------------------------------------\n\n/**\n * Two composed toolkits resolved to the same final name. Carries the colliding\n * `sources` (the positions of the clashing toolkits) so the error says *which*\n * inputs collided. Raised at runtime by `compose` for dynamic sources; static\n * collisions are caught earlier at compile time.\n */\nexport class DuplicateToolName extends Data.TaggedError(\"DuplicateToolName\")<{\n readonly name: string\n readonly sources: ReadonlyArray<string>\n}> {}\n\n// --- namespacing -----------------------------------------------------------\n\ntype PrefixName<Prefix extends string, Name extends string> = `${Prefix}__${Name}`\n\ntype PrefixTool<Prefix extends string, T extends AnyTool> =\n T extends Tool<infer N, infer I, infer Ev, infer O, infer R>\n ? Tool<PrefixName<Prefix, N>, I, Ev, O, R>\n : T extends ProviderTool<infer N, infer I, infer P, infer C>\n ? ProviderTool<PrefixName<Prefix, N>, I, P, C>\n : T extends SignalTool<infer N, infer I>\n ? SignalTool<PrefixName<Prefix, N>, I>\n : T extends InteractionTool<infer N, infer I>\n ? InteractionTool<PrefixName<Prefix, N>, I>\n : T\n\ntype Namespaced<Prefix extends string, T extends ToolMap> = {\n readonly [K in keyof T & string as PrefixName<Prefix, K>]: T[K] extends AnyTool\n ? PrefixTool<Prefix, T[K]>\n : never\n}\n\n/**\n * Prefix every tool name with `<namespace>__`, returning a new toolkit. Use it\n * to keep generic names (`search`) from two sources distinct before `compose`.\n * If two namespaces collide at the `__` separator, the duplicate final name\n * surfaces from `compose` as a `DuplicateToolName`.\n */\nexport const namespace = <const Prefix extends string, const T extends ToolMap>(\n prefix: Prefix,\n toolkit: Toolkit<T>,\n): Toolkit<Namespaced<Prefix, T>> => {\n const renamed = Arr.map(Record.values(toolkit), (tool) => ({\n ...tool,\n name: `${prefix}__${tool.name}`,\n }))\n return indexByName(renamed) as Toolkit<Namespaced<Prefix, T>>\n}\n\n/** `namespace(prefix, make(...tools))` in one step, for static namespaced tools. */\nexport const makeNamespaced = <\n const Prefix extends string,\n const Tools extends ReadonlyArray<AnyTool>,\n>(\n prefix: Prefix,\n ...tools: UniqueTools<Tools>\n): Toolkit<Namespaced<Prefix, { [T in Tools[number] as T[\"name\"]]: T }>> =>\n namespace(prefix, buildStatic(tools as unknown as Tools))\n\n// --- composition (the application boundary) --------------------------------\n\n/**\n * Combine independently-built toolkits into one. This is where names from\n * separate sources can collide, so it is the one effectful constructor: a\n * duplicate final name fails with `DuplicateToolName` (naming the colliding\n * input positions) instead of silently overwriting or 400-ing later at the\n * provider. Statically-known collisions are additionally a compile error.\n */\nexport const compose = <const Kits extends ReadonlyArray<Toolkit>>(\n ...toolkits: Kits & NoDuplicateComposedKeys<Kits>\n): Effect.Effect<Toolkit<Composed<Kits>>, DuplicateToolName> =>\n Effect.gen(function* () {\n const kits = toolkits as unknown as ReadonlyArray<Toolkit>\n const labelled = Arr.flatMap(kits, (kit, i) =>\n Arr.map(Record.keys(kit), (name) => ({ name, source: `toolkit-${i}` })),\n )\n const collision = Arr.findFirst(\n Record.toEntries(Arr.groupBy(labelled, (entry) => entry.name)),\n ([, group]) => group.length > 1,\n )\n if (Option.isSome(collision)) {\n const [name, group] = collision.value\n return yield* new DuplicateToolName({ name, sources: Arr.map(group, (e) => e.source) })\n }\n return Arr.reduce(kits, {} as ToolMap, (acc, kit) => ({ ...acc, ...kit })) as Toolkit<\n Composed<Kits>\n >\n })\n\n// ---------------------------------------------------------------------------\n// Middleware: a `Toolkit -> Toolkit` transform applied up front, then the\n// wrapped toolkit is run. A middleware transforms one local tool's `run` given\n// its name (logging, retry, auth, metrics); `wrap` lifts it over the whole\n// toolkit, leaving provider/signal/interaction kinds untouched. The one reason\n// this is a combinator rather than native `Record.map`: it tracks the\n// middleware's added requirement `R2`, unioning it into `ToolkitR` instead of\n// widening every tool's type.\n// ---------------------------------------------------------------------------\n\ntype Handler<Input, Event, Output, R> = (\n input: Input,\n emit: Emit<Event>,\n) => Effect.Effect<Output, unknown, R>\n\nexport type Middleware<R2 = never> = <Input, Event, Output, R>(\n run: Handler<Input, Event, Output, R>,\n name: string,\n) => Handler<Input, Event, Output, R | R2>\n\ntype WrapTool<T extends AnyTool, R2> =\n T extends Tool<infer N, infer I, infer Ev, infer O, infer R> ? Tool<N, I, Ev, O, R | R2> : T\n\ntype WrapToolkit<T extends ToolMap, R2> = {\n readonly [K in keyof T]: T[K] extends AnyTool ? WrapTool<T[K], R2> : T[K]\n}\n\n/**\n * Wrap every local tool's `run` with a middleware, preserving each tool's\n * `Input`/`Output` and unioning the middleware's requirement `R2` into the\n * toolkit's `ToolkitR`. Non-local kinds pass through unchanged. Stack with\n * `pipe`:\n *\n * const observed = pipe(toolkit, Toolkit.wrap(logging), Toolkit.wrap(authz))\n * Toolkit.run(observed, calls) // R = ToolkitR<typeof toolkit> | <authz R>\n *\n * A middleware that `Effect.fail`s surfaces as a `ToolResult.Failure` from the\n * executor like any run failure; use the `Approval` flow for a distinct\n * \"denied\" verdict.\n */\nexport const wrap: {\n <R2>(middleware: Middleware<R2>): <T extends Toolkit>(toolkit: T) => WrapToolkit<T, R2>\n <T extends Toolkit, R2>(toolkit: T, middleware: Middleware<R2>): WrapToolkit<T, R2>\n} = Function.dual(\n 2,\n <T extends Toolkit, R2>(toolkit: T, middleware: Middleware<R2>): WrapToolkit<T, R2> =>\n Record.map(toolkit, (tool) =>\n tool._tag === \"LocalTool\" ? { ...tool, run: middleware(tool.run, tool.name) } : tool,\n ) as WrapToolkit<T, R2>,\n)\n\n// ---------------------------------------------------------------------------\n// Tool executor. Streams `ToolEvent`s in real time. Policy stays outside this\n// module: callers pass only the calls they have already decided should run.\n// ---------------------------------------------------------------------------\n\nexport type ExecuteOptions = {\n readonly concurrency?: number | \"unbounded\"\n}\n\n/** Execute every provided call. Approval/rejection policy belongs upstream. */\nexport const run = <T extends Toolkit>(\n toolkit: T,\n calls: ReadonlyArray<ToolCall>,\n options?: ExecuteOptions,\n): Stream.Stream<ToolEvent, never, ToolkitR<T>> =>\n Stream.fromIterable(calls).pipe(\n Stream.flatMap((call) => runOne(toolkit, call), {\n concurrency: options?.concurrency ?? \"unbounded\",\n }),\n ) as Stream.Stream<ToolEvent, never, ToolkitR<T>>\n\nconst okResult = (call: ToolCall, tool: string, value: unknown): ToolResult =>\n ToolResult.Ok({ call_id: call.call_id, tool, value })\n\nconst outputEvent = (result: ToolResult): Stream.Stream<ToolEvent, never, any> =>\n Stream.succeed(ToolEvent.Output({ result }))\n\nconst runOne = (toolkit: ToolMap, call: ToolCall): Stream.Stream<ToolEvent, never, any> =>\n Match.value(toolkit[call.name]).pipe(\n // No such tool. Graceful: emit a synthetic Failure so OTHER calls in this\n // turn still execute. LLMs hallucinate tool names; MCP tools come and go.\n Match.when(Match.undefined, () =>\n outputEvent(failed(call, \"unknown_tool\", `No tool registered with name \"${call.name}\"`)),\n ),\n Match.tag(\"LocalTool\", (local) => runTool(local, call)),\n // Provider/signal/interaction tools are model-visible but not locally\n // executable. The loop is expected to intercept these (decode + act) before\n // execution; reaching here means it didn't, so report it rather than crash.\n Match.orElse(() => outputEvent(nonLocalTool(call))),\n )\n\nconst parseJsonUnknown = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown))\n\nconst runTool = (tool: AnyLocalTool, call: ToolCall): Stream.Stream<ToolEvent, never, any> =>\n Stream.unwrap(\n Effect.gen(function* () {\n const parsed = yield* parseJsonUnknown(call.arguments).pipe(\n Effect.mapError(() => \"json_parse_error\" as const),\n )\n const validated = yield* Effect.tryPromise({\n try: () => Promise.resolve(tool.inputSchema[\"~standard\"].validate(parsed)),\n catch: () => \"validation_threw\" as const,\n })\n if (validated.issues !== undefined) {\n return Stream.succeed<ToolEvent>(ToolEvent.Output({ result: validationError(call) }))\n }\n\n // Per-call queue: `emit` offers events as `run` produces them; the\n // progress stream drains them in real time; `Queue.end` (in the\n // `ensuring`) flushes pending events then signals a clean end, which\n // `Stream.fromQueue` treats as completion. `Queue.shutdown` would clear\n // queued items and interrupt pending takes — wrong for graceful teardown.\n const queue = yield* Queue.make<unknown, Cause.Done>({ capacity: tool.emitBufferSize })\n const emit = (event: unknown) => Effect.asVoid(Queue.offer(queue, event))\n const fiber = yield* tool\n .run(validated.value, emit)\n .pipe(Effect.ensuring(Queue.end(queue)), Effect.forkScoped)\n\n const progress = Stream.fromQueue(queue).pipe(\n Stream.map((data) => ToolEvent.Progress({ call_id: call.call_id, tool: tool.name, data })),\n )\n const output = Stream.fromEffect(\n Fiber.join(fiber).pipe(\n Effect.map((value) => ToolEvent.Output({ result: okResult(call, tool.name, value) })),\n Effect.catchCause(() =>\n Effect.succeed(\n ToolEvent.Output({ result: executionError(call, \"Tool execution failed\") }),\n ),\n ),\n ),\n )\n return progress.pipe(Stream.concat(output))\n }),\n ).pipe(\n // Backstop for the input-parsing failures (`json_parse_error`,\n // `validation_threw`) and any defect before the run fiber is forked.\n Stream.catchCause(() =>\n Stream.succeed(ToolEvent.Output({ result: executionError(call, \"Tool execution failed\") })),\n ),\n )\n\n// ---------------------------------------------------------------------------\n// `continueWithResults` - bridge from a `Stream<ToolEvent>` to the loop's\n// emit shape. Drains the stream to the consumer in real-time, taps every\n// `Output` into an internal Ref, and at end-of-stream emits\n// `Loop.next(build(results))`. Recipe never sees the Ref.\n//\n// Dual: data-first `continueWithResults(stream, build)` and data-last\n// `stream.pipe(continueWithResults(build))` both work.\n// ---------------------------------------------------------------------------\n\n/**\n * Append a completed turn plus tool results to a state's history, converting\n * the results to wire-format `ToolCallOutput`s. Curried so it slots directly\n * into `continueWithResults`:\n *\n * Toolkit.run(toolkit, calls).pipe(\n * Toolkit.continueWithResults(Toolkit.appendToolResults(state, turn)),\n * )\n *\n * Equivalent to `appendToHistory(state, turn, results.map(toToolCallOutput))`\n * — the helper just hides the wire-conversion step.\n */\nexport const appendToolResults =\n <S extends { readonly history: ReadonlyArray<HistoryItem> }>(state: S, turn: Turn) =>\n (results: ReadonlyArray<ToolResult>): S =>\n appendToHistory(state, turn, results.map(toToolCallOutput))\n\n/**\n * Drain a `Stream<ToolEvent>` and return the accumulated `ToolResult`s\n * from every `Output` event. One-shot — the type is `Effect<results>`,\n * not a one-element stream — so it composes naturally with `Effect.map`\n * to build state.\n *\n * Counterpart to `continueWithResults`: `continueWithResults` bundles\n * drain + emit Next into one bridge; this one is the right arm of a\n * broadcast/fork-and-merge for callers that want to vary the left arm\n * (e.g., filter/tap ToolEvents before forwarding).\n */\nexport const collectResults = <E, R>(\n stream: Stream.Stream<ToolEvent, E, R>,\n): Effect.Effect<ReadonlyArray<ToolResult>, E, R> =>\n stream.pipe(\n Stream.filter(isOutput),\n Stream.map((e) => e.result),\n Stream.runCollect,\n )\n\n/**\n * Bridge from a `Stream<ToolEvent>` to the loop's emit shape. Forwards\n * every ToolEvent downstream as a `Loop.value` and at end-of-stream emits\n * one `Loop.next(build(results))` carrying the accumulated `ToolResult`s\n * from terminal `Output` events.\n *\n * Conceptually a broadcast fork (one arm passes events through, the other\n * drains them into a state). Implemented as a single-pass Ref tap + concat\n * for zero buffering — the broadcast version is observationally equivalent\n * but holds events in a PubSub until both arms drain. The public primitives\n * (`Loop.value`, `collectResults`, `Loop.next`) compose the same pattern\n * when you need to vary an arm.\n *\n * Dual: data-first `continueWithResults(stream, build)` and data-last\n * `stream.pipe(continueWithResults(build))` both work.\n */\nexport const continueWithResults: {\n <S>(\n build: (results: ReadonlyArray<ToolResult>) => S,\n ): <R>(\n stream: Stream.Stream<ToolEvent, never, R>,\n ) => Stream.Stream<Loop.Step<ToolEvent, S>, never, R>\n <S, R>(\n stream: Stream.Stream<ToolEvent, never, R>,\n build: (results: ReadonlyArray<ToolResult>) => S,\n ): Stream.Stream<Loop.Step<ToolEvent, S>, never, R>\n} = Function.dual(\n 2,\n <S, R>(\n stream: Stream.Stream<ToolEvent, never, R>,\n build: (results: ReadonlyArray<ToolResult>) => S,\n ): Stream.Stream<Loop.Step<ToolEvent, S>, never, R> =>\n Stream.unwrap(\n Effect.gen(function* () {\n const ref = yield* Ref.make<ReadonlyArray<ToolResult>>([])\n const tapped = stream.pipe(\n Stream.tap((e) =>\n isOutput(e) ? Ref.update(ref, (acc) => Arr.append(acc, e.result)) : Effect.void,\n ),\n Stream.map(Loop.value),\n )\n const continuation = Stream.unwrap(\n Ref.get(ref).pipe(Effect.map((acc) => Loop.next(build(acc)))),\n )\n return tapped.pipe(Stream.concat(continuation))\n }),\n ),\n)\n"],"mappings":"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"}
1
+ {"version":3,"file":"Toolkit.mjs","names":["Arr","Loop.value","Loop.next"],"sources":["../../src/tool/Toolkit.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\"\nimport {\n Array as Arr,\n Cause,\n Data,\n Effect,\n Fiber,\n Function,\n Match,\n Option,\n Queue,\n Record,\n Ref,\n Stream,\n} from \"effect\"\nimport * as Loop from \"../loop/Loop.js\"\nimport type { HistoryItem, ToolCall } from \"../domain/Items.js\"\nimport { appendToHistory, type Turn } from \"../domain/Turn.js\"\nimport {\n type AnyLocalTool,\n type AnyTool,\n type DecodeResult,\n decodeCallInput,\n type Emit,\n type InteractionTool,\n type ProviderTool,\n type SignalTool,\n type Tool,\n type ToolDescriptor,\n type ToolE,\n ToolFailed,\n type ToolR,\n toDescriptors,\n} from \"./Tool.js\"\nimport {\n ToolResult,\n failed,\n nonLocalTool,\n toToolCallOutput,\n validationError,\n} from \"./ToolResult.js\"\nimport { ToolEvent } from \"./ToolEvent.js\"\nimport { isOutput } from \"./ToolEvent.js\"\n\n// ---------------------------------------------------------------------------\n// Toolkit: a name-indexed record of tools — \"what the model sees\". The record\n// gives O(1) dispatch and within-toolkit name-uniqueness for free; descriptors\n// are derived, not stored. Build a static one with `make` (compile-time\n// duplicate-name check), a dynamic one with `fromArray` (trusted single source,\n// last-wins), and combine independent ones with `compose` (the application\n// boundary where cross-source names can clash).\n// ---------------------------------------------------------------------------\n\nexport type ToolMap = Record<string, AnyTool>\n\nexport type Toolkit<Tools extends ToolMap = ToolMap> = Tools\n\nconst indexByName = (tools: ReadonlyArray<AnyTool>): ToolMap =>\n Object.fromEntries(tools.map((tool) => [tool.name, tool]))\n\n/**\n * Index literal tools by their `name`. Duplicate names are a compile error\n * (`UniqueTools`). Names are not otherwise checked: a malformed name is a\n * developer typo in a literal that the provider rejects clearly on first use,\n * and there is no single cross-provider name rule to enforce here anyway.\n */\nconst buildStatic = <Tools extends ReadonlyArray<AnyTool>>(\n tools: Tools,\n): Toolkit<{ [T in Tools[number] as T[\"name\"]]: T }> =>\n indexByName(tools) as Toolkit<{ [T in Tools[number] as T[\"name\"]]: T }>\n\nexport const make = <const Tools extends ReadonlyArray<AnyTool>>(\n ...tools: UniqueTools<Tools>\n): Toolkit<{ [T in Tools[number] as T[\"name\"]]: T }> => buildStatic(tools as unknown as Tools)\n\n/**\n * Build a toolkit from a dynamically-sourced array (MCP server, plugin). Names\n * are not validated (MCP legitimately uses dots / >64 chars — sanitize at the\n * provider adapter); same-named tools resolve last-wins. Cross-source clashes\n * are caught by `compose`.\n */\nexport const fromArray = (tools: ReadonlyArray<AnyTool>): Toolkit => indexByName(tools)\n\n/** Render the provider-facing descriptors for a toolkit on demand. */\nexport const descriptors = (toolkit: Toolkit): ReadonlyArray<ToolDescriptor> =>\n toDescriptors(Object.values(toolkit))\n\n/**\n * Union of every tool's `R` requirements in a toolkit. Surfaced by `run` so the\n * loop's stream type carries the services tools need through to `Effect.provide`.\n *\n * The `ToolMap extends T` guard yields `never` for the wide `Toolkit` type (a\n * toolkit passed through an untyped boundary, where the requirements aren't\n * statically known) instead of `any`, which would otherwise poison the\n * resulting stream's `R`. Concrete toolkits from `make` keep their precise R.\n */\nexport type ToolkitR<T extends Toolkit> = ToolMap extends T ? never : ToolR<T[keyof T]>\n\n/**\n * Union of every tool's `E` error type in a toolkit. `Toolkit.run` reports\n * `Exclude<ToolkitE<T>, string | ToolFailed>` on its stream: the sentinel\n * failures are absorbed into results, everything else propagates typed. The\n * same `ToolMap extends T` guard as `ToolkitR` keeps a wide toolkit at\n * `never` rather than `any`.\n */\nexport type ToolkitE<T extends Toolkit> = ToolMap extends T ? never : ToolE<T[keyof T]>\n\n// ---------------------------------------------------------------------------\n// Uniqueness & composition. `make` rejects duplicate literal names at compile\n// time; `compose` rejects cross-source collisions — at compile time when the\n// names are statically known, and at runtime (`DuplicateToolName`) for dynamic\n// sources whose keys are only `string`. No silent last-wins overwrite, no late\n// provider 400.\n// ---------------------------------------------------------------------------\n\n// --- compile-time duplicate detection --------------------------------------\n\ntype DuplicateName<\n Tools extends ReadonlyArray<AnyTool>,\n Seen extends string = never,\n> = Tools extends readonly [\n infer Head extends AnyTool,\n ...infer Tail extends ReadonlyArray<AnyTool>,\n]\n ? Head[\"name\"] extends Seen\n ? Head[\"name\"]\n : DuplicateName<Tail, Seen | Head[\"name\"]>\n : never\n\ntype DuplicateNameError<Name extends string> = {\n readonly __error: `Duplicate tool name: ${Name}`\n}\n\nexport type NoDuplicateNames<Tools extends ReadonlyArray<AnyTool>> = [\n DuplicateName<Tools>,\n] extends [never]\n ? unknown\n : DuplicateNameError<DuplicateName<Tools>>\n\n/** `Tools` constrained so a repeated literal name is a compile error in `make`. */\nexport type UniqueTools<Tools extends ReadonlyArray<AnyTool>> = Tools & NoDuplicateNames<Tools>\n\n// Cross-toolkit literal-key collision, skipping wide (`string`-keyed, dynamic)\n// toolkits so the compile-time check never false-positives on `fromArray`.\ntype ComposeDuplicateKey<\n Kits extends ReadonlyArray<Toolkit>,\n Seen extends string = never,\n> = Kits extends readonly [infer Head extends Toolkit, ...infer Tail extends ReadonlyArray<Toolkit>]\n ? string extends keyof Head\n ? ComposeDuplicateKey<Tail, Seen>\n : Extract<keyof Head & string, Seen> extends never\n ? ComposeDuplicateKey<Tail, Seen | (keyof Head & string)>\n : Extract<keyof Head & string, Seen>\n : never\n\ntype NoDuplicateComposedKeys<Kits extends ReadonlyArray<Toolkit>> = [\n ComposeDuplicateKey<Kits>,\n] extends [never]\n ? unknown\n : DuplicateNameError<ComposeDuplicateKey<Kits>>\n\ntype UnionToIntersection<U> = (U extends unknown ? (x: U) => void : never) extends (\n x: infer I,\n) => void\n ? I\n : never\n\ntype Composed<Kits extends ReadonlyArray<Toolkit>> =\n UnionToIntersection<Kits[number]> extends infer M ? (M extends ToolMap ? M : ToolMap) : ToolMap\n\n// --- uniqueness error ------------------------------------------------------\n\n/**\n * Two composed toolkits resolved to the same final name. Carries the colliding\n * `sources` (the positions of the clashing toolkits) so the error says *which*\n * inputs collided. Raised at runtime by `compose` for dynamic sources; static\n * collisions are caught earlier at compile time.\n */\nexport class DuplicateToolName extends Data.TaggedError(\"DuplicateToolName\")<{\n readonly name: string\n readonly sources: ReadonlyArray<string>\n}> {}\n\n// --- namespacing -----------------------------------------------------------\n\ntype PrefixName<Prefix extends string, Name extends string> = `${Prefix}__${Name}`\n\ntype PrefixTool<Prefix extends string, T extends AnyTool> =\n T extends Tool<infer N, infer I, infer Ev, infer O, infer R>\n ? Tool<PrefixName<Prefix, N>, I, Ev, O, R>\n : T extends ProviderTool<infer N, infer I, infer P, infer C>\n ? ProviderTool<PrefixName<Prefix, N>, I, P, C>\n : T extends SignalTool<infer N, infer I>\n ? SignalTool<PrefixName<Prefix, N>, I>\n : T extends InteractionTool<infer N, infer I>\n ? InteractionTool<PrefixName<Prefix, N>, I>\n : T\n\ntype Namespaced<Prefix extends string, T extends ToolMap> = {\n readonly [K in keyof T & string as PrefixName<Prefix, K>]: T[K] extends AnyTool\n ? PrefixTool<Prefix, T[K]>\n : never\n}\n\n/**\n * Prefix every tool name with `<namespace>__`, returning a new toolkit. Use it\n * to keep generic names (`search`) from two sources distinct before `compose`.\n * If two namespaces collide at the `__` separator, the duplicate final name\n * surfaces from `compose` as a `DuplicateToolName`.\n */\nexport const namespace = <const Prefix extends string, const T extends ToolMap>(\n prefix: Prefix,\n toolkit: Toolkit<T>,\n): Toolkit<Namespaced<Prefix, T>> => {\n const renamed = Arr.map(Record.values(toolkit), (tool) => ({\n ...tool,\n name: `${prefix}__${tool.name}`,\n }))\n return indexByName(renamed) as Toolkit<Namespaced<Prefix, T>>\n}\n\n/** `namespace(prefix, make(...tools))` in one step, for static namespaced tools. */\nexport const makeNamespaced = <\n const Prefix extends string,\n const Tools extends ReadonlyArray<AnyTool>,\n>(\n prefix: Prefix,\n ...tools: UniqueTools<Tools>\n): Toolkit<Namespaced<Prefix, { [T in Tools[number] as T[\"name\"]]: T }>> =>\n namespace(prefix, buildStatic(tools as unknown as Tools))\n\n// --- composition (the application boundary) --------------------------------\n\n/**\n * Combine independently-built toolkits into one. This is where names from\n * separate sources can collide, so it is the one effectful constructor: a\n * duplicate final name fails with `DuplicateToolName` (naming the colliding\n * input positions) instead of silently overwriting or 400-ing later at the\n * provider. Statically-known collisions are additionally a compile error.\n */\nexport const compose = <const Kits extends ReadonlyArray<Toolkit>>(\n ...toolkits: Kits & NoDuplicateComposedKeys<Kits>\n): Effect.Effect<Toolkit<Composed<Kits>>, DuplicateToolName> =>\n Effect.gen(function* () {\n const kits = toolkits as unknown as ReadonlyArray<Toolkit>\n const labelled = Arr.flatMap(kits, (kit, i) =>\n Arr.map(Record.keys(kit), (name) => ({ name, source: `toolkit-${i}` })),\n )\n const collision = Arr.findFirst(\n Record.toEntries(Arr.groupBy(labelled, (entry) => entry.name)),\n ([, group]) => group.length > 1,\n )\n if (Option.isSome(collision)) {\n const [name, group] = collision.value\n return yield* new DuplicateToolName({ name, sources: Arr.map(group, (e) => e.source) })\n }\n return Arr.reduce(kits, {} as ToolMap, (acc, kit) => ({ ...acc, ...kit })) as Toolkit<\n Composed<Kits>\n >\n })\n\n// ---------------------------------------------------------------------------\n// Middleware: a `Toolkit -> Toolkit` transform applied up front, then the\n// wrapped toolkit is run. A middleware transforms one local tool's `run` given\n// its name (logging, retry, auth, metrics); `wrap` lifts it over the whole\n// toolkit, leaving provider/signal/interaction kinds untouched. The one reason\n// this is a combinator rather than native `Record.map`: it tracks the\n// middleware's added requirement `R2`, unioning it into `ToolkitR` instead of\n// widening every tool's type.\n// ---------------------------------------------------------------------------\n\ntype Handler<Input, Event, Output, E, R> = (\n input: Input,\n emit: Emit<Event>,\n) => Effect.Effect<Output, E, R>\n\n/**\n * A `run` transform, tracking an added requirement `R2` and optional added\n * error `E2`. The tool's own `E` is preserved; `describeFailures` is the\n * combinator that *replaces* it (mapping every error to `string`).\n */\nexport type Middleware<R2 = never, E2 = never> = <Input, Event, Output, E, R>(\n run: Handler<Input, Event, Output, E, R>,\n name: string,\n) => Handler<Input, Event, Output, E | E2, R | R2>\n\ntype WrapTool<T extends AnyTool, R2, E2> =\n T extends Tool<infer N, infer I, infer Ev, infer O, infer E, infer R>\n ? Tool<N, I, Ev, O, E | E2, R | R2>\n : T\n\ntype WrapToolkit<T extends ToolMap, R2, E2> = {\n readonly [K in keyof T]: T[K] extends AnyTool ? WrapTool<T[K], R2, E2> : T[K]\n}\n\n/**\n * Wrap every local tool's `run` with a middleware, preserving each tool's\n * `Input`/`Output` and unioning the middleware's requirement `R2` into the\n * toolkit's `ToolkitR`. Non-local kinds pass through unchanged. Stack with\n * `pipe` - inner-first, so the last `wrap` applied is the outermost and runs\n * first:\n *\n * const observed = pipe(toolkit, Toolkit.wrap(logging), Toolkit.wrap(authz))\n * Toolkit.run(observed, calls) // authz runs, then logging, then the tool\n *\n * A middleware that fails with `string` / `ToolFailed` is absorbed by the\n * executor into a `ToolResult.Failure`; any other error propagates typed. Use\n * `describeFailures` for the total \"everything to the model\" case, or the\n * `Approval` flow for a distinct \"denied\" verdict.\n */\nexport const wrap: {\n <R2, E2>(\n middleware: Middleware<R2, E2>,\n ): <T extends Toolkit>(toolkit: T) => WrapToolkit<T, R2, E2>\n <T extends Toolkit, R2, E2>(toolkit: T, middleware: Middleware<R2, E2>): WrapToolkit<T, R2, E2>\n} = Function.dual(\n 2,\n <T extends Toolkit, R2, E2>(toolkit: T, middleware: Middleware<R2, E2>): WrapToolkit<T, R2, E2> =>\n Record.map(toolkit, (tool) =>\n tool._tag === \"LocalTool\" ? { ...tool, run: middleware(tool.run, tool.name) } : tool,\n ) as WrapToolkit<T, R2, E2>,\n)\n\n// ---------------------------------------------------------------------------\n// describeFailures: map every local tool's error to a model-visible string,\n// opting its failures into recovery. Group tools by recovery policy, wrap\n// each group, then compose.\n// ---------------------------------------------------------------------------\n\ntype DescribeTool<T extends AnyTool> =\n T extends Tool<infer N, infer I, infer Ev, infer O, any, infer R>\n ? Tool<N, I, Ev, O, string, R>\n : T\n\ntype DescribeToolkit<T extends ToolMap> = {\n readonly [K in keyof T]: T[K] extends AnyTool ? DescribeTool<T[K]> : T[K]\n}\n\n/**\n * Map every local tool's error to a `string`, so `Toolkit.run` absorbs those\n * failures into `ToolResult.Failure` the model reads and adapts to. Pairs\n * with the domain `describe` helpers (`AiError.describe`,\n * `BrowserError.describe`). Non-local kinds pass through.\n *\n * const kit = yield* Toolkit.compose(\n * Toolkit.describeFailures(searchKit, AiError.describe),\n * browserToolkit(session), // failures stay typed\n * )\n */\nexport const describeFailures: {\n <E>(describe: (e: E) => string): <T extends Toolkit>(toolkit: T) => DescribeToolkit<T>\n <T extends Toolkit>(toolkit: T, describe: (e: ToolkitE<T>) => string): DescribeToolkit<T>\n} = Function.dual(\n 2,\n <T extends Toolkit>(toolkit: T, describe: (e: any) => string): DescribeToolkit<T> =>\n Record.map(toolkit, (tool) =>\n tool._tag === \"LocalTool\"\n ? {\n ...tool,\n run: (input: unknown, emit: Emit<unknown>) =>\n (tool as AnyLocalTool).run(input, emit).pipe(Effect.mapError(describe)),\n }\n : tool,\n ) as DescribeToolkit<T>,\n)\n\n// ---------------------------------------------------------------------------\n// Tool executor. Streams `ToolEvent`s in real time. Policy stays outside this\n// module: callers pass only the calls they have already decided should run.\n// ---------------------------------------------------------------------------\n\nexport type ExecuteOptions = {\n readonly concurrency?: number | \"unbounded\"\n}\n\n/**\n * Execute every provided call. Approval/rejection policy belongs upstream.\n *\n * String and `ToolFailed` failures are absorbed into `ToolResult.Failure`\n * (the model reads and adapts to them); every other tool error propagates\n * typed on the stream, so `Exclude<ToolkitE<T>, string | ToolFailed>` is\n * what a caller catches or lets end the run. If one call in a concurrent\n * batch fails the stream, siblings may go unanswered: a loop that catches\n * and continues must reconcile history first (`HistoryCheck.cancelAllPending`).\n */\nexport const run = <T extends Toolkit>(\n toolkit: T,\n calls: ReadonlyArray<ToolCall>,\n options?: ExecuteOptions,\n): Stream.Stream<ToolEvent, Exclude<ToolkitE<T>, string | ToolFailed>, ToolkitR<T>> =>\n Stream.fromIterable(calls).pipe(\n Stream.flatMap((call) => runOne(toolkit, call), {\n concurrency: options?.concurrency ?? \"unbounded\",\n }),\n ) as Stream.Stream<ToolEvent, Exclude<ToolkitE<T>, string | ToolFailed>, ToolkitR<T>>\n\nconst okResult = (call: ToolCall, tool: string, value: unknown): ToolResult =>\n ToolResult.Ok({ call_id: call.call_id, tool, value })\n\nconst outputEvent = (result: ToolResult): Stream.Stream<ToolEvent, never, any> =>\n Stream.succeed(ToolEvent.Output({ result }))\n\nconst runOne = (toolkit: ToolMap, call: ToolCall): Stream.Stream<ToolEvent, unknown, any> => {\n // Own-property lookup: a hallucinated call named `constructor` / `toString`\n // must not resolve to a prototype method.\n const tool = Object.hasOwn(toolkit, call.name) ? toolkit[call.name] : undefined\n return Match.value(tool).pipe(\n // No such tool. Graceful: emit a synthetic Failure so OTHER calls in this\n // turn still execute. LLMs hallucinate tool names; MCP tools come and go.\n Match.when(Match.undefined, () =>\n outputEvent(failed(call, \"unknown_tool\", `No tool registered with name \"${call.name}\"`)),\n ),\n Match.tag(\"LocalTool\", (local) => runTool(local, call)),\n // Provider/signal/interaction tools are model-visible but not locally\n // executable. The loop is expected to intercept these (decode + act) before\n // execution; reaching here means it didn't, so report it rather than crash.\n Match.orElse(() => outputEvent(nonLocalTool(call))),\n )\n}\n\nconst describeIssues = (issues: ReadonlyArray<StandardSchemaV1.Issue>): string =>\n issues\n .map((issue) => {\n const path = (issue.path ?? [])\n .map((seg) => (typeof seg === \"object\" && seg !== null ? String(seg.key) : String(seg)))\n .join(\".\")\n return path === \"\" ? issue.message : `${path}: ${issue.message}`\n })\n .join(\"; \")\n .slice(0, 500)\n\n// A decode failure is the model's contract violation: report it as a result\n// (never a stream failure) so the model can correct its next call.\nconst runTool = (tool: AnyLocalTool, call: ToolCall): Stream.Stream<ToolEvent, unknown, any> =>\n Stream.unwrap(\n decodeCallInput(tool, call).pipe(\n Effect.map(\n Match.type<DecodeResult<unknown>>().pipe(\n Match.tag(\"parseError\", () =>\n outputEvent(validationError(call, \"arguments are not valid JSON\")),\n ),\n Match.tag(\"invalid\", ({ issues }) =>\n outputEvent(validationError(call, describeIssues(issues))),\n ),\n Match.tag(\"ok\", ({ input }) => runValidated(tool, call, input)),\n Match.exhaustive,\n ),\n ),\n ),\n )\n\nconst runValidated = (\n tool: AnyLocalTool,\n call: ToolCall,\n input: unknown,\n): Stream.Stream<ToolEvent, unknown, any> =>\n Stream.unwrap(\n Effect.gen(function* () {\n // Per-call queue: `emit` offers events as `run` produces them; the\n // progress stream drains them in real time; `Queue.end` (in the\n // `ensuring`) flushes pending events then signals a clean end, which\n // `Stream.fromQueue` treats as completion. `Queue.shutdown` would clear\n // queued items and interrupt pending takes — wrong for graceful teardown.\n const queue = yield* Queue.make<unknown, Cause.Done>({ capacity: tool.emitBufferSize })\n const emit = (event: unknown) => Effect.asVoid(Queue.offer(queue, event))\n const fiber = yield* tool\n .run(input, emit)\n .pipe(Effect.ensuring(Queue.end(queue)), Effect.forkScoped)\n\n const progress = Stream.fromQueue(queue).pipe(\n Stream.map((data) => ToolEvent.Progress({ call_id: call.call_id, tool: tool.name, data })),\n )\n const output = Stream.fromEffect(\n Fiber.join(fiber).pipe(\n Effect.matchCauseEffect({\n onSuccess: (value: unknown) =>\n Effect.succeed(ToolEvent.Output({ result: okResult(call, tool.name, value) })),\n onFailure: (cause: Cause.Cause<unknown>) => routeCause(call, cause),\n }),\n ),\n )\n return progress.pipe(Stream.concat(output))\n }),\n )\n\n// A clean single string / ToolFailed failure is absorbed as a model-visible\n// result; interruption, defects, and typed non-sentinel errors propagate.\nconst routeCause = (\n call: ToolCall,\n cause: Cause.Cause<unknown>,\n): Effect.Effect<ToolEvent, unknown> => {\n const only = cause.reasons.length === 1 ? cause.reasons[0] : undefined\n if (only !== undefined && Cause.isFailReason(only)) {\n const error = only.error\n if (typeof error === \"string\") {\n return Effect.succeed(ToolEvent.Output({ result: failed(call, \"tool_failed\", error) }))\n }\n if (error instanceof ToolFailed) {\n return Effect.succeed(\n ToolEvent.Output({ result: failed(call, error.kind ?? \"tool_failed\", error.message) }),\n )\n }\n }\n return Effect.failCause(cause)\n}\n\n// ---------------------------------------------------------------------------\n// `continueWithResults` - bridge from a `Stream<ToolEvent>` to the loop's\n// emit shape. Drains the stream to the consumer in real-time, taps every\n// `Output` into an internal Ref, and at end-of-stream emits\n// `Loop.next(build(results))`. Recipe never sees the Ref.\n//\n// Dual: data-first `continueWithResults(stream, build)` and data-last\n// `stream.pipe(continueWithResults(build))` both work.\n// ---------------------------------------------------------------------------\n\n/**\n * Append a completed turn plus tool results to a state's history, converting\n * the results to wire-format `ToolCallOutput`s. Curried so it slots directly\n * into `continueWithResults`:\n *\n * Toolkit.run(toolkit, calls).pipe(\n * Toolkit.continueWithResults(Toolkit.appendToolResults(state, turn)),\n * )\n *\n * Equivalent to `appendToHistory(state, turn, results.map(toToolCallOutput))`\n * — the helper just hides the wire-conversion step.\n */\nexport const appendToolResults =\n <S extends { readonly history: ReadonlyArray<HistoryItem> }>(state: S, turn: Turn) =>\n (results: ReadonlyArray<ToolResult>): S =>\n appendToHistory(state, turn, results.map(toToolCallOutput))\n\n/**\n * Drain a `Stream<ToolEvent>` and return the accumulated `ToolResult`s\n * from every `Output` event. One-shot — the type is `Effect<results>`,\n * not a one-element stream — so it composes naturally with `Effect.map`\n * to build state.\n *\n * Counterpart to `continueWithResults`: `continueWithResults` bundles\n * drain + emit Next into one bridge; this one is the right arm of a\n * broadcast/fork-and-merge for callers that want to vary the left arm\n * (e.g., filter/tap ToolEvents before forwarding).\n */\nexport const collectResults = <E, R>(\n stream: Stream.Stream<ToolEvent, E, R>,\n): Effect.Effect<ReadonlyArray<ToolResult>, E, R> =>\n stream.pipe(\n Stream.filter(isOutput),\n Stream.map((e) => e.result),\n Stream.runCollect,\n )\n\n/**\n * Bridge from a `Stream<ToolEvent>` to the loop's emit shape. Forwards\n * every ToolEvent downstream as a `Loop.value` and at end-of-stream emits\n * one `Loop.next(build(results))` carrying the accumulated `ToolResult`s\n * from terminal `Output` events.\n *\n * Conceptually a broadcast fork (one arm passes events through, the other\n * drains them into a state). Implemented as a single-pass Ref tap + concat\n * for zero buffering — the broadcast version is observationally equivalent\n * but holds events in a PubSub until both arms drain. The public primitives\n * (`Loop.value`, `collectResults`, `Loop.next`) compose the same pattern\n * when you need to vary an arm.\n *\n * Dual: data-first `continueWithResults(stream, build)` and data-last\n * `stream.pipe(continueWithResults(build))` both work.\n */\nexport const continueWithResults: {\n <S>(\n build: (results: ReadonlyArray<ToolResult>) => S,\n ): <E, R>(stream: Stream.Stream<ToolEvent, E, R>) => Stream.Stream<Loop.Step<ToolEvent, S>, E, R>\n <S, E, R>(\n stream: Stream.Stream<ToolEvent, E, R>,\n build: (results: ReadonlyArray<ToolResult>) => S,\n ): Stream.Stream<Loop.Step<ToolEvent, S>, E, R>\n} = Function.dual(\n 2,\n <S, E, R>(\n stream: Stream.Stream<ToolEvent, E, R>,\n build: (results: ReadonlyArray<ToolResult>) => S,\n ): Stream.Stream<Loop.Step<ToolEvent, S>, E, R> =>\n Stream.unwrap(\n Effect.gen(function* () {\n const ref = yield* Ref.make<ReadonlyArray<ToolResult>>([])\n const tapped = stream.pipe(\n Stream.tap((e) =>\n isOutput(e) ? Ref.update(ref, (acc) => Arr.append(acc, e.result)) : Effect.void,\n ),\n Stream.map(Loop.value),\n )\n const continuation = Stream.unwrap(\n Ref.get(ref).pipe(Effect.map((acc) => Loop.next(build(acc)))),\n )\n return tapped.pipe(Stream.concat(continuation))\n }),\n ),\n)\n"],"mappings":"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"}
@@ -1,11 +1,20 @@
1
- import { fromEffectSchema, make, provider, signal, toDescriptors } from "./Tool.mjs";
2
- import { isOk } from "./ToolResult.mjs";
3
- import { isOutput } from "./ToolEvent.mjs";
4
- import { compose, fromArray, make as make$1, namespace, run, wrap } from "./Toolkit.mjs";
5
- import { a as describe, n as globalExpect, o as it, t as import_dist } from "../dist-B04dlFHg.mjs";
1
+ import { RateLimited, describe } from "../domain/AiError.mjs";
2
+ import { isFailure, isOk, toToolCallOutput } from "./ToolResult.mjs";
3
+ import { fail, fromEffectSchema, make, provider, signal, toDescriptors } from "./Tool.mjs";
4
+ import { isOutput, isProgress } from "./ToolEvent.mjs";
5
+ import { compose, describeFailures, fromArray, make as make$1, namespace, run, wrap } from "./Toolkit.mjs";
6
+ import { a as describe$1, n as globalExpect, o as it, t as import_dist } from "../dist-B04dlFHg.mjs";
6
7
  import { Context, Effect, Layer, Schema, Stream, pipe } from "effect";
7
8
  //#region src/tool/Toolkit.test.ts
8
- describe("Tool.toDescriptors", () => {
9
+ const Empty = Schema.Struct({});
10
+ const toolCall = (name, id, args = "{}") => ({
11
+ type: "function_call",
12
+ call_id: id,
13
+ name,
14
+ arguments: args
15
+ });
16
+ const runToArray = (toolkit, calls, options) => run(toolkit, calls, options).pipe(Stream.runCollect, Effect.map((events) => Array.from(events)));
17
+ describe$1("Tool.toDescriptors", () => {
9
18
  const GetWeatherInput = Schema.Struct({ city: Schema.String });
10
19
  const getWeather = make({
11
20
  name: "get_weather",
@@ -43,7 +52,7 @@ describe("Tool.toDescriptors", () => {
43
52
  globalExpect(l).not.toHaveProperty("strict");
44
53
  });
45
54
  });
46
- describe("Toolkit.run - tools with R requirements", () => {
55
+ describe$1("Toolkit.run - tools with R requirements", () => {
47
56
  class WeatherApiKey extends Context.Service()("test/WeatherApiKey") {}
48
57
  class GeoApiKey extends Context.Service()("test/GeoApiKey") {}
49
58
  const Empty = Schema.Struct({});
@@ -132,7 +141,7 @@ describe("Toolkit.run - tools with R requirements", () => {
132
141
  (0, import_dist.expectTypeOf)(run(make$1(plain), [])).toEqualTypeOf();
133
142
  });
134
143
  });
135
- describe("Toolkit.make / compose / namespace - uniqueness", () => {
144
+ describe$1("Toolkit.make / compose / namespace - uniqueness", () => {
136
145
  const Empty = Schema.Struct({});
137
146
  const tool = (name) => make({
138
147
  name,
@@ -173,7 +182,7 @@ describe("Toolkit.make / compose / namespace - uniqueness", () => {
173
182
  })).toEqualTypeOf();
174
183
  });
175
184
  });
176
- describe("Toolkit.wrap - middleware", () => {
185
+ describe$1("Toolkit.wrap - middleware", () => {
177
186
  const Empty = Schema.Struct({});
178
187
  class Audit extends Context.Service()("test/Audit") {}
179
188
  const getWeather = make({
@@ -213,6 +222,151 @@ describe("Toolkit.wrap - middleware", () => {
213
222
  globalExpect(s?._tag === "Failure" && s.kind).toBe("non_local_tool");
214
223
  });
215
224
  });
225
+ describe$1("Toolkit.run - executor outcomes", () => {
226
+ const plain = (name, out) => make({
227
+ name,
228
+ description: "",
229
+ inputSchema: fromEffectSchema(Empty),
230
+ run: () => Effect.succeed(out)
231
+ });
232
+ it("absorbs a bare string failure as a tool_failed result", async () => {
233
+ const t = make({
234
+ name: "t",
235
+ description: "",
236
+ inputSchema: fromEffectSchema(Empty),
237
+ run: () => Effect.fail("nope")
238
+ });
239
+ const result = (await Effect.runPromise(runToArray(make$1(t), [toolCall("t", "1")]))).filter(isOutput)[0]?.result;
240
+ globalExpect(result).toMatchObject({
241
+ _tag: "Failure",
242
+ kind: "tool_failed",
243
+ reason: "nope"
244
+ });
245
+ });
246
+ it("absorbs Tool.fail with a custom kind", async () => {
247
+ const t = make({
248
+ name: "t",
249
+ description: "",
250
+ inputSchema: fromEffectSchema(Empty),
251
+ run: () => fail("missing", { kind: "not_found" })
252
+ });
253
+ const result = (await Effect.runPromise(runToArray(make$1(t), [toolCall("t", "1")]))).filter(isOutput)[0]?.result;
254
+ globalExpect(result).toMatchObject({
255
+ _tag: "Failure",
256
+ kind: "not_found",
257
+ reason: "missing"
258
+ });
259
+ });
260
+ it("propagates a typed non-sentinel failure on the stream", async () => {
261
+ const t = make({
262
+ name: "t",
263
+ description: "",
264
+ inputSchema: fromEffectSchema(Empty),
265
+ run: () => Effect.fail(new RateLimited({
266
+ provider: "x",
267
+ raw: null
268
+ }))
269
+ });
270
+ globalExpect((await Effect.runPromiseExit(runToArray(make$1(t), [toolCall("t", "1")])))._tag).toBe("Failure");
271
+ });
272
+ it("dies on a defect instead of masking it as a result", async () => {
273
+ const t = make({
274
+ name: "t",
275
+ description: "",
276
+ inputSchema: fromEffectSchema(Empty),
277
+ run: () => Effect.die(/* @__PURE__ */ new Error("boom"))
278
+ });
279
+ globalExpect((await Effect.runPromiseExit(runToArray(make$1(t), [toolCall("t", "1")])))._tag).toBe("Failure");
280
+ });
281
+ it("streams progress events strictly before the terminal output", async () => {
282
+ const t = make({
283
+ name: "t",
284
+ description: "",
285
+ inputSchema: fromEffectSchema(Empty),
286
+ run: (_input, emit) => emit("a").pipe(Effect.andThen(emit("b")), Effect.andThen(Effect.succeed("done")))
287
+ });
288
+ const events = await Effect.runPromise(runToArray(make$1(t), [toolCall("t", "1")]));
289
+ globalExpect(events.map((e) => e._tag)).toEqual([
290
+ "Progress",
291
+ "Progress",
292
+ "Output"
293
+ ]);
294
+ globalExpect(events.filter(isProgress).map((p) => p.data)).toEqual(["a", "b"]);
295
+ });
296
+ it("reports input validation failures as a result carrying the issue detail", async () => {
297
+ const t = make({
298
+ name: "t",
299
+ description: "",
300
+ inputSchema: fromEffectSchema(Schema.Struct({ x: Schema.Number })),
301
+ run: () => Effect.succeed(1)
302
+ });
303
+ const result = (await Effect.runPromise(runToArray(make$1(t), [toolCall("t", "1", "{\"x\":\"nope\"}")]))).filter(isOutput)[0]?.result;
304
+ globalExpect(result).toMatchObject({
305
+ _tag: "Failure",
306
+ kind: "input_validation_error"
307
+ });
308
+ globalExpect(result !== void 0 && isFailure(result) && result.reason).toBeTruthy();
309
+ });
310
+ it("normalizes empty arguments to {} for a zero-arg tool", async () => {
311
+ const result = (await Effect.runPromise(runToArray(make$1(plain("t", "ok")), [toolCall("t", "1", "")]))).filter(isOutput)[0]?.result;
312
+ globalExpect(result).toMatchObject({
313
+ _tag: "Ok",
314
+ value: "ok"
315
+ });
316
+ });
317
+ it("reports unparseable arguments as input_validation_error", async () => {
318
+ const result = (await Effect.runPromise(runToArray(make$1(plain("t", "ok")), [toolCall("t", "1", "not json")]))).filter(isOutput)[0]?.result;
319
+ globalExpect(result).toMatchObject({
320
+ _tag: "Failure",
321
+ kind: "input_validation_error"
322
+ });
323
+ });
324
+ it("a call named constructor is unknown_tool, not non_local_tool", async () => {
325
+ const result = (await Effect.runPromise(runToArray(make$1(plain("t", "ok")), [toolCall("constructor", "1")]))).filter(isOutput)[0]?.result;
326
+ globalExpect(result).toMatchObject({
327
+ _tag: "Failure",
328
+ kind: "unknown_tool"
329
+ });
330
+ });
331
+ it("serializes an undefined output as \"null\" on the wire", async () => {
332
+ const t = make({
333
+ name: "t",
334
+ description: "",
335
+ inputSchema: fromEffectSchema(Empty),
336
+ run: () => Effect.void
337
+ });
338
+ const result = (await Effect.runPromise(runToArray(make$1(t), [toolCall("t", "1")]))).filter(isOutput)[0]?.result;
339
+ globalExpect(result !== void 0 && toToolCallOutput(result).output).toBe("null");
340
+ });
341
+ it("preserves call order with concurrency 1", async () => {
342
+ const kit = make$1(plain("a", "A"), plain("b", "B"));
343
+ globalExpect((await Effect.runPromise(runToArray(kit, [toolCall("a", "a1"), toolCall("b", "b1")], { concurrency: 1 }))).filter(isOutput).map((e) => e.result.call_id)).toEqual(["a1", "b1"]);
344
+ });
345
+ it("type: run of a describeFailures toolkit has no stream error", () => {
346
+ const kit = describeFailures(make$1(make({
347
+ name: "t",
348
+ description: "",
349
+ inputSchema: fromEffectSchema(Empty),
350
+ run: () => Effect.fail(new RateLimited({
351
+ provider: "x",
352
+ raw: null
353
+ }))
354
+ })), describe);
355
+ (0, import_dist.expectTypeOf)(run(kit, [])).toEqualTypeOf();
356
+ });
357
+ it("type: run of a typed toolkit propagates the tool's E", () => {
358
+ const t = make({
359
+ name: "t",
360
+ description: "",
361
+ inputSchema: fromEffectSchema(Empty),
362
+ run: () => Effect.fail(new RateLimited({
363
+ provider: "x",
364
+ raw: null
365
+ }))
366
+ });
367
+ (0, import_dist.expectTypeOf)(run(make$1(t), [])).toEqualTypeOf();
368
+ });
369
+ });
216
370
  //#endregion
217
371
  export {};
218
372
 
@@ -1 +1 @@
1
- {"version":3,"file":"Toolkit.test.mjs","names":["Tool.make","Tool.fromEffectSchema","Tool.toDescriptors","Toolkit.run","Toolkit.make","Tool.signal","Tool.provider","Toolkit.fromArray","Toolkit.compose","Toolkit.namespace","Toolkit.wrap"],"sources":["../../src/tool/Toolkit.test.ts"],"sourcesContent":["import { Context, Effect, Layer, pipe, Schema, Stream } from \"effect\"\nimport { describe, expect, expectTypeOf, it } from \"vitest\"\nimport type { ToolCall } from \"../domain/Items.js\"\nimport { isOutput } from \"./ToolEvent.js\"\nimport { isOk } from \"./ToolResult.js\"\nimport * as Tool from \"./Tool.js\"\nimport * as Toolkit from \"./Toolkit.js\"\n\ndescribe(\"Tool.toDescriptors\", () => {\n const GetWeatherInput = Schema.Struct({ city: Schema.String })\n\n const getWeather = Tool.make({\n name: \"get_weather\",\n description: \"Look up the current temperature for a city.\",\n inputSchema: Tool.fromEffectSchema(GetWeatherInput),\n run: ({ city }) => Effect.succeed({ city, tempC: 18 }),\n })\n\n it(\"renders the input schema as a JSON Schema document\", () => {\n const [desc] = Tool.toDescriptors([getWeather])\n expect(desc?.name).toBe(\"get_weather\")\n expect(desc?.description).toBe(\"Look up the current temperature for a city.\")\n expect(desc?.inputSchema).toMatchObject({\n type: \"object\",\n properties: { city: { type: \"string\" } },\n required: [\"city\"],\n })\n })\n\n it(\"includes strict flag only when set on the tool\", () => {\n const strictTool = Tool.make({\n name: \"strict_one\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(GetWeatherInput),\n run: () => Effect.succeed({}),\n strict: true,\n })\n const looseTool = Tool.make({\n name: \"loose_one\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(GetWeatherInput),\n run: () => Effect.succeed({}),\n })\n const [s, l] = Tool.toDescriptors([strictTool, looseTool])\n expect(s?.strict).toBe(true)\n expect(l).not.toHaveProperty(\"strict\")\n })\n})\n\ndescribe(\"Toolkit.run - tools with R requirements\", () => {\n // Two distinct services, modelling the \"typed per-tool context\" use case\n // (cf. AI SDK 7's `toolsContext`). In Effect each tool declares its R, the\n // compiler enforces it, and `run` surfaces the union for the caller\n // to provide via Layer.\n type WeatherApiKeyShape = { readonly key: string }\n class WeatherApiKey extends Context.Service<WeatherApiKey, WeatherApiKeyShape>()(\n \"test/WeatherApiKey\",\n ) {}\n\n type GeoApiKeyShape = { readonly key: string }\n class GeoApiKey extends Context.Service<GeoApiKey, GeoApiKeyShape>()(\"test/GeoApiKey\") {}\n\n const Empty = Schema.Struct({})\n\n const getWeather = Tool.make({\n name: \"get_weather\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () =>\n Effect.gen(function* () {\n const { key } = yield* WeatherApiKey\n return { source: \"weather\", key }\n }),\n })\n\n const getCoords = Tool.make({\n name: \"get_coords\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () =>\n Effect.gen(function* () {\n const { key } = yield* GeoApiKey\n return { source: \"geo\", key }\n }),\n })\n\n const call = (name: string, id: string): ToolCall => ({\n type: \"function_call\",\n call_id: id,\n name,\n arguments: \"{}\",\n })\n\n it(\"propagates each tool's R into the resulting Stream's requirements\", () => {\n const stream = Toolkit.run(Toolkit.make(getWeather, getCoords), [])\n expectTypeOf(stream).toEqualTypeOf<\n Stream.Stream<import(\"./ToolEvent.js\").ToolEvent, never, WeatherApiKey | GeoApiKey>\n >()\n })\n\n it(\"runs each tool with its own service injected\", async () => {\n const layer = Layer.mergeAll(\n Layer.succeed(WeatherApiKey, { key: \"weather-123\" }),\n Layer.succeed(GeoApiKey, { key: \"geo-456\" }),\n )\n\n const program = Toolkit.run(Toolkit.make(getWeather, getCoords), [\n call(\"get_weather\", \"c1\"),\n call(\"get_coords\", \"c2\"),\n ]).pipe(Stream.runCollect, Effect.provide(layer))\n\n const events = await Effect.runPromise(program)\n const outputs = Array.from(events).filter(isOutput)\n const byCall = new Map(outputs.map((e) => [e.result.call_id, e.result]))\n\n const w = byCall.get(\"c1\")\n const g = byCall.get(\"c2\")\n expect(w !== undefined && isOk(w) && w.value).toEqual({\n source: \"weather\",\n key: \"weather-123\",\n })\n expect(g !== undefined && isOk(g) && g.value).toEqual({\n source: \"geo\",\n key: \"geo-456\",\n })\n })\n\n it(\"reports non-local kinds as non_local_tool while local calls in the same turn still run\", async () => {\n const escalate = Tool.signal({\n name: \"escalate\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n })\n const localPlain = Tool.make({\n name: \"plain\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () => Effect.succeed({ ok: true }),\n })\n\n const events = await Effect.runPromise(\n Toolkit.run(Toolkit.make(localPlain, escalate), [\n call(\"escalate\", \"s1\"),\n call(\"plain\", \"p1\"),\n call(\"ghost\", \"g1\"),\n ]).pipe(Stream.runCollect),\n )\n const byCall = new Map(\n Array.from(events)\n .filter(isOutput)\n .map((e) => [e.result.call_id, e.result] as const),\n )\n\n // Signal tool: model-visible but no local executor.\n const s = byCall.get(\"s1\")\n expect(s?._tag === \"Failure\" && s.kind).toBe(\"non_local_tool\")\n // Unknown tool stays distinct from non-local.\n const g = byCall.get(\"g1\")\n expect(g?._tag === \"Failure\" && g.kind).toBe(\"unknown_tool\")\n // The local call still executed despite the non-local sibling.\n const p = byCall.get(\"p1\")\n expect(p !== undefined && isOk(p) && p.value).toEqual({ ok: true })\n })\n\n it(\"with no service-needing tools, R is never\", () => {\n const plain = Tool.make({\n name: \"plain\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () => Effect.succeed(0),\n })\n const stream = Toolkit.run(Toolkit.make(plain), [])\n expectTypeOf(stream).toEqualTypeOf<\n Stream.Stream<import(\"./ToolEvent.js\").ToolEvent, never, never>\n >()\n })\n})\n\ndescribe(\"Toolkit.make / compose / namespace - uniqueness\", () => {\n const Empty = Schema.Struct({})\n const tool = (name: string) =>\n Tool.make({\n name,\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () => Effect.succeed(0),\n })\n\n it(\"rejects a duplicate literal name at compile time\", () => {\n const a = tool(\"search\")\n const b = Tool.provider({\n name: \"search\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n provider: \"demo\",\n config: {},\n })\n // @ts-expect-error Duplicate tool name: search\n Toolkit.make(a, b)\n })\n\n it(\"fails compose with DuplicateToolName naming the colliding sources (dynamic)\", async () => {\n const github = Toolkit.fromArray([tool(\"search\")])\n const linear = Toolkit.fromArray([tool(\"search\")])\n const exit = await Effect.runPromiseExit(Toolkit.compose(github, linear))\n expect(exit._tag).toBe(\"Failure\")\n if (exit._tag === \"Failure\") {\n const json = JSON.stringify(exit.cause)\n expect(json).toContain(\"DuplicateToolName\")\n expect(json).toContain(\"toolkit-0\")\n expect(json).toContain(\"toolkit-1\")\n }\n })\n\n it(\"namespaces names so generic names from two sources compose cleanly\", async () => {\n const github = Toolkit.namespace(\"github\", Toolkit.fromArray([tool(\"search\")]))\n const linear = Toolkit.namespace(\"linear\", Toolkit.fromArray([tool(\"search\")]))\n const composed = await Effect.runPromise(Toolkit.compose(github, linear))\n expect(Object.keys(composed).sort()).toEqual([\"github__search\", \"linear__search\"])\n })\n\n it(\"preserves each tool's R through compose\", () => {\n const stream = Effect.gen(function* () {\n const composed = yield* Toolkit.compose(Toolkit.make(tool(\"a\")), Toolkit.make(tool(\"b\")))\n return Toolkit.run(composed, [])\n })\n // No service-needing tools here -> R stays never (compose didn't poison it).\n expectTypeOf(stream).toEqualTypeOf<\n Effect.Effect<\n Stream.Stream<import(\"./ToolEvent.js\").ToolEvent, never, never>,\n Toolkit.DuplicateToolName\n >\n >()\n })\n})\n\ndescribe(\"Toolkit.wrap - middleware\", () => {\n const Empty = Schema.Struct({})\n type AuditShape = { readonly record: (name: string) => Effect.Effect<void> }\n class Audit extends Context.Service<Audit, AuditShape>()(\"test/Audit\") {}\n\n const getWeather = Tool.make({\n name: \"get_weather\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () => Effect.succeed({ tempC: 18 }),\n })\n const escalate = Tool.signal({\n name: \"escalate\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n })\n\n // A middleware that needs the Audit service -> R2 = Audit.\n const audited: Toolkit.Middleware<Audit> = (run, name) => (input, emit) =>\n Effect.gen(function* () {\n const a = yield* Audit\n yield* a.record(name)\n return yield* run(input, emit)\n })\n\n const call = (name: string, id: string): ToolCall => ({\n type: \"function_call\",\n call_id: id,\n name,\n arguments: \"{}\",\n })\n\n it(\"unions the middleware's R2 into the toolkit's requirements\", () => {\n const wrapped = Toolkit.wrap(Toolkit.make(getWeather), audited)\n const stream = Toolkit.run(wrapped, [])\n expectTypeOf(stream).toEqualTypeOf<\n Stream.Stream<import(\"./ToolEvent.js\").ToolEvent, never, Audit>\n >()\n })\n\n it(\"runs the wrapped handler and leaves non-local kinds untouched\", async () => {\n const recorded: Array<string> = []\n const wrapped = pipe(Toolkit.make(getWeather, escalate), Toolkit.wrap(audited))\n\n const events = await Effect.runPromise(\n Toolkit.run(wrapped, [call(\"get_weather\", \"w1\"), call(\"escalate\", \"s1\")]).pipe(\n Stream.runCollect,\n Effect.provideService(Audit, { record: (n) => Effect.sync(() => void recorded.push(n)) }),\n ),\n )\n const byCall = new Map(\n Array.from(events)\n .filter(isOutput)\n .map((e) => [e.result.call_id, e.result] as const),\n )\n\n // Local tool ran through the middleware (audit fired).\n expect(recorded).toEqual([\"get_weather\"])\n const w = byCall.get(\"w1\")\n expect(w !== undefined && isOk(w) && w.value).toEqual({ tempC: 18 })\n // Signal kind is left alone by wrap -> still reported non-local.\n const s = byCall.get(\"s1\")\n expect(s?._tag === \"Failure\" && s.kind).toBe(\"non_local_tool\")\n })\n})\n"],"mappings":";;;;;;;AAQA,SAAS,4BAA4B;CACnC,MAAM,kBAAkB,OAAO,OAAO,EAAE,MAAM,OAAO,QAAQ,CAAC;CAE9D,MAAM,aAAaA,KAAU;EAC3B,MAAM;EACN,aAAa;EACb,aAAaC,iBAAsB,gBAAgB;EACnD,MAAM,EAAE,WAAW,OAAO,QAAQ;GAAE;GAAM,OAAO;GAAI,CAAC;EACvD,CAAC;AAEF,IAAG,4DAA4D;EAC7D,MAAM,CAAC,QAAQC,cAAmB,CAAC,WAAW,CAAC;AAC/C,eAAO,MAAM,KAAK,CAAC,KAAK,cAAc;AACtC,eAAO,MAAM,YAAY,CAAC,KAAK,8CAA8C;AAC7E,eAAO,MAAM,YAAY,CAAC,cAAc;GACtC,MAAM;GACN,YAAY,EAAE,MAAM,EAAE,MAAM,UAAU,EAAE;GACxC,UAAU,CAAC,OAAO;GACnB,CAAC;GACF;AAEF,IAAG,wDAAwD;EAczD,MAAM,CAAC,GAAG,KAAKA,cAAmB,CAbfF,KAAU;GAC3B,MAAM;GACN,aAAa;GACb,aAAaC,iBAAsB,gBAAgB;GACnD,WAAW,OAAO,QAAQ,EAAE,CAAC;GAC7B,QAAQ;GACT,CAOkC,EANjBD,KAAU;GAC1B,MAAM;GACN,aAAa;GACb,aAAaC,iBAAsB,gBAAgB;GACnD,WAAW,OAAO,QAAQ,EAAE,CAAC;GAC9B,CAC8C,CAAU,CAAC;AAC1D,eAAO,GAAG,OAAO,CAAC,KAAK,KAAK;AAC5B,eAAO,EAAE,CAAC,IAAI,eAAe,SAAS;GACtC;EACF;AAEF,SAAS,iDAAiD;CAMxD,MAAM,sBAAsB,QAAQ,SAA4C,CAC9E,qBACD,CAAC;CAGF,MAAM,kBAAkB,QAAQ,SAAoC,CAAC,iBAAiB,CAAC;CAEvF,MAAM,QAAQ,OAAO,OAAO,EAAE,CAAC;CAE/B,MAAM,aAAaD,KAAU;EAC3B,MAAM;EACN,aAAa;EACb,aAAaC,iBAAsB,MAAM;EACzC,WACE,OAAO,IAAI,aAAa;GACtB,MAAM,EAAE,QAAQ,OAAO;AACvB,UAAO;IAAE,QAAQ;IAAW;IAAK;IACjC;EACL,CAAC;CAEF,MAAM,YAAYD,KAAU;EAC1B,MAAM;EACN,aAAa;EACb,aAAaC,iBAAsB,MAAM;EACzC,WACE,OAAO,IAAI,aAAa;GACtB,MAAM,EAAE,QAAQ,OAAO;AACvB,UAAO;IAAE,QAAQ;IAAO;IAAK;IAC7B;EACL,CAAC;CAEF,MAAM,QAAQ,MAAc,QAA0B;EACpD,MAAM;EACN,SAAS;EACT;EACA,WAAW;EACZ;AAED,IAAG,2EAA2E;AAE5E,GAAA,GAAA,YAAA,cADeE,IAAYC,OAAa,YAAY,UAAU,EAAE,EAAE,CAC/C,CAAC,CAAC,eAElB;GACH;AAEF,IAAG,gDAAgD,YAAY;EAC7D,MAAM,QAAQ,MAAM,SAClB,MAAM,QAAQ,eAAe,EAAE,KAAK,eAAe,CAAC,EACpD,MAAM,QAAQ,WAAW,EAAE,KAAK,WAAW,CAAC,CAC7C;EAED,MAAM,UAAUD,IAAYC,OAAa,YAAY,UAAU,EAAE,CAC/D,KAAK,eAAe,KAAK,EACzB,KAAK,cAAc,KAAK,CACzB,CAAC,CAAC,KAAK,OAAO,YAAY,OAAO,QAAQ,MAAM,CAAC;EAEjD,MAAM,SAAS,MAAM,OAAO,WAAW,QAAQ;EAC/C,MAAM,UAAU,MAAM,KAAK,OAAO,CAAC,OAAO,SAAS;EACnD,MAAM,SAAS,IAAI,IAAI,QAAQ,KAAK,MAAM,CAAC,EAAE,OAAO,SAAS,EAAE,OAAO,CAAC,CAAC;EAExE,MAAM,IAAI,OAAO,IAAI,KAAK;EAC1B,MAAM,IAAI,OAAO,IAAI,KAAK;AAC1B,eAAO,MAAM,KAAA,KAAa,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,QAAQ;GACpD,QAAQ;GACR,KAAK;GACN,CAAC;AACF,eAAO,MAAM,KAAA,KAAa,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,QAAQ;GACpD,QAAQ;GACR,KAAK;GACN,CAAC;GACF;AAEF,IAAG,0FAA0F,YAAY;EACvG,MAAM,WAAWC,OAAY;GAC3B,MAAM;GACN,aAAa;GACb,aAAaJ,iBAAsB,MAAM;GAC1C,CAAC;EACF,MAAM,aAAaD,KAAU;GAC3B,MAAM;GACN,aAAa;GACb,aAAaC,iBAAsB,MAAM;GACzC,WAAW,OAAO,QAAQ,EAAE,IAAI,MAAM,CAAC;GACxC,CAAC;EAEF,MAAM,SAAS,MAAM,OAAO,WAC1BE,IAAYC,OAAa,YAAY,SAAS,EAAE;GAC9C,KAAK,YAAY,KAAK;GACtB,KAAK,SAAS,KAAK;GACnB,KAAK,SAAS,KAAK;GACpB,CAAC,CAAC,KAAK,OAAO,WAAW,CAC3B;EACD,MAAM,SAAS,IAAI,IACjB,MAAM,KAAK,OAAO,CACf,OAAO,SAAS,CAChB,KAAK,MAAM,CAAC,EAAE,OAAO,SAAS,EAAE,OAAO,CAAU,CACrD;EAGD,MAAM,IAAI,OAAO,IAAI,KAAK;AAC1B,eAAO,GAAG,SAAS,aAAa,EAAE,KAAK,CAAC,KAAK,iBAAiB;EAE9D,MAAM,IAAI,OAAO,IAAI,KAAK;AAC1B,eAAO,GAAG,SAAS,aAAa,EAAE,KAAK,CAAC,KAAK,eAAe;EAE5D,MAAM,IAAI,OAAO,IAAI,KAAK;AAC1B,eAAO,MAAM,KAAA,KAAa,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,QAAQ,EAAE,IAAI,MAAM,CAAC;GACnE;AAEF,IAAG,mDAAmD;EACpD,MAAM,QAAQJ,KAAU;GACtB,MAAM;GACN,aAAa;GACb,aAAaC,iBAAsB,MAAM;GACzC,WAAW,OAAO,QAAQ,EAAE;GAC7B,CAAC;AAEF,GAAA,GAAA,YAAA,cADeE,IAAYC,OAAa,MAAM,EAAE,EAAE,CAC/B,CAAC,CAAC,eAElB;GACH;EACF;AAEF,SAAS,yDAAyD;CAChE,MAAM,QAAQ,OAAO,OAAO,EAAE,CAAC;CAC/B,MAAM,QAAQ,SACZJ,KAAU;EACR;EACA,aAAa;EACb,aAAaC,iBAAsB,MAAM;EACzC,WAAW,OAAO,QAAQ,EAAE;EAC7B,CAAC;AAEJ,IAAG,0DAA0D;AAU3D,SATU,KAAK,SASF,EARHK,SAAc;GACtB,MAAM;GACN,aAAa;GACb,aAAaL,iBAAsB,MAAM;GACzC,UAAU;GACV,QAAQ,EAAE;GACX,CAEe,CAAE;GAClB;AAEF,IAAG,+EAA+E,YAAY;EAC5F,MAAM,SAASM,UAAkB,CAAC,KAAK,SAAS,CAAC,CAAC;EAClD,MAAM,SAASA,UAAkB,CAAC,KAAK,SAAS,CAAC,CAAC;EAClD,MAAM,OAAO,MAAM,OAAO,eAAeC,QAAgB,QAAQ,OAAO,CAAC;AACzE,eAAO,KAAK,KAAK,CAAC,KAAK,UAAU;AACjC,MAAI,KAAK,SAAS,WAAW;GAC3B,MAAM,OAAO,KAAK,UAAU,KAAK,MAAM;AACvC,gBAAO,KAAK,CAAC,UAAU,oBAAoB;AAC3C,gBAAO,KAAK,CAAC,UAAU,YAAY;AACnC,gBAAO,KAAK,CAAC,UAAU,YAAY;;GAErC;AAEF,IAAG,sEAAsE,YAAY;EACnF,MAAM,SAASC,UAAkB,UAAUF,UAAkB,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC;EAC/E,MAAM,SAASE,UAAkB,UAAUF,UAAkB,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC;EAC/E,MAAM,WAAW,MAAM,OAAO,WAAWC,QAAgB,QAAQ,OAAO,CAAC;AACzE,eAAO,OAAO,KAAK,SAAS,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,kBAAkB,iBAAiB,CAAC;GAClF;AAEF,IAAG,iDAAiD;AAMlD,GAAA,GAAA,YAAA,cALe,OAAO,IAAI,aAAa;AAErC,UAAOL,IAAY,OADKK,QAAgBJ,OAAa,KAAK,IAAI,CAAC,EAAEA,OAAa,KAAK,IAAI,CAAC,CAAC,EAC5D,EAAE,CAAC;IAGf,CAAC,CAAC,eAKlB;GACH;EACF;AAEF,SAAS,mCAAmC;CAC1C,MAAM,QAAQ,OAAO,OAAO,EAAE,CAAC;CAE/B,MAAM,cAAc,QAAQ,SAA4B,CAAC,aAAa,CAAC;CAEvE,MAAM,aAAaJ,KAAU;EAC3B,MAAM;EACN,aAAa;EACb,aAAaC,iBAAsB,MAAM;EACzC,WAAW,OAAO,QAAQ,EAAE,OAAO,IAAI,CAAC;EACzC,CAAC;CACF,MAAM,WAAWI,OAAY;EAC3B,MAAM;EACN,aAAa;EACb,aAAaJ,iBAAsB,MAAM;EAC1C,CAAC;CAGF,MAAM,WAAsC,KAAK,UAAU,OAAO,SAChE,OAAO,IAAI,aAAa;AAEtB,UAAO,OADU,OACR,OAAO,KAAK;AACrB,SAAO,OAAO,IAAI,OAAO,KAAK;GAC9B;CAEJ,MAAM,QAAQ,MAAc,QAA0B;EACpD,MAAM;EACN,SAAS;EACT;EACA,WAAW;EACZ;AAED,IAAG,oEAAoE;EACrE,MAAM,UAAUS,KAAaN,OAAa,WAAW,EAAE,QAAQ;AAE/D,GAAA,GAAA,YAAA,cADeD,IAAY,SAAS,EAAE,CACnB,CAAC,CAAC,eAElB;GACH;AAEF,IAAG,iEAAiE,YAAY;EAC9E,MAAM,WAA0B,EAAE;EAClC,MAAM,UAAU,KAAKC,OAAa,YAAY,SAAS,EAAEM,KAAa,QAAQ,CAAC;EAE/E,MAAM,SAAS,MAAM,OAAO,WAC1BP,IAAY,SAAS,CAAC,KAAK,eAAe,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KACxE,OAAO,YACP,OAAO,eAAe,OAAO,EAAE,SAAS,MAAM,OAAO,WAAW,KAAK,SAAS,KAAK,EAAE,CAAC,EAAE,CAAC,CAC1F,CACF;EACD,MAAM,SAAS,IAAI,IACjB,MAAM,KAAK,OAAO,CACf,OAAO,SAAS,CAChB,KAAK,MAAM,CAAC,EAAE,OAAO,SAAS,EAAE,OAAO,CAAU,CACrD;AAGD,eAAO,SAAS,CAAC,QAAQ,CAAC,cAAc,CAAC;EACzC,MAAM,IAAI,OAAO,IAAI,KAAK;AAC1B,eAAO,MAAM,KAAA,KAAa,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,QAAQ,EAAE,OAAO,IAAI,CAAC;EAEpE,MAAM,IAAI,OAAO,IAAI,KAAK;AAC1B,eAAO,GAAG,SAAS,aAAa,EAAE,KAAK,CAAC,KAAK,iBAAiB;GAC9D;EACF"}
1
+ {"version":3,"file":"Toolkit.test.mjs","names":["Toolkit.run","describe","Tool.make","Tool.fromEffectSchema","Tool.toDescriptors","Toolkit.make","Tool.signal","Tool.provider","Toolkit.fromArray","Toolkit.compose","Toolkit.namespace","Toolkit.wrap","Tool.fail","AiError.RateLimited","Toolkit.describeFailures","AiError.describe"],"sources":["../../src/tool/Toolkit.test.ts"],"sourcesContent":["import { Context, Effect, Layer, pipe, Schema, Stream } from \"effect\"\nimport { describe, expect, expectTypeOf, it } from \"vitest\"\nimport * as AiError from \"../domain/AiError.js\"\nimport type { ToolCall } from \"../domain/Items.js\"\nimport { type ToolEvent, isOutput, isProgress } from \"./ToolEvent.js\"\nimport { isFailure, isOk, toToolCallOutput } from \"./ToolResult.js\"\nimport * as Tool from \"./Tool.js\"\nimport * as Toolkit from \"./Toolkit.js\"\n\nconst Empty = Schema.Struct({})\nconst toolCall = (name: string, id: string, args = \"{}\"): ToolCall => ({\n type: \"function_call\",\n call_id: id,\n name,\n arguments: args,\n})\nconst runToArray = <T extends Toolkit.Toolkit>(\n toolkit: T,\n calls: ReadonlyArray<ToolCall>,\n options?: Toolkit.ExecuteOptions,\n) =>\n Toolkit.run(toolkit, calls, options).pipe(\n Stream.runCollect,\n Effect.map((events): ReadonlyArray<ToolEvent> => Array.from(events)),\n )\n\ndescribe(\"Tool.toDescriptors\", () => {\n const GetWeatherInput = Schema.Struct({ city: Schema.String })\n\n const getWeather = Tool.make({\n name: \"get_weather\",\n description: \"Look up the current temperature for a city.\",\n inputSchema: Tool.fromEffectSchema(GetWeatherInput),\n run: ({ city }) => Effect.succeed({ city, tempC: 18 }),\n })\n\n it(\"renders the input schema as a JSON Schema document\", () => {\n const [desc] = Tool.toDescriptors([getWeather])\n expect(desc?.name).toBe(\"get_weather\")\n expect(desc?.description).toBe(\"Look up the current temperature for a city.\")\n expect(desc?.inputSchema).toMatchObject({\n type: \"object\",\n properties: { city: { type: \"string\" } },\n required: [\"city\"],\n })\n })\n\n it(\"includes strict flag only when set on the tool\", () => {\n const strictTool = Tool.make({\n name: \"strict_one\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(GetWeatherInput),\n run: () => Effect.succeed({}),\n strict: true,\n })\n const looseTool = Tool.make({\n name: \"loose_one\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(GetWeatherInput),\n run: () => Effect.succeed({}),\n })\n const [s, l] = Tool.toDescriptors([strictTool, looseTool])\n expect(s?.strict).toBe(true)\n expect(l).not.toHaveProperty(\"strict\")\n })\n})\n\ndescribe(\"Toolkit.run - tools with R requirements\", () => {\n // Two distinct services, modelling the \"typed per-tool context\" use case\n // (cf. AI SDK 7's `toolsContext`). In Effect each tool declares its R, the\n // compiler enforces it, and `run` surfaces the union for the caller\n // to provide via Layer.\n type WeatherApiKeyShape = { readonly key: string }\n class WeatherApiKey extends Context.Service<WeatherApiKey, WeatherApiKeyShape>()(\n \"test/WeatherApiKey\",\n ) {}\n\n type GeoApiKeyShape = { readonly key: string }\n class GeoApiKey extends Context.Service<GeoApiKey, GeoApiKeyShape>()(\"test/GeoApiKey\") {}\n\n const Empty = Schema.Struct({})\n\n const getWeather = Tool.make({\n name: \"get_weather\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () =>\n Effect.gen(function* () {\n const { key } = yield* WeatherApiKey\n return { source: \"weather\", key }\n }),\n })\n\n const getCoords = Tool.make({\n name: \"get_coords\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () =>\n Effect.gen(function* () {\n const { key } = yield* GeoApiKey\n return { source: \"geo\", key }\n }),\n })\n\n const call = (name: string, id: string): ToolCall => ({\n type: \"function_call\",\n call_id: id,\n name,\n arguments: \"{}\",\n })\n\n it(\"propagates each tool's R into the resulting Stream's requirements\", () => {\n const stream = Toolkit.run(Toolkit.make(getWeather, getCoords), [])\n expectTypeOf(stream).toEqualTypeOf<\n Stream.Stream<import(\"./ToolEvent.js\").ToolEvent, never, WeatherApiKey | GeoApiKey>\n >()\n })\n\n it(\"runs each tool with its own service injected\", async () => {\n const layer = Layer.mergeAll(\n Layer.succeed(WeatherApiKey, { key: \"weather-123\" }),\n Layer.succeed(GeoApiKey, { key: \"geo-456\" }),\n )\n\n const program = Toolkit.run(Toolkit.make(getWeather, getCoords), [\n call(\"get_weather\", \"c1\"),\n call(\"get_coords\", \"c2\"),\n ]).pipe(Stream.runCollect, Effect.provide(layer))\n\n const events = await Effect.runPromise(program)\n const outputs = Array.from(events).filter(isOutput)\n const byCall = new Map(outputs.map((e) => [e.result.call_id, e.result]))\n\n const w = byCall.get(\"c1\")\n const g = byCall.get(\"c2\")\n expect(w !== undefined && isOk(w) && w.value).toEqual({\n source: \"weather\",\n key: \"weather-123\",\n })\n expect(g !== undefined && isOk(g) && g.value).toEqual({\n source: \"geo\",\n key: \"geo-456\",\n })\n })\n\n it(\"reports non-local kinds as non_local_tool while local calls in the same turn still run\", async () => {\n const escalate = Tool.signal({\n name: \"escalate\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n })\n const localPlain = Tool.make({\n name: \"plain\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () => Effect.succeed({ ok: true }),\n })\n\n const events = await Effect.runPromise(\n Toolkit.run(Toolkit.make(localPlain, escalate), [\n call(\"escalate\", \"s1\"),\n call(\"plain\", \"p1\"),\n call(\"ghost\", \"g1\"),\n ]).pipe(Stream.runCollect),\n )\n const byCall = new Map(\n Array.from(events)\n .filter(isOutput)\n .map((e) => [e.result.call_id, e.result] as const),\n )\n\n // Signal tool: model-visible but no local executor.\n const s = byCall.get(\"s1\")\n expect(s?._tag === \"Failure\" && s.kind).toBe(\"non_local_tool\")\n // Unknown tool stays distinct from non-local.\n const g = byCall.get(\"g1\")\n expect(g?._tag === \"Failure\" && g.kind).toBe(\"unknown_tool\")\n // The local call still executed despite the non-local sibling.\n const p = byCall.get(\"p1\")\n expect(p !== undefined && isOk(p) && p.value).toEqual({ ok: true })\n })\n\n it(\"with no service-needing tools, R is never\", () => {\n const plain = Tool.make({\n name: \"plain\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () => Effect.succeed(0),\n })\n const stream = Toolkit.run(Toolkit.make(plain), [])\n expectTypeOf(stream).toEqualTypeOf<\n Stream.Stream<import(\"./ToolEvent.js\").ToolEvent, never, never>\n >()\n })\n})\n\ndescribe(\"Toolkit.make / compose / namespace - uniqueness\", () => {\n const Empty = Schema.Struct({})\n const tool = (name: string) =>\n Tool.make({\n name,\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () => Effect.succeed(0),\n })\n\n it(\"rejects a duplicate literal name at compile time\", () => {\n const a = tool(\"search\")\n const b = Tool.provider({\n name: \"search\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n provider: \"demo\",\n config: {},\n })\n // @ts-expect-error Duplicate tool name: search\n Toolkit.make(a, b)\n })\n\n it(\"fails compose with DuplicateToolName naming the colliding sources (dynamic)\", async () => {\n const github = Toolkit.fromArray([tool(\"search\")])\n const linear = Toolkit.fromArray([tool(\"search\")])\n const exit = await Effect.runPromiseExit(Toolkit.compose(github, linear))\n expect(exit._tag).toBe(\"Failure\")\n if (exit._tag === \"Failure\") {\n const json = JSON.stringify(exit.cause)\n expect(json).toContain(\"DuplicateToolName\")\n expect(json).toContain(\"toolkit-0\")\n expect(json).toContain(\"toolkit-1\")\n }\n })\n\n it(\"namespaces names so generic names from two sources compose cleanly\", async () => {\n const github = Toolkit.namespace(\"github\", Toolkit.fromArray([tool(\"search\")]))\n const linear = Toolkit.namespace(\"linear\", Toolkit.fromArray([tool(\"search\")]))\n const composed = await Effect.runPromise(Toolkit.compose(github, linear))\n expect(Object.keys(composed).sort()).toEqual([\"github__search\", \"linear__search\"])\n })\n\n it(\"preserves each tool's R through compose\", () => {\n const stream = Effect.gen(function* () {\n const composed = yield* Toolkit.compose(Toolkit.make(tool(\"a\")), Toolkit.make(tool(\"b\")))\n return Toolkit.run(composed, [])\n })\n // No service-needing tools here -> R stays never (compose didn't poison it).\n expectTypeOf(stream).toEqualTypeOf<\n Effect.Effect<\n Stream.Stream<import(\"./ToolEvent.js\").ToolEvent, never, never>,\n Toolkit.DuplicateToolName\n >\n >()\n })\n})\n\ndescribe(\"Toolkit.wrap - middleware\", () => {\n const Empty = Schema.Struct({})\n type AuditShape = { readonly record: (name: string) => Effect.Effect<void> }\n class Audit extends Context.Service<Audit, AuditShape>()(\"test/Audit\") {}\n\n const getWeather = Tool.make({\n name: \"get_weather\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () => Effect.succeed({ tempC: 18 }),\n })\n const escalate = Tool.signal({\n name: \"escalate\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n })\n\n // A middleware that needs the Audit service -> R2 = Audit.\n const audited: Toolkit.Middleware<Audit> = (run, name) => (input, emit) =>\n Effect.gen(function* () {\n const a = yield* Audit\n yield* a.record(name)\n return yield* run(input, emit)\n })\n\n const call = (name: string, id: string): ToolCall => ({\n type: \"function_call\",\n call_id: id,\n name,\n arguments: \"{}\",\n })\n\n it(\"unions the middleware's R2 into the toolkit's requirements\", () => {\n const wrapped = Toolkit.wrap(Toolkit.make(getWeather), audited)\n const stream = Toolkit.run(wrapped, [])\n expectTypeOf(stream).toEqualTypeOf<\n Stream.Stream<import(\"./ToolEvent.js\").ToolEvent, never, Audit>\n >()\n })\n\n it(\"runs the wrapped handler and leaves non-local kinds untouched\", async () => {\n const recorded: Array<string> = []\n const wrapped = pipe(Toolkit.make(getWeather, escalate), Toolkit.wrap(audited))\n\n const events = await Effect.runPromise(\n Toolkit.run(wrapped, [call(\"get_weather\", \"w1\"), call(\"escalate\", \"s1\")]).pipe(\n Stream.runCollect,\n Effect.provideService(Audit, { record: (n) => Effect.sync(() => void recorded.push(n)) }),\n ),\n )\n const byCall = new Map(\n Array.from(events)\n .filter(isOutput)\n .map((e) => [e.result.call_id, e.result] as const),\n )\n\n // Local tool ran through the middleware (audit fired).\n expect(recorded).toEqual([\"get_weather\"])\n const w = byCall.get(\"w1\")\n expect(w !== undefined && isOk(w) && w.value).toEqual({ tempC: 18 })\n // Signal kind is left alone by wrap -> still reported non-local.\n const s = byCall.get(\"s1\")\n expect(s?._tag === \"Failure\" && s.kind).toBe(\"non_local_tool\")\n })\n})\n\ndescribe(\"Toolkit.run - executor outcomes\", () => {\n const plain = <N extends string>(name: N, out: unknown) =>\n Tool.make({\n name,\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () => Effect.succeed(out),\n })\n\n it(\"absorbs a bare string failure as a tool_failed result\", async () => {\n const t = Tool.make({\n name: \"t\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () => Effect.fail(\"nope\"),\n })\n const events = await Effect.runPromise(runToArray(Toolkit.make(t), [toolCall(\"t\", \"1\")]))\n const result = events.filter(isOutput)[0]?.result\n expect(result).toMatchObject({ _tag: \"Failure\", kind: \"tool_failed\", reason: \"nope\" })\n })\n\n it(\"absorbs Tool.fail with a custom kind\", async () => {\n const t = Tool.make({\n name: \"t\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () => Tool.fail(\"missing\", { kind: \"not_found\" }),\n })\n const events = await Effect.runPromise(runToArray(Toolkit.make(t), [toolCall(\"t\", \"1\")]))\n const result = events.filter(isOutput)[0]?.result\n expect(result).toMatchObject({ _tag: \"Failure\", kind: \"not_found\", reason: \"missing\" })\n })\n\n it(\"propagates a typed non-sentinel failure on the stream\", async () => {\n const t = Tool.make({\n name: \"t\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () => Effect.fail(new AiError.RateLimited({ provider: \"x\", raw: null })),\n })\n const exit = await Effect.runPromiseExit(runToArray(Toolkit.make(t), [toolCall(\"t\", \"1\")]))\n expect(exit._tag).toBe(\"Failure\")\n })\n\n it(\"dies on a defect instead of masking it as a result\", async () => {\n const t = Tool.make({\n name: \"t\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () => Effect.die(new Error(\"boom\")),\n })\n const exit = await Effect.runPromiseExit(runToArray(Toolkit.make(t), [toolCall(\"t\", \"1\")]))\n expect(exit._tag).toBe(\"Failure\")\n })\n\n it(\"streams progress events strictly before the terminal output\", async () => {\n const t = Tool.make({\n name: \"t\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: (_input, emit) =>\n emit(\"a\").pipe(Effect.andThen(emit(\"b\")), Effect.andThen(Effect.succeed(\"done\"))),\n })\n const events = await Effect.runPromise(runToArray(Toolkit.make(t), [toolCall(\"t\", \"1\")]))\n expect(events.map((e) => e._tag)).toEqual([\"Progress\", \"Progress\", \"Output\"])\n expect(events.filter(isProgress).map((p) => p.data)).toEqual([\"a\", \"b\"])\n })\n\n it(\"reports input validation failures as a result carrying the issue detail\", async () => {\n const t = Tool.make({\n name: \"t\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Schema.Struct({ x: Schema.Number })),\n run: () => Effect.succeed(1),\n })\n const events = await Effect.runPromise(\n runToArray(Toolkit.make(t), [toolCall(\"t\", \"1\", '{\"x\":\"nope\"}')]),\n )\n const result = events.filter(isOutput)[0]?.result\n expect(result).toMatchObject({ _tag: \"Failure\", kind: \"input_validation_error\" })\n expect(result !== undefined && isFailure(result) && result.reason).toBeTruthy()\n })\n\n it(\"normalizes empty arguments to {} for a zero-arg tool\", async () => {\n const events = await Effect.runPromise(\n runToArray(Toolkit.make(plain(\"t\", \"ok\")), [toolCall(\"t\", \"1\", \"\")]),\n )\n const result = events.filter(isOutput)[0]?.result\n expect(result).toMatchObject({ _tag: \"Ok\", value: \"ok\" })\n })\n\n it(\"reports unparseable arguments as input_validation_error\", async () => {\n const events = await Effect.runPromise(\n runToArray(Toolkit.make(plain(\"t\", \"ok\")), [toolCall(\"t\", \"1\", \"not json\")]),\n )\n const result = events.filter(isOutput)[0]?.result\n expect(result).toMatchObject({ _tag: \"Failure\", kind: \"input_validation_error\" })\n })\n\n it(\"a call named constructor is unknown_tool, not non_local_tool\", async () => {\n const events = await Effect.runPromise(\n runToArray(Toolkit.make(plain(\"t\", \"ok\")), [toolCall(\"constructor\", \"1\")]),\n )\n const result = events.filter(isOutput)[0]?.result\n expect(result).toMatchObject({ _tag: \"Failure\", kind: \"unknown_tool\" })\n })\n\n it('serializes an undefined output as \"null\" on the wire', async () => {\n const t = Tool.make({\n name: \"t\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () => Effect.void,\n })\n const events = await Effect.runPromise(runToArray(Toolkit.make(t), [toolCall(\"t\", \"1\")]))\n const result = events.filter(isOutput)[0]?.result\n expect(result !== undefined && toToolCallOutput(result).output).toBe(\"null\")\n })\n\n it(\"preserves call order with concurrency 1\", async () => {\n const kit = Toolkit.make(plain(\"a\", \"A\"), plain(\"b\", \"B\"))\n const events = await Effect.runPromise(\n runToArray(kit, [toolCall(\"a\", \"a1\"), toolCall(\"b\", \"b1\")], { concurrency: 1 }),\n )\n expect(events.filter(isOutput).map((e) => e.result.call_id)).toEqual([\"a1\", \"b1\"])\n })\n\n it(\"type: run of a describeFailures toolkit has no stream error\", () => {\n const t = Tool.make({\n name: \"t\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () => Effect.fail(new AiError.RateLimited({ provider: \"x\", raw: null })),\n })\n const kit = Toolkit.describeFailures(Toolkit.make(t), AiError.describe)\n const stream = Toolkit.run(kit, [])\n expectTypeOf(stream).toEqualTypeOf<Stream.Stream<ToolEvent, never, never>>()\n })\n\n it(\"type: run of a typed toolkit propagates the tool's E\", () => {\n const t = Tool.make({\n name: \"t\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () => Effect.fail(new AiError.RateLimited({ provider: \"x\", raw: null })),\n })\n const stream = Toolkit.run(Toolkit.make(t), [])\n expectTypeOf(stream).toEqualTypeOf<Stream.Stream<ToolEvent, AiError.RateLimited, never>>()\n })\n})\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { t as AiError } from "../AiError-CrdR1EQi.mjs";
2
- import { a as AudioSource, r as AudioFormat } from "../Audio-DDSKCkSe.mjs";
1
+ import { t as AiError } from "../AiError-Bf1pq4BB.mjs";
2
+ import { a as AudioSource, r as AudioFormat } from "../Audio-CR7CSljJ.mjs";
3
3
  import { TranscriptEvent, TranscriptResult } from "../domain/Transcript.mjs";
4
4
  import { Context, Effect, Stream } from "effect";
5
5