@effect-uai/core 0.2.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.
- package/README.md +1 -1
- package/dist/{AiError-CqmYjXyx.d.mts → AiError-csR8Bhxx.d.mts} +26 -4
- package/dist/{AiError-CqmYjXyx.d.mts.map → AiError-csR8Bhxx.d.mts.map} +1 -1
- package/dist/Audio-BfCTGnH3.d.mts +61 -0
- package/dist/Audio-BfCTGnH3.d.mts.map +1 -0
- package/dist/Image-DxyXqzAM.d.mts +61 -0
- package/dist/Image-DxyXqzAM.d.mts.map +1 -0
- package/dist/{Items-D1C2686t.d.mts → Items-Hg5AsYxl.d.mts} +132 -80
- package/dist/Items-Hg5AsYxl.d.mts.map +1 -0
- package/dist/Media-D_CpcM1Z.d.mts +57 -0
- package/dist/Media-D_CpcM1Z.d.mts.map +1 -0
- package/dist/{StructuredFormat-B5ueioNr.d.mts → StructuredFormat-Cl41C56K.d.mts} +5 -5
- package/dist/StructuredFormat-Cl41C56K.d.mts.map +1 -0
- package/dist/{Tool-5wxOCuOh.d.mts → Tool-B8B5qVEy.d.mts} +13 -13
- package/dist/Tool-B8B5qVEy.d.mts.map +1 -0
- package/dist/{Turn-Bi83du4I.d.mts → Turn-7geUcKsf.d.mts} +5 -11
- package/dist/Turn-7geUcKsf.d.mts.map +1 -0
- package/dist/{chunk-CfYAbeIz.mjs → chunk-uyGKjUfl.mjs} +2 -1
- package/dist/dist-DV5ISja1.mjs +13782 -0
- package/dist/dist-DV5ISja1.mjs.map +1 -0
- package/dist/domain/AiError.d.mts +2 -2
- package/dist/domain/AiError.mjs +19 -3
- package/dist/domain/AiError.mjs.map +1 -1
- package/dist/domain/Audio.d.mts +2 -0
- package/dist/domain/Audio.mjs +14 -0
- package/dist/domain/Audio.mjs.map +1 -0
- package/dist/domain/Image.d.mts +2 -0
- package/dist/domain/Image.mjs +58 -0
- package/dist/domain/Image.mjs.map +1 -0
- package/dist/domain/Items.d.mts +2 -2
- package/dist/domain/Items.mjs +19 -42
- package/dist/domain/Items.mjs.map +1 -1
- package/dist/domain/Media.d.mts +2 -0
- package/dist/domain/Media.mjs +14 -0
- package/dist/domain/Media.mjs.map +1 -0
- package/dist/domain/Music.d.mts +116 -0
- package/dist/domain/Music.d.mts.map +1 -0
- package/dist/domain/Music.mjs +29 -0
- package/dist/domain/Music.mjs.map +1 -0
- package/dist/domain/Transcript.d.mts +95 -0
- package/dist/domain/Transcript.d.mts.map +1 -0
- package/dist/domain/Transcript.mjs +22 -0
- package/dist/domain/Transcript.mjs.map +1 -0
- package/dist/domain/Turn.d.mts +1 -1
- package/dist/domain/Turn.mjs +1 -1
- package/dist/embedding-model/Embedding.d.mts +107 -0
- package/dist/embedding-model/Embedding.d.mts.map +1 -0
- package/dist/embedding-model/Embedding.mjs +18 -0
- package/dist/embedding-model/Embedding.mjs.map +1 -0
- package/dist/embedding-model/EmbeddingModel.d.mts +97 -0
- package/dist/embedding-model/EmbeddingModel.d.mts.map +1 -0
- package/dist/embedding-model/EmbeddingModel.mjs +17 -0
- package/dist/embedding-model/EmbeddingModel.mjs.map +1 -0
- package/dist/index.d.mts +21 -7
- package/dist/index.mjs +16 -2
- package/dist/language-model/LanguageModel.d.mts +12 -20
- package/dist/language-model/LanguageModel.d.mts.map +1 -1
- package/dist/language-model/LanguageModel.mjs +3 -20
- package/dist/language-model/LanguageModel.mjs.map +1 -1
- package/dist/loop/Loop.d.mts +31 -7
- package/dist/loop/Loop.d.mts.map +1 -1
- package/dist/loop/Loop.mjs +39 -6
- package/dist/loop/Loop.mjs.map +1 -1
- package/dist/loop/Loop.test.d.mts +1 -0
- package/dist/loop/Loop.test.mjs +411 -0
- package/dist/loop/Loop.test.mjs.map +1 -0
- package/dist/magic-string.es-BgIV5Mu3.mjs +1013 -0
- package/dist/magic-string.es-BgIV5Mu3.mjs.map +1 -0
- package/dist/math/Vector.d.mts +47 -0
- package/dist/math/Vector.d.mts.map +1 -0
- package/dist/math/Vector.mjs +117 -0
- package/dist/math/Vector.mjs.map +1 -0
- package/dist/music-generator/MusicGenerator.d.mts +77 -0
- package/dist/music-generator/MusicGenerator.d.mts.map +1 -0
- package/dist/music-generator/MusicGenerator.mjs +51 -0
- package/dist/music-generator/MusicGenerator.mjs.map +1 -0
- package/dist/music-generator/MusicGenerator.test.d.mts +1 -0
- package/dist/music-generator/MusicGenerator.test.mjs +154 -0
- package/dist/music-generator/MusicGenerator.test.mjs.map +1 -0
- package/dist/observability/Metrics.d.mts +2 -2
- package/dist/observability/Metrics.d.mts.map +1 -1
- package/dist/observability/Metrics.mjs +1 -1
- package/dist/observability/Metrics.mjs.map +1 -1
- package/dist/speech-synthesizer/SpeechSynthesizer.d.mts +96 -0
- package/dist/speech-synthesizer/SpeechSynthesizer.d.mts.map +1 -0
- package/dist/speech-synthesizer/SpeechSynthesizer.mjs +48 -0
- package/dist/speech-synthesizer/SpeechSynthesizer.mjs.map +1 -0
- package/dist/speech-synthesizer/SpeechSynthesizer.test.d.mts +1 -0
- package/dist/speech-synthesizer/SpeechSynthesizer.test.mjs +112 -0
- package/dist/speech-synthesizer/SpeechSynthesizer.test.mjs.map +1 -0
- package/dist/streaming/JSONL.d.mts +10 -3
- package/dist/streaming/JSONL.d.mts.map +1 -1
- package/dist/streaming/JSONL.mjs +13 -2
- package/dist/streaming/JSONL.mjs.map +1 -1
- package/dist/streaming/JSONL.test.d.mts +1 -0
- package/dist/streaming/JSONL.test.mjs +70 -0
- package/dist/streaming/JSONL.test.mjs.map +1 -0
- package/dist/streaming/Lines.mjs +1 -1
- package/dist/streaming/SSE.d.mts +2 -2
- package/dist/streaming/SSE.d.mts.map +1 -1
- package/dist/streaming/SSE.mjs +1 -1
- package/dist/streaming/SSE.mjs.map +1 -1
- package/dist/streaming/SSE.test.d.mts +1 -0
- package/dist/streaming/SSE.test.mjs +72 -0
- package/dist/streaming/SSE.test.mjs.map +1 -0
- package/dist/structured-format/StructuredFormat.d.mts +1 -1
- package/dist/structured-format/StructuredFormat.mjs +1 -1
- package/dist/structured-format/StructuredFormat.mjs.map +1 -1
- package/dist/testing/MockMusicGenerator.d.mts +39 -0
- package/dist/testing/MockMusicGenerator.d.mts.map +1 -0
- package/dist/testing/MockMusicGenerator.mjs +96 -0
- package/dist/testing/MockMusicGenerator.mjs.map +1 -0
- package/dist/testing/MockProvider.d.mts +6 -6
- package/dist/testing/MockProvider.d.mts.map +1 -1
- package/dist/testing/MockProvider.mjs.map +1 -1
- package/dist/testing/MockSpeechSynthesizer.d.mts +37 -0
- package/dist/testing/MockSpeechSynthesizer.d.mts.map +1 -0
- package/dist/testing/MockSpeechSynthesizer.mjs +95 -0
- package/dist/testing/MockSpeechSynthesizer.mjs.map +1 -0
- package/dist/testing/MockTranscriber.d.mts +37 -0
- package/dist/testing/MockTranscriber.d.mts.map +1 -0
- package/dist/testing/MockTranscriber.mjs +77 -0
- package/dist/testing/MockTranscriber.mjs.map +1 -0
- package/dist/tool/HistoryCheck.d.mts +6 -3
- package/dist/tool/HistoryCheck.d.mts.map +1 -1
- package/dist/tool/HistoryCheck.mjs +7 -1
- package/dist/tool/HistoryCheck.mjs.map +1 -1
- package/dist/tool/Outcome.d.mts +138 -2
- package/dist/tool/Outcome.d.mts.map +1 -0
- package/dist/tool/Outcome.mjs +32 -10
- package/dist/tool/Outcome.mjs.map +1 -1
- package/dist/tool/Resolvers.d.mts +11 -8
- package/dist/tool/Resolvers.d.mts.map +1 -1
- package/dist/tool/Resolvers.mjs +10 -1
- package/dist/tool/Resolvers.mjs.map +1 -1
- package/dist/tool/Resolvers.test.d.mts +1 -0
- package/dist/tool/Resolvers.test.mjs +317 -0
- package/dist/tool/Resolvers.test.mjs.map +1 -0
- package/dist/tool/Tool.d.mts +1 -1
- package/dist/tool/Tool.mjs +1 -1
- package/dist/tool/Tool.mjs.map +1 -1
- package/dist/tool/ToolEvent.d.mts +151 -2
- package/dist/tool/ToolEvent.d.mts.map +1 -0
- package/dist/tool/ToolEvent.mjs +30 -4
- package/dist/tool/ToolEvent.mjs.map +1 -1
- package/dist/tool/Toolkit.d.mts +19 -10
- package/dist/tool/Toolkit.d.mts.map +1 -1
- package/dist/tool/Toolkit.mjs +5 -5
- package/dist/tool/Toolkit.mjs.map +1 -1
- package/dist/tool/Toolkit.test.d.mts +1 -0
- package/dist/tool/Toolkit.test.mjs +113 -0
- package/dist/tool/Toolkit.test.mjs.map +1 -0
- package/dist/transcriber/Transcriber.d.mts +101 -0
- package/dist/transcriber/Transcriber.d.mts.map +1 -0
- package/dist/transcriber/Transcriber.mjs +49 -0
- package/dist/transcriber/Transcriber.mjs.map +1 -0
- package/dist/transcriber/Transcriber.test.d.mts +1 -0
- package/dist/transcriber/Transcriber.test.mjs +130 -0
- package/dist/transcriber/Transcriber.test.mjs.map +1 -0
- package/package.json +65 -13
- package/src/domain/AiError.ts +21 -0
- package/src/domain/Audio.ts +88 -0
- package/src/domain/Image.ts +75 -0
- package/src/domain/Items.ts +18 -47
- package/src/domain/Media.ts +61 -0
- package/src/domain/Music.ts +121 -0
- package/src/domain/Transcript.ts +83 -0
- package/src/embedding-model/Embedding.ts +117 -0
- package/src/embedding-model/EmbeddingModel.ts +107 -0
- package/src/index.ts +15 -1
- package/src/language-model/LanguageModel.ts +2 -22
- package/src/loop/Loop.test.ts +114 -2
- package/src/loop/Loop.ts +69 -5
- package/src/math/Vector.ts +138 -0
- package/src/music-generator/MusicGenerator.test.ts +170 -0
- package/src/music-generator/MusicGenerator.ts +123 -0
- package/src/observability/Metrics.ts +1 -1
- package/src/speech-synthesizer/SpeechSynthesizer.test.ts +141 -0
- package/src/speech-synthesizer/SpeechSynthesizer.ts +131 -0
- package/src/streaming/JSONL.ts +12 -0
- package/src/streaming/SSE.ts +1 -1
- package/src/structured-format/StructuredFormat.ts +2 -2
- package/src/testing/MockMusicGenerator.ts +170 -0
- package/src/testing/MockProvider.ts +2 -2
- package/src/testing/MockSpeechSynthesizer.ts +165 -0
- package/src/testing/MockTranscriber.ts +139 -0
- package/src/tool/HistoryCheck.ts +2 -5
- package/src/tool/Outcome.ts +36 -36
- package/src/tool/Resolvers.test.ts +11 -35
- package/src/tool/Resolvers.ts +5 -14
- package/src/tool/Tool.ts +9 -9
- package/src/tool/ToolEvent.ts +28 -24
- package/src/tool/Toolkit.test.ts +97 -2
- package/src/tool/Toolkit.ts +57 -33
- package/src/transcriber/Transcriber.test.ts +125 -0
- package/src/transcriber/Transcriber.ts +127 -0
- package/dist/Items-D1C2686t.d.mts.map +0 -1
- package/dist/Outcome-GiaNvt7i.d.mts +0 -32
- package/dist/Outcome-GiaNvt7i.d.mts.map +0 -1
- package/dist/StructuredFormat-B5ueioNr.d.mts.map +0 -1
- package/dist/Tool-5wxOCuOh.d.mts.map +0 -1
- package/dist/ToolEvent-wTMgb2GO.d.mts +0 -29
- package/dist/ToolEvent-wTMgb2GO.d.mts.map +0 -1
- package/dist/Turn-Bi83du4I.d.mts.map +0 -1
- package/dist/match/Match.d.mts +0 -16
- package/dist/match/Match.d.mts.map +0 -1
- package/dist/match/Match.mjs +0 -15
- package/dist/match/Match.mjs.map +0 -1
- package/src/match/Match.ts +0 -9
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { fromBytes, parse, toBytes } from "./JSONL.mjs";
|
|
2
|
+
import { i as it, n as globalExpect, r as describe } from "../dist-DV5ISja1.mjs";
|
|
3
|
+
import { Effect, Result, Schema, Stream } from "effect";
|
|
4
|
+
//#region src/streaming/JSONL.test.ts
|
|
5
|
+
const enc = new TextEncoder();
|
|
6
|
+
const bytesOf = (...chunks) => Stream.fromIterable(chunks.map((c) => enc.encode(c)));
|
|
7
|
+
const collect = (s) => Effect.runPromise(Stream.runCollect(s));
|
|
8
|
+
const collectResult = (s) => Effect.runPromise(Effect.result(Stream.runCollect(s)));
|
|
9
|
+
const Patch = Schema.Struct({
|
|
10
|
+
op: Schema.String,
|
|
11
|
+
value: Schema.Number
|
|
12
|
+
});
|
|
13
|
+
describe("JSONL.fromBytes", () => {
|
|
14
|
+
it("emits one string per line", async () => {
|
|
15
|
+
globalExpect(await collect(fromBytes(bytesOf("a\nb\nc\n")))).toEqual([
|
|
16
|
+
"a",
|
|
17
|
+
"b",
|
|
18
|
+
"c"
|
|
19
|
+
]);
|
|
20
|
+
});
|
|
21
|
+
it("buffers lines across chunk boundaries", async () => {
|
|
22
|
+
globalExpect(await collect(fromBytes(bytesOf("ab", "c\nde", "f\n")))).toEqual(["abc", "def"]);
|
|
23
|
+
});
|
|
24
|
+
it("flushes a trailing line without a final newline", async () => {
|
|
25
|
+
globalExpect(await collect(fromBytes(bytesOf("a\nb")))).toEqual(["a", "b"]);
|
|
26
|
+
});
|
|
27
|
+
it("ignores blank lines", async () => {
|
|
28
|
+
globalExpect(await collect(fromBytes(bytesOf("a\n\n\nb\n")))).toEqual(["a", "b"]);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
describe("JSONL.parse", () => {
|
|
32
|
+
it("decodes well-formed JSON lines through a Schema", async () => {
|
|
33
|
+
globalExpect(await collect(bytesOf(`{"op":"add","value":1}\n{"op":"sub","value":2}\n`).pipe(fromBytes, parse(Patch)))).toEqual([{
|
|
34
|
+
op: "add",
|
|
35
|
+
value: 1
|
|
36
|
+
}, {
|
|
37
|
+
op: "sub",
|
|
38
|
+
value: 2
|
|
39
|
+
}]);
|
|
40
|
+
});
|
|
41
|
+
it("fails with JsonParseError on malformed JSON", async () => {
|
|
42
|
+
const result = await collectResult(bytesOf(`{"op":"add","value":1}\nNOT_JSON\n`).pipe(fromBytes, parse(Patch)));
|
|
43
|
+
globalExpect(Result.isFailure(result)).toBe(true);
|
|
44
|
+
if (Result.isFailure(result)) {
|
|
45
|
+
globalExpect(result.failure._tag).toBe("JsonParseError");
|
|
46
|
+
globalExpect(result.failure.line).toBe("NOT_JSON");
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
it("fails with JsonParseError on schema mismatch", async () => {
|
|
50
|
+
const result = await collectResult(bytesOf(`{"op":"add","value":"not a number"}\n`).pipe(fromBytes, parse(Patch)));
|
|
51
|
+
globalExpect(Result.isFailure(result)).toBe(true);
|
|
52
|
+
if (Result.isFailure(result)) globalExpect(result.failure._tag).toBe("JsonParseError");
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
describe("JSONL round-trip", () => {
|
|
56
|
+
it("toBytes then fromBytes/parse recovers the values", async () => {
|
|
57
|
+
const values = [{
|
|
58
|
+
op: "a",
|
|
59
|
+
value: 1
|
|
60
|
+
}, {
|
|
61
|
+
op: "b",
|
|
62
|
+
value: 2
|
|
63
|
+
}];
|
|
64
|
+
globalExpect(await collect(Stream.fromIterable(values).pipe(toBytes(Patch), fromBytes, parse(Patch)))).toEqual(values);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
//#endregion
|
|
68
|
+
export {};
|
|
69
|
+
|
|
70
|
+
//# sourceMappingURL=JSONL.test.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JSONL.test.mjs","names":["JSONL.fromBytes","JSONL.parse","JSONL.toBytes"],"sources":["../../src/streaming/JSONL.test.ts"],"sourcesContent":["import { Effect, Result, Schema, Stream } from \"effect\"\nimport { describe, expect, it } from \"vitest\"\nimport * as JSONL from \"./JSONL.js\"\n\nconst enc = new TextEncoder()\nconst bytesOf = (...chunks: ReadonlyArray<string>) =>\n Stream.fromIterable(chunks.map((c) => enc.encode(c)))\n\nconst collect = <A, E>(s: Stream.Stream<A, E>) => Effect.runPromise(Stream.runCollect(s))\n\nconst collectResult = <A, E>(s: Stream.Stream<A, E>) =>\n Effect.runPromise(Effect.result(Stream.runCollect(s)))\n\nconst Patch = Schema.Struct({ op: Schema.String, value: Schema.Number })\n\ndescribe(\"JSONL.fromBytes\", () => {\n it(\"emits one string per line\", async () => {\n const out = await collect(JSONL.fromBytes(bytesOf(\"a\\nb\\nc\\n\")))\n expect(out).toEqual([\"a\", \"b\", \"c\"])\n })\n\n it(\"buffers lines across chunk boundaries\", async () => {\n const out = await collect(JSONL.fromBytes(bytesOf(\"ab\", \"c\\nde\", \"f\\n\")))\n expect(out).toEqual([\"abc\", \"def\"])\n })\n\n it(\"flushes a trailing line without a final newline\", async () => {\n const out = await collect(JSONL.fromBytes(bytesOf(\"a\\nb\")))\n expect(out).toEqual([\"a\", \"b\"])\n })\n\n it(\"ignores blank lines\", async () => {\n const out = await collect(JSONL.fromBytes(bytesOf(\"a\\n\\n\\nb\\n\")))\n expect(out).toEqual([\"a\", \"b\"])\n })\n})\n\ndescribe(\"JSONL.parse\", () => {\n it(\"decodes well-formed JSON lines through a Schema\", async () => {\n const out = await collect(\n bytesOf(`{\"op\":\"add\",\"value\":1}\\n{\"op\":\"sub\",\"value\":2}\\n`).pipe(\n JSONL.fromBytes,\n JSONL.parse(Patch),\n ),\n )\n expect(out).toEqual([\n { op: \"add\", value: 1 },\n { op: \"sub\", value: 2 },\n ])\n })\n\n it(\"fails with JsonParseError on malformed JSON\", async () => {\n const result = await collectResult(\n bytesOf(`{\"op\":\"add\",\"value\":1}\\nNOT_JSON\\n`).pipe(JSONL.fromBytes, JSONL.parse(Patch)),\n )\n expect(Result.isFailure(result)).toBe(true)\n if (Result.isFailure(result)) {\n expect(result.failure._tag).toBe(\"JsonParseError\")\n expect(result.failure.line).toBe(\"NOT_JSON\")\n }\n })\n\n it(\"fails with JsonParseError on schema mismatch\", async () => {\n const result = await collectResult(\n bytesOf(`{\"op\":\"add\",\"value\":\"not a number\"}\\n`).pipe(JSONL.fromBytes, JSONL.parse(Patch)),\n )\n expect(Result.isFailure(result)).toBe(true)\n if (Result.isFailure(result)) {\n expect(result.failure._tag).toBe(\"JsonParseError\")\n }\n })\n})\n\ndescribe(\"JSONL round-trip\", () => {\n it(\"toBytes then fromBytes/parse recovers the values\", async () => {\n const values = [\n { op: \"a\", value: 1 },\n { op: \"b\", value: 2 },\n ]\n const out = await collect(\n Stream.fromIterable(values).pipe(JSONL.toBytes(Patch), JSONL.fromBytes, JSONL.parse(Patch)),\n )\n expect(out).toEqual(values)\n })\n})\n"],"mappings":";;;;AAIA,MAAM,MAAM,IAAI,aAAa;AAC7B,MAAM,WAAW,GAAG,WAClB,OAAO,aAAa,OAAO,KAAK,MAAM,IAAI,OAAO,EAAE,CAAC,CAAC;AAEvD,MAAM,WAAiB,MAA2B,OAAO,WAAW,OAAO,WAAW,EAAE,CAAC;AAEzF,MAAM,iBAAuB,MAC3B,OAAO,WAAW,OAAO,OAAO,OAAO,WAAW,EAAE,CAAC,CAAC;AAExD,MAAM,QAAQ,OAAO,OAAO;CAAE,IAAI,OAAO;CAAQ,OAAO,OAAO;CAAQ,CAAC;AAExE,SAAS,yBAAyB;AAChC,IAAG,6BAA6B,YAAY;AAE1C,eAAO,MADW,QAAQA,UAAgB,QAAQ,YAAY,CAAC,CAAC,CACrD,CAAC,QAAQ;GAAC;GAAK;GAAK;GAAI,CAAC;GACpC;AAEF,IAAG,yCAAyC,YAAY;AAEtD,eAAO,MADW,QAAQA,UAAgB,QAAQ,MAAM,SAAS,MAAM,CAAC,CAAC,CAC9D,CAAC,QAAQ,CAAC,OAAO,MAAM,CAAC;GACnC;AAEF,IAAG,mDAAmD,YAAY;AAEhE,eAAO,MADW,QAAQA,UAAgB,QAAQ,OAAO,CAAC,CAAC,CAChD,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC;GAC/B;AAEF,IAAG,uBAAuB,YAAY;AAEpC,eAAO,MADW,QAAQA,UAAgB,QAAQ,aAAa,CAAC,CAAC,CACtD,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC;GAC/B;EACF;AAEF,SAAS,qBAAqB;AAC5B,IAAG,mDAAmD,YAAY;AAOhE,eAAO,MANW,QAChB,QAAQ,mDAAmD,CAAC,KAC1DA,WACAC,MAAY,MAAM,CACnB,CACF,CACU,CAAC,QAAQ,CAClB;GAAE,IAAI;GAAO,OAAO;GAAG,EACvB;GAAE,IAAI;GAAO,OAAO;GAAG,CACxB,CAAC;GACF;AAEF,IAAG,+CAA+C,YAAY;EAC5D,MAAM,SAAS,MAAM,cACnB,QAAQ,qCAAqC,CAAC,KAAKD,WAAiBC,MAAY,MAAM,CAAC,CACxF;AACD,eAAO,OAAO,UAAU,OAAO,CAAC,CAAC,KAAK,KAAK;AAC3C,MAAI,OAAO,UAAU,OAAO,EAAE;AAC5B,gBAAO,OAAO,QAAQ,KAAK,CAAC,KAAK,iBAAiB;AAClD,gBAAO,OAAO,QAAQ,KAAK,CAAC,KAAK,WAAW;;GAE9C;AAEF,IAAG,gDAAgD,YAAY;EAC7D,MAAM,SAAS,MAAM,cACnB,QAAQ,wCAAwC,CAAC,KAAKD,WAAiBC,MAAY,MAAM,CAAC,CAC3F;AACD,eAAO,OAAO,UAAU,OAAO,CAAC,CAAC,KAAK,KAAK;AAC3C,MAAI,OAAO,UAAU,OAAO,CAC1B,cAAO,OAAO,QAAQ,KAAK,CAAC,KAAK,iBAAiB;GAEpD;EACF;AAEF,SAAS,0BAA0B;AACjC,IAAG,oDAAoD,YAAY;EACjE,MAAM,SAAS,CACb;GAAE,IAAI;GAAK,OAAO;GAAG,EACrB;GAAE,IAAI;GAAK,OAAO;GAAG,CACtB;AAID,eAAO,MAHW,QAChB,OAAO,aAAa,OAAO,CAAC,KAAKC,QAAc,MAAM,EAAEF,WAAiBC,MAAY,MAAM,CAAC,CAC5F,CACU,CAAC,QAAQ,OAAO;GAC3B;EACF"}
|
package/dist/streaming/Lines.mjs
CHANGED
package/dist/streaming/SSE.d.mts
CHANGED
|
@@ -10,11 +10,11 @@ declare namespace SSE_d_exports {
|
|
|
10
10
|
* - `data`: payload, with multiple `data:` lines joined by `\n`
|
|
11
11
|
* - `id`: optional last-event id
|
|
12
12
|
*/
|
|
13
|
-
|
|
13
|
+
type Event = {
|
|
14
14
|
readonly event?: string;
|
|
15
15
|
readonly data: string;
|
|
16
16
|
readonly id?: string;
|
|
17
|
-
}
|
|
17
|
+
};
|
|
18
18
|
/**
|
|
19
19
|
* Decode a `Stream<Uint8Array>` (e.g. an HTTP response body) into a
|
|
20
20
|
* `Stream<SSE.Event>`. Handles partial UTF-8 sequences, CRLF/LF line
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SSE.d.mts","names":[],"sources":["../../src/streaming/SSE.ts"],"mappings":";;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"SSE.d.mts","names":[],"sources":["../../src/streaming/SSE.ts"],"mappings":";;;;;;;;;;;;KAQY,KAAA;EAAA,SACD,KAAA;EAAA,SACA,IAAA;EAAA,SACA,EAAA;AAAA;;;;;;cA4EE,SAAA,SACX,IAAA,EAAM,MAAA,CAAO,MAAA,CAAO,UAAA,EAAY,CAAA,EAAG,CAAA,MAClC,MAAA,CAAO,MAAA,CAAO,KAAA,EAAO,CAAA,EAAG,CAAA;AAF3B;;;;AAAA,cAyBa,OAAA,SAAiB,IAAA,EAAM,MAAA,CAAO,MAAA,CAAO,KAAA,EAAO,CAAA,EAAG,CAAA,MAAK,MAAA,CAAO,MAAA,CAAO,UAAA,EAAY,CAAA,EAAG,CAAA"}
|
package/dist/streaming/SSE.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SSE.mjs","names":[],"sources":["../../src/streaming/SSE.ts"],"sourcesContent":["import { Stream } from \"effect\"\n\n/**\n * One Server-Sent Event. Fields per the WHATWG spec:\n * - `event`: optional event name (default \"message\" on the wire)\n * - `data`: payload, with multiple `data:` lines joined by `\\n`\n * - `id`: optional last-event id\n */\nexport
|
|
1
|
+
{"version":3,"file":"SSE.mjs","names":[],"sources":["../../src/streaming/SSE.ts"],"sourcesContent":["import { Stream } from \"effect\"\n\n/**\n * One Server-Sent Event. Fields per the WHATWG spec:\n * - `event`: optional event name (default \"message\" on the wire)\n * - `data`: payload, with multiple `data:` lines joined by `\\n`\n * - `id`: optional last-event id\n */\nexport type Event = {\n readonly event?: string\n readonly data: string\n readonly id?: string\n}\n\n// ---------------------------------------------------------------------------\n// Generic stream helpers (kept module-local for now; promote to a shared\n// Stream module once a third caller appears).\n// ---------------------------------------------------------------------------\n\n/** Decode `Uint8Array` chunks as UTF-8, handling multi-byte boundaries. */\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\n/** Split a text stream on a separator, buffering across chunk boundaries. */\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// Parser\n// ---------------------------------------------------------------------------\n\nconst parseField = (line: string): readonly [string, string] => {\n const colon = line.indexOf(\":\")\n if (colon < 0) return [line, \"\"]\n const value = line.slice(colon + 1)\n return [line.slice(0, colon), value.startsWith(\" \") ? value.slice(1) : value]\n}\n\nconst parseBlock = (block: string): Event | null => {\n const lines = block.split(\"\\n\").filter((l) => l.length > 0 && !l.startsWith(\":\"))\n if (lines.length === 0) return null\n\n const fields = lines.map(parseField)\n const dataLines = fields.filter(([f]) => f === \"data\").map(([, v]) => v)\n const event = fields.find(([f]) => f === \"event\")?.[1]\n const id = fields.find(([f]) => f === \"id\")?.[1]\n\n const out: { event?: string; data: string; id?: string } = {\n data: dataLines.join(\"\\n\"),\n }\n if (event !== undefined) out.event = event\n if (id !== undefined) out.id = id\n return out as Event\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Decode a `Stream<Uint8Array>` (e.g. an HTTP response body) into a\n * `Stream<SSE.Event>`. Handles partial UTF-8 sequences, CRLF/LF line\n * endings, and events split across chunk boundaries.\n */\nexport const fromBytes = <E, R>(\n self: Stream.Stream<Uint8Array, E, R>,\n): Stream.Stream<Event, E, R> =>\n self.pipe(\n decodeText,\n Stream.map((s) => s.replace(/\\r/g, \"\")), // SSE allows CRLF; normalize to LF\n splitOn(\"\\n\\n\"),\n Stream.map(parseBlock),\n Stream.filter((ev): ev is Event => ev !== null),\n )\n\nconst eventToString = (ev: Event): string => {\n const parts: string[] = []\n if (ev.event !== undefined) parts.push(`event: ${ev.event}`)\n if (ev.id !== undefined) parts.push(`id: ${ev.id}`)\n for (const line of ev.data.split(\"\\n\")) parts.push(`data: ${line}`)\n return parts.join(\"\\n\") + \"\\n\\n\"\n}\n\nconst encoder = new TextEncoder()\n\n/**\n * Encode a `Stream<Event>` as `Stream<Uint8Array>` ready to send on an\n * HTTP response with `Content-Type: text/event-stream`.\n */\nexport const toBytes = <E, R>(self: Stream.Stream<Event, E, R>): Stream.Stream<Uint8Array, E, R> =>\n Stream.map(self, (ev) => encoder.encode(eventToString(ev)))\n"],"mappings":";;;;;;;;AAoBA,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;;AAGH,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;AAML,MAAM,cAAc,SAA4C;CAC9D,MAAM,QAAQ,KAAK,QAAQ,IAAI;AAC/B,KAAI,QAAQ,EAAG,QAAO,CAAC,MAAM,GAAG;CAChC,MAAM,QAAQ,KAAK,MAAM,QAAQ,EAAE;AACnC,QAAO,CAAC,KAAK,MAAM,GAAG,MAAM,EAAE,MAAM,WAAW,IAAI,GAAG,MAAM,MAAM,EAAE,GAAG,MAAM;;AAG/E,MAAM,cAAc,UAAgC;CAClD,MAAM,QAAQ,MAAM,MAAM,KAAK,CAAC,QAAQ,MAAM,EAAE,SAAS,KAAK,CAAC,EAAE,WAAW,IAAI,CAAC;AACjF,KAAI,MAAM,WAAW,EAAG,QAAO;CAE/B,MAAM,SAAS,MAAM,IAAI,WAAW;CACpC,MAAM,YAAY,OAAO,QAAQ,CAAC,OAAO,MAAM,OAAO,CAAC,KAAK,GAAG,OAAO,EAAE;CACxE,MAAM,QAAQ,OAAO,MAAM,CAAC,OAAO,MAAM,QAAQ,GAAG;CACpD,MAAM,KAAK,OAAO,MAAM,CAAC,OAAO,MAAM,KAAK,GAAG;CAE9C,MAAM,MAAqD,EACzD,MAAM,UAAU,KAAK,KAAK,EAC3B;AACD,KAAI,UAAU,KAAA,EAAW,KAAI,QAAQ;AACrC,KAAI,OAAO,KAAA,EAAW,KAAI,KAAK;AAC/B,QAAO;;;;;;;AAYT,MAAa,aACX,SAEA,KAAK,KACH,YACA,OAAO,KAAK,MAAM,EAAE,QAAQ,OAAO,GAAG,CAAC,EACvC,QAAQ,OAAO,EACf,OAAO,IAAI,WAAW,EACtB,OAAO,QAAQ,OAAoB,OAAO,KAAK,CAChD;AAEH,MAAM,iBAAiB,OAAsB;CAC3C,MAAM,QAAkB,EAAE;AAC1B,KAAI,GAAG,UAAU,KAAA,EAAW,OAAM,KAAK,UAAU,GAAG,QAAQ;AAC5D,KAAI,GAAG,OAAO,KAAA,EAAW,OAAM,KAAK,OAAO,GAAG,KAAK;AACnD,MAAK,MAAM,QAAQ,GAAG,KAAK,MAAM,KAAK,CAAE,OAAM,KAAK,SAAS,OAAO;AACnE,QAAO,MAAM,KAAK,KAAK,GAAG;;AAG5B,MAAM,UAAU,IAAI,aAAa;;;;;AAMjC,MAAa,WAAiB,SAC5B,OAAO,IAAI,OAAO,OAAO,QAAQ,OAAO,cAAc,GAAG,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { fromBytes, toBytes } from "./SSE.mjs";
|
|
2
|
+
import { i as it, n as globalExpect, r as describe } from "../dist-DV5ISja1.mjs";
|
|
3
|
+
import { Effect, Stream } from "effect";
|
|
4
|
+
//#region src/streaming/SSE.test.ts
|
|
5
|
+
const enc = new TextEncoder();
|
|
6
|
+
const bytesOf = (...chunks) => Stream.fromIterable(chunks.map((c) => enc.encode(c)));
|
|
7
|
+
const collect = (s) => Effect.runPromise(Stream.runCollect(s));
|
|
8
|
+
describe("SSE.fromBytes", () => {
|
|
9
|
+
it("parses a single complete event", async () => {
|
|
10
|
+
globalExpect(await collect(fromBytes(bytesOf("event: foo\ndata: hello\n\n")))).toEqual([{
|
|
11
|
+
event: "foo",
|
|
12
|
+
data: "hello"
|
|
13
|
+
}]);
|
|
14
|
+
});
|
|
15
|
+
it("joins multiple data lines with \\n", async () => {
|
|
16
|
+
globalExpect(await collect(fromBytes(bytesOf("data: line1\ndata: line2\ndata: line3\n\n")))).toEqual([{ data: "line1\nline2\nline3" }]);
|
|
17
|
+
});
|
|
18
|
+
it("handles events split across chunk boundaries", async () => {
|
|
19
|
+
globalExpect(await collect(fromBytes(bytesOf("event: split\nda", "ta: hi\n", "\nevent: next\ndata: x\n\n")))).toEqual([{
|
|
20
|
+
event: "split",
|
|
21
|
+
data: "hi"
|
|
22
|
+
}, {
|
|
23
|
+
event: "next",
|
|
24
|
+
data: "x"
|
|
25
|
+
}]);
|
|
26
|
+
});
|
|
27
|
+
it("handles CRLF line endings", async () => {
|
|
28
|
+
globalExpect(await collect(fromBytes(bytesOf("event: a\r\ndata: b\r\n\r\n")))).toEqual([{
|
|
29
|
+
event: "a",
|
|
30
|
+
data: "b"
|
|
31
|
+
}]);
|
|
32
|
+
});
|
|
33
|
+
it("preserves id and skips comment lines", async () => {
|
|
34
|
+
globalExpect(await collect(fromBytes(bytesOf(": ping\nid: 42\ndata: x\n\n")))).toEqual([{
|
|
35
|
+
id: "42",
|
|
36
|
+
data: "x"
|
|
37
|
+
}]);
|
|
38
|
+
});
|
|
39
|
+
it("flushes a trailing event without a closing blank line", async () => {
|
|
40
|
+
globalExpect(await collect(fromBytes(bytesOf("data: tail")))).toEqual([{ data: "tail" }]);
|
|
41
|
+
});
|
|
42
|
+
it("ignores empty blocks between events", async () => {
|
|
43
|
+
globalExpect(await collect(fromBytes(bytesOf("data: a\n\n\n\ndata: b\n\n")))).toEqual([{ data: "a" }, { data: "b" }]);
|
|
44
|
+
});
|
|
45
|
+
it("handles a UTF-8 multi-byte char split across chunks", async () => {
|
|
46
|
+
const squidBytes = enc.encode("data: 🦑\n\n");
|
|
47
|
+
const a = squidBytes.slice(0, 8);
|
|
48
|
+
const b = squidBytes.slice(8);
|
|
49
|
+
globalExpect(await collect(fromBytes(Stream.fromIterable([a, b])))).toEqual([{ data: "🦑" }]);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
describe("SSE.toBytes round-trip", () => {
|
|
53
|
+
it("re-parses what it serializes", async () => {
|
|
54
|
+
const events = [
|
|
55
|
+
{
|
|
56
|
+
event: "a",
|
|
57
|
+
data: "hello"
|
|
58
|
+
},
|
|
59
|
+
{ data: "multi\nline" },
|
|
60
|
+
{
|
|
61
|
+
event: "b",
|
|
62
|
+
id: "7",
|
|
63
|
+
data: "x"
|
|
64
|
+
}
|
|
65
|
+
];
|
|
66
|
+
globalExpect(await collect(Stream.fromIterable(events).pipe(toBytes, fromBytes))).toEqual(events);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
//#endregion
|
|
70
|
+
export {};
|
|
71
|
+
|
|
72
|
+
//# sourceMappingURL=SSE.test.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SSE.test.mjs","names":["SSE.fromBytes","SSE.toBytes"],"sources":["../../src/streaming/SSE.test.ts"],"sourcesContent":["import { Effect, Stream } from \"effect\"\nimport { describe, expect, it } from \"vitest\"\nimport * as SSE from \"./SSE.js\"\n\nconst enc = new TextEncoder()\nconst bytesOf = (...chunks: ReadonlyArray<string>) =>\n Stream.fromIterable(chunks.map((c) => enc.encode(c)))\n\nconst collect = <A, E>(s: Stream.Stream<A, E>) => Effect.runPromise(Stream.runCollect(s))\n\ndescribe(\"SSE.fromBytes\", () => {\n it(\"parses a single complete event\", async () => {\n const out = await collect(SSE.fromBytes(bytesOf(\"event: foo\\ndata: hello\\n\\n\")))\n expect(out).toEqual([{ event: \"foo\", data: \"hello\" }])\n })\n\n it(\"joins multiple data lines with \\\\n\", async () => {\n const out = await collect(SSE.fromBytes(bytesOf(\"data: line1\\ndata: line2\\ndata: line3\\n\\n\")))\n expect(out).toEqual([{ data: \"line1\\nline2\\nline3\" }])\n })\n\n it(\"handles events split across chunk boundaries\", async () => {\n const out = await collect(\n SSE.fromBytes(bytesOf(\"event: split\\nda\", \"ta: hi\\n\", \"\\nevent: next\\ndata: x\\n\\n\")),\n )\n expect(out).toEqual([\n { event: \"split\", data: \"hi\" },\n { event: \"next\", data: \"x\" },\n ])\n })\n\n it(\"handles CRLF line endings\", async () => {\n const out = await collect(SSE.fromBytes(bytesOf(\"event: a\\r\\ndata: b\\r\\n\\r\\n\")))\n expect(out).toEqual([{ event: \"a\", data: \"b\" }])\n })\n\n it(\"preserves id and skips comment lines\", async () => {\n const out = await collect(SSE.fromBytes(bytesOf(\": ping\\nid: 42\\ndata: x\\n\\n\")))\n expect(out).toEqual([{ id: \"42\", data: \"x\" }])\n })\n\n it(\"flushes a trailing event without a closing blank line\", async () => {\n const out = await collect(SSE.fromBytes(bytesOf(\"data: tail\")))\n expect(out).toEqual([{ data: \"tail\" }])\n })\n\n it(\"ignores empty blocks between events\", async () => {\n const out = await collect(SSE.fromBytes(bytesOf(\"data: a\\n\\n\\n\\ndata: b\\n\\n\")))\n expect(out).toEqual([{ data: \"a\" }, { data: \"b\" }])\n })\n\n it(\"handles a UTF-8 multi-byte char split across chunks\", async () => {\n // \"🦑\" is 0xF0 0x9F 0xA6 0x91. Split between bytes 2 and 3.\n const squidBytes = enc.encode(\"data: 🦑\\n\\n\")\n const a = squidBytes.slice(0, 8) // \"data: \" + first 2 bytes of squid\n const b = squidBytes.slice(8) // remaining squid bytes + \"\\n\\n\"\n const out = await collect(SSE.fromBytes(Stream.fromIterable([a, b])))\n expect(out).toEqual([{ data: \"🦑\" }])\n })\n})\n\ndescribe(\"SSE.toBytes round-trip\", () => {\n it(\"re-parses what it serializes\", async () => {\n const events: ReadonlyArray<SSE.Event> = [\n { event: \"a\", data: \"hello\" },\n { data: \"multi\\nline\" },\n { event: \"b\", id: \"7\", data: \"x\" },\n ]\n const reparsed = await collect(Stream.fromIterable(events).pipe(SSE.toBytes, SSE.fromBytes))\n expect(reparsed).toEqual(events)\n })\n})\n"],"mappings":";;;;AAIA,MAAM,MAAM,IAAI,aAAa;AAC7B,MAAM,WAAW,GAAG,WAClB,OAAO,aAAa,OAAO,KAAK,MAAM,IAAI,OAAO,EAAE,CAAC,CAAC;AAEvD,MAAM,WAAiB,MAA2B,OAAO,WAAW,OAAO,WAAW,EAAE,CAAC;AAEzF,SAAS,uBAAuB;AAC9B,IAAG,kCAAkC,YAAY;AAE/C,eAAO,MADW,QAAQA,UAAc,QAAQ,8BAA8B,CAAC,CAAC,CACrE,CAAC,QAAQ,CAAC;GAAE,OAAO;GAAO,MAAM;GAAS,CAAC,CAAC;GACtD;AAEF,IAAG,sCAAsC,YAAY;AAEnD,eAAO,MADW,QAAQA,UAAc,QAAQ,4CAA4C,CAAC,CAAC,CACnF,CAAC,QAAQ,CAAC,EAAE,MAAM,uBAAuB,CAAC,CAAC;GACtD;AAEF,IAAG,gDAAgD,YAAY;AAI7D,eAAO,MAHW,QAChBA,UAAc,QAAQ,oBAAoB,YAAY,6BAA6B,CAAC,CACrF,CACU,CAAC,QAAQ,CAClB;GAAE,OAAO;GAAS,MAAM;GAAM,EAC9B;GAAE,OAAO;GAAQ,MAAM;GAAK,CAC7B,CAAC;GACF;AAEF,IAAG,6BAA6B,YAAY;AAE1C,eAAO,MADW,QAAQA,UAAc,QAAQ,8BAA8B,CAAC,CAAC,CACrE,CAAC,QAAQ,CAAC;GAAE,OAAO;GAAK,MAAM;GAAK,CAAC,CAAC;GAChD;AAEF,IAAG,wCAAwC,YAAY;AAErD,eAAO,MADW,QAAQA,UAAc,QAAQ,8BAA8B,CAAC,CAAC,CACrE,CAAC,QAAQ,CAAC;GAAE,IAAI;GAAM,MAAM;GAAK,CAAC,CAAC;GAC9C;AAEF,IAAG,yDAAyD,YAAY;AAEtE,eAAO,MADW,QAAQA,UAAc,QAAQ,aAAa,CAAC,CAAC,CACpD,CAAC,QAAQ,CAAC,EAAE,MAAM,QAAQ,CAAC,CAAC;GACvC;AAEF,IAAG,uCAAuC,YAAY;AAEpD,eAAO,MADW,QAAQA,UAAc,QAAQ,6BAA6B,CAAC,CAAC,CACpE,CAAC,QAAQ,CAAC,EAAE,MAAM,KAAK,EAAE,EAAE,MAAM,KAAK,CAAC,CAAC;GACnD;AAEF,IAAG,uDAAuD,YAAY;EAEpE,MAAM,aAAa,IAAI,OAAO,eAAe;EAC7C,MAAM,IAAI,WAAW,MAAM,GAAG,EAAE;EAChC,MAAM,IAAI,WAAW,MAAM,EAAE;AAE7B,eAAO,MADW,QAAQA,UAAc,OAAO,aAAa,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAC1D,CAAC,QAAQ,CAAC,EAAE,MAAM,MAAM,CAAC,CAAC;GACrC;EACF;AAEF,SAAS,gCAAgC;AACvC,IAAG,gCAAgC,YAAY;EAC7C,MAAM,SAAmC;GACvC;IAAE,OAAO;IAAK,MAAM;IAAS;GAC7B,EAAE,MAAM,eAAe;GACvB;IAAE,OAAO;IAAK,IAAI;IAAK,MAAM;IAAK;GACnC;AAED,eAAO,MADgB,QAAQ,OAAO,aAAa,OAAO,CAAC,KAAKC,SAAaD,UAAc,CAAC,CAC5E,CAAC,QAAQ,OAAO;GAChC;EACF"}
|
|
@@ -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-
|
|
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 };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as __exportAll } from "../chunk-uyGKjUfl.mjs";
|
|
2
2
|
import { Data, Effect, Match, Schema, Stream, pipe } from "effect";
|
|
3
3
|
//#region src/structured-format/StructuredFormat.ts
|
|
4
4
|
var StructuredFormat_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StructuredFormat.mjs","names":[],"sources":["../../src/structured-format/StructuredFormat.ts"],"sourcesContent":["import type { StandardJSONSchemaV1, StandardSchemaV1 } from \"@standard-schema/spec\"\nimport { Data, Effect, Match, Schema, Stream, pipe } from \"effect\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Cross-validator schema constraint for structured outputs. Any schema\n * implementing both Standard Schema (runtime validation) and Standard\n * JSON Schema (wire encoding) works directly: Zod 4+, Valibot, ArkType,\n * and Effect Schema after `fromEffectSchema`.\n */\nexport type StructuredSchema<Output = unknown> = StandardSchemaV1<unknown, Output> &\n StandardJSONSchemaV1<unknown, Output>\n\n/**\n * A schema-bound output the user wants the model to produce. Pairs the\n * cross-validator schema with metadata providers need (name, description,\n * strict-mode flag).\n */\nexport
|
|
1
|
+
{"version":3,"file":"StructuredFormat.mjs","names":[],"sources":["../../src/structured-format/StructuredFormat.ts"],"sourcesContent":["import type { StandardJSONSchemaV1, StandardSchemaV1 } from \"@standard-schema/spec\"\nimport { Data, Effect, Match, Schema, Stream, pipe } from \"effect\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Cross-validator schema constraint for structured outputs. Any schema\n * implementing both Standard Schema (runtime validation) and Standard\n * JSON Schema (wire encoding) works directly: Zod 4+, Valibot, ArkType,\n * and Effect Schema after `fromEffectSchema`.\n */\nexport type StructuredSchema<Output = unknown> = StandardSchemaV1<unknown, Output> &\n StandardJSONSchemaV1<unknown, Output>\n\n/**\n * A schema-bound output the user wants the model to produce. Pairs the\n * cross-validator schema with metadata providers need (name, description,\n * strict-mode flag).\n */\nexport type StructuredFormat<A> = {\n readonly name: string\n readonly description?: string\n readonly schema: StructuredSchema<A>\n /**\n * Provider strict-mode flag. OpenAI, Anthropic, and Mistral honour it\n * (constrained decoding); other providers ignore.\n */\n readonly strict?: boolean\n}\n\n/** A single path-scoped validation problem. Library-agnostic shape. */\nexport type DecodeIssue = {\n readonly path: ReadonlyArray<string | number>\n readonly message: string\n}\n\n// ---------------------------------------------------------------------------\n// Errors\n// ---------------------------------------------------------------------------\n\n/**\n * Schema validation failed. `raw` is the original text (or stringified\n * value) that failed; `issues` is a flat list of per-field problems.\n */\nexport class StructuredDecodeError extends Data.TaggedError(\"StructuredDecodeError\")<{\n readonly raw: string\n readonly issues: ReadonlyArray<DecodeIssue>\n}> {}\n\n/**\n * `JSON.parse` threw on a string that was supposed to be JSON. Distinct\n * from `StructuredDecodeError`: the bytes weren't even JSON.\n */\nexport class JsonParseError extends Data.TaggedError(\"StructuredJsonParseError\")<{\n readonly raw: string\n readonly cause: unknown\n}> {}\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\n/**\n * Wrap an Effect `Schema` as a `StructuredFormat`. Effect Schema doesn't\n * natively implement Standard Schema; this helper installs the\n * `~standard` and JSON Schema interfaces.\n */\nexport const fromEffectSchema = <S extends Schema.Codec<any, any, never, any>>(\n schema: S,\n options?: {\n readonly name?: string\n readonly description?: string\n readonly strict?: boolean\n },\n): StructuredFormat<S[\"Type\"]> => ({\n name: options?.name ?? \"output\",\n schema: Schema.toStandardJSONSchemaV1(Schema.toStandardSchemaV1(schema)),\n ...(options?.description !== undefined && {\n description: options.description,\n }),\n ...(options?.strict !== undefined && { strict: options.strict }),\n})\n\n// ---------------------------------------------------------------------------\n// Standard Schema → DecodeIssue\n// ---------------------------------------------------------------------------\n\nconst propertyKeyToScalar = Match.type<PropertyKey>().pipe(\n Match.when(Match.string, (s) => s),\n Match.when(Match.number, (n) => n),\n Match.when(Match.symbol, (s) => s.toString()),\n Match.exhaustive,\n)\n\nconst segmentToKey = Match.type<PropertyKey | StandardSchemaV1.PathSegment>().pipe(\n Match.when(Match.string, (s) => s),\n Match.when(Match.number, (n) => n),\n Match.when(Match.symbol, (s) => s.toString()),\n Match.orElse((segment) => propertyKeyToScalar(segment.key)),\n)\n\nconst issueToDecode = (issue: StandardSchemaV1.Issue): DecodeIssue => ({\n path: (issue.path ?? []).map(segmentToKey),\n message: issue.message,\n})\n\n// ---------------------------------------------------------------------------\n// Decoding\n// ---------------------------------------------------------------------------\n\n/**\n * Validate an `unknown` against the format's schema. Returns the typed\n * value or a `StructuredDecodeError`. Standard Schema's `validate` may\n * be async; this function handles both sync and async results.\n */\nexport const decode =\n <A>(format: StructuredFormat<A>) =>\n (raw: unknown): Effect.Effect<A, StructuredDecodeError> =>\n pipe(\n Effect.promise(async () => format.schema[\"~standard\"].validate(raw)),\n Effect.flatMap((result) =>\n result.issues === undefined\n ? Effect.succeed(result.value)\n : Effect.fail(\n new StructuredDecodeError({\n raw: typeof raw === \"string\" ? raw : JSON.stringify(raw),\n issues: result.issues.map(issueToDecode),\n }),\n ),\n ),\n )\n\n/**\n * Parse a JSON string then validate against the format's schema. Two\n * failure modes: `JsonParseError` (bytes weren't JSON) and\n * `StructuredDecodeError` (JSON didn't match the schema).\n */\nexport const parseJson =\n <A>(format: StructuredFormat<A>) =>\n (raw: string): Effect.Effect<A, JsonParseError | StructuredDecodeError> =>\n pipe(\n Effect.try({\n try: () => JSON.parse(raw),\n catch: (cause) => new JsonParseError({ raw, cause }),\n }),\n Effect.flatMap(decode(format)),\n )\n\n/**\n * Stream operator: each input string is JSON-parsed and validated.\n * Failures surface in the stream's failure channel, distinguished by tag.\n */\nexport const decodeJsonLines =\n <A>(format: StructuredFormat<A>) =>\n <E, R>(\n self: Stream.Stream<string, E, R>,\n ): Stream.Stream<A, E | JsonParseError | StructuredDecodeError, R> =>\n self.pipe(Stream.mapEffect(parseJson(format)))\n"],"mappings":";;;;;;;;;;;;;;;AA8CA,IAAa,wBAAb,cAA2C,KAAK,YAAY,wBAAwB,CAGjF;;;;;AAMH,IAAa,iBAAb,cAAoC,KAAK,YAAY,2BAA2B,CAG7E;;;;;;AAWH,MAAa,oBACX,QACA,aAKiC;CACjC,MAAM,SAAS,QAAQ;CACvB,QAAQ,OAAO,uBAAuB,OAAO,mBAAmB,OAAO,CAAC;CACxE,GAAI,SAAS,gBAAgB,KAAA,KAAa,EACxC,aAAa,QAAQ,aACtB;CACD,GAAI,SAAS,WAAW,KAAA,KAAa,EAAE,QAAQ,QAAQ,QAAQ;CAChE;AAMD,MAAM,sBAAsB,MAAM,MAAmB,CAAC,KACpD,MAAM,KAAK,MAAM,SAAS,MAAM,EAAE,EAClC,MAAM,KAAK,MAAM,SAAS,MAAM,EAAE,EAClC,MAAM,KAAK,MAAM,SAAS,MAAM,EAAE,UAAU,CAAC,EAC7C,MAAM,WACP;AAED,MAAM,eAAe,MAAM,MAAkD,CAAC,KAC5E,MAAM,KAAK,MAAM,SAAS,MAAM,EAAE,EAClC,MAAM,KAAK,MAAM,SAAS,MAAM,EAAE,EAClC,MAAM,KAAK,MAAM,SAAS,MAAM,EAAE,UAAU,CAAC,EAC7C,MAAM,QAAQ,YAAY,oBAAoB,QAAQ,IAAI,CAAC,CAC5D;AAED,MAAM,iBAAiB,WAAgD;CACrE,OAAO,MAAM,QAAQ,EAAE,EAAE,IAAI,aAAa;CAC1C,SAAS,MAAM;CAChB;;;;;;AAWD,MAAa,UACP,YACH,QACC,KACE,OAAO,QAAQ,YAAY,OAAO,OAAO,aAAa,SAAS,IAAI,CAAC,EACpE,OAAO,SAAS,WACd,OAAO,WAAW,KAAA,IACd,OAAO,QAAQ,OAAO,MAAM,GAC5B,OAAO,KACL,IAAI,sBAAsB;CACxB,KAAK,OAAO,QAAQ,WAAW,MAAM,KAAK,UAAU,IAAI;CACxD,QAAQ,OAAO,OAAO,IAAI,cAAc;CACzC,CAAC,CACH,CACN,CACF;;;;;;AAOL,MAAa,aACP,YACH,QACC,KACE,OAAO,IAAI;CACT,WAAW,KAAK,MAAM,IAAI;CAC1B,QAAQ,UAAU,IAAI,eAAe;EAAE;EAAK;EAAO,CAAC;CACrD,CAAC,EACF,OAAO,QAAQ,OAAO,OAAO,CAAC,CAC/B;;;;;AAML,MAAa,mBACP,YAEF,SAEA,KAAK,KAAK,OAAO,UAAU,UAAU,OAAO,CAAC,CAAC"}
|
|
@@ -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,29 +1,29 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { r as Turn } from "../Turn-
|
|
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
|
|
|
6
6
|
//#region src/testing/MockProvider.d.ts
|
|
7
|
-
|
|
7
|
+
type MockOptions = {
|
|
8
8
|
/**
|
|
9
9
|
* If set, deltas of each scripted turn are spaced by this duration via
|
|
10
10
|
* `Schedule.spaced`. Combine with `TestClock.adjust` for deterministic
|
|
11
11
|
* timing in tests.
|
|
12
12
|
*/
|
|
13
13
|
readonly deltaInterval?: Duration.Input;
|
|
14
|
-
}
|
|
14
|
+
};
|
|
15
15
|
/**
|
|
16
16
|
* A scripted mock provider. Pre-canned `Turn` outputs are returned in order,
|
|
17
17
|
* one per call to `streamTurn`. Each scripted turn is split into synthetic
|
|
18
18
|
* deltas (text → tool_call_start → tool_call_args_delta → ... → turn_complete)
|
|
19
19
|
* so streaming consumers can see realistic delta shapes.
|
|
20
20
|
*/
|
|
21
|
-
|
|
21
|
+
type MockRecorder = {
|
|
22
22
|
readonly calls: ReadonlyArray<{
|
|
23
23
|
readonly history: ReadonlyArray<Item>;
|
|
24
24
|
readonly turn: Turn;
|
|
25
25
|
}>;
|
|
26
|
-
}
|
|
26
|
+
};
|
|
27
27
|
declare const layer: (scriptedTurns: ReadonlyArray<Turn>, options?: MockOptions) => Layer.Layer<LanguageModel>;
|
|
28
28
|
/**
|
|
29
29
|
* Synchronous constructor that returns the `LanguageModelService` value
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MockProvider.d.mts","names":[],"sources":["../../src/testing/MockProvider.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"MockProvider.d.mts","names":[],"sources":["../../src/testing/MockProvider.ts"],"mappings":";;;;;;KAMY,WAAA;;AAAZ;;;;WAMW,aAAA,GAAgB,QAAA,CAAS,KAAA;AAAA;;;;AASpC;;;KAAY,YAAA;EAAA,SACD,KAAA,EAAO,aAAA;IAAA,SACL,OAAA,EAAS,aAAA,CAAc,IAAA;IAAA,SACvB,IAAA,EAAM,IAAA;EAAA;AAAA;AAAA,cAqEN,KAAA,GACX,aAAA,EAAe,aAAA,CAAc,IAAA,GAC7B,OAAA,GAAU,WAAA,KACT,KAAA,CAAM,KAAA,CAAM,aAAA;;;;;;;cAQF,IAAA,GACX,aAAA,EAAe,aAAA,CAAc,IAAA,GAC7B,OAAA,GAAU,WAAA;EAAA,SAED,OAAA,EAAS,oBAAA;EAAA,SACT,QAAA,EAAU,MAAA,CAAO,MAAA,CAAO,YAAA;AAAA;;;;;cAiCtB,iBAAA,GACX,aAAA,EAAe,aAAA,CAAc,IAAA,GAC7B,OAAA,GAAU,WAAA;EAAA,SAED,KAAA,EAAO,KAAA,CAAM,KAAA,CAAM,aAAA;EAAA,SACnB,QAAA,EAAU,MAAA,CAAO,MAAA,CAAO,YAAA;AAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MockProvider.mjs","names":["AiError.InvalidRequest"],"sources":["../../src/testing/MockProvider.ts"],"sourcesContent":["import { Duration, Effect, Layer, Ref, Schedule, Stream } from \"effect\"\nimport * as AiError from \"../domain/AiError.js\"\nimport type { Item } from \"../domain/Items.js\"\nimport { LanguageModel, type LanguageModelService } from \"../language-model/LanguageModel.js\"\nimport type { Turn, TurnEvent } from \"../domain/Turn.js\"\n\nexport
|
|
1
|
+
{"version":3,"file":"MockProvider.mjs","names":["AiError.InvalidRequest"],"sources":["../../src/testing/MockProvider.ts"],"sourcesContent":["import { Duration, Effect, Layer, Ref, Schedule, Stream } from \"effect\"\nimport * as AiError from \"../domain/AiError.js\"\nimport type { Item } from \"../domain/Items.js\"\nimport { LanguageModel, type LanguageModelService } from \"../language-model/LanguageModel.js\"\nimport type { Turn, TurnEvent } from \"../domain/Turn.js\"\n\nexport type MockOptions = {\n /**\n * If set, deltas of each scripted turn are spaced by this duration via\n * `Schedule.spaced`. Combine with `TestClock.adjust` for deterministic\n * timing in tests.\n */\n readonly deltaInterval?: Duration.Input\n}\n\n/**\n * A scripted mock provider. Pre-canned `Turn` outputs are returned in order,\n * one per call to `streamTurn`. Each scripted turn is split into synthetic\n * deltas (text → tool_call_start → tool_call_args_delta → ... → turn_complete)\n * so streaming consumers can see realistic delta shapes.\n */\nexport type MockRecorder = {\n readonly calls: ReadonlyArray<{\n readonly history: ReadonlyArray<Item>\n readonly turn: Turn\n }>\n}\n\nconst turnToDeltas = (turn: Turn): ReadonlyArray<TurnEvent> => {\n const deltas: TurnEvent[] = []\n for (const item of turn.items) {\n if (item.type === \"message\" && item.role === \"assistant\") {\n for (const block of item.content) {\n if (block.type === \"output_text\") {\n deltas.push({ type: \"text_delta\", text: block.text })\n }\n }\n } else if (item.type === \"function_call\") {\n deltas.push({\n type: \"tool_call_start\",\n call_id: item.call_id,\n name: item.name,\n })\n deltas.push({\n type: \"tool_call_args_delta\",\n call_id: item.call_id,\n delta: item.arguments,\n })\n } else if (item.type === \"reasoning\" && item.summary !== undefined) {\n deltas.push({ type: \"reasoning_delta\", text: item.summary, kind: \"summary\" })\n }\n }\n deltas.push({ type: \"turn_complete\", turn })\n return deltas\n}\n\nconst pacedDeltas = (turn: Turn, options?: MockOptions): Stream.Stream<TurnEvent> => {\n const base = Stream.fromIterable(turnToDeltas(turn))\n return options?.deltaInterval === undefined\n ? base\n : base.pipe(Stream.schedule(Schedule.spaced(options.deltaInterval)))\n}\n\nconst makeService = (\n scriptedTurns: ReadonlyArray<Turn>,\n options?: MockOptions,\n recordCall?: (history: ReadonlyArray<Item>, turn: Turn) => Effect.Effect<void>,\n) =>\n Effect.gen(function* () {\n const cursor = yield* Ref.make(0)\n return LanguageModel.of({\n streamTurn: (request) =>\n Stream.unwrap(\n Effect.gen(function* () {\n const i = yield* Ref.getAndUpdate(cursor, (n) => n + 1)\n if (i >= scriptedTurns.length) {\n return Stream.fail(\n new AiError.InvalidRequest({\n provider: \"mock\",\n raw: `MockProvider exhausted: ${scriptedTurns.length} turns scripted, but call ${i + 1} was made`,\n }),\n )\n }\n const turn = scriptedTurns[i]!\n if (recordCall !== undefined) {\n yield* recordCall(request.history, turn)\n }\n return pacedDeltas(turn, options)\n }),\n ),\n })\n })\n\nexport const layer = (\n scriptedTurns: ReadonlyArray<Turn>,\n options?: MockOptions,\n): Layer.Layer<LanguageModel> => Layer.effect(LanguageModel, makeService(scriptedTurns, options))\n\n/**\n * Synchronous constructor that returns the `LanguageModelService` value\n * directly, plus a recorder. Use this when you want to swap models\n * mid-stream via `Effect.provideService` instead of providing one model\n * for the whole program via `Layer`.\n */\nexport const make = (\n scriptedTurns: ReadonlyArray<Turn>,\n options?: MockOptions,\n): {\n readonly service: LanguageModelService\n readonly recorder: Effect.Effect<MockRecorder>\n} => {\n const cursor = Ref.makeUnsafe(0)\n const callsRef = Ref.makeUnsafe<ReadonlyArray<{ history: ReadonlyArray<Item>; turn: Turn }>>([])\n const service: LanguageModelService = {\n streamTurn: (request) =>\n Stream.unwrap(\n Effect.gen(function* () {\n const i = yield* Ref.getAndUpdate(cursor, (n) => n + 1)\n if (i >= scriptedTurns.length) {\n return Stream.fail(\n new AiError.InvalidRequest({\n provider: \"mock\",\n raw: `MockProvider exhausted: ${scriptedTurns.length} turns scripted, but call ${i + 1} was made`,\n }),\n )\n }\n const turn = scriptedTurns[i]!\n yield* Ref.update(callsRef, (xs) => [...xs, { history: request.history, turn }])\n return pacedDeltas(turn, options)\n }),\n ),\n }\n return {\n service,\n recorder: Ref.get(callsRef).pipe(Effect.map((calls) => ({ calls }))),\n }\n}\n\n/**\n * Same as `layer`, but also exposes a recorder that captures every call\n * (history + returned turn).\n */\nexport const layerWithRecorder = (\n scriptedTurns: ReadonlyArray<Turn>,\n options?: MockOptions,\n): {\n readonly layer: Layer.Layer<LanguageModel>\n readonly recorder: Effect.Effect<MockRecorder>\n} => {\n const callsRef = Ref.makeUnsafe<ReadonlyArray<{ history: ReadonlyArray<Item>; turn: Turn }>>([])\n const live = Layer.effect(\n LanguageModel,\n makeService(scriptedTurns, options, (history, turn) =>\n Ref.update(callsRef, (xs) => [...xs, { history, turn }]),\n ),\n )\n return {\n layer: live,\n recorder: Ref.get(callsRef).pipe(Effect.map((calls) => ({ calls }))),\n }\n}\n"],"mappings":"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"}
|
|
@@ -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"}
|