@effect-uai/core 0.8.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 (165) hide show
  1. package/dist/{AiError-CAX_48RU.d.mts → AiError-Bf1pq4BB.d.mts} +9 -4
  2. package/dist/{AiError-CAX_48RU.d.mts.map → AiError-Bf1pq4BB.d.mts.map} +1 -1
  3. package/dist/{Audio-DPlTsNdz.d.mts → Audio-CR7CSljJ.d.mts} +1 -1
  4. package/dist/{Audio-DPlTsNdz.d.mts.map → Audio-CR7CSljJ.d.mts.map} +1 -1
  5. package/dist/{Image-HNmMpMTh.d.mts → Image-BZmKfIdq.d.mts} +1 -1
  6. package/dist/{Image-HNmMpMTh.d.mts.map → Image-BZmKfIdq.d.mts.map} +1 -1
  7. package/dist/{Items-7ljaVxH_.d.mts → Items-BnIduEV2.d.mts} +7 -7
  8. package/dist/{Items-7ljaVxH_.d.mts.map → Items-BnIduEV2.d.mts.map} +1 -1
  9. package/dist/Loop-D8nWpowc.d.mts +173 -0
  10. package/dist/Loop-D8nWpowc.d.mts.map +1 -0
  11. package/dist/{SandboxError-BvKypkJm.d.mts → SandboxError-CIrVDOMV.d.mts} +2 -2
  12. package/dist/{SandboxError-BvKypkJm.d.mts.map → SandboxError-CIrVDOMV.d.mts.map} +1 -1
  13. package/dist/{StructuredFormat-BbN4dosH.d.mts → StructuredFormat-DHaUJBdd.d.mts} +2 -2
  14. package/dist/{StructuredFormat-BbN4dosH.d.mts.map → StructuredFormat-DHaUJBdd.d.mts.map} +1 -1
  15. package/dist/Tool-2lC_XMth.d.mts +284 -0
  16. package/dist/Tool-2lC_XMth.d.mts.map +1 -0
  17. package/dist/ToolEvent-cWZ2BnbS.d.mts +151 -0
  18. package/dist/ToolEvent-cWZ2BnbS.d.mts.map +1 -0
  19. package/dist/ToolResult-CL4hK_9z.d.mts +157 -0
  20. package/dist/ToolResult-CL4hK_9z.d.mts.map +1 -0
  21. package/dist/Toolkit-Dr9l_hNy.d.mts +196 -0
  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/{dist-DV5ISja1.mjs → dist-B04dlFHg.mjs} +3 -3
  36. package/dist/{dist-DV5ISja1.mjs.map → dist-B04dlFHg.mjs.map} +1 -1
  37. package/dist/dist-DWgRbDN3.mjs +140 -0
  38. package/dist/dist-DWgRbDN3.mjs.map +1 -0
  39. package/dist/domain/AiError.d.mts +2 -2
  40. package/dist/domain/AiError.mjs +22 -3
  41. package/dist/domain/AiError.mjs.map +1 -1
  42. package/dist/domain/Audio.d.mts +1 -1
  43. package/dist/domain/Image.d.mts +1 -1
  44. package/dist/domain/Items.d.mts +1 -1
  45. package/dist/domain/Music.d.mts +1 -1
  46. package/dist/domain/Turn.d.mts +4 -4
  47. package/dist/domain/Turn.test.mjs +1 -1
  48. package/dist/embedding-model/Embedding.d.mts +1 -1
  49. package/dist/embedding-model/EmbeddingModel.d.mts +1 -1
  50. package/dist/embedding-model/EmbeddingModel.test.mjs +1 -1
  51. package/dist/embedding-model/EmbeddingModel.test.mjs.map +1 -1
  52. package/dist/index.d.mts +19 -13
  53. package/dist/index.mjs +15 -9
  54. package/dist/language-model/LanguageModel.d.mts +10 -5
  55. package/dist/language-model/LanguageModel.d.mts.map +1 -1
  56. package/dist/language-model/LanguageModel.mjs.map +1 -1
  57. package/dist/language-model/LanguageModel.test.mjs +1 -1
  58. package/dist/loop/Loop.d.mts +2 -141
  59. package/dist/loop/Loop.mjs +16 -20
  60. package/dist/loop/Loop.mjs.map +1 -1
  61. package/dist/loop/Loop.test.mjs +18 -1
  62. package/dist/loop/Loop.test.mjs.map +1 -1
  63. package/dist/{magic-string.es-BgIV5Mu3.mjs → magic-string.es-Dk-MMA0F.mjs} +1 -1
  64. package/dist/{magic-string.es-BgIV5Mu3.mjs.map → magic-string.es-Dk-MMA0F.mjs.map} +1 -1
  65. package/dist/music-generator/MusicGenerator.d.mts +2 -2
  66. package/dist/music-generator/MusicGenerator.test.mjs +1 -1
  67. package/dist/observability/Metrics.d.mts +116 -28
  68. package/dist/observability/Metrics.d.mts.map +1 -1
  69. package/dist/observability/Metrics.mjs +250 -38
  70. package/dist/observability/Metrics.mjs.map +1 -1
  71. package/dist/observability/Metrics.test.d.mts +1 -0
  72. package/dist/observability/Metrics.test.mjs +187 -0
  73. package/dist/observability/Metrics.test.mjs.map +1 -0
  74. package/dist/observability/Telemetry.d.mts +37 -0
  75. package/dist/observability/Telemetry.d.mts.map +1 -0
  76. package/dist/observability/Telemetry.mjs +57 -0
  77. package/dist/observability/Telemetry.mjs.map +1 -0
  78. package/dist/observability/Telemetry.test.d.mts +1 -0
  79. package/dist/observability/Telemetry.test.mjs +92 -0
  80. package/dist/observability/Telemetry.test.mjs.map +1 -0
  81. package/dist/retry/Retry.d.mts +2 -2
  82. package/dist/retry/Retry.test.mjs +1 -1
  83. package/dist/sandbox/Sandbox.d.mts +1 -1
  84. package/dist/sandbox/SandboxError.d.mts +1 -1
  85. package/dist/speech-synthesizer/SpeechSynthesizer.d.mts +2 -2
  86. package/dist/speech-synthesizer/SpeechSynthesizer.test.mjs +1 -1
  87. package/dist/streaming/JSONL.d.mts +1 -1
  88. package/dist/streaming/JSONL.test.mjs +1 -1
  89. package/dist/streaming/SSE.test.mjs +1 -1
  90. package/dist/structured-format/StructuredFormat.d.mts +1 -1
  91. package/dist/structured-format/StructuredFormat.mjs.map +1 -1
  92. package/dist/structured-format/StructuredFormat.test.mjs +1 -1
  93. package/dist/testing/MockMusicGenerator.d.mts +1 -1
  94. package/dist/testing/MockProvider.d.mts +1 -1
  95. package/dist/testing/MockSpeechSynthesizer.d.mts +1 -1
  96. package/dist/tool/Approval.d.mts +19 -7
  97. package/dist/tool/Approval.d.mts.map +1 -1
  98. package/dist/tool/Approval.mjs +18 -11
  99. package/dist/tool/Approval.mjs.map +1 -1
  100. package/dist/tool/Approval.test.mjs +80 -40
  101. package/dist/tool/Approval.test.mjs.map +1 -1
  102. package/dist/tool/HistoryCheck.d.mts +2 -2
  103. package/dist/tool/Tool.d.mts +2 -2
  104. package/dist/tool/Tool.mjs +190 -36
  105. package/dist/tool/Tool.mjs.map +1 -1
  106. package/dist/tool/Tool.test.mjs +41 -3
  107. package/dist/tool/Tool.test.mjs.map +1 -1
  108. package/dist/tool/ToolEvent.d.mts +2 -151
  109. package/dist/tool/ToolEvent.mjs +5 -0
  110. package/dist/tool/ToolEvent.mjs.map +1 -1
  111. package/dist/tool/ToolResult.d.mts +2 -138
  112. package/dist/tool/ToolResult.mjs +32 -8
  113. package/dist/tool/ToolResult.mjs.map +1 -1
  114. package/dist/tool/Toolkit.d.mts +2 -73
  115. package/dist/tool/Toolkit.mjs +153 -35
  116. package/dist/tool/Toolkit.mjs.map +1 -1
  117. package/dist/tool/Toolkit.test.mjs +271 -11
  118. package/dist/tool/Toolkit.test.mjs.map +1 -1
  119. package/dist/transcriber/Transcriber.d.mts +2 -2
  120. package/dist/transcriber/Transcriber.test.mjs +1 -1
  121. package/dist/web-read/WebRead.d.mts +44 -0
  122. package/dist/web-read/WebRead.d.mts.map +1 -0
  123. package/dist/web-read/WebRead.mjs +18 -0
  124. package/dist/web-read/WebRead.mjs.map +1 -0
  125. package/dist/web-read/WebReadTool.d.mts +57 -0
  126. package/dist/web-read/WebReadTool.d.mts.map +1 -0
  127. package/dist/web-read/WebReadTool.mjs +59 -0
  128. package/dist/web-read/WebReadTool.mjs.map +1 -0
  129. package/dist/web-search/WebSearch.d.mts +1 -1
  130. package/dist/web-search/WebSearchTool.d.mts +12 -6
  131. package/dist/web-search/WebSearchTool.d.mts.map +1 -1
  132. package/dist/web-search/WebSearchTool.mjs +10 -5
  133. package/dist/web-search/WebSearchTool.mjs.map +1 -1
  134. package/package.json +21 -1
  135. package/src/browser/Browser.ts +268 -0
  136. package/src/browser/BrowserError.ts +145 -0
  137. package/src/browser/BrowserTool.ts +161 -0
  138. package/src/domain/AiError.ts +27 -1
  139. package/src/embedding-model/EmbeddingModel.test.ts +4 -0
  140. package/src/index.ts +6 -0
  141. package/src/language-model/LanguageModel.ts +7 -2
  142. package/src/loop/Loop.test.ts +22 -0
  143. package/src/loop/Loop.ts +27 -4
  144. package/src/observability/Metrics.test.ts +266 -0
  145. package/src/observability/Metrics.ts +505 -65
  146. package/src/observability/Telemetry.test.ts +123 -0
  147. package/src/observability/Telemetry.ts +96 -0
  148. package/src/structured-format/StructuredFormat.ts +4 -0
  149. package/src/tool/Approval.test.ts +77 -26
  150. package/src/tool/Approval.ts +72 -27
  151. package/src/tool/Tool.test.ts +55 -2
  152. package/src/tool/Tool.ts +335 -70
  153. package/src/tool/ToolEvent.ts +5 -0
  154. package/src/tool/ToolResult.ts +36 -9
  155. package/src/tool/Toolkit.test.ts +339 -9
  156. package/src/tool/Toolkit.ts +470 -120
  157. package/src/web-read/WebRead.ts +70 -0
  158. package/src/web-read/WebReadTool.ts +96 -0
  159. package/src/web-search/WebSearchTool.ts +11 -5
  160. package/dist/Tool-BKiieFXk.d.mts +0 -102
  161. package/dist/Tool-BKiieFXk.d.mts.map +0 -1
  162. package/dist/loop/Loop.d.mts.map +0 -1
  163. package/dist/tool/ToolEvent.d.mts.map +0 -1
  164. package/dist/tool/ToolResult.d.mts.map +0 -1
  165. package/dist/tool/Toolkit.d.mts.map +0 -1
