@effect-uai/core 0.3.0 → 0.4.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 (97) hide show
  1. package/dist/{AiError-CBuPHVKA.d.mts → AiError-csR8Bhxx.d.mts} +26 -4
  2. package/dist/{AiError-CBuPHVKA.d.mts.map → AiError-csR8Bhxx.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-DxyXqzAM.d.mts} +4 -4
  6. package/dist/{Image-BZmKfIdq.d.mts.map → Image-DxyXqzAM.d.mts.map} +1 -1
  7. package/dist/{Items-CB8Bo3FI.d.mts → Items-Hg5AsYxl.d.mts} +5 -5
  8. package/dist/{Items-CB8Bo3FI.d.mts.map → Items-Hg5AsYxl.d.mts.map} +1 -1
  9. package/dist/{StructuredFormat-BWq5Hd1O.d.mts → StructuredFormat-Cl41C56K.d.mts} +1 -1
  10. package/dist/{StructuredFormat-BWq5Hd1O.d.mts.map → StructuredFormat-Cl41C56K.d.mts.map} +1 -1
  11. package/dist/{Tool-DjVufH7i.d.mts → Tool-B8B5qVEy.d.mts} +2 -2
  12. package/dist/{Tool-DjVufH7i.d.mts.map → Tool-B8B5qVEy.d.mts.map} +1 -1
  13. package/dist/{Turn-OPaILVIB.d.mts → Turn-7geUcKsf.d.mts} +4 -4
  14. package/dist/{Turn-OPaILVIB.d.mts.map → Turn-7geUcKsf.d.mts.map} +1 -1
  15. package/dist/domain/AiError.d.mts +2 -2
  16. package/dist/domain/AiError.mjs +18 -2
  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/Music.d.mts +116 -0
  24. package/dist/domain/Music.d.mts.map +1 -0
  25. package/dist/domain/Music.mjs +29 -0
  26. package/dist/domain/Music.mjs.map +1 -0
  27. package/dist/domain/Transcript.d.mts +95 -0
  28. package/dist/domain/Transcript.d.mts.map +1 -0
  29. package/dist/domain/Transcript.mjs +22 -0
  30. package/dist/domain/Transcript.mjs.map +1 -0
  31. package/dist/domain/Turn.d.mts +1 -1
  32. package/dist/embedding-model/Embedding.d.mts +1 -1
  33. package/dist/embedding-model/EmbeddingModel.d.mts +1 -1
  34. package/dist/index.d.mts +13 -7
  35. package/dist/index.mjs +7 -1
  36. package/dist/language-model/LanguageModel.d.mts +5 -5
  37. package/dist/loop/Loop.d.mts +2 -2
  38. package/dist/music-generator/MusicGenerator.d.mts +77 -0
  39. package/dist/music-generator/MusicGenerator.d.mts.map +1 -0
  40. package/dist/music-generator/MusicGenerator.mjs +51 -0
  41. package/dist/music-generator/MusicGenerator.mjs.map +1 -0
  42. package/dist/music-generator/MusicGenerator.test.d.mts +1 -0
  43. package/dist/music-generator/MusicGenerator.test.mjs +154 -0
  44. package/dist/music-generator/MusicGenerator.test.mjs.map +1 -0
  45. package/dist/speech-synthesizer/SpeechSynthesizer.d.mts +96 -0
  46. package/dist/speech-synthesizer/SpeechSynthesizer.d.mts.map +1 -0
  47. package/dist/speech-synthesizer/SpeechSynthesizer.mjs +48 -0
  48. package/dist/speech-synthesizer/SpeechSynthesizer.mjs.map +1 -0
  49. package/dist/speech-synthesizer/SpeechSynthesizer.test.d.mts +1 -0
  50. package/dist/speech-synthesizer/SpeechSynthesizer.test.mjs +112 -0
  51. package/dist/speech-synthesizer/SpeechSynthesizer.test.mjs.map +1 -0
  52. package/dist/streaming/JSONL.d.mts +10 -3
  53. package/dist/streaming/JSONL.d.mts.map +1 -1
  54. package/dist/streaming/JSONL.mjs +12 -1
  55. package/dist/streaming/JSONL.mjs.map +1 -1
  56. package/dist/structured-format/StructuredFormat.d.mts +1 -1
  57. package/dist/testing/MockMusicGenerator.d.mts +39 -0
  58. package/dist/testing/MockMusicGenerator.d.mts.map +1 -0
  59. package/dist/testing/MockMusicGenerator.mjs +96 -0
  60. package/dist/testing/MockMusicGenerator.mjs.map +1 -0
  61. package/dist/testing/MockProvider.d.mts +2 -2
  62. package/dist/testing/MockSpeechSynthesizer.d.mts +37 -0
  63. package/dist/testing/MockSpeechSynthesizer.d.mts.map +1 -0
  64. package/dist/testing/MockSpeechSynthesizer.mjs +95 -0
  65. package/dist/testing/MockSpeechSynthesizer.mjs.map +1 -0
  66. package/dist/testing/MockTranscriber.d.mts +37 -0
  67. package/dist/testing/MockTranscriber.d.mts.map +1 -0
  68. package/dist/testing/MockTranscriber.mjs +77 -0
  69. package/dist/testing/MockTranscriber.mjs.map +1 -0
  70. package/dist/tool/HistoryCheck.d.mts +1 -1
  71. package/dist/tool/Outcome.d.mts +1 -1
  72. package/dist/tool/Resolvers.d.mts +1 -1
  73. package/dist/tool/Tool.d.mts +1 -1
  74. package/dist/tool/Toolkit.d.mts +2 -2
  75. package/dist/transcriber/Transcriber.d.mts +101 -0
  76. package/dist/transcriber/Transcriber.d.mts.map +1 -0
  77. package/dist/transcriber/Transcriber.mjs +49 -0
  78. package/dist/transcriber/Transcriber.mjs.map +1 -0
  79. package/dist/transcriber/Transcriber.test.d.mts +1 -0
  80. package/dist/transcriber/Transcriber.test.mjs +130 -0
  81. package/dist/transcriber/Transcriber.test.mjs.map +1 -0
  82. package/package.json +37 -1
  83. package/src/domain/AiError.ts +21 -0
  84. package/src/domain/Audio.ts +88 -0
  85. package/src/domain/Music.ts +121 -0
  86. package/src/domain/Transcript.ts +83 -0
  87. package/src/index.ts +6 -0
  88. package/src/music-generator/MusicGenerator.test.ts +170 -0
  89. package/src/music-generator/MusicGenerator.ts +123 -0
  90. package/src/speech-synthesizer/SpeechSynthesizer.test.ts +141 -0
  91. package/src/speech-synthesizer/SpeechSynthesizer.ts +131 -0
  92. package/src/streaming/JSONL.ts +12 -0
  93. package/src/testing/MockMusicGenerator.ts +170 -0
  94. package/src/testing/MockSpeechSynthesizer.ts +165 -0
  95. package/src/testing/MockTranscriber.ts +139 -0
  96. package/src/transcriber/Transcriber.test.ts +125 -0
  97. package/src/transcriber/Transcriber.ts +127 -0
@@ -0,0 +1,96 @@
1
+ import { t as AiError } from "../AiError-csR8Bhxx.mjs";
2
+ import { n as AudioChunk, r as AudioFormat, t as AudioBlob } from "../Audio-BfCTGnH3.mjs";
3
+ import { Context, Effect, Stream } from "effect";
4
+
5
+ //#region src/speech-synthesizer/SpeechSynthesizer.d.ts
6
+ declare namespace SpeechSynthesizer_d_exports {
7
+ export { CommonStreamSynthesizeRequest, CommonSynthesizeRequest, SpeechSynthesizer, SpeechSynthesizerService, TtsIncrementalText, streamSynthesis, streamSynthesisFrom, synthesize };
8
+ }
9
+ /**
10
+ * Cross-provider synthesis request. Provider-specific extensions
11
+ * (ElevenLabs `stability` / `similarity_boost`, Cartesia `emotion`,
12
+ * MiniMax `vol` / `pitch`, Azure SSML style tags) live on each
13
+ * provider's typed request which extends this and narrows `model` and
14
+ * `voiceId`.
15
+ */
16
+ type CommonSynthesizeRequest = {
17
+ readonly text: string; /** Model identifier. Each provider narrows. */
18
+ readonly model: string;
19
+ /**
20
+ * Voice identifier. Per-provider request types narrow this to a
21
+ * typed literal union of stock voices + `(string & {})` escape for
22
+ * custom cloned voice IDs. Providers without custom-voice support
23
+ * (OpenAI, Deepgram Aura, AWS Polly) narrow to the stock-only union.
24
+ */
25
+ readonly voiceId: string;
26
+ readonly outputFormat?: AudioFormat;
27
+ readonly speed?: number;
28
+ readonly languageCode?: string;
29
+ };
30
+ /**
31
+ * Incremental-synthesis request — text arrives as `Stream<string>`.
32
+ * Gated by the `TtsIncrementalText` capability marker; only providers
33
+ * that ship the marker can be used.
34
+ *
35
+ * Multi-context features (Cartesia `context_id`, ElevenLabs `multi-
36
+ * stream-input`) are NOT exposed here — one logical utterance per
37
+ * call. Provider extensions can expose `forkContext` for that.
38
+ */
39
+ type CommonStreamSynthesizeRequest = Omit<CommonSynthesizeRequest, "text">;
40
+ type SpeechSynthesizerService = {
41
+ /** One-shot. Full text in, full audio bytes out. Universally supported. */readonly synthesize: (request: CommonSynthesizeRequest) => Effect.Effect<AudioBlob, AiError>;
42
+ /**
43
+ * Full text in, audio chunks streamed out (chunked HTTP). Universally
44
+ * supported across providers that offer any streaming TTS at all.
45
+ */
46
+ readonly streamSynthesis: (request: CommonSynthesizeRequest) => Stream.Stream<AudioChunk, AiError>;
47
+ /**
48
+ * Incremental text in (as a Stream), audio chunks streamed out. The
49
+ * underlying WS connection is acquired on first pull and released
50
+ * when the output stream is finalized via `Stream.scoped`.
51
+ *
52
+ * Gated by the `TtsIncrementalText` capability marker on the top-
53
+ * level helper — providers without WS-style incremental input don't
54
+ * ship the marker, so calls fail at `Effect.provide` with a type
55
+ * error.
56
+ */
57
+ readonly streamSynthesisFrom: <E, R>(textIn: Stream.Stream<string, E, R>, request: CommonStreamSynthesizeRequest) => Stream.Stream<AudioChunk, AiError | E, R>;
58
+ };
59
+ declare const SpeechSynthesizer_base: Context.ServiceClass<SpeechSynthesizer, "@betalyra/effect-uai/SpeechSynthesizer", SpeechSynthesizerService>;
60
+ declare class SpeechSynthesizer extends SpeechSynthesizer_base {}
61
+ declare const TtsIncrementalText_base: Context.ServiceClass<TtsIncrementalText, "@betalyra/effect-uai/capability/TtsIncrementalText", void>;
62
+ /**
63
+ * Capability marker — provided by provider layers whose
64
+ * `streamSynthesisFrom` is wired up at the wire level. OpenAI, Azure
65
+ * (wire), and AWS Polly non-Generative do not ship it. Calling
66
+ * `streamSynthesisFrom` while only one of those Layers is in scope
67
+ * fails at `Effect.provide` with a type error.
68
+ *
69
+ * Phantom — the value is `void`; providers register with
70
+ * `Layer.succeed(TtsIncrementalText, undefined)`.
71
+ */
72
+ declare class TtsIncrementalText extends TtsIncrementalText_base {}
73
+ /** One-shot synthesis. */
74
+ declare const synthesize: (request: CommonSynthesizeRequest) => Effect.Effect<AudioBlob, AiError, SpeechSynthesizer>;
75
+ /** Full text in, audio chunks out. */
76
+ declare const streamSynthesis: (request: CommonSynthesizeRequest) => Stream.Stream<AudioChunk, AiError, SpeechSynthesizer>;
77
+ /**
78
+ * Incremental synthesis. Dual-arity: pipeable (data-last) and direct
79
+ * (data-first). Requires `TtsIncrementalText` in R — providers without
80
+ * incremental-text-in support are a type error at provide time.
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * const audio = LanguageModel.streamTurn(turnReq).pipe(
85
+ * Stream.filterMap(Turn.toTextDelta),
86
+ * SpeechSynthesizer.streamSynthesisFrom(synthReq),
87
+ * )
88
+ * ```
89
+ */
90
+ declare const streamSynthesisFrom: {
91
+ (request: CommonStreamSynthesizeRequest): <E, R>(textIn: Stream.Stream<string, E, R>) => Stream.Stream<AudioChunk, AiError | E, R | SpeechSynthesizer | TtsIncrementalText>;
92
+ <E, R>(textIn: Stream.Stream<string, E, R>, request: CommonStreamSynthesizeRequest): Stream.Stream<AudioChunk, AiError | E, R | SpeechSynthesizer | TtsIncrementalText>;
93
+ };
94
+ //#endregion
95
+ export { CommonStreamSynthesizeRequest, CommonSynthesizeRequest, SpeechSynthesizer, SpeechSynthesizerService, TtsIncrementalText, streamSynthesis, streamSynthesisFrom, synthesize, SpeechSynthesizer_d_exports as t };
96
+ //# sourceMappingURL=SpeechSynthesizer.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SpeechSynthesizer.d.mts","names":[],"sources":["../../src/speech-synthesizer/SpeechSynthesizer.ts"],"mappings":";;;;;;;;;;;;;;;KAWY,uBAAA;EAAA,SACD,IAAA;WAEA,KAAA;;;AAHX;;;;WAUW,OAAA;EAAA,SACA,YAAA,GAAe,WAAA;EAAA,SACf,KAAA;EAAA,SACA,YAAA;AAAA;;;;;AAYX;;;;;KAAY,6BAAA,GAAgC,IAAA,CAAK,uBAAA;AAAA,KAErC,wBAAA;EAAwB,oFAEzB,UAAA,GACP,OAAA,EAAS,uBAAA,KACN,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,OAAA;EAAX;;;;EAAA,SAKV,eAAA,GACP,OAAA,EAAS,uBAAA,KACN,MAAA,CAAO,MAAA,CAAO,UAAA,EAAY,OAAA;EAAA;;;;;;;;;;EAAA,SAWtB,mBAAA,SACP,MAAA,EAAQ,MAAA,CAAO,MAAA,SAAe,CAAA,EAAG,CAAA,GACjC,OAAA,EAAS,6BAAA,KACN,MAAA,CAAO,MAAA,CAAO,UAAA,EAAY,OAAA,GAAkB,CAAA,EAAG,CAAA;AAAA;AAAA,cACrD,sBAAA;cAEY,iBAAA,SAA0B,sBAAA;AAAA,cAGS,uBAAA;;;;;;;;;;;cAYnC,kBAAA,SAA2B,uBAAA;;cAK3B,UAAA,GACX,OAAA,EAAS,uBAAA,KACR,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,OAAA,EAAiB,iBAAA;;cAIhC,eAAA,GACX,OAAA,EAAS,uBAAA,KACR,MAAA,CAAO,MAAA,CAAO,UAAA,EAAY,OAAA,EAAiB,iBAAA;;;;;;;;;;;;;;cAgBjC,mBAAA;EAAA,CAET,OAAA,EAAS,6BAAA,UAET,MAAA,EAAQ,MAAA,CAAO,MAAA,SAAe,CAAA,EAAG,CAAA,MAC9B,MAAA,CAAO,MAAA,CAAO,UAAA,EAAY,OAAA,GAAkB,CAAA,EAAG,CAAA,GAAI,iBAAA,GAAoB,kBAAA;EAAA,OAE1E,MAAA,EAAQ,MAAA,CAAO,MAAA,SAAe,CAAA,EAAG,CAAA,GACjC,OAAA,EAAS,6BAAA,GACR,MAAA,CAAO,MAAA,CAAO,UAAA,EAAY,OAAA,GAAkB,CAAA,EAAG,CAAA,GAAI,iBAAA,GAAoB,kBAAA;AAAA"}
@@ -0,0 +1,48 @@
1
+ import { n as __exportAll } from "../chunk-uyGKjUfl.mjs";
2
+ import { Context, Effect, Function, Stream } from "effect";
3
+ //#region src/speech-synthesizer/SpeechSynthesizer.ts
4
+ var SpeechSynthesizer_exports = /* @__PURE__ */ __exportAll({
5
+ SpeechSynthesizer: () => SpeechSynthesizer,
6
+ TtsIncrementalText: () => TtsIncrementalText,
7
+ streamSynthesis: () => streamSynthesis,
8
+ streamSynthesisFrom: () => streamSynthesisFrom,
9
+ synthesize: () => synthesize
10
+ });
11
+ var SpeechSynthesizer = class extends Context.Service()("@betalyra/effect-uai/SpeechSynthesizer") {};
12
+ /**
13
+ * Capability marker — provided by provider layers whose
14
+ * `streamSynthesisFrom` is wired up at the wire level. OpenAI, Azure
15
+ * (wire), and AWS Polly non-Generative do not ship it. Calling
16
+ * `streamSynthesisFrom` while only one of those Layers is in scope
17
+ * fails at `Effect.provide` with a type error.
18
+ *
19
+ * Phantom — the value is `void`; providers register with
20
+ * `Layer.succeed(TtsIncrementalText, undefined)`.
21
+ */
22
+ var TtsIncrementalText = class extends Context.Service()("@betalyra/effect-uai/capability/TtsIncrementalText") {};
23
+ /** One-shot synthesis. */
24
+ const synthesize = (request) => Effect.flatMap(SpeechSynthesizer.asEffect(), (s) => s.synthesize(request));
25
+ /** Full text in, audio chunks out. */
26
+ const streamSynthesis = (request) => Stream.unwrap(Effect.map(SpeechSynthesizer.asEffect(), (s) => s.streamSynthesis(request)));
27
+ /**
28
+ * Incremental synthesis. Dual-arity: pipeable (data-last) and direct
29
+ * (data-first). Requires `TtsIncrementalText` in R — providers without
30
+ * incremental-text-in support are a type error at provide time.
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * const audio = LanguageModel.streamTurn(turnReq).pipe(
35
+ * Stream.filterMap(Turn.toTextDelta),
36
+ * SpeechSynthesizer.streamSynthesisFrom(synthReq),
37
+ * )
38
+ * ```
39
+ */
40
+ const streamSynthesisFrom = Function.dual(2, (textIn, request) => Stream.unwrap(Effect.gen(function* () {
41
+ const s = yield* SpeechSynthesizer.asEffect();
42
+ yield* TtsIncrementalText.asEffect();
43
+ return s.streamSynthesisFrom(textIn, request);
44
+ })));
45
+ //#endregion
46
+ export { SpeechSynthesizer, TtsIncrementalText, streamSynthesis, streamSynthesisFrom, synthesize, SpeechSynthesizer_exports as t };
47
+
48
+ //# sourceMappingURL=SpeechSynthesizer.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SpeechSynthesizer.mjs","names":[],"sources":["../../src/speech-synthesizer/SpeechSynthesizer.ts"],"sourcesContent":["import { Context, Effect, Function, Stream } from \"effect\"\nimport * as AiError from \"../domain/AiError.js\"\nimport type { AudioBlob, AudioChunk, AudioFormat } from \"../domain/Audio.js\"\n\n/**\n * Cross-provider synthesis request. Provider-specific extensions\n * (ElevenLabs `stability` / `similarity_boost`, Cartesia `emotion`,\n * MiniMax `vol` / `pitch`, Azure SSML style tags) live on each\n * provider's typed request which extends this and narrows `model` and\n * `voiceId`.\n */\nexport type CommonSynthesizeRequest = {\n readonly text: string\n /** Model identifier. Each provider narrows. */\n readonly model: string\n /**\n * Voice identifier. Per-provider request types narrow this to a\n * typed literal union of stock voices + `(string & {})` escape for\n * custom cloned voice IDs. Providers without custom-voice support\n * (OpenAI, Deepgram Aura, AWS Polly) narrow to the stock-only union.\n */\n readonly voiceId: string\n readonly outputFormat?: AudioFormat\n readonly speed?: number\n readonly languageCode?: string\n}\n\n/**\n * Incremental-synthesis request — text arrives as `Stream<string>`.\n * Gated by the `TtsIncrementalText` capability marker; only providers\n * that ship the marker can be used.\n *\n * Multi-context features (Cartesia `context_id`, ElevenLabs `multi-\n * stream-input`) are NOT exposed here — one logical utterance per\n * call. Provider extensions can expose `forkContext` for that.\n */\nexport type CommonStreamSynthesizeRequest = Omit<CommonSynthesizeRequest, \"text\">\n\nexport type SpeechSynthesizerService = {\n /** One-shot. Full text in, full audio bytes out. Universally supported. */\n readonly synthesize: (\n request: CommonSynthesizeRequest,\n ) => Effect.Effect<AudioBlob, AiError.AiError>\n /**\n * Full text in, audio chunks streamed out (chunked HTTP). Universally\n * supported across providers that offer any streaming TTS at all.\n */\n readonly streamSynthesis: (\n request: CommonSynthesizeRequest,\n ) => Stream.Stream<AudioChunk, AiError.AiError>\n /**\n * Incremental text in (as a Stream), audio chunks streamed out. The\n * underlying WS connection is acquired on first pull and released\n * when the output stream is finalized via `Stream.scoped`.\n *\n * Gated by the `TtsIncrementalText` capability marker on the top-\n * level helper — providers without WS-style incremental input don't\n * ship the marker, so calls fail at `Effect.provide` with a type\n * error.\n */\n readonly streamSynthesisFrom: <E, R>(\n textIn: Stream.Stream<string, E, R>,\n request: CommonStreamSynthesizeRequest,\n ) => Stream.Stream<AudioChunk, AiError.AiError | E, R>\n}\n\nexport class SpeechSynthesizer extends Context.Service<\n SpeechSynthesizer,\n SpeechSynthesizerService\n>()(\"@betalyra/effect-uai/SpeechSynthesizer\") {}\n\n/**\n * Capability marker — provided by provider layers whose\n * `streamSynthesisFrom` is wired up at the wire level. OpenAI, Azure\n * (wire), and AWS Polly non-Generative do not ship it. Calling\n * `streamSynthesisFrom` while only one of those Layers is in scope\n * fails at `Effect.provide` with a type error.\n *\n * Phantom — the value is `void`; providers register with\n * `Layer.succeed(TtsIncrementalText, undefined)`.\n */\nexport class TtsIncrementalText extends Context.Service<TtsIncrementalText, void>()(\n \"@betalyra/effect-uai/capability/TtsIncrementalText\",\n) {}\n\n/** One-shot synthesis. */\nexport const synthesize = (\n request: CommonSynthesizeRequest,\n): Effect.Effect<AudioBlob, AiError.AiError, SpeechSynthesizer> =>\n Effect.flatMap(SpeechSynthesizer.asEffect(), (s) => s.synthesize(request))\n\n/** Full text in, audio chunks out. */\nexport const streamSynthesis = (\n request: CommonSynthesizeRequest,\n): Stream.Stream<AudioChunk, AiError.AiError, SpeechSynthesizer> =>\n Stream.unwrap(Effect.map(SpeechSynthesizer.asEffect(), (s) => s.streamSynthesis(request)))\n\n/**\n * Incremental synthesis. Dual-arity: pipeable (data-last) and direct\n * (data-first). Requires `TtsIncrementalText` in R — providers without\n * incremental-text-in support are a type error at provide time.\n *\n * @example\n * ```ts\n * const audio = LanguageModel.streamTurn(turnReq).pipe(\n * Stream.filterMap(Turn.toTextDelta),\n * SpeechSynthesizer.streamSynthesisFrom(synthReq),\n * )\n * ```\n */\nexport const streamSynthesisFrom: {\n (\n request: CommonStreamSynthesizeRequest,\n ): <E, R>(\n textIn: Stream.Stream<string, E, R>,\n ) => Stream.Stream<AudioChunk, AiError.AiError | E, R | SpeechSynthesizer | TtsIncrementalText>\n <E, R>(\n textIn: Stream.Stream<string, E, R>,\n request: CommonStreamSynthesizeRequest,\n ): Stream.Stream<AudioChunk, AiError.AiError | E, R | SpeechSynthesizer | TtsIncrementalText>\n} = Function.dual(\n 2,\n <E, R>(textIn: Stream.Stream<string, E, R>, request: CommonStreamSynthesizeRequest) =>\n Stream.unwrap(\n Effect.gen(function* () {\n const s = yield* SpeechSynthesizer.asEffect()\n yield* TtsIncrementalText.asEffect()\n return s.streamSynthesisFrom(textIn, request)\n }),\n ),\n)\n"],"mappings":";;;;;;;;;;AAkEA,IAAa,oBAAb,cAAuC,QAAQ,SAG5C,CAAC,yCAAyC,CAAC;;;;;;;;;;;AAY9C,IAAa,qBAAb,cAAwC,QAAQ,SAAmC,CACjF,qDACD,CAAC;;AAGF,MAAa,cACX,YAEA,OAAO,QAAQ,kBAAkB,UAAU,GAAG,MAAM,EAAE,WAAW,QAAQ,CAAC;;AAG5E,MAAa,mBACX,YAEA,OAAO,OAAO,OAAO,IAAI,kBAAkB,UAAU,GAAG,MAAM,EAAE,gBAAgB,QAAQ,CAAC,CAAC;;;;;;;;;;;;;;AAe5F,MAAa,sBAUT,SAAS,KACX,IACO,QAAqC,YAC1C,OAAO,OACL,OAAO,IAAI,aAAa;CACtB,MAAM,IAAI,OAAO,kBAAkB,UAAU;AAC7C,QAAO,mBAAmB,UAAU;AACpC,QAAO,EAAE,oBAAoB,QAAQ,QAAQ;EAC7C,CACH,CACJ"}
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,112 @@
1
+ import { streamSynthesis, streamSynthesisFrom, synthesize } from "./SpeechSynthesizer.mjs";
2
+ import { i as it, n as globalExpect, r as describe, t as import_dist } from "../dist-DV5ISja1.mjs";
3
+ import { layer, layerWithoutIncremental } from "../testing/MockSpeechSynthesizer.mjs";
4
+ import { Effect, Stream } from "effect";
5
+ //#region src/speech-synthesizer/SpeechSynthesizer.test.ts
6
+ const blob = {
7
+ format: {
8
+ container: "raw",
9
+ encoding: "pcm_s16le",
10
+ sampleRate: 24e3
11
+ },
12
+ bytes: new Uint8Array([
13
+ 222,
14
+ 173,
15
+ 190,
16
+ 239
17
+ ]),
18
+ durationSeconds: .5
19
+ };
20
+ const chunk = (n) => ({ bytes: new Uint8Array([n]) });
21
+ describe("SpeechSynthesizer.synthesize", () => {
22
+ it("returns the scripted AudioBlob", async () => {
23
+ const mock = layer({ blobs: [blob] });
24
+ const program = synthesize({
25
+ text: "hi",
26
+ model: "mock-tts",
27
+ voiceId: "stock-voice"
28
+ });
29
+ const result = await Effect.runPromise(program.pipe(Effect.provide(mock.layer)));
30
+ globalExpect(result.bytes).toEqual(blob.bytes);
31
+ globalExpect(result.durationSeconds).toBe(.5);
32
+ });
33
+ });
34
+ describe("SpeechSynthesizer.streamSynthesis", () => {
35
+ it("emits scripted chunks for full-text-in streaming", async () => {
36
+ const mock = layer({ streamSynthesisChunks: [[
37
+ chunk(1),
38
+ chunk(2),
39
+ chunk(3)
40
+ ]] });
41
+ const program = Stream.runCollect(streamSynthesis({
42
+ text: "hi",
43
+ model: "mock-tts",
44
+ voiceId: "stock-voice"
45
+ }));
46
+ globalExpect((await Effect.runPromise(program.pipe(Effect.provide(mock.layer)))).map((c) => Array.from(c.bytes))).toEqual([
47
+ [1],
48
+ [2],
49
+ [3]
50
+ ]);
51
+ });
52
+ });
53
+ describe("SpeechSynthesizer capability marker (compile-time)", () => {
54
+ const ssfReq = {
55
+ model: "mock-tts",
56
+ voiceId: "v"
57
+ };
58
+ it("requires `TtsIncrementalText` on the R channel of streamSynthesisFrom", () => {
59
+ (0, import_dist.expectTypeOf)(Stream.fromIterable(["a"]).pipe(streamSynthesisFrom(ssfReq))).toEqualTypeOf();
60
+ });
61
+ it("does NOT require `TtsIncrementalText` for sync `synthesize`", () => {
62
+ (0, import_dist.expectTypeOf)(synthesize({
63
+ text: "hi",
64
+ model: "m",
65
+ voiceId: "v"
66
+ })).toEqualTypeOf();
67
+ });
68
+ it("does NOT require `TtsIncrementalText` for full-text `streamSynthesis`", () => {
69
+ (0, import_dist.expectTypeOf)(streamSynthesis({
70
+ text: "hi",
71
+ model: "m",
72
+ voiceId: "v"
73
+ })).toEqualTypeOf();
74
+ });
75
+ it("a layer without the marker leaves `TtsIncrementalText` unsatisfied in R", () => {
76
+ const noMarker = layerWithoutIncremental({});
77
+ const audio = Stream.fromIterable(["a"]).pipe(streamSynthesisFrom(ssfReq));
78
+ (0, import_dist.expectTypeOf)(Stream.runDrain(audio).pipe(Effect.provide(noMarker.layer))).toEqualTypeOf();
79
+ });
80
+ it("a full layer (with marker) clears R to never", () => {
81
+ const fullMock = layer({ streamSynthesisFromChunks: [[]] });
82
+ const audio = Stream.fromIterable(["a"]).pipe(streamSynthesisFrom(ssfReq));
83
+ (0, import_dist.expectTypeOf)(Stream.runDrain(audio).pipe(Effect.provide(fullMock.layer))).toEqualTypeOf();
84
+ });
85
+ });
86
+ describe("SpeechSynthesizer.streamSynthesisFrom", () => {
87
+ const ssfReq = {
88
+ model: "mock-tts",
89
+ voiceId: "stock-voice"
90
+ };
91
+ it("pipes an LLM-style text stream into audio chunks", async () => {
92
+ const mock = layer({ streamSynthesisFromChunks: [[chunk(10), chunk(20)]] });
93
+ const audio = Stream.fromIterable(["Hello, ", "world."]).pipe(streamSynthesisFrom(ssfReq));
94
+ globalExpect((await Effect.runPromise(Stream.runCollect(audio).pipe(Effect.provide(mock.layer)))).map((c) => Array.from(c.bytes))).toEqual([[10], [20]]);
95
+ });
96
+ it("records the request on the streamSynthesisFrom call channel", async () => {
97
+ const mock = layer({ streamSynthesisFromChunks: [[chunk(42)]] });
98
+ const program = Effect.gen(function* () {
99
+ yield* Stream.runDrain(Stream.fromIterable(["x"]).pipe(streamSynthesisFrom(ssfReq)));
100
+ return yield* mock.recorder;
101
+ });
102
+ const rec = await Effect.runPromise(program.pipe(Effect.provide(mock.layer)));
103
+ globalExpect(rec.streamSynthesisFromCalls.length).toBe(1);
104
+ globalExpect(rec.streamSynthesisFromCalls[0].voiceId).toBe("stock-voice");
105
+ globalExpect(rec.synthesizeCalls.length).toBe(0);
106
+ globalExpect(rec.streamSynthesisCalls.length).toBe(0);
107
+ });
108
+ });
109
+ //#endregion
110
+ export {};
111
+
112
+ //# sourceMappingURL=SpeechSynthesizer.test.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SpeechSynthesizer.test.mjs","names":["MockSpeechSynthesizer.layer","SpeechSynthesizer.synthesize","SpeechSynthesizer.streamSynthesis","SpeechSynthesizer.streamSynthesisFrom","MockSpeechSynthesizer.layerWithoutIncremental"],"sources":["../../src/speech-synthesizer/SpeechSynthesizer.test.ts"],"sourcesContent":["import { Effect, Stream } from \"effect\"\nimport { describe, expect, expectTypeOf, it } from \"vitest\"\nimport type * as AiError from \"../domain/AiError.js\"\nimport type { AudioBlob, AudioChunk, AudioFormat } from \"../domain/Audio.js\"\nimport * as MockSpeechSynthesizer from \"../testing/MockSpeechSynthesizer.js\"\nimport * as SpeechSynthesizer from \"./SpeechSynthesizer.js\"\n\nconst pcmFormat: AudioFormat = {\n container: \"raw\",\n encoding: \"pcm_s16le\",\n sampleRate: 24000,\n}\n\nconst blob: AudioBlob = {\n format: pcmFormat,\n bytes: new Uint8Array([0xde, 0xad, 0xbe, 0xef]),\n durationSeconds: 0.5,\n}\n\nconst chunk = (n: number): AudioChunk => ({ bytes: new Uint8Array([n]) })\n\ndescribe(\"SpeechSynthesizer.synthesize\", () => {\n it(\"returns the scripted AudioBlob\", async () => {\n const mock = MockSpeechSynthesizer.layer({ blobs: [blob] })\n const program = SpeechSynthesizer.synthesize({\n text: \"hi\",\n model: \"mock-tts\",\n voiceId: \"stock-voice\",\n })\n const result = await Effect.runPromise(program.pipe(Effect.provide(mock.layer)))\n expect(result.bytes).toEqual(blob.bytes)\n expect(result.durationSeconds).toBe(0.5)\n })\n})\n\ndescribe(\"SpeechSynthesizer.streamSynthesis\", () => {\n it(\"emits scripted chunks for full-text-in streaming\", async () => {\n const mock = MockSpeechSynthesizer.layer({\n streamSynthesisChunks: [[chunk(1), chunk(2), chunk(3)]],\n })\n const program = Stream.runCollect(\n SpeechSynthesizer.streamSynthesis({\n text: \"hi\",\n model: \"mock-tts\",\n voiceId: \"stock-voice\",\n }),\n )\n const out = await Effect.runPromise(program.pipe(Effect.provide(mock.layer)))\n expect(out.map((c) => Array.from(c.bytes))).toEqual([[1], [2], [3]])\n })\n})\n\ndescribe(\"SpeechSynthesizer capability marker (compile-time)\", () => {\n const ssfReq: SpeechSynthesizer.CommonStreamSynthesizeRequest = {\n model: \"mock-tts\",\n voiceId: \"v\",\n }\n\n it(\"requires `TtsIncrementalText` on the R channel of streamSynthesisFrom\", () => {\n const tokens: Stream.Stream<string> = Stream.fromIterable([\"a\"])\n const audio = tokens.pipe(SpeechSynthesizer.streamSynthesisFrom(ssfReq))\n expectTypeOf(audio).toEqualTypeOf<\n Stream.Stream<\n AudioChunk,\n AiError.AiError,\n SpeechSynthesizer.SpeechSynthesizer | SpeechSynthesizer.TtsIncrementalText\n >\n >()\n })\n\n it(\"does NOT require `TtsIncrementalText` for sync `synthesize`\", () => {\n const eff = SpeechSynthesizer.synthesize({ text: \"hi\", model: \"m\", voiceId: \"v\" })\n expectTypeOf(eff).toEqualTypeOf<\n Effect.Effect<AudioBlob, AiError.AiError, SpeechSynthesizer.SpeechSynthesizer>\n >()\n })\n\n it(\"does NOT require `TtsIncrementalText` for full-text `streamSynthesis`\", () => {\n const audio = SpeechSynthesizer.streamSynthesis({ text: \"hi\", model: \"m\", voiceId: \"v\" })\n expectTypeOf(audio).toEqualTypeOf<\n Stream.Stream<AudioChunk, AiError.AiError, SpeechSynthesizer.SpeechSynthesizer>\n >()\n })\n\n it(\"a layer without the marker leaves `TtsIncrementalText` unsatisfied in R\", () => {\n const noMarker = MockSpeechSynthesizer.layerWithoutIncremental({})\n const tokens: Stream.Stream<string> = Stream.fromIterable([\"a\"])\n const audio = tokens.pipe(SpeechSynthesizer.streamSynthesisFrom(ssfReq))\n const program = Stream.runDrain(audio).pipe(Effect.provide(noMarker.layer))\n // `SpeechSynthesizer` is provided by the layer; `TtsIncrementalText` is not,\n // so it remains in R — calling `Effect.runPromise(program)` would be a type\n // error because runPromise requires `R = never`.\n expectTypeOf(program).toEqualTypeOf<\n Effect.Effect<void, AiError.AiError, SpeechSynthesizer.TtsIncrementalText>\n >()\n })\n\n it(\"a full layer (with marker) clears R to never\", () => {\n const fullMock = MockSpeechSynthesizer.layer({\n streamSynthesisFromChunks: [[]],\n })\n const tokens: Stream.Stream<string> = Stream.fromIterable([\"a\"])\n const audio = tokens.pipe(SpeechSynthesizer.streamSynthesisFrom(ssfReq))\n const program = Stream.runDrain(audio).pipe(Effect.provide(fullMock.layer))\n expectTypeOf(program).toEqualTypeOf<Effect.Effect<void, AiError.AiError, never>>()\n })\n})\n\ndescribe(\"SpeechSynthesizer.streamSynthesisFrom\", () => {\n const ssfReq: SpeechSynthesizer.CommonStreamSynthesizeRequest = {\n model: \"mock-tts\",\n voiceId: \"stock-voice\",\n }\n\n it(\"pipes an LLM-style text stream into audio chunks\", async () => {\n const mock = MockSpeechSynthesizer.layer({\n streamSynthesisFromChunks: [[chunk(10), chunk(20)]],\n })\n const tokens = Stream.fromIterable([\"Hello, \", \"world.\"])\n const audio = tokens.pipe(SpeechSynthesizer.streamSynthesisFrom(ssfReq))\n const out = await Effect.runPromise(Stream.runCollect(audio).pipe(Effect.provide(mock.layer)))\n expect(out.map((c) => Array.from(c.bytes))).toEqual([[10], [20]])\n })\n\n it(\"records the request on the streamSynthesisFrom call channel\", async () => {\n const mock = MockSpeechSynthesizer.layer({\n streamSynthesisFromChunks: [[chunk(42)]],\n })\n const program = Effect.gen(function* () {\n yield* Stream.runDrain(\n Stream.fromIterable([\"x\"]).pipe(SpeechSynthesizer.streamSynthesisFrom(ssfReq)),\n )\n return yield* mock.recorder\n })\n const rec = await Effect.runPromise(program.pipe(Effect.provide(mock.layer)))\n expect(rec.streamSynthesisFromCalls.length).toBe(1)\n expect(rec.streamSynthesisFromCalls[0]!.voiceId).toBe(\"stock-voice\")\n expect(rec.synthesizeCalls.length).toBe(0)\n expect(rec.streamSynthesisCalls.length).toBe(0)\n })\n})\n"],"mappings":"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"}
@@ -1,10 +1,10 @@
1
- import { Schema, Stream } from "effect";
1
+ import { Effect, Schema, Stream } from "effect";
2
2
  import * as _$effect_Types0 from "effect/Types";
3
3
  import * as _$effect_Cause0 from "effect/Cause";
4
4
 
5
5
  //#region src/streaming/JSONL.d.ts
6
6
  declare namespace JSONL_d_exports {
7
- export { JsonParseError, fromBytes, parse, toBytes };
7
+ export { JsonParseError, fromBytes, parse, parseSafe, toBytes };
8
8
  }
9
9
  declare const JsonParseError_base: new <A extends Record<string, any> = {}>(args: _$effect_Types0.VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => _$effect_Cause0.YieldableError & {
10
10
  readonly _tag: "JsonParseError";
@@ -24,11 +24,18 @@ declare const fromBytes: <E, R>(self: Stream.Stream<Uint8Array, E, R>) => Stream
24
24
  * uniformly.
25
25
  */
26
26
  declare const parse: <A, I>(schema: Schema.Codec<A, I>) => <E, R>(self: Stream.Stream<string, E, R>) => Stream.Stream<A, JsonParseError | E, R>;
27
+ /**
28
+ * Best-effort parse of a single JSON frame. Returns the parsed value or
29
+ * `undefined` on malformed input. Realtime WS adapters use this to skip
30
+ * non-JSON or partially-received frames silently rather than fail the
31
+ * entire session over one bad frame.
32
+ */
33
+ declare const parseSafe: (raw: string) => Effect.Effect<unknown, never, never>;
27
34
  /**
28
35
  * Serialize a stream of values to JSONL bytes. Encodes each value via
29
36
  * `Schema.encodeUnknownSync`. Each line ends with `\n`.
30
37
  */
31
38
  declare const toBytes: <A, I>(schema: Schema.Codec<A, I>) => <E, R>(self: Stream.Stream<A, E, R>) => Stream.Stream<Uint8Array, E, R>;
32
39
  //#endregion
33
- export { JsonParseError, fromBytes, parse, JSONL_d_exports as t, toBytes };
40
+ export { JsonParseError, fromBytes, parse, parseSafe, JSONL_d_exports as t, toBytes };
34
41
  //# sourceMappingURL=JSONL.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"JSONL.d.mts","names":[],"sources":["../../src/streaming/JSONL.ts"],"mappings":";;;;;;;;cAAqD,mBAAA;;;cAExC,cAAA,SAAuB,mBAAA;EAAA,SACzB,IAAA;EAAA,SACA,KAAA;AAAA;;;;AAJ0C;cAgDxC,SAAA,SACX,IAAA,EAAM,MAAA,CAAO,MAAA,CAAO,UAAA,EAAY,CAAA,EAAG,CAAA,MAClC,MAAA,CAAO,MAAA,SAAe,CAAA,EAAG,CAAA;;;;;;cAaf,KAAA,SACJ,MAAA,EAAQ,MAAA,CAAO,KAAA,CAAM,CAAA,EAAG,CAAA,aACxB,IAAA,EAAM,MAAA,CAAO,MAAA,SAAe,CAAA,EAAG,CAAA,MAAK,MAAA,CAAO,MAAA,CAAO,CAAA,EAAG,cAAA,GAAiB,CAAA,EAAG,CAAA;;;;;cAsBrE,OAAA,SACJ,MAAA,EAAQ,MAAA,CAAO,KAAA,CAAM,CAAA,EAAG,CAAA,aACxB,IAAA,EAAM,MAAA,CAAO,MAAA,CAAO,CAAA,EAAG,CAAA,EAAG,CAAA,MAAK,MAAA,CAAO,MAAA,CAAO,UAAA,EAAY,CAAA,EAAG,CAAA"}
1
+ {"version":3,"file":"JSONL.d.mts","names":[],"sources":["../../src/streaming/JSONL.ts"],"mappings":";;;;;;;;cAAqD,mBAAA;;;cAExC,cAAA,SAAuB,mBAAA;EAAA,SACzB,IAAA;EAAA,SACA,KAAA;AAAA;;;;;cA4CE,SAAA,SACX,IAAA,EAAM,MAAA,CAAO,MAAA,CAAO,UAAA,EAAY,CAAA,EAAG,CAAA,MAClC,MAAA,CAAO,MAAA,SAAe,CAAA,EAAG,CAAA;;;;;;cAaf,KAAA,SACJ,MAAA,EAAQ,MAAA,CAAO,KAAA,CAAM,CAAA,EAAG,CAAA,aACxB,IAAA,EAAM,MAAA,CAAO,MAAA,SAAe,CAAA,EAAG,CAAA,MAAK,MAAA,CAAO,MAAA,CAAO,CAAA,EAAG,cAAA,GAAiB,CAAA,EAAG,CAAA;;;;;;;cAsBrE,SAAA,GAAa,GAAA,aAAW,MAAA,CAAA,MAAA;;;;;cAYxB,OAAA,SACJ,MAAA,EAAQ,MAAA,CAAO,KAAA,CAAM,CAAA,EAAG,CAAA,aACxB,IAAA,EAAM,MAAA,CAAO,MAAA,CAAO,CAAA,EAAG,CAAA,EAAG,CAAA,MAAK,MAAA,CAAO,MAAA,CAAO,UAAA,EAAY,CAAA,EAAG,CAAA"}
@@ -5,6 +5,7 @@ var JSONL_exports = /* @__PURE__ */ __exportAll({
5
5
  JsonParseError: () => JsonParseError,
6
6
  fromBytes: () => fromBytes,
7
7
  parse: () => parse,
8
+ parseSafe: () => parseSafe,
8
9
  toBytes: () => toBytes
9
10
  });
10
11
  var JsonParseError = class extends Data.TaggedError("JsonParseError") {};
@@ -36,6 +37,16 @@ const parse = (schema) => (self) => self.pipe(Stream.mapEffect((line) => Effect.
36
37
  line,
37
38
  cause
38
39
  })))))));
40
+ /**
41
+ * Best-effort parse of a single JSON frame. Returns the parsed value or
42
+ * `undefined` on malformed input. Realtime WS adapters use this to skip
43
+ * non-JSON or partially-received frames silently rather than fail the
44
+ * entire session over one bad frame.
45
+ */
46
+ const parseSafe = (raw) => Effect.try({
47
+ try: () => JSON.parse(raw),
48
+ catch: () => void 0
49
+ }).pipe(Effect.orElseSucceed(() => void 0));
39
50
  const encoder = new TextEncoder();
40
51
  /**
41
52
  * Serialize a stream of values to JSONL bytes. Encodes each value via
@@ -46,6 +57,6 @@ const toBytes = (schema) => (self) => self.pipe(Stream.map((value) => {
46
57
  return encoder.encode(JSON.stringify(encoded) + "\n");
47
58
  }));
48
59
  //#endregion
49
- export { JsonParseError, fromBytes, parse, JSONL_exports as t, toBytes };
60
+ export { JsonParseError, fromBytes, parse, parseSafe, JSONL_exports as t, toBytes };
50
61
 
51
62
  //# sourceMappingURL=JSONL.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"JSONL.mjs","names":[],"sources":["../../src/streaming/JSONL.ts"],"sourcesContent":["import { Data, Effect, Schema, Stream } from \"effect\"\n\nexport class JsonParseError extends Data.TaggedError(\"JsonParseError\")<{\n readonly line: string\n readonly cause: unknown\n}> {}\n\n// ---------------------------------------------------------------------------\n// Generic stream helpers (kept module-local; see SSE.ts for the same shape).\n// ---------------------------------------------------------------------------\n\nconst decodeText = <E, R>(self: Stream.Stream<Uint8Array, E, R>): Stream.Stream<string, E, R> =>\n self.pipe(\n Stream.mapAccum(\n (): TextDecoder => new TextDecoder(\"utf-8\"),\n (decoder, chunk: Uint8Array) => [decoder, [decoder.decode(chunk, { stream: true })]] as const,\n {\n onHalt: (decoder: TextDecoder) => {\n const tail = decoder.decode()\n return tail.length > 0 ? [tail] : []\n },\n },\n ),\n )\n\nconst splitOn =\n (separator: string) =>\n <E, R>(self: Stream.Stream<string, E, R>): Stream.Stream<string, E, R> =>\n self.pipe(\n Stream.mapAccum(\n (): string => \"\",\n (buffer, chunk: string) => {\n const parts = (buffer + chunk).split(separator)\n const tail = parts[parts.length - 1] ?? \"\"\n return [tail, parts.slice(0, -1)] as const\n },\n { onHalt: (tail: string) => (tail.length > 0 ? [tail] : []) },\n ),\n )\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Decode a `Stream<Uint8Array>` into a `Stream<string>` of newline-delimited\n * lines. Empty lines are skipped. Buffers across chunk boundaries.\n */\nexport const fromBytes = <E, R>(\n self: Stream.Stream<Uint8Array, E, R>,\n): Stream.Stream<string, E, R> =>\n self.pipe(\n decodeText,\n Stream.map((s) => s.replace(/\\r/g, \"\")),\n splitOn(\"\\n\"),\n Stream.filter((line) => line.length > 0),\n )\n\n/**\n * Validate each JSONL line against a Schema. JSON parse errors and Schema\n * decode errors both surface as a `JsonParseError` so callers can `catchTag`\n * uniformly.\n */\nexport const parse =\n <A, I>(schema: Schema.Codec<A, I>) =>\n <E, R>(self: Stream.Stream<string, E, R>): Stream.Stream<A, JsonParseError | E, R> =>\n self.pipe(\n Stream.mapEffect((line) =>\n Effect.try({\n try: () => JSON.parse(line) as unknown,\n catch: (cause) => new JsonParseError({ line, cause }),\n }).pipe(\n Effect.flatMap((value) =>\n Schema.decodeUnknownEffect(schema)(value).pipe(\n Effect.mapError((cause) => new JsonParseError({ line, cause })),\n ),\n ),\n ),\n ),\n )\n\nconst encoder = new TextEncoder()\n\n/**\n * Serialize a stream of values to JSONL bytes. Encodes each value via\n * `Schema.encodeUnknownSync`. Each line ends with `\\n`.\n */\nexport const toBytes =\n <A, I>(schema: Schema.Codec<A, I>) =>\n <E, R>(self: Stream.Stream<A, E, R>): Stream.Stream<Uint8Array, E, R> =>\n self.pipe(\n Stream.map((value) => {\n const encoded = Schema.encodeUnknownSync(schema)(value)\n return encoder.encode(JSON.stringify(encoded) + \"\\n\")\n }),\n )\n"],"mappings":";;;;;;;;;AAEA,IAAa,iBAAb,cAAoC,KAAK,YAAY,iBAAiB,CAGnE;AAMH,MAAM,cAAoB,SACxB,KAAK,KACH,OAAO,eACc,IAAI,YAAY,QAAQ,GAC1C,SAAS,UAAsB,CAAC,SAAS,CAAC,QAAQ,OAAO,OAAO,EAAE,QAAQ,MAAM,CAAC,CAAC,CAAC,EACpF,EACE,SAAS,YAAyB;CAChC,MAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAO,KAAK,SAAS,IAAI,CAAC,KAAK,GAAG,EAAE;GAEvC,CACF,CACF;AAEH,MAAM,WACH,eACM,SACL,KAAK,KACH,OAAO,eACS,KACb,QAAQ,UAAkB;CACzB,MAAM,SAAS,SAAS,OAAO,MAAM,UAAU;AAE/C,QAAO,CADM,MAAM,MAAM,SAAS,MAAM,IAC1B,MAAM,MAAM,GAAG,GAAG,CAAC;GAEnC,EAAE,SAAS,SAAkB,KAAK,SAAS,IAAI,CAAC,KAAK,GAAG,EAAE,EAAG,CAC9D,CACF;;;;;AAUL,MAAa,aACX,SAEA,KAAK,KACH,YACA,OAAO,KAAK,MAAM,EAAE,QAAQ,OAAO,GAAG,CAAC,EACvC,QAAQ,KAAK,EACb,OAAO,QAAQ,SAAS,KAAK,SAAS,EAAE,CACzC;;;;;;AAOH,MAAa,SACJ,YACA,SACL,KAAK,KACH,OAAO,WAAW,SAChB,OAAO,IAAI;CACT,WAAW,KAAK,MAAM,KAAK;CAC3B,QAAQ,UAAU,IAAI,eAAe;EAAE;EAAM;EAAO,CAAC;CACtD,CAAC,CAAC,KACD,OAAO,SAAS,UACd,OAAO,oBAAoB,OAAO,CAAC,MAAM,CAAC,KACxC,OAAO,UAAU,UAAU,IAAI,eAAe;CAAE;CAAM;CAAO,CAAC,CAAC,CAChE,CACF,CACF,CACF,CACF;AAEL,MAAM,UAAU,IAAI,aAAa;;;;;AAMjC,MAAa,WACJ,YACA,SACL,KAAK,KACH,OAAO,KAAK,UAAU;CACpB,MAAM,UAAU,OAAO,kBAAkB,OAAO,CAAC,MAAM;AACvD,QAAO,QAAQ,OAAO,KAAK,UAAU,QAAQ,GAAG,KAAK;EACrD,CACH"}
1
+ {"version":3,"file":"JSONL.mjs","names":[],"sources":["../../src/streaming/JSONL.ts"],"sourcesContent":["import { Data, Effect, Schema, Stream } from \"effect\"\n\nexport class JsonParseError extends Data.TaggedError(\"JsonParseError\")<{\n readonly line: string\n readonly cause: unknown\n}> {}\n\n// ---------------------------------------------------------------------------\n// Generic stream helpers (kept module-local; see SSE.ts for the same shape).\n// ---------------------------------------------------------------------------\n\nconst decodeText = <E, R>(self: Stream.Stream<Uint8Array, E, R>): Stream.Stream<string, E, R> =>\n self.pipe(\n Stream.mapAccum(\n (): TextDecoder => new TextDecoder(\"utf-8\"),\n (decoder, chunk: Uint8Array) => [decoder, [decoder.decode(chunk, { stream: true })]] as const,\n {\n onHalt: (decoder: TextDecoder) => {\n const tail = decoder.decode()\n return tail.length > 0 ? [tail] : []\n },\n },\n ),\n )\n\nconst splitOn =\n (separator: string) =>\n <E, R>(self: Stream.Stream<string, E, R>): Stream.Stream<string, E, R> =>\n self.pipe(\n Stream.mapAccum(\n (): string => \"\",\n (buffer, chunk: string) => {\n const parts = (buffer + chunk).split(separator)\n const tail = parts[parts.length - 1] ?? \"\"\n return [tail, parts.slice(0, -1)] as const\n },\n { onHalt: (tail: string) => (tail.length > 0 ? [tail] : []) },\n ),\n )\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Decode a `Stream<Uint8Array>` into a `Stream<string>` of newline-delimited\n * lines. Empty lines are skipped. Buffers across chunk boundaries.\n */\nexport const fromBytes = <E, R>(\n self: Stream.Stream<Uint8Array, E, R>,\n): Stream.Stream<string, E, R> =>\n self.pipe(\n decodeText,\n Stream.map((s) => s.replace(/\\r/g, \"\")),\n splitOn(\"\\n\"),\n Stream.filter((line) => line.length > 0),\n )\n\n/**\n * Validate each JSONL line against a Schema. JSON parse errors and Schema\n * decode errors both surface as a `JsonParseError` so callers can `catchTag`\n * uniformly.\n */\nexport const parse =\n <A, I>(schema: Schema.Codec<A, I>) =>\n <E, R>(self: Stream.Stream<string, E, R>): Stream.Stream<A, JsonParseError | E, R> =>\n self.pipe(\n Stream.mapEffect((line) =>\n Effect.try({\n try: () => JSON.parse(line) as unknown,\n catch: (cause) => new JsonParseError({ line, cause }),\n }).pipe(\n Effect.flatMap((value) =>\n Schema.decodeUnknownEffect(schema)(value).pipe(\n Effect.mapError((cause) => new JsonParseError({ line, cause })),\n ),\n ),\n ),\n ),\n )\n\n/**\n * Best-effort parse of a single JSON frame. Returns the parsed value or\n * `undefined` on malformed input. Realtime WS adapters use this to skip\n * non-JSON or partially-received frames silently rather than fail the\n * entire session over one bad frame.\n */\nexport const parseSafe = (raw: string) =>\n Effect.try({\n try: () => JSON.parse(raw) as unknown,\n catch: () => undefined,\n }).pipe(Effect.orElseSucceed(() => undefined))\n\nconst encoder = new TextEncoder()\n\n/**\n * Serialize a stream of values to JSONL bytes. Encodes each value via\n * `Schema.encodeUnknownSync`. Each line ends with `\\n`.\n */\nexport const toBytes =\n <A, I>(schema: Schema.Codec<A, I>) =>\n <E, R>(self: Stream.Stream<A, E, R>): Stream.Stream<Uint8Array, E, R> =>\n self.pipe(\n Stream.map((value) => {\n const encoded = Schema.encodeUnknownSync(schema)(value)\n return encoder.encode(JSON.stringify(encoded) + \"\\n\")\n }),\n )\n"],"mappings":";;;;;;;;;;AAEA,IAAa,iBAAb,cAAoC,KAAK,YAAY,iBAAiB,CAGnE;AAMH,MAAM,cAAoB,SACxB,KAAK,KACH,OAAO,eACc,IAAI,YAAY,QAAQ,GAC1C,SAAS,UAAsB,CAAC,SAAS,CAAC,QAAQ,OAAO,OAAO,EAAE,QAAQ,MAAM,CAAC,CAAC,CAAC,EACpF,EACE,SAAS,YAAyB;CAChC,MAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAO,KAAK,SAAS,IAAI,CAAC,KAAK,GAAG,EAAE;GAEvC,CACF,CACF;AAEH,MAAM,WACH,eACM,SACL,KAAK,KACH,OAAO,eACS,KACb,QAAQ,UAAkB;CACzB,MAAM,SAAS,SAAS,OAAO,MAAM,UAAU;AAE/C,QAAO,CADM,MAAM,MAAM,SAAS,MAAM,IAC1B,MAAM,MAAM,GAAG,GAAG,CAAC;GAEnC,EAAE,SAAS,SAAkB,KAAK,SAAS,IAAI,CAAC,KAAK,GAAG,EAAE,EAAG,CAC9D,CACF;;;;;AAUL,MAAa,aACX,SAEA,KAAK,KACH,YACA,OAAO,KAAK,MAAM,EAAE,QAAQ,OAAO,GAAG,CAAC,EACvC,QAAQ,KAAK,EACb,OAAO,QAAQ,SAAS,KAAK,SAAS,EAAE,CACzC;;;;;;AAOH,MAAa,SACJ,YACA,SACL,KAAK,KACH,OAAO,WAAW,SAChB,OAAO,IAAI;CACT,WAAW,KAAK,MAAM,KAAK;CAC3B,QAAQ,UAAU,IAAI,eAAe;EAAE;EAAM;EAAO,CAAC;CACtD,CAAC,CAAC,KACD,OAAO,SAAS,UACd,OAAO,oBAAoB,OAAO,CAAC,MAAM,CAAC,KACxC,OAAO,UAAU,UAAU,IAAI,eAAe;CAAE;CAAM;CAAO,CAAC,CAAC,CAChE,CACF,CACF,CACF,CACF;;;;;;;AAQL,MAAa,aAAa,QACxB,OAAO,IAAI;CACT,WAAW,KAAK,MAAM,IAAI;CAC1B,aAAa,KAAA;CACd,CAAC,CAAC,KAAK,OAAO,oBAAoB,KAAA,EAAU,CAAC;AAEhD,MAAM,UAAU,IAAI,aAAa;;;;;AAMjC,MAAa,WACJ,YACA,SACL,KAAK,KACH,OAAO,KAAK,UAAU;CACpB,MAAM,UAAU,OAAO,kBAAkB,OAAO,CAAC,MAAM;AACvD,QAAO,QAAQ,OAAO,KAAK,UAAU,QAAQ,GAAG,KAAK;EACrD,CACH"}
@@ -1,2 +1,2 @@
1
- import { c as decodeJsonLines, i as StructuredFormat, l as fromEffectSchema, n as JsonParseError, o as StructuredSchema, r as StructuredDecodeError, s as decode, t as DecodeIssue, u as parseJson } from "../StructuredFormat-BWq5Hd1O.mjs";
1
+ import { c as decodeJsonLines, i as StructuredFormat, l as fromEffectSchema, n as JsonParseError, o as StructuredSchema, r as StructuredDecodeError, s as decode, t as DecodeIssue, u as parseJson } from "../StructuredFormat-Cl41C56K.mjs";
2
2
  export { DecodeIssue, JsonParseError, StructuredDecodeError, StructuredFormat, StructuredSchema, decode, decodeJsonLines, fromEffectSchema, parseJson };
@@ -0,0 +1,39 @@
1
+ import { n as AudioChunk } from "../Audio-BfCTGnH3.mjs";
2
+ import { CommonGenerateMusicRequest, CommonStreamGenerateMusicRequest, MusicResult } from "../domain/Music.mjs";
3
+ import { MusicGenerator, MusicInteractiveSession } from "../music-generator/MusicGenerator.mjs";
4
+ import { Effect, Layer } from "effect";
5
+
6
+ //#region src/testing/MockMusicGenerator.d.ts
7
+ type MockMusicGeneratorRecorder = {
8
+ readonly generateCalls: ReadonlyArray<CommonGenerateMusicRequest>;
9
+ readonly streamGenerationCalls: ReadonlyArray<CommonStreamGenerateMusicRequest>;
10
+ readonly streamGenerationFromCalls: ReadonlyArray<CommonStreamGenerateMusicRequest>;
11
+ };
12
+ type MockMusicGeneratorScript = {
13
+ /** One result per `generate` call, consumed in order. */readonly results?: ReadonlyArray<MusicResult>; /** One chunk-list per `streamGeneration` call, consumed in order. */
14
+ readonly streamGenerationChunks?: ReadonlyArray<ReadonlyArray<AudioChunk>>; /** One chunk-list per `streamGenerationFrom` call, consumed in order. */
15
+ readonly streamGenerationFromChunks?: ReadonlyArray<ReadonlyArray<AudioChunk>>;
16
+ };
17
+ /**
18
+ * Layer providing the `MusicGenerator` service AND the
19
+ * `MusicInteractiveSession` capability marker. Use for the common case
20
+ * where code under test exercises `streamGenerationFrom`.
21
+ */
22
+ declare const layer: (script: MockMusicGeneratorScript) => {
23
+ readonly layer: Layer.Layer<MusicGenerator | MusicInteractiveSession>;
24
+ readonly recorder: Effect.Effect<MockMusicGeneratorRecorder>;
25
+ };
26
+ /**
27
+ * Variant that omits the `MusicInteractiveSession` marker — simulates a
28
+ * provider without bidirectional support (Lyria 3 sync, ElevenLabs,
29
+ * Mureka, MiniMax, Stable Audio, Suno). Calls to
30
+ * `streamGenerationFrom` in code under test should be a compile-time
31
+ * error against this Layer alone.
32
+ */
33
+ declare const layerWithoutInteractive: (script: MockMusicGeneratorScript) => {
34
+ readonly layer: Layer.Layer<MusicGenerator>;
35
+ readonly recorder: Effect.Effect<MockMusicGeneratorRecorder>;
36
+ };
37
+ //#endregion
38
+ export { MockMusicGeneratorRecorder, MockMusicGeneratorScript, layer, layerWithoutInteractive };
39
+ //# sourceMappingURL=MockMusicGenerator.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MockMusicGenerator.d.mts","names":[],"sources":["../../src/testing/MockMusicGenerator.ts"],"mappings":";;;;;;KAeY,0BAAA;EAAA,SACD,aAAA,EAAe,aAAA,CAAc,0BAAA;EAAA,SAC7B,qBAAA,EAAuB,aAAA,CAAc,gCAAA;EAAA,SACrC,yBAAA,EAA2B,aAAA,CAAc,gCAAA;AAAA;AAAA,KAGxC,wBAAA;EALc,kEAOf,OAAA,GAAU,aAAA,CAAc,WAAA,GAND;EAAA,SAQvB,sBAAA,GAAyB,aAAA,CAAc,aAAA,CAAc,UAAA,IAP1B;EAAA,SAS3B,0BAAA,GAA6B,aAAA,CAAc,aAAA,CAAc,UAAA;AAAA;;;;;;cAgFvD,KAAA,GACX,MAAA,EAAQ,wBAAA;EAAA,SAEC,KAAA,EAAO,KAAA,CAAM,KAAA,CAAM,cAAA,GAAiB,uBAAA;EAAA,SACpC,QAAA,EAAU,MAAA,CAAO,MAAA,CAAO,0BAAA;AAAA;;;AA1FnC;;;;;cA0Ha,uBAAA,GACX,MAAA,EAAQ,wBAAA;EAAA,SAEC,KAAA,EAAO,KAAA,CAAM,KAAA,CAAM,cAAA;EAAA,SACnB,QAAA,EAAU,MAAA,CAAO,MAAA,CAAO,0BAAA;AAAA"}
@@ -0,0 +1,96 @@
1
+ import { InvalidRequest } from "../domain/AiError.mjs";
2
+ import { MusicGenerator, MusicInteractiveSession } from "../music-generator/MusicGenerator.mjs";
3
+ import { Effect, Layer, Ref, Stream } from "effect";
4
+ //#region src/testing/MockMusicGenerator.ts
5
+ const makeService = (script, record) => Effect.gen(function* () {
6
+ const gCursor = yield* Ref.make(0);
7
+ const sgCursor = yield* Ref.make(0);
8
+ const sgfCursor = yield* Ref.make(0);
9
+ return {
10
+ generate: (request) => Effect.gen(function* () {
11
+ yield* record.generate(request);
12
+ const i = yield* Ref.getAndUpdate(gCursor, (n) => n + 1);
13
+ const scripted = script.results ?? [];
14
+ if (i >= scripted.length) return yield* Effect.fail(new InvalidRequest({
15
+ provider: "mock",
16
+ raw: `MockMusicGenerator exhausted: ${scripted.length} results scripted, but call ${i + 1} was made`
17
+ }));
18
+ return scripted[i];
19
+ }),
20
+ streamGeneration: (request) => Stream.unwrap(Effect.gen(function* () {
21
+ yield* record.streamGeneration(request);
22
+ const i = yield* Ref.getAndUpdate(sgCursor, (n) => n + 1);
23
+ const scripted = script.streamGenerationChunks ?? [];
24
+ if (i >= scripted.length) return Stream.fail(new InvalidRequest({
25
+ provider: "mock",
26
+ raw: `MockMusicGenerator exhausted: ${scripted.length} streamGeneration lists scripted, but call ${i + 1} was made`
27
+ }));
28
+ return Stream.fromIterable(scripted[i]);
29
+ })),
30
+ streamGenerationFrom: (input, request) => Stream.unwrap(Effect.gen(function* () {
31
+ yield* record.streamGenerationFrom(request);
32
+ const i = yield* Ref.getAndUpdate(sgfCursor, (n) => n + 1);
33
+ const scripted = script.streamGenerationFromChunks ?? [];
34
+ if (i >= scripted.length) return Stream.fail(new InvalidRequest({
35
+ provider: "mock",
36
+ raw: `MockMusicGenerator exhausted: ${scripted.length} streamGenerationFrom lists scripted, but call ${i + 1} was made`
37
+ }));
38
+ return Stream.drain(input).pipe(Stream.concat(Stream.fromIterable(scripted[i])));
39
+ }))
40
+ };
41
+ });
42
+ /**
43
+ * Layer providing the `MusicGenerator` service AND the
44
+ * `MusicInteractiveSession` capability marker. Use for the common case
45
+ * where code under test exercises `streamGenerationFrom`.
46
+ */
47
+ const layer = (script) => {
48
+ const gCalls = Ref.makeUnsafe([]);
49
+ const sgCalls = Ref.makeUnsafe([]);
50
+ const sgfCalls = Ref.makeUnsafe([]);
51
+ const generatorLayer = Layer.effect(MusicGenerator, makeService(script, {
52
+ generate: (req) => Ref.update(gCalls, (xs) => [...xs, req]),
53
+ streamGeneration: (req) => Ref.update(sgCalls, (xs) => [...xs, req]),
54
+ streamGenerationFrom: (req) => Ref.update(sgfCalls, (xs) => [...xs, req])
55
+ }));
56
+ return {
57
+ layer: Layer.merge(generatorLayer, Layer.succeed(MusicInteractiveSession, void 0)),
58
+ recorder: Effect.gen(function* () {
59
+ return {
60
+ generateCalls: yield* Ref.get(gCalls),
61
+ streamGenerationCalls: yield* Ref.get(sgCalls),
62
+ streamGenerationFromCalls: yield* Ref.get(sgfCalls)
63
+ };
64
+ })
65
+ };
66
+ };
67
+ /**
68
+ * Variant that omits the `MusicInteractiveSession` marker — simulates a
69
+ * provider without bidirectional support (Lyria 3 sync, ElevenLabs,
70
+ * Mureka, MiniMax, Stable Audio, Suno). Calls to
71
+ * `streamGenerationFrom` in code under test should be a compile-time
72
+ * error against this Layer alone.
73
+ */
74
+ const layerWithoutInteractive = (script) => {
75
+ const gCalls = Ref.makeUnsafe([]);
76
+ const sgCalls = Ref.makeUnsafe([]);
77
+ const sgfCalls = Ref.makeUnsafe([]);
78
+ return {
79
+ layer: Layer.effect(MusicGenerator, makeService(script, {
80
+ generate: (req) => Ref.update(gCalls, (xs) => [...xs, req]),
81
+ streamGeneration: (req) => Ref.update(sgCalls, (xs) => [...xs, req]),
82
+ streamGenerationFrom: (req) => Ref.update(sgfCalls, (xs) => [...xs, req])
83
+ })),
84
+ recorder: Effect.gen(function* () {
85
+ return {
86
+ generateCalls: yield* Ref.get(gCalls),
87
+ streamGenerationCalls: yield* Ref.get(sgCalls),
88
+ streamGenerationFromCalls: yield* Ref.get(sgfCalls)
89
+ };
90
+ })
91
+ };
92
+ };
93
+ //#endregion
94
+ export { layer, layerWithoutInteractive };
95
+
96
+ //# sourceMappingURL=MockMusicGenerator.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MockMusicGenerator.mjs","names":["AiError.InvalidRequest"],"sources":["../../src/testing/MockMusicGenerator.ts"],"sourcesContent":["import { Effect, Layer, Ref, Stream } from \"effect\"\nimport * as AiError from \"../domain/AiError.js\"\nimport type { AudioChunk } from \"../domain/Audio.js\"\nimport type {\n CommonGenerateMusicRequest,\n CommonStreamGenerateMusicRequest,\n MusicResult,\n MusicSessionInput,\n} from \"../domain/Music.js\"\nimport {\n MusicGenerator,\n MusicInteractiveSession,\n type MusicGeneratorService,\n} from \"../music-generator/MusicGenerator.js\"\n\nexport type MockMusicGeneratorRecorder = {\n readonly generateCalls: ReadonlyArray<CommonGenerateMusicRequest>\n readonly streamGenerationCalls: ReadonlyArray<CommonStreamGenerateMusicRequest>\n readonly streamGenerationFromCalls: ReadonlyArray<CommonStreamGenerateMusicRequest>\n}\n\nexport type MockMusicGeneratorScript = {\n /** One result per `generate` call, consumed in order. */\n readonly results?: ReadonlyArray<MusicResult>\n /** One chunk-list per `streamGeneration` call, consumed in order. */\n readonly streamGenerationChunks?: ReadonlyArray<ReadonlyArray<AudioChunk>>\n /** One chunk-list per `streamGenerationFrom` call, consumed in order. */\n readonly streamGenerationFromChunks?: ReadonlyArray<ReadonlyArray<AudioChunk>>\n}\n\nconst makeService = (\n script: MockMusicGeneratorScript,\n record: {\n readonly generate: (req: CommonGenerateMusicRequest) => Effect.Effect<void>\n readonly streamGeneration: (req: CommonStreamGenerateMusicRequest) => Effect.Effect<void>\n readonly streamGenerationFrom: (req: CommonStreamGenerateMusicRequest) => Effect.Effect<void>\n },\n) =>\n Effect.gen(function* () {\n const gCursor = yield* Ref.make(0)\n const sgCursor = yield* Ref.make(0)\n const sgfCursor = yield* Ref.make(0)\n const service: MusicGeneratorService = {\n generate: (request) =>\n Effect.gen(function* () {\n yield* record.generate(request)\n const i = yield* Ref.getAndUpdate(gCursor, (n) => n + 1)\n const scripted = script.results ?? []\n if (i >= scripted.length) {\n return yield* Effect.fail(\n new AiError.InvalidRequest({\n provider: \"mock\",\n raw: `MockMusicGenerator exhausted: ${scripted.length} results scripted, but call ${i + 1} was made`,\n }),\n )\n }\n return scripted[i]!\n }),\n streamGeneration: (request) =>\n Stream.unwrap(\n Effect.gen(function* () {\n yield* record.streamGeneration(request)\n const i = yield* Ref.getAndUpdate(sgCursor, (n) => n + 1)\n const scripted = script.streamGenerationChunks ?? []\n if (i >= scripted.length) {\n return Stream.fail(\n new AiError.InvalidRequest({\n provider: \"mock\",\n raw: `MockMusicGenerator exhausted: ${scripted.length} streamGeneration lists scripted, but call ${i + 1} was made`,\n }),\n )\n }\n return Stream.fromIterable(scripted[i]!)\n }),\n ),\n streamGenerationFrom: <E, R>(\n input: Stream.Stream<MusicSessionInput, E, R>,\n request: CommonStreamGenerateMusicRequest,\n ): Stream.Stream<AudioChunk, AiError.AiError | E, R> =>\n Stream.unwrap(\n Effect.gen(function* () {\n yield* record.streamGenerationFrom(request)\n const i = yield* Ref.getAndUpdate(sgfCursor, (n) => n + 1)\n const scripted = script.streamGenerationFromChunks ?? []\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: `MockMusicGenerator exhausted: ${scripted.length} streamGenerationFrom lists scripted, but call ${i + 1} was made`,\n }),\n )\n return exhausted\n }\n // Drain the input fully before emitting scripted audio chunks,\n // so consumers can assert on what session messages were pushed.\n return Stream.drain(input).pipe(Stream.concat(Stream.fromIterable(scripted[i]!)))\n }),\n ),\n }\n return service\n })\n\n/**\n * Layer providing the `MusicGenerator` service AND the\n * `MusicInteractiveSession` capability marker. Use for the common case\n * where code under test exercises `streamGenerationFrom`.\n */\nexport const layer = (\n script: MockMusicGeneratorScript,\n): {\n readonly layer: Layer.Layer<MusicGenerator | MusicInteractiveSession>\n readonly recorder: Effect.Effect<MockMusicGeneratorRecorder>\n} => {\n const gCalls = Ref.makeUnsafe<ReadonlyArray<CommonGenerateMusicRequest>>([])\n const sgCalls = Ref.makeUnsafe<ReadonlyArray<CommonStreamGenerateMusicRequest>>([])\n const sgfCalls = Ref.makeUnsafe<ReadonlyArray<CommonStreamGenerateMusicRequest>>([])\n const generatorLayer = Layer.effect(\n MusicGenerator,\n makeService(script, {\n generate: (req) => Ref.update(gCalls, (xs) => [...xs, req]),\n streamGeneration: (req) => Ref.update(sgCalls, (xs) => [...xs, req]),\n streamGenerationFrom: (req) => Ref.update(sgfCalls, (xs) => [...xs, req]),\n }),\n )\n const live = Layer.merge(generatorLayer, Layer.succeed(MusicInteractiveSession, undefined))\n return {\n layer: live,\n recorder: Effect.gen(function* () {\n const generateCalls = yield* Ref.get(gCalls)\n const streamGenerationCalls = yield* Ref.get(sgCalls)\n const streamGenerationFromCalls = yield* Ref.get(sgfCalls)\n return { generateCalls, streamGenerationCalls, streamGenerationFromCalls }\n }),\n }\n}\n\n/**\n * Variant that omits the `MusicInteractiveSession` marker — simulates a\n * provider without bidirectional support (Lyria 3 sync, ElevenLabs,\n * Mureka, MiniMax, Stable Audio, Suno). Calls to\n * `streamGenerationFrom` in code under test should be a compile-time\n * error against this Layer alone.\n */\nexport const layerWithoutInteractive = (\n script: MockMusicGeneratorScript,\n): {\n readonly layer: Layer.Layer<MusicGenerator>\n readonly recorder: Effect.Effect<MockMusicGeneratorRecorder>\n} => {\n const gCalls = Ref.makeUnsafe<ReadonlyArray<CommonGenerateMusicRequest>>([])\n const sgCalls = Ref.makeUnsafe<ReadonlyArray<CommonStreamGenerateMusicRequest>>([])\n const sgfCalls = Ref.makeUnsafe<ReadonlyArray<CommonStreamGenerateMusicRequest>>([])\n const live = Layer.effect(\n MusicGenerator,\n makeService(script, {\n generate: (req) => Ref.update(gCalls, (xs) => [...xs, req]),\n streamGeneration: (req) => Ref.update(sgCalls, (xs) => [...xs, req]),\n streamGenerationFrom: (req) => Ref.update(sgfCalls, (xs) => [...xs, req]),\n }),\n )\n return {\n layer: live,\n recorder: Effect.gen(function* () {\n const generateCalls = yield* Ref.get(gCalls)\n const streamGenerationCalls = yield* Ref.get(sgCalls)\n const streamGenerationFromCalls = yield* Ref.get(sgfCalls)\n return { generateCalls, streamGenerationCalls, streamGenerationFromCalls }\n }),\n }\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { d as Item } from "../Items-CB8Bo3FI.mjs";
2
- import { r as Turn } from "../Turn-OPaILVIB.mjs";
1
+ import { d as Item } from "../Items-Hg5AsYxl.mjs";
2
+ import { r as Turn } from "../Turn-7geUcKsf.mjs";
3
3
  import { LanguageModel, LanguageModelService } from "../language-model/LanguageModel.mjs";
4
4
  import { Duration, Effect, Layer } from "effect";
5
5
 
@@ -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;;;;;;cAgFtD,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;;;AA1FnC;;;;cAyHa,uBAAA,GACX,MAAA,EAAQ,qBAAA;EAAA,SAEC,KAAA,EAAO,KAAA,CAAM,KAAA,CAAM,iBAAA;EAAA,SACnB,QAAA,EAAU,MAAA,CAAO,MAAA,CAAO,uBAAA;AAAA"}