@effect-uai/core 0.5.1 → 0.6.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/{Items-BH8xUkoR.d.mts → Items-pXR5BAgW.d.mts} +12 -12
- package/dist/Items-pXR5BAgW.d.mts.map +1 -0
- package/dist/SandboxError-BvKypkJm.d.mts +158 -0
- package/dist/SandboxError-BvKypkJm.d.mts.map +1 -0
- package/dist/{Tool-87ViKCCO.d.mts → Tool-DP-AwKOf.d.mts} +11 -11
- package/dist/{Tool-87ViKCCO.d.mts.map → Tool-DP-AwKOf.d.mts.map} +1 -1
- package/dist/domain/Items.d.mts +2 -2
- package/dist/domain/Items.mjs +15 -15
- package/dist/domain/Items.mjs.map +1 -1
- package/dist/domain/Turn.d.mts +388 -2
- package/dist/domain/Turn.d.mts.map +1 -0
- package/dist/domain/Turn.mjs +13 -13
- package/dist/domain/Turn.mjs.map +1 -1
- package/dist/index.d.mts +12 -6
- package/dist/index.mjs +9 -3
- package/dist/language-model/LanguageModel.d.mts +29 -27
- package/dist/language-model/LanguageModel.d.mts.map +1 -1
- package/dist/language-model/LanguageModel.mjs +16 -25
- package/dist/language-model/LanguageModel.mjs.map +1 -1
- package/dist/language-model/LanguageModel.test.mjs +10 -81
- package/dist/language-model/LanguageModel.test.mjs.map +1 -1
- package/dist/loop/Loop.d.mts +37 -90
- package/dist/loop/Loop.d.mts.map +1 -1
- package/dist/loop/Loop.mjs +39 -93
- package/dist/loop/Loop.mjs.map +1 -1
- package/dist/loop/Loop.test.mjs +46 -54
- package/dist/loop/Loop.test.mjs.map +1 -1
- package/dist/retry/Retry.d.mts +64 -0
- package/dist/retry/Retry.d.mts.map +1 -0
- package/dist/retry/Retry.mjs +69 -0
- package/dist/retry/Retry.mjs.map +1 -0
- package/dist/retry/Retry.test.mjs +142 -0
- package/dist/retry/Retry.test.mjs.map +1 -0
- package/dist/sandbox/Memory.d.mts +53 -0
- package/dist/sandbox/Memory.d.mts.map +1 -0
- package/dist/sandbox/Memory.mjs +68 -0
- package/dist/sandbox/Memory.mjs.map +1 -0
- package/dist/sandbox/Sandbox.d.mts +717 -0
- package/dist/sandbox/Sandbox.d.mts.map +1 -0
- package/dist/sandbox/Sandbox.mjs +192 -0
- package/dist/sandbox/Sandbox.mjs.map +1 -0
- package/dist/sandbox/SandboxError.d.mts +2 -0
- package/dist/sandbox/SandboxError.mjs +89 -0
- package/dist/sandbox/SandboxError.mjs.map +1 -0
- package/dist/sandbox/SandboxImage.d.mts +28 -0
- package/dist/sandbox/SandboxImage.d.mts.map +1 -0
- package/dist/sandbox/SandboxImage.mjs +32 -0
- package/dist/sandbox/SandboxImage.mjs.map +1 -0
- package/dist/sandbox/SandboxNetwork.d.mts +34 -0
- package/dist/sandbox/SandboxNetwork.d.mts.map +1 -0
- package/dist/sandbox/SandboxNetwork.mjs +36 -0
- package/dist/sandbox/SandboxNetwork.mjs.map +1 -0
- package/dist/speech-synthesizer/SpeechSynthesizer.d.mts +98 -2
- package/dist/speech-synthesizer/SpeechSynthesizer.d.mts.map +1 -1
- package/dist/speech-synthesizer/SpeechSynthesizer.mjs +32 -2
- package/dist/speech-synthesizer/SpeechSynthesizer.mjs.map +1 -1
- package/dist/speech-synthesizer/SpeechSynthesizer.test.mjs +56 -2
- package/dist/speech-synthesizer/SpeechSynthesizer.test.mjs.map +1 -1
- package/dist/testing/MockProvider.d.mts +3 -3
- package/dist/testing/MockProvider.d.mts.map +1 -1
- package/dist/testing/MockProvider.mjs +14 -8
- package/dist/testing/MockProvider.mjs.map +1 -1
- package/dist/testing/MockSandbox.d.mts +604 -0
- package/dist/testing/MockSandbox.d.mts.map +1 -0
- package/dist/testing/MockSandbox.mjs +160 -0
- package/dist/testing/MockSandbox.mjs.map +1 -0
- package/dist/testing/MockSpeechSynthesizer.d.mts +26 -11
- package/dist/testing/MockSpeechSynthesizer.d.mts.map +1 -1
- package/dist/testing/MockSpeechSynthesizer.mjs +75 -51
- package/dist/testing/MockSpeechSynthesizer.mjs.map +1 -1
- package/dist/tool/{Resolvers.d.mts → Approval.d.mts} +27 -27
- package/dist/tool/Approval.d.mts.map +1 -0
- package/dist/tool/{Resolvers.mjs → Approval.mjs} +18 -18
- package/dist/tool/Approval.mjs.map +1 -0
- package/dist/tool/Approval.test.d.mts +1 -0
- package/dist/tool/{Resolvers.test.mjs → Approval.test.mjs} +40 -40
- package/dist/tool/Approval.test.mjs.map +1 -0
- package/dist/tool/HistoryCheck.d.mts +6 -6
- package/dist/tool/HistoryCheck.d.mts.map +1 -1
- package/dist/tool/HistoryCheck.mjs +5 -5
- package/dist/tool/HistoryCheck.mjs.map +1 -1
- package/dist/tool/Tool.d.mts +2 -2
- package/dist/tool/Tool.mjs +4 -4
- package/dist/tool/Tool.mjs.map +1 -1
- package/dist/tool/ToolEvent.d.mts +19 -19
- package/dist/tool/ToolEvent.d.mts.map +1 -1
- package/dist/tool/ToolEvent.mjs +8 -8
- package/dist/tool/ToolEvent.mjs.map +1 -1
- package/dist/tool/{Outcome.d.mts → ToolResult.d.mts} +29 -29
- package/dist/tool/ToolResult.d.mts.map +1 -0
- package/dist/tool/ToolResult.mjs +61 -0
- package/dist/tool/ToolResult.mjs.map +1 -0
- package/dist/tool/Toolkit.d.mts +58 -26
- package/dist/tool/Toolkit.d.mts.map +1 -1
- package/dist/tool/Toolkit.mjs +62 -34
- package/dist/tool/Toolkit.mjs.map +1 -1
- package/dist/tool/Toolkit.test.mjs +13 -13
- package/dist/tool/Toolkit.test.mjs.map +1 -1
- package/package.json +35 -7
- package/src/domain/Items.ts +9 -9
- package/src/domain/Turn.ts +16 -16
- package/src/index.ts +8 -2
- package/src/language-model/LanguageModel.test.ts +21 -102
- package/src/language-model/LanguageModel.ts +36 -59
- package/src/loop/Loop.test.ts +93 -91
- package/src/loop/Loop.ts +72 -170
- package/src/retry/Retry.test.ts +163 -0
- package/src/retry/Retry.ts +84 -0
- package/src/sandbox/Memory.ts +128 -0
- package/src/sandbox/Sandbox.ts +639 -0
- package/src/sandbox/SandboxError.ts +140 -0
- package/src/sandbox/SandboxImage.ts +26 -0
- package/src/sandbox/SandboxNetwork.ts +34 -0
- package/src/speech-synthesizer/SpeechSynthesizer.test.ts +89 -0
- package/src/speech-synthesizer/SpeechSynthesizer.ts +124 -0
- package/src/testing/MockProvider.ts +31 -25
- package/src/testing/MockSandbox.ts +342 -0
- package/src/testing/MockSpeechSynthesizer.ts +137 -77
- package/src/tool/{Resolvers.test.ts → Approval.test.ts} +45 -54
- package/src/tool/{Resolvers.ts → Approval.ts} +25 -25
- package/src/tool/HistoryCheck.ts +10 -13
- package/src/tool/Tool.ts +13 -15
- package/src/tool/ToolEvent.ts +7 -7
- package/src/tool/{Outcome.ts → ToolResult.ts} +20 -20
- package/src/tool/Toolkit.test.ts +13 -13
- package/src/tool/Toolkit.ts +97 -73
- package/dist/Items-BH8xUkoR.d.mts.map +0 -1
- package/dist/Turn-0CwCAyVe.d.mts +0 -388
- package/dist/Turn-0CwCAyVe.d.mts.map +0 -1
- package/dist/tool/Outcome.d.mts.map +0 -1
- package/dist/tool/Outcome.mjs +0 -61
- package/dist/tool/Outcome.mjs.map +0 -1
- package/dist/tool/Resolvers.d.mts.map +0 -1
- package/dist/tool/Resolvers.mjs.map +0 -1
- package/dist/tool/Resolvers.test.mjs.map +0 -1
- /package/dist/{tool/Resolvers.test.d.mts → retry/Retry.test.d.mts} +0 -0
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { t as AiError } from "../AiError-CAX_48RU.mjs";
|
|
2
|
+
import { o as HistoryItem } from "../Items-pXR5BAgW.mjs";
|
|
3
3
|
import { i as StructuredFormat } from "../StructuredFormat-BbN4dosH.mjs";
|
|
4
|
-
import {
|
|
5
|
-
import { o as ToolDescriptor } from "../Tool-
|
|
6
|
-
import { Context, Effect,
|
|
7
|
-
import * as _$effect_Types0 from "effect/Types";
|
|
8
|
-
import * as _$effect_Cause0 from "effect/Cause";
|
|
4
|
+
import { Turn, TurnEvent } from "../domain/Turn.mjs";
|
|
5
|
+
import { o as ToolDescriptor } from "../Tool-DP-AwKOf.mjs";
|
|
6
|
+
import { Context, Effect, Stream } from "effect";
|
|
9
7
|
|
|
10
8
|
//#region src/language-model/LanguageModel.d.ts
|
|
11
9
|
declare namespace LanguageModel_d_exports {
|
|
12
|
-
export { CommonRequest, LanguageModel, LanguageModelService,
|
|
10
|
+
export { CommonRequest, LanguageModel, LanguageModelService, streamTurn, turn, turnFromStream };
|
|
13
11
|
}
|
|
14
12
|
/**
|
|
15
13
|
* Cross-provider request shape. Every call carries its own `history` and
|
|
@@ -18,7 +16,7 @@ declare namespace LanguageModel_d_exports {
|
|
|
18
16
|
* ...) lives in that provider's own request interface, which extends this.
|
|
19
17
|
*/
|
|
20
18
|
type CommonRequest = {
|
|
21
|
-
readonly history: ReadonlyArray<
|
|
19
|
+
readonly history: ReadonlyArray<HistoryItem>;
|
|
22
20
|
/**
|
|
23
21
|
* Model identifier. Each provider narrows this to its typed literal union,
|
|
24
22
|
* so code that yields a typed provider tag gets autocompletion.
|
|
@@ -34,7 +32,7 @@ type CommonRequest = {
|
|
|
34
32
|
readonly maxOutputTokens?: number;
|
|
35
33
|
/**
|
|
36
34
|
* Schema-bound JSON output. The provider constrains the wire to match the
|
|
37
|
-
* schema; pair with `Turn.
|
|
35
|
+
* schema; pair with `Turn.decodeStructured` for runtime validation. Supported
|
|
38
36
|
* across all current providers (OpenAI Responses json_schema, Anthropic
|
|
39
37
|
* `output_config`, Gemini `responseJsonSchema`).
|
|
40
38
|
*/
|
|
@@ -42,6 +40,16 @@ type CommonRequest = {
|
|
|
42
40
|
};
|
|
43
41
|
type LanguageModelService = {
|
|
44
42
|
readonly streamTurn: (request: CommonRequest) => Stream.Stream<TurnEvent, AiError>;
|
|
43
|
+
/**
|
|
44
|
+
* Drain a single turn and return the assembled `Turn` from the
|
|
45
|
+
* terminal `TurnComplete` event. Fails with `IncompleteTurn` if the
|
|
46
|
+
* stream ends without one.
|
|
47
|
+
*
|
|
48
|
+
* Most providers derive this from `streamTurn` via
|
|
49
|
+
* {@link turnFromStream}; providers with a native non-streaming
|
|
50
|
+
* endpoint may override with a cheaper direct call.
|
|
51
|
+
*/
|
|
52
|
+
readonly turn: (request: CommonRequest) => Effect.Effect<Turn, AiError>;
|
|
45
53
|
};
|
|
46
54
|
declare const LanguageModel_base: Context.ServiceClass<LanguageModel, "@betalyra/effect-uai/LanguageModel", LanguageModelService>;
|
|
47
55
|
declare class LanguageModel extends LanguageModel_base {}
|
|
@@ -50,25 +58,19 @@ declare class LanguageModel extends LanguageModel_base {}
|
|
|
50
58
|
*/
|
|
51
59
|
declare const streamTurn: (request: CommonRequest) => Stream.Stream<TurnEvent, AiError, LanguageModel>;
|
|
52
60
|
/**
|
|
53
|
-
* Drain
|
|
54
|
-
* `
|
|
55
|
-
*
|
|
61
|
+
* Drain a single turn and return the assembled `Turn`. Delegates to the
|
|
62
|
+
* service's `turn` method — providers with a native complete endpoint
|
|
63
|
+
* can override; the rest get the default streamTurn-drain via
|
|
64
|
+
* {@link turnFromStream}.
|
|
56
65
|
*/
|
|
57
|
-
declare const turn: (request: CommonRequest) => Effect.Effect<Turn, AiError
|
|
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
|
-
}> {}
|
|
66
|
+
declare const turn: (request: CommonRequest) => Effect.Effect<Turn, AiError, LanguageModel>;
|
|
65
67
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
68
|
+
* Build a `turn` implementation from a `streamTurn` implementation.
|
|
69
|
+
* Providers without a native non-streaming endpoint use this to
|
|
70
|
+
* populate the service's `turn` field. Generic over the request type so
|
|
71
|
+
* provider-typed services (with their narrowed request) can reuse it.
|
|
70
72
|
*/
|
|
71
|
-
declare const
|
|
73
|
+
declare const turnFromStream: <Req>(streamTurn: (request: Req) => Stream.Stream<TurnEvent, AiError>) => (request: Req) => Effect.Effect<Turn, AiError>;
|
|
72
74
|
//#endregion
|
|
73
|
-
export { CommonRequest, LanguageModel, LanguageModelService,
|
|
75
|
+
export { CommonRequest, LanguageModel, LanguageModelService, streamTurn, LanguageModel_d_exports as t, turn, turnFromStream };
|
|
74
76
|
//# 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,WAAA;;;;AADlC;WAMW,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;EAdA;;;;;;EAAA,SAqBA,UAAA,GAAa,gBAAA;AAAA;AAAA,KAGZ,oBAAA;EAAA,SACD,UAAA,GAAa,OAAA,EAAS,aAAA,KAAkB,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,OAAA;EAbjE;;;;;;;;AAYX;EAZW,SAuBA,IAAA,GAAO,OAAA,EAAS,aAAA,KAAkB,MAAA,CAAO,MAAA,CAAO,IAAA,EAAM,OAAA;AAAA;AAAA,cAChE,kBAAA;cAEY,aAAA,SAAsB,kBAAA;;;;cAOtB,UAAA,GACX,OAAA,EAAS,aAAA,KACR,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,OAAA,EAAiB,aAAA;;;;;;;cAShC,IAAA,GAAQ,OAAA,EAAS,aAAA,KAAgB,MAAA,CAAO,MAAA,CAAO,IAAA,EAAM,OAAA,EAAiB,aAAA;;;;;;;cAStE,cAAA,QACL,UAAA,GAAa,OAAA,EAAS,GAAA,KAAQ,MAAA,CAAO,MAAA,CAAO,SAAA,EAAW,OAAA,OAC5D,OAAA,EAAS,GAAA,KAAM,MAAA,CAAO,MAAA,CAAO,IAAA,EAAM,OAAA"}
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { n as __exportAll } from "../chunk-uyGKjUfl.mjs";
|
|
2
2
|
import { IncompleteTurn } from "../domain/AiError.mjs";
|
|
3
3
|
import { isTurnComplete } from "../domain/Turn.mjs";
|
|
4
|
-
import { Array, Context,
|
|
4
|
+
import { Array, Context, Effect, Option, Stream } from "effect";
|
|
5
5
|
//#region src/language-model/LanguageModel.ts
|
|
6
6
|
var LanguageModel_exports = /* @__PURE__ */ __exportAll({
|
|
7
7
|
LanguageModel: () => LanguageModel,
|
|
8
|
-
Retryable: () => Retryable,
|
|
9
|
-
retry: () => retry,
|
|
10
8
|
streamTurn: () => streamTurn,
|
|
11
|
-
turn: () => turn
|
|
9
|
+
turn: () => turn,
|
|
10
|
+
turnFromStream: () => turnFromStream
|
|
12
11
|
});
|
|
13
12
|
var LanguageModel = class extends Context.Service()("@betalyra/effect-uai/LanguageModel") {};
|
|
14
13
|
/**
|
|
@@ -16,31 +15,23 @@ var LanguageModel = class extends Context.Service()("@betalyra/effect-uai/Langua
|
|
|
16
15
|
*/
|
|
17
16
|
const streamTurn = (request) => Stream.unwrap(Effect.map(LanguageModel.asEffect(), (m) => m.streamTurn(request)));
|
|
18
17
|
/**
|
|
19
|
-
* Drain
|
|
20
|
-
* `
|
|
21
|
-
*
|
|
18
|
+
* Drain a single turn and return the assembled `Turn`. Delegates to the
|
|
19
|
+
* service's `turn` method — providers with a native complete endpoint
|
|
20
|
+
* can override; the rest get the default streamTurn-drain via
|
|
21
|
+
* {@link turnFromStream}.
|
|
22
22
|
*/
|
|
23
|
-
const turn = (request) =>
|
|
23
|
+
const turn = (request) => Effect.flatMap(LanguageModel.asEffect(), (m) => m.turn(request));
|
|
24
|
+
/**
|
|
25
|
+
* Build a `turn` implementation from a `streamTurn` implementation.
|
|
26
|
+
* Providers without a native non-streaming endpoint use this to
|
|
27
|
+
* populate the service's `turn` field. Generic over the request type so
|
|
28
|
+
* provider-typed services (with their narrowed request) can reuse it.
|
|
29
|
+
*/
|
|
30
|
+
const turnFromStream = (streamTurn) => (request) => streamTurn(request).pipe(Stream.runCollect, Effect.flatMap((events) => Array.findLast(events, isTurnComplete).pipe(Option.match({
|
|
24
31
|
onNone: () => Effect.fail(new IncompleteTurn({})),
|
|
25
32
|
onSome: (e) => Effect.succeed(e.turn)
|
|
26
33
|
}))));
|
|
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)));
|
|
43
34
|
//#endregion
|
|
44
|
-
export { LanguageModel,
|
|
35
|
+
export { LanguageModel, streamTurn, LanguageModel_exports as t, turn, turnFromStream };
|
|
45
36
|
|
|
46
37
|
//# sourceMappingURL=LanguageModel.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LanguageModel.mjs","names":["Arr","AiError.IncompleteTurn"],"sources":["../../src/language-model/LanguageModel.ts"],"sourcesContent":["import { Array as Arr, Context,
|
|
1
|
+
{"version":3,"file":"LanguageModel.mjs","names":["Arr","AiError.IncompleteTurn"],"sources":["../../src/language-model/LanguageModel.ts"],"sourcesContent":["import { Array as Arr, Context, Effect, Option, Stream } from \"effect\"\nimport * as AiError from \"../domain/AiError.js\"\nimport type { HistoryItem } 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<HistoryItem>\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.decodeStructured` 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 * Drain a single turn and return the assembled `Turn` from the\n * terminal `TurnComplete` event. Fails with `IncompleteTurn` if the\n * stream ends without one.\n *\n * Most providers derive this from `streamTurn` via\n * {@link turnFromStream}; providers with a native non-streaming\n * endpoint may override with a cheaper direct call.\n */\n readonly turn: (request: CommonRequest) => Effect.Effect<Turn, 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 a single turn and return the assembled `Turn`. Delegates to the\n * service's `turn` method — providers with a native complete endpoint\n * can override; the rest get the default streamTurn-drain via\n * {@link turnFromStream}.\n */\nexport const turn = (request: CommonRequest): Effect.Effect<Turn, AiError.AiError, LanguageModel> =>\n Effect.flatMap(LanguageModel.asEffect(), (m) => m.turn(request))\n\n/**\n * Build a `turn` implementation from a `streamTurn` implementation.\n * Providers without a native non-streaming endpoint use this to\n * populate the service's `turn` field. Generic over the request type so\n * provider-typed services (with their narrowed request) can reuse it.\n */\nexport const turnFromStream =\n <Req>(streamTurn: (request: Req) => Stream.Stream<TurnEvent, AiError.AiError>) =>\n (request: Req): Effect.Effect<Turn, AiError.AiError> =>\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"],"mappings":";;;;;;;;;;;AAoDA,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;;;;;;;AAQnF,MAAa,QAAQ,YACnB,OAAO,QAAQ,cAAc,UAAU,GAAG,MAAM,EAAE,KAAK,QAAQ,CAAC;;;;;;;AAQlE,MAAa,kBACL,gBACL,YACC,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"}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { IncompleteTurn, RateLimited } from "../domain/AiError.mjs";
|
|
2
2
|
import { assistantText, userText } from "../domain/Items.mjs";
|
|
3
3
|
import { TurnEvent } from "../domain/Turn.mjs";
|
|
4
|
-
import { LanguageModel,
|
|
4
|
+
import { LanguageModel, turn, turnFromStream } from "./LanguageModel.mjs";
|
|
5
5
|
import { i as it, n as globalExpect, r as describe } from "../dist-DV5ISja1.mjs";
|
|
6
6
|
import { layer } from "../testing/MockProvider.mjs";
|
|
7
|
-
import { Cause, Effect, Exit, Layer, Option,
|
|
7
|
+
import { Cause, Effect, Exit, Layer, Option, Stream } from "effect";
|
|
8
8
|
//#region src/language-model/LanguageModel.test.ts
|
|
9
|
+
const serviceFromStream = (streamTurn) => ({
|
|
10
|
+
streamTurn,
|
|
11
|
+
turn: turnFromStream(streamTurn)
|
|
12
|
+
});
|
|
9
13
|
const oneTextTurn = (text) => ({
|
|
10
14
|
items: [assistantText(text)],
|
|
11
15
|
usage: {
|
|
@@ -24,7 +28,7 @@ describe("LanguageModel.turn", () => {
|
|
|
24
28
|
globalExpect(await Effect.runPromise(program.pipe(Effect.provide(layer([expected]))))).toEqual(expected);
|
|
25
29
|
});
|
|
26
30
|
it("fails with IncompleteTurn when the stream ends without TurnComplete", async () => {
|
|
27
|
-
const broken = Layer.succeed(LanguageModel,
|
|
31
|
+
const broken = Layer.succeed(LanguageModel, serviceFromStream(() => Stream.fromIterable([TurnEvent.TextDelta({ text: "partial" })])));
|
|
28
32
|
const program = turn({
|
|
29
33
|
history: [userText("hi")],
|
|
30
34
|
model: "mock"
|
|
@@ -42,7 +46,7 @@ describe("LanguageModel.turn", () => {
|
|
|
42
46
|
provider: "mock",
|
|
43
47
|
raw: null
|
|
44
48
|
});
|
|
45
|
-
const failing = Layer.succeed(LanguageModel,
|
|
49
|
+
const failing = Layer.succeed(LanguageModel, serviceFromStream(() => Stream.fail(rateLimited)));
|
|
46
50
|
const program = turn({
|
|
47
51
|
history: [],
|
|
48
52
|
model: "mock"
|
|
@@ -54,7 +58,7 @@ describe("LanguageModel.turn", () => {
|
|
|
54
58
|
it("returns the LAST TurnComplete when the stream contains multiple (defensive)", async () => {
|
|
55
59
|
const first = oneTextTurn("first");
|
|
56
60
|
const second = oneTextTurn("second");
|
|
57
|
-
const weird = Layer.succeed(LanguageModel,
|
|
61
|
+
const weird = Layer.succeed(LanguageModel, serviceFromStream(() => Stream.fromIterable([TurnEvent.TurnComplete({ turn: first }), TurnEvent.TurnComplete({ turn: second })])));
|
|
58
62
|
const program = turn({
|
|
59
63
|
history: [],
|
|
60
64
|
model: "mock"
|
|
@@ -62,81 +66,6 @@ describe("LanguageModel.turn", () => {
|
|
|
62
66
|
globalExpect(await Effect.runPromise(program.pipe(Effect.provide(weird)))).toEqual(second);
|
|
63
67
|
});
|
|
64
68
|
});
|
|
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
69
|
//#endregion
|
|
141
70
|
export {};
|
|
142
71
|
|
|
@@ -1 +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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"}
|
|
1
|
+
{"version":3,"file":"LanguageModel.test.mjs","names":["Items.assistantText","Items.userText","MockProvider.layer","AiError.IncompleteTurn","AiError.RateLimited"],"sources":["../../src/language-model/LanguageModel.test.ts"],"sourcesContent":["import { Cause, Effect, Exit, Layer, Option, 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, type LanguageModelService, turn, turnFromStream } from \"./LanguageModel.js\"\n\nconst serviceFromStream = (\n streamTurn: LanguageModelService[\"streamTurn\"],\n): LanguageModelService => ({ streamTurn, turn: turnFromStream(streamTurn) })\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(\n LanguageModel,\n serviceFromStream(() =>\n Stream.fromIterable<TurnEvent>([TurnEvent.TextDelta({ text: \"partial\" })]),\n ),\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(\n LanguageModel,\n serviceFromStream(() => 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(\n LanguageModel,\n serviceFromStream(() =>\n Stream.fromIterable<TurnEvent>([\n TurnEvent.TurnComplete({ turn: first }),\n TurnEvent.TurnComplete({ turn: second }),\n ]),\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"],"mappings":";;;;;;;;AAOA,MAAM,qBACJ,gBAC0B;CAAE;CAAY,MAAM,eAAe,WAAW;CAAE;AAG5E,MAAM,eAAe,UAAwB;CAC3C,OAAO,CAACA,cAAoB,KAAK,CAAC;CAClC,OAAO;EAAE,cAAc;EAAG,eAAe;EAAG;CAC5C,aAAa;CACd;AAED,SAAS,4BAA4B;AACnC,IAAG,mEAAmE,YAAY;EAChF,MAAM,WAAW,YAAY,cAAc;EAC3C,MAAM,UAAU,KAAK;GAAE,SAAS,CAACC,SAAe,KAAK,CAAC;GAAE,OAAO;GAAQ,CAAC;AAMxE,eAAO,MAJc,OAAO,WAC1B,QAAQ,KAAK,OAAO,QAAQC,MAAmB,CAAC,SAAS,CAAC,CAAC,CAAC,CAC7D,CAEa,CAAC,QAAQ,SAAS;GAChC;AAEF,IAAG,uEAAuE,YAAY;EAEpF,MAAM,SAAS,MAAM,QACnB,eACA,wBACE,OAAO,aAAwB,CAAC,UAAU,UAAU,EAAE,MAAM,WAAW,CAAC,CAAC,CAAC,CAC3E,CACF;EAED,MAAM,UAAU,KAAK;GAAE,SAAS,CAACD,SAAe,KAAK,CAAC;GAAE,OAAO;GAAQ,CAAC;EACxE,MAAM,OAAO,MAAM,OAAO,WAAW,OAAO,KAAK,QAAQ,KAAK,OAAO,QAAQ,OAAO,CAAC,CAAC,CAAC;AAEvF,eAAO,KAAK,UAAU,KAAK,CAAC,CAAC,KAAK,KAAK;AACvC,MAAI,KAAK,UAAU,KAAK,EAAE;GACxB,MAAM,UAAU,MAAM,gBAAgB,KAAK,MAAM;AACjD,gBAAO,OAAO,OAAO,QAAQ,CAAC,CAAC,KAAK,KAAK;AACzC,OAAI,OAAO,OAAO,QAAQ,CACxB,cAAO,QAAQ,MAAM,CAAC,eAAeE,eAAuB;;GAGhE;AAEF,IAAG,8CAA8C,YAAY;EAC3D,MAAM,cAAc,IAAIC,YAAoB;GAAE,UAAU;GAAQ,KAAK;GAAM,CAAC;EAC5E,MAAM,UAAU,MAAM,QACpB,eACA,wBAAwB,OAAO,KAAsB,YAAY,CAAC,CACnE;EAED,MAAM,UAAU,KAAK;GAAE,SAAS,EAAE;GAAE,OAAO;GAAQ,CAAC;EACpD,MAAM,OAAO,MAAM,OAAO,WAAW,OAAO,KAAK,QAAQ,KAAK,OAAO,QAAQ,QAAQ,CAAC,CAAC,CAAC;AAExF,eAAO,KAAK,UAAU,KAAK,CAAC,CAAC,KAAK,KAAK;AACvC,MAAI,KAAK,UAAU,KAAK,CACtB,cAAO,MAAM,gBAAgB,KAAK,MAAM,CAAC,CAAC,QAAQ,OAAO,KAAK,YAAY,CAAC;GAE7E;AAEF,IAAG,+EAA+E,YAAY;EAG5F,MAAM,QAAQ,YAAY,QAAQ;EAClC,MAAM,SAAS,YAAY,SAAS;EACpC,MAAM,QAAQ,MAAM,QAClB,eACA,wBACE,OAAO,aAAwB,CAC7B,UAAU,aAAa,EAAE,MAAM,OAAO,CAAC,EACvC,UAAU,aAAa,EAAE,MAAM,QAAQ,CAAC,CACzC,CAAC,CACH,CACF;EAED,MAAM,UAAU,KAAK;GAAE,SAAS,EAAE;GAAE,OAAO;GAAQ,CAAC;AAGpD,eAAO,MAFc,OAAO,WAAW,QAAQ,KAAK,OAAO,QAAQ,MAAM,CAAC,CAAC,CAE7D,CAAC,QAAQ,OAAO;GAC9B;EACF"}
|
package/dist/loop/Loop.d.mts
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
import { l as IncompleteTurn } from "../AiError-CAX_48RU.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { Turn, TurnEvent } from "../domain/Turn.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 {
|
|
7
|
+
export { Step, loop, loopOver, loopWithState, next, onTurnComplete, stop, 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
12
|
* new state. `Stop` ends the loop entirely with no carried state.
|
|
13
|
-
* `StopWith` also ends the loop but carries a final state that `
|
|
13
|
+
* `StopWith` also ends the loop but carries a final state that `loopOver`
|
|
14
14
|
* will thread to the next input and `loopWithState` will write to its
|
|
15
15
|
* `SubscriptionRef` before the loop ends. Plain `loop` has no next
|
|
16
16
|
* iteration to apply it to and treats `StopWith` like `Stop`.
|
|
17
17
|
*
|
|
18
|
-
* `Stop` is intentionally `{}` so the bare `
|
|
19
|
-
*
|
|
18
|
+
* `Stop` is intentionally `{}` so the bare `stop()` helper doesn't
|
|
19
|
+
* constrain `S` from a body's stream type — every body has a `Stop`
|
|
20
20
|
* variant in its union, and forcing `S` to flow through it would break
|
|
21
|
-
* inference whenever the body never uses `next` / `
|
|
21
|
+
* inference whenever the body never uses `next` / `stop(state)`.
|
|
22
22
|
*/
|
|
23
|
-
type
|
|
23
|
+
type Step<A, S> = Data.TaggedEnum<{
|
|
24
24
|
Value: {
|
|
25
25
|
readonly value: A;
|
|
26
26
|
};
|
|
@@ -33,80 +33,27 @@ type Event<A, S> = Data.TaggedEnum<{
|
|
|
33
33
|
};
|
|
34
34
|
}>;
|
|
35
35
|
/** Wrap a value so it flows through the loop to downstream consumers. */
|
|
36
|
-
declare const value: <A>(a: A) =>
|
|
37
|
-
/** End the current iteration and continue with a new state. */
|
|
38
|
-
declare const next: <S>(state: S) => Event<never, S>;
|
|
36
|
+
declare const value: <A>(a: A) => Step<A, never>;
|
|
39
37
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
38
|
+
* End the current iteration and continue with a new state. Emits a single
|
|
39
|
+
* terminal `Next` step as a one-element stream, so it slots directly into a
|
|
40
|
+
* loop body or after a run of values: `values.pipe(Stream.concat(next(s)))`.
|
|
42
41
|
*/
|
|
43
|
-
declare const
|
|
42
|
+
declare const next: <S>(state: S) => Stream.Stream<Step<never, S>>;
|
|
44
43
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
44
|
+
* End the loop. Called with no argument it emits a bare `Stop`; called with
|
|
45
|
+
* a state it emits `StopWith(state)` — `loopOver` threads that final state
|
|
46
|
+
* to the next input and `loopWithState` writes it to its `SubscriptionRef`
|
|
47
|
+
* before the loop ends. Either way it returns a single-element stream, so
|
|
48
|
+
* return it directly from a loop body.
|
|
50
49
|
*/
|
|
51
|
-
declare const
|
|
50
|
+
declare const stop: <S = never>(state?: S) => Stream.Stream<Step<never, S>>;
|
|
52
51
|
/**
|
|
53
|
-
*
|
|
54
|
-
* there's nothing else to emit; equivalent to `stopAfter(Stream.empty)` but
|
|
55
|
-
* named for the common case.
|
|
56
|
-
*/
|
|
57
|
-
declare const stop: Stream.Stream<Event<never, never>>;
|
|
58
|
-
/**
|
|
59
|
-
* Pipe a raw `Stream<A>` into the loop's emit shape, then terminate the
|
|
60
|
-
* iteration with `next(state)`. Common shape for "stream this turn's
|
|
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.
|
|
65
|
-
*/
|
|
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
|
-
};
|
|
70
|
-
/**
|
|
71
|
-
* Pipe a raw `Stream<A>` into the loop's emit shape, then terminate the
|
|
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)`.
|
|
75
|
-
*/
|
|
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
|
-
};
|
|
89
|
-
/**
|
|
90
|
-
* General `nextAfter` variant: drain `stream` to the consumer, fold elements
|
|
91
|
-
* into an accumulator, and at end-of-stream emit one `next(build(finalAcc))`.
|
|
92
|
-
*
|
|
93
|
-
* Subsumes `nextAfter` when state is constant (`reduce: (s, _) => s`,
|
|
94
|
-
* `build: (s) => s`). Used by `Toolkit.continueWith` to collect tool
|
|
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.
|
|
99
|
-
*/
|
|
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
|
-
};
|
|
104
|
-
/**
|
|
105
|
-
* Lift a provider's `Stream<TurnEvent>` into a loop body's `Stream<Event<TurnEvent | A, S>>`.
|
|
52
|
+
* Lift a provider's `Stream<TurnEvent>` into a loop body's `Stream<Step<TurnEvent | A, S>>`.
|
|
106
53
|
* Each delta passes through as `value(delta)` (including the terminal
|
|
107
54
|
* `TurnComplete`, so the consumer sees turn boundaries naturally). Once
|
|
108
55
|
* the terminal arrives, `then(turn)` runs and its returned stream of loop
|
|
109
|
-
*
|
|
56
|
+
* steps (typically tool outputs followed by `next(state)` or `stop()`) is
|
|
110
57
|
* concatenated.
|
|
111
58
|
*
|
|
112
59
|
* Pre-pipe transforms (`Stream.tap` / `Stream.map` / `Stream.filter`) on
|
|
@@ -120,14 +67,14 @@ declare const nextAfterFold: {
|
|
|
120
67
|
* `deltas.pipe(onTurnComplete(then))` both work.
|
|
121
68
|
*/
|
|
122
69
|
declare const onTurnComplete: {
|
|
123
|
-
<S, A, E2 = never, R2 = never>(then: (turn: Turn) => Effect.Effect<Stream.Stream<
|
|
124
|
-
<S, A, E, R, E2 = never, R2 = never>(deltas: Stream.Stream<TurnEvent, E, R>, then: (turn: Turn) => Effect.Effect<Stream.Stream<
|
|
70
|
+
<S, A, E2 = never, R2 = never>(then: (turn: Turn) => Effect.Effect<Stream.Stream<Step<A, S>, E2, R2>, E2, R2>): <E, R>(deltas: Stream.Stream<TurnEvent, E, R>) => Stream.Stream<Step<TurnEvent | A, S>, E | E2 | IncompleteTurn, R | R2>;
|
|
71
|
+
<S, A, E, R, E2 = never, R2 = never>(deltas: Stream.Stream<TurnEvent, E, R>, then: (turn: Turn) => Effect.Effect<Stream.Stream<Step<A, S>, E2, R2>, E2, R2>): Stream.Stream<Step<TurnEvent | A, S>, E | E2 | IncompleteTurn, R | R2>;
|
|
125
72
|
};
|
|
126
|
-
type LoopBody<S, A, E, R> = (state: S) => Stream.Stream<
|
|
73
|
+
type LoopBody<S, A, E, R> = (state: S) => Stream.Stream<Step<A, S>, E, R> | Effect.Effect<Stream.Stream<Step<A, S>, E, R>, E, R>;
|
|
127
74
|
/**
|
|
128
75
|
* Drive a state-threaded loop body. Each iteration runs `body(state)` to get
|
|
129
|
-
* a `Stream<
|
|
130
|
-
* a new state, `stop` ends the loop. See the file header for the full
|
|
76
|
+
* a `Stream<Step<A, S>>`; values flow downstream, `next(s)` continues with
|
|
77
|
+
* a new state, `stop()` ends the loop. See the file header for the full
|
|
131
78
|
* pull-based execution model.
|
|
132
79
|
*
|
|
133
80
|
* Dual: data-first `loop(initial, body)` and data-last `loop(body)(initial)`
|
|
@@ -137,40 +84,40 @@ declare const loop: {
|
|
|
137
84
|
<S, A, E, R>(body: LoopBody<S, A, E, R>): (initial: S) => Stream.Stream<A, E, R>;
|
|
138
85
|
<S, A, E, R>(initial: S, body: LoopBody<S, A, E, R>): Stream.Stream<A, E, R>;
|
|
139
86
|
};
|
|
140
|
-
type
|
|
87
|
+
type LoopOverBody<S, I, A, E, R> = (state: S, input: I) => Stream.Stream<Step<A, S>, E, R> | Effect.Effect<Stream.Stream<Step<A, S>, E, R>, E, R>;
|
|
141
88
|
/**
|
|
142
89
|
* Input-driven sibling of `loop`. For each item pulled from the input
|
|
143
90
|
* stream, runs an inner seed-driven `loop` whose body is
|
|
144
91
|
* `(s) => body(s, item)`. State is threaded across input items.
|
|
145
92
|
*
|
|
146
|
-
* **Per-input semantics — the body emits standard `
|
|
93
|
+
* **Per-input semantics — the body emits standard `Step<A, S>`:**
|
|
147
94
|
* - `value(a)`: emit `a` downstream
|
|
148
95
|
* - `next(s)`: re-run the body with the SAME input and new state `s`
|
|
149
96
|
* (multi-turn within one input — e.g. multiple model turns + tool
|
|
150
97
|
* calls for one document)
|
|
151
|
-
* - `stop`: end this input's inner loop, advance to the next input
|
|
98
|
+
* - `stop()`: end this input's inner loop, advance to the next input
|
|
152
99
|
* (state preserved)
|
|
153
|
-
* - body stream ending without a decision: same as `stop` (advance)
|
|
100
|
+
* - body stream ending without a decision: same as `stop()` (advance)
|
|
154
101
|
*
|
|
155
102
|
* **Outer termination:** the input stream ending. To halt programmatically
|
|
156
103
|
* from within, end the input stream upstream (`Stream.takeWhile`, a
|
|
157
|
-
* `SubscriptionRef` gate, etc.). Reserving `stop` for per-item
|
|
104
|
+
* `SubscriptionRef` gate, etc.). Reserving `stop()` for per-item
|
|
158
105
|
* advancement is what makes the common "stream of documents, multi-turn
|
|
159
106
|
* conversation per document" shape readable.
|
|
160
107
|
*
|
|
161
|
-
* Dual: data-first `
|
|
162
|
-
* `input.pipe(
|
|
108
|
+
* Dual: data-first `loopOver(input, initial, body)` and data-last
|
|
109
|
+
* `input.pipe(loopOver(initial, body))` both work.
|
|
163
110
|
*/
|
|
164
|
-
declare const
|
|
165
|
-
<S, I, A, E, R>(initial: S, body:
|
|
166
|
-
<S, I, A, E, R, EI, RI>(input: Stream.Stream<I, EI, RI>, initial: S, body:
|
|
111
|
+
declare const loopOver: {
|
|
112
|
+
<S, I, A, E, R>(initial: S, body: LoopOverBody<S, I, A, E, R>): <EI, RI>(input: Stream.Stream<I, EI, RI>) => Stream.Stream<A, E | EI, R | RI>;
|
|
113
|
+
<S, I, A, E, R, EI, RI>(input: Stream.Stream<I, EI, RI>, initial: S, body: LoopOverBody<S, I, A, E, R>): Stream.Stream<A, E | EI, R | RI>;
|
|
167
114
|
};
|
|
168
115
|
/**
|
|
169
116
|
* Like `loop`, but exposes the current loop state as a `SubscriptionRef`
|
|
170
117
|
* alongside the value stream.
|
|
171
118
|
*
|
|
172
119
|
* Allocates one `SubscriptionRef<S>` seeded with `initial`, then runs the
|
|
173
|
-
* loop with a wrapped body that taps every `Next(s)`
|
|
120
|
+
* loop with a wrapped body that taps every `Next(s)` step into the ref
|
|
174
121
|
* before forwarding it. The caller decides how to consume both channels:
|
|
175
122
|
*
|
|
176
123
|
* - **Final state**: drain the stream, then `SubscriptionRef.get(state)`
|
|
@@ -190,5 +137,5 @@ declare const loopWithState: <S, A, E, R>(initial: S, body: LoopBody<S, A, E, R>
|
|
|
190
137
|
readonly state: SubscriptionRef.SubscriptionRef<S>;
|
|
191
138
|
}>;
|
|
192
139
|
//#endregion
|
|
193
|
-
export {
|
|
140
|
+
export { Step, loop, loopOver, loopWithState, next, onTurnComplete, stop, Loop_d_exports as t, value };
|
|
194
141
|
//# 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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"}
|