@effect-uai/core 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{AiError-csR8Bhxx.d.mts → AiError-CAX_48RU.d.mts} +2 -2
- package/dist/{AiError-csR8Bhxx.d.mts.map → AiError-CAX_48RU.d.mts.map} +1 -1
- package/dist/{Image-DxyXqzAM.d.mts → Image-HNmMpMTh.d.mts} +4 -4
- package/dist/{Image-DxyXqzAM.d.mts.map → Image-HNmMpMTh.d.mts.map} +1 -1
- package/dist/{Items-Hg5AsYxl.d.mts → Items-DqbaJoz7.d.mts} +8 -8
- package/dist/{Items-Hg5AsYxl.d.mts.map → Items-DqbaJoz7.d.mts.map} +1 -1
- package/dist/{StructuredFormat-Cl41C56K.d.mts → StructuredFormat-BbN4dosH.d.mts} +11 -4
- package/dist/StructuredFormat-BbN4dosH.d.mts.map +1 -0
- package/dist/{Tool-B8B5qVEy.d.mts → Tool-Y0__Py1H.d.mts} +20 -4
- package/dist/Tool-Y0__Py1H.d.mts.map +1 -0
- package/dist/Turn-ChbL2foc.d.mts +388 -0
- package/dist/Turn-ChbL2foc.d.mts.map +1 -0
- package/dist/domain/AiError.d.mts +1 -1
- package/dist/domain/AiError.mjs +1 -1
- package/dist/domain/AiError.mjs.map +1 -1
- package/dist/domain/Image.d.mts +1 -1
- package/dist/domain/Items.d.mts +1 -1
- package/dist/domain/Items.mjs +1 -1
- package/dist/domain/Items.mjs.map +1 -1
- package/dist/domain/Turn.d.mts +2 -2
- package/dist/domain/Turn.mjs +22 -4
- package/dist/domain/Turn.mjs.map +1 -1
- package/dist/domain/Turn.test.d.mts +1 -0
- package/dist/domain/Turn.test.mjs +136 -0
- package/dist/domain/Turn.test.mjs.map +1 -0
- package/dist/embedding-model/Embedding.d.mts +15 -3
- package/dist/embedding-model/Embedding.d.mts.map +1 -1
- package/dist/embedding-model/Embedding.mjs.map +1 -1
- package/dist/embedding-model/EmbeddingModel.d.mts +33 -17
- package/dist/embedding-model/EmbeddingModel.d.mts.map +1 -1
- package/dist/embedding-model/EmbeddingModel.mjs.map +1 -1
- package/dist/embedding-model/EmbeddingModel.test.d.mts +1 -0
- package/dist/embedding-model/EmbeddingModel.test.mjs +59 -0
- package/dist/embedding-model/EmbeddingModel.test.mjs.map +1 -0
- package/dist/index.d.mts +6 -6
- package/dist/language-model/LanguageModel.d.mts +30 -8
- package/dist/language-model/LanguageModel.d.mts.map +1 -1
- package/dist/language-model/LanguageModel.mjs +33 -3
- package/dist/language-model/LanguageModel.mjs.map +1 -1
- package/dist/language-model/LanguageModel.test.d.mts +1 -0
- package/dist/language-model/LanguageModel.test.mjs +143 -0
- package/dist/language-model/LanguageModel.test.mjs.map +1 -0
- package/dist/loop/Loop.d.mts +94 -11
- package/dist/loop/Loop.d.mts.map +1 -1
- package/dist/loop/Loop.mjs +92 -26
- package/dist/loop/Loop.mjs.map +1 -1
- package/dist/loop/Loop.test.mjs +171 -3
- package/dist/loop/Loop.test.mjs.map +1 -1
- package/dist/music-generator/MusicGenerator.d.mts +1 -1
- package/dist/observability/Metrics.d.mts +1 -1
- package/dist/observability/Metrics.mjs +1 -1
- package/dist/observability/Metrics.mjs.map +1 -1
- package/dist/speech-synthesizer/SpeechSynthesizer.d.mts +1 -1
- package/dist/streaming/JSONL.d.mts +1 -1
- package/dist/streaming/JSONL.d.mts.map +1 -1
- package/dist/streaming/JSONL.mjs +7 -12
- package/dist/streaming/JSONL.mjs.map +1 -1
- package/dist/structured-format/StructuredFormat.d.mts +2 -2
- package/dist/structured-format/StructuredFormat.mjs +9 -1
- package/dist/structured-format/StructuredFormat.mjs.map +1 -1
- package/dist/structured-format/StructuredFormat.test.d.mts +1 -0
- package/dist/structured-format/StructuredFormat.test.mjs +70 -0
- package/dist/structured-format/StructuredFormat.test.mjs.map +1 -0
- package/dist/testing/MockMusicGenerator.d.mts.map +1 -1
- package/dist/testing/MockMusicGenerator.mjs +2 -2
- package/dist/testing/MockMusicGenerator.mjs.map +1 -1
- package/dist/testing/MockProvider.d.mts +23 -18
- package/dist/testing/MockProvider.d.mts.map +1 -1
- package/dist/testing/MockProvider.mjs +56 -72
- package/dist/testing/MockProvider.mjs.map +1 -1
- package/dist/testing/MockSpeechSynthesizer.d.mts.map +1 -1
- package/dist/testing/MockSpeechSynthesizer.mjs +2 -2
- package/dist/testing/MockSpeechSynthesizer.mjs.map +1 -1
- package/dist/testing/MockTranscriber.d.mts.map +1 -1
- package/dist/testing/MockTranscriber.mjs +2 -2
- package/dist/testing/MockTranscriber.mjs.map +1 -1
- package/dist/tool/HistoryCheck.d.mts +1 -1
- package/dist/tool/Outcome.d.mts +1 -1
- package/dist/tool/Resolvers.d.mts +65 -8
- package/dist/tool/Resolvers.d.mts.map +1 -1
- package/dist/tool/Resolvers.mjs +8 -12
- package/dist/tool/Resolvers.mjs.map +1 -1
- package/dist/tool/Resolvers.test.mjs +6 -5
- package/dist/tool/Resolvers.test.mjs.map +1 -1
- package/dist/tool/Tool.d.mts +2 -2
- package/dist/tool/Tool.mjs +18 -1
- package/dist/tool/Tool.mjs.map +1 -1
- package/dist/tool/Tool.test.d.mts +1 -0
- package/dist/tool/Tool.test.mjs +66 -0
- package/dist/tool/Tool.test.mjs.map +1 -0
- package/dist/tool/Toolkit.d.mts +4 -6
- package/dist/tool/Toolkit.d.mts.map +1 -1
- package/dist/tool/Toolkit.mjs +14 -43
- package/dist/tool/Toolkit.mjs.map +1 -1
- package/dist/transcriber/Transcriber.d.mts +1 -1
- package/package.json +1 -1
- package/src/domain/AiError.ts +1 -1
- package/src/domain/Items.ts +1 -1
- package/src/domain/Turn.test.ts +141 -0
- package/src/domain/Turn.ts +50 -43
- package/src/embedding-model/Embedding.ts +23 -0
- package/src/embedding-model/EmbeddingModel.test.ts +92 -0
- package/src/embedding-model/EmbeddingModel.ts +30 -20
- package/src/language-model/LanguageModel.test.ts +170 -0
- package/src/language-model/LanguageModel.ts +64 -1
- package/src/loop/Loop.test.ts +256 -3
- package/src/loop/Loop.ts +225 -49
- package/src/observability/Metrics.ts +1 -1
- package/src/streaming/JSONL.ts +9 -18
- package/src/structured-format/StructuredFormat.test.ts +105 -0
- package/src/structured-format/StructuredFormat.ts +14 -1
- package/src/testing/MockMusicGenerator.ts +4 -6
- package/src/testing/MockProvider.ts +126 -105
- package/src/testing/MockSpeechSynthesizer.ts +4 -6
- package/src/testing/MockTranscriber.ts +4 -6
- package/src/tool/Resolvers.test.ts +8 -5
- package/src/tool/Resolvers.ts +17 -19
- package/src/tool/Tool.test.ts +105 -0
- package/src/tool/Tool.ts +20 -0
- package/src/tool/Toolkit.ts +49 -50
- package/dist/StructuredFormat-Cl41C56K.d.mts.map +0 -1
- package/dist/Tool-B8B5qVEy.d.mts.map +0 -1
- package/dist/Turn-7geUcKsf.d.mts +0 -194
- package/dist/Turn-7geUcKsf.d.mts.map +0 -1
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { embed, embedMany } from "./EmbeddingModel.mjs";
|
|
2
|
+
import { i as it, r as describe, t as import_dist } from "../dist-DV5ISja1.mjs";
|
|
3
|
+
//#region src/embedding-model/EmbeddingModel.test.ts
|
|
4
|
+
describe("EmbedResponse<E> conditional narrowing", () => {
|
|
5
|
+
it("defaults to Float32Embedding when E is undefined", () => {
|
|
6
|
+
(0, import_dist.expectTypeOf)().toEqualTypeOf();
|
|
7
|
+
});
|
|
8
|
+
it("maps each EmbedEncoding literal to the matching Embedding variant", () => {
|
|
9
|
+
(0, import_dist.expectTypeOf)().toEqualTypeOf();
|
|
10
|
+
(0, import_dist.expectTypeOf)().toEqualTypeOf();
|
|
11
|
+
(0, import_dist.expectTypeOf)().toEqualTypeOf();
|
|
12
|
+
(0, import_dist.expectTypeOf)().toEqualTypeOf();
|
|
13
|
+
(0, import_dist.expectTypeOf)().toEqualTypeOf();
|
|
14
|
+
});
|
|
15
|
+
it("falls back to the open Embedding union when E is the full EmbedEncoding", () => {
|
|
16
|
+
(0, import_dist.expectTypeOf)().toEqualTypeOf();
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
describe("EmbedManyResponse<E> mirrors EmbedResponse<E>", () => {
|
|
20
|
+
it("defaults to ReadonlyArray<Float32Embedding>", () => {
|
|
21
|
+
(0, import_dist.expectTypeOf)().toEqualTypeOf();
|
|
22
|
+
});
|
|
23
|
+
it("narrows per encoding", () => {
|
|
24
|
+
(0, import_dist.expectTypeOf)().toEqualTypeOf();
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
describe("embed / embedMany free exports preserve E in their return type", () => {
|
|
28
|
+
it("embed with no encoding returns EmbedResponse<undefined> = Float32", () => {
|
|
29
|
+
(0, import_dist.expectTypeOf)(embed({
|
|
30
|
+
input: "x",
|
|
31
|
+
model: "m"
|
|
32
|
+
})).toEqualTypeOf();
|
|
33
|
+
});
|
|
34
|
+
it("embed with encoding: int8 returns EmbedResponse<int8>", () => {
|
|
35
|
+
(0, import_dist.expectTypeOf)(embed({
|
|
36
|
+
input: "x",
|
|
37
|
+
model: "m",
|
|
38
|
+
encoding: "int8"
|
|
39
|
+
})).toEqualTypeOf();
|
|
40
|
+
});
|
|
41
|
+
it("embedMany with encoding: multivector returns EmbedManyResponse<multivector>", () => {
|
|
42
|
+
(0, import_dist.expectTypeOf)(embedMany({
|
|
43
|
+
inputs: ["x"],
|
|
44
|
+
model: "m",
|
|
45
|
+
encoding: "multivector"
|
|
46
|
+
})).toEqualTypeOf();
|
|
47
|
+
});
|
|
48
|
+
it("when the request is widened to CommonEmbedRequest, the response is the open union", () => {
|
|
49
|
+
(0, import_dist.expectTypeOf)(embed({
|
|
50
|
+
input: "x",
|
|
51
|
+
model: "m",
|
|
52
|
+
encoding: "int8"
|
|
53
|
+
})).toEqualTypeOf();
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
//#endregion
|
|
57
|
+
export {};
|
|
58
|
+
|
|
59
|
+
//# sourceMappingURL=EmbeddingModel.test.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EmbeddingModel.test.mjs","names":[],"sources":["../../src/embedding-model/EmbeddingModel.test.ts"],"sourcesContent":["import { Effect } from \"effect\"\nimport { describe, expectTypeOf, it } from \"vitest\"\nimport type * as AiError from \"../domain/AiError.js\"\nimport type {\n BinaryEmbedding,\n Float32Embedding,\n Int8Embedding,\n MultivectorEmbedding,\n SparseEmbedding,\n} from \"./Embedding.js\"\nimport {\n type EmbedEncoding,\n embed,\n embedMany,\n type EmbedManyResponse,\n type EmbedResponse,\n EmbeddingModel,\n} from \"./EmbeddingModel.js\"\n\n// Type-level tests only. The actual narrowing happens at the type system\n// boundary — the runtime impl is exercised in provider-specific test files.\ndescribe(\"EmbedResponse<E> conditional narrowing\", () => {\n it(\"defaults to Float32Embedding when E is undefined\", () => {\n expectTypeOf<EmbedResponse>().toEqualTypeOf<{\n readonly embedding: Float32Embedding\n readonly usage: import(\"./Embedding.js\").Usage\n }>()\n })\n\n it(\"maps each EmbedEncoding literal to the matching Embedding variant\", () => {\n expectTypeOf<EmbedResponse<\"float32\">[\"embedding\"]>().toEqualTypeOf<Float32Embedding>()\n expectTypeOf<EmbedResponse<\"int8\">[\"embedding\"]>().toEqualTypeOf<Int8Embedding>()\n expectTypeOf<EmbedResponse<\"binary\">[\"embedding\"]>().toEqualTypeOf<BinaryEmbedding>()\n expectTypeOf<EmbedResponse<\"sparse\">[\"embedding\"]>().toEqualTypeOf<SparseEmbedding>()\n expectTypeOf<EmbedResponse<\"multivector\">[\"embedding\"]>().toEqualTypeOf<MultivectorEmbedding>()\n })\n\n it(\"falls back to the open Embedding union when E is the full EmbedEncoding\", () => {\n expectTypeOf<EmbedResponse<EmbedEncoding>[\"embedding\"]>().toEqualTypeOf<\n Float32Embedding | Int8Embedding | BinaryEmbedding | SparseEmbedding | MultivectorEmbedding\n >()\n })\n})\n\ndescribe(\"EmbedManyResponse<E> mirrors EmbedResponse<E>\", () => {\n it(\"defaults to ReadonlyArray<Float32Embedding>\", () => {\n expectTypeOf<EmbedManyResponse[\"embeddings\"]>().toEqualTypeOf<ReadonlyArray<Float32Embedding>>()\n })\n\n it(\"narrows per encoding\", () => {\n expectTypeOf<EmbedManyResponse<\"int8\">[\"embeddings\"]>().toEqualTypeOf<\n ReadonlyArray<Int8Embedding>\n >()\n })\n})\n\ndescribe(\"embed / embedMany free exports preserve E in their return type\", () => {\n it(\"embed with no encoding returns EmbedResponse<undefined> = Float32\", () => {\n const result = embed({ input: \"x\", model: \"m\" })\n expectTypeOf(result).toEqualTypeOf<\n Effect.Effect<EmbedResponse<undefined>, AiError.AiError, EmbeddingModel>\n >()\n })\n\n it(\"embed with encoding: int8 returns EmbedResponse<int8>\", () => {\n const result = embed({ input: \"x\", model: \"m\", encoding: \"int8\" })\n expectTypeOf(result).toEqualTypeOf<\n Effect.Effect<EmbedResponse<\"int8\">, AiError.AiError, EmbeddingModel>\n >()\n })\n\n it(\"embedMany with encoding: multivector returns EmbedManyResponse<multivector>\", () => {\n const result = embedMany({ inputs: [\"x\"], model: \"m\", encoding: \"multivector\" })\n expectTypeOf(result).toEqualTypeOf<\n Effect.Effect<EmbedManyResponse<\"multivector\">, AiError.AiError, EmbeddingModel>\n >()\n })\n\n it(\"when the request is widened to CommonEmbedRequest, the response is the open union\", () => {\n // Demonstrates the documented case: annotating the request type erases\n // the literal `encoding` and the response falls back to `Embedding`.\n const req: { input: string; model: string; encoding?: EmbedEncoding } = {\n input: \"x\",\n model: \"m\",\n encoding: \"int8\",\n }\n const result = embed(req)\n expectTypeOf(result).toEqualTypeOf<\n Effect.Effect<EmbedResponse<EmbedEncoding>, AiError.AiError, EmbeddingModel>\n >()\n })\n})\n"],"mappings":";;;AAqBA,SAAS,gDAAgD;AACvD,IAAG,0DAA0D;AAC3D,GAAA,GAAA,YAAA,eAA6B,CAAC,eAG1B;GACJ;AAEF,IAAG,2EAA2E;AAC5E,GAAA,GAAA,YAAA,eAAqD,CAAC,eAAiC;AACvF,GAAA,GAAA,YAAA,eAAkD,CAAC,eAA8B;AACjF,GAAA,GAAA,YAAA,eAAoD,CAAC,eAAgC;AACrF,GAAA,GAAA,YAAA,eAAoD,CAAC,eAAgC;AACrF,GAAA,GAAA,YAAA,eAAyD,CAAC,eAAqC;GAC/F;AAEF,IAAG,iFAAiF;AAClF,GAAA,GAAA,YAAA,eAAyD,CAAC,eAEvD;GACH;EACF;AAEF,SAAS,uDAAuD;AAC9D,IAAG,qDAAqD;AACtD,GAAA,GAAA,YAAA,eAA+C,CAAC,eAAgD;GAChG;AAEF,IAAG,8BAA8B;AAC/B,GAAA,GAAA,YAAA,eAAuD,CAAC,eAErD;GACH;EACF;AAEF,SAAS,wEAAwE;AAC/E,IAAG,2EAA2E;AAE5E,GAAA,GAAA,YAAA,cADe,MAAM;GAAE,OAAO;GAAK,OAAO;GAAK,CAC5B,CAAC,CAAC,eAElB;GACH;AAEF,IAAG,+DAA+D;AAEhE,GAAA,GAAA,YAAA,cADe,MAAM;GAAE,OAAO;GAAK,OAAO;GAAK,UAAU;GAAQ,CAC9C,CAAC,CAAC,eAElB;GACH;AAEF,IAAG,qFAAqF;AAEtF,GAAA,GAAA,YAAA,cADe,UAAU;GAAE,QAAQ,CAAC,IAAI;GAAE,OAAO;GAAK,UAAU;GAAe,CAC5D,CAAC,CAAC,eAElB;GACH;AAEF,IAAG,2FAA2F;AAS5F,GAAA,GAAA,YAAA,cADe,MAAM;GAJnB,OAAO;GACP,OAAO;GACP,UAAU;GAEY,CACL,CAAC,CAAC,eAElB;GACH;EACF"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { n as AiError_d_exports } from "./AiError-
|
|
1
|
+
import { n as AiError_d_exports } from "./AiError-CAX_48RU.mjs";
|
|
2
2
|
import { a as Media_d_exports } from "./Media-D_CpcM1Z.mjs";
|
|
3
3
|
import { o as Audio_d_exports } from "./Audio-BfCTGnH3.mjs";
|
|
4
|
-
import { o as Image_d_exports } from "./Image-
|
|
5
|
-
import { f as Items_d_exports } from "./Items-
|
|
4
|
+
import { o as Image_d_exports } from "./Image-HNmMpMTh.mjs";
|
|
5
|
+
import { f as Items_d_exports } from "./Items-DqbaJoz7.mjs";
|
|
6
6
|
import { t as Music_d_exports } from "./domain/Music.mjs";
|
|
7
7
|
import { t as Transcript_d_exports } from "./domain/Transcript.mjs";
|
|
8
|
-
import { a as StructuredFormat_d_exports } from "./StructuredFormat-
|
|
9
|
-
import { a as Turn_d_exports } from "./Turn-
|
|
8
|
+
import { a as StructuredFormat_d_exports } from "./StructuredFormat-BbN4dosH.mjs";
|
|
9
|
+
import { a as Turn_d_exports } from "./Turn-ChbL2foc.mjs";
|
|
10
10
|
import { t as Embedding_d_exports } from "./embedding-model/Embedding.mjs";
|
|
11
11
|
import { t as EmbeddingModel_d_exports } from "./embedding-model/EmbeddingModel.mjs";
|
|
12
|
-
import { l as Tool_d_exports } from "./Tool-
|
|
12
|
+
import { l as Tool_d_exports } from "./Tool-Y0__Py1H.mjs";
|
|
13
13
|
import { t as LanguageModel_d_exports } from "./language-model/LanguageModel.mjs";
|
|
14
14
|
import { t as MusicGenerator_d_exports } from "./music-generator/MusicGenerator.mjs";
|
|
15
15
|
import { t as SpeechSynthesizer_d_exports } from "./speech-synthesizer/SpeechSynthesizer.mjs";
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import { t as AiError } from "../AiError-
|
|
2
|
-
import { d as Item } from "../Items-
|
|
3
|
-
import { i as StructuredFormat } from "../StructuredFormat-
|
|
4
|
-
import { i as TurnEvent } from "../Turn-
|
|
5
|
-
import { o as ToolDescriptor } from "../Tool-
|
|
6
|
-
import { Context, Stream } from "effect";
|
|
1
|
+
import { d as RateLimited, l as IncompleteTurn, m as Unavailable, p as Timeout, t as AiError } from "../AiError-CAX_48RU.mjs";
|
|
2
|
+
import { d as Item } from "../Items-DqbaJoz7.mjs";
|
|
3
|
+
import { i as StructuredFormat } from "../StructuredFormat-BbN4dosH.mjs";
|
|
4
|
+
import { i as TurnEvent, r as Turn } from "../Turn-ChbL2foc.mjs";
|
|
5
|
+
import { o as ToolDescriptor } from "../Tool-Y0__Py1H.mjs";
|
|
6
|
+
import { Context, Effect, Schedule, Stream } from "effect";
|
|
7
|
+
import * as _$effect_Types0 from "effect/Types";
|
|
8
|
+
import * as _$effect_Cause0 from "effect/Cause";
|
|
7
9
|
|
|
8
10
|
//#region src/language-model/LanguageModel.d.ts
|
|
9
11
|
declare namespace LanguageModel_d_exports {
|
|
10
|
-
export { CommonRequest, LanguageModel, LanguageModelService, streamTurn };
|
|
12
|
+
export { CommonRequest, LanguageModel, LanguageModelService, Retryable, retry, streamTurn, turn };
|
|
11
13
|
}
|
|
12
14
|
/**
|
|
13
15
|
* Cross-provider request shape. Every call carries its own `history` and
|
|
@@ -47,6 +49,26 @@ declare class LanguageModel extends LanguageModel_base {}
|
|
|
47
49
|
* Stream the deltas of a single turn.
|
|
48
50
|
*/
|
|
49
51
|
declare const streamTurn: (request: CommonRequest) => Stream.Stream<TurnEvent, AiError, LanguageModel>;
|
|
52
|
+
/**
|
|
53
|
+
* Drain `streamTurn` and return the assembled `Turn` from the terminal
|
|
54
|
+
* `TurnComplete` event. Fails with `IncompleteTurn` if the stream ends
|
|
55
|
+
* without one. Derived from `streamTurn`; providers get it for free.
|
|
56
|
+
*/
|
|
57
|
+
declare const turn: (request: CommonRequest) => Effect.Effect<Turn, AiError | IncompleteTurn, LanguageModel>;
|
|
58
|
+
declare const Retryable_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 & {
|
|
59
|
+
readonly _tag: "RetryableAi";
|
|
60
|
+
} & Readonly<A>;
|
|
61
|
+
/** Internal wrapper around the retryable subset of `AiError`. */
|
|
62
|
+
declare class Retryable extends Retryable_base<{
|
|
63
|
+
readonly cause: RateLimited | Unavailable | Timeout;
|
|
64
|
+
}> {}
|
|
65
|
+
/**
|
|
66
|
+
* Retry a stream of `AiError` on the retryable subset
|
|
67
|
+
* (`RateLimited | Unavailable | Timeout`). Other failures bypass the
|
|
68
|
+
* schedule and propagate unchanged. Like all `Stream.retry`, the entire
|
|
69
|
+
* stream re-runs — deltas before the failure replay on the next attempt.
|
|
70
|
+
*/
|
|
71
|
+
declare const retry: <Out>(schedule: Schedule.Schedule<Out, Retryable>) => <A, R>(stream: Stream.Stream<A, AiError, R>) => Stream.Stream<A, AiError, R>;
|
|
50
72
|
//#endregion
|
|
51
|
-
export { CommonRequest, LanguageModel, LanguageModelService, streamTurn, LanguageModel_d_exports as t };
|
|
73
|
+
export { CommonRequest, LanguageModel, LanguageModelService, Retryable, retry, streamTurn, LanguageModel_d_exports as t, turn };
|
|
52
74
|
//# sourceMappingURL=LanguageModel.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LanguageModel.d.mts","names":[],"sources":["../../src/language-model/LanguageModel.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"LanguageModel.d.mts","names":[],"sources":["../../src/language-model/LanguageModel.ts"],"mappings":";;;;;;;;;;;;;;;;;;;KAaY,aAAA;EAAA,SACD,OAAA,EAAS,aAAA,CAAc,IAAA;;;;;WAKvB,KAAA;EAAA,SACA,KAAA,GAAQ,aAAA,CAAc,cAAA;EAAA,SACtB,UAAA;IAAA,SAIM,IAAA;IAAA,SAA2B,IAAA;EAAA;EAAA,SACjC,WAAA;EAAA,SACA,IAAA;EAAA,SACA,eAAA;EAOa;;;;;;EAAA,SAAb,UAAA,GAAa,gBAAA;AAAA;AAAA,KAGZ,oBAAA;EAAA,SACD,UAAA,GAAa,OAAA,EAAS,aAAA,KAAkB,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,OAAA;AAAA;AAAA,cAC3E,kBAAA;cAEY,aAAA,SAAsB,kBAAA;;;;cAOtB,UAAA,GACX,OAAA,EAAS,aAAA,KACR,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,OAAA,EAAiB,aAAA;;;;AAb7C;;cAqBa,IAAA,GACX,OAAA,EAAS,aAAA,KACR,MAAA,CAAO,MAAA,CAAO,IAAA,EAAM,OAAA,GAAkB,cAAA,EAAwB,aAAA;AAAA,cAW9D,cAAA;;;;cAOU,SAAA,SAAkB,cAAA;EAAA,SACpB,KAAA,EAAO,WAAA,GAAsB,WAAA,GAAsB,OAAA;AAAA;;;;;;;cAoBjD,KAAA,QACL,QAAA,EAAU,QAAA,CAAS,QAAA,CAAS,GAAA,EAAK,SAAA,aAChC,MAAA,EAAQ,MAAA,CAAO,MAAA,CAAO,CAAA,EAAG,OAAA,EAAiB,CAAA,MAAK,MAAA,CAAO,MAAA,CAAO,CAAA,EAAG,OAAA,EAAiB,CAAA"}
|
|
@@ -1,16 +1,46 @@
|
|
|
1
1
|
import { n as __exportAll } from "../chunk-uyGKjUfl.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { IncompleteTurn } from "../domain/AiError.mjs";
|
|
3
|
+
import { isTurnComplete } from "../domain/Turn.mjs";
|
|
4
|
+
import { Array, Context, Data, Effect, Option, Stream } from "effect";
|
|
3
5
|
//#region src/language-model/LanguageModel.ts
|
|
4
6
|
var LanguageModel_exports = /* @__PURE__ */ __exportAll({
|
|
5
7
|
LanguageModel: () => LanguageModel,
|
|
6
|
-
|
|
8
|
+
Retryable: () => Retryable,
|
|
9
|
+
retry: () => retry,
|
|
10
|
+
streamTurn: () => streamTurn,
|
|
11
|
+
turn: () => turn
|
|
7
12
|
});
|
|
8
13
|
var LanguageModel = class extends Context.Service()("@betalyra/effect-uai/LanguageModel") {};
|
|
9
14
|
/**
|
|
10
15
|
* Stream the deltas of a single turn.
|
|
11
16
|
*/
|
|
12
17
|
const streamTurn = (request) => Stream.unwrap(Effect.map(LanguageModel.asEffect(), (m) => m.streamTurn(request)));
|
|
18
|
+
/**
|
|
19
|
+
* Drain `streamTurn` and return the assembled `Turn` from the terminal
|
|
20
|
+
* `TurnComplete` event. Fails with `IncompleteTurn` if the stream ends
|
|
21
|
+
* without one. Derived from `streamTurn`; providers get it for free.
|
|
22
|
+
*/
|
|
23
|
+
const turn = (request) => streamTurn(request).pipe(Stream.runCollect, Effect.flatMap((events) => Array.findLast(events, isTurnComplete).pipe(Option.match({
|
|
24
|
+
onNone: () => Effect.fail(new IncompleteTurn({})),
|
|
25
|
+
onSome: (e) => Effect.succeed(e.turn)
|
|
26
|
+
}))));
|
|
27
|
+
/** Internal wrapper around the retryable subset of `AiError`. */
|
|
28
|
+
var Retryable = class extends Data.TaggedError("RetryableAi") {};
|
|
29
|
+
const isRetryable = (e) => e._tag === "RateLimited" || e._tag === "Unavailable" || e._tag === "Timeout";
|
|
30
|
+
/**
|
|
31
|
+
* Retry a stream of `AiError` on the retryable subset
|
|
32
|
+
* (`RateLimited | Unavailable | Timeout`). Other failures bypass the
|
|
33
|
+
* schedule and propagate unchanged. Like all `Stream.retry`, the entire
|
|
34
|
+
* stream re-runs — deltas before the failure replay on the next attempt.
|
|
35
|
+
*/
|
|
36
|
+
const retry = (schedule) => (stream) => stream.pipe(Stream.map((value) => ({
|
|
37
|
+
_tag: "Item",
|
|
38
|
+
value
|
|
39
|
+
})), Stream.catchIf(isRetryable, (cause) => Stream.fail(new Retryable({ cause })), (cause) => Stream.succeed({
|
|
40
|
+
_tag: "Terminal",
|
|
41
|
+
cause
|
|
42
|
+
})), Stream.retry(schedule), Stream.catchTag("RetryableAi", (e) => Stream.fail(e.cause)), Stream.flatMap((item) => item._tag === "Item" ? Stream.succeed(item.value) : Stream.fail(item.cause)));
|
|
13
43
|
//#endregion
|
|
14
|
-
export { LanguageModel, streamTurn, LanguageModel_exports as t };
|
|
44
|
+
export { LanguageModel, Retryable, retry, streamTurn, LanguageModel_exports as t, turn };
|
|
15
45
|
|
|
16
46
|
//# sourceMappingURL=LanguageModel.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LanguageModel.mjs","names":[],"sources":["../../src/language-model/LanguageModel.ts"],"sourcesContent":["import { Context, Effect, Stream } from \"effect\"\nimport * as AiError from \"../domain/AiError.js\"\nimport type { Item } from \"../domain/Items.js\"\nimport type * as StructuredFormat from \"../structured-format/StructuredFormat.js\"\nimport type { ToolDescriptor } from \"../tool/Tool.js\"\nimport { isTurnComplete, type Turn, type TurnEvent } from \"../domain/Turn.js\"\n\n/**\n * Cross-provider request shape. Every call carries its own `history` and\n * `model` - models are not bound at layer construction. Anything specific\n * to a single provider (reasoning effort, prompt caching, store flags,\n * ...) lives in that provider's own request interface, which extends this.\n */\nexport type CommonRequest = {\n readonly history: ReadonlyArray<Item>\n /**\n * Model identifier. Each provider narrows this to its typed literal union,\n * so code that yields a typed provider tag gets autocompletion.\n */\n readonly model: string\n readonly tools?: ReadonlyArray<ToolDescriptor>\n readonly toolChoice?:\n | \"auto\"\n | \"required\"\n | \"none\"\n | { readonly type: \"function\"; readonly name: string }\n readonly temperature?: number\n readonly topP?: number\n readonly maxOutputTokens?: number\n /**\n * Schema-bound JSON output. The provider constrains the wire to match the\n * schema; pair with `Turn.toStructured` for runtime validation. Supported\n * across all current providers (OpenAI Responses json_schema, Anthropic\n * `output_config`, Gemini `responseJsonSchema`).\n */\n readonly structured?: StructuredFormat.StructuredFormat<unknown>\n}\n\nexport type LanguageModelService = {\n readonly streamTurn: (request: CommonRequest) => Stream.Stream<TurnEvent, AiError.AiError>\n}\n\nexport class LanguageModel extends Context.Service<LanguageModel, LanguageModelService>()(\n \"@betalyra/effect-uai/LanguageModel\",\n) {}\n\n/**\n * Stream the deltas of a single turn.\n */\nexport const streamTurn = (\n request: CommonRequest,\n): Stream.Stream<TurnEvent, AiError.AiError, LanguageModel> =>\n Stream.unwrap(Effect.map(LanguageModel.asEffect(), (m) => m.streamTurn(request)))\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"LanguageModel.mjs","names":["Arr","AiError.IncompleteTurn"],"sources":["../../src/language-model/LanguageModel.ts"],"sourcesContent":["import { Array as Arr, Context, Data, Effect, Option, type Schedule, Stream } from \"effect\"\nimport * as AiError from \"../domain/AiError.js\"\nimport type { Item } from \"../domain/Items.js\"\nimport type * as StructuredFormat from \"../structured-format/StructuredFormat.js\"\nimport type { ToolDescriptor } from \"../tool/Tool.js\"\nimport { isTurnComplete, type Turn, type TurnEvent } from \"../domain/Turn.js\"\n\n/**\n * Cross-provider request shape. Every call carries its own `history` and\n * `model` - models are not bound at layer construction. Anything specific\n * to a single provider (reasoning effort, prompt caching, store flags,\n * ...) lives in that provider's own request interface, which extends this.\n */\nexport type CommonRequest = {\n readonly history: ReadonlyArray<Item>\n /**\n * Model identifier. Each provider narrows this to its typed literal union,\n * so code that yields a typed provider tag gets autocompletion.\n */\n readonly model: string\n readonly tools?: ReadonlyArray<ToolDescriptor>\n readonly toolChoice?:\n | \"auto\"\n | \"required\"\n | \"none\"\n | { readonly type: \"function\"; readonly name: string }\n readonly temperature?: number\n readonly topP?: number\n readonly maxOutputTokens?: number\n /**\n * Schema-bound JSON output. The provider constrains the wire to match the\n * schema; pair with `Turn.toStructured` for runtime validation. Supported\n * across all current providers (OpenAI Responses json_schema, Anthropic\n * `output_config`, Gemini `responseJsonSchema`).\n */\n readonly structured?: StructuredFormat.StructuredFormat<unknown>\n}\n\nexport type LanguageModelService = {\n readonly streamTurn: (request: CommonRequest) => Stream.Stream<TurnEvent, AiError.AiError>\n}\n\nexport class LanguageModel extends Context.Service<LanguageModel, LanguageModelService>()(\n \"@betalyra/effect-uai/LanguageModel\",\n) {}\n\n/**\n * Stream the deltas of a single turn.\n */\nexport const streamTurn = (\n request: CommonRequest,\n): Stream.Stream<TurnEvent, AiError.AiError, LanguageModel> =>\n Stream.unwrap(Effect.map(LanguageModel.asEffect(), (m) => m.streamTurn(request)))\n\n/**\n * Drain `streamTurn` and return the assembled `Turn` from the terminal\n * `TurnComplete` event. Fails with `IncompleteTurn` if the stream ends\n * without one. Derived from `streamTurn`; providers get it for free.\n */\nexport const turn = (\n request: CommonRequest,\n): Effect.Effect<Turn, AiError.AiError | AiError.IncompleteTurn, LanguageModel> =>\n streamTurn(request).pipe(\n Stream.runCollect,\n Effect.flatMap((events) =>\n Arr.findLast(events, isTurnComplete).pipe(\n Option.match({\n onNone: () => Effect.fail(new AiError.IncompleteTurn({})),\n onSome: (e) => Effect.succeed(e.turn),\n }),\n ),\n ),\n )\n\n// ---------------------------------------------------------------------------\n// retry — retry the retryable subset of AiError, let other failures escape\n// ---------------------------------------------------------------------------\n\n/** Internal wrapper around the retryable subset of `AiError`. */\nexport class Retryable extends Data.TaggedError(\"RetryableAi\")<{\n readonly cause: AiError.RateLimited | AiError.Unavailable | AiError.Timeout\n}> {}\n\nconst isRetryable = (\n e: AiError.AiError,\n): e is AiError.RateLimited | AiError.Unavailable | AiError.Timeout =>\n e._tag === \"RateLimited\" || e._tag === \"Unavailable\" || e._tag === \"Timeout\"\n\n// Lift events to Items, non-retryable failures to Terminal values (escape\n// retry), retryable failures to wrapped errors (only thing retry sees).\ntype Lifted<A> =\n | { readonly _tag: \"Item\"; readonly value: A }\n | { readonly _tag: \"Terminal\"; readonly cause: AiError.AiError }\n\n/**\n * Retry a stream of `AiError` on the retryable subset\n * (`RateLimited | Unavailable | Timeout`). Other failures bypass the\n * schedule and propagate unchanged. Like all `Stream.retry`, the entire\n * stream re-runs — deltas before the failure replay on the next attempt.\n */\nexport const retry =\n <Out>(schedule: Schedule.Schedule<Out, Retryable>) =>\n <A, R>(stream: Stream.Stream<A, AiError.AiError, R>): Stream.Stream<A, AiError.AiError, R> =>\n stream.pipe(\n Stream.map((value): Lifted<A> => ({ _tag: \"Item\", value })),\n Stream.catchIf(\n isRetryable,\n (cause) => Stream.fail(new Retryable({ cause })),\n (cause) => Stream.succeed<Lifted<A>>({ _tag: \"Terminal\", cause }),\n ),\n Stream.retry(schedule),\n Stream.catchTag(\"RetryableAi\", (e) => Stream.fail<AiError.AiError>(e.cause)),\n Stream.flatMap((item) =>\n item._tag === \"Item\" ? Stream.succeed(item.value) : Stream.fail(item.cause),\n ),\n )\n"],"mappings":";;;;;;;;;;;;AA0CA,IAAa,gBAAb,cAAmC,QAAQ,SAA8C,CACvF,qCACD,CAAC;;;;AAKF,MAAa,cACX,YAEA,OAAO,OAAO,OAAO,IAAI,cAAc,UAAU,GAAG,MAAM,EAAE,WAAW,QAAQ,CAAC,CAAC;;;;;;AAOnF,MAAa,QACX,YAEA,WAAW,QAAQ,CAAC,KAClB,OAAO,YACP,OAAO,SAAS,WACdA,MAAI,SAAS,QAAQ,eAAe,CAAC,KACnC,OAAO,MAAM;CACX,cAAc,OAAO,KAAK,IAAIC,eAAuB,EAAE,CAAC,CAAC;CACzD,SAAS,MAAM,OAAO,QAAQ,EAAE,KAAK;CACtC,CAAC,CACH,CACF,CACF;;AAOH,IAAa,YAAb,cAA+B,KAAK,YAAY,cAAc,CAE3D;AAEH,MAAM,eACJ,MAEA,EAAE,SAAS,iBAAiB,EAAE,SAAS,iBAAiB,EAAE,SAAS;;;;;;;AAcrE,MAAa,SACL,cACC,WACL,OAAO,KACL,OAAO,KAAK,WAAsB;CAAE,MAAM;CAAQ;CAAO,EAAE,EAC3D,OAAO,QACL,cACC,UAAU,OAAO,KAAK,IAAI,UAAU,EAAE,OAAO,CAAC,CAAC,GAC/C,UAAU,OAAO,QAAmB;CAAE,MAAM;CAAY;CAAO,CAAC,CAClE,EACD,OAAO,MAAM,SAAS,EACtB,OAAO,SAAS,gBAAgB,MAAM,OAAO,KAAsB,EAAE,MAAM,CAAC,EAC5E,OAAO,SAAS,SACd,KAAK,SAAS,SAAS,OAAO,QAAQ,KAAK,MAAM,GAAG,OAAO,KAAK,KAAK,MAAM,CAC5E,CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { ContentFiltered, IncompleteTurn, RateLimited, Timeout, Unavailable } from "../domain/AiError.mjs";
|
|
2
|
+
import { assistantText, userText } from "../domain/Items.mjs";
|
|
3
|
+
import { TurnEvent } from "../domain/Turn.mjs";
|
|
4
|
+
import { LanguageModel, retry, turn } from "./LanguageModel.mjs";
|
|
5
|
+
import { i as it, n as globalExpect, r as describe } from "../dist-DV5ISja1.mjs";
|
|
6
|
+
import { layer } from "../testing/MockProvider.mjs";
|
|
7
|
+
import { Cause, Effect, Exit, Layer, Option, Ref, Schedule, Stream } from "effect";
|
|
8
|
+
//#region src/language-model/LanguageModel.test.ts
|
|
9
|
+
const oneTextTurn = (text) => ({
|
|
10
|
+
items: [assistantText(text)],
|
|
11
|
+
usage: {
|
|
12
|
+
input_tokens: 1,
|
|
13
|
+
output_tokens: 1
|
|
14
|
+
},
|
|
15
|
+
stop_reason: "stop"
|
|
16
|
+
});
|
|
17
|
+
describe("LanguageModel.turn", () => {
|
|
18
|
+
it("returns the assembled Turn from the terminal TurnComplete event", async () => {
|
|
19
|
+
const expected = oneTextTurn("hello world");
|
|
20
|
+
const program = turn({
|
|
21
|
+
history: [userText("hi")],
|
|
22
|
+
model: "mock"
|
|
23
|
+
});
|
|
24
|
+
globalExpect(await Effect.runPromise(program.pipe(Effect.provide(layer([expected]))))).toEqual(expected);
|
|
25
|
+
});
|
|
26
|
+
it("fails with IncompleteTurn when the stream ends without TurnComplete", async () => {
|
|
27
|
+
const broken = Layer.succeed(LanguageModel, { streamTurn: () => Stream.fromIterable([TurnEvent.TextDelta({ text: "partial" })]) });
|
|
28
|
+
const program = turn({
|
|
29
|
+
history: [userText("hi")],
|
|
30
|
+
model: "mock"
|
|
31
|
+
});
|
|
32
|
+
const exit = await Effect.runPromise(Effect.exit(program.pipe(Effect.provide(broken))));
|
|
33
|
+
globalExpect(Exit.isFailure(exit)).toBe(true);
|
|
34
|
+
if (Exit.isFailure(exit)) {
|
|
35
|
+
const failure = Cause.findErrorOption(exit.cause);
|
|
36
|
+
globalExpect(Option.isSome(failure)).toBe(true);
|
|
37
|
+
if (Option.isSome(failure)) globalExpect(failure.value).toBeInstanceOf(IncompleteTurn);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
it("propagates an AiError raised by streamTurn", async () => {
|
|
41
|
+
const rateLimited = new RateLimited({
|
|
42
|
+
provider: "mock",
|
|
43
|
+
raw: null
|
|
44
|
+
});
|
|
45
|
+
const failing = Layer.succeed(LanguageModel, { streamTurn: () => Stream.fail(rateLimited) });
|
|
46
|
+
const program = turn({
|
|
47
|
+
history: [],
|
|
48
|
+
model: "mock"
|
|
49
|
+
});
|
|
50
|
+
const exit = await Effect.runPromise(Effect.exit(program.pipe(Effect.provide(failing))));
|
|
51
|
+
globalExpect(Exit.isFailure(exit)).toBe(true);
|
|
52
|
+
if (Exit.isFailure(exit)) globalExpect(Cause.findErrorOption(exit.cause)).toEqual(Option.some(rateLimited));
|
|
53
|
+
});
|
|
54
|
+
it("returns the LAST TurnComplete when the stream contains multiple (defensive)", async () => {
|
|
55
|
+
const first = oneTextTurn("first");
|
|
56
|
+
const second = oneTextTurn("second");
|
|
57
|
+
const weird = Layer.succeed(LanguageModel, { streamTurn: () => Stream.fromIterable([TurnEvent.TurnComplete({ turn: first }), TurnEvent.TurnComplete({ turn: second })]) });
|
|
58
|
+
const program = turn({
|
|
59
|
+
history: [],
|
|
60
|
+
model: "mock"
|
|
61
|
+
});
|
|
62
|
+
globalExpect(await Effect.runPromise(program.pipe(Effect.provide(weird)))).toEqual(second);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
describe("LanguageModel.retry", () => {
|
|
66
|
+
const textDelta = (text) => TurnEvent.TextDelta({ text });
|
|
67
|
+
const textTurn = (text) => ({
|
|
68
|
+
items: [assistantText(text)],
|
|
69
|
+
usage: {
|
|
70
|
+
input_tokens: 0,
|
|
71
|
+
output_tokens: 0
|
|
72
|
+
},
|
|
73
|
+
stop_reason: "stop"
|
|
74
|
+
});
|
|
75
|
+
const completeEvent = (text) => TurnEvent.TurnComplete({ turn: textTurn(text) });
|
|
76
|
+
const attemptStream = (attempts, plan) => Stream.unwrap(Ref.getAndUpdate(attempts, (n) => n + 1).pipe(Effect.map((n) => plan[Math.min(n, plan.length - 1)])));
|
|
77
|
+
it("retries on RateLimited and yields the success on the next attempt", async () => {
|
|
78
|
+
const program = Effect.gen(function* () {
|
|
79
|
+
const attempts = yield* Ref.make(0);
|
|
80
|
+
const stream = attemptStream(attempts, [Stream.fail(new RateLimited({
|
|
81
|
+
provider: "mock",
|
|
82
|
+
raw: null
|
|
83
|
+
})), Stream.fromIterable([textDelta("ok"), completeEvent("ok")])]).pipe(retry(Schedule.recurs(3)));
|
|
84
|
+
const events = yield* Stream.runCollect(stream);
|
|
85
|
+
const count = yield* Ref.get(attempts);
|
|
86
|
+
return {
|
|
87
|
+
events: Array.from(events),
|
|
88
|
+
count
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
const { events, count } = await Effect.runPromise(program);
|
|
92
|
+
globalExpect(count).toBe(2);
|
|
93
|
+
globalExpect(events.map((e) => e._tag)).toEqual(["TextDelta", "TurnComplete"]);
|
|
94
|
+
});
|
|
95
|
+
it("surfaces the underlying retryable failure when retries are exhausted", async () => {
|
|
96
|
+
const cause = new Unavailable({
|
|
97
|
+
provider: "mock",
|
|
98
|
+
raw: null
|
|
99
|
+
});
|
|
100
|
+
const stream = Stream.fail(cause).pipe(retry(Schedule.recurs(2)));
|
|
101
|
+
const exit = await Effect.runPromise(Effect.exit(Stream.runCollect(stream)));
|
|
102
|
+
globalExpect(Exit.isFailure(exit)).toBe(true);
|
|
103
|
+
if (Exit.isFailure(exit)) globalExpect(Cause.findErrorOption(exit.cause)).toEqual(Option.some(cause));
|
|
104
|
+
});
|
|
105
|
+
it("bypasses retry for non-retryable AiError (ContentFiltered)", async () => {
|
|
106
|
+
const program = Effect.gen(function* () {
|
|
107
|
+
const attempts = yield* Ref.make(0);
|
|
108
|
+
const cause = new ContentFiltered({
|
|
109
|
+
provider: "mock",
|
|
110
|
+
raw: null
|
|
111
|
+
});
|
|
112
|
+
const stream = attemptStream(attempts, [Stream.fail(cause)]).pipe(retry(Schedule.recurs(5)));
|
|
113
|
+
return {
|
|
114
|
+
exit: yield* Effect.exit(Stream.runCollect(stream)),
|
|
115
|
+
count: yield* Ref.get(attempts),
|
|
116
|
+
cause
|
|
117
|
+
};
|
|
118
|
+
});
|
|
119
|
+
const { exit, count, cause } = await Effect.runPromise(program);
|
|
120
|
+
globalExpect(count).toBe(1);
|
|
121
|
+
globalExpect(Exit.isFailure(exit)).toBe(true);
|
|
122
|
+
if (Exit.isFailure(exit)) globalExpect(Cause.findErrorOption(exit.cause)).toEqual(Option.some(cause));
|
|
123
|
+
});
|
|
124
|
+
it("preserves deltas emitted before a retryable failure (and replays on retry)", async () => {
|
|
125
|
+
const program = Effect.gen(function* () {
|
|
126
|
+
const stream = attemptStream(yield* Ref.make(0), [Stream.concat(Stream.succeed(textDelta("partial")), Stream.fail(new Timeout({
|
|
127
|
+
provider: "mock",
|
|
128
|
+
raw: null
|
|
129
|
+
}))), Stream.fromIterable([textDelta("partial"), completeEvent("done")])]).pipe(retry(Schedule.recurs(1)));
|
|
130
|
+
const events = yield* Stream.runCollect(stream);
|
|
131
|
+
return Array.from(events);
|
|
132
|
+
});
|
|
133
|
+
globalExpect((await Effect.runPromise(program)).map((e) => e._tag)).toEqual([
|
|
134
|
+
"TextDelta",
|
|
135
|
+
"TextDelta",
|
|
136
|
+
"TurnComplete"
|
|
137
|
+
]);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
//#endregion
|
|
141
|
+
export {};
|
|
142
|
+
|
|
143
|
+
//# sourceMappingURL=LanguageModel.test.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LanguageModel.test.mjs","names":["Items.assistantText","Items.userText","MockProvider.layer","AiError.IncompleteTurn","AiError.RateLimited","AiError.Unavailable","AiError.ContentFiltered","AiError.Timeout"],"sources":["../../src/language-model/LanguageModel.test.ts"],"sourcesContent":["import { Cause, Effect, Exit, Layer, Option, Ref, Schedule, Stream } from \"effect\"\nimport { describe, expect, it } from \"vitest\"\nimport * as AiError from \"../domain/AiError.js\"\nimport * as Items from \"../domain/Items.js\"\nimport { type Turn, TurnEvent } from \"../domain/Turn.js\"\nimport { LanguageModel, retry, turn } from \"./LanguageModel.js\"\nimport * as MockProvider from \"../testing/MockProvider.js\"\n\nconst oneTextTurn = (text: string): Turn => ({\n items: [Items.assistantText(text)],\n usage: { input_tokens: 1, output_tokens: 1 },\n stop_reason: \"stop\",\n})\n\ndescribe(\"LanguageModel.turn\", () => {\n it(\"returns the assembled Turn from the terminal TurnComplete event\", async () => {\n const expected = oneTextTurn(\"hello world\")\n const program = turn({ history: [Items.userText(\"hi\")], model: \"mock\" })\n\n const result = await Effect.runPromise(\n program.pipe(Effect.provide(MockProvider.layer([expected]))),\n )\n\n expect(result).toEqual(expected)\n })\n\n it(\"fails with IncompleteTurn when the stream ends without TurnComplete\", async () => {\n // Custom service whose stream emits a single TextDelta and then ends.\n const broken = Layer.succeed(LanguageModel, {\n streamTurn: () => Stream.fromIterable<TurnEvent>([TurnEvent.TextDelta({ text: \"partial\" })]),\n })\n\n const program = turn({ history: [Items.userText(\"hi\")], model: \"mock\" })\n const exit = await Effect.runPromise(Effect.exit(program.pipe(Effect.provide(broken))))\n\n expect(Exit.isFailure(exit)).toBe(true)\n if (Exit.isFailure(exit)) {\n const failure = Cause.findErrorOption(exit.cause)\n expect(Option.isSome(failure)).toBe(true)\n if (Option.isSome(failure)) {\n expect(failure.value).toBeInstanceOf(AiError.IncompleteTurn)\n }\n }\n })\n\n it(\"propagates an AiError raised by streamTurn\", async () => {\n const rateLimited = new AiError.RateLimited({ provider: \"mock\", raw: null })\n const failing = Layer.succeed(LanguageModel, {\n streamTurn: () => Stream.fail<AiError.AiError>(rateLimited),\n })\n\n const program = turn({ history: [], model: \"mock\" })\n const exit = await Effect.runPromise(Effect.exit(program.pipe(Effect.provide(failing))))\n\n expect(Exit.isFailure(exit)).toBe(true)\n if (Exit.isFailure(exit)) {\n expect(Cause.findErrorOption(exit.cause)).toEqual(Option.some(rateLimited))\n }\n })\n\n it(\"returns the LAST TurnComplete when the stream contains multiple (defensive)\", async () => {\n // A misbehaving provider might emit two TurnComplete events; turn\n // should pick the last one (the most recent assembled Turn).\n const first = oneTextTurn(\"first\")\n const second = oneTextTurn(\"second\")\n const weird = Layer.succeed(LanguageModel, {\n streamTurn: () =>\n Stream.fromIterable<TurnEvent>([\n TurnEvent.TurnComplete({ turn: first }),\n TurnEvent.TurnComplete({ turn: second }),\n ]),\n })\n\n const program = turn({ history: [], model: \"mock\" })\n const result = await Effect.runPromise(program.pipe(Effect.provide(weird)))\n\n expect(result).toEqual(second)\n })\n})\n\ndescribe(\"LanguageModel.retry\", () => {\n const textDelta = (text: string): TurnEvent => TurnEvent.TextDelta({ text })\n const textTurn = (text: string): Turn => ({\n items: [Items.assistantText(text)],\n usage: { input_tokens: 0, output_tokens: 0 },\n stop_reason: \"stop\",\n })\n const completeEvent = (text: string): TurnEvent =>\n TurnEvent.TurnComplete({ turn: textTurn(text) })\n\n // Builds a stream that emits a failure or success based on attempt counter.\n // Each call to the returned Effect produces a fresh attempt stream.\n const attemptStream = (\n attempts: Ref.Ref<number>,\n plan: ReadonlyArray<Stream.Stream<TurnEvent, AiError.AiError>>,\n ): Stream.Stream<TurnEvent, AiError.AiError> =>\n Stream.unwrap(\n Ref.getAndUpdate(attempts, (n) => n + 1).pipe(\n Effect.map((n) => plan[Math.min(n, plan.length - 1)]!),\n ),\n )\n\n it(\"retries on RateLimited and yields the success on the next attempt\", async () => {\n const program = Effect.gen(function* () {\n const attempts = yield* Ref.make(0)\n const stream = attemptStream(attempts, [\n Stream.fail(new AiError.RateLimited({ provider: \"mock\", raw: null })),\n Stream.fromIterable([textDelta(\"ok\"), completeEvent(\"ok\")]),\n ]).pipe(retry(Schedule.recurs(3)))\n const events = yield* Stream.runCollect(stream)\n const count = yield* Ref.get(attempts)\n return { events: Array.from(events), count }\n })\n\n const { events, count } = await Effect.runPromise(program)\n expect(count).toBe(2)\n expect(events.map((e) => e._tag)).toEqual([\"TextDelta\", \"TurnComplete\"])\n })\n\n it(\"surfaces the underlying retryable failure when retries are exhausted\", async () => {\n const cause = new AiError.Unavailable({ provider: \"mock\", raw: null })\n const stream = Stream.fail<AiError.AiError>(cause).pipe(retry(Schedule.recurs(2)))\n\n const exit = await Effect.runPromise(Effect.exit(Stream.runCollect(stream)))\n expect(Exit.isFailure(exit)).toBe(true)\n if (Exit.isFailure(exit)) {\n expect(Cause.findErrorOption(exit.cause)).toEqual(Option.some(cause))\n }\n })\n\n it(\"bypasses retry for non-retryable AiError (ContentFiltered)\", async () => {\n const program = Effect.gen(function* () {\n const attempts = yield* Ref.make(0)\n const cause = new AiError.ContentFiltered({ provider: \"mock\", raw: null })\n const stream = attemptStream(attempts, [Stream.fail(cause)]).pipe(retry(Schedule.recurs(5)))\n const exit = yield* Effect.exit(Stream.runCollect(stream))\n const count = yield* Ref.get(attempts)\n return { exit, count, cause }\n })\n\n const { exit, count, cause } = await Effect.runPromise(program)\n expect(count).toBe(1) // no retry happened\n expect(Exit.isFailure(exit)).toBe(true)\n if (Exit.isFailure(exit)) {\n expect(Cause.findErrorOption(exit.cause)).toEqual(Option.some(cause))\n }\n })\n\n it(\"preserves deltas emitted before a retryable failure (and replays on retry)\", async () => {\n // Documents the 'replays on retry' caveat in the JSDoc — first attempt\n // emits a delta then fails; second attempt is a clean success. Consumer\n // sees the first delta twice (once from the failed attempt, once from\n // the replay).\n const program = Effect.gen(function* () {\n const attempts = yield* Ref.make(0)\n const stream = attemptStream(attempts, [\n Stream.concat(\n Stream.succeed<TurnEvent>(textDelta(\"partial\")),\n Stream.fail(new AiError.Timeout({ provider: \"mock\", raw: null })),\n ),\n Stream.fromIterable([textDelta(\"partial\"), completeEvent(\"done\")]),\n ]).pipe(retry(Schedule.recurs(1)))\n const events = yield* Stream.runCollect(stream)\n return Array.from(events)\n })\n\n const events = await Effect.runPromise(program)\n expect(events.map((e) => e._tag)).toEqual([\"TextDelta\", \"TextDelta\", \"TurnComplete\"])\n })\n})\n"],"mappings":"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"}
|
package/dist/loop/Loop.d.mts
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
|
-
import { l as IncompleteTurn } from "../AiError-
|
|
2
|
-
import { i as TurnEvent, r as Turn } from "../Turn-
|
|
1
|
+
import { l as IncompleteTurn } from "../AiError-CAX_48RU.mjs";
|
|
2
|
+
import { i as TurnEvent, r as Turn } from "../Turn-ChbL2foc.mjs";
|
|
3
3
|
import { Data, Effect, Stream, SubscriptionRef } from "effect";
|
|
4
4
|
|
|
5
5
|
//#region src/loop/Loop.d.ts
|
|
6
6
|
declare namespace Loop_d_exports {
|
|
7
|
-
export { Event, loop, loopWithState, next, nextAfter, nextAfterFold, onTurnComplete, stop, stopAfter, stopEvent, value };
|
|
7
|
+
export { Event, loop, loopFrom, loopWithState, next, nextAfter, nextAfterFold, onTurnComplete, stop, stopAfter, stopEvent, stopWith, stopWithAfter, value };
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
10
|
* The tagged union a body emits per pull. `Value` carries a payload that
|
|
11
11
|
* flows downstream. `Next` ends the current iteration and continues with a
|
|
12
|
-
* new state. `Stop` ends the loop entirely.
|
|
12
|
+
* new state. `Stop` ends the loop entirely with no carried state.
|
|
13
|
+
* `StopWith` also ends the loop but carries a final state that `loopFrom`
|
|
14
|
+
* will thread to the next input and `loopWithState` will write to its
|
|
15
|
+
* `SubscriptionRef` before the loop ends. Plain `loop` has no next
|
|
16
|
+
* iteration to apply it to and treats `StopWith` like `Stop`.
|
|
17
|
+
*
|
|
18
|
+
* `Stop` is intentionally `{}` so the bare `stopEvent` / `stop` helpers
|
|
19
|
+
* don't constrain `S` from a body's stream type — every body has a `Stop`
|
|
20
|
+
* variant in its union, and forcing `S` to flow through it would break
|
|
21
|
+
* inference whenever the body never uses `next` / `stopWith`.
|
|
13
22
|
*/
|
|
14
23
|
type Event<A, S> = Data.TaggedEnum<{
|
|
15
24
|
Value: {
|
|
@@ -19,13 +28,27 @@ type Event<A, S> = Data.TaggedEnum<{
|
|
|
19
28
|
readonly state: S;
|
|
20
29
|
};
|
|
21
30
|
Stop: {};
|
|
31
|
+
StopWith: {
|
|
32
|
+
readonly state: S;
|
|
33
|
+
};
|
|
22
34
|
}>;
|
|
23
35
|
/** Wrap a value so it flows through the loop to downstream consumers. */
|
|
24
36
|
declare const value: <A>(a: A) => Event<A, never>;
|
|
25
37
|
/** End the current iteration and continue with a new state. */
|
|
26
38
|
declare const next: <S>(state: S) => Event<never, S>;
|
|
27
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* The terminal `Stop` event with no carried state. Use `stop` (the Stream)
|
|
41
|
+
* to end a loop body without communicating a final state.
|
|
42
|
+
*/
|
|
28
43
|
declare const stopEvent: Event<never, never>;
|
|
44
|
+
/**
|
|
45
|
+
* Terminal event that ends the loop AND carries a final state. For
|
|
46
|
+
* `loopFrom` this is the natural "this input is done, here's the state to
|
|
47
|
+
* carry forward to the next input" signal — symmetric with `next(s)` but
|
|
48
|
+
* ending the inner loop instead of continuing it. For `loopWithState` the
|
|
49
|
+
* carried state is written to the `SubscriptionRef` before the loop ends.
|
|
50
|
+
*/
|
|
51
|
+
declare const stopWith: <S>(state: S) => Event<never, S>;
|
|
29
52
|
/**
|
|
30
53
|
* A single-element stream that ends the loop. Return this from a body when
|
|
31
54
|
* there's nothing else to emit; equivalent to `stopAfter(Stream.empty)` but
|
|
@@ -36,13 +59,33 @@ declare const stop: Stream.Stream<Event<never, never>>;
|
|
|
36
59
|
* Pipe a raw `Stream<A>` into the loop's emit shape, then terminate the
|
|
37
60
|
* iteration with `next(state)`. Common shape for "stream this turn's
|
|
38
61
|
* deltas, then continue with updated history."
|
|
62
|
+
*
|
|
63
|
+
* Dual: data-first `nextAfter(stream, state)` and data-last
|
|
64
|
+
* `stream.pipe(nextAfter(state))` both work.
|
|
39
65
|
*/
|
|
40
|
-
declare const nextAfter:
|
|
66
|
+
declare const nextAfter: {
|
|
67
|
+
<S>(state: S): <A, E, R>(stream: Stream.Stream<A, E, R>) => Stream.Stream<Event<A, S>, E, R>;
|
|
68
|
+
<S, A, E, R>(stream: Stream.Stream<A, E, R>, state: S): Stream.Stream<Event<A, S>, E, R>;
|
|
69
|
+
};
|
|
41
70
|
/**
|
|
42
71
|
* Pipe a raw `Stream<A>` into the loop's emit shape, then terminate the
|
|
43
72
|
* loop. Common shape for "stream this turn's deltas, then we're done."
|
|
73
|
+
*
|
|
74
|
+
* Unary on the stream — already pipe-compatible via `stream.pipe(stopAfter)`.
|
|
44
75
|
*/
|
|
45
76
|
declare const stopAfter: <A, E, R>(stream: Stream.Stream<A, E, R>) => Stream.Stream<Event<A, never>, E, R>;
|
|
77
|
+
/**
|
|
78
|
+
* Pipe a raw `Stream<A>` into the loop's emit shape, then terminate with
|
|
79
|
+
* `stopWith(state)`. The natural "emit final outputs, advance state, end
|
|
80
|
+
* this input's inner loop" shape for `loopFrom`.
|
|
81
|
+
*
|
|
82
|
+
* Dual: data-first `stopWithAfter(stream, state)` and data-last
|
|
83
|
+
* `stream.pipe(stopWithAfter(state))` both work.
|
|
84
|
+
*/
|
|
85
|
+
declare const stopWithAfter: {
|
|
86
|
+
<S>(state: S): <A, E, R>(stream: Stream.Stream<A, E, R>) => Stream.Stream<Event<A, S>, E, R>;
|
|
87
|
+
<S, A, E, R>(stream: Stream.Stream<A, E, R>, state: S): Stream.Stream<Event<A, S>, E, R>;
|
|
88
|
+
};
|
|
46
89
|
/**
|
|
47
90
|
* General `nextAfter` variant: drain `stream` to the consumer, fold elements
|
|
48
91
|
* into an accumulator, and at end-of-stream emit one `next(build(finalAcc))`.
|
|
@@ -50,12 +93,18 @@ declare const stopAfter: <A, E, R>(stream: Stream.Stream<A, E, R>) => Stream.Str
|
|
|
50
93
|
* Subsumes `nextAfter` when state is constant (`reduce: (s, _) => s`,
|
|
51
94
|
* `build: (s) => s`). Used by `Toolkit.continueWith` to collect tool
|
|
52
95
|
* results and build next state without exposing a Ref to recipes.
|
|
96
|
+
*
|
|
97
|
+
* Dual: data-first `nextAfterFold(stream, initial, reduce, build)` and
|
|
98
|
+
* data-last `stream.pipe(nextAfterFold(initial, reduce, build))` both work.
|
|
53
99
|
*/
|
|
54
|
-
declare const nextAfterFold:
|
|
100
|
+
declare const nextAfterFold: {
|
|
101
|
+
<A, B, S>(initial: B, reduce: (acc: B, a: A) => B, build: (b: B) => S): <E, R>(stream: Stream.Stream<A, E, R>) => Stream.Stream<Event<A, S>, E, R>;
|
|
102
|
+
<A, B, S, E, R>(stream: Stream.Stream<A, E, R>, initial: B, reduce: (acc: B, a: A) => B, build: (b: B) => S): Stream.Stream<Event<A, S>, E, R>;
|
|
103
|
+
};
|
|
55
104
|
/**
|
|
56
105
|
* Lift a provider's `Stream<TurnEvent>` into a loop body's `Stream<Event<TurnEvent | A, S>>`.
|
|
57
106
|
* Each delta passes through as `value(delta)` (including the terminal
|
|
58
|
-
* `
|
|
107
|
+
* `TurnComplete`, so the consumer sees turn boundaries naturally). Once
|
|
59
108
|
* the terminal arrives, `then(turn)` runs and its returned stream of loop
|
|
60
109
|
* events (typically tool outputs followed by `next(state)` or `stop`) is
|
|
61
110
|
* concatenated.
|
|
@@ -63,11 +112,17 @@ declare const nextAfterFold: <A, B, S, E, R>(stream: Stream.Stream<A, E, R>, ini
|
|
|
63
112
|
* Pre-pipe transforms (`Stream.tap` / `Stream.map` / `Stream.filter`) on
|
|
64
113
|
* the raw delta stream cover anything an `emit`-style callback would do.
|
|
65
114
|
*
|
|
66
|
-
* If the upstream ends without a `
|
|
115
|
+
* If the upstream ends without a `TurnComplete`, the resulting stream
|
|
67
116
|
* fails with `AiError.IncompleteTurn`. Catch it via `Stream.catchTag` if
|
|
68
117
|
* you want to recover.
|
|
118
|
+
*
|
|
119
|
+
* Dual: data-first `onTurnComplete(deltas, then)` and data-last
|
|
120
|
+
* `deltas.pipe(onTurnComplete(then))` both work.
|
|
69
121
|
*/
|
|
70
|
-
declare const onTurnComplete:
|
|
122
|
+
declare const onTurnComplete: {
|
|
123
|
+
<S, A, E2 = never, R2 = never>(then: (turn: Turn) => Effect.Effect<Stream.Stream<Event<A, S>, E2, R2>, E2, R2>): <E, R>(deltas: Stream.Stream<TurnEvent, E, R>) => Stream.Stream<Event<TurnEvent | A, S>, E | E2 | IncompleteTurn, R | R2>;
|
|
124
|
+
<S, A, E, R, E2 = never, R2 = never>(deltas: Stream.Stream<TurnEvent, E, R>, then: (turn: Turn) => Effect.Effect<Stream.Stream<Event<A, S>, E2, R2>, E2, R2>): Stream.Stream<Event<TurnEvent | A, S>, E | E2 | IncompleteTurn, R | R2>;
|
|
125
|
+
};
|
|
71
126
|
type LoopBody<S, A, E, R> = (state: S) => Stream.Stream<Event<A, S>, E, R> | Effect.Effect<Stream.Stream<Event<A, S>, E, R>, E, R>;
|
|
72
127
|
/**
|
|
73
128
|
* Drive a state-threaded loop body. Each iteration runs `body(state)` to get
|
|
@@ -82,6 +137,34 @@ declare const loop: {
|
|
|
82
137
|
<S, A, E, R>(body: LoopBody<S, A, E, R>): (initial: S) => Stream.Stream<A, E, R>;
|
|
83
138
|
<S, A, E, R>(initial: S, body: LoopBody<S, A, E, R>): Stream.Stream<A, E, R>;
|
|
84
139
|
};
|
|
140
|
+
type LoopFromBody<S, I, A, E, R> = (state: S, input: I) => Stream.Stream<Event<A, S>, E, R> | Effect.Effect<Stream.Stream<Event<A, S>, E, R>, E, R>;
|
|
141
|
+
/**
|
|
142
|
+
* Input-driven sibling of `loop`. For each item pulled from the input
|
|
143
|
+
* stream, runs an inner seed-driven `loop` whose body is
|
|
144
|
+
* `(s) => body(s, item)`. State is threaded across input items.
|
|
145
|
+
*
|
|
146
|
+
* **Per-input semantics — the body emits standard `Event<A, S>`:**
|
|
147
|
+
* - `value(a)`: emit `a` downstream
|
|
148
|
+
* - `next(s)`: re-run the body with the SAME input and new state `s`
|
|
149
|
+
* (multi-turn within one input — e.g. multiple model turns + tool
|
|
150
|
+
* calls for one document)
|
|
151
|
+
* - `stop`: end this input's inner loop, advance to the next input
|
|
152
|
+
* (state preserved)
|
|
153
|
+
* - body stream ending without a decision: same as `stop` (advance)
|
|
154
|
+
*
|
|
155
|
+
* **Outer termination:** the input stream ending. To halt programmatically
|
|
156
|
+
* from within, end the input stream upstream (`Stream.takeWhile`, a
|
|
157
|
+
* `SubscriptionRef` gate, etc.). Reserving `stop` for per-item
|
|
158
|
+
* advancement is what makes the common "stream of documents, multi-turn
|
|
159
|
+
* conversation per document" shape readable.
|
|
160
|
+
*
|
|
161
|
+
* Dual: data-first `loopFrom(input, initial, body)` and data-last
|
|
162
|
+
* `input.pipe(loopFrom(initial, body))` both work.
|
|
163
|
+
*/
|
|
164
|
+
declare const loopFrom: {
|
|
165
|
+
<S, I, A, E, R>(initial: S, body: LoopFromBody<S, I, A, E, R>): <EI, RI>(input: Stream.Stream<I, EI, RI>) => Stream.Stream<A, E | EI, R | RI>;
|
|
166
|
+
<S, I, A, E, R, EI, RI>(input: Stream.Stream<I, EI, RI>, initial: S, body: LoopFromBody<S, I, A, E, R>): Stream.Stream<A, E | EI, R | RI>;
|
|
167
|
+
};
|
|
85
168
|
/**
|
|
86
169
|
* Like `loop`, but exposes the current loop state as a `SubscriptionRef`
|
|
87
170
|
* alongside the value stream.
|
|
@@ -107,5 +190,5 @@ declare const loopWithState: <S, A, E, R>(initial: S, body: LoopBody<S, A, E, R>
|
|
|
107
190
|
readonly state: SubscriptionRef.SubscriptionRef<S>;
|
|
108
191
|
}>;
|
|
109
192
|
//#endregion
|
|
110
|
-
export { Event, loop, loopWithState, next, nextAfter, nextAfterFold, onTurnComplete, stop, stopAfter, stopEvent, Loop_d_exports as t, value };
|
|
193
|
+
export { Event, loop, loopFrom, loopWithState, next, nextAfter, nextAfterFold, onTurnComplete, stop, stopAfter, stopEvent, stopWith, stopWithAfter, Loop_d_exports as t, value };
|
|
111
194
|
//# sourceMappingURL=Loop.d.mts.map
|
package/dist/loop/Loop.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Loop.d.mts","names":[],"sources":["../../src/loop/Loop.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"Loop.d.mts","names":[],"sources":["../../src/loop/Loop.ts"],"mappings":"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"}
|