@effect-uai/core 0.17.0 → 0.19.0-dev-20261109183959

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 (218) hide show
  1. package/README.md +5 -5
  2. package/dist/Content-D_Qfn8Ja.d.mts +48 -0
  3. package/dist/Content-D_Qfn8Ja.d.mts.map +1 -0
  4. package/dist/File-B923KvZ9.d.mts +46 -0
  5. package/dist/File-B923KvZ9.d.mts.map +1 -0
  6. package/dist/{Image-Cv6iBLTy.d.mts → Image-Bzc1H_UK.d.mts} +19 -8
  7. package/dist/Image-Bzc1H_UK.d.mts.map +1 -0
  8. package/dist/{Items-C7MyrYPX.d.mts → Items-Bvx8s9QH.d.mts} +109 -16
  9. package/dist/Items-Bvx8s9QH.d.mts.map +1 -0
  10. package/dist/{Loop-ChpaO-ya.d.mts → Loop-CeA0AelN.d.mts} +7 -6
  11. package/dist/Loop-CeA0AelN.d.mts.map +1 -0
  12. package/dist/Tool-Bkryw6-h.d.mts +206 -0
  13. package/dist/Tool-Bkryw6-h.d.mts.map +1 -0
  14. package/dist/ToolEvent-9inLx5cI.d.mts +22 -0
  15. package/dist/ToolEvent-9inLx5cI.d.mts.map +1 -0
  16. package/dist/ToolOutput-BNTIQgGB.d.mts +151 -0
  17. package/dist/ToolOutput-BNTIQgGB.d.mts.map +1 -0
  18. package/dist/Toolkit-D2VGeD5R.d.mts +212 -0
  19. package/dist/Toolkit-D2VGeD5R.d.mts.map +1 -0
  20. package/dist/browser/BrowserTool.d.mts +84 -22
  21. package/dist/browser/BrowserTool.d.mts.map +1 -1
  22. package/dist/browser/BrowserTool.mjs +5 -0
  23. package/dist/browser/BrowserTool.mjs.map +1 -1
  24. package/dist/capabilities/Capabilities.d.mts +1 -1
  25. package/dist/decision-model/Decision.test.mjs +4 -4
  26. package/dist/dist-B5K-y4Be.mjs +191 -0
  27. package/dist/dist-B5K-y4Be.mjs.map +1 -0
  28. package/dist/dist-CorQTQL-.mjs +1533 -0
  29. package/dist/dist-CorQTQL-.mjs.map +1 -0
  30. package/dist/dist-DNeuh84d.mjs +7 -0
  31. package/dist/dist-DNeuh84d.mjs.map +1 -0
  32. package/dist/dist-DzeSyeYF.mjs +10253 -0
  33. package/dist/dist-DzeSyeYF.mjs.map +1 -0
  34. package/dist/domain/File.d.mts +2 -0
  35. package/dist/domain/File.mjs +51 -0
  36. package/dist/domain/File.mjs.map +1 -0
  37. package/dist/domain/Image.d.mts +2 -2
  38. package/dist/domain/Image.mjs +12 -2
  39. package/dist/domain/Image.mjs.map +1 -1
  40. package/dist/domain/Items.d.mts +2 -2
  41. package/dist/domain/Items.mjs +65 -4
  42. package/dist/domain/Items.mjs.map +1 -1
  43. package/dist/domain/Realtime.d.mts +72 -490
  44. package/dist/domain/Realtime.d.mts.map +1 -1
  45. package/dist/domain/Research.d.mts +1 -1
  46. package/dist/domain/Transcript.test.mjs +3 -3
  47. package/dist/domain/Turn.d.mts +57 -262
  48. package/dist/domain/Turn.d.mts.map +1 -1
  49. package/dist/domain/Turn.mjs +1 -1
  50. package/dist/domain/Turn.test.mjs +2 -2
  51. package/dist/embedding-model/Embedding.d.mts +1 -1
  52. package/dist/embedding-model/EmbeddingModel.test.mjs +2 -2
  53. package/dist/embedding-model/EmbeddingModel.test.mjs.map +1 -1
  54. package/dist/image-generator/ImageGenerator.d.mts +10 -66
  55. package/dist/image-generator/ImageGenerator.d.mts.map +1 -1
  56. package/dist/index.C4tKZ0yW-LuFnBtmu.mjs +7042 -0
  57. package/dist/index.C4tKZ0yW-LuFnBtmu.mjs.map +1 -0
  58. package/dist/index.d.mts +10 -9
  59. package/dist/index.mjs +7 -6
  60. package/dist/language-model/LanguageModel.d.mts +2 -2
  61. package/dist/language-model/LanguageModel.test.mjs +2 -2
  62. package/dist/loop/Loop.d.mts +1 -1
  63. package/dist/loop/Loop.mjs.map +1 -1
  64. package/dist/loop/Loop.test.mjs +3 -3
  65. package/dist/math/Probability.test.mjs +2 -2
  66. package/dist/messenger/Messenger.d.mts +43 -289
  67. package/dist/messenger/Messenger.d.mts.map +1 -1
  68. package/dist/messenger/MessengerAdapter.test.mjs +3 -3
  69. package/dist/music-generator/MusicGenerator.test.mjs +3 -3
  70. package/dist/observability/Metrics.d.mts +1 -1
  71. package/dist/observability/Metrics.test.mjs +3 -3
  72. package/dist/observability/Telemetry.d.mts +1 -1
  73. package/dist/observability/Telemetry.mjs +1 -1
  74. package/dist/observability/Telemetry.mjs.map +1 -1
  75. package/dist/observability/Telemetry.test.mjs +4 -4
  76. package/dist/observability/Telemetry.test.mjs.map +1 -1
  77. package/dist/realtime/RealtimeSession.d.mts +1 -1
  78. package/dist/realtime/RealtimeSession.test.mjs +4 -4
  79. package/dist/retry/Retry.test.mjs +2 -2
  80. package/dist/sandbox/Sandbox.d.mts +32 -237
  81. package/dist/sandbox/Sandbox.d.mts.map +1 -1
  82. package/dist/speech-synthesizer/SpeechSynthesizer.test.mjs +3 -3
  83. package/dist/streaming/JSONL.test.mjs +2 -2
  84. package/dist/streaming/Multipart.d.mts +1 -1
  85. package/dist/streaming/Multipart.mjs +3 -2
  86. package/dist/streaming/Multipart.mjs.map +1 -1
  87. package/dist/streaming/Multipart.test.mjs +28 -0
  88. package/dist/streaming/Multipart.test.mjs.map +1 -0
  89. package/dist/streaming/SSE.d.mts +1 -1
  90. package/dist/streaming/SSE.mjs +2 -2
  91. package/dist/streaming/SSE.mjs.map +1 -1
  92. package/dist/streaming/SSE.test.mjs +2 -2
  93. package/dist/streaming/Settle.test.mjs +3 -3
  94. package/dist/streaming/WebSocketSession.d.mts +5 -3
  95. package/dist/streaming/WebSocketSession.d.mts.map +1 -1
  96. package/dist/streaming/WebSocketSession.mjs +22 -10
  97. package/dist/streaming/WebSocketSession.mjs.map +1 -1
  98. package/dist/streaming/WebSocketSession.test.mjs +41 -5
  99. package/dist/streaming/WebSocketSession.test.mjs.map +1 -1
  100. package/dist/structured-format/StructuredFormat.mjs.map +1 -1
  101. package/dist/structured-format/StructuredFormat.test.mjs +2 -2
  102. package/dist/testing/FakeWebSocket.d.mts +5 -1
  103. package/dist/testing/FakeWebSocket.d.mts.map +1 -1
  104. package/dist/testing/FakeWebSocket.mjs +12 -6
  105. package/dist/testing/FakeWebSocket.mjs.map +1 -1
  106. package/dist/testing/MockMessenger.d.mts +19 -134
  107. package/dist/testing/MockMessenger.d.mts.map +1 -1
  108. package/dist/testing/MockProvider.d.mts +1 -1
  109. package/dist/testing/MockSandbox.d.mts +65 -446
  110. package/dist/testing/MockSandbox.d.mts.map +1 -1
  111. package/dist/tool/Content.d.mts +2 -0
  112. package/dist/tool/Content.mjs +46 -0
  113. package/dist/tool/Content.mjs.map +1 -0
  114. package/dist/tool/Content.test.d.mts +1 -0
  115. package/dist/tool/Content.test.mjs +48 -0
  116. package/dist/tool/Content.test.mjs.map +1 -0
  117. package/dist/tool/HistoryCheck.d.mts +6 -15
  118. package/dist/tool/HistoryCheck.d.mts.map +1 -1
  119. package/dist/tool/HistoryCheck.mjs +10 -24
  120. package/dist/tool/HistoryCheck.mjs.map +1 -1
  121. package/dist/tool/Tool.d.mts +2 -2
  122. package/dist/tool/Tool.mjs +55 -130
  123. package/dist/tool/Tool.mjs.map +1 -1
  124. package/dist/tool/Tool.test.mjs +222 -178
  125. package/dist/tool/Tool.test.mjs.map +1 -1
  126. package/dist/tool/ToolDecision.d.mts +164 -0
  127. package/dist/tool/ToolDecision.d.mts.map +1 -0
  128. package/dist/tool/ToolDecision.mjs +146 -0
  129. package/dist/tool/ToolDecision.mjs.map +1 -0
  130. package/dist/tool/ToolDecision.test.d.mts +1 -0
  131. package/dist/tool/ToolDecision.test.mjs +330 -0
  132. package/dist/tool/ToolDecision.test.mjs.map +1 -0
  133. package/dist/tool/ToolEvent.d.mts +2 -2
  134. package/dist/tool/ToolEvent.mjs +2 -34
  135. package/dist/tool/ToolEvent.mjs.map +1 -1
  136. package/dist/tool/ToolOutput.d.mts +2 -0
  137. package/dist/tool/ToolOutput.mjs +128 -0
  138. package/dist/tool/ToolOutput.mjs.map +1 -0
  139. package/dist/tool/ToolOutput.test.d.mts +1 -0
  140. package/dist/tool/ToolOutput.test.mjs +153 -0
  141. package/dist/tool/ToolOutput.test.mjs.map +1 -0
  142. package/dist/tool/Toolkit.d.mts +2 -2
  143. package/dist/tool/Toolkit.mjs +230 -165
  144. package/dist/tool/Toolkit.mjs.map +1 -1
  145. package/dist/tool/Toolkit.test.mjs +433 -391
  146. package/dist/tool/Toolkit.test.mjs.map +1 -1
  147. package/dist/transcriber/Transcriber.test.mjs +3 -3
  148. package/dist/web-read/WebReadTool.d.mts +2 -2
  149. package/dist/web-read/WebReadTool.mjs +1 -0
  150. package/dist/web-read/WebReadTool.mjs.map +1 -1
  151. package/dist/web-search/WebSearchTool.d.mts +2 -2
  152. package/dist/web-search/WebSearchTool.mjs +1 -0
  153. package/dist/web-search/WebSearchTool.mjs.map +1 -1
  154. package/package.json +18 -10
  155. package/src/browser/BrowserTool.ts +5 -0
  156. package/src/domain/File.ts +79 -0
  157. package/src/domain/Image.ts +14 -4
  158. package/src/domain/Items.ts +39 -3
  159. package/src/embedding-model/EmbeddingModel.test.ts +2 -2
  160. package/src/index.ts +3 -2
  161. package/src/loop/Loop.ts +25 -14
  162. package/src/observability/Telemetry.test.ts +1 -1
  163. package/src/observability/Telemetry.ts +2 -2
  164. package/src/streaming/Multipart.test.ts +26 -0
  165. package/src/streaming/Multipart.ts +6 -5
  166. package/src/streaming/SSE.ts +2 -2
  167. package/src/streaming/WebSocketSession.test.ts +54 -2
  168. package/src/streaming/WebSocketSession.ts +27 -6
  169. package/src/structured-format/StructuredFormat.ts +1 -1
  170. package/src/testing/FakeWebSocket.ts +21 -6
  171. package/src/tool/Content.test.ts +40 -0
  172. package/src/tool/Content.ts +38 -0
  173. package/src/tool/HistoryCheck.ts +21 -26
  174. package/src/tool/Tool.test.ts +240 -241
  175. package/src/tool/Tool.ts +204 -309
  176. package/src/tool/ToolDecision.test.ts +301 -0
  177. package/src/tool/ToolDecision.ts +360 -0
  178. package/src/tool/ToolEvent.ts +21 -41
  179. package/src/tool/ToolOutput.test.ts +132 -0
  180. package/src/tool/ToolOutput.ts +198 -0
  181. package/src/tool/Toolkit.test.ts +594 -454
  182. package/src/tool/Toolkit.ts +631 -356
  183. package/src/web-read/WebReadTool.ts +2 -1
  184. package/src/web-search/WebSearchTool.ts +2 -1
  185. package/dist/Image-Cv6iBLTy.d.mts.map +0 -1
  186. package/dist/Items-C7MyrYPX.d.mts.map +0 -1
  187. package/dist/Loop-ChpaO-ya.d.mts.map +0 -1
  188. package/dist/Tool-BF78QWtn.d.mts +0 -314
  189. package/dist/Tool-BF78QWtn.d.mts.map +0 -1
  190. package/dist/ToolEvent-Di4a723B.d.mts +0 -144
  191. package/dist/ToolEvent-Di4a723B.d.mts.map +0 -1
  192. package/dist/ToolResult-BsYQRjmW.d.mts +0 -152
  193. package/dist/ToolResult-BsYQRjmW.d.mts.map +0 -1
  194. package/dist/Toolkit-tlL0F2YR.d.mts +0 -193
  195. package/dist/Toolkit-tlL0F2YR.d.mts.map +0 -1
  196. package/dist/dist-BaenoU1A.mjs +0 -145
  197. package/dist/dist-BaenoU1A.mjs.map +0 -1
  198. package/dist/dist-nohcnkD6.mjs +0 -182
  199. package/dist/dist-nohcnkD6.mjs.map +0 -1
  200. package/dist/magic-string.es-CB2hOw9v.mjs +0 -1014
  201. package/dist/magic-string.es-CB2hOw9v.mjs.map +0 -1
  202. package/dist/test.DNmyFkvJ-YPzDR0Co.mjs +0 -10254
  203. package/dist/test.DNmyFkvJ-YPzDR0Co.mjs.map +0 -1
  204. package/dist/tool/Approval.d.mts +0 -113
  205. package/dist/tool/Approval.d.mts.map +0 -1
  206. package/dist/tool/Approval.mjs +0 -80
  207. package/dist/tool/Approval.mjs.map +0 -1
  208. package/dist/tool/Approval.test.mjs +0 -371
  209. package/dist/tool/Approval.test.mjs.map +0 -1
  210. package/dist/tool/ToolResult.d.mts +0 -2
  211. package/dist/tool/ToolResult.mjs +0 -85
  212. package/dist/tool/ToolResult.mjs.map +0 -1
  213. package/dist/utils-CiH_DjFW.mjs +0 -3918
  214. package/dist/utils-CiH_DjFW.mjs.map +0 -1
  215. package/src/tool/Approval.test.ts +0 -400
  216. package/src/tool/Approval.ts +0 -181
  217. package/src/tool/ToolResult.ts +0 -114
  218. /package/dist/{tool/Approval.test.d.mts → streaming/Multipart.test.d.mts} +0 -0
