@effect-uai/core 0.3.0 → 0.5.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 (169) hide show
  1. package/dist/{AiError-CBuPHVKA.d.mts → AiError-CAX_48RU.d.mts} +27 -5
  2. package/dist/{AiError-CBuPHVKA.d.mts.map → AiError-CAX_48RU.d.mts.map} +1 -1
  3. package/dist/Audio-BfCTGnH3.d.mts +61 -0
  4. package/dist/Audio-BfCTGnH3.d.mts.map +1 -0
  5. package/dist/{Image-BZmKfIdq.d.mts → Image-HNmMpMTh.d.mts} +1 -1
  6. package/dist/{Image-BZmKfIdq.d.mts.map → Image-HNmMpMTh.d.mts.map} +1 -1
  7. package/dist/{Items-CB8Bo3FI.d.mts → Items-DqbaJoz7.d.mts} +5 -5
  8. package/dist/{Items-CB8Bo3FI.d.mts.map → Items-DqbaJoz7.d.mts.map} +1 -1
  9. package/dist/{StructuredFormat-BWq5Hd1O.d.mts → StructuredFormat-BbN4dosH.d.mts} +11 -4
  10. package/dist/StructuredFormat-BbN4dosH.d.mts.map +1 -0
  11. package/dist/{Tool-DjVufH7i.d.mts → Tool-Y0__Py1H.d.mts} +20 -4
  12. package/dist/Tool-Y0__Py1H.d.mts.map +1 -0
  13. package/dist/Turn-ChbL2foc.d.mts +388 -0
  14. package/dist/Turn-ChbL2foc.d.mts.map +1 -0
  15. package/dist/domain/AiError.d.mts +2 -2
  16. package/dist/domain/AiError.mjs +19 -3
  17. package/dist/domain/AiError.mjs.map +1 -1
  18. package/dist/domain/Audio.d.mts +2 -0
  19. package/dist/domain/Audio.mjs +14 -0
  20. package/dist/domain/Audio.mjs.map +1 -0
  21. package/dist/domain/Image.d.mts +1 -1
  22. package/dist/domain/Items.d.mts +1 -1
  23. package/dist/domain/Items.mjs +1 -1
  24. package/dist/domain/Items.mjs.map +1 -1
  25. package/dist/domain/Music.d.mts +116 -0
  26. package/dist/domain/Music.d.mts.map +1 -0
  27. package/dist/domain/Music.mjs +29 -0
  28. package/dist/domain/Music.mjs.map +1 -0
  29. package/dist/domain/Transcript.d.mts +95 -0
  30. package/dist/domain/Transcript.d.mts.map +1 -0
  31. package/dist/domain/Transcript.mjs +22 -0
  32. package/dist/domain/Transcript.mjs.map +1 -0
  33. package/dist/domain/Turn.d.mts +2 -2
  34. package/dist/domain/Turn.mjs +22 -4
  35. package/dist/domain/Turn.mjs.map +1 -1
  36. package/dist/domain/Turn.test.d.mts +1 -0
  37. package/dist/domain/Turn.test.mjs +136 -0
  38. package/dist/domain/Turn.test.mjs.map +1 -0
  39. package/dist/embedding-model/Embedding.d.mts +15 -3
  40. package/dist/embedding-model/Embedding.d.mts.map +1 -1
  41. package/dist/embedding-model/Embedding.mjs.map +1 -1
  42. package/dist/embedding-model/EmbeddingModel.d.mts +33 -17
  43. package/dist/embedding-model/EmbeddingModel.d.mts.map +1 -1
  44. package/dist/embedding-model/EmbeddingModel.mjs.map +1 -1
  45. package/dist/embedding-model/EmbeddingModel.test.d.mts +1 -0
  46. package/dist/embedding-model/EmbeddingModel.test.mjs +59 -0
  47. package/dist/embedding-model/EmbeddingModel.test.mjs.map +1 -0
  48. package/dist/index.d.mts +13 -7
  49. package/dist/index.mjs +7 -1
  50. package/dist/language-model/LanguageModel.d.mts +30 -8
  51. package/dist/language-model/LanguageModel.d.mts.map +1 -1
  52. package/dist/language-model/LanguageModel.mjs +33 -3
  53. package/dist/language-model/LanguageModel.mjs.map +1 -1
  54. package/dist/language-model/LanguageModel.test.d.mts +1 -0
  55. package/dist/language-model/LanguageModel.test.mjs +143 -0
  56. package/dist/language-model/LanguageModel.test.mjs.map +1 -0
  57. package/dist/loop/Loop.d.mts +94 -11
  58. package/dist/loop/Loop.d.mts.map +1 -1
  59. package/dist/loop/Loop.mjs +92 -26
  60. package/dist/loop/Loop.mjs.map +1 -1
  61. package/dist/loop/Loop.test.mjs +171 -3
  62. package/dist/loop/Loop.test.mjs.map +1 -1
  63. package/dist/music-generator/MusicGenerator.d.mts +77 -0
  64. package/dist/music-generator/MusicGenerator.d.mts.map +1 -0
  65. package/dist/music-generator/MusicGenerator.mjs +51 -0
  66. package/dist/music-generator/MusicGenerator.mjs.map +1 -0
  67. package/dist/music-generator/MusicGenerator.test.d.mts +1 -0
  68. package/dist/music-generator/MusicGenerator.test.mjs +154 -0
  69. package/dist/music-generator/MusicGenerator.test.mjs.map +1 -0
  70. package/dist/observability/Metrics.d.mts +1 -1
  71. package/dist/observability/Metrics.mjs +1 -1
  72. package/dist/observability/Metrics.mjs.map +1 -1
  73. package/dist/speech-synthesizer/SpeechSynthesizer.d.mts +96 -0
  74. package/dist/speech-synthesizer/SpeechSynthesizer.d.mts.map +1 -0
  75. package/dist/speech-synthesizer/SpeechSynthesizer.mjs +48 -0
  76. package/dist/speech-synthesizer/SpeechSynthesizer.mjs.map +1 -0
  77. package/dist/speech-synthesizer/SpeechSynthesizer.test.d.mts +1 -0
  78. package/dist/speech-synthesizer/SpeechSynthesizer.test.mjs +112 -0
  79. package/dist/speech-synthesizer/SpeechSynthesizer.test.mjs.map +1 -0
  80. package/dist/streaming/JSONL.d.mts +10 -3
  81. package/dist/streaming/JSONL.d.mts.map +1 -1
  82. package/dist/streaming/JSONL.mjs +15 -9
  83. package/dist/streaming/JSONL.mjs.map +1 -1
  84. package/dist/structured-format/StructuredFormat.d.mts +2 -2
  85. package/dist/structured-format/StructuredFormat.mjs +9 -1
  86. package/dist/structured-format/StructuredFormat.mjs.map +1 -1
  87. package/dist/structured-format/StructuredFormat.test.d.mts +1 -0
  88. package/dist/structured-format/StructuredFormat.test.mjs +70 -0
  89. package/dist/structured-format/StructuredFormat.test.mjs.map +1 -0
  90. package/dist/testing/MockMusicGenerator.d.mts +39 -0
  91. package/dist/testing/MockMusicGenerator.d.mts.map +1 -0
  92. package/dist/testing/MockMusicGenerator.mjs +96 -0
  93. package/dist/testing/MockMusicGenerator.mjs.map +1 -0
  94. package/dist/testing/MockProvider.d.mts +23 -18
  95. package/dist/testing/MockProvider.d.mts.map +1 -1
  96. package/dist/testing/MockProvider.mjs +56 -72
  97. package/dist/testing/MockProvider.mjs.map +1 -1
  98. package/dist/testing/MockSpeechSynthesizer.d.mts +37 -0
  99. package/dist/testing/MockSpeechSynthesizer.d.mts.map +1 -0
  100. package/dist/testing/MockSpeechSynthesizer.mjs +95 -0
  101. package/dist/testing/MockSpeechSynthesizer.mjs.map +1 -0
  102. package/dist/testing/MockTranscriber.d.mts +37 -0
  103. package/dist/testing/MockTranscriber.d.mts.map +1 -0
  104. package/dist/testing/MockTranscriber.mjs +77 -0
  105. package/dist/testing/MockTranscriber.mjs.map +1 -0
  106. package/dist/tool/HistoryCheck.d.mts +1 -1
  107. package/dist/tool/Outcome.d.mts +1 -1
  108. package/dist/tool/Resolvers.d.mts +65 -8
  109. package/dist/tool/Resolvers.d.mts.map +1 -1
  110. package/dist/tool/Resolvers.mjs +8 -12
  111. package/dist/tool/Resolvers.mjs.map +1 -1
  112. package/dist/tool/Resolvers.test.mjs +6 -5
  113. package/dist/tool/Resolvers.test.mjs.map +1 -1
  114. package/dist/tool/Tool.d.mts +2 -2
  115. package/dist/tool/Tool.mjs +18 -1
  116. package/dist/tool/Tool.mjs.map +1 -1
  117. package/dist/tool/Tool.test.d.mts +1 -0
  118. package/dist/tool/Tool.test.mjs +66 -0
  119. package/dist/tool/Tool.test.mjs.map +1 -0
  120. package/dist/tool/Toolkit.d.mts +4 -6
  121. package/dist/tool/Toolkit.d.mts.map +1 -1
  122. package/dist/tool/Toolkit.mjs +14 -43
  123. package/dist/tool/Toolkit.mjs.map +1 -1
  124. package/dist/transcriber/Transcriber.d.mts +101 -0
  125. package/dist/transcriber/Transcriber.d.mts.map +1 -0
  126. package/dist/transcriber/Transcriber.mjs +49 -0
  127. package/dist/transcriber/Transcriber.mjs.map +1 -0
  128. package/dist/transcriber/Transcriber.test.d.mts +1 -0
  129. package/dist/transcriber/Transcriber.test.mjs +130 -0
  130. package/dist/transcriber/Transcriber.test.mjs.map +1 -0
  131. package/package.json +37 -1
  132. package/src/domain/AiError.ts +22 -1
  133. package/src/domain/Audio.ts +88 -0
  134. package/src/domain/Items.ts +1 -1
  135. package/src/domain/Music.ts +121 -0
  136. package/src/domain/Transcript.ts +83 -0
  137. package/src/domain/Turn.test.ts +141 -0
  138. package/src/domain/Turn.ts +50 -43
  139. package/src/embedding-model/Embedding.ts +23 -0
  140. package/src/embedding-model/EmbeddingModel.test.ts +92 -0
  141. package/src/embedding-model/EmbeddingModel.ts +30 -20
  142. package/src/index.ts +6 -0
  143. package/src/language-model/LanguageModel.test.ts +170 -0
  144. package/src/language-model/LanguageModel.ts +64 -1
  145. package/src/loop/Loop.test.ts +256 -3
  146. package/src/loop/Loop.ts +225 -49
  147. package/src/music-generator/MusicGenerator.test.ts +170 -0
  148. package/src/music-generator/MusicGenerator.ts +123 -0
  149. package/src/observability/Metrics.ts +1 -1
  150. package/src/speech-synthesizer/SpeechSynthesizer.test.ts +141 -0
  151. package/src/speech-synthesizer/SpeechSynthesizer.ts +131 -0
  152. package/src/streaming/JSONL.ts +16 -13
  153. package/src/structured-format/StructuredFormat.test.ts +105 -0
  154. package/src/structured-format/StructuredFormat.ts +14 -1
  155. package/src/testing/MockMusicGenerator.ts +168 -0
  156. package/src/testing/MockProvider.ts +126 -105
  157. package/src/testing/MockSpeechSynthesizer.ts +163 -0
  158. package/src/testing/MockTranscriber.ts +137 -0
  159. package/src/tool/Resolvers.test.ts +8 -5
  160. package/src/tool/Resolvers.ts +17 -19
  161. package/src/tool/Tool.test.ts +105 -0
  162. package/src/tool/Tool.ts +20 -0
  163. package/src/tool/Toolkit.ts +49 -50
  164. package/src/transcriber/Transcriber.test.ts +125 -0
  165. package/src/transcriber/Transcriber.ts +127 -0
  166. package/dist/StructuredFormat-BWq5Hd1O.d.mts.map +0 -1
  167. package/dist/Tool-DjVufH7i.d.mts.map +0 -1
  168. package/dist/Turn-OPaILVIB.d.mts +0 -194
  169. package/dist/Turn-OPaILVIB.d.mts.map +0 -1
