@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
@@ -0,0 +1,57 @@
1
+ import { n as __exportAll } from "../chunk-uyGKjUfl.mjs";
2
+ import { isMetricEvent } from "./Metrics.mjs";
3
+ import { Effect, Layer, Match, Metric, Stream } from "effect";
4
+ import { OtlpMetrics, OtlpSerialization } from "effect/unstable/observability";
5
+ //#region src/observability/Telemetry.ts
6
+ var Telemetry_exports = /* @__PURE__ */ __exportAll({
7
+ layerOtlp: () => layerOtlp,
8
+ record: () => record
9
+ });
10
+ /**
11
+ * Histogram bucket boundaries used for every histogram measurement (the only
12
+ * built-in one is throughput). Custom histogram metrics share these; a metric
13
+ * that needs bespoke boundaries can update its own instrument directly instead
14
+ * of routing through `record`.
15
+ */
16
+ const histogramBoundaries = Metric.exponentialBoundaries({
17
+ start: 1,
18
+ factor: 2,
19
+ count: 16
20
+ });
21
+ /**
22
+ * Build the effect `Metric` instrument a measurement maps to. Dispatches on
23
+ * `kind`, never on the event `_tag`, so a new built-in or custom metric needs
24
+ * no change here. Instruments dedupe by name in the global registry, so a fresh
25
+ * instance per call accumulates onto the same series (no cache needed).
26
+ */
27
+ const buildInstrument = (m) => Match.value(m.kind).pipe(Match.when("counter", () => Metric.counter(m.name, { incremental: true })), Match.when("histogram", () => Metric.histogram(m.name, { boundaries: histogramBoundaries })), Match.when("timer", () => Metric.timer(m.name)), Match.when("gauge", () => Metric.gauge(m.name)), Match.exhaustive);
28
+ const mergeAttributes = (base, extra) => base === void 0 ? extra : extra === void 0 ? base : {
29
+ ...base,
30
+ ...extra
31
+ };
32
+ const recordEvent = (ev, baseAttributes) => Effect.forEach(ev.measurements, (m) => {
33
+ const attributes = mergeAttributes(baseAttributes, m.attributes);
34
+ const base = buildInstrument(m);
35
+ const instrument = attributes === void 0 ? base : Metric.withAttributes(base, attributes);
36
+ return Metric.update(instrument, m.value);
37
+ }, { discard: true });
38
+ /**
39
+ * Record every measurement on every metric event into effect `Metric`
40
+ * instruments, then pass events through unchanged. Matches events structurally
41
+ * (`isMetricEvent`), so it records built-in and custom metrics alike, and
42
+ * dispatches on `measurement.kind`, never on the event tag. Sits mid-pipeline
43
+ * before the transport. Provide an OTLP (or Prometheus) layer to export the
44
+ * instruments it populates.
45
+ */
46
+ const record = (options) => (self) => self.pipe(Stream.tap((ev) => isMetricEvent(ev) ? recordEvent(ev, options?.attributes) : Effect.void));
47
+ /**
48
+ * Convenience layer that exports recorded metrics over OTLP. Provides the JSON
49
+ * serializer and leaves `HttpClient` as a requirement, so the platform's client
50
+ * (FetchHttpClient, NodeHttpClient, ...) is provided at the edge. This keeps
51
+ * the library platform-independent.
52
+ */
53
+ const layerOtlp = (options) => OtlpMetrics.layer(options).pipe(Layer.provide(OtlpSerialization.layerJson));
54
+ //#endregion
55
+ export { layerOtlp, record, Telemetry_exports as t };
56
+
57
+ //# sourceMappingURL=Telemetry.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Telemetry.mjs","names":[],"sources":["../../src/observability/Telemetry.ts"],"sourcesContent":["import { Duration, Effect, Layer, Match, Metric, Stream } from \"effect\"\nimport type { HttpClient } from \"effect/unstable/http\"\nimport { OtlpMetrics, OtlpSerialization } from \"effect/unstable/observability\"\nimport { isMetricEvent, type Measurement, type MetricEvent } from \"./Metrics.js\"\n\n// ---------------------------------------------------------------------------\n// The agnostic recorder\n// ---------------------------------------------------------------------------\n\n/**\n * Histogram bucket boundaries used for every histogram measurement (the only\n * built-in one is throughput). Custom histogram metrics share these; a metric\n * that needs bespoke boundaries can update its own instrument directly instead\n * of routing through `record`.\n */\nconst histogramBoundaries = Metric.exponentialBoundaries({ start: 1, factor: 2, count: 16 })\n\n/**\n * Build the effect `Metric` instrument a measurement maps to. Dispatches on\n * `kind`, never on the event `_tag`, so a new built-in or custom metric needs\n * no change here. Instruments dedupe by name in the global registry, so a fresh\n * instance per call accumulates onto the same series (no cache needed).\n */\nconst buildInstrument = (m: Measurement): Metric.Metric<any, any> =>\n Match.value(m.kind).pipe(\n Match.when(\"counter\", () => Metric.counter(m.name, { incremental: true })),\n Match.when(\"histogram\", () => Metric.histogram(m.name, { boundaries: histogramBoundaries })),\n Match.when(\"timer\", () => Metric.timer(m.name)),\n Match.when(\"gauge\", () => Metric.gauge(m.name)),\n Match.exhaustive,\n )\n\nconst mergeAttributes = (\n base: Readonly<Record<string, string>> | undefined,\n extra: Readonly<Record<string, string>> | undefined,\n): Readonly<Record<string, string>> | undefined =>\n base === undefined ? extra : extra === undefined ? base : { ...base, ...extra }\n\nconst recordEvent = (\n ev: MetricEvent,\n baseAttributes: Readonly<Record<string, string>> | undefined,\n): Effect.Effect<void> =>\n Effect.forEach(\n ev.measurements,\n (m) => {\n const attributes = mergeAttributes(baseAttributes, m.attributes)\n const base = buildInstrument(m)\n const instrument = attributes === undefined ? base : Metric.withAttributes(base, attributes)\n return Metric.update(instrument, m.value)\n },\n { discard: true },\n )\n\nexport type RecordOptions = {\n /** Attributes applied to every recorded metric, e.g. `{ model, provider }`. */\n readonly attributes?: Readonly<Record<string, string>>\n}\n\n/**\n * Record every measurement on every metric event into effect `Metric`\n * instruments, then pass events through unchanged. Matches events structurally\n * (`isMetricEvent`), so it records built-in and custom metrics alike, and\n * dispatches on `measurement.kind`, never on the event tag. Sits mid-pipeline\n * before the transport. Provide an OTLP (or Prometheus) layer to export the\n * instruments it populates.\n */\nexport const record =\n (options?: RecordOptions) =>\n <A, E, R>(self: Stream.Stream<A, E, R>): Stream.Stream<A, E, R> =>\n self.pipe(\n Stream.tap((ev) => (isMetricEvent(ev) ? recordEvent(ev, options?.attributes) : Effect.void)),\n )\n\n// ---------------------------------------------------------------------------\n// OTLP wiring\n// ---------------------------------------------------------------------------\n\nexport type LayerOtlpOptions = {\n readonly url: string\n readonly resource?: {\n readonly serviceName?: string\n readonly serviceVersion?: string\n }\n readonly exportInterval?: Duration.Input\n}\n\n/**\n * Convenience layer that exports recorded metrics over OTLP. Provides the JSON\n * serializer and leaves `HttpClient` as a requirement, so the platform's client\n * (FetchHttpClient, NodeHttpClient, ...) is provided at the edge. This keeps\n * the library platform-independent.\n */\nexport const layerOtlp = (\n options: LayerOtlpOptions,\n): Layer.Layer<never, never, HttpClient.HttpClient> =>\n OtlpMetrics.layer(options).pipe(Layer.provide(OtlpSerialization.layerJson))\n"],"mappings":";;;;;;;;;;;;;;;AAeA,MAAM,sBAAsB,OAAO,sBAAsB;CAAE,OAAO;CAAG,QAAQ;CAAG,OAAO;CAAI,CAAC;;;;;;;AAQ5F,MAAM,mBAAmB,MACvB,MAAM,MAAM,EAAE,KAAK,CAAC,KAClB,MAAM,KAAK,iBAAiB,OAAO,QAAQ,EAAE,MAAM,EAAE,aAAa,MAAM,CAAC,CAAC,EAC1E,MAAM,KAAK,mBAAmB,OAAO,UAAU,EAAE,MAAM,EAAE,YAAY,qBAAqB,CAAC,CAAC,EAC5F,MAAM,KAAK,eAAe,OAAO,MAAM,EAAE,KAAK,CAAC,EAC/C,MAAM,KAAK,eAAe,OAAO,MAAM,EAAE,KAAK,CAAC,EAC/C,MAAM,WACP;AAEH,MAAM,mBACJ,MACA,UAEA,SAAS,KAAA,IAAY,QAAQ,UAAU,KAAA,IAAY,OAAO;CAAE,GAAG;CAAM,GAAG;CAAO;AAEjF,MAAM,eACJ,IACA,mBAEA,OAAO,QACL,GAAG,eACF,MAAM;CACL,MAAM,aAAa,gBAAgB,gBAAgB,EAAE,WAAW;CAChE,MAAM,OAAO,gBAAgB,EAAE;CAC/B,MAAM,aAAa,eAAe,KAAA,IAAY,OAAO,OAAO,eAAe,MAAM,WAAW;AAC5F,QAAO,OAAO,OAAO,YAAY,EAAE,MAAM;GAE3C,EAAE,SAAS,MAAM,CAClB;;;;;;;;;AAeH,MAAa,UACV,aACS,SACR,KAAK,KACH,OAAO,KAAK,OAAQ,cAAc,GAAG,GAAG,YAAY,IAAI,SAAS,WAAW,GAAG,OAAO,KAAM,CAC7F;;;;;;;AAqBL,MAAa,aACX,YAEA,YAAY,MAAM,QAAQ,CAAC,KAAK,MAAM,QAAQ,kBAAkB,UAAU,CAAC"}
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,92 @@
1
+ import { makeEvent } from "./Metrics.mjs";
2
+ import { layerOtlp, record } from "./Telemetry.mjs";
3
+ import { a as describe, n as globalExpect } from "../dist-B04dlFHg.mjs";
4
+ import { t as it } from "../dist-DWgRbDN3.mjs";
5
+ import { Array, Duration, Effect, Layer, Metric, Option, Ref, Stream } from "effect";
6
+ import { HttpClient, HttpClientResponse } from "effect/unstable/http";
7
+ //#region src/observability/Telemetry.test.ts
8
+ const event = (measurements) => makeEvent({
9
+ _tag: "Test",
10
+ turnIndex: 0,
11
+ measurements
12
+ });
13
+ const isolated = (self) => Effect.provideService(self, Metric.MetricRegistry, /* @__PURE__ */ new Map());
14
+ const findById = (snap, id) => Option.getOrThrow(Array.findFirst(snap, (s) => s.id === id));
15
+ describe("record", () => {
16
+ it.effect("accumulates a counter measurement across events", () => isolated(Effect.gen(function* () {
17
+ const ev = event([{
18
+ name: "rec_counter",
19
+ kind: "counter",
20
+ value: 5
21
+ }]);
22
+ yield* Stream.make(ev, ev).pipe(record(), Stream.runDrain);
23
+ const c = findById(yield* Metric.snapshot, "rec_counter");
24
+ globalExpect(c.type).toBe("Counter");
25
+ globalExpect(c.state.count).toBe(10);
26
+ })));
27
+ it.effect("records a timer measurement as a histogram observation", () => isolated(Effect.gen(function* () {
28
+ const ev = event([{
29
+ name: "rec_timer",
30
+ kind: "timer",
31
+ value: Duration.millis(100)
32
+ }]);
33
+ yield* Stream.make(ev).pipe(record(), Stream.runDrain);
34
+ const h = findById(yield* Metric.snapshot, "rec_timer");
35
+ globalExpect(h.type).toBe("Histogram");
36
+ globalExpect(h.state.count).toBe(1);
37
+ })));
38
+ it.effect("applies base attributes to every recorded metric", () => isolated(Effect.gen(function* () {
39
+ const ev = event([{
40
+ name: "rec_attr",
41
+ kind: "counter",
42
+ value: 1
43
+ }]);
44
+ yield* Stream.make(ev).pipe(record({ attributes: { model: "test-model" } }), Stream.runDrain);
45
+ globalExpect(findById(yield* Metric.snapshot, "rec_attr").attributes).toMatchObject({ model: "test-model" });
46
+ })));
47
+ it.effect("records a custom metric with a novel name, unchanged recorder", () => isolated(Effect.gen(function* () {
48
+ const custom = makeEvent({
49
+ _tag: "ToolLatency",
50
+ turnIndex: 0,
51
+ measurements: [{
52
+ name: "myapp_tool_latency_total",
53
+ kind: "counter",
54
+ value: 3
55
+ }]
56
+ });
57
+ yield* Stream.make(custom).pipe(record(), Stream.runDrain);
58
+ globalExpect(findById(yield* Metric.snapshot, "myapp_tool_latency_total").state.count).toBe(3);
59
+ })));
60
+ it.effect("passes non-metric elements through untouched", () => isolated(Effect.gen(function* () {
61
+ globalExpect(yield* Stream.make("a", "b", "c").pipe(record(), Stream.runCollect)).toEqual([
62
+ "a",
63
+ "b",
64
+ "c"
65
+ ]);
66
+ })));
67
+ });
68
+ const bodyToString = (body) => body._tag === "Uint8Array" ? new TextDecoder().decode(body.body) : "";
69
+ describe("layerOtlp", () => {
70
+ it.effect("exports recorded metrics over OTLP via a mock HttpClient", () => isolated(Effect.gen(function* () {
71
+ const bodies = yield* Ref.make([]);
72
+ const mockClient = HttpClient.make((request) => Ref.update(bodies, (b) => [...b, bodyToString(request.body)]).pipe(Effect.as(HttpClientResponse.fromWeb(request, new Response("{}", { status: 200 })))));
73
+ const clientLayer = Layer.succeed(HttpClient.HttpClient, mockClient);
74
+ const ev = event([{
75
+ name: "otlp_export_counter",
76
+ kind: "counter",
77
+ value: 3
78
+ }]);
79
+ yield* Stream.make(ev).pipe(record(), Stream.runDrain, Effect.provide(layerOtlp({
80
+ url: "http://localhost:4318/v1/metrics",
81
+ resource: { serviceName: "test-service" },
82
+ exportInterval: "10 minutes"
83
+ }).pipe(Layer.provide(clientLayer))));
84
+ const captured = yield* Ref.get(bodies);
85
+ globalExpect(captured.length).toBeGreaterThanOrEqual(1);
86
+ globalExpect(captured.join("\n")).toContain("otlp_export_counter");
87
+ })));
88
+ });
89
+ //#endregion
90
+ export {};
91
+
92
+ //# sourceMappingURL=Telemetry.test.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Telemetry.test.mjs","names":["Arr"],"sources":["../../src/observability/Telemetry.test.ts"],"sourcesContent":["import { describe, it } from \"@effect/vitest\"\nimport { Array as Arr, Duration, Effect, Layer, Metric, Option, Ref, Stream } from \"effect\"\nimport { type HttpBody, HttpClient, HttpClientResponse } from \"effect/unstable/http\"\nimport { expect } from \"vitest\"\nimport { type Measurement, makeEvent } from \"./Metrics.js\"\nimport { layerOtlp, record } from \"./Telemetry.js\"\n\nconst event = (measurements: ReadonlyArray<Measurement>) =>\n makeEvent({ _tag: \"Test\", turnIndex: 0, measurements })\n\n// Each test gets a fresh registry so instruments do not leak across tests.\nconst isolated = <A, E, R>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>\n Effect.provideService(self, Metric.MetricRegistry, new Map())\n\nconst findById = (snap: ReadonlyArray<Metric.Metric.Snapshot>, id: string) =>\n Option.getOrThrow(Arr.findFirst(snap, (s) => s.id === id))\n\ndescribe(\"record\", () => {\n it.effect(\"accumulates a counter measurement across events\", () =>\n isolated(\n Effect.gen(function* () {\n const ev = event([{ name: \"rec_counter\", kind: \"counter\", value: 5 }])\n yield* Stream.make(ev, ev).pipe(record(), Stream.runDrain)\n const snap = yield* Metric.snapshot\n const c = findById(snap, \"rec_counter\")\n expect(c.type).toBe(\"Counter\")\n expect((c.state as { readonly count: number }).count).toBe(10)\n }),\n ),\n )\n\n it.effect(\"records a timer measurement as a histogram observation\", () =>\n isolated(\n Effect.gen(function* () {\n const ev = event([{ name: \"rec_timer\", kind: \"timer\", value: Duration.millis(100) }])\n yield* Stream.make(ev).pipe(record(), Stream.runDrain)\n const snap = yield* Metric.snapshot\n const h = findById(snap, \"rec_timer\")\n expect(h.type).toBe(\"Histogram\")\n expect((h.state as { readonly count: number }).count).toBe(1)\n }),\n ),\n )\n\n it.effect(\"applies base attributes to every recorded metric\", () =>\n isolated(\n Effect.gen(function* () {\n const ev = event([{ name: \"rec_attr\", kind: \"counter\", value: 1 }])\n yield* Stream.make(ev).pipe(\n record({ attributes: { model: \"test-model\" } }),\n Stream.runDrain,\n )\n const snap = yield* Metric.snapshot\n const c = findById(snap, \"rec_attr\")\n expect(c.attributes).toMatchObject({ model: \"test-model\" })\n }),\n ),\n )\n\n it.effect(\"records a custom metric with a novel name, unchanged recorder\", () =>\n isolated(\n Effect.gen(function* () {\n // A user-defined event the recorder has never seen: still exported.\n const custom = makeEvent({\n _tag: \"ToolLatency\",\n turnIndex: 0,\n measurements: [{ name: \"myapp_tool_latency_total\", kind: \"counter\", value: 3 }],\n })\n yield* Stream.make(custom).pipe(record(), Stream.runDrain)\n const snap = yield* Metric.snapshot\n const c = findById(snap, \"myapp_tool_latency_total\")\n expect((c.state as { readonly count: number }).count).toBe(3)\n }),\n ),\n )\n\n it.effect(\"passes non-metric elements through untouched\", () =>\n isolated(\n Effect.gen(function* () {\n const out = yield* Stream.make(\"a\", \"b\", \"c\").pipe(record(), Stream.runCollect)\n expect(out).toEqual([\"a\", \"b\", \"c\"])\n }),\n ),\n )\n})\n\nconst bodyToString = (body: HttpBody.HttpBody): string =>\n body._tag === \"Uint8Array\" ? new TextDecoder().decode(body.body) : \"\"\n\ndescribe(\"layerOtlp\", () => {\n it.effect(\"exports recorded metrics over OTLP via a mock HttpClient\", () =>\n isolated(\n Effect.gen(function* () {\n const bodies = yield* Ref.make<ReadonlyArray<string>>([])\n const mockClient = HttpClient.make((request) =>\n Ref.update(bodies, (b) => [...b, bodyToString(request.body)]).pipe(\n Effect.as(HttpClientResponse.fromWeb(request, new Response(\"{}\", { status: 200 }))),\n ),\n )\n const clientLayer = Layer.succeed(HttpClient.HttpClient, mockClient)\n const ev = event([{ name: \"otlp_export_counter\", kind: \"counter\", value: 3 }])\n\n // Provide the OTLP layer scoped to the recording program. The exporter\n // flushes on scope close (interval is long, so we rely on the flush).\n yield* Stream.make(ev).pipe(\n record(),\n Stream.runDrain,\n Effect.provide(\n layerOtlp({\n url: \"http://localhost:4318/v1/metrics\",\n resource: { serviceName: \"test-service\" },\n exportInterval: \"10 minutes\",\n }).pipe(Layer.provide(clientLayer)),\n ),\n )\n\n const captured = yield* Ref.get(bodies)\n expect(captured.length).toBeGreaterThanOrEqual(1)\n expect(captured.join(\"\\n\")).toContain(\"otlp_export_counter\")\n }),\n ),\n )\n})\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
- import { d as RateLimited, m as Unavailable, p as Timeout, t as AiError } from "../AiError-CAX_48RU.mjs";
1
+ import { d as RateLimited, m as Unavailable, p as Timeout, t as AiError } from "../AiError-Bf1pq4BB.mjs";
2
2
  import { Effect, Schedule, Stream } from "effect";