@@ -1,224 +1,289 @@
1
1
  import { n as __exportAll } from "../rolldown-runtime-EAsCxrDo.mjs";
2
- import { ToolResult, failed, nonLocalTool, toToolCallOutput, validationError } from "./ToolResult.mjs";
3
- import { ToolFailed, decodeCallInput, toDescriptors } from "./Tool.mjs";
4
- import { appendToHistory } from "../domain/Turn.mjs";
5
- import { next, value } from "../loop/Loop.mjs";
6
- import { ToolEvent, isOutput } from "./ToolEvent.mjs";
7
- import { Array, Cause, Data, Effect, Fiber, Function, Match, Option, Queue, Record, Ref, Stream } from "effect";
2
+ import { ToolFailed, decodeCallInput, isStreamed, toDescriptors, toStream, withFailures as withFailures$1 } from "./Tool.mjs";
3
+ import { appendToHistory, getToolCalls } from "../domain/Turn.mjs";
4
+ import { next as next$1, value } from "../loop/Loop.mjs";
5
+ import { Value } from "./Content.mjs";
6
+ import { CoreError, InputInvalid, OutputInvalid, UnknownTool, encoded, failure, success, value as value$1 } from "./ToolOutput.mjs";
7
+ import { Array, Cause, Data, Effect, Function, Match, Option, Predicate, Record, Ref, Result, Schema, Stream } from "effect";
8
8
  //#region src/tool/Toolkit.ts