@@ -0,0 +1,37 @@
1
+ import { n as AudioChunk, t as AudioBlob } from "../Audio-BfCTGnH3.mjs";
2
+ import { CommonStreamSynthesizeRequest, CommonSynthesizeRequest, SpeechSynthesizer, TtsIncrementalText } from "../speech-synthesizer/SpeechSynthesizer.mjs";
3
+ import { Effect, Layer } from "effect";
4
+
5
+ //#region src/testing/MockSpeechSynthesizer.d.ts
6
+ type MockSynthesizerRecorder = {
7
+ readonly synthesizeCalls: ReadonlyArray<CommonSynthesizeRequest>;
8
+ readonly streamSynthesisCalls: ReadonlyArray<CommonSynthesizeRequest>;
9
+ readonly streamSynthesisFromCalls: ReadonlyArray<CommonStreamSynthesizeRequest>;
10
+ };
11
+ type MockSynthesizerScript = {
12
+ /** One blob per `synthesize` call, consumed in order. */readonly blobs?: ReadonlyArray<AudioBlob>; /** One chunk-list per `streamSynthesis` call, consumed in order. */
13
+ readonly streamSynthesisChunks?: ReadonlyArray<ReadonlyArray<AudioChunk>>; /** One chunk-list per `streamSynthesisFrom` call, consumed in order. */
14
+ readonly streamSynthesisFromChunks?: ReadonlyArray<ReadonlyArray<AudioChunk>>;
15
+ };
16
+ /**
17
+ * Layer providing the `SpeechSynthesizer` service AND the
18
+ * `TtsIncrementalText` capability marker. Use for the common case
19
+ * where code under test exercises `streamSynthesisFrom`.
20
+ */
21
+ declare const layer: (script: MockSynthesizerScript) => {
22
+ readonly layer: Layer.Layer<SpeechSynthesizer | TtsIncrementalText>;
23
+ readonly recorder: Effect.Effect<MockSynthesizerRecorder>;
24
+ };
25
+ /**
26
+ * Variant that omits the `TtsIncrementalText` marker — simulates a
27
+ * provider without incremental-text-in support (e.g. OpenAI, AWS
28
+ * Polly non-Generative). Calls to `streamSynthesisFrom` in code under
29
+ * test should be a compile-time error.
30
+ */
31
+ declare const layerWithoutIncremental: (script: MockSynthesizerScript) => {
32
+ readonly layer: Layer.Layer<SpeechSynthesizer>;
33
+ readonly recorder: Effect.Effect<MockSynthesizerRecorder>;
34
+ };
35
+ //#endregion
36
+ export { MockSynthesizerRecorder, MockSynthesizerScript, layer, layerWithoutIncremental };
37
+ //# sourceMappingURL=MockSpeechSynthesizer.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MockSpeechSynthesizer.d.mts","names":[],"sources":["../../src/testing/MockSpeechSynthesizer.ts"],"mappings":";;;;;KAWY,uBAAA;EAAA,SACD,eAAA,EAAiB,aAAA,CAAc,uBAAA;EAAA,SAC/B,oBAAA,EAAsB,aAAA,CAAc,uBAAA;EAAA,SACpC,wBAAA,EAA0B,aAAA,CAAc,6BAAA;AAAA;AAAA,KAGvC,qBAAA;EALgB,kEAOjB,KAAA,GAAQ,aAAA,CAAc,SAAA,GANA;EAAA,SAQtB,qBAAA,GAAwB,aAAA,CAAc,aAAA,CAAc,UAAA,IAP1B;EAAA,SAS1B,yBAAA,GAA4B,aAAA,CAAc,aAAA,CAAc,UAAA;AAAA;;;;;;cA8EtD,KAAA,GACX,MAAA,EAAQ,qBAAA;EAAA,SAEC,KAAA,EAAO,KAAA,CAAM,KAAA,CAAM,iBAAA,GAAoB,kBAAA;EAAA,SACvC,QAAA,EAAU,MAAA,CAAO,MAAA,CAAO,uBAAA;AAAA;;;AAxFnC;;;;cAuHa,uBAAA,GACX,MAAA,EAAQ,qBAAA;EAAA,SAEC,KAAA,EAAO,KAAA,CAAM,KAAA,CAAM,iBAAA;EAAA,SACnB,QAAA,EAAU,MAAA,CAAO,MAAA,CAAO,uBAAA;AAAA"}
@@ -0,0 +1,95 @@
1
+ import { InvalidRequest } from "../domain/AiError.mjs";
2
+ import { SpeechSynthesizer, TtsIncrementalText } from "../speech-synthesizer/SpeechSynthesizer.mjs";
3
+ import { Effect, Layer, Ref, Stream } from "effect";
4
+ //#region src/testing/MockSpeechSynthesizer.ts
5
+ const makeService = (script, record) => Effect.gen(function* () {
6
+ const bCursor = yield* Ref.make(0);
7
+ const ssCursor = yield* Ref.make(0);
8
+ const ssfCursor = yield* Ref.make(0);
9
+ return {
10
+ synthesize: (request) => Effect.gen(function* () {
11
+ yield* record.synthesize(request);
12
+ const i = yield* Ref.getAndUpdate(bCursor, (n) => n + 1);
13
+ const scripted = script.blobs ?? [];
14
+ if (i >= scripted.length) return yield* new InvalidRequest({
15
+ provider: "mock",
16
+ raw: `MockSpeechSynthesizer exhausted: ${scripted.length} blobs scripted, but call ${i + 1} was made`
17
+ });
18
+ return scripted[i];
19
+ }),
20
+ streamSynthesis: (request) => Stream.unwrap(Effect.gen(function* () {
21
+ yield* record.streamSynthesis(request);
22
+ const i = yield* Ref.getAndUpdate(ssCursor, (n) => n + 1);
23
+ const scripted = script.streamSynthesisChunks ?? [];
24
+ if (i >= scripted.length) return Stream.fail(new InvalidRequest({
25
+ provider: "mock",
26
+ raw: `MockSpeechSynthesizer exhausted: ${scripted.length} streamSynthesis lists scripted, but call ${i + 1} was made`
27
+ }));
28
+ return Stream.fromIterable(scripted[i]);
29
+ })),
30
+ streamSynthesisFrom: (textIn, request) => Stream.unwrap(Effect.gen(function* () {
31
+ yield* record.streamSynthesisFrom(request);
32
+ const i = yield* Ref.getAndUpdate(ssfCursor, (n) => n + 1);
33
+ const scripted = script.streamSynthesisFromChunks ?? [];
34
+ if (i >= scripted.length) return Stream.fail(new InvalidRequest({
35
+ provider: "mock",
36
+ raw: `MockSpeechSynthesizer exhausted: ${scripted.length} streamSynthesisFrom lists scripted, but call ${i + 1} was made`
37
+ }));
38
+ return Stream.drain(textIn).pipe(Stream.concat(Stream.fromIterable(scripted[i])));
39
+ }))
40
+ };
41
+ });
42
+ /**
43
+ * Layer providing the `SpeechSynthesizer` service AND the
44
+ * `TtsIncrementalText` capability marker. Use for the common case
45
+ * where code under test exercises `streamSynthesisFrom`.
46
+ */
47
+ const layer = (script) => {
48
+ const bCalls = Ref.makeUnsafe([]);
49
+ const ssCalls = Ref.makeUnsafe([]);
50
+ const ssfCalls = Ref.makeUnsafe([]);
51
+ const synthesizerLayer = Layer.effect(SpeechSynthesizer, makeService(script, {
52
+ synthesize: (req) => Ref.update(bCalls, (xs) => [...xs, req]),
53
+ streamSynthesis: (req) => Ref.update(ssCalls, (xs) => [...xs, req]),
54
+ streamSynthesisFrom: (req) => Ref.update(ssfCalls, (xs) => [...xs, req])
55
+ }));
56
+ return {
57
+ layer: Layer.merge(synthesizerLayer, Layer.succeed(TtsIncrementalText, void 0)),
58
+ recorder: Effect.gen(function* () {
59
+ return {
60
+ synthesizeCalls: yield* Ref.get(bCalls),
61
+ streamSynthesisCalls: yield* Ref.get(ssCalls),
62
+ streamSynthesisFromCalls: yield* Ref.get(ssfCalls)
63
+ };
64
+ })
65
+ };
66
+ };
67
+ /**
68
+ * Variant that omits the `TtsIncrementalText` marker — simulates a
69
+ * provider without incremental-text-in support (e.g. OpenAI, AWS
70
+ * Polly non-Generative). Calls to `streamSynthesisFrom` in code under
71
+ * test should be a compile-time error.
72
+ */
73
+ const layerWithoutIncremental = (script) => {
74
+ const bCalls = Ref.makeUnsafe([]);
75
+ const ssCalls = Ref.makeUnsafe([]);
76
+ const ssfCalls = Ref.makeUnsafe([]);
77
+ return {
78
+ layer: Layer.effect(SpeechSynthesizer, makeService(script, {
79
+ synthesize: (req) => Ref.update(bCalls, (xs) => [...xs, req]),
80
+ streamSynthesis: (req) => Ref.update(ssCalls, (xs) => [...xs, req]),
81
+ streamSynthesisFrom: (req) => Ref.update(ssfCalls, (xs) => [...xs, req])
82
+ })),
83
+ recorder: Effect.gen(function* () {
84
+ return {
85
+ synthesizeCalls: yield* Ref.get(bCalls),
86
+ streamSynthesisCalls: yield* Ref.get(ssCalls),
87
+ streamSynthesisFromCalls: yield* Ref.get(ssfCalls)
88
+ };
89
+ })
90
+ };
91
+ };
92
+ //#endregion
93
+ export { layer, layerWithoutIncremental };
94
+
95
+ //# sourceMappingURL=MockSpeechSynthesizer.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MockSpeechSynthesizer.mjs","names":["AiError.InvalidRequest"],"sources":["../../src/testing/MockSpeechSynthesizer.ts"],"sourcesContent":["import { Effect, Layer, Ref, Stream } from \"effect\"\nimport type { AudioBlob, AudioChunk } from \"../domain/Audio.js\"\nimport * as AiError from \"../domain/AiError.js\"\nimport {\n SpeechSynthesizer,\n TtsIncrementalText,\n type CommonStreamSynthesizeRequest,\n type CommonSynthesizeRequest,\n type SpeechSynthesizerService,\n} from \"../speech-synthesizer/SpeechSynthesizer.js\"\n\nexport type MockSynthesizerRecorder = {\n readonly synthesizeCalls: ReadonlyArray<CommonSynthesizeRequest>\n readonly streamSynthesisCalls: ReadonlyArray<CommonSynthesizeRequest>\n readonly streamSynthesisFromCalls: ReadonlyArray<CommonStreamSynthesizeRequest>\n}\n\nexport type MockSynthesizerScript = {\n /** One blob per `synthesize` call, consumed in order. */\n readonly blobs?: ReadonlyArray<AudioBlob>\n /** One chunk-list per `streamSynthesis` call, consumed in order. */\n readonly streamSynthesisChunks?: ReadonlyArray<ReadonlyArray<AudioChunk>>\n /** One chunk-list per `streamSynthesisFrom` call, consumed in order. */\n readonly streamSynthesisFromChunks?: ReadonlyArray<ReadonlyArray<AudioChunk>>\n}\n\nconst makeService = (\n script: MockSynthesizerScript,\n record: {\n readonly synthesize: (req: CommonSynthesizeRequest) => Effect.Effect<void>\n readonly streamSynthesis: (req: CommonSynthesizeRequest) => Effect.Effect<void>\n readonly streamSynthesisFrom: (req: CommonStreamSynthesizeRequest) => Effect.Effect<void>\n },\n) =>\n Effect.gen(function* () {\n const bCursor = yield* Ref.make(0)\n const ssCursor = yield* Ref.make(0)\n const ssfCursor = yield* Ref.make(0)\n const service: SpeechSynthesizerService = {\n synthesize: (request) =>\n Effect.gen(function* () {\n yield* record.synthesize(request)\n const i = yield* Ref.getAndUpdate(bCursor, (n) => n + 1)\n const scripted = script.blobs ?? []\n if (i >= scripted.length) {\n return yield* new AiError.InvalidRequest({\n provider: \"mock\",\n raw: `MockSpeechSynthesizer exhausted: ${scripted.length} blobs scripted, but call ${i + 1} was made`,\n })\n }\n return scripted[i]!\n }),\n streamSynthesis: (request) =>\n Stream.unwrap(\n Effect.gen(function* () {\n yield* record.streamSynthesis(request)\n const i = yield* Ref.getAndUpdate(ssCursor, (n) => n + 1)\n const scripted = script.streamSynthesisChunks ?? []\n if (i >= scripted.length) {\n return Stream.fail(\n new AiError.InvalidRequest({\n provider: \"mock\",\n raw: `MockSpeechSynthesizer exhausted: ${scripted.length} streamSynthesis lists scripted, but call ${i + 1} was made`,\n }),\n )\n }\n return Stream.fromIterable(scripted[i]!)\n }),\n ),\n streamSynthesisFrom: <E, R>(\n textIn: Stream.Stream<string, E, R>,\n request: CommonStreamSynthesizeRequest,\n ): Stream.Stream<AudioChunk, AiError.AiError | E, R> =>\n Stream.unwrap(\n Effect.gen(function* () {\n yield* record.streamSynthesisFrom(request)\n const i = yield* Ref.getAndUpdate(ssfCursor, (n) => n + 1)\n const scripted = script.streamSynthesisFromChunks ?? []\n if (i >= scripted.length) {\n const exhausted: Stream.Stream<AudioChunk, AiError.AiError | E, R> = Stream.fail(\n new AiError.InvalidRequest({\n provider: \"mock\",\n raw: `MockSpeechSynthesizer exhausted: ${scripted.length} streamSynthesisFrom lists scripted, but call ${i + 1} was made`,\n }),\n )\n return exhausted\n }\n // Drain the input text fully before emitting scripted audio chunks,\n // so consumers can assert on what text was pushed.\n return Stream.drain(textIn).pipe(Stream.concat(Stream.fromIterable(scripted[i]!)))\n }),\n ),\n }\n return service\n })\n\n/**\n * Layer providing the `SpeechSynthesizer` service AND the\n * `TtsIncrementalText` capability marker. Use for the common case\n * where code under test exercises `streamSynthesisFrom`.\n */\nexport const layer = (\n script: MockSynthesizerScript,\n): {\n readonly layer: Layer.Layer<SpeechSynthesizer | TtsIncrementalText>\n readonly recorder: Effect.Effect<MockSynthesizerRecorder>\n} => {\n const bCalls = Ref.makeUnsafe<ReadonlyArray<CommonSynthesizeRequest>>([])\n const ssCalls = Ref.makeUnsafe<ReadonlyArray<CommonSynthesizeRequest>>([])\n const ssfCalls = Ref.makeUnsafe<ReadonlyArray<CommonStreamSynthesizeRequest>>([])\n const synthesizerLayer = Layer.effect(\n SpeechSynthesizer,\n makeService(script, {\n synthesize: (req) => Ref.update(bCalls, (xs) => [...xs, req]),\n streamSynthesis: (req) => Ref.update(ssCalls, (xs) => [...xs, req]),\n streamSynthesisFrom: (req) => Ref.update(ssfCalls, (xs) => [...xs, req]),\n }),\n )\n const live = Layer.merge(synthesizerLayer, Layer.succeed(TtsIncrementalText, undefined))\n return {\n layer: live,\n recorder: Effect.gen(function* () {\n const synthesizeCalls = yield* Ref.get(bCalls)\n const streamSynthesisCalls = yield* Ref.get(ssCalls)\n const streamSynthesisFromCalls = yield* Ref.get(ssfCalls)\n return { synthesizeCalls, streamSynthesisCalls, streamSynthesisFromCalls }\n }),\n }\n}\n\n/**\n * Variant that omits the `TtsIncrementalText` marker — simulates a\n * provider without incremental-text-in support (e.g. OpenAI, AWS\n * Polly non-Generative). Calls to `streamSynthesisFrom` in code under\n * test should be a compile-time error.\n */\nexport const layerWithoutIncremental = (\n script: MockSynthesizerScript,\n): {\n readonly layer: Layer.Layer<SpeechSynthesizer>\n readonly recorder: Effect.Effect<MockSynthesizerRecorder>\n} => {\n const bCalls = Ref.makeUnsafe<ReadonlyArray<CommonSynthesizeRequest>>([])\n const ssCalls = Ref.makeUnsafe<ReadonlyArray<CommonSynthesizeRequest>>([])\n const ssfCalls = Ref.makeUnsafe<ReadonlyArray<CommonStreamSynthesizeRequest>>([])\n const live = Layer.effect(\n SpeechSynthesizer,\n makeService(script, {\n synthesize: (req) => Ref.update(bCalls, (xs) => [...xs, req]),\n streamSynthesis: (req) => Ref.update(ssCalls, (xs) => [...xs, req]),\n streamSynthesisFrom: (req) => Ref.update(ssfCalls, (xs) => [...xs, req]),\n }),\n )\n return {\n layer: live,\n recorder: Effect.gen(function* () {\n const synthesizeCalls = yield* Ref.get(bCalls)\n const streamSynthesisCalls = yield* Ref.get(ssCalls)\n const streamSynthesisFromCalls = yield* Ref.get(ssfCalls)\n return { synthesizeCalls, streamSynthesisCalls, streamSynthesisFromCalls }\n }),\n }\n}\n"],"mappings":";;;;AA0BA,MAAM,eACJ,QACA,WAMA,OAAO,IAAI,aAAa;CACtB,MAAM,UAAU,OAAO,IAAI,KAAK,EAAE;CAClC,MAAM,WAAW,OAAO,IAAI,KAAK,EAAE;CACnC,MAAM,YAAY,OAAO,IAAI,KAAK,EAAE;AAwDpC,QAAO;EAtDL,aAAa,YACX,OAAO,IAAI,aAAa;AACtB,UAAO,OAAO,WAAW,QAAQ;GACjC,MAAM,IAAI,OAAO,IAAI,aAAa,UAAU,MAAM,IAAI,EAAE;GACxD,MAAM,WAAW,OAAO,SAAS,EAAE;AACnC,OAAI,KAAK,SAAS,OAChB,QAAO,OAAO,IAAIA,eAAuB;IACvC,UAAU;IACV,KAAK,oCAAoC,SAAS,OAAO,4BAA4B,IAAI,EAAE;IAC5F,CAAC;AAEJ,UAAO,SAAS;IAChB;EACJ,kBAAkB,YAChB,OAAO,OACL,OAAO,IAAI,aAAa;AACtB,UAAO,OAAO,gBAAgB,QAAQ;GACtC,MAAM,IAAI,OAAO,IAAI,aAAa,WAAW,MAAM,IAAI,EAAE;GACzD,MAAM,WAAW,OAAO,yBAAyB,EAAE;AACnD,OAAI,KAAK,SAAS,OAChB,QAAO,OAAO,KACZ,IAAIA,eAAuB;IACzB,UAAU;IACV,KAAK,oCAAoC,SAAS,OAAO,4CAA4C,IAAI,EAAE;IAC5G,CAAC,CACH;AAEH,UAAO,OAAO,aAAa,SAAS,GAAI;IACxC,CACH;EACH,sBACE,QACA,YAEA,OAAO,OACL,OAAO,IAAI,aAAa;AACtB,UAAO,OAAO,oBAAoB,QAAQ;GAC1C,MAAM,IAAI,OAAO,IAAI,aAAa,YAAY,MAAM,IAAI,EAAE;GAC1D,MAAM,WAAW,OAAO,6BAA6B,EAAE;AACvD,OAAI,KAAK,SAAS,OAOhB,QANqE,OAAO,KAC1E,IAAIA,eAAuB;IACzB,UAAU;IACV,KAAK,oCAAoC,SAAS,OAAO,gDAAgD,IAAI,EAAE;IAChH,CAAC,CAEY;AAIlB,UAAO,OAAO,MAAM,OAAO,CAAC,KAAK,OAAO,OAAO,OAAO,aAAa,SAAS,GAAI,CAAC,CAAC;IAClF,CACH;EAES;EACd;;;;;;AAOJ,MAAa,SACX,WAIG;CACH,MAAM,SAAS,IAAI,WAAmD,EAAE,CAAC;CACzE,MAAM,UAAU,IAAI,WAAmD,EAAE,CAAC;CAC1E,MAAM,WAAW,IAAI,WAAyD,EAAE,CAAC;CACjF,MAAM,mBAAmB,MAAM,OAC7B,mBACA,YAAY,QAAQ;EAClB,aAAa,QAAQ,IAAI,OAAO,SAAS,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC;EAC7D,kBAAkB,QAAQ,IAAI,OAAO,UAAU,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC;EACnE,sBAAsB,QAAQ,IAAI,OAAO,WAAW,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC;EACzE,CAAC,CACH;AAED,QAAO;EACL,OAFW,MAAM,MAAM,kBAAkB,MAAM,QAAQ,oBAAoB,KAAA,EAAU,CAE1E;EACX,UAAU,OAAO,IAAI,aAAa;AAIhC,UAAO;IAAE,iBAAA,OAHsB,IAAI,IAAI,OAAO;IAGpB,sBAAA,OAFU,IAAI,IAAI,QAAQ;IAEJ,0BAAA,OADR,IAAI,IAAI,SAAS;IACiB;IAC1E;EACH;;;;;;;;AASH,MAAa,2BACX,WAIG;CACH,MAAM,SAAS,IAAI,WAAmD,EAAE,CAAC;CACzE,MAAM,UAAU,IAAI,WAAmD,EAAE,CAAC;CAC1E,MAAM,WAAW,IAAI,WAAyD,EAAE,CAAC;AASjF,QAAO;EACL,OATW,MAAM,OACjB,mBACA,YAAY,QAAQ;GAClB,aAAa,QAAQ,IAAI,OAAO,SAAS,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC;GAC7D,kBAAkB,QAAQ,IAAI,OAAO,UAAU,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC;GACnE,sBAAsB,QAAQ,IAAI,OAAO,WAAW,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC;GACzE,CAAC,CAGS;EACX,UAAU,OAAO,IAAI,aAAa;AAIhC,UAAO;IAAE,iBAAA,OAHsB,IAAI,IAAI,OAAO;IAGpB,sBAAA,OAFU,IAAI,IAAI,QAAQ;IAEJ,0BAAA,OADR,IAAI,IAAI,SAAS;IACiB;IAC1E;EACH"}
@@ -0,0 +1,37 @@
1
+ import { TranscriptEvent, TranscriptResult } from "../domain/Transcript.mjs";
2
+ import { CommonStreamTranscribeRequest, CommonTranscribeRequest, SttStreaming, Transcriber } from "../transcriber/Transcriber.mjs";
3
+ import { Effect, Layer } from "effect";
4
+
5
+ //#region src/testing/MockTranscriber.d.ts
6
+ /**
7
+ * Recorder of every call made to the mock.
8
+ */
9
+ type MockTranscriberRecorder = {
10
+ readonly transcribeCalls: ReadonlyArray<CommonTranscribeRequest>;
11
+ readonly streamCalls: ReadonlyArray<CommonStreamTranscribeRequest>;
12
+ };
13
+ type MockTranscriberScript = {
14
+ /** One result per `transcribe` call, consumed in order. */readonly transcripts?: ReadonlyArray<TranscriptResult>; /** One event-list per `streamTranscriptionFrom` call, consumed in order. */
15
+ readonly streams?: ReadonlyArray<ReadonlyArray<TranscriptEvent>>;
16
+ };
17
+ /**
18
+ * Returns a Layer that provides both the `Transcriber` service and the
19
+ * `SttStreaming` capability marker. Use when the code under test calls
20
+ * `streamTranscriptionFrom`.
21
+ */
22
+ declare const layer: (script: MockTranscriberScript) => {
23
+ readonly layer: Layer.Layer<Transcriber | SttStreaming>;
24
+ readonly recorder: Effect.Effect<MockTranscriberRecorder>;
25
+ };
26
+ /**
27
+ * Variant that omits the `SttStreaming` marker — use to test that
28
+ * consumers calling `streamTranscriptionFrom` fail to compile against
29
+ * a non-streaming provider.
30
+ */
31
+ declare const layerSyncOnly: (script: MockTranscriberScript) => {
32
+ readonly layer: Layer.Layer<Transcriber>;
33
+ readonly recorder: Effect.Effect<MockTranscriberRecorder>;
34
+ };
35
+ //#endregion
36
+ export { MockTranscriberRecorder, MockTranscriberScript, layer, layerSyncOnly };
37
+ //# sourceMappingURL=MockTranscriber.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MockTranscriber.d.mts","names":[],"sources":["../../src/testing/MockTranscriber.ts"],"mappings":";;;;;;;AAcA;KAAY,uBAAA;EAAA,SACD,eAAA,EAAiB,aAAA,CAAc,uBAAA;EAAA,SAC/B,WAAA,EAAa,aAAA,CAAc,6BAAA;AAAA;AAAA,KAG1B,qBAAA;EAHY,oEAKb,WAAA,GAAc,aAAA,CAAc,gBAAA,GALF;EAAA,SAO1B,OAAA,GAAU,aAAA,CAAc,aAAA,CAAc,eAAA;AAAA;;;;;;cA2DpC,KAAA,GACX,MAAA,EAAQ,qBAAA;EAAA,SAEC,KAAA,EAAO,KAAA,CAAM,KAAA,CAAM,WAAA,GAAc,YAAA;EAAA,SACjC,QAAA,EAAU,MAAA,CAAO,MAAA,CAAO,uBAAA;AAAA;;;;;;cA2BtB,aAAA,GACX,MAAA,EAAQ,qBAAA;EAAA,SAEC,KAAA,EAAO,KAAA,CAAM,KAAA,CAAM,WAAA;EAAA,SACnB,QAAA,EAAU,MAAA,CAAO,MAAA,CAAO,uBAAA;AAAA"}
@@ -0,0 +1,77 @@
1
+ import { InvalidRequest } from "../domain/AiError.mjs";
2
+ import { SttStreaming, Transcriber } from "../transcriber/Transcriber.mjs";
3
+ import { Effect, Layer, Ref, Stream } from "effect";
4
+ //#region src/testing/MockTranscriber.ts
5
+ const makeService = (script, record) => Effect.gen(function* () {
6
+ const tCursor = yield* Ref.make(0);
7
+ const sCursor = yield* Ref.make(0);
8
+ return {
9
+ transcribe: (request) => Effect.gen(function* () {
10
+ yield* record.transcribe(request);
11
+ const i = yield* Ref.getAndUpdate(tCursor, (n) => n + 1);
12
+ const scripted = script.transcripts ?? [];
13
+ if (i >= scripted.length) return yield* new InvalidRequest({
14
+ provider: "mock",
15
+ raw: `MockTranscriber exhausted: ${scripted.length} transcripts scripted, but call ${i + 1} was made`
16
+ });
17
+ return scripted[i];
18
+ }),
19
+ streamTranscriptionFrom: (audioIn, request) => Stream.unwrap(Effect.gen(function* () {
20
+ yield* record.stream(request);
21
+ const i = yield* Ref.getAndUpdate(sCursor, (n) => n + 1);
22
+ const scripted = script.streams ?? [];
23
+ if (i >= scripted.length) return Stream.fail(new InvalidRequest({
24
+ provider: "mock",
25
+ raw: `MockTranscriber exhausted: ${scripted.length} streams scripted, but call ${i + 1} was made`
26
+ }));
27
+ return Stream.drain(audioIn).pipe(Stream.concat(Stream.fromIterable(scripted[i])));
28
+ }))
29
+ };
30
+ });
31
+ /**
32
+ * Returns a Layer that provides both the `Transcriber` service and the
33
+ * `SttStreaming` capability marker. Use when the code under test calls
34
+ * `streamTranscriptionFrom`.
35
+ */
36
+ const layer = (script) => {
37
+ const tCalls = Ref.makeUnsafe([]);
38
+ const sCalls = Ref.makeUnsafe([]);
39
+ const transcriberLayer = Layer.effect(Transcriber, makeService(script, {
40
+ transcribe: (req) => Ref.update(tCalls, (xs) => [...xs, req]),
41
+ stream: (req) => Ref.update(sCalls, (xs) => [...xs, req])
42
+ }));
43
+ return {
44
+ layer: Layer.merge(transcriberLayer, Layer.succeed(SttStreaming, void 0)),
45
+ recorder: Effect.gen(function* () {
46
+ return {
47
+ transcribeCalls: yield* Ref.get(tCalls),
48
+ streamCalls: yield* Ref.get(sCalls)
49
+ };
50
+ })
51
+ };
52
+ };
53
+ /**
54
+ * Variant that omits the `SttStreaming` marker — use to test that
55
+ * consumers calling `streamTranscriptionFrom` fail to compile against
56
+ * a non-streaming provider.
57
+ */
58
+ const layerSyncOnly = (script) => {
59
+ const tCalls = Ref.makeUnsafe([]);
60
+ const sCalls = Ref.makeUnsafe([]);
61
+ return {
62
+ layer: Layer.effect(Transcriber, makeService(script, {
63
+ transcribe: (req) => Ref.update(tCalls, (xs) => [...xs, req]),
64
+ stream: (req) => Ref.update(sCalls, (xs) => [...xs, req])
65
+ })),
66
+ recorder: Effect.gen(function* () {
67
+ return {
68
+ transcribeCalls: yield* Ref.get(tCalls),
69
+ streamCalls: yield* Ref.get(sCalls)
70
+ };
71
+ })
72
+ };
73
+ };
74
+ //#endregion
75
+ export { layer, layerSyncOnly };
76
+
77
+ //# sourceMappingURL=MockTranscriber.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MockTranscriber.mjs","names":["AiError.InvalidRequest"],"sources":["../../src/testing/MockTranscriber.ts"],"sourcesContent":["import { Effect, Layer, Ref, Stream } from \"effect\"\nimport * as AiError from \"../domain/AiError.js\"\nimport type { TranscriptEvent, TranscriptResult } from \"../domain/Transcript.js\"\nimport {\n SttStreaming,\n Transcriber,\n type CommonStreamTranscribeRequest,\n type CommonTranscribeRequest,\n type TranscriberService,\n} from \"../transcriber/Transcriber.js\"\n\n/**\n * Recorder of every call made to the mock.\n */\nexport type MockTranscriberRecorder = {\n readonly transcribeCalls: ReadonlyArray<CommonTranscribeRequest>\n readonly streamCalls: ReadonlyArray<CommonStreamTranscribeRequest>\n}\n\nexport type MockTranscriberScript = {\n /** One result per `transcribe` call, consumed in order. */\n readonly transcripts?: ReadonlyArray<TranscriptResult>\n /** One event-list per `streamTranscriptionFrom` call, consumed in order. */\n readonly streams?: ReadonlyArray<ReadonlyArray<TranscriptEvent>>\n}\n\nconst makeService = (\n script: MockTranscriberScript,\n record: {\n readonly transcribe: (req: CommonTranscribeRequest) => Effect.Effect<void>\n readonly stream: (req: CommonStreamTranscribeRequest) => Effect.Effect<void>\n },\n) =>\n Effect.gen(function* () {\n const tCursor = yield* Ref.make(0)\n const sCursor = yield* Ref.make(0)\n const service: TranscriberService = {\n transcribe: (request) =>\n Effect.gen(function* () {\n yield* record.transcribe(request)\n const i = yield* Ref.getAndUpdate(tCursor, (n) => n + 1)\n const scripted = script.transcripts ?? []\n if (i >= scripted.length) {\n return yield* new AiError.InvalidRequest({\n provider: \"mock\",\n raw: `MockTranscriber exhausted: ${scripted.length} transcripts scripted, but call ${i + 1} was made`,\n })\n }\n return scripted[i]!\n }),\n streamTranscriptionFrom: <E, R>(\n audioIn: Stream.Stream<Uint8Array, E, R>,\n request: CommonStreamTranscribeRequest,\n ): Stream.Stream<TranscriptEvent, AiError.AiError | E, R> =>\n Stream.unwrap(\n Effect.gen(function* () {\n yield* record.stream(request)\n const i = yield* Ref.getAndUpdate(sCursor, (n) => n + 1)\n const scripted = script.streams ?? []\n if (i >= scripted.length) {\n const exhausted: Stream.Stream<TranscriptEvent, AiError.AiError | E, R> = Stream.fail(\n new AiError.InvalidRequest({\n provider: \"mock\",\n raw: `MockTranscriber exhausted: ${scripted.length} streams scripted, but call ${i + 1} was made`,\n }),\n )\n return exhausted\n }\n // Drain the input audio fully before emitting the scripted events,\n // so consumers can assert on what bytes were pushed.\n return Stream.drain(audioIn).pipe(Stream.concat(Stream.fromIterable(scripted[i]!)))\n }),\n ),\n }\n return service\n })\n\n/**\n * Returns a Layer that provides both the `Transcriber` service and the\n * `SttStreaming` capability marker. Use when the code under test calls\n * `streamTranscriptionFrom`.\n */\nexport const layer = (\n script: MockTranscriberScript,\n): {\n readonly layer: Layer.Layer<Transcriber | SttStreaming>\n readonly recorder: Effect.Effect<MockTranscriberRecorder>\n} => {\n const tCalls = Ref.makeUnsafe<ReadonlyArray<CommonTranscribeRequest>>([])\n const sCalls = Ref.makeUnsafe<ReadonlyArray<CommonStreamTranscribeRequest>>([])\n const transcriberLayer = Layer.effect(\n Transcriber,\n makeService(script, {\n transcribe: (req) => Ref.update(tCalls, (xs) => [...xs, req]),\n stream: (req) => Ref.update(sCalls, (xs) => [...xs, req]),\n }),\n )\n const live = Layer.merge(transcriberLayer, Layer.succeed(SttStreaming, undefined))\n return {\n layer: live,\n recorder: Effect.gen(function* () {\n const transcribeCalls = yield* Ref.get(tCalls)\n const streamCalls = yield* Ref.get(sCalls)\n return { transcribeCalls, streamCalls }\n }),\n }\n}\n\n/**\n * Variant that omits the `SttStreaming` marker — use to test that\n * consumers calling `streamTranscriptionFrom` fail to compile against\n * a non-streaming provider.\n */\nexport const layerSyncOnly = (\n script: MockTranscriberScript,\n): {\n readonly layer: Layer.Layer<Transcriber>\n readonly recorder: Effect.Effect<MockTranscriberRecorder>\n} => {\n const tCalls = Ref.makeUnsafe<ReadonlyArray<CommonTranscribeRequest>>([])\n const sCalls = Ref.makeUnsafe<ReadonlyArray<CommonStreamTranscribeRequest>>([])\n const live = Layer.effect(\n Transcriber,\n makeService(script, {\n transcribe: (req) => Ref.update(tCalls, (xs) => [...xs, req]),\n stream: (req) => Ref.update(sCalls, (xs) => [...xs, req]),\n }),\n )\n return {\n layer: live,\n recorder: Effect.gen(function* () {\n const transcribeCalls = yield* Ref.get(tCalls)\n const streamCalls = yield* Ref.get(sCalls)\n return { transcribeCalls, streamCalls }\n }),\n }\n}\n"],"mappings":";;;;AA0BA,MAAM,eACJ,QACA,WAKA,OAAO,IAAI,aAAa;CACtB,MAAM,UAAU,OAAO,IAAI,KAAK,EAAE;CAClC,MAAM,UAAU,OAAO,IAAI,KAAK,EAAE;AAuClC,QAAO;EArCL,aAAa,YACX,OAAO,IAAI,aAAa;AACtB,UAAO,OAAO,WAAW,QAAQ;GACjC,MAAM,IAAI,OAAO,IAAI,aAAa,UAAU,MAAM,IAAI,EAAE;GACxD,MAAM,WAAW,OAAO,eAAe,EAAE;AACzC,OAAI,KAAK,SAAS,OAChB,QAAO,OAAO,IAAIA,eAAuB;IACvC,UAAU;IACV,KAAK,8BAA8B,SAAS,OAAO,kCAAkC,IAAI,EAAE;IAC5F,CAAC;AAEJ,UAAO,SAAS;IAChB;EACJ,0BACE,SACA,YAEA,OAAO,OACL,OAAO,IAAI,aAAa;AACtB,UAAO,OAAO,OAAO,QAAQ;GAC7B,MAAM,IAAI,OAAO,IAAI,aAAa,UAAU,MAAM,IAAI,EAAE;GACxD,MAAM,WAAW,OAAO,WAAW,EAAE;AACrC,OAAI,KAAK,SAAS,OAOhB,QAN0E,OAAO,KAC/E,IAAIA,eAAuB;IACzB,UAAU;IACV,KAAK,8BAA8B,SAAS,OAAO,8BAA8B,IAAI,EAAE;IACxF,CAAC,CAEY;AAIlB,UAAO,OAAO,MAAM,QAAQ,CAAC,KAAK,OAAO,OAAO,OAAO,aAAa,SAAS,GAAI,CAAC,CAAC;IACnF,CACH;EAES;EACd;;;;;;AAOJ,MAAa,SACX,WAIG;CACH,MAAM,SAAS,IAAI,WAAmD,EAAE,CAAC;CACzE,MAAM,SAAS,IAAI,WAAyD,EAAE,CAAC;CAC/E,MAAM,mBAAmB,MAAM,OAC7B,aACA,YAAY,QAAQ;EAClB,aAAa,QAAQ,IAAI,OAAO,SAAS,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC;EAC7D,SAAS,QAAQ,IAAI,OAAO,SAAS,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC;EAC1D,CAAC,CACH;AAED,QAAO;EACL,OAFW,MAAM,MAAM,kBAAkB,MAAM,QAAQ,cAAc,KAAA,EAAU,CAEpE;EACX,UAAU,OAAO,IAAI,aAAa;AAGhC,UAAO;IAAE,iBAAA,OAFsB,IAAI,IAAI,OAAO;IAEpB,aAAA,OADC,IAAI,IAAI,OAAO;IACH;IACvC;EACH;;;;;;;AAQH,MAAa,iBACX,WAIG;CACH,MAAM,SAAS,IAAI,WAAmD,EAAE,CAAC;CACzE,MAAM,SAAS,IAAI,WAAyD,EAAE,CAAC;AAQ/E,QAAO;EACL,OARW,MAAM,OACjB,aACA,YAAY,QAAQ;GAClB,aAAa,QAAQ,IAAI,OAAO,SAAS,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC;GAC7D,SAAS,QAAQ,IAAI,OAAO,SAAS,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC;GAC1D,CAAC,CAGS;EACX,UAAU,OAAO,IAAI,aAAa;AAGhC,UAAO;IAAE,iBAAA,OAFsB,IAAI,IAAI,OAAO;IAEpB,aAAA,OADC,IAAI,IAAI,OAAO;IACH;IACvC;EACH"}
@@ -1,4 +1,4 @@
1
- import { d as Item, o as FunctionCall } from "../Items-CB8Bo3FI.mjs";
1
+ import { d as Item, o as FunctionCall } from "../Items-DqbaJoz7.mjs";
2
2
  import { ToolResult } from "./Outcome.mjs";
3
3
 
4
4
  //#region src/tool/HistoryCheck.d.ts
@@ -1,4 +1,4 @@
1
- import { o as FunctionCall, s as FunctionCallOutput } from "../Items-CB8Bo3FI.mjs";
1
+ import { o as FunctionCall, s as FunctionCallOutput } from "../Items-DqbaJoz7.mjs";
2
2
  import { Data } from "effect";
3
3
  import * as _$effect_Unify0 from "effect/Unify";
4
4
 
@@ -1,7 +1,8 @@
1
- import { o as FunctionCall } from "../Items-CB8Bo3FI.mjs";
1
+ import { o as FunctionCall } from "../Items-DqbaJoz7.mjs";
2
2
  import { ToolResult } from "./Outcome.mjs";
3
3
  import { ToolEvent } from "./ToolEvent.mjs";
4
- import { Effect, Queue, Scope, Stream } from "effect";
4
+ import { Data, Effect, Queue, Scope, Stream } from "effect";
5
+ import * as _$effect_Unify0 from "effect/Unify";
5
6
 
6
7
  //#region src/tool/Resolvers.d.ts
7
8
  declare namespace Resolvers_d_exports {
@@ -11,12 +12,68 @@ type ToolCallPlan = {
11
12
  readonly approved: ReadonlyArray<FunctionCall>;
12
13
  readonly rejected: ReadonlyArray<ToolResult>;
13
14
  };
14
- type ToolCallDecision = {
15
- readonly _tag: "Approved";
16
- readonly call: FunctionCall;
17
- } | {
18
- readonly _tag: "Rejected";
19
- readonly result: ToolResult;
15
+ type ToolCallDecision = Data.TaggedEnum<{
16
+ Approved: {
17
+ readonly call: FunctionCall;
18
+ };
19
+ Rejected: {
20
+ readonly result: ToolResult;
21
+ };
22
+ }>;
23
+ declare const ToolCallDecision: {
24
+ readonly Approved: Data.TaggedEnum.ConstructorFrom<{
25
+ readonly _tag: "Approved";
26
+ readonly call: FunctionCall;
27
+ }, "_tag">;
28
+ readonly Rejected: Data.TaggedEnum.ConstructorFrom<{
29
+ readonly _tag: "Rejected";
30
+ readonly result: ToolResult;
31
+ }, "_tag">;
32
+ readonly $is: <Tag extends "Approved" | "Rejected">(tag: Tag) => (u: unknown) => u is Extract<{
33
+ readonly _tag: "Approved";
34
+ readonly call: FunctionCall;
35
+ }, {
36
+ readonly _tag: Tag;
37
+ }> | Extract<{
38
+ readonly _tag: "Rejected";
39
+ readonly result: ToolResult;
40
+ }, {
41
+ readonly _tag: Tag;
42
+ }>;
43
+ readonly $match: {
44
+ <Cases extends {
45
+ readonly Approved: (args: {
46
+ readonly _tag: "Approved";
47
+ readonly call: FunctionCall;
48
+ }) => any;
49
+ readonly Rejected: (args: {
50
+ readonly _tag: "Rejected";
51
+ readonly result: ToolResult;
52
+ }) => any;
53
+ }>(cases: Cases): (value: {
54
+ readonly _tag: "Approved";
55
+ readonly call: FunctionCall;
56
+ } | {
57
+ readonly _tag: "Rejected";
58
+ readonly result: ToolResult;
59
+ }) => _$effect_Unify0.Unify<ReturnType<Cases["Approved" | "Rejected"]>>;
60
+ <Cases extends {
61
+ readonly Approved: (args: {
62
+ readonly _tag: "Approved";
63
+ readonly call: FunctionCall;
64
+ }) => any;
65
+ readonly Rejected: (args: {
66
+ readonly _tag: "Rejected";
67
+ readonly result: ToolResult;
68
+ }) => any;
69
+ }>(value: {
70
+ readonly _tag: "Approved";
71
+ readonly call: FunctionCall;
72
+ } | {
73
+ readonly _tag: "Rejected";
74
+ readonly result: ToolResult;
75
+ }, cases: Cases): _$effect_Unify0.Unify<ReturnType<Cases["Approved" | "Rejected"]>>;
76
+ };
20
77
  };
21
78
  declare const approve: (call: FunctionCall) => ToolCallDecision;
22
79
  declare const reject: (result: ToolResult) => ToolCallDecision;
@@ -1 +1 @@
1
- {"version":3,"file":"Resolvers.d.mts","names":[],"sources":["../../src/tool/Resolvers.ts"],"mappings":";;;;;;;;;KAYY,YAAA;EAAA,SACD,QAAA,EAAU,aAAA,CAAc,YAAA;EAAA,SACxB,QAAA,EAAU,aAAA,CAAc,UAAA;AAAA;AAAA,KAGvB,gBAAA;EAAA,SACG,IAAA;EAAA,SAA2B,IAAA,EAAM,YAAA;AAAA;EAAA,SACjC,IAAA;EAAA,SAA2B,MAAA,EAAQ,UAAA;AAAA;AAAA,cAErC,OAAA,GAAW,IAAA,EAAM,YAAA,KAAe,gBAAA;AAAA,cAKhC,MAAA,GAAU,MAAA,EAAQ,UAAA,KAAa,gBAAA;AAAA,cAK/B,sBAAA,GAA0B,SAAA,EAAW,aAAA,CAAc,gBAAA,MAAoB,YAAA;AAAA,cASvE,iBAAA,GAAqB,IAAA,EAAM,YAAA,KAAe,SAAA;AAAA,KAW3C,OAAA;EAAA,SACD,OAAA;EAAA,SACA,QAAA;EAAA,SACA,MAAA;AAAA;;;;;;cAQE,gBAAA,GACV,SAAA,GAAY,IAAA,EAAM,YAAA,cAA0B,QAAA,EAAU,KAAA,CAAM,OAAA,CAAQ,OAAA,OAEnE,KAAA,EAAO,aAAA,CAAc,YAAA,MACpB,MAAA,CAAO,MAAA;EAAA,SAEG,QAAA,EAAU,aAAA,CAAc,YAAA;EAAA,SACxB,SAAA,EAAW,MAAA,CAAO,MAAA,CAAO,gBAAA;EAAA,SACzB,QAAA,EAAU,MAAA,CAAO,MAAA,CAAO,SAAA;AAAA,UAGnC,KAAA,CAAM,KAAA;AAAA,KAkDE,gBAAA;EAAA,SACG,QAAA;AAAA;EAAA,SACA,QAAA;EAAA,SAA2B,MAAA;AAAA;AAAA,cAE7B,eAAA,GACV,SAAA,GAAY,IAAA,EAAM,YAAA,cAA0B,SAAA,EAAW,WAAA,SAAoB,gBAAA,OAC3E,KAAA,EAAO,aAAA,CAAc,YAAA,MAAgB,YAAA"}
1
+ {"version":3,"file":"Resolvers.d.mts","names":[],"sources":["../../src/tool/Resolvers.ts"],"mappings":";;;;;;;;;;KAYY,YAAA;EAAA,SACD,QAAA,EAAU,aAAA,CAAc,YAAA;EAAA,SACxB,QAAA,EAAU,aAAA,CAAc,UAAA;AAAA;AAAA,KAGvB,gBAAA,GAAmB,IAAA,CAAK,UAAA;EAClC,QAAA;IAAA,SAAqB,IAAA,EAAM,YAAA;EAAA;EAC3B,QAAA;IAAA,SAAqB,MAAA,EAAQ,UAAA;EAAA;AAAA;AAAA,cAGlB,gBAAA;EAAA;;mBAJgB,YAAA;EAAA;EAAA;;qBACE,UAAA;EAAA;EAAA;;mBADF,YAAA;EAAA;IAAA;;;qBACE,UAAA;EAAA;IAAA;;;;;;uBADF,YAAA;MAAA;MAAA;;yBACE,UAAA;MAAA;IAAA;;qBADF,YAAA;IAAA;MAAA;uBACE,UAAA;IAAA;;;;uBADF,YAAA;MAAA;MAAA;;yBACE,UAAA;MAAA;IAAA;;qBADF,YAAA;IAAA;MAAA;uBACE,UAAA;IAAA;;;cAKlB,OAAA,GAAW,IAAA,EAAM,YAAA,KAAe,gBAAA;AAAA,cAEhC,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,YAAA,KAAe,SAAA;AAAA,KAW3C,OAAA;EAAA,SACD,OAAA;EAAA,SACA,QAAA;EAAA,SACA,MAAA;AAAA;;;;;;cAQE,gBAAA,GACV,SAAA,GAAY,IAAA,EAAM,YAAA,cAA0B,QAAA,EAAU,KAAA,CAAM,OAAA,CAAQ,OAAA,OAEnE,KAAA,EAAO,aAAA,CAAc,YAAA,MACpB,MAAA,CAAO,MAAA;EAAA,SAEG,QAAA,EAAU,aAAA,CAAc,YAAA;EAAA,SACxB,SAAA,EAAW,MAAA,CAAO,MAAA,CAAO,gBAAA;EAAA,SACzB,QAAA,EAAU,MAAA,CAAO,MAAA,CAAO,SAAA;AAAA,UAGnC,KAAA,CAAM,KAAA;AAAA,KAkDE,gBAAA;EAAA,SACG,QAAA;AAAA;EAAA,SACA,QAAA;EAAA,SAA2B,MAAA;AAAA;AAAA,cAE7B,eAAA,GACV,SAAA,GAAY,IAAA,EAAM,YAAA,cAA0B,SAAA,EAAW,WAAA,SAAoB,gBAAA,OAC3E,KAAA,EAAO,aAAA,CAAc,YAAA,MAAgB,YAAA"}
@@ -1,6 +1,7 @@
1
1
  import { n as __exportAll } from "../chunk-uyGKjUfl.mjs";
2
2
  import { cancelled, denied } from "./Outcome.mjs";
3
- import { Deferred, Effect, Queue, Stream } from "effect";
3
+ import { ToolEvent } from "./ToolEvent.mjs";
4
+ import { Data, Deferred, Effect, Queue, Stream } from "effect";
4
5
  //#region src/tool/Resolvers.ts
5
6
  /**
6
7
  * Approval helpers for the two transport flavors.
@@ -10,6 +11,7 @@ import { Deferred, Effect, Queue, Stream } from "effect";
10
11
  * the recipe boundary via `Toolkit.executeAll`.
11
12
  */
12
13
  var Resolvers_exports = /* @__PURE__ */ __exportAll({
14
+ ToolCallDecision: () => ToolCallDecision,
13
15
  approvalRequested: () => approvalRequested,
14
16
  approve: () => approve,
15
17
  fromApprovalMap: () => fromApprovalMap,
@@ -17,14 +19,9 @@ var Resolvers_exports = /* @__PURE__ */ __exportAll({
17
19
  reject: () => reject,
18
20
  splitToolCallDecisions: () => splitToolCallDecisions
19
21
  });
20
- const approve = (call) => ({
21
- _tag: "Approved",
22
- call
23
- });
24
- const reject = (result) => ({
25
- _tag: "Rejected",
26
- result
27
- });
22
+ const ToolCallDecision = Data.taggedEnum();
23
+ const approve = (call) => ToolCallDecision.Approved({ call });
24
+ const reject = (result) => ToolCallDecision.Rejected({ result });
28
25
  const splitToolCallDecisions = (decisions) => decisions.reduce((acc, decision) => decision._tag === "Approved" ? {
29
26
  ...acc,
30
27
  approved: [...acc.approved, decision.call]
@@ -35,8 +32,7 @@ const splitToolCallDecisions = (decisions) => decisions.reduce((acc, decision) =
35
32
  approved: [],
36
33
  rejected: []
37
34
  });
38
- const approvalRequested = (call) => ({
39
- _tag: "ApprovalRequested",
35
+ const approvalRequested = (call) => ToolEvent.ApprovalRequested({
40
36
  call_id: call.call_id,
41
37
  tool: call.name,
42
38
  arguments: call.arguments
@@ -72,6 +68,6 @@ const fromApprovalMap = (predicate, approvals) => (calls) => splitToolCallDecisi
72
68
  return v.decision === "approve" ? approve(call) : reject(denied(call, v.reason));
73
69
  }));
74
70
  //#endregion
75
- export { approvalRequested, approve, fromApprovalMap, fromVerdictQueue, reject, splitToolCallDecisions, Resolvers_exports as t };
71
+ export { ToolCallDecision, approvalRequested, approve, fromApprovalMap, fromVerdictQueue, reject, splitToolCallDecisions, Resolvers_exports as t };
76
72
 
77
73
  //# sourceMappingURL=Resolvers.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Resolvers.mjs","names":[],"sources":["../../src/tool/Resolvers.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.executeAll`.\n */\nimport { Deferred, Effect, Queue, Scope, Stream } from \"effect\"\nimport type { FunctionCall } from \"../domain/Items.js\"\nimport { type ToolResult, cancelled, denied } from \"./Outcome.js\"\nimport type { ToolEvent } from \"./ToolEvent.js\"\n\nexport type ToolCallPlan = {\n readonly approved: ReadonlyArray<FunctionCall>\n readonly rejected: ReadonlyArray<ToolResult>\n}\n\nexport type ToolCallDecision =\n | { readonly _tag: \"Approved\"; readonly call: FunctionCall }\n | { readonly _tag: \"Rejected\"; readonly result: ToolResult }\n\nexport const approve = (call: FunctionCall): ToolCallDecision => ({\n _tag: \"Approved\",\n call,\n})\n\nexport const reject = (result: ToolResult): ToolCallDecision => ({\n _tag: \"Rejected\",\n result,\n})\n\nexport const splitToolCallDecisions = (decisions: ReadonlyArray<ToolCallDecision>): 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: FunctionCall): ToolEvent => ({\n _tag: \"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 `ToolCallDecision` when their matching verdict arrives.\n */\nexport const fromVerdictQueue =\n (predicate: (call: FunctionCall) => boolean, verdicts: Queue.Dequeue<Verdict>) =>\n (\n calls: ReadonlyArray<FunctionCall>,\n ): Effect.Effect<\n {\n readonly approved: ReadonlyArray<FunctionCall>\n readonly decisions: Stream.Stream<ToolCallDecision>\n readonly announce: 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 announce = Stream.fromIterable<ToolEvent>(gated.map(approvalRequested))\n\n return { approved, decisions, announce }\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 fromApprovalMap =\n (predicate: (call: FunctionCall) => boolean, approvals: ReadonlyMap<string, ApprovalMapEntry>) =>\n (calls: ReadonlyArray<FunctionCall>): ToolCallPlan =>\n splitToolCallDecisions(\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":";;;;;;;;;;;;;;;;;;;AAqBA,MAAa,WAAW,UAA0C;CAChE,MAAM;CACN;CACD;AAED,MAAa,UAAU,YAA0C;CAC/D,MAAM;CACN;CACD;AAED,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,UAAmC;CACnE,MAAM;CACN,SAAS,KAAK;CACd,MAAM,KAAK;CACX,WAAW,KAAK;CACjB;;;;;;AAiBD,MAAa,oBACV,WAA4C,cAE3C,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,UAFb,OAAO,aAAwB,MAAM,IAAI,kBAAkB,CAEtC;EAAE;EACxC;AAWN,MAAa,mBACV,WAA4C,eAC5C,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":"Resolvers.mjs","names":[],"sources":["../../src/tool/Resolvers.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.executeAll`.\n */\nimport { Data, Deferred, Effect, Queue, Scope, Stream } from \"effect\"\nimport type { FunctionCall } from \"../domain/Items.js\"\nimport { type ToolResult, cancelled, denied } from \"./Outcome.js\"\nimport { ToolEvent } from \"./ToolEvent.js\"\n\nexport type ToolCallPlan = {\n readonly approved: ReadonlyArray<FunctionCall>\n readonly rejected: ReadonlyArray<ToolResult>\n}\n\nexport type ToolCallDecision = Data.TaggedEnum<{\n Approved: { readonly call: FunctionCall }\n Rejected: { readonly result: ToolResult }\n}>\n\nexport const ToolCallDecision = Data.taggedEnum<ToolCallDecision>()\n\nexport const approve = (call: FunctionCall): ToolCallDecision => ToolCallDecision.Approved({ call })\n\nexport const reject = (result: ToolResult): ToolCallDecision =>\n ToolCallDecision.Rejected({ result })\n\nexport const splitToolCallDecisions = (decisions: ReadonlyArray<ToolCallDecision>): 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: FunctionCall): 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 `ToolCallDecision` when their matching verdict arrives.\n */\nexport const fromVerdictQueue =\n (predicate: (call: FunctionCall) => boolean, verdicts: Queue.Dequeue<Verdict>) =>\n (\n calls: ReadonlyArray<FunctionCall>,\n ): Effect.Effect<\n {\n readonly approved: ReadonlyArray<FunctionCall>\n readonly decisions: Stream.Stream<ToolCallDecision>\n readonly announce: 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 announce = Stream.fromIterable<ToolEvent>(gated.map(approvalRequested))\n\n return { approved, decisions, announce }\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 fromApprovalMap =\n (predicate: (call: FunctionCall) => boolean, approvals: ReadonlyMap<string, ApprovalMapEntry>) =>\n (calls: ReadonlyArray<FunctionCall>): ToolCallPlan =>\n splitToolCallDecisions(\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,SAAyC,iBAAiB,SAAS,EAAE,MAAM,CAAC;AAEpG,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,oBACV,WAA4C,cAE3C,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,UAFb,OAAO,aAAwB,MAAM,IAAI,kBAAkB,CAEtC;EAAE;EACxC;AAWN,MAAa,mBACV,WAA4C,eAC5C,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,8 +1,8 @@
1
1
  import { functionCallOutput, userText } from "../domain/Items.mjs";
2
2
  import { fromEffectSchema, make, streaming } from "./Tool.mjs";
3
3
  import { isFailure, isValue, toFunctionCallOutput } from "./Outcome.mjs";
4
- import { isApprovalRequested, isIntermediate, isOutput } from "./ToolEvent.mjs";
5
- import { executeAll, outputEvent, outputEvents } from "./Toolkit.mjs";
4
+ import { ToolEvent, isApprovalRequested, isIntermediate, isOutput } from "./ToolEvent.mjs";
5
+ import { executeAll } from "./Toolkit.mjs";
6
6
  import { fromApprovalMap, fromVerdictQueue } from "./Resolvers.mjs";
7
7
  import { cancelAllPending, findUnansweredCalls, isReconciled } from "./HistoryCheck.mjs";
8
8
  import { i as it, n as globalExpect, r as describe } from "../dist-DV5ISja1.mjs";
@@ -86,11 +86,12 @@ const calls = [
86
86
  ];
87
87
  const resultsFrom = (collected) => collected.filter(isOutput).map((e) => e.result);
88
88
  const byCallId = (results) => new Map(results.map((r) => [r.call_id, r]));
89
+ const rejectedStream = (rejected) => Stream.fromIterable(rejected.map((result) => ToolEvent.Output({ result })));
89
90
  const eventsFromApprovalMap = (approvals) => {
90
91
  const plan = fromApprovalMap(isSensitive, approvals)(calls);
91
- return Stream.merge(executeAll(allTools, plan.approved), outputEvents(plan.rejected));
92
+ return Stream.merge(executeAll(allTools, plan.approved), rejectedStream(plan.rejected));
92
93
  };
93
- const eventsFromDecision = (decision) => decision._tag === "Approved" ? executeAll(allTools, [decision.call]) : Stream.succeed(outputEvent(decision.result));
94
+ const eventsFromDecision = (decision) => decision._tag === "Approved" ? executeAll(allTools, [decision.call]) : Stream.succeed(ToolEvent.Output({ result: decision.result }));
94
95
  describe("fromApprovalMap + executeAll", () => {
95
96
  it("approval: all gated approved → tools execute, structured Values", async () => {
96
97
  const approvals = new Map([["c2", { decision: "approve" }], ["c3", { decision: "approve" }]]);
@@ -179,7 +180,7 @@ describe("executeAll: graceful degradation", () => {
179
180
  ];
180
181
  const by = byCallId(resultsFrom(await Effect.runPromise(Stream.runCollect((() => {
181
182
  const plan = fromApprovalMap(isSensitive, new Map([["c3", { decision: "approve" }]]))(callsWithBogus);
182
- return Stream.merge(executeAll(allTools, plan.approved), outputEvents(plan.rejected));
183
+ return Stream.merge(executeAll(allTools, plan.approved), rejectedStream(plan.rejected));
183
184
  })()))));
184
185
  globalExpect(by.get("c1")).toMatchObject({ _tag: "Value" });
185
186
  globalExpect(by.get("c2")).toMatchObject({