3
- import * as _$effect_Types0 from "effect/Types";
4
3
  import * as _$effect_Cause0 from "effect/Cause";
4
+ import * as _$effect_Types0 from "effect/Types";
5
5
 
6
6
  //#region src/retry/Retry.d.ts
7
7
  declare namespace Retry_d_exports {
@@ -1,6 +1,6 @@
1
1
  import { ContentFiltered, RateLimited, Timeout, Unavailable } from "../domain/AiError.mjs";
2
2
  import { effect, stream } from "./Retry.mjs";
3
- import { i as it, n as globalExpect, r as describe } from "../dist-DV5ISja1.mjs";
3
+ import { a as describe, n as globalExpect, o as it } from "../dist-B04dlFHg.mjs";
4
4
  import { Cause, Effect, Exit, Option, Ref, Schedule, Stream } from "effect";
5
5
  //#region src/retry/Retry.test.ts
6
6
  const planned = (attempts, plan) => Stream.unwrap(Ref.getAndUpdate(attempts, (n) => n + 1).pipe(Effect.map((n) => plan[Math.min(n, plan.length - 1)])));
@@ -1,4 +1,4 @@
1
- import { a as SandboxError } from "../SandboxError-BvKypkJm.mjs";
1
+ import { a as SandboxError } from "../SandboxError-CIrVDOMV.mjs";
2
2
  import { Brand, Context, Data, Duration, Effect, Redacted, Scope, Stream } from "effect";
3
3
  import * as _$effect_Unify0 from "effect/Unify";
4
4
 
@@ -1,2 +1,2 @@
1
- import { a as SandboxError, c as SandboxInvalidRequest, d as SandboxQuotaExceeded, f as SandboxTimeout, i as SandboxCreateFailed, l as SandboxNetworkPolicyDenied, n as SandboxAuthFailed, p as SandboxUnsupported, r as SandboxAuthSubtype, s as SandboxExecFailed, t as SandboxAlreadyExists, u as SandboxNotFound } from "../SandboxError-BvKypkJm.mjs";
1
+ import { a as SandboxError, c as SandboxInvalidRequest, d as SandboxQuotaExceeded, f as SandboxTimeout, i as SandboxCreateFailed, l as SandboxNetworkPolicyDenied, n as SandboxAuthFailed, p as SandboxUnsupported, r as SandboxAuthSubtype, s as SandboxExecFailed, t as SandboxAlreadyExists, u as SandboxNotFound } from "../SandboxError-CIrVDOMV.mjs";
2
2
  export { SandboxAlreadyExists, SandboxAuthFailed, SandboxAuthSubtype, SandboxCreateFailed, SandboxError, SandboxExecFailed, SandboxInvalidRequest, SandboxNetworkPolicyDenied, SandboxNotFound, SandboxQuotaExceeded, SandboxTimeout, SandboxUnsupported };
@@ -1,5 +1,5 @@
1
- import { t as AiError } from "../AiError-CAX_48RU.mjs";
2
- import { n as AudioChunk, r as AudioFormat, t as AudioBlob } from "../Audio-DPlTsNdz.mjs";
1
+ import { t as AiError } from "../AiError-Bf1pq4BB.mjs";
2
+ import { n as AudioChunk, r as AudioFormat, t as AudioBlob } from "../Audio-CR7CSljJ.mjs";
3
3
  import { Context, Effect, Stream } from "effect";
4
4
 
5
5
  //#region src/speech-synthesizer/SpeechSynthesizer.d.ts
@@ -1,5 +1,5 @@
1
1
  import { streamSynthesis, streamSynthesisFrom, streamSynthesizeDialogue, synthesize, synthesizeDialogue } from "./SpeechSynthesizer.mjs";
2
- import { i as it, n as globalExpect, r as describe, t as import_dist } from "../dist-DV5ISja1.mjs";
2
+ import { a as describe, n as globalExpect, o as it, t as import_dist } from "../dist-B04dlFHg.mjs";
3
3
  import { layer, layerWithoutIncremental, layerWithoutMultiSpeaker } from "../testing/MockSpeechSynthesizer.mjs";
4
4
  import { Duration, Effect, Stream } from "effect";
5
5
  //#region src/speech-synthesizer/SpeechSynthesizer.test.ts
@@ -1,6 +1,6 @@
1
1
  import { Effect, Schema, Stream } from "effect";
2
- import * as _$effect_Types0 from "effect/Types";
3
2
  import * as _$effect_Cause0 from "effect/Cause";
3
+ import * as _$effect_Types0 from "effect/Types";
4
4
 
5
5
  //#region src/streaming/JSONL.d.ts
6
6
  declare namespace JSONL_d_exports {
@@ -1,5 +1,5 @@
1
1
  import { fromBytes, parse, toBytes } from "./JSONL.mjs";
2
- import { i as it, n as globalExpect, r as describe } from "../dist-DV5ISja1.mjs";
2
+ import { a as describe, n as globalExpect, o as it } from "../dist-B04dlFHg.mjs";
3
3
  import { Effect, Result, Schema, Stream } from "effect";
4
4
  //#region src/streaming/JSONL.test.ts
5
5
  const enc = new TextEncoder();
@@ -1,5 +1,5 @@
1
1
  import { fromBytes, toBytes } from "./SSE.mjs";
2
- import { i as it, n as globalExpect, r as describe } from "../dist-DV5ISja1.mjs";
2
+ import { a as describe, n as globalExpect, o as it } from "../dist-B04dlFHg.mjs";
3
3
  import { Effect, Stream } from "effect";
4
4
  //#region src/streaming/SSE.test.ts
5
5
  const enc = new TextEncoder();
@@ -1,2 +1,2 @@
1
- import { c as decodeJsonLines, d as parseJson, i as StructuredFormat, l as decodeJsonLinesRecoverable, n as JsonParseError, o as StructuredSchema, r as StructuredDecodeError, s as decode, t as DecodeIssue, u as fromEffectSchema } from "../StructuredFormat-BbN4dosH.mjs";
1
+ import { c as decodeJsonLines, d as parseJson, i as StructuredFormat, l as decodeJsonLinesRecoverable, n as JsonParseError, o as StructuredSchema, r as StructuredDecodeError, s as decode, t as DecodeIssue, u as fromEffectSchema } from "../StructuredFormat-DHaUJBdd.mjs";
2
2
  export { DecodeIssue, JsonParseError, StructuredDecodeError, StructuredFormat, StructuredSchema, decode, decodeJsonLines, decodeJsonLinesRecoverable, fromEffectSchema, parseJson };
@@ -1 +1 @@
1
- {"version":3,"file":"StructuredFormat.mjs","names":[],"sources":["../../src/structured-format/StructuredFormat.ts"],"sourcesContent":["import type { StandardJSONSchemaV1, StandardSchemaV1 } from \"@standard-schema/spec\"\nimport { Data, Effect, Match, Result, Schema, Stream, pipe } from \"effect\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Cross-validator schema constraint for structured outputs. Any schema\n * implementing both Standard Schema (runtime validation) and Standard\n * JSON Schema (wire encoding) works directly: Zod 4+, Valibot, ArkType,\n * and Effect Schema after `fromEffectSchema`.\n */\nexport type StructuredSchema<Output = unknown> = StandardSchemaV1<unknown, Output> &\n StandardJSONSchemaV1<unknown, Output>\n\n/**\n * A schema-bound output the user wants the model to produce. Pairs the\n * cross-validator schema with metadata providers need (name, description,\n * strict-mode flag).\n */\nexport type StructuredFormat<A> = {\n readonly name: string\n readonly description?: string\n readonly schema: StructuredSchema<A>\n /**\n * Provider strict-mode flag. OpenAI, Anthropic, and Mistral honour it\n * (constrained decoding); other providers ignore.\n */\n readonly strict?: boolean\n}\n\n/** A single path-scoped validation problem. Library-agnostic shape. */\nexport type DecodeIssue = {\n readonly path: ReadonlyArray<string | number>\n readonly message: string\n}\n\n// ---------------------------------------------------------------------------\n// Errors\n// ---------------------------------------------------------------------------\n\n/**\n * Schema validation failed. `raw` is the original text (or stringified\n * value) that failed; `issues` is a flat list of per-field problems.\n */\nexport class StructuredDecodeError extends Data.TaggedError(\"StructuredDecodeError\")<{\n readonly raw: string\n readonly issues: ReadonlyArray<DecodeIssue>\n}> {}\n\n/**\n * `JSON.parse` threw on a string that was supposed to be JSON. Distinct\n * from `StructuredDecodeError`: the bytes weren't even JSON.\n */\nexport class JsonParseError extends Data.TaggedError(\"StructuredJsonParseError\")<{\n readonly raw: string\n readonly cause: unknown\n}> {}\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\n/**\n * Wrap an Effect `Schema` as a `StructuredFormat`. Effect Schema doesn't\n * natively implement Standard Schema; this helper installs the\n * `~standard` and JSON Schema interfaces.\n */\nexport const fromEffectSchema = <S extends Schema.Codec<any, any, never, any>>(\n schema: S,\n options?: {\n readonly name?: string\n readonly description?: string\n readonly strict?: boolean\n },\n): StructuredFormat<S[\"Type\"]> => ({\n name: options?.name ?? \"output\",\n schema: Schema.toStandardJSONSchemaV1(Schema.toStandardSchemaV1(schema)),\n ...(options?.description !== undefined && {\n description: options.description,\n }),\n ...(options?.strict !== undefined && { strict: options.strict }),\n})\n\n// ---------------------------------------------------------------------------\n// Standard Schema → DecodeIssue\n// ---------------------------------------------------------------------------\n\nconst propertyKeyToScalar = Match.type<PropertyKey>().pipe(\n Match.when(Match.string, (s) => s),\n Match.when(Match.number, (n) => n),\n Match.when(Match.symbol, (s) => s.toString()),\n Match.exhaustive,\n)\n\nconst segmentToKey = Match.type<PropertyKey | StandardSchemaV1.PathSegment>().pipe(\n Match.when(Match.string, (s) => s),\n Match.when(Match.number, (n) => n),\n Match.when(Match.symbol, (s) => s.toString()),\n Match.orElse((segment) => propertyKeyToScalar(segment.key)),\n)\n\nconst issueToDecode = (issue: StandardSchemaV1.Issue): DecodeIssue => ({\n path: (issue.path ?? []).map(segmentToKey),\n message: issue.message,\n})\n\n// ---------------------------------------------------------------------------\n// Decoding\n// ---------------------------------------------------------------------------\n\n/**\n * Validate an `unknown` against the format's schema. Returns the typed\n * value or a `StructuredDecodeError`. Standard Schema's `validate` may\n * be async; this function handles both sync and async results.\n */\nexport const decode =\n <A>(format: StructuredFormat<A>) =>\n (raw: unknown): Effect.Effect<A, StructuredDecodeError> =>\n pipe(\n Effect.promise(async () => format.schema[\"~standard\"].validate(raw)),\n Effect.flatMap((result) =>\n result.issues === undefined\n ? Effect.succeed(result.value)\n : Effect.fail(\n new StructuredDecodeError({\n raw: typeof raw === \"string\" ? raw : JSON.stringify(raw),\n issues: result.issues.map(issueToDecode),\n }),\n ),\n ),\n )\n\n/**\n * Parse a JSON string then validate against the format's schema. Two\n * failure modes: `JsonParseError` (bytes weren't JSON) and\n * `StructuredDecodeError` (JSON didn't match the schema).\n */\nexport const parseJson =\n <A>(format: StructuredFormat<A>) =>\n (raw: string): Effect.Effect<A, JsonParseError | StructuredDecodeError> =>\n pipe(\n Effect.try({\n try: () => JSON.parse(raw),\n catch: (cause) => new JsonParseError({ raw, cause }),\n }),\n Effect.flatMap(decode(format)),\n )\n\n/**\n * Stream operator: each input string is JSON-parsed and validated.\n * Failures surface in the stream's failure channel, distinguished by tag.\n */\nexport const decodeJsonLines =\n <A>(format: StructuredFormat<A>) =>\n <E, R>(\n self: Stream.Stream<string, E, R>,\n ): Stream.Stream<A, E | JsonParseError | StructuredDecodeError, R> =>\n self.pipe(Stream.mapEffect(parseJson(format)))\n\n/**\n * Like {@link decodeJsonLines}, but each line yields a `Result` instead of\n * failing the stream. Use when one bad line shouldn't abort the rest —\n * log-and-continue, or partial-recovery with a corrective re-prompt.\n * Upstream errors (the input stream's own `E`) still propagate normally.\n */\nexport const decodeJsonLinesRecoverable =\n <A>(format: StructuredFormat<A>) =>\n <E, R>(\n self: Stream.Stream<string, E, R>,\n ): Stream.Stream<Result.Result<A, JsonParseError | StructuredDecodeError>, E, R> =>\n self.pipe(Stream.mapEffect((line) => Effect.result(parseJson(format)(line))))\n"],"mappings":";;;;;;;;;;;;;;;;AA8CA,IAAa,wBAAb,cAA2C,KAAK,YAAY,wBAAwB,CAGjF;;;;;AAMH,IAAa,iBAAb,cAAoC,KAAK,YAAY,2BAA2B,CAG7E;;;;;;AAWH,MAAa,oBACX,QACA,aAKiC;CACjC,MAAM,SAAS,QAAQ;CACvB,QAAQ,OAAO,uBAAuB,OAAO,mBAAmB,OAAO,CAAC;CACxE,GAAI,SAAS,gBAAgB,KAAA,KAAa,EACxC,aAAa,QAAQ,aACtB;CACD,GAAI,SAAS,WAAW,KAAA,KAAa,EAAE,QAAQ,QAAQ,QAAQ;CAChE;AAMD,MAAM,sBAAsB,MAAM,MAAmB,CAAC,KACpD,MAAM,KAAK,MAAM,SAAS,MAAM,EAAE,EAClC,MAAM,KAAK,MAAM,SAAS,MAAM,EAAE,EAClC,MAAM,KAAK,MAAM,SAAS,MAAM,EAAE,UAAU,CAAC,EAC7C,MAAM,WACP;AAED,MAAM,eAAe,MAAM,MAAkD,CAAC,KAC5E,MAAM,KAAK,MAAM,SAAS,MAAM,EAAE,EAClC,MAAM,KAAK,MAAM,SAAS,MAAM,EAAE,EAClC,MAAM,KAAK,MAAM,SAAS,MAAM,EAAE,UAAU,CAAC,EAC7C,MAAM,QAAQ,YAAY,oBAAoB,QAAQ,IAAI,CAAC,CAC5D;AAED,MAAM,iBAAiB,WAAgD;CACrE,OAAO,MAAM,QAAQ,EAAE,EAAE,IAAI,aAAa;CAC1C,SAAS,MAAM;CAChB;;;;;;AAWD,MAAa,UACP,YACH,QACC,KACE,OAAO,QAAQ,YAAY,OAAO,OAAO,aAAa,SAAS,IAAI,CAAC,EACpE,OAAO,SAAS,WACd,OAAO,WAAW,KAAA,IACd,OAAO,QAAQ,OAAO,MAAM,GAC5B,OAAO,KACL,IAAI,sBAAsB;CACxB,KAAK,OAAO,QAAQ,WAAW,MAAM,KAAK,UAAU,IAAI;CACxD,QAAQ,OAAO,OAAO,IAAI,cAAc;CACzC,CAAC,CACH,CACN,CACF;;;;;;AAOL,MAAa,aACP,YACH,QACC,KACE,OAAO,IAAI;CACT,WAAW,KAAK,MAAM,IAAI;CAC1B,QAAQ,UAAU,IAAI,eAAe;EAAE;EAAK;EAAO,CAAC;CACrD,CAAC,EACF,OAAO,QAAQ,OAAO,OAAO,CAAC,CAC/B;;;;;AAML,MAAa,mBACP,YAEF,SAEA,KAAK,KAAK,OAAO,UAAU,UAAU,OAAO,CAAC,CAAC;;;;;;;AAQlD,MAAa,8BACP,YAEF,SAEA,KAAK,KAAK,OAAO,WAAW,SAAS,OAAO,OAAO,UAAU,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"StructuredFormat.mjs","names":[],"sources":["../../src/structured-format/StructuredFormat.ts"],"sourcesContent":["import type { StandardJSONSchemaV1, StandardSchemaV1 } from \"@standard-schema/spec\"\nimport { Data, Effect, Match, Result, Schema, Stream, pipe } from \"effect\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Cross-validator schema constraint for structured outputs. Any schema\n * implementing both Standard Schema (runtime validation) and Standard\n * JSON Schema (wire encoding) works directly: Zod 4+, Valibot, ArkType,\n * and Effect Schema after `fromEffectSchema`.\n */\nexport type StructuredSchema<Output = unknown> = StandardSchemaV1<unknown, Output> &\n StandardJSONSchemaV1<unknown, Output>\n\n/**\n * A schema-bound output the user wants the model to produce. Pairs the\n * cross-validator schema with metadata providers need (name, description,\n * strict-mode flag).\n */\nexport type StructuredFormat<A> = {\n readonly name: string\n readonly description?: string\n readonly schema: StructuredSchema<A>\n /**\n * Provider strict-mode flag. OpenAI, Anthropic, and Mistral honour it\n * (constrained decoding); other providers ignore.\n */\n readonly strict?: boolean\n}\n\n/** A single path-scoped validation problem. Library-agnostic shape. */\nexport type DecodeIssue = {\n readonly path: ReadonlyArray<string | number>\n readonly message: string\n}\n\n// ---------------------------------------------------------------------------\n// Errors\n// ---------------------------------------------------------------------------\n\n/**\n * Schema validation failed. `raw` is the original text (or stringified\n * value) that failed; `issues` is a flat list of per-field problems.\n */\nexport class StructuredDecodeError extends Data.TaggedError(\"StructuredDecodeError\")<{\n readonly raw: string\n readonly issues: ReadonlyArray<DecodeIssue>\n}> {}\n\n/**\n * `JSON.parse` threw on a string that was supposed to be JSON. Distinct\n * from `StructuredDecodeError`: the bytes weren't even JSON.\n */\nexport class JsonParseError extends Data.TaggedError(\"StructuredJsonParseError\")<{\n readonly raw: string\n readonly cause: unknown\n}> {}\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\n/**\n * Wrap an Effect `Schema` as a `StructuredFormat`. Effect Schema doesn't\n * natively implement Standard Schema; this helper installs the\n * `~standard` and JSON Schema interfaces.\n */\nexport const fromEffectSchema = <S extends Schema.Codec<any, any, never, any>>(\n schema: S,\n options?: {\n readonly name?: string\n readonly description?: string\n readonly strict?: boolean\n },\n): StructuredFormat<S[\"Type\"]> => ({\n name: options?.name ?? \"output\",\n schema: Schema.toStandardJSONSchemaV1(Schema.toStandardSchemaV1(schema)),\n ...(options?.description !== undefined && {\n description: options.description,\n }),\n ...(options?.strict !== undefined && { strict: options.strict }),\n})\n\n// ---------------------------------------------------------------------------\n// Standard Schema → DecodeIssue\n// ---------------------------------------------------------------------------\n\nconst propertyKeyToScalar = Match.type<PropertyKey>().pipe(\n Match.when(Match.string, (s) => s),\n Match.when(Match.number, (n) => n),\n Match.when(Match.symbol, (s) => s.toString()),\n Match.exhaustive,\n)\n\nconst segmentToKey = Match.type<PropertyKey | StandardSchemaV1.PathSegment>().pipe(\n Match.when(Match.string, (s) => s),\n Match.when(Match.number, (n) => n),\n Match.when(Match.symbol, (s) => s.toString()),\n Match.orElse((segment) => propertyKeyToScalar(segment.key)),\n)\n\nconst issueToDecode = (issue: StandardSchemaV1.Issue): DecodeIssue => ({\n path: (issue.path ?? []).map(segmentToKey),\n message: issue.message,\n})\n\n// ---------------------------------------------------------------------------\n// Decoding\n// ---------------------------------------------------------------------------\n\n/**\n * Validate an `unknown` against the format's schema. Returns the typed\n * value or a `StructuredDecodeError`. Standard Schema's `validate` may\n * be async; this function handles both sync and async results.\n */\nexport const decode =\n <A>(format: StructuredFormat<A>) =>\n (raw: unknown): Effect.Effect<A, StructuredDecodeError> =>\n pipe(\n Effect.promise(async () => format.schema[\"~standard\"].validate(raw)),\n Effect.flatMap((result) =>\n result.issues === undefined\n ? Effect.succeed(result.value)\n : Effect.fail(\n new StructuredDecodeError({\n raw: typeof raw === \"string\" ? raw : JSON.stringify(raw),\n issues: result.issues.map(issueToDecode),\n }),\n ),\n ),\n )\n\n/**\n * Parse a JSON string then validate against the format's schema. Two\n * failure modes: `JsonParseError` (bytes weren't JSON) and\n * `StructuredDecodeError` (JSON didn't match the schema).\n */\nexport const parseJson =\n <A>(format: StructuredFormat<A>) =>\n (raw: string): Effect.Effect<A, JsonParseError | StructuredDecodeError> =>\n pipe(\n Effect.try({\n // Validation runs through Standard Schema (Zod / Valibot / Effect), not\n // Effect Schema, so its JSON codecs don't apply here; and JSON.parse is\n // a separate step on purpose, to surface JsonParseError distinctly.\n // @effect-diagnostics-next-line effect/preferSchemaOverJson:off\n try: () => JSON.parse(raw),\n catch: (cause) => new JsonParseError({ raw, cause }),\n }),\n Effect.flatMap(decode(format)),\n )\n\n/**\n * Stream operator: each input string is JSON-parsed and validated.\n * Failures surface in the stream's failure channel, distinguished by tag.\n */\nexport const decodeJsonLines =\n <A>(format: StructuredFormat<A>) =>\n <E, R>(\n self: Stream.Stream<string, E, R>,\n ): Stream.Stream<A, E | JsonParseError | StructuredDecodeError, R> =>\n self.pipe(Stream.mapEffect(parseJson(format)))\n\n/**\n * Like {@link decodeJsonLines}, but each line yields a `Result` instead of\n * failing the stream. Use when one bad line shouldn't abort the rest —\n * log-and-continue, or partial-recovery with a corrective re-prompt.\n * Upstream errors (the input stream's own `E`) still propagate normally.\n */\nexport const decodeJsonLinesRecoverable =\n <A>(format: StructuredFormat<A>) =>\n <E, R>(\n self: Stream.Stream<string, E, R>,\n ): Stream.Stream<Result.Result<A, JsonParseError | StructuredDecodeError>, E, R> =>\n self.pipe(Stream.mapEffect((line) => Effect.result(parseJson(format)(line))))\n"],"mappings":";;;;;;;;;;;;;;;;AA8CA,IAAa,wBAAb,cAA2C,KAAK,YAAY,wBAAwB,CAGjF;;;;;AAMH,IAAa,iBAAb,cAAoC,KAAK,YAAY,2BAA2B,CAG7E;;;;;;AAWH,MAAa,oBACX,QACA,aAKiC;CACjC,MAAM,SAAS,QAAQ;CACvB,QAAQ,OAAO,uBAAuB,OAAO,mBAAmB,OAAO,CAAC;CACxE,GAAI,SAAS,gBAAgB,KAAA,KAAa,EACxC,aAAa,QAAQ,aACtB;CACD,GAAI,SAAS,WAAW,KAAA,KAAa,EAAE,QAAQ,QAAQ,QAAQ;CAChE;AAMD,MAAM,sBAAsB,MAAM,MAAmB,CAAC,KACpD,MAAM,KAAK,MAAM,SAAS,MAAM,EAAE,EAClC,MAAM,KAAK,MAAM,SAAS,MAAM,EAAE,EAClC,MAAM,KAAK,MAAM,SAAS,MAAM,EAAE,UAAU,CAAC,EAC7C,MAAM,WACP;AAED,MAAM,eAAe,MAAM,MAAkD,CAAC,KAC5E,MAAM,KAAK,MAAM,SAAS,MAAM,EAAE,EAClC,MAAM,KAAK,MAAM,SAAS,MAAM,EAAE,EAClC,MAAM,KAAK,MAAM,SAAS,MAAM,EAAE,UAAU,CAAC,EAC7C,MAAM,QAAQ,YAAY,oBAAoB,QAAQ,IAAI,CAAC,CAC5D;AAED,MAAM,iBAAiB,WAAgD;CACrE,OAAO,MAAM,QAAQ,EAAE,EAAE,IAAI,aAAa;CAC1C,SAAS,MAAM;CAChB;;;;;;AAWD,MAAa,UACP,YACH,QACC,KACE,OAAO,QAAQ,YAAY,OAAO,OAAO,aAAa,SAAS,IAAI,CAAC,EACpE,OAAO,SAAS,WACd,OAAO,WAAW,KAAA,IACd,OAAO,QAAQ,OAAO,MAAM,GAC5B,OAAO,KACL,IAAI,sBAAsB;CACxB,KAAK,OAAO,QAAQ,WAAW,MAAM,KAAK,UAAU,IAAI;CACxD,QAAQ,OAAO,OAAO,IAAI,cAAc;CACzC,CAAC,CACH,CACN,CACF;;;;;;AAOL,MAAa,aACP,YACH,QACC,KACE,OAAO,IAAI;CAKT,WAAW,KAAK,MAAM,IAAI;CAC1B,QAAQ,UAAU,IAAI,eAAe;EAAE;EAAK;EAAO,CAAC;CACrD,CAAC,EACF,OAAO,QAAQ,OAAO,OAAO,CAAC,CAC/B;;;;;AAML,MAAa,mBACP,YAEF,SAEA,KAAK,KAAK,OAAO,UAAU,UAAU,OAAO,CAAC,CAAC;;;;;;;AAQlD,MAAa,8BACP,YAEF,SAEA,KAAK,KAAK,OAAO,WAAW,SAAS,OAAO,OAAO,UAAU,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import { JsonParseError, StructuredDecodeError, decodeJsonLines, decodeJsonLinesRecoverable, fromEffectSchema } from "./StructuredFormat.mjs";
2
- import { i as it, n as globalExpect, r as describe } from "../dist-DV5ISja1.mjs";
2
+ import { a as describe, n as globalExpect, o as it } from "../dist-B04dlFHg.mjs";
3
3
  import { Effect, Exit, Filter, Result, Schema, Stream } from "effect";
4
4
  //#region src/structured-format/StructuredFormat.test.ts
5
5
  const itemFormat = fromEffectSchema(Schema.Struct({
@@ -1,4 +1,4 @@
1
- import { n as AudioChunk } from "../Audio-DPlTsNdz.mjs";
1
+ import { n as AudioChunk } from "../Audio-CR7CSljJ.mjs";
2
2
  import { CommonGenerateMusicRequest, CommonStreamGenerateMusicRequest, GenerateResult, MusicStreamEvent, audioEvent } from "../domain/Music.mjs";
3
3
  import { MusicGenerator, MusicInteractiveSession } from "../music-generator/MusicGenerator.mjs";
4
4
  import { Effect, Layer } from "effect";
@@ -1,4 +1,4 @@
1
- import { o as HistoryItem } from "../Items-7ljaVxH_.mjs";
1
+ import { o as HistoryItem } from "../Items-BnIduEV2.mjs";
2
2
  import { Turn } from "../domain/Turn.mjs";
3
3
  import { LanguageModel, LanguageModelService } from "../language-model/LanguageModel.mjs";
4
4
  import { Duration, Effect, Layer } from "effect";
@@ -1,4 +1,4 @@
1
- import { n as AudioChunk, t as AudioBlob } from "../Audio-DPlTsNdz.mjs";
1
+ import { n as AudioChunk, t as AudioBlob } from "../Audio-CR7CSljJ.mjs";
2
2
  import { CommonStreamSynthesizeRequest, CommonSynthesizeDialogueRequest, CommonSynthesizeRequest, MultiSpeakerTts, SpeechSynthesizer, TtsIncrementalText } from "../speech-synthesizer/SpeechSynthesizer.mjs";
3
3
  import { Effect, Layer } from "effect";
4
4
 
@@ -1,12 +1,12 @@
1
- import { v as ToolCall } from "../Items-7ljaVxH_.mjs";
2
- import { ToolResult } from "./ToolResult.mjs";
3
- import { ToolEvent } from "./ToolEvent.mjs";
4
- import { Data, Effect, Queue, Scope, Stream } from "effect";
1
+ import { v as ToolCall } from "../Items-BnIduEV2.mjs";
2
+ import { t as ToolResult } from "../ToolResult-CL4hK_9z.mjs";
3
+ import { t as ToolEvent } from "../ToolEvent-cWZ2BnbS.mjs";
4
+ import { Data, Duration, Effect, Queue, Scope, Stream } from "effect";
5
5
  import * as _$effect_Unify0 from "effect/Unify";
6
6
 
7
7
  //#region src/tool/Approval.d.ts
8
8
  declare namespace Approval_d_exports {
9
- export { ApprovalDecision, ApprovalMapEntry, ToolCallPlan, Verdict, approvalRequested, approve, fromMap, fromQueue, reject, splitApprovalDecisions };
9
+ export { ApprovalDecision, ApprovalMapEntry, FromQueueOptions, ToolCallPlan, Verdict, approvalRequested, approve, fromMap, fromQueue, reject, splitApprovalDecisions };
10
10
  }
11
11
  type ToolCallPlan = {
12
12
  readonly approved: ReadonlyArray<ToolCall>;
@@ -84,12 +84,24 @@ type Verdict = {
84
84
  readonly decision: "approve" | "deny";
85
85
  readonly reason?: string;
86
86
  };
87
+ type FromQueueOptions = {
88
+ /**
89
+ * Resolve any still-pending gated call as `cancelled` once this elapses,
90
+ * so a lost or never-sent verdict cannot hang the decisions stream
91
+ * forever. Omit to wait indefinitely.
92
+ */
93
+ readonly timeout?: Duration.Input;
94
+ };
87
95
  /**
88
96
  * Queue-backed approval planner. Safe calls are returned immediately in
89
97
  * `approved`; gated calls emit `ApprovalRequested` events and later produce
90
98
  * one `ApprovalDecision` when their matching verdict arrives.
99
+ *
100
+ * The verdict queue is a single-consumer transport: run one approval round
101
+ * at a time (the router retires once every gated call of the round is
102
+ * resolved). For overlapping rounds, use one queue per round.
91
103
  */
92
- declare const fromQueue: (predicate: (call: ToolCall) => boolean, verdicts: Queue.Dequeue<Verdict>) => (calls: ReadonlyArray<ToolCall>) => Effect.Effect<{
104
+ declare const fromQueue: (predicate: (call: ToolCall) => boolean, verdicts: Queue.Dequeue<Verdict>, options?: FromQueueOptions) => (calls: ReadonlyArray<ToolCall>) => Effect.Effect<{
93
105
  readonly approved: ReadonlyArray<ToolCall>;
94
106
  readonly decisions: Stream.Stream<ApprovalDecision>;
95
107
  readonly approvalRequests: Stream.Stream<ToolEvent>;
@@ -102,5 +114,5 @@ type ApprovalMapEntry = {
102
114
  };
103
115
  declare const fromMap: (predicate: (call: ToolCall) => boolean, approvals: ReadonlyMap<string, ApprovalMapEntry>) => (calls: ReadonlyArray<ToolCall>) => ToolCallPlan;
104
116
  //#endregion
105
- export { ApprovalDecision, ApprovalMapEntry, ToolCallPlan, Verdict, approvalRequested, approve, fromMap, fromQueue, reject, splitApprovalDecisions, Approval_d_exports as t };
117
+ export { ApprovalDecision, ApprovalMapEntry, FromQueueOptions, ToolCallPlan, Verdict, approvalRequested, approve, fromMap, fromQueue, reject, splitApprovalDecisions, Approval_d_exports as t };
106
118
  //# sourceMappingURL=Approval.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Approval.d.mts","names":[],"sources":["../../src/tool/Approval.ts"],"mappings":";;;;;;;;;;KAYY,YAAA;EAAA,SACD,QAAA,EAAU,aAAA,CAAc,QAAA;EAAA,SACxB,QAAA,EAAU,aAAA,CAAc,UAAA;AAAA;AAAA,KAGvB,gBAAA,GAAmB,IAAA,CAAK,UAAA;EAClC,QAAA;IAAA,SAAqB,IAAA,EAAM,QAAA;EAAA;EAC3B,QAAA;IAAA,SAAqB,MAAA,EAAQ,UAAA;EAAA;AAAA;AAAA,cAGlB,gBAAA;EAAA;;mBAJgB,QAAA;EAAA;EAAA;;qBACE,UAAA;EAAA;EAAA;;mBADF,QAAA;EAAA;IAAA;;;qBACE,UAAA;EAAA;IAAA;;;;;;uBADF,QAAA;MAAA;MAAA;;yBACE,UAAA;MAAA;IAAA;;qBADF,QAAA;IAAA;MAAA;uBACE,UAAA;IAAA;;;;uBADF,QAAA;MAAA;MAAA;;yBACE,UAAA;MAAA;IAAA;;qBADF,QAAA;IAAA;MAAA;uBACE,UAAA;IAAA;;;cAKlB,OAAA,GAAW,IAAA,EAAM,QAAA,KAAW,gBAAA;AAAA,cAE5B,MAAA,GAAU,MAAA,EAAQ,UAAA,KAAa,gBAAA;AAAA,cAG/B,sBAAA,GAA0B,SAAA,EAAW,aAAA,CAAc,gBAAA,MAAoB,YAAA;AAAA,cASvE,iBAAA,GAAqB,IAAA,EAAM,QAAA,KAAW,SAAA;AAAA,KAWvC,OAAA;EAAA,SACD,OAAA;EAAA,SACA,QAAA;EAAA,SACA,MAAA;AAAA;;;;;;cAQE,SAAA,GACV,SAAA,GAAY,IAAA,EAAM,QAAA,cAAsB,QAAA,EAAU,KAAA,CAAM,OAAA,CAAQ,OAAA,OAE/D,KAAA,EAAO,aAAA,CAAc,QAAA,MACpB,MAAA,CAAO,MAAA;EAAA,SAEG,QAAA,EAAU,aAAA,CAAc,QAAA;EAAA,SACxB,SAAA,EAAW,MAAA,CAAO,MAAA,CAAO,gBAAA;EAAA,SACzB,gBAAA,EAAkB,MAAA,CAAO,MAAA,CAAO,SAAA;AAAA,UAG3C,KAAA,CAAM,KAAA;AAAA,KAkDE,gBAAA;EAAA,SACG,QAAA;AAAA;EAAA,SACA,QAAA;EAAA,SAA2B,MAAA;AAAA;AAAA,cAE7B,OAAA,GACV,SAAA,GAAY,IAAA,EAAM,QAAA,cAAsB,SAAA,EAAW,WAAA,SAAoB,gBAAA,OACvE,KAAA,EAAO,aAAA,CAAc,QAAA,MAAY,YAAA"}
1
+ {"version":3,"file":"Approval.d.mts","names":[],"sources":["../../src/tool/Approval.ts"],"mappings":";;;;;;;;;;KAYY,YAAA;EAAA,SACD,QAAA,EAAU,aAAA,CAAc,QAAA;EAAA,SACxB,QAAA,EAAU,aAAA,CAAc,UAAA;AAAA;AAAA,KAGvB,gBAAA,GAAmB,IAAA,CAAK,UAAA;EAClC,QAAA;IAAA,SAAqB,IAAA,EAAM,QAAA;EAAA;EAC3B,QAAA;IAAA,SAAqB,MAAA,EAAQ,UAAA;EAAA;AAAA;AAAA,cAGlB,gBAAA;EAAA;;mBAJgB,QAAA;EAAA;EAAA;;qBACE,UAAA;EAAA;EAAA;;mBADF,QAAA;EAAA;IAAA;;;qBACE,UAAA;EAAA;IAAA;;;;;;uBADF,QAAA;MAAA;MAAA;;yBACE,UAAA;MAAA;IAAA;;qBADF,QAAA;IAAA;MAAA;uBACE,UAAA;IAAA;;;;uBADF,QAAA;MAAA;MAAA;;yBACE,UAAA;MAAA;IAAA;;qBADF,QAAA;IAAA;MAAA;uBACE,UAAA;IAAA;;;cAKlB,OAAA,GAAW,IAAA,EAAM,QAAA,KAAW,gBAAA;AAAA,cAE5B,MAAA,GAAU,MAAA,EAAQ,UAAA,KAAa,gBAAA;AAAA,cAG/B,sBAAA,GAA0B,SAAA,EAAW,aAAA,CAAc,gBAAA,MAAoB,YAAA;AAAA,cASvE,iBAAA,GAAqB,IAAA,EAAM,QAAA,KAAW,SAAA;AAAA,KAWvC,OAAA;EAAA,SACD,OAAA;EAAA,SACA,QAAA;EAAA,SACA,MAAA;AAAA;AAAA,KAGC,gBAAA;;;;;;WAMD,OAAA,GAAU,QAAA,CAAS,KAAA;AAAA;;;;;;;;;;cAYjB,SAAA,GAET,SAAA,GAAY,IAAA,EAAM,QAAA,cAClB,QAAA,EAAU,KAAA,CAAM,OAAA,CAAQ,OAAA,GACxB,OAAA,GAAU,gBAAA,MAGV,KAAA,EAAO,aAAA,CAAc,QAAA,MACpB,MAAA,CAAO,MAAA;EAAA,SAEG,QAAA,EAAU,aAAA,CAAc,QAAA;EAAA,SACxB,SAAA,EAAW,MAAA,CAAO,MAAA,CAAO,gBAAA;EAAA,SACzB,gBAAA,EAAkB,MAAA,CAAO,MAAA,CAAO,SAAA;AAAA,UAG3C,KAAA,CAAM,KAAA;AAAA,KA8EE,gBAAA;EAAA,SACG,QAAA;AAAA;EAAA,SACA,QAAA;EAAA,SAA2B,MAAA;AAAA;AAAA,cAE7B,OAAA,GACV,SAAA,GAAY,IAAA,EAAM,QAAA,cAAsB,SAAA,EAAW,WAAA,SAAoB,gBAAA,OACvE,KAAA,EAAO,aAAA,CAAc,QAAA,MAAY,YAAA"}
@@ -1,7 +1,7 @@
1
1
  import { n as __exportAll } from "../chunk-uyGKjUfl.mjs";
2
2
  import { cancelled, denied } from "./ToolResult.mjs";
3
3
  import { ToolEvent } from "./ToolEvent.mjs";
4
- import { Data, Deferred, Effect, Queue, Stream } from "effect";
4
+ import { Data, Deferred, Effect, Queue, Ref, Stream } from "effect";
5
5
  //#region src/tool/Approval.ts
6
6
  /**
7
7
  * Approval helpers for the two transport flavors.
@@ -41,23 +41,30 @@ const approvalRequested = (call) => ToolEvent.ApprovalRequested({
41
41
  * Queue-backed approval planner. Safe calls are returned immediately in
42
42
  * `approved`; gated calls emit `ApprovalRequested` events and later produce
43
43
  * one `ApprovalDecision` when their matching verdict arrives.
44
+ *
45
+ * The verdict queue is a single-consumer transport: run one approval round
46
+ * at a time (the router retires once every gated call of the round is
47
+ * resolved). For overlapping rounds, use one queue per round.
44
48
  */
45
- const fromQueue = (predicate, verdicts) => (calls) => Effect.gen(function* () {
49
+ const fromQueue = (predicate, verdicts, options) => (calls) => Effect.gen(function* () {
46
50
  const gated = calls.filter(predicate);
47
51
  const approved = calls.filter((call) => !predicate(call));
48
52
  const entries = yield* Effect.forEach(gated, (call) => Deferred.make().pipe(Effect.map((d) => [call.call_id, d])));
49
53
  const deferreds = new Map(entries);
50
- yield* Effect.forkScoped(Effect.forever(Effect.gen(function* () {
51
- const v = yield* Queue.take(verdicts);
52
- const d = deferreds.get(v.call_id);
53
- if (d !== void 0) yield* Deferred.succeed(d, v);
54
- })));
54
+ const callById = new Map(gated.map((c) => [c.call_id, c]));
55
+ const outstanding = yield* Ref.make(gated.length);
56
+ const resolve = (call, decision) => Deferred.succeed(deferreds.get(call.call_id), decision).pipe(Effect.flatMap((won) => won ? Ref.update(outstanding, (n) => n - 1) : Effect.void));
57
+ const route = Effect.suspend(() => Ref.get(outstanding).pipe(Effect.flatMap((n) => n <= 0 ? Effect.void : Queue.take(verdicts).pipe(Effect.flatMap((v) => {
58
+ const call = callById.get(v.call_id);
59
+ return (call === void 0 ? Effect.void : resolve(call, v.decision === "approve" ? approve(call) : reject(denied(call, v.reason)))).pipe(Effect.andThen(route));
60
+ })))));
61
+ if (gated.length > 0) {
62
+ yield* Effect.forkScoped(route);
63
+ if (options?.timeout !== void 0) yield* Effect.forkScoped(Effect.sleep(options.timeout).pipe(Effect.andThen(Effect.forEach(gated, (call) => resolve(call, reject(cancelled(call, "approval timed out")))))));
64
+ }
55
65
  return {
56
66
  approved,
57
- decisions: Stream.fromIterable(gated).pipe(Stream.flatMap((call) => {
58
- const d = deferreds.get(call.call_id);
59
- return Stream.fromEffect(Deferred.await(d).pipe(Effect.map((v) => v.decision === "approve" ? approve(call) : reject(denied(call, v.reason)))));
60
- }, { concurrency: "unbounded" })),
67
+ decisions: Stream.fromIterable(gated).pipe(Stream.flatMap((call) => Stream.fromEffect(Deferred.await(deferreds.get(call.call_id))), { concurrency: "unbounded" })),
61
68
  approvalRequests: Stream.fromIterable(gated.map(approvalRequested))
62
69
  };
63
70
  });
@@ -1 +1 @@
1
- {"version":3,"file":"Approval.mjs","names":[],"sources":["../../src/tool/Approval.ts"],"sourcesContent":["/**\n * Approval helpers for the two transport flavors.\n *\n * These helpers only decide which calls are approved and which synthetic\n * results must be returned to the model. Tool execution stays explicit at\n * the recipe boundary via `Toolkit.run`.\n */\nimport { Data, Deferred, Effect, Queue, Scope, Stream } from \"effect\"\nimport type { ToolCall } from \"../domain/Items.js\"\nimport { type ToolResult, cancelled, denied } from \"./ToolResult.js\"\nimport { ToolEvent } from \"./ToolEvent.js\"\n\nexport type ToolCallPlan = {\n readonly approved: ReadonlyArray<ToolCall>\n readonly rejected: ReadonlyArray<ToolResult>\n}\n\nexport type ApprovalDecision = Data.TaggedEnum<{\n Approved: { readonly call: ToolCall }\n Rejected: { readonly result: ToolResult }\n}>\n\nexport const ApprovalDecision = Data.taggedEnum<ApprovalDecision>()\n\nexport const approve = (call: ToolCall): ApprovalDecision => ApprovalDecision.Approved({ call })\n\nexport const reject = (result: ToolResult): ApprovalDecision =>\n ApprovalDecision.Rejected({ result })\n\nexport const splitApprovalDecisions = (decisions: ReadonlyArray<ApprovalDecision>): ToolCallPlan =>\n decisions.reduce<ToolCallPlan>(\n (acc, decision) =>\n decision._tag === \"Approved\"\n ? { ...acc, approved: [...acc.approved, decision.call] }\n : { ...acc, rejected: [...acc.rejected, decision.result] },\n { approved: [], rejected: [] },\n )\n\nexport const approvalRequested = (call: ToolCall): ToolEvent =>\n ToolEvent.ApprovalRequested({\n call_id: call.call_id,\n tool: call.name,\n arguments: call.arguments,\n })\n\n// ---------------------------------------------------------------------------\n// Verdict queue (WebSocket-style transport).\n// ---------------------------------------------------------------------------\n\nexport type Verdict = {\n readonly call_id: string\n readonly decision: \"approve\" | \"deny\"\n readonly reason?: string\n}\n\n/**\n * Queue-backed approval planner. Safe calls are returned immediately in\n * `approved`; gated calls emit `ApprovalRequested` events and later produce\n * one `ApprovalDecision` when their matching verdict arrives.\n */\nexport const fromQueue =\n (predicate: (call: ToolCall) => boolean, verdicts: Queue.Dequeue<Verdict>) =>\n (\n calls: ReadonlyArray<ToolCall>,\n ): Effect.Effect<\n {\n readonly approved: ReadonlyArray<ToolCall>\n readonly decisions: Stream.Stream<ApprovalDecision>\n readonly approvalRequests: Stream.Stream<ToolEvent>\n },\n never,\n Scope.Scope\n > =>\n Effect.gen(function* () {\n const gated = calls.filter(predicate)\n const approved = calls.filter((call) => !predicate(call))\n\n const entries = yield* Effect.forEach(gated, (call) =>\n Deferred.make<Verdict>().pipe(Effect.map((d) => [call.call_id, d] as const)),\n )\n const deferreds: ReadonlyMap<string, Deferred.Deferred<Verdict>> = new Map(entries)\n\n // Router is forked into the surrounding Scope so it lives as long\n // as the consumer is pulling events. Recipes typically supply the\n // scope by wrapping the events construction in `Stream.unwrap`.\n yield* Effect.forkScoped(\n Effect.forever(\n Effect.gen(function* () {\n const v = yield* Queue.take(verdicts)\n const d = deferreds.get(v.call_id)\n if (d !== undefined) yield* Deferred.succeed(d, v)\n }),\n ),\n )\n\n const decisions = Stream.fromIterable(gated).pipe(\n Stream.flatMap(\n (call) => {\n const d = deferreds.get(call.call_id)!\n return Stream.fromEffect(\n Deferred.await(d).pipe(\n Effect.map((v) =>\n v.decision === \"approve\" ? approve(call) : reject(denied(call, v.reason)),\n ),\n ),\n )\n },\n { concurrency: \"unbounded\" },\n ),\n )\n\n const approvalRequests = Stream.fromIterable<ToolEvent>(gated.map(approvalRequested))\n\n return { approved, decisions, approvalRequests }\n })\n\n// ---------------------------------------------------------------------------\n// Approval map (HTTP-style transport). Verdicts arrive synchronously\n// bundled in the request payload. Missing entries → cancelled.\n// ---------------------------------------------------------------------------\n\nexport type ApprovalMapEntry =\n | { readonly decision: \"approve\" }\n | { readonly decision: \"deny\"; readonly reason?: string }\n\nexport const fromMap =\n (predicate: (call: ToolCall) => boolean, approvals: ReadonlyMap<string, ApprovalMapEntry>) =>\n (calls: ReadonlyArray<ToolCall>): ToolCallPlan =>\n splitApprovalDecisions(\n calls.map((call) => {\n if (!predicate(call)) return approve(call)\n const v = approvals.get(call.call_id)\n if (v === undefined) return reject(cancelled(call))\n return v.decision === \"approve\" ? approve(call) : reject(denied(call, v.reason))\n }),\n )\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAsBA,MAAa,mBAAmB,KAAK,YAA8B;AAEnE,MAAa,WAAW,SAAqC,iBAAiB,SAAS,EAAE,MAAM,CAAC;AAEhG,MAAa,UAAU,WACrB,iBAAiB,SAAS,EAAE,QAAQ,CAAC;AAEvC,MAAa,0BAA0B,cACrC,UAAU,QACP,KAAK,aACJ,SAAS,SAAS,aACd;CAAE,GAAG;CAAK,UAAU,CAAC,GAAG,IAAI,UAAU,SAAS,KAAK;CAAE,GACtD;CAAE,GAAG;CAAK,UAAU,CAAC,GAAG,IAAI,UAAU,SAAS,OAAO;CAAE,EAC9D;CAAE,UAAU,EAAE;CAAE,UAAU,EAAE;CAAE,CAC/B;AAEH,MAAa,qBAAqB,SAChC,UAAU,kBAAkB;CAC1B,SAAS,KAAK;CACd,MAAM,KAAK;CACX,WAAW,KAAK;CACjB,CAAC;;;;;;AAiBJ,MAAa,aACV,WAAwC,cAEvC,UAUA,OAAO,IAAI,aAAa;CACtB,MAAM,QAAQ,MAAM,OAAO,UAAU;CACrC,MAAM,WAAW,MAAM,QAAQ,SAAS,CAAC,UAAU,KAAK,CAAC;CAEzD,MAAM,UAAU,OAAO,OAAO,QAAQ,QAAQ,SAC5C,SAAS,MAAe,CAAC,KAAK,OAAO,KAAK,MAAM,CAAC,KAAK,SAAS,EAAE,CAAU,CAAC,CAC7E;CACD,MAAM,YAA6D,IAAI,IAAI,QAAQ;AAKnF,QAAO,OAAO,WACZ,OAAO,QACL,OAAO,IAAI,aAAa;EACtB,MAAM,IAAI,OAAO,MAAM,KAAK,SAAS;EACrC,MAAM,IAAI,UAAU,IAAI,EAAE,QAAQ;AAClC,MAAI,MAAM,KAAA,EAAW,QAAO,SAAS,QAAQ,GAAG,EAAE;GAClD,CACH,CACF;AAoBD,QAAO;EAAE;EAAU,WAlBD,OAAO,aAAa,MAAM,CAAC,KAC3C,OAAO,SACJ,SAAS;GACR,MAAM,IAAI,UAAU,IAAI,KAAK,QAAQ;AACrC,UAAO,OAAO,WACZ,SAAS,MAAM,EAAE,CAAC,KAChB,OAAO,KAAK,MACV,EAAE,aAAa,YAAY,QAAQ,KAAK,GAAG,OAAO,OAAO,MAAM,EAAE,OAAO,CAAC,CAC1E,CACF,CACF;KAEH,EAAE,aAAa,aAAa,CAC7B,CAKyB;EAAE,kBAFL,OAAO,aAAwB,MAAM,IAAI,kBAAkB,CAEtC;EAAE;EAChD;AAWN,MAAa,WACV,WAAwC,eACxC,UACC,uBACE,MAAM,KAAK,SAAS;AAClB,KAAI,CAAC,UAAU,KAAK,CAAE,QAAO,QAAQ,KAAK;CAC1C,MAAM,IAAI,UAAU,IAAI,KAAK,QAAQ;AACrC,KAAI,MAAM,KAAA,EAAW,QAAO,OAAO,UAAU,KAAK,CAAC;AACnD,QAAO,EAAE,aAAa,YAAY,QAAQ,KAAK,GAAG,OAAO,OAAO,MAAM,EAAE,OAAO,CAAC;EAChF,CACH"}
1
+ {"version":3,"file":"Approval.mjs","names":[],"sources":["../../src/tool/Approval.ts"],"sourcesContent":["/**\n * Approval helpers for the two transport flavors.\n *\n * These helpers only decide which calls are approved and which synthetic\n * results must be returned to the model. Tool execution stays explicit at\n * the recipe boundary via `Toolkit.run`.\n */\nimport { Data, Deferred, type Duration, Effect, Queue, Ref, Scope, Stream } from \"effect\"\nimport type { ToolCall } from \"../domain/Items.js\"\nimport { type ToolResult, cancelled, denied } from \"./ToolResult.js\"\nimport { ToolEvent } from \"./ToolEvent.js\"\n\nexport type ToolCallPlan = {\n readonly approved: ReadonlyArray<ToolCall>\n readonly rejected: ReadonlyArray<ToolResult>\n}\n\nexport type ApprovalDecision = Data.TaggedEnum<{\n Approved: { readonly call: ToolCall }\n Rejected: { readonly result: ToolResult }\n}>\n\nexport const ApprovalDecision = Data.taggedEnum<ApprovalDecision>()\n\nexport const approve = (call: ToolCall): ApprovalDecision => ApprovalDecision.Approved({ call })\n\nexport const reject = (result: ToolResult): ApprovalDecision =>\n ApprovalDecision.Rejected({ result })\n\nexport const splitApprovalDecisions = (decisions: ReadonlyArray<ApprovalDecision>): ToolCallPlan =>\n decisions.reduce<ToolCallPlan>(\n (acc, decision) =>\n decision._tag === \"Approved\"\n ? { ...acc, approved: [...acc.approved, decision.call] }\n : { ...acc, rejected: [...acc.rejected, decision.result] },\n { approved: [], rejected: [] },\n )\n\nexport const approvalRequested = (call: ToolCall): ToolEvent =>\n ToolEvent.ApprovalRequested({\n call_id: call.call_id,\n tool: call.name,\n arguments: call.arguments,\n })\n\n// ---------------------------------------------------------------------------\n// Verdict queue (WebSocket-style transport).\n// ---------------------------------------------------------------------------\n\nexport type Verdict = {\n readonly call_id: string\n readonly decision: \"approve\" | \"deny\"\n readonly reason?: string\n}\n\nexport type FromQueueOptions = {\n /**\n * Resolve any still-pending gated call as `cancelled` once this elapses,\n * so a lost or never-sent verdict cannot hang the decisions stream\n * forever. Omit to wait indefinitely.\n */\n readonly timeout?: Duration.Input\n}\n\n/**\n * Queue-backed approval planner. Safe calls are returned immediately in\n * `approved`; gated calls emit `ApprovalRequested` events and later produce\n * one `ApprovalDecision` when their matching verdict arrives.\n *\n * The verdict queue is a single-consumer transport: run one approval round\n * at a time (the router retires once every gated call of the round is\n * resolved). For overlapping rounds, use one queue per round.\n */\nexport const fromQueue =\n (\n predicate: (call: ToolCall) => boolean,\n verdicts: Queue.Dequeue<Verdict>,\n options?: FromQueueOptions,\n ) =>\n (\n calls: ReadonlyArray<ToolCall>,\n ): Effect.Effect<\n {\n readonly approved: ReadonlyArray<ToolCall>\n readonly decisions: Stream.Stream<ApprovalDecision>\n readonly approvalRequests: Stream.Stream<ToolEvent>\n },\n never,\n Scope.Scope\n > =>\n Effect.gen(function* () {\n const gated = calls.filter(predicate)\n const approved = calls.filter((call) => !predicate(call))\n\n const entries = yield* Effect.forEach(gated, (call) =>\n Deferred.make<ApprovalDecision>().pipe(Effect.map((d) => [call.call_id, d] as const)),\n )\n const deferreds: ReadonlyMap<string, Deferred.Deferred<ApprovalDecision>> = new Map(entries)\n const callById: ReadonlyMap<string, ToolCall> = new Map(gated.map((c) => [c.call_id, c]))\n const outstanding = yield* Ref.make(gated.length)\n\n // Complete a deferred once; decrement only on the winning transition so\n // a late verdict for an already-resolved call is a no-op.\n const resolve = (call: ToolCall, decision: ApprovalDecision) =>\n Deferred.succeed(deferreds.get(call.call_id)!, decision).pipe(\n Effect.flatMap((won) => (won ? Ref.update(outstanding, (n) => n - 1) : Effect.void)),\n )\n\n // Router retires once every gated call is resolved (by verdict or\n // timeout), so sequential rounds over one queue do not accumulate\n // racing routers. Recursive + suspended: stack-safe, no imperative loop.\n const route: Effect.Effect<void> = Effect.suspend(() =>\n Ref.get(outstanding).pipe(\n Effect.flatMap((n) =>\n n <= 0\n ? Effect.void\n : Queue.take(verdicts).pipe(\n Effect.flatMap((v) => {\n const call = callById.get(v.call_id)\n return (\n call === undefined\n ? Effect.void\n : resolve(\n call,\n v.decision === \"approve\"\n ? approve(call)\n : reject(denied(call, v.reason)),\n )\n ).pipe(Effect.andThen(route))\n }),\n ),\n ),\n ),\n )\n\n if (gated.length > 0) {\n yield* Effect.forkScoped(route)\n if (options?.timeout !== undefined) {\n yield* Effect.forkScoped(\n Effect.sleep(options.timeout).pipe(\n Effect.andThen(\n Effect.forEach(gated, (call) =>\n resolve(call, reject(cancelled(call, \"approval timed out\"))),\n ),\n ),\n ),\n )\n }\n }\n\n const decisions = Stream.fromIterable(gated).pipe(\n Stream.flatMap((call) => Stream.fromEffect(Deferred.await(deferreds.get(call.call_id)!)), {\n concurrency: \"unbounded\",\n }),\n )\n\n const approvalRequests = Stream.fromIterable<ToolEvent>(gated.map(approvalRequested))\n\n return { approved, decisions, approvalRequests }\n })\n\n// ---------------------------------------------------------------------------\n// Approval map (HTTP-style transport). Verdicts arrive synchronously\n// bundled in the request payload. Missing entries → cancelled.\n// ---------------------------------------------------------------------------\n\nexport type ApprovalMapEntry =\n | { readonly decision: \"approve\" }\n | { readonly decision: \"deny\"; readonly reason?: string }\n\nexport const fromMap =\n (predicate: (call: ToolCall) => boolean, approvals: ReadonlyMap<string, ApprovalMapEntry>) =>\n (calls: ReadonlyArray<ToolCall>): ToolCallPlan =>\n splitApprovalDecisions(\n calls.map((call) => {\n if (!predicate(call)) return approve(call)\n const v = approvals.get(call.call_id)\n if (v === undefined) return reject(cancelled(call))\n return v.decision === \"approve\" ? approve(call) : reject(denied(call, v.reason))\n }),\n )\n"],"mappings":"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"}