9
9
  var Toolkit_exports = /* @__PURE__ */ __exportAll({
10
10
  DuplicateToolName: () => DuplicateToolName,
11
- appendToolResults: () => appendToolResults,
12
- collectResults: () => collectResults,
11
+ UndeclaredFailure: () => UndeclaredFailure,
12
+ appendResults: () => appendResults,
13
+ close: () => close,
14
+ complete: () => complete,
13
15
  compose: () => compose,
14
- continueWithResults: () => continueWithResults,
16
+ continueWith: () => continueWith,
17
+ decode: () => decode,
15
18
  describeFailures: () => describeFailures,
16
19
  descriptors: () => descriptors,
17
20
  fromArray: () => fromArray,
21
+ isOutcome: () => isOutcome,
18
22
  make: () => make,
19
23
  makeNamespaced: () => makeNamespaced,
20
24
  namespace: () => namespace,
25
+ next: () => next,
26
+ output: () => output,
21
27
  run: () => run,
28
+ split: () => split,
29
+ withFailures: () => withFailures,
22
30
  wrap: () => wrap
23
31
  });
24
32
  const indexByName = (tools) => Object.fromEntries(tools.map((tool) => [tool.name, tool]));
25
- /**
26
- * Index literal tools by their `name`. Duplicate names are a compile error
27
- * (`UniqueTools`). Names are not otherwise checked: a malformed name is a
28
- * developer typo in a literal that the provider rejects clearly on first use,
29
- * and there is no single cross-provider name rule to enforce here anyway.
30
- */
31
33
  const buildStatic = (tools) => indexByName(tools);