@@ -1,11 +1,11 @@
1
1
  import { toolCallOutput, userText } from "../domain/Items.mjs";
2
- import { fromEffectSchema, make, streaming } from "./Tool.mjs";
3
2
  import { isFailure, isOk, toToolCallOutput } from "./ToolResult.mjs";
3
+ import { fromEffectSchema, make } from "./Tool.mjs";
4
4
  import { ToolEvent, isApprovalRequested, isOutput, isProgress } from "./ToolEvent.mjs";
5
- import { run } from "./Toolkit.mjs";
5
+ import { make as make$1, run } from "./Toolkit.mjs";
6
6
  import { fromMap, fromQueue } from "./Approval.mjs";
7
7
  import { cancelAllPending, findUnansweredCalls, isReconciled } from "./HistoryCheck.mjs";
8
- import { i as it, n as globalExpect, r as describe } from "../dist-DV5ISja1.mjs";
8
+ import { a as describe, n as globalExpect, o as it } from "../dist-B04dlFHg.mjs";
9
9
  import { Effect, Queue, Schema, Stream } from "effect";
10
10
  //#region src/tool/Approval.test.ts
11
11
  /**
@@ -20,12 +20,12 @@ import { Effect, Queue, Schema, Stream } from "effect";
20
20
  *
21
21
  * Plus: hallucinated tool name (graceful Failure), unknown_tool kind.
22
22
  */
23
- const allTools = [
24
- streaming({
25
- name: "web_search",
26
- description: "search",
27
- inputSchema: fromEffectSchema(Schema.Struct({ query: Schema.String })),
28
- run: ({ query }) => Stream.fromIterable([
23
+ const allTools = make$1(make({
24
+ name: "web_search",
25
+ description: "search",
26
+ inputSchema: fromEffectSchema(Schema.Struct({ query: Schema.String })),
27
+ run: ({ query }, emit) => Effect.gen(function* () {
28
+ const hits = [
29
29
  {
30
30
  url: "a",
31
31
  title: `${query} 1`
@@ -38,36 +38,37 @@ const allTools = [
38
38
  url: "c",
39
39
  title: `${query} 3`
40
40
  }
41
- ]),
42
- finalize: (hits) => ({ count: hits.length })
43
- }),
44
- streaming({
45
- name: "bulk_email",
46
- description: "send",
47
- inputSchema: fromEffectSchema(Schema.Struct({
48
- recipients: Schema.Array(Schema.String),
49
- subject: Schema.String
50
- })),
51
- run: ({ recipients }) => Stream.fromIterable(recipients.map((_, i) => ({
41
+ ];
42
+ yield* Stream.runForEach(Stream.fromIterable(hits), emit);
43
+ return { count: hits.length };
44
+ })
45
+ }), make({
46
+ name: "bulk_email",
47
+ description: "send",
48
+ inputSchema: fromEffectSchema(Schema.Struct({
49
+ recipients: Schema.Array(Schema.String),
50
+ subject: Schema.String
51
+ })),
52
+ run: ({ recipients }, emit) => Effect.gen(function* () {
53
+ yield* Effect.forEach(recipients, (_, i) => emit({
52
54
  type: "progress",
53
55
  sent: i + 1,
54
56
  total: recipients.length
55
- }))),
56
- finalize: (events) => ({
57
+ }));
58
+ return {
57
59
  status: "sent",
58
- delivered: events.length
59
- })
60
- }),
61
- make({
62
- name: "delete_database",
63
- description: "drop",
64
- inputSchema: fromEffectSchema(Schema.Struct({ name: Schema.String })),
65
- run: ({ name }) => Effect.succeed({
66
- status: "dropped",
67
- name
68
- })
60
+ delivered: recipients.length
61
+ };
69
62
  })
70
- ];
63
+ }), make({
64
+ name: "delete_database",
65
+ description: "drop",
66
+ inputSchema: fromEffectSchema(Schema.Struct({ name: Schema.String })),
67
+ run: ({ name }) => Effect.succeed({
68
+ status: "dropped",
69
+ name
70
+ })
71
+ }));
71
72
  const SENSITIVE = new Set(["bulk_email", "delete_database"]);
72
73
  const isSensitive = (call) => SENSITIVE.has(call.name);
73
74
  const fc = (call_id, name, args) => ({
@@ -224,6 +225,45 @@ describe("fromQueue + run", () => {
224
225
  reason: "too risky"
225
226
  });
226
227
  });
228
+ it("router retires per round, so sequential rounds over one queue both resolve", async () => {
229
+ const decisions = await Effect.runPromise(Effect.scoped(Effect.gen(function* () {
230
+ const verdicts = yield* Queue.unbounded();
231
+ const round = (id) => [fc(id, "delete_database", { name: "prod" })];
232
+ const r1 = yield* fromQueue(isSensitive, verdicts)(round("c1"));
233
+ yield* Queue.offer(verdicts, {
234
+ call_id: "c1",
235
+ decision: "approve"
236
+ });
237
+ const d1 = Array.from(yield* Stream.runCollect(r1.decisions));
238
+ const r2 = yield* fromQueue(isSensitive, verdicts)(round("c2"));
239
+ yield* Queue.offer(verdicts, {
240
+ call_id: "c2",
241
+ decision: "deny",
242
+ reason: "no"
243
+ });
244
+ return {
245
+ d1,
246
+ d2: Array.from(yield* Stream.runCollect(r2.decisions))
247
+ };
248
+ })));
249
+ globalExpect(decisions.d1[0]).toMatchObject({ _tag: "Approved" });
250
+ globalExpect(decisions.d2[0]).toMatchObject({
251
+ _tag: "Rejected",
252
+ result: { kind: "denied" }
253
+ });
254
+ });
255
+ it("timeout resolves an unanswered gated call as cancelled", async () => {
256
+ globalExpect((await Effect.runPromise(Effect.scoped(Effect.gen(function* () {
257
+ const { decisions } = yield* fromQueue(isSensitive, yield* Queue.unbounded(), { timeout: "10 millis" })([fc("c1", "delete_database", { name: "prod" })]);
258
+ return Array.from(yield* Stream.runCollect(decisions));
259
+ }))))[0]).toMatchObject({
260
+ _tag: "Rejected",
261
+ result: {
262
+ _tag: "Failure",
263
+ kind: "cancelled"
264
+ }
265
+ });
266
+ });
227
267
  });
228
268
  describe("findUnansweredCalls / cancelAllPending / isReconciled", () => {
229
269
  const orphan = fc("c99", "delete_database", { name: "prod" });
@@ -282,7 +322,7 @@ describe("toToolCallOutput", () => {
282
322
  globalExpect(out.call_id).toBe("c1");
283
323
  globalExpect(JSON.parse(out.output)).toEqual({ count: 3 });
284
324
  });
285
- it("round-trips a Failure result with reason", () => {
325
+ it("renders a Failure with reason as a nested error object", () => {
286
326
  const out = toToolCallOutput({
287
327
  _tag: "Failure",
288
328
  call_id: "c2",
@@ -290,19 +330,19 @@ describe("toToolCallOutput", () => {
290
330
  kind: "denied",
291
331
  reason: "spam concern"
292
332
  });
293
- globalExpect(JSON.parse(out.output)).toEqual({
333
+ globalExpect(JSON.parse(out.output)).toEqual({ error: {
294
334
  kind: "denied",
295
- reason: "spam concern"
296
- });
335
+ message: "spam concern"
336
+ } });
297
337
  });
298
- it("round-trips a Failure result without reason (omits the field)", () => {
338
+ it("renders a Failure without reason (omits message)", () => {
299
339
  const out = toToolCallOutput({
300
340
  _tag: "Failure",
301
341
  call_id: "c3",
302
342
  tool: "delete_database",
303
343
  kind: "cancelled"
304
344
  });
305
- globalExpect(JSON.parse(out.output)).toEqual({ kind: "cancelled" });
345
+ globalExpect(JSON.parse(out.output)).toEqual({ error: { kind: "cancelled" } });
306
346
  });
307
347
  });
308
348
  describe("run", () => {
@@ -1 +1 @@
1
- {"version":3,"file":"Approval.test.mjs","names":["makeTool","Items.toolCallOutput","Items.userText"],"sources":["../../src/tool/Approval.test.ts"],"sourcesContent":["/**\n * Tests for approval planners + history-reconciliation primitives. Exercises\n * the full HITL + streaming-tool stack end-to-end by composing approval plans\n * with `run`, with the four wire-shaped scenarios:\n *\n * 1. Approval : gated calls approved → tools execute, structured Values\n * 2. Denial : gated calls denied → Failure(denied) results\n * 3. Cancellation : missing verdicts → Failure(cancelled) results\n * 4. Mixed + history : reconciliation via cancelAllPending\n *\n * Plus: hallucinated tool name (graceful Failure), unknown_tool kind.\n */\nimport { Effect, Queue, Schema, Stream } from \"effect\"\nimport { describe, expect, it } from \"vitest\"\nimport * as Items from \"../domain/Items.js\"\nimport { findUnansweredCalls, cancelAllPending, isReconciled } from \"./HistoryCheck.js\"\nimport { type ToolResult, isFailure, isOk, toToolCallOutput } from \"./ToolResult.js\"\nimport { type ApprovalMapEntry, type ApprovalDecision, fromMap, fromQueue } from \"./Approval.js\"\nimport { fromEffectSchema, make as makeTool, streaming } from \"./Tool.js\"\nimport { run } from \"./Toolkit.js\"\nimport { ToolEvent, isApprovalRequested, isProgress, isOutput } from \"./ToolEvent.js\"\n\n// ---------------------------------------------------------------------------\n// Three demo tools covering the matrix:\n// - web_search : streaming, no approval\n// - bulk_email : streaming, requires approval\n// - delete_database : non-streaming, requires approval\n// ---------------------------------------------------------------------------\n\nconst webSearch = streaming({\n name: \"web_search\",\n description: \"search\",\n inputSchema: fromEffectSchema(Schema.Struct({ query: Schema.String })),\n run: ({ query }) =>\n Stream.fromIterable([\n { url: \"a\", title: `${query} 1` },\n { url: \"b\", title: `${query} 2` },\n { url: \"c\", title: `${query} 3` },\n ]),\n finalize: (hits) => ({ count: hits.length }),\n})\n\nconst bulkEmail = streaming({\n name: \"bulk_email\",\n description: \"send\",\n inputSchema: fromEffectSchema(\n Schema.Struct({ recipients: Schema.Array(Schema.String), subject: Schema.String }),\n ),\n run: ({ recipients }) =>\n Stream.fromIterable(\n recipients.map((_, i) => ({\n type: \"progress\" as const,\n sent: i + 1,\n total: recipients.length,\n })),\n ),\n finalize: (events) => ({ status: \"sent\" as const, delivered: events.length }),\n})\n\nconst deleteDatabase = makeTool({\n name: \"delete_database\",\n description: \"drop\",\n inputSchema: fromEffectSchema(Schema.Struct({ name: Schema.String })),\n run: ({ name }) => Effect.succeed({ status: \"dropped\", name }),\n})\n\nconst allTools = [webSearch, bulkEmail, deleteDatabase]\nconst SENSITIVE = new Set([\"bulk_email\", \"delete_database\"])\nconst isSensitive = (call: Items.ToolCall) => SENSITIVE.has(call.name)\n\nconst fc = (call_id: string, name: string, args: unknown): Items.ToolCall => ({\n type: \"function_call\",\n call_id,\n name,\n arguments: JSON.stringify(args),\n})\n\nconst calls = [\n fc(\"c1\", \"web_search\", { query: \"effect\" }),\n fc(\"c2\", \"bulk_email\", { recipients: [\"a@x\", \"b@x\"], subject: \"Hi\" }),\n fc(\"c3\", \"delete_database\", { name: \"prod\" }),\n]\n\nconst resultsFrom = (collected: ReadonlyArray<ToolEvent>): ReadonlyArray<ToolResult> =>\n collected.filter(isOutput).map((e) => e.result)\n\nconst byCallId = (results: ReadonlyArray<ToolResult>) => new Map(results.map((r) => [r.call_id, r]))\n\nconst rejectedStream = (rejected: ReadonlyArray<ToolResult>) =>\n Stream.fromIterable(rejected.map((result) => ToolEvent.Output({ result })))\n\nconst eventsFromApprovalMap = (approvals: ReadonlyMap<string, ApprovalMapEntry>) => {\n const plan = fromMap(isSensitive, approvals)(calls)\n return Stream.merge(run(allTools, plan.approved), rejectedStream(plan.rejected))\n}\n\nconst eventsFromDecision = (decision: ApprovalDecision): Stream.Stream<ToolEvent> =>\n decision._tag === \"Approved\"\n ? run(allTools, [decision.call])\n : Stream.succeed(ToolEvent.Output({ result: decision.result }))\n\n// ---------------------------------------------------------------------------\n// fromMap: HTTP-style scenarios\n// ---------------------------------------------------------------------------\n\ndescribe(\"fromMap + run\", () => {\n it(\"approval: all gated approved → tools execute, structured Values\", async () => {\n const approvals = new Map<string, ApprovalMapEntry>([\n [\"c2\", { decision: \"approve\" }],\n [\"c3\", { decision: \"approve\" }],\n ])\n const collected = await Effect.runPromise(Stream.runCollect(eventsFromApprovalMap(approvals)))\n const by = byCallId(resultsFrom(collected))\n expect(by.get(\"c1\")).toMatchObject({ _tag: \"Ok\", value: { count: 3 } })\n expect(by.get(\"c2\")).toMatchObject({\n _tag: \"Ok\",\n value: { status: \"sent\", delivered: 2 },\n })\n expect(by.get(\"c3\")).toMatchObject({\n _tag: \"Ok\",\n value: { status: \"dropped\", name: \"prod\" },\n })\n\n // No ApprovalRequested events from the pure HTTP flow.\n expect(collected.filter(isApprovalRequested)).toHaveLength(0)\n })\n\n it(\"denial: gated denied → Failure(denied), no execution\", async () => {\n const approvals = new Map<string, ApprovalMapEntry>([\n [\"c2\", { decision: \"deny\", reason: \"spam concern\" }],\n [\"c3\", { decision: \"deny\", reason: \"prod is sacred\" }],\n ])\n const collected = await Effect.runPromise(Stream.runCollect(eventsFromApprovalMap(approvals)))\n\n // bulk_email never ran.\n expect(collected.filter(isProgress).filter((e) => e.tool === \"bulk_email\")).toHaveLength(0)\n\n const by = byCallId(resultsFrom(collected))\n expect(by.get(\"c2\")).toMatchObject({\n _tag: \"Failure\",\n kind: \"denied\",\n reason: \"spam concern\",\n })\n expect(by.get(\"c3\")).toMatchObject({\n _tag: \"Failure\",\n kind: \"denied\",\n reason: \"prod is sacred\",\n })\n })\n\n it(\"cancellation: missing verdicts → Failure(cancelled)\", async () => {\n const collected = await Effect.runPromise(Stream.runCollect(eventsFromApprovalMap(new Map())))\n const by = byCallId(resultsFrom(collected))\n expect(by.get(\"c1\")).toMatchObject({ _tag: \"Ok\", value: { count: 3 } })\n expect(by.get(\"c2\")).toMatchObject({ _tag: \"Failure\", kind: \"cancelled\" })\n expect(by.get(\"c3\")).toMatchObject({ _tag: \"Failure\", kind: \"cancelled\" })\n })\n\n it(\"mixed: approve + deny + omit → all three kinds\", async () => {\n const approvals = new Map<string, ApprovalMapEntry>([\n [\"c2\", { decision: \"approve\" }],\n // c3 omitted → cancelled\n ])\n const collected = await Effect.runPromise(Stream.runCollect(eventsFromApprovalMap(approvals)))\n const by = byCallId(resultsFrom(collected))\n expect(by.get(\"c1\")).toMatchObject({ _tag: \"Ok\", value: { count: 3 } })\n expect(by.get(\"c2\")).toMatchObject({ _tag: \"Ok\", value: { status: \"sent\" } })\n expect(by.get(\"c3\")).toMatchObject({ _tag: \"Failure\", kind: \"cancelled\" })\n })\n})\n\n// ---------------------------------------------------------------------------\n// Graceful degradation: hallucinated tool name doesn't kill the turn.\n// ---------------------------------------------------------------------------\n\ndescribe(\"run: graceful degradation\", () => {\n it(\"unknown tool name → Failure(unknown_tool); other calls still execute\", async () => {\n const callsWithBogus = [\n fc(\"c1\", \"web_search\", { query: \"x\" }),\n fc(\"c2\", \"does_not_exist\", {}),\n fc(\"c3\", \"delete_database\", { name: \"prod\" }),\n ]\n const collected = await Effect.runPromise(\n Stream.runCollect(\n (() => {\n const plan = fromMap(\n isSensitive,\n new Map([[\"c3\", { decision: \"approve\" }]]),\n )(callsWithBogus)\n return Stream.merge(run(allTools, plan.approved), rejectedStream(plan.rejected))\n })(),\n ),\n )\n const by = byCallId(resultsFrom(collected))\n expect(by.get(\"c1\")).toMatchObject({ _tag: \"Ok\" })\n expect(by.get(\"c2\")).toMatchObject({ _tag: \"Failure\", kind: \"unknown_tool\" })\n expect(by.get(\"c3\")).toMatchObject({ _tag: \"Ok\", value: { status: \"dropped\" } })\n })\n})\n\n// ---------------------------------------------------------------------------\n// fromQueue: WebSocket-style scenarios\n// ---------------------------------------------------------------------------\n\ndescribe(\"fromQueue + run\", () => {\n it(\"queue-driven: approve + deny resolve correctly with ApprovalRequested events\", async () => {\n const collected = await Effect.runPromise(\n Effect.gen(function* () {\n const verdicts = yield* Queue.unbounded<{\n readonly call_id: string\n readonly decision: \"approve\" | \"deny\"\n readonly reason?: string\n }>()\n yield* Queue.offer(verdicts, { call_id: \"c2\", decision: \"approve\" })\n yield* Queue.offer(verdicts, {\n call_id: \"c3\",\n decision: \"deny\",\n reason: \"too risky\",\n })\n\n // Stream.unwrap supplies the Scope for fromQueue's router.\n const events = Stream.unwrap(\n Effect.gen(function* () {\n const { approved, decisions, approvalRequests } = yield* fromQueue(\n isSensitive,\n verdicts,\n )(calls)\n return Stream.merge(\n approvalRequests,\n Stream.merge(\n run(allTools, approved),\n decisions.pipe(Stream.flatMap(eventsFromDecision)),\n ),\n )\n }),\n )\n return yield* Stream.runCollect(events)\n }),\n )\n\n expect(collected.filter(isApprovalRequested)).toHaveLength(2)\n\n const by = byCallId(resultsFrom(collected))\n expect(by.get(\"c2\")).toMatchObject({ _tag: \"Ok\", value: { status: \"sent\" } })\n expect(by.get(\"c3\")).toMatchObject({\n _tag: \"Failure\",\n kind: \"denied\",\n reason: \"too risky\",\n })\n })\n})\n\n// ---------------------------------------------------------------------------\n// History reconciliation\n// ---------------------------------------------------------------------------\n\ndescribe(\"findUnansweredCalls / cancelAllPending / isReconciled\", () => {\n const orphan = fc(\"c99\", \"delete_database\", { name: \"prod\" })\n const answered = fc(\"c98\", \"web_search\", { query: \"x\" })\n const answeredOutput = Items.toolCallOutput(\"c98\", JSON.stringify({ count: 0 }))\n\n it(\"findUnansweredCalls returns only orphans\", () => {\n const history = [Items.userText(\"hi\"), answered, orphan, answeredOutput]\n const unanswered = findUnansweredCalls(history)\n expect(unanswered).toHaveLength(1)\n expect(unanswered[0]!.call_id).toBe(\"c99\")\n })\n\n it(\"isReconciled is false when orphans exist, true otherwise\", () => {\n const stale = [Items.userText(\"hi\"), orphan]\n expect(isReconciled(stale)).toBe(false)\n const reconciled = [...stale, ...cancelAllPending(stale).map(toToolCallOutput)]\n expect(isReconciled(reconciled)).toBe(true)\n })\n\n it(\"cancelAllPending synthesizes one Failure(cancelled) per orphan\", () => {\n const history = [Items.userText(\"hi\"), answered, orphan, answeredOutput]\n const closures = cancelAllPending(history, \"user moved on\")\n expect(closures).toHaveLength(1)\n const c = closures[0]!\n expect(isFailure(c)).toBe(true)\n expect(c).toMatchObject({\n _tag: \"Failure\",\n call_id: \"c99\",\n kind: \"cancelled\",\n reason: \"user moved on\",\n })\n })\n\n it(\"follow-up: map closures to ToolCallOutput before appending new user message\", () => {\n const stale = [Items.userText(\"first request\"), orphan]\n const closures = cancelAllPending(stale, \"user redirected\")\n const reconciled = [...stale, ...closures.map(toToolCallOutput), Items.userText(\"never mind\")]\n expect(findUnansweredCalls(reconciled)).toHaveLength(0)\n })\n})\n\n// ---------------------------------------------------------------------------\n// Wire conversion\n// ---------------------------------------------------------------------------\n\ndescribe(\"toToolCallOutput\", () => {\n it(\"round-trips a Value result\", () => {\n const r: ToolResult = {\n _tag: \"Ok\",\n call_id: \"c1\",\n tool: \"web_search\",\n value: { count: 3 },\n }\n const out = toToolCallOutput(r)\n expect(out.call_id).toBe(\"c1\")\n expect(JSON.parse(out.output)).toEqual({ count: 3 })\n })\n\n it(\"round-trips a Failure result with reason\", () => {\n const r: ToolResult = {\n _tag: \"Failure\",\n call_id: \"c2\",\n tool: \"bulk_email\",\n kind: \"denied\",\n reason: \"spam concern\",\n }\n const out = toToolCallOutput(r)\n expect(JSON.parse(out.output)).toEqual({ kind: \"denied\", reason: \"spam concern\" })\n })\n\n it(\"round-trips a Failure result without reason (omits the field)\", () => {\n const r: ToolResult = {\n _tag: \"Failure\",\n call_id: \"c3\",\n tool: \"delete_database\",\n kind: \"cancelled\",\n }\n const out = toToolCallOutput(r)\n expect(JSON.parse(out.output)).toEqual({ kind: \"cancelled\" })\n })\n})\n\n// ---------------------------------------------------------------------------\n// run\n// ---------------------------------------------------------------------------\n\ndescribe(\"run\", () => {\n it(\"runs all calls passed to it\", async () => {\n const collected = await Effect.runPromise(Stream.runCollect(run(allTools, calls)))\n expect(collected.filter(isOutput)).toHaveLength(3)\n expect(collected.filter(isOutput).every((e) => isOk(e.result))).toBe(true)\n })\n})\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAkEA,MAAM,WAAW;CArCC,UAAU;EAC1B,MAAM;EACN,aAAa;EACb,aAAa,iBAAiB,OAAO,OAAO,EAAE,OAAO,OAAO,QAAQ,CAAC,CAAC;EACtE,MAAM,EAAE,YACN,OAAO,aAAa;GAClB;IAAE,KAAK;IAAK,OAAO,GAAG,MAAM;IAAK;GACjC;IAAE,KAAK;IAAK,OAAO,GAAG,MAAM;IAAK;GACjC;IAAE,KAAK;IAAK,OAAO,GAAG,MAAM;IAAK;GAClC,CAAC;EACJ,WAAW,UAAU,EAAE,OAAO,KAAK,QAAQ;EAC5C,CA0B0B;CAxBT,UAAU;EAC1B,MAAM;EACN,aAAa;EACb,aAAa,iBACX,OAAO,OAAO;GAAE,YAAY,OAAO,MAAM,OAAO,OAAO;GAAE,SAAS,OAAO;GAAQ,CAAC,CACnF;EACD,MAAM,EAAE,iBACN,OAAO,aACL,WAAW,KAAK,GAAG,OAAO;GACxB,MAAM;GACN,MAAM,IAAI;GACV,OAAO,WAAW;GACnB,EAAE,CACJ;EACH,WAAW,YAAY;GAAE,QAAQ;GAAiB,WAAW,OAAO;GAAQ;EAC7E,CASqC;CAPfA,KAAS;EAC9B,MAAM;EACN,aAAa;EACb,aAAa,iBAAiB,OAAO,OAAO,EAAE,MAAM,OAAO,QAAQ,CAAC,CAAC;EACrE,MAAM,EAAE,WAAW,OAAO,QAAQ;GAAE,QAAQ;GAAW;GAAM,CAAC;EAC/D,CAEqD;CAAC;AACvD,MAAM,YAAY,IAAI,IAAI,CAAC,cAAc,kBAAkB,CAAC;AAC5D,MAAM,eAAe,SAAyB,UAAU,IAAI,KAAK,KAAK;AAEtE,MAAM,MAAM,SAAiB,MAAc,UAAmC;CAC5E,MAAM;CACN;CACA;CACA,WAAW,KAAK,UAAU,KAAK;CAChC;AAED,MAAM,QAAQ;CACZ,GAAG,MAAM,cAAc,EAAE,OAAO,UAAU,CAAC;CAC3C,GAAG,MAAM,cAAc;EAAE,YAAY,CAAC,OAAO,MAAM;EAAE,SAAS;EAAM,CAAC;CACrE,GAAG,MAAM,mBAAmB,EAAE,MAAM,QAAQ,CAAC;CAC9C;AAED,MAAM,eAAe,cACnB,UAAU,OAAO,SAAS,CAAC,KAAK,MAAM,EAAE,OAAO;AAEjD,MAAM,YAAY,YAAuC,IAAI,IAAI,QAAQ,KAAK,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC;AAEpG,MAAM,kBAAkB,aACtB,OAAO,aAAa,SAAS,KAAK,WAAW,UAAU,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;AAE7E,MAAM,yBAAyB,cAAqD;CAClF,MAAM,OAAO,QAAQ,aAAa,UAAU,CAAC,MAAM;AACnD,QAAO,OAAO,MAAM,IAAI,UAAU,KAAK,SAAS,EAAE,eAAe,KAAK,SAAS,CAAC;;AAGlF,MAAM,sBAAsB,aAC1B,SAAS,SAAS,aACd,IAAI,UAAU,CAAC,SAAS,KAAK,CAAC,GAC9B,OAAO,QAAQ,UAAU,OAAO,EAAE,QAAQ,SAAS,QAAQ,CAAC,CAAC;AAMnE,SAAS,uBAAuB;AAC9B,IAAG,mEAAmE,YAAY;EAChF,MAAM,YAAY,IAAI,IAA8B,CAClD,CAAC,MAAM,EAAE,UAAU,WAAW,CAAC,EAC/B,CAAC,MAAM,EAAE,UAAU,WAAW,CAAC,CAChC,CAAC;EACF,MAAM,YAAY,MAAM,OAAO,WAAW,OAAO,WAAW,sBAAsB,UAAU,CAAC,CAAC;EAC9F,MAAM,KAAK,SAAS,YAAY,UAAU,CAAC;AAC3C,eAAO,GAAG,IAAI,KAAK,CAAC,CAAC,cAAc;GAAE,MAAM;GAAM,OAAO,EAAE,OAAO,GAAG;GAAE,CAAC;AACvE,eAAO,GAAG,IAAI,KAAK,CAAC,CAAC,cAAc;GACjC,MAAM;GACN,OAAO;IAAE,QAAQ;IAAQ,WAAW;IAAG;GACxC,CAAC;AACF,eAAO,GAAG,IAAI,KAAK,CAAC,CAAC,cAAc;GACjC,MAAM;GACN,OAAO;IAAE,QAAQ;IAAW,MAAM;IAAQ;GAC3C,CAAC;AAGF,eAAO,UAAU,OAAO,oBAAoB,CAAC,CAAC,aAAa,EAAE;GAC7D;AAEF,IAAG,wDAAwD,YAAY;EACrE,MAAM,YAAY,IAAI,IAA8B,CAClD,CAAC,MAAM;GAAE,UAAU;GAAQ,QAAQ;GAAgB,CAAC,EACpD,CAAC,MAAM;GAAE,UAAU;GAAQ,QAAQ;GAAkB,CAAC,CACvD,CAAC;EACF,MAAM,YAAY,MAAM,OAAO,WAAW,OAAO,WAAW,sBAAsB,UAAU,CAAC,CAAC;AAG9F,eAAO,UAAU,OAAO,WAAW,CAAC,QAAQ,MAAM,EAAE,SAAS,aAAa,CAAC,CAAC,aAAa,EAAE;EAE3F,MAAM,KAAK,SAAS,YAAY,UAAU,CAAC;AAC3C,eAAO,GAAG,IAAI,KAAK,CAAC,CAAC,cAAc;GACjC,MAAM;GACN,MAAM;GACN,QAAQ;GACT,CAAC;AACF,eAAO,GAAG,IAAI,KAAK,CAAC,CAAC,cAAc;GACjC,MAAM;GACN,MAAM;GACN,QAAQ;GACT,CAAC;GACF;AAEF,IAAG,uDAAuD,YAAY;EAEpE,MAAM,KAAK,SAAS,YAAY,MADR,OAAO,WAAW,OAAO,WAAW,sCAAsB,IAAI,KAAK,CAAC,CAAC,CAAC,CACpD,CAAC;AAC3C,eAAO,GAAG,IAAI,KAAK,CAAC,CAAC,cAAc;GAAE,MAAM;GAAM,OAAO,EAAE,OAAO,GAAG;GAAE,CAAC;AACvE,eAAO,GAAG,IAAI,KAAK,CAAC,CAAC,cAAc;GAAE,MAAM;GAAW,MAAM;GAAa,CAAC;AAC1E,eAAO,GAAG,IAAI,KAAK,CAAC,CAAC,cAAc;GAAE,MAAM;GAAW,MAAM;GAAa,CAAC;GAC1E;AAEF,IAAG,kDAAkD,YAAY;EAC/D,MAAM,YAAY,IAAI,IAA8B,CAClD,CAAC,MAAM,EAAE,UAAU,WAAW,CAAC,CAEhC,CAAC;EAEF,MAAM,KAAK,SAAS,YAAY,MADR,OAAO,WAAW,OAAO,WAAW,sBAAsB,UAAU,CAAC,CAAC,CACpD,CAAC;AAC3C,eAAO,GAAG,IAAI,KAAK,CAAC,CAAC,cAAc;GAAE,MAAM;GAAM,OAAO,EAAE,OAAO,GAAG;GAAE,CAAC;AACvE,eAAO,GAAG,IAAI,KAAK,CAAC,CAAC,cAAc;GAAE,MAAM;GAAM,OAAO,EAAE,QAAQ,QAAQ;GAAE,CAAC;AAC7E,eAAO,GAAG,IAAI,KAAK,CAAC,CAAC,cAAc;GAAE,MAAM;GAAW,MAAM;GAAa,CAAC;GAC1E;EACF;AAMF,SAAS,mCAAmC;AAC1C,IAAG,wEAAwE,YAAY;EACrF,MAAM,iBAAiB;GACrB,GAAG,MAAM,cAAc,EAAE,OAAO,KAAK,CAAC;GACtC,GAAG,MAAM,kBAAkB,EAAE,CAAC;GAC9B,GAAG,MAAM,mBAAmB,EAAE,MAAM,QAAQ,CAAC;GAC9C;EAYD,MAAM,KAAK,SAAS,YAAY,MAXR,OAAO,WAC7B,OAAO,kBACE;GACL,MAAM,OAAO,QACX,aACA,IAAI,IAAI,CAAC,CAAC,MAAM,EAAE,UAAU,WAAW,CAAC,CAAC,CAAC,CAC3C,CAAC,eAAe;AACjB,UAAO,OAAO,MAAM,IAAI,UAAU,KAAK,SAAS,EAAE,eAAe,KAAK,SAAS,CAAC;MAC9E,CACL,CACF,CACyC,CAAC;AAC3C,eAAO,GAAG,IAAI,KAAK,CAAC,CAAC,cAAc,EAAE,MAAM,MAAM,CAAC;AAClD,eAAO,GAAG,IAAI,KAAK,CAAC,CAAC,cAAc;GAAE,MAAM;GAAW,MAAM;GAAgB,CAAC;AAC7E,eAAO,GAAG,IAAI,KAAK,CAAC,CAAC,cAAc;GAAE,MAAM;GAAM,OAAO,EAAE,QAAQ,WAAW;GAAE,CAAC;GAChF;EACF;AAMF,SAAS,yBAAyB;AAChC,IAAG,gFAAgF,YAAY;EAC7F,MAAM,YAAY,MAAM,OAAO,WAC7B,OAAO,IAAI,aAAa;GACtB,MAAM,WAAW,OAAO,MAAM,WAI1B;AACJ,UAAO,MAAM,MAAM,UAAU;IAAE,SAAS;IAAM,UAAU;IAAW,CAAC;AACpE,UAAO,MAAM,MAAM,UAAU;IAC3B,SAAS;IACT,UAAU;IACV,QAAQ;IACT,CAAC;GAGF,MAAM,SAAS,OAAO,OACpB,OAAO,IAAI,aAAa;IACtB,MAAM,EAAE,UAAU,WAAW,qBAAqB,OAAO,UACvD,aACA,SACD,CAAC,MAAM;AACR,WAAO,OAAO,MACZ,kBACA,OAAO,MACL,IAAI,UAAU,SAAS,EACvB,UAAU,KAAK,OAAO,QAAQ,mBAAmB,CAAC,CACnD,CACF;KACD,CACH;AACD,UAAO,OAAO,OAAO,WAAW,OAAO;IACvC,CACH;AAED,eAAO,UAAU,OAAO,oBAAoB,CAAC,CAAC,aAAa,EAAE;EAE7D,MAAM,KAAK,SAAS,YAAY,UAAU,CAAC;AAC3C,eAAO,GAAG,IAAI,KAAK,CAAC,CAAC,cAAc;GAAE,MAAM;GAAM,OAAO,EAAE,QAAQ,QAAQ;GAAE,CAAC;AAC7E,eAAO,GAAG,IAAI,KAAK,CAAC,CAAC,cAAc;GACjC,MAAM;GACN,MAAM;GACN,QAAQ;GACT,CAAC;GACF;EACF;AAMF,SAAS,+DAA+D;CACtE,MAAM,SAAS,GAAG,OAAO,mBAAmB,EAAE,MAAM,QAAQ,CAAC;CAC7D,MAAM,WAAW,GAAG,OAAO,cAAc,EAAE,OAAO,KAAK,CAAC;CACxD,MAAM,iBAAiBC,eAAqB,OAAO,KAAK,UAAU,EAAE,OAAO,GAAG,CAAC,CAAC;AAEhF,IAAG,kDAAkD;EAEnD,MAAM,aAAa,oBAAoB;GADtBC,SAAe,KAAK;GAAE;GAAU;GAAQ;GACX,CAAC;AAC/C,eAAO,WAAW,CAAC,aAAa,EAAE;AAClC,eAAO,WAAW,GAAI,QAAQ,CAAC,KAAK,MAAM;GAC1C;AAEF,IAAG,kEAAkE;EACnE,MAAM,QAAQ,CAACA,SAAe,KAAK,EAAE,OAAO;AAC5C,eAAO,aAAa,MAAM,CAAC,CAAC,KAAK,MAAM;AAEvC,eAAO,aAAa,CADA,GAAG,OAAO,GAAG,iBAAiB,MAAM,CAAC,IAAI,iBAAiB,CAChD,CAAC,CAAC,CAAC,KAAK,KAAK;GAC3C;AAEF,IAAG,wEAAwE;EAEzE,MAAM,WAAW,iBAAiB;GADjBA,SAAe,KAAK;GAAE;GAAU;GAAQ;GAChB,EAAE,gBAAgB;AAC3D,eAAO,SAAS,CAAC,aAAa,EAAE;EAChC,MAAM,IAAI,SAAS;AACnB,eAAO,UAAU,EAAE,CAAC,CAAC,KAAK,KAAK;AAC/B,eAAO,EAAE,CAAC,cAAc;GACtB,MAAM;GACN,SAAS;GACT,MAAM;GACN,QAAQ;GACT,CAAC;GACF;AAEF,IAAG,qFAAqF;EACtF,MAAM,QAAQ,CAACA,SAAe,gBAAgB,EAAE,OAAO;EACvD,MAAM,WAAW,iBAAiB,OAAO,kBAAkB;AAE3D,eAAO,oBAAoB;GADP,GAAG;GAAO,GAAG,SAAS,IAAI,iBAAiB;GAAEA,SAAe,aAAa;GACxD,CAAC,CAAC,CAAC,aAAa,EAAE;GACvD;EACF;AAMF,SAAS,0BAA0B;AACjC,IAAG,oCAAoC;EAOrC,MAAM,MAAM,iBAAiB;GAL3B,MAAM;GACN,SAAS;GACT,MAAM;GACN,OAAO,EAAE,OAAO,GAAG;GAES,CAAC;AAC/B,eAAO,IAAI,QAAQ,CAAC,KAAK,KAAK;AAC9B,eAAO,KAAK,MAAM,IAAI,OAAO,CAAC,CAAC,QAAQ,EAAE,OAAO,GAAG,CAAC;GACpD;AAEF,IAAG,kDAAkD;EAQnD,MAAM,MAAM,iBAAiB;GAN3B,MAAM;GACN,SAAS;GACT,MAAM;GACN,MAAM;GACN,QAAQ;GAEoB,CAAC;AAC/B,eAAO,KAAK,MAAM,IAAI,OAAO,CAAC,CAAC,QAAQ;GAAE,MAAM;GAAU,QAAQ;GAAgB,CAAC;GAClF;AAEF,IAAG,uEAAuE;EAOxE,MAAM,MAAM,iBAAiB;GAL3B,MAAM;GACN,SAAS;GACT,MAAM;GACN,MAAM;GAEsB,CAAC;AAC/B,eAAO,KAAK,MAAM,IAAI,OAAO,CAAC,CAAC,QAAQ,EAAE,MAAM,aAAa,CAAC;GAC7D;EACF;AAMF,SAAS,aAAa;AACpB,IAAG,+BAA+B,YAAY;EAC5C,MAAM,YAAY,MAAM,OAAO,WAAW,OAAO,WAAW,IAAI,UAAU,MAAM,CAAC,CAAC;AAClF,eAAO,UAAU,OAAO,SAAS,CAAC,CAAC,aAAa,EAAE;AAClD,eAAO,UAAU,OAAO,SAAS,CAAC,OAAO,MAAM,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,KAAK;GAC1E;EACF"}
1
+ {"version":3,"file":"Approval.test.mjs","names":["makeToolkit","makeTool","Items.toolCallOutput","Items.userText"],"sources":["../../src/tool/Approval.test.ts"],"sourcesContent":["/**\n * Tests for approval planners + history-reconciliation primitives. Exercises\n * the full HITL + streaming-tool stack end-to-end by composing approval plans\n * with `run`, with the four wire-shaped scenarios:\n *\n * 1. Approval : gated calls approved → tools execute, structured Values\n * 2. Denial : gated calls denied → Failure(denied) results\n * 3. Cancellation : missing verdicts → Failure(cancelled) results\n * 4. Mixed + history : reconciliation via cancelAllPending\n *\n * Plus: hallucinated tool name (graceful Failure), unknown_tool kind.\n */\nimport { Effect, Queue, Schema, Stream } from \"effect\"\nimport { describe, expect, it } from \"vitest\"\nimport * as Items from \"../domain/Items.js\"\nimport { findUnansweredCalls, cancelAllPending, isReconciled } from \"./HistoryCheck.js\"\nimport { type ToolResult, isFailure, isOk, toToolCallOutput } from \"./ToolResult.js\"\nimport {\n type ApprovalMapEntry,\n type ApprovalDecision,\n type Verdict,\n fromMap,\n fromQueue,\n} from \"./Approval.js\"\nimport { fromEffectSchema, make as makeTool } from \"./Tool.js\"\nimport { make as makeToolkit, run } from \"./Toolkit.js\"\nimport { ToolEvent, isApprovalRequested, isProgress, isOutput } from \"./ToolEvent.js\"\n\n// ---------------------------------------------------------------------------\n// Three demo tools covering the matrix:\n// - web_search : streaming, no approval\n// - bulk_email : streaming, requires approval\n// - delete_database : non-streaming, requires approval\n// ---------------------------------------------------------------------------\n\nconst webSearch = makeTool({\n name: \"web_search\",\n description: \"search\",\n inputSchema: fromEffectSchema(Schema.Struct({ query: Schema.String })),\n run: ({ query }, emit) =>\n Effect.gen(function* () {\n const hits = [\n { url: \"a\", title: `${query} 1` },\n { url: \"b\", title: `${query} 2` },\n { url: \"c\", title: `${query} 3` },\n ]\n yield* Stream.runForEach(Stream.fromIterable(hits), emit)\n return { count: hits.length }\n }),\n})\n\nconst bulkEmail = makeTool({\n name: \"bulk_email\",\n description: \"send\",\n inputSchema: fromEffectSchema(\n Schema.Struct({ recipients: Schema.Array(Schema.String), subject: Schema.String }),\n ),\n run: ({ recipients }, emit) =>\n Effect.gen(function* () {\n yield* Effect.forEach(recipients, (_, i) =>\n emit({ type: \"progress\" as const, sent: i + 1, total: recipients.length }),\n )\n return { status: \"sent\" as const, delivered: recipients.length }\n }),\n})\n\nconst deleteDatabase = makeTool({\n name: \"delete_database\",\n description: \"drop\",\n inputSchema: fromEffectSchema(Schema.Struct({ name: Schema.String })),\n run: ({ name }) => Effect.succeed({ status: \"dropped\", name }),\n})\n\nconst allTools = makeToolkit(webSearch, bulkEmail, deleteDatabase)\nconst SENSITIVE = new Set([\"bulk_email\", \"delete_database\"])\nconst isSensitive = (call: Items.ToolCall) => SENSITIVE.has(call.name)\n\nconst fc = (call_id: string, name: string, args: unknown): Items.ToolCall => ({\n type: \"function_call\",\n call_id,\n name,\n arguments: JSON.stringify(args),\n})\n\nconst calls = [\n fc(\"c1\", \"web_search\", { query: \"effect\" }),\n fc(\"c2\", \"bulk_email\", { recipients: [\"a@x\", \"b@x\"], subject: \"Hi\" }),\n fc(\"c3\", \"delete_database\", { name: \"prod\" }),\n]\n\nconst resultsFrom = (collected: ReadonlyArray<ToolEvent>): ReadonlyArray<ToolResult> =>\n collected.filter(isOutput).map((e) => e.result)\n\nconst byCallId = (results: ReadonlyArray<ToolResult>) => new Map(results.map((r) => [r.call_id, r]))\n\nconst rejectedStream = (rejected: ReadonlyArray<ToolResult>) =>\n Stream.fromIterable(rejected.map((result) => ToolEvent.Output({ result })))\n\nconst eventsFromApprovalMap = (approvals: ReadonlyMap<string, ApprovalMapEntry>) => {\n const plan = fromMap(isSensitive, approvals)(calls)\n return Stream.merge(run(allTools, plan.approved), rejectedStream(plan.rejected))\n}\n\nconst eventsFromDecision = (decision: ApprovalDecision): Stream.Stream<ToolEvent> =>\n decision._tag === \"Approved\"\n ? run(allTools, [decision.call])\n : Stream.succeed(ToolEvent.Output({ result: decision.result }))\n\n// ---------------------------------------------------------------------------\n// fromMap: HTTP-style scenarios\n// ---------------------------------------------------------------------------\n\ndescribe(\"fromMap + run\", () => {\n it(\"approval: all gated approved → tools execute, structured Values\", async () => {\n const approvals = new Map<string, ApprovalMapEntry>([\n [\"c2\", { decision: \"approve\" }],\n [\"c3\", { decision: \"approve\" }],\n ])\n const collected = await Effect.runPromise(Stream.runCollect(eventsFromApprovalMap(approvals)))\n const by = byCallId(resultsFrom(collected))\n expect(by.get(\"c1\")).toMatchObject({ _tag: \"Ok\", value: { count: 3 } })\n expect(by.get(\"c2\")).toMatchObject({\n _tag: \"Ok\",\n value: { status: \"sent\", delivered: 2 },\n })\n expect(by.get(\"c3\")).toMatchObject({\n _tag: \"Ok\",\n value: { status: \"dropped\", name: \"prod\" },\n })\n\n // No ApprovalRequested events from the pure HTTP flow.\n expect(collected.filter(isApprovalRequested)).toHaveLength(0)\n })\n\n it(\"denial: gated denied → Failure(denied), no execution\", async () => {\n const approvals = new Map<string, ApprovalMapEntry>([\n [\"c2\", { decision: \"deny\", reason: \"spam concern\" }],\n [\"c3\", { decision: \"deny\", reason: \"prod is sacred\" }],\n ])\n const collected = await Effect.runPromise(Stream.runCollect(eventsFromApprovalMap(approvals)))\n\n // bulk_email never ran.\n expect(collected.filter(isProgress).filter((e) => e.tool === \"bulk_email\")).toHaveLength(0)\n\n const by = byCallId(resultsFrom(collected))\n expect(by.get(\"c2\")).toMatchObject({\n _tag: \"Failure\",\n kind: \"denied\",\n reason: \"spam concern\",\n })\n expect(by.get(\"c3\")).toMatchObject({\n _tag: \"Failure\",\n kind: \"denied\",\n reason: \"prod is sacred\",\n })\n })\n\n it(\"cancellation: missing verdicts → Failure(cancelled)\", async () => {\n const collected = await Effect.runPromise(Stream.runCollect(eventsFromApprovalMap(new Map())))\n const by = byCallId(resultsFrom(collected))\n expect(by.get(\"c1\")).toMatchObject({ _tag: \"Ok\", value: { count: 3 } })\n expect(by.get(\"c2\")).toMatchObject({ _tag: \"Failure\", kind: \"cancelled\" })\n expect(by.get(\"c3\")).toMatchObject({ _tag: \"Failure\", kind: \"cancelled\" })\n })\n\n it(\"mixed: approve + deny + omit → all three kinds\", async () => {\n const approvals = new Map<string, ApprovalMapEntry>([\n [\"c2\", { decision: \"approve\" }],\n // c3 omitted → cancelled\n ])\n const collected = await Effect.runPromise(Stream.runCollect(eventsFromApprovalMap(approvals)))\n const by = byCallId(resultsFrom(collected))\n expect(by.get(\"c1\")).toMatchObject({ _tag: \"Ok\", value: { count: 3 } })\n expect(by.get(\"c2\")).toMatchObject({ _tag: \"Ok\", value: { status: \"sent\" } })\n expect(by.get(\"c3\")).toMatchObject({ _tag: \"Failure\", kind: \"cancelled\" })\n })\n})\n\n// ---------------------------------------------------------------------------\n// Graceful degradation: hallucinated tool name doesn't kill the turn.\n// ---------------------------------------------------------------------------\n\ndescribe(\"run: graceful degradation\", () => {\n it(\"unknown tool name → Failure(unknown_tool); other calls still execute\", async () => {\n const callsWithBogus = [\n fc(\"c1\", \"web_search\", { query: \"x\" }),\n fc(\"c2\", \"does_not_exist\", {}),\n fc(\"c3\", \"delete_database\", { name: \"prod\" }),\n ]\n const collected = await Effect.runPromise(\n Stream.runCollect(\n (() => {\n const plan = fromMap(\n isSensitive,\n new Map([[\"c3\", { decision: \"approve\" }]]),\n )(callsWithBogus)\n return Stream.merge(run(allTools, plan.approved), rejectedStream(plan.rejected))\n })(),\n ),\n )\n const by = byCallId(resultsFrom(collected))\n expect(by.get(\"c1\")).toMatchObject({ _tag: \"Ok\" })\n expect(by.get(\"c2\")).toMatchObject({ _tag: \"Failure\", kind: \"unknown_tool\" })\n expect(by.get(\"c3\")).toMatchObject({ _tag: \"Ok\", value: { status: \"dropped\" } })\n })\n})\n\n// ---------------------------------------------------------------------------\n// fromQueue: WebSocket-style scenarios\n// ---------------------------------------------------------------------------\n\ndescribe(\"fromQueue + run\", () => {\n it(\"queue-driven: approve + deny resolve correctly with ApprovalRequested events\", async () => {\n const collected = await Effect.runPromise(\n Effect.gen(function* () {\n const verdicts = yield* Queue.unbounded<{\n readonly call_id: string\n readonly decision: \"approve\" | \"deny\"\n readonly reason?: string\n }>()\n yield* Queue.offer(verdicts, { call_id: \"c2\", decision: \"approve\" })\n yield* Queue.offer(verdicts, {\n call_id: \"c3\",\n decision: \"deny\",\n reason: \"too risky\",\n })\n\n // Stream.unwrap supplies the Scope for fromQueue's router.\n const events = Stream.unwrap(\n Effect.gen(function* () {\n const { approved, decisions, approvalRequests } = yield* fromQueue(\n isSensitive,\n verdicts,\n )(calls)\n return Stream.merge(\n approvalRequests,\n Stream.merge(\n run(allTools, approved),\n decisions.pipe(Stream.flatMap(eventsFromDecision)),\n ),\n )\n }),\n )\n return yield* Stream.runCollect(events)\n }),\n )\n\n expect(collected.filter(isApprovalRequested)).toHaveLength(2)\n\n const by = byCallId(resultsFrom(collected))\n expect(by.get(\"c2\")).toMatchObject({ _tag: \"Ok\", value: { status: \"sent\" } })\n expect(by.get(\"c3\")).toMatchObject({\n _tag: \"Failure\",\n kind: \"denied\",\n reason: \"too risky\",\n })\n })\n\n it(\"router retires per round, so sequential rounds over one queue both resolve\", async () => {\n // A verdict-per-round over one shared queue and scope. The old\n // never-terminating router would still be alive for round 1 and steal\n // round 2's verdict (not in its map), hanging round 2.\n const decisions = await Effect.runPromise(\n Effect.scoped(\n Effect.gen(function* () {\n const verdicts = yield* Queue.unbounded<Verdict>()\n const round = (id: string) => [fc(id, \"delete_database\", { name: \"prod\" })]\n\n const r1 = yield* fromQueue(isSensitive, verdicts)(round(\"c1\"))\n yield* Queue.offer(verdicts, { call_id: \"c1\", decision: \"approve\" })\n const d1 = Array.from(yield* Stream.runCollect(r1.decisions))\n\n const r2 = yield* fromQueue(isSensitive, verdicts)(round(\"c2\"))\n yield* Queue.offer(verdicts, { call_id: \"c2\", decision: \"deny\", reason: \"no\" })\n const d2 = Array.from(yield* Stream.runCollect(r2.decisions))\n\n return { d1, d2 }\n }),\n ),\n )\n expect(decisions.d1[0]).toMatchObject({ _tag: \"Approved\" })\n expect(decisions.d2[0]).toMatchObject({ _tag: \"Rejected\", result: { kind: \"denied\" } })\n })\n\n it(\"timeout resolves an unanswered gated call as cancelled\", async () => {\n const decisions = await Effect.runPromise(\n Effect.scoped(\n Effect.gen(function* () {\n const verdicts = yield* Queue.unbounded<Verdict>()\n const { decisions } = yield* fromQueue(isSensitive, verdicts, { timeout: \"10 millis\" })([\n fc(\"c1\", \"delete_database\", { name: \"prod\" }),\n ])\n return Array.from(yield* Stream.runCollect(decisions))\n }),\n ),\n )\n expect(decisions[0]).toMatchObject({\n _tag: \"Rejected\",\n result: { _tag: \"Failure\", kind: \"cancelled\" },\n })\n })\n})\n\n// ---------------------------------------------------------------------------\n// History reconciliation\n// ---------------------------------------------------------------------------\n\ndescribe(\"findUnansweredCalls / cancelAllPending / isReconciled\", () => {\n const orphan = fc(\"c99\", \"delete_database\", { name: \"prod\" })\n const answered = fc(\"c98\", \"web_search\", { query: \"x\" })\n const answeredOutput = Items.toolCallOutput(\"c98\", JSON.stringify({ count: 0 }))\n\n it(\"findUnansweredCalls returns only orphans\", () => {\n const history = [Items.userText(\"hi\"), answered, orphan, answeredOutput]\n const unanswered = findUnansweredCalls(history)\n expect(unanswered).toHaveLength(1)\n expect(unanswered[0]!.call_id).toBe(\"c99\")\n })\n\n it(\"isReconciled is false when orphans exist, true otherwise\", () => {\n const stale = [Items.userText(\"hi\"), orphan]\n expect(isReconciled(stale)).toBe(false)\n const reconciled = [...stale, ...cancelAllPending(stale).map(toToolCallOutput)]\n expect(isReconciled(reconciled)).toBe(true)\n })\n\n it(\"cancelAllPending synthesizes one Failure(cancelled) per orphan\", () => {\n const history = [Items.userText(\"hi\"), answered, orphan, answeredOutput]\n const closures = cancelAllPending(history, \"user moved on\")\n expect(closures).toHaveLength(1)\n const c = closures[0]!\n expect(isFailure(c)).toBe(true)\n expect(c).toMatchObject({\n _tag: \"Failure\",\n call_id: \"c99\",\n kind: \"cancelled\",\n reason: \"user moved on\",\n })\n })\n\n it(\"follow-up: map closures to ToolCallOutput before appending new user message\", () => {\n const stale = [Items.userText(\"first request\"), orphan]\n const closures = cancelAllPending(stale, \"user redirected\")\n const reconciled = [...stale, ...closures.map(toToolCallOutput), Items.userText(\"never mind\")]\n expect(findUnansweredCalls(reconciled)).toHaveLength(0)\n })\n})\n\n// ---------------------------------------------------------------------------\n// Wire conversion\n// ---------------------------------------------------------------------------\n\ndescribe(\"toToolCallOutput\", () => {\n it(\"round-trips a Value result\", () => {\n const r: ToolResult = {\n _tag: \"Ok\",\n call_id: \"c1\",\n tool: \"web_search\",\n value: { count: 3 },\n }\n const out = toToolCallOutput(r)\n expect(out.call_id).toBe(\"c1\")\n expect(JSON.parse(out.output)).toEqual({ count: 3 })\n })\n\n it(\"renders a Failure with reason as a nested error object\", () => {\n const r: ToolResult = {\n _tag: \"Failure\",\n call_id: \"c2\",\n tool: \"bulk_email\",\n kind: \"denied\",\n reason: \"spam concern\",\n }\n const out = toToolCallOutput(r)\n expect(JSON.parse(out.output)).toEqual({ error: { kind: \"denied\", message: \"spam concern\" } })\n })\n\n it(\"renders a Failure without reason (omits message)\", () => {\n const r: ToolResult = {\n _tag: \"Failure\",\n call_id: \"c3\",\n tool: \"delete_database\",\n kind: \"cancelled\",\n }\n const out = toToolCallOutput(r)\n expect(JSON.parse(out.output)).toEqual({ error: { kind: \"cancelled\" } })\n })\n})\n\n// ---------------------------------------------------------------------------\n// run\n// ---------------------------------------------------------------------------\n\ndescribe(\"run\", () => {\n it(\"runs all calls passed to it\", async () => {\n const collected = await Effect.runPromise(Stream.runCollect(run(allTools, calls)))\n expect(collected.filter(isOutput)).toHaveLength(3)\n expect(collected.filter(isOutput).every((e) => isOk(e.result))).toBe(true)\n })\n})\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { o as HistoryItem, v as ToolCall } from "../Items-7ljaVxH_.mjs";
2
- import { ToolResult } from "./ToolResult.mjs";
1
+ import { o as HistoryItem, v as ToolCall } from "../Items-BnIduEV2.mjs";
2
+ import { t as ToolResult } from "../ToolResult-CL4hK_9z.mjs";
3
3
 
4
4
  //#region src/tool/HistoryCheck.d.ts
5
5
  declare namespace HistoryCheck_d_exports {
@@ -1,2 +1,2 @@
1
- import { a as Tool, c as ToolInputSchema, d as fromEffectSchema, f as fromStandardSchema, g as toDescriptors, h as streaming, i as StreamingTool, m as make, n as AnyStreamingTool, o as ToolDescriptor, p as isStreamingTool, r as AnyTool, s as ToolError, t as AnyPlainTool, u as execute } from "../Tool-BKiieFXk.mjs";
2
- export { AnyPlainTool, AnyStreamingTool, AnyTool, StreamingTool, Tool, ToolDescriptor, ToolError, ToolInputSchema, execute, fromEffectSchema, fromStandardSchema, isStreamingTool, make, streaming, toDescriptors };
1
+ import { A as toDescriptors, C as fromEffectSchema, D as noInput, E as make, M as withRun, O as provider, S as fail, T as interaction, a as Emit, b as descriptorsOf, c as SignalTool, d as ToolE, f as ToolError, g as ToolValidationError, h as ToolR, i as DecodeResult, j as withName, k as signal, l as Tool, m as ToolInputSchema, n as AnyTool, o as InteractionTool, p as ToolFailed, r as DecodableTool, s as ProviderTool, t as AnyLocalTool, u as ToolDescriptor, v as decodeArgs, w as fromStandardSchema, x as execute, y as decodeCallInput } from "../Tool-2lC_XMth.mjs";
2
+ export { AnyLocalTool, AnyTool, DecodableTool, DecodeResult, Emit, InteractionTool, ProviderTool, SignalTool, Tool, ToolDescriptor, ToolE, ToolError, ToolFailed, ToolInputSchema, ToolR, ToolValidationError, decodeArgs, decodeCallInput, descriptorsOf, execute, fail, fromEffectSchema, fromStandardSchema, interaction, make, noInput, provider, signal, toDescriptors, withName, withRun };
@@ -1,16 +1,27 @@
1
1
  import { n as __exportAll } from "../chunk-uyGKjUfl.mjs";
2
2
  import { toolCallOutput } from "../domain/Items.mjs";
3
- import { Effect, Schema } from "effect";
3
+ import { serializeValue } from "./ToolResult.mjs";
4
+ import { Effect, Match, Schema } from "effect";
4
5
  //#region src/tool/Tool.ts
5
6
  var Tool_exports = /* @__PURE__ */ __exportAll({
6
7
  ToolError: () => ToolError,
8
+ ToolFailed: () => ToolFailed,
9
+ ToolValidationError: () => ToolValidationError,
10
+ decodeArgs: () => decodeArgs,
11
+ decodeCallInput: () => decodeCallInput,
12
+ descriptorsOf: () => descriptorsOf,
7
13
  execute: () => execute,
14
+ fail: () => fail,
8
15
  fromEffectSchema: () => fromEffectSchema,
9
16
  fromStandardSchema: () => fromStandardSchema,
10
- isStreamingTool: () => isStreamingTool,
17
+ interaction: () => interaction,
11
18
  make: () => make,
12
- streaming: () => streaming,
13
- toDescriptors: () => toDescriptors
19
+ noInput: () => noInput,
20
+ provider: () => provider,
21
+ signal: () => signal,
22
+ toDescriptors: () => toDescriptors,
23
+ withName: () => withName,
24
+ withRun: () => withRun
14
25
  });
15
26
  var ToolError = class extends Schema.TaggedErrorClass("@betalyra/effect-uai/ToolError")("ToolError", {
16
27
  call_id: Schema.String,
@@ -19,6 +30,48 @@ var ToolError = class extends Schema.TaggedErrorClass("@betalyra/effect-uai/Tool
19
30
  cause: Schema.optional(Schema.Unknown)
20
31
  }) {};
21
32
  /**
33
+ * One Standard Schema validation issue, carried on `ToolValidationError`.
34
+ * Mirrors `StandardSchemaV1.Issue`; `path` is left as `unknown` elements
35
+ * because Standard Schema path segments may be symbols or `{ key }` objects.
36
+ */
37
+ const ToolValidationIssue = Schema.Struct({
38
+ message: Schema.String,
39
+ path: Schema.optional(Schema.Array(Schema.Unknown))
40
+ });
41
+ /**
42
+ * The model's `arguments` for a call failed the tool's `inputSchema`. Distinct
43
+ * from `ToolError` (malformed JSON, runtime crash) so callers can tell a
44
+ * contract violation apart from a parse or execution failure, and so the
45
+ * structured `issues` survive instead of being flattened into a message string.
46
+ */
47
+ var ToolValidationError = class extends Schema.TaggedErrorClass("@betalyra/effect-uai/ToolValidationError")("ToolValidationError", {
48
+ call_id: Schema.String,
49
+ tool: Schema.String,
50
+ issues: Schema.Array(ToolValidationIssue)
51
+ }) {};
52
+ /**
53
+ * The deliberate "speak to the model" failure sentinel. A tool's `run` that
54
+ * fails with a `ToolFailed` (or a bare `string`, its sugar) is absorbed by
55
+ * `Toolkit.run` into a `ToolResult.Failure` the model reads and adapts to,
56
+ * instead of ending the run. Every other error type propagates typed on the
57
+ * executor's stream. `kind` overrides the synthesized `Failure.kind`
58
+ * (default `"tool_failed"`) so a tool can hand the model a distinguishable
59
+ * category (`"not_found"`, `"rate_limited"`) without a new result variant.
60
+ */
61
+ var ToolFailed = class extends Schema.TaggedErrorClass("@betalyra/effect-uai/ToolFailed")("ToolFailed", {
62
+ message: Schema.String,
63
+ kind: Schema.optional(Schema.String)
64
+ }) {};
65
+ /**
66
+ * Fail a tool's `run` with a model-visible message (optionally a `kind`).
67
+ * `yield* Tool.fail("page not found", { kind: "not_found" })` inside a
68
+ * `run`'s `Effect.gen`; the executor absorbs it as `ToolResult.Failure`.
69
+ */
70
+ const fail = (message, options) => Effect.fail(new ToolFailed({
71
+ message,
72
+ ...options?.kind !== void 0 ? { kind: options.kind } : {}
73
+ }));
74
+ /**
22
75
  * Convenience wrapper for Effect Schema users - adds both the
23
76
  * `validate` and `jsonSchema` extensions to a plain Effect Schema so it
24
77
  * can be used as a `Tool.inputSchema`.
@@ -40,57 +93,158 @@ const fromEffectSchema = (schema) => Schema.toStandardJSONSchemaV1(Schema.toStan
40
93
  * helper makes the input type inference explicit at the call site.
41
94
  */
42
95
  const fromStandardSchema = (schema) => schema;
43
- const make = (spec) => spec;
44
- const streaming = (spec) => ({
45
- _kind: "streaming",
96
+ /**
97
+ * Input schema for a tool that takes no arguments. Renders a clean
98
+ * `{ type: "object", properties: {} }`; validation accepts anything and
99
+ * yields `{}`. Use it instead of `fromEffectSchema(Schema.Struct({}))`,
100
+ * which renders a degenerate `anyOf` (object-or-array) that strict
101
+ * providers like Gemini reject.
102
+ */
103
+ const noInput = { "~standard": {
104
+ version: 1,
105
+ vendor: "effect-uai",
106
+ validate: () => ({ value: {} }),
107
+ jsonSchema: {
108
+ input: () => ({
109
+ type: "object",
110
+ properties: {}
111
+ }),
112
+ output: () => ({
113
+ type: "object",
114
+ properties: {}
115
+ })
116
+ }
117
+ } };
118
+ /** A model-visible tool with a local `Effect` executor. */
119
+ const make = (spec) => ({
120
+ _tag: "LocalTool",
121
+ ...spec
122
+ });
123
+ /**
124
+ * A provider-hosted tool (native web search, code execution, RAG grounding).
125
+ * No local executor — `provider`/`config` drive the provider adapter's
126
+ * rendering. The model sees it; `Toolkit.run` reports it as `non_local_tool`.
127
+ */
128
+ const provider = (spec) => ({
129
+ _tag: "ProviderTool",
46
130
  ...spec
47
131
  });
48
- const isStreamingTool = (t) => "_kind" in t && t._kind === "streaming";
49
132
  /**
50
- * Render any-kind tools (mixed plain and streaming) to provider-agnostic
51
- * descriptors. Accepts the union type so a single list can carry both
52
- * plain and streaming tools.
133
+ * A typed control-flow signal to the agent loop (escalate, pause, schedule,
134
+ * hand off). Decode-only: the loop interprets the call rather than executing a
135
+ * handler, so there is no `run`.
53
136
  */
137
+ const signal = (spec) => ({
138
+ _tag: "SignalTool",
139
+ ...spec
140
+ });
141
+ /**
142
+ * A typed request for an external actor to respond before the loop resumes
143
+ * (frontend, Telegram, CLI prompt). Decode-only; the loop stops/pauses and
144
+ * resumes by appending the actor's `function_call_output`.
145
+ */
146
+ const interaction = (spec) => ({
147
+ _tag: "InteractionTool",
148
+ ...spec
149
+ });
150
+ /**
151
+ * Return a copy of a tool under a new `name`. Useful for resolving a name
152
+ * clash before combining tools into one `Toolkit`. Literal-preserving, so the
153
+ * renamed tool keys the toolkit record under the new name statically.
154
+ */
155
+ const withName = (tool, name) => ({
156
+ ...tool,
157
+ name
158
+ });
159
+ /**
160
+ * Replace a local tool's `run`, keeping its model-facing definition (name,
161
+ * description, schema, `_tag`) identical. The new `run`'s `input` is typed from
162
+ * the original tool, so override/mock need no annotation:
163
+ *
164
+ * const safe = { ...toolkit, send_email: Tool.withRun(toolkit.send_email,
165
+ * ({ to }) => Effect.succeed({ status: "dry-run", to })) }
166
+ *
167
+ * Descriptors stay identical to production, so a mocked toolkit can't drift
168
+ * from the contract the model sees.
169
+ */
170
+ const withRun = (tool, run) => ({
171
+ ...tool,
172
+ run
173
+ });
174
+ /** Render tools to provider-agnostic descriptors. Every kind is model-visible. */
54
175
  const toDescriptors = (tools) => tools.map((tool) => {
55
176
  const inputSchema = tool.inputSchema["~standard"].jsonSchema.input({ target: "draft-2020-12" });
56
- return tool.strict !== void 0 ? {
177
+ const strict = "strict" in tool ? tool.strict : void 0;
178
+ return strict !== void 0 ? {
57
179
  name: tool.name,
58
180
  description: tool.description,
59
181
  inputSchema,
60
- strict: tool.strict
182
+ strict
61
183
  } : {
62
184
  name: tool.name,
63
185
  description: tool.description,
64
186
  inputSchema
65
187
  };
66
188
  });
67
- const toToolError = (call, toolName, message) => (cause) => new ToolError({
189
+ /**
190
+ * Render a `Toolkit` (a name-indexed record of tools) to descriptors, treating
191
+ * an absent toolkit as no tools. The normalization point the `LanguageModel`
192
+ * boundary uses so a request can carry the toolkit itself instead of a
193
+ * pre-rendered descriptor array. Lives here (not on `Toolkit`) to keep providers
194
+ * free of a dependency on the toolkit module — it only needs the tool values.
195
+ */
196
+ const descriptorsOf = (toolkit) => toolkit === void 0 ? [] : toDescriptors(Object.values(toolkit));
197
+ const VALIDATION_THREW = [{ message: "input schema validation threw" }];
198
+ /**
199
+ * Parse and validate a call's `arguments`. Empty arguments normalize to
200
+ * `{}` (some models emit `""` for zero-arg tools); a throwing validator is
201
+ * captured, not propagated. Never fails.
202
+ */
203
+ const decodeCallInput = (tool, call) => {
204
+ const raw = call.arguments.trim() === "" ? "{}" : call.arguments;
205
+ return Effect.try({
206
+ try: () => JSON.parse(raw),
207
+ catch: () => ({ _tag: "parseError" })
208
+ }).pipe(Effect.flatMap((parsed) => Effect.tryPromise({
209
+ try: () => Promise.resolve(tool.inputSchema["~standard"].validate(parsed)),
210
+ catch: () => ({
211
+ _tag: "invalid",
212
+ issues: VALIDATION_THREW
213
+ })
214
+ }).pipe(Effect.map((result) => result.issues !== void 0 ? {
215
+ _tag: "invalid",
216
+ issues: result.issues
217
+ } : {
218
+ _tag: "ok",
219
+ input: result.value
220
+ }))), Effect.catch((captured) => Effect.succeed(captured)));
221
+ };
222
+ /**
223
+ * Decode and validate a function_call's JSON `arguments` against the tool's
224
+ * own `inputSchema`, yielding the typed input. The decode-only half of
225
+ * `execute` - reach for it when you intercept a call to translate it into a
226
+ * control-flow decision rather than running the tool's `run`.
227
+ *
228
+ * Malformed JSON fails with `ToolError`; a parsed body that violates
229
+ * `inputSchema` fails with `ToolValidationError` carrying the issues.
230
+ */
231
+ const decodeArgs = (tool, call) => decodeCallInput(tool, call).pipe(Effect.flatMap(Match.type().pipe(Match.tag("ok", ({ input }) => Effect.succeed(input)), Match.tag("parseError", () => Effect.fail(new ToolError({
68
232
  call_id: call.call_id,
69
- tool: toolName,
70
- message,
71
- cause
72
- });
233
+ tool: tool.name,
234
+ message: "Failed to parse JSON arguments"
235
+ }))), Match.tag("invalid", ({ issues }) => Effect.fail(new ToolValidationError({
236
+ call_id: call.call_id,
237
+ tool: tool.name,
238
+ issues
239
+ }))), Match.exhaustive)));
73
240
  /**
74
241
  * Decode and validate the JSON arguments of a function_call against the
75
242
  * tool's input schema, run the tool, and serialize the output into a
76
- * function_call_output item.
77
- */
78
- const execute = (tool, call) => Effect.gen(function* () {
79
- const parsed = yield* Effect.try({
80
- try: () => JSON.parse(call.arguments),
81
- catch: toToolError(call, tool.name, "Failed to parse JSON arguments")
82
- });
83
- const result = yield* Effect.promise(() => Promise.resolve(tool.inputSchema["~standard"].validate(parsed)));
84
- if (result.issues !== void 0) return yield* new ToolError({
85
- call_id: call.call_id,
86
- tool: tool.name,
87
- message: "Tool input failed schema validation",
88
- cause: result.issues
89
- });
90
- const output = yield* tool.run(result.value).pipe(Effect.mapError(toToolError(call, tool.name, "Tool execution failed")));
91
- return toolCallOutput(call.call_id, JSON.stringify(output));
92
- });
243
+ * function_call_output item. Single-shot: intermediate events emitted by
244
+ * `run` are discarded (use `Toolkit.run` to stream them).
245
+ */
246
+ const execute = (tool, call) => decodeArgs(tool, call).pipe(Effect.flatMap((input) => tool.run(input, () => Effect.void)), Effect.map((output) => toolCallOutput(call.call_id, serializeValue(output))));
93
247
  //#endregion
94
- export { ToolError, execute, fromEffectSchema, fromStandardSchema, isStreamingTool, make, streaming, Tool_exports as t, toDescriptors };
248
+ export { ToolError, ToolFailed, ToolValidationError, decodeArgs, decodeCallInput, descriptorsOf, execute, fail, fromEffectSchema, fromStandardSchema, interaction, make, noInput, provider, signal, Tool_exports as t, toDescriptors, withName, withRun };
95
249
 
96
250
  //# sourceMappingURL=Tool.mjs.map