34
+ /** Index tools by `name`. A repeated literal name is a compile error. */
32
35
  const make = (...tools) => buildStatic(tools);
33
- /**
34
- * Build a toolkit from a dynamically-sourced array (MCP server, plugin). Names
35
- * are not validated (MCP legitimately uses dots / >64 chars — sanitize at the
36
- * provider adapter); same-named tools resolve last-wins. Cross-source clashes
37
- * are caught by `compose`.
38
- */
36
+ /** A toolkit from a dynamic source (MCP server, plugin). Same-named tools: last wins. */
39
37
  const fromArray = (tools) => indexByName(tools);
40
- /** Render the provider-facing descriptors for a toolkit on demand. */
41
38
  const descriptors = (toolkit) => toDescriptors(Object.values(toolkit));
42
- /**
43
- * Two composed toolkits resolved to the same final name. Carries the colliding
44
- * `sources` (the positions of the clashing toolkits) so the error says *which*
45
- * inputs collided. Raised at runtime by `compose` for dynamic sources; static
46
- * collisions are caught earlier at compile time.
47
- */
39
+ /** Two composed toolkits share a final name; `sources` are their positions. */
48
40
  var DuplicateToolName = class extends Data.TaggedError("DuplicateToolName") {};
49
- /**
50
- * Prefix every tool name with `<namespace>__`, returning a new toolkit. Use it
51
- * to keep generic names (`search`) from two sources distinct before `compose`.
52
- * If two namespaces collide at the `__` separator, the duplicate final name
53
- * surfaces from `compose` as a `DuplicateToolName`.
54
- */
55
- const namespace = (prefix, toolkit) => {
56
- const renamed = Array.map(Record.values(toolkit), (tool) => ({
57
- ...tool,
58
- name: `${prefix}__${tool.name}`
59
- }));
60
- return indexByName(renamed);
61
- };
62
- /** `namespace(prefix, make(...tools))` in one step, for static namespaced tools. */
41
+ /** Prefix every tool name with `<namespace>__`, e.g. to keep two `search` tools apart. */
42
+ const namespace = (prefix, toolkit) => indexByName(Array.map(Record.values(toolkit), (tool) => ({
43
+ ...tool,
44
+ name: `${prefix}__${tool.name}`
45
+ })));
46
+ /** `namespace(prefix, make(...tools))` in one step. */
63
47
  const makeNamespaced = (prefix, ...tools) => namespace(prefix, buildStatic(tools));
64
48
  /**
65
- * Combine independently-built toolkits into one. This is where names from
66
- * separate sources can collide, so it is the one effectful constructor: a
67
- * duplicate final name fails with `DuplicateToolName` (naming the colliding
68
- * input positions) instead of silently overwriting or 400-ing later at the
69
- * provider. Statically-known collisions are additionally a compile error.
49
+ * Join independently built toolkits. A duplicate final name fails with
50
+ * `DuplicateToolName`; statically known duplicates are a compile error too.
70
51
  */
71
- const compose = (...toolkits) => Effect.gen(function* () {
52
+ const compose = (...toolkits) => {
72
53
  const kits = toolkits;
73
54
  const labelled = Array.flatMap(kits, (kit, i) => Array.map(Record.keys(kit), (name) => ({
74
55
  name,
75
56
  source: `toolkit-${i}`
76
57
  })));
77
- const collision = Array.findFirst(Record.toEntries(Array.groupBy(labelled, (entry) => entry.name)), ([, group]) => group.length > 1);
78
- if (Option.isSome(collision)) {
79
- const [name, group] = collision.value;
80
- return yield* new DuplicateToolName({
58
+ return Array.findFirst(Record.toEntries(Array.groupBy(labelled, (entry) => entry.name)), ([, group]) => group.length > 1).pipe(Option.match({
59
+ onNone: () => Effect.succeed(Array.reduce(kits, {}, (acc, kit) => ({
60
+ ...acc,
61
+ ...kit
62
+ }))),
63
+ onSome: ([name, group]) => Effect.fail(new DuplicateToolName({
81
64
  name,
82
65
  sources: Array.map(group, (e) => e.source)
83
- });
84
- }
85
- return Array.reduce(kits, {}, (acc, kit) => ({
86
- ...acc,
87
- ...kit
66
+ }))
88
67
  }));
89
- });
68
+ };
69
+ const mapLocal = (toolkit, f) => Record.map(toolkit, (tool) => tool._tag === "LocalTool" ? f(tool) : tool);
90
70
  /**
91
- * Wrap every local tool's `run` with a middleware, preserving each tool's
92
- * `Input`/`Output` and unioning the middleware's requirement `R2` into the
93
- * toolkit's `ToolkitR`. Non-local kinds pass through unchanged. Stack with
94
- * `pipe` - inner-first, so the last `wrap` applied is the outermost and runs
95
- * first:
71
+ * Wrap every local tool's `run`. Stacks with `pipe`, inner first: the last
72
+ * `wrap` runs first. The middleware sees a stream; an `Effect` tool stays one,
73
+ * with the stream's last value.
96
74
  *
97
75
  * const observed = pipe(toolkit, Toolkit.wrap(logging), Toolkit.wrap(authz))
98
- * Toolkit.run(observed, calls) // authz runs, then logging, then the tool
99
- *
100
- * A middleware that fails with `string` / `ToolFailed` is absorbed by the
101
- * executor into a `ToolResult.Failure`; any other error propagates typed. Use
102
- * `describeFailures` for the total "everything to the model" case, or the
103
- * `Approval` flow for a distinct "denied" verdict.
104
76
  */
105
- const wrap = Function.dual(2, (toolkit, middleware) => Record.map(toolkit, (tool) => tool._tag === "LocalTool" ? {
106
- ...tool,
107
- run: middleware(tool.run, tool.name)
108
- } : tool));
77
+ const wrap = Function.dual(2, (toolkit, middleware) => mapLocal(toolkit, (tool) => {
78
+ const wrapped = middleware((input) => toStream(tool.run(input)), tool.name);
79
+ return {
80
+ ...tool,
81
+ run: (input) => isStreamed(tool.run(input)) ? wrapped(input) : lastOf(tool.name, wrapped(input))
82
+ };
83
+ }));
84
+ const lastOf = (name, values) => Effect.flatMap(Stream.runLast(values), Option.match({
85
+ onSome: Effect.succeed,
86
+ onNone: () => Effect.die(`A middleware left the Effect tool "${name}" without a value`)
87
+ }));
109
88
  /**
110
- * Map every local tool's error to a `string`, so `Toolkit.run` absorbs those
111
- * failures into `ToolResult.Failure` the model reads and adapts to. Pairs
112
- * with the domain `describe` helpers (`AiError.describe`,
113
- * `BrowserError.describe`). Non-local kinds pass through.
114
- *
115
- * const kit = yield* Toolkit.compose(
116
- * Toolkit.describeFailures(searchKit, AiError.describe),
117
- * browserToolkit(session), // failures stay typed
118
- * )
89
+ * `Tool.withFailures` for every local tool, e.g. a whole MCP toolkit that needs
90
+ * approval. Prefer it per tool when only some tools are gated.
119
91
  */
120
- const describeFailures = Function.dual(2, (toolkit, describe) => Record.map(toolkit, (tool) => tool._tag === "LocalTool" ? {
121
- ...tool,
122
- run: (input, emit) => tool.run(input, emit).pipe(Effect.mapError(describe))
123
- } : tool));
92
+ const withFailures = Function.dual(2, (toolkit, failures) => mapLocal(toolkit, (tool) => withFailures$1(tool, failures)));
124
93
  /**
125
- * Execute every provided call. Approval/rejection policy belongs upstream.
126
- *
127
- * String and `ToolFailed` failures are absorbed into `ToolResult.Failure`
128
- * (the model reads and adapts to them); every other tool error propagates
129
- * typed on the stream, so `Exclude<ToolkitE<T>, string | ToolFailed>` is
130
- * what a caller catches or lets end the run. If one call in a concurrent
131
- * batch fails the stream, siblings may go unanswered: a loop that catches
132
- * and continues must reconcile history first (`HistoryCheck.cancelAllPending`).
94
+ * Undeclared errors become a `ToolFailed` the model reads, e.g. with
95
+ * `AiError.describe`. Declared failures pass through unchanged. Return
96
+ * `undefined` for an error that should still fail the run (a dead session).
97
+ */
98
+ const describeFailures = Function.dual(2, (toolkit, describe) => mapLocal(toolkit, (tool) => {
99
+ const isDeclared = isDeclaredBy(tool);
100
+ const toFailed = (e) => isDeclared(e) ? e : Option.match(Option.fromNullishOr(describe(e)), {
101
+ onNone: () => e,
102
+ onSome: (message) => new ToolFailed({ message })
103
+ });
104
+ return {
105
+ ...tool,
106
+ failureSchema: tool.failureSchema === void 0 ? ToolFailed : Schema.Union([tool.failureSchema, ToolFailed]),
107
+ run: (input) => {
108
+ const out = tool.run(input);
109
+ return isStreamed(out) ? Stream.mapError(out, toFailed) : Effect.mapError(out, toFailed);
110
+ }
111
+ };
112
+ }));
113
+ /**
114
+ * Run the calls, emitting each one's partials and its `Output`, typed per tool
115
+ * and decoded. An error matching the tool's `failureSchema` becomes a `Failure`;
116
+ * any other error fails the stream (siblings of a failed batch may then stay
117
+ * unanswered: see `HistoryCheck.cancelAllPending`). Which calls run is the caller's call.
133
118
  */
134
119
  const run = (toolkit, calls, options) => Stream.fromIterable(calls).pipe(Stream.flatMap((call) => runOne(toolkit, call), { concurrency: options?.concurrency ?? "unbounded" }));
135
- const okResult = (call, tool, value) => ToolResult.Ok({
120
+ const lookup = (toolkit, name) => Object.hasOwn(toolkit, name) ? toolkit[name] : void 0;
121
+ const local = (toolkit, name) => {
122
+ const tool = lookup(toolkit, name);
123
+ return tool === void 0 || tool._tag === "ProviderTool" ? void 0 : tool;
124
+ };
125
+ const done = (call, result) => ({
126
+ _tag: "Output",
136
127
  call_id: call.call_id,
137
- tool,
138
- value
128
+ tool: call.name,
129
+ result
139
130
  });
140
- const outputEvent = (result) => Stream.succeed(ToolEvent.Output({ result }));
141
- const runOne = (toolkit, call) => {
142
- const tool = Object.hasOwn(toolkit, call.name) ? toolkit[call.name] : void 0;
143
- 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))));
144
- };
131
+ const failWith = (call, error) => Stream.succeed(done(call, Result.fail(error)));
132
+ const runOne = (toolkit, call) => Match.value(lookup(toolkit, call.name)).pipe(Match.when(Match.undefined, () => failWith(call, new UnknownTool({ message: `No tool named "${call.name}"` }))), Match.discriminatorsExhaustive("_tag")({
133
+ LocalTool: (tool) => runTool(tool, call),
134
+ DeclaredTool: () => failWith(call, new UnknownTool({ message: `"${call.name}" has no run; the app handles its calls` })),
135
+ ProviderTool: () => failWith(call, new UnknownTool({ message: `"${call.name}" is run by the provider` }))
136
+ }));
145
137
  const describeIssues = (issues) => issues.map((issue) => {
146
138
  const path = (issue.path ?? []).map((seg) => typeof seg === "object" && seg !== null ? String(seg.key) : String(seg)).join(".");
147
139
  return path === "" ? issue.message : `${path}: ${issue.message}`;
148
140
  }).join("; ").slice(0, 500);
149
- 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))));
150
- const runValidated = (tool, call, input) => Stream.unwrap(Effect.gen(function* () {
151
- const queue = yield* Queue.make({ capacity: tool.emitBufferSize });
152
- const emit = (event) => Effect.asVoid(Queue.offer(queue, event));
153
- const fiber = yield* tool.run(input, emit).pipe(Effect.ensuring(Queue.end(queue)), Effect.forkScoped);
154
- const progress = Stream.fromQueue(queue).pipe(Stream.map((data) => ToolEvent.Progress({
141
+ const decodeInput = (tool, call) => Effect.map(decodeCallInput(tool, call), Match.type().pipe(Match.tag("parseError", () => Result.fail(new InputInvalid({ message: "arguments are not valid JSON" }))), Match.tag("invalid", ({ issues }) => Result.fail(new InputInvalid({ message: describeIssues(issues) }))), Match.tag("ok", ({ input }) => Result.succeed(input)), Match.exhaustive));
142
+ const runTool = (tool, call) => Stream.unwrap(Effect.map(decodeInput(tool, call), Result.match({
143
+ onFailure: (error) => failWith(call, error),
144
+ onSuccess: (input) => execute(tool, call, input)
145
+ })));
146
+ const execute = (tool, call, input) => {
147
+ const out = tool.run(input);
148
+ const events = isStreamed(out) ? streamed(tool, call, out) : Stream.fromEffect(Effect.map(Effect.tap(out, (value) => check(tool, value)), (value) => done(call, Result.succeed(value))));
149
+ return Stream.catchCause(events, (cause) => Stream.fromEffect(Effect.map(declared(tool, cause), (error) => done(call, Result.fail(error)))));
150
+ };
151
+ const streamed = (tool, call, values) => values.pipe(Stream.mapEffect((value) => Effect.as(check(tool, value), Option.some(value))), Stream.concat(Stream.succeed(Option.none())), Stream.mapAccum(() => Option.none(), (last, next) => Option.match(next, {
152
+ onSome: (value) => [Option.some(value), [{
153
+ _tag: "Partial",
155
154
  call_id: call.call_id,
156
155
  tool: tool.name,
157
- data
158
- })));
159
- const output = Stream.fromEffect(Fiber.join(fiber).pipe(Effect.matchCauseEffect({
160
- onSuccess: (value) => Effect.succeed(ToolEvent.Output({ result: okResult(call, tool.name, value) })),
161
- onFailure: (cause) => routeCause(call, cause)
162
- })));
163
- return progress.pipe(Stream.concat(output));
156
+ value
157
+ }]],
158
+ onNone: () => [last, [done(call, Option.match(last, {
159
+ onNone: () => Result.fail(new OutputInvalid({ message: "The tool produced no value" })),
160
+ onSome: Result.succeed
161
+ }))]]
162
+ })));
163
+ var EncodeFailed = class extends Data.TaggedError("EncodeFailed") {};
164
+ const check = (tool, value) => tool.outputSchema === void 0 ? Effect.void : Schema.encodeUnknownEffect(tool.outputSchema)(value).pipe(Effect.mapError((e) => new EncodeFailed({ message: e.message.slice(0, 500) })));
165
+ const isDeclaredBy = (tool) => tool.failureSchema === void 0 ? () => false : Schema.is(tool.failureSchema);
166
+ const soleError = (cause) => Option.fromNullishOr(cause.reasons.length === 1 ? cause.reasons[0] : void 0).pipe(Option.filter(Cause.isFailReason), Option.map((reason) => reason.error));
167
+ const declared = (tool, cause) => soleError(cause).pipe(Option.flatMap((error) => Match.value(error).pipe(Match.when(Match.instanceOf(EncodeFailed), (e) => Option.some(new OutputInvalid({ message: e.message }))), Match.when(isDeclaredBy(tool), Option.some), Match.orElse(() => Option.none()))), Option.match({
168
+ onNone: () => Effect.failCause(cause),
169
+ onSome: Effect.succeed
164
170
  }));
165
- const routeCause = (call, cause) => {
166
- const only = cause.reasons.length === 1 ? cause.reasons[0] : void 0;
167
- if (only !== void 0 && Cause.isFailReason(only)) {
168
- const error = only.error;
169
- if (typeof error === "string") return Effect.succeed(ToolEvent.Output({ result: failed(call, "tool_failed", error) }));
170
- if (error instanceof ToolFailed) return Effect.succeed(ToolEvent.Output({ result: failed(call, error.kind ?? "tool_failed", error.message) }));
171
- }
172
- return Effect.failCause(cause);
173
- };
171
+ const outputs = (tool) => tool?.outputSchema ?? Value;
172
+ const failures = (tool) => tool?.failureSchema === void 0 ? CoreError : Schema.Union([tool.failureSchema, CoreError]);
173
+ /** `Toolkit.close` got an error the call's tool doesn't declare: a bug in the app. */
174
+ var UndeclaredFailure = class extends Data.TaggedError("UndeclaredFailure") {};
175
+ const describeError = (error) => Predicate.hasProperty(error, "_tag") && Predicate.isString(error._tag) ? error._tag : String(error);
176
+ function close(toolkit, ...args) {
177
+ const [name, call, error] = args.length === 3 ? args : [args[0].name, ...args];
178
+ return Match.value({
179
+ name,
180
+ call,
181
+ error
182
+ }).pipe(Match.when(({ call }) => call.name !== name, () => Effect.fail(new UndeclaredFailure({
183
+ tool: name,
184
+ message: `the call is for "${call.name}", not "${name}"`
185
+ }))), Match.when(({ error }) => !Schema.is(failures(local(toolkit, name)))(error), () => Effect.fail(new UndeclaredFailure({
186
+ tool: name,
187
+ message: `"${name}" does not declare ${describeError(error)} in its failureSchema`
188
+ }))), Match.orElse(() => Effect.succeed({
189
+ _tag: "Output",
190
+ call_id: call.call_id,
191
+ tool: name,
192
+ result: Result.fail(error)
193
+ })));
194
+ }
174
195
  /**
175
- * Append a completed turn plus tool results to a state's history, converting
176
- * the results to wire-format `ToolCallOutput`s. Curried so it slots directly
177
- * into `continueWithResults`:
178
- *
179
- * Toolkit.run(toolkit, calls).pipe(
180
- * Toolkit.continueWithResults(Toolkit.appendToolResults(state, turn)),
181
- * )
182
- *
183
- * Equivalent to `appendToHistory(state, turn, results.map(toToolCallOutput))`
184
- * — the helper just hides the wire-conversion step.
196
+ * A call the app answers itself (`ask_user`, a signal's acknowledgement), as
197
+ * the outcome `run` would emit: `close`'s success side. `value` is typed by the
198
+ * tool's `outputSchema`; a value it rejects ends the call with `OutputInvalid`,
199
+ * as from `run`. Fails with `UndeclaredFailure` if `call` is for another tool.
185
200
  */
186
- const appendToolResults = (state, turn) => (results) => appendToHistory(state, turn, results.map(toToolCallOutput));
201
+ const complete = (toolkit, name, call, value) => Option.match(Option.fromNullishOr(call.name === name ? local(toolkit, name) : void 0), {
202
+ onNone: () => Effect.fail(new UndeclaredFailure({
203
+ tool: name,
204
+ message: call.name === name ? `"${name}" is not a tool of this toolkit` : `the call is for "${call.name}", not "${name}"`
205
+ })),
206
+ onSome: (tool) => check(tool, value).pipe(Effect.match({
207
+ onFailure: (e) => Result.fail(new OutputInvalid({ message: e.message })),
208
+ onSuccess: () => Result.succeed(value)
209
+ }), Effect.map((result) => ({
210
+ _tag: "Output",
211
+ call_id: call.call_id,
212
+ tool: name,
213
+ result
214
+ })))
215
+ });
216
+ const sortCall = (toolkit, call) => {
217
+ const tool = lookup(toolkit, call.name);
218
+ return tool?._tag === "DeclaredTool" ? Effect.map(decodeInput(tool, call), Result.match({
219
+ onFailure: (error) => ({
220
+ _tag: "invalid",
221
+ output: {
222
+ _tag: "Output",
223
+ call_id: call.call_id,
224
+ tool: call.name,
225
+ result: Result.fail(error)
226
+ }
227
+ }),
228
+ onSuccess: (input) => ({
229
+ _tag: "declared",
230
+ declared: {
231
+ tool: call.name,
232
+ call,
233
+ input
234
+ }
235
+ })
236
+ })) : Effect.succeed({
237
+ _tag: "run",
238
+ call
239
+ });
240
+ };
187
241
  /**
188
- * Drain a `Stream<ToolEvent>` and return the accumulated `ToolResult`s
189
- * from every `Output` event. One-shot — the type is `Effect<results>`,
190
- * not a one-element stream — so it composes naturally with `Effect.map`
191
- * to build state.
242
+ * The turn's tool calls, sorted: `run` for `Toolkit.run`, and the calls to
243
+ * declared tools decoded and typed per tool. Handling `declared` with
244
+ * `Match.discriminatorsExhaustive("tool")` makes a new declared tool a compile
245
+ * error until the loop handles it:
192
246
  *
193
- * Counterpart to `continueWithResults`: `continueWithResults` bundles
194
- * drain + emit Next into one bridge; this one is the right arm of a
195
- * broadcast/fork-and-merge for callers that want to vary the left arm
196
- * (e.g., filter/tap ToolEvents before forwarding).
247
+ * const { run, declared, invalid } = yield* Toolkit.split(toolkit, turn)
248
+ * declared.map(Match.discriminatorsExhaustive("tool")({ ask_user: ..., escalate: ... }))
197
249
  */
198
- const collectResults = (stream) => stream.pipe(Stream.filter(isOutput), Stream.map((e) => e.result), Stream.runCollect);
250
+ const split = (toolkit, turn) => Effect.map(Effect.forEach(getToolCalls(turn), (call) => sortCall(toolkit, call)), (sorted) => ({
251
+ run: sorted.flatMap((s) => s._tag === "run" ? [s.call] : []),
252
+ declared: sorted.flatMap((s) => s._tag === "declared" ? [s.declared] : []),
253
+ invalid: sorted.flatMap((s) => s._tag === "invalid" ? [s.output] : [])
254
+ }));
255
+ /** Narrows to the `Output` members (also in a loop's mixed stream), keeping the per-tool types. */
256
+ const isOutcome = (e) => e._tag === "Output";
257
+ const toOutput = (toolkit, event) => {
258
+ const tool = local(toolkit, event.tool);
259
+ return Result.match(event.result, {
260
+ onSuccess: (value) => success(event, outputs(tool), value),
261
+ onFailure: (error) => failure(event, failures(tool), error)
262
+ });
263
+ };
264
+ function output(toolkit, event) {
265
+ return event === void 0 ? (e) => toOutput(toolkit, e) : toOutput(toolkit, event);
266
+ }
267
+ function decode(toolkit, name, item) {
268
+ const tool = local(toolkit, name);
269
+ const decodeWith = (schema) => Effect.flatMap(encoded(item.output), Schema.decodeUnknownEffect(schema));
270
+ return Match.value(item).pipe(Match.when({ isError: true }, () => Effect.map(decodeWith(failures(tool)), Result.fail)), Match.orElse(() => Option.match(Option.fromNullishOr(tool?.outputSchema), {
271
+ onNone: () => Effect.succeed(Result.succeed(value$1(item.output))),
272
+ onSome: (schema) => Effect.map(decodeWith(schema), Result.succeed)
273
+ })));
274
+ }
275
+ const isOutcomeOf = (e) => e._tag === "Output";
199
276
  /**
200
- * Bridge from a `Stream<ToolEvent>` to the loop's emit shape. Forwards
201
- * every ToolEvent downstream as a `Loop.value` and at end-of-stream emits
202
- * one `Loop.next(build(results))` carrying the accumulated `ToolResult`s
203
- * from terminal `Output` events.
204
- *
205
- * Conceptually a broadcast fork (one arm passes events through, the other
206
- * drains them into a state). Implemented as a single-pass Ref tap + concat
207
- * for zero buffering — the broadcast version is observationally equivalent
208
- * but holds events in a PubSub until both arms drain. The public primitives
209
- * (`Loop.value`, `collectResults`, `Loop.next`) compose the same pattern
210
- * when you need to vary an arm.
211
- *
212
- * Dual: data-first `continueWithResults(stream, build)` and data-last
213
- * `stream.pipe(continueWithResults(build))` both work.
277
+ * Forward every event as a `Loop.value`, then continue the loop with
278
+ * `build(outcomes)`. `build` is an Effect, so it can encode. The stream may
279
+ * carry the app's own tagged events too (any tag but `Output`).
214
280
  */
215
- const continueWithResults = Function.dual(2, (stream, build) => Stream.unwrap(Effect.gen(function* () {
216
- const ref = yield* Ref.make([]);
217
- const tapped = stream.pipe(Stream.tap((e) => isOutput(e) ? Ref.update(ref, (acc) => Array.append(acc, e.result)) : Effect.void), Stream.map(value));
218
- const continuation = Stream.unwrap(Ref.get(ref).pipe(Effect.map((acc) => next(build(acc)))));
219
- return tapped.pipe(Stream.concat(continuation));
220
- })));
281
+ const continueWith = Function.dual(2, (stream, build) => Stream.unwrap(Effect.map(Ref.make([]), (outcomes) => Stream.concat(stream.pipe(Stream.tap((e) => isOutcomeOf(e) ? Ref.update(outcomes, Array.append(e)) : Effect.void), Stream.map(value)), Stream.unwrap(Ref.get(outcomes).pipe(Effect.flatMap(build), Effect.map(next$1)))))));
282
+ /** Encode each outcome with `output` and append the turn plus the outputs to history. */
283
+ const appendResults = (toolkit, state, turn) => (outcomes) => Effect.map(Effect.forEach(outcomes, output(toolkit)), (items) => appendToHistory(state, turn, items));
284
+ /** `continueWith(appendResults(toolkit, state, turn))`. */
285
+ const next = (toolkit, state, turn) => continueWith(appendResults(toolkit, state, turn));
221
286
  //#endregion
222
- export { DuplicateToolName, appendToolResults, collectResults, compose, continueWithResults, describeFailures, descriptors, fromArray, make, makeNamespaced, namespace, run, Toolkit_exports as t, wrap };
287
+ export { DuplicateToolName, UndeclaredFailure, appendResults, close, complete, compose, continueWith, decode, describeFailures, descriptors, fromArray, isOutcome, make, makeNamespaced, namespace, next, output, run, split, Toolkit_exports as t, withFailures, wrap };
223
288
 
224
289
  //# sourceMappingURL=Toolkit.mjs.map