@effect-uai/core 0.4.0 → 0.5.1
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-BH8xUkoR.d.mts} +3 -3
- package/dist/{Items-Hg5AsYxl.d.mts.map → Items-BH8xUkoR.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-87ViKCCO.d.mts} +20 -4
- package/dist/Tool-87ViKCCO.d.mts.map +1 -0
- package/dist/Turn-0CwCAyVe.d.mts +388 -0
- package/dist/Turn-0CwCAyVe.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
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { d as Item } from "../Items-
|
|
2
|
-
import { r as Turn } from "../Turn-
|
|
1
|
+
import { d as Item } from "../Items-BH8xUkoR.mjs";
|
|
2
|
+
import { r as Turn } from "../Turn-0CwCAyVe.mjs";
|
|
3
3
|
import { LanguageModel, LanguageModelService } from "../language-model/LanguageModel.mjs";
|
|
4
4
|
import { Duration, Effect, Layer } from "effect";
|
|
5
5
|
|
|
@@ -12,37 +12,42 @@ type MockOptions = {
|
|
|
12
12
|
*/
|
|
13
13
|
readonly deltaInterval?: Duration.Input;
|
|
14
14
|
};
|
|
15
|
+
type Call = {
|
|
16
|
+
readonly history: ReadonlyArray<Item>;
|
|
17
|
+
readonly turn: Turn;
|
|
18
|
+
};
|
|
15
19
|
/**
|
|
16
20
|
* A scripted mock provider. Pre-canned `Turn` outputs are returned in order,
|
|
17
21
|
* one per call to `streamTurn`. Each scripted turn is split into synthetic
|
|
18
|
-
* deltas (text →
|
|
22
|
+
* deltas (text → ToolCallStart → ToolCallArgsDelta → ... → TurnComplete)
|
|
19
23
|
* so streaming consumers can see realistic delta shapes.
|
|
20
24
|
*/
|
|
21
25
|
type MockRecorder = {
|
|
22
|
-
readonly calls: ReadonlyArray<
|
|
23
|
-
readonly history: ReadonlyArray<Item>;
|
|
24
|
-
readonly turn: Turn;
|
|
25
|
-
}>;
|
|
26
|
+
readonly calls: ReadonlyArray<Call>;
|
|
26
27
|
};
|
|
27
|
-
declare const layer: (scriptedTurns: ReadonlyArray<Turn>, options?: MockOptions) => Layer.Layer<LanguageModel>;
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* mid-stream via `Effect.provideService` instead of providing one model
|
|
32
|
-
* for the whole program via `Layer`.
|
|
29
|
+
* Layer that registers a `MockProvider` against the `LanguageModel` tag.
|
|
30
|
+
* Calls beyond the scripted turn count fail with `InvalidRequest`.
|
|
33
31
|
*/
|
|
34
|
-
declare const
|
|
35
|
-
readonly service: LanguageModelService;
|
|
36
|
-
readonly recorder: Effect.Effect<MockRecorder>;
|
|
37
|
-
};
|
|
32
|
+
declare const layer: (scriptedTurns: ReadonlyArray<Turn>, options?: MockOptions) => Layer.Layer<LanguageModel>;
|
|
38
33
|
/**
|
|
39
|
-
*
|
|
34
|
+
* Like `layer`, but also exposes a recorder that captures every call
|
|
40
35
|
* (history + returned turn).
|
|
41
36
|
*/
|
|
42
37
|
declare const layerWithRecorder: (scriptedTurns: ReadonlyArray<Turn>, options?: MockOptions) => {
|
|
43
38
|
readonly layer: Layer.Layer<LanguageModel>;
|
|
44
39
|
readonly recorder: Effect.Effect<MockRecorder>;
|
|
45
40
|
};
|
|
41
|
+
/**
|
|
42
|
+
* Build the `LanguageModelService` value directly (no Layer), plus a
|
|
43
|
+
* recorder. Use this when you want to swap models mid-program via
|
|
44
|
+
* `Effect.provideService` instead of providing one model for the whole
|
|
45
|
+
* program via `Layer`.
|
|
46
|
+
*/
|
|
47
|
+
declare const make: (scriptedTurns: ReadonlyArray<Turn>, options?: MockOptions) => {
|
|
48
|
+
readonly service: LanguageModelService;
|
|
49
|
+
readonly recorder: Effect.Effect<MockRecorder>;
|
|
50
|
+
};
|
|
46
51
|
//#endregion
|
|
47
|
-
export { MockOptions, MockRecorder, layer, layerWithRecorder, make };
|
|
52
|
+
export { Call, MockOptions, MockRecorder, layer, layerWithRecorder, make };
|
|
48
53
|
//# sourceMappingURL=MockProvider.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MockProvider.d.mts","names":[],"sources":["../../src/testing/MockProvider.ts"],"mappings":";;;;;;KAMY,WAAA;;AAAZ;;;;WAMW,aAAA,GAAgB,QAAA,CAAS,KAAA;AAAA
|
|
1
|
+
{"version":3,"file":"MockProvider.d.mts","names":[],"sources":["../../src/testing/MockProvider.ts"],"mappings":";;;;;;KAMY,WAAA;;AAAZ;;;;WAMW,aAAA,GAAgB,QAAA,CAAS,KAAA;AAAA;AAAA,KAGxB,IAAA;EAAA,SACD,OAAA,EAAS,aAAA,CAAc,IAAA;EAAA,SACvB,IAAA,EAAM,IAAA;AAAA;;;;;;;KASL,YAAA;EAAA,SACD,KAAA,EAAO,aAAA,CAAc,IAAA;AAAA;;;;;cAqGnB,KAAA,GACX,aAAA,EAAe,aAAA,CAAc,IAAA,GAC7B,OAAA,GAAU,WAAA,KACT,KAAA,CAAM,KAAA,CAAM,aAAA;AAzGf;;;;AAAA,cAqHa,iBAAA,GACX,aAAA,EAAe,aAAA,CAAc,IAAA,GAC7B,OAAA,GAAU,WAAA;EAAA,SAED,KAAA,EAAO,KAAA,CAAM,KAAA,CAAM,aAAA;EAAA,SACnB,QAAA,EAAU,MAAA,CAAO,MAAA,CAAO,YAAA;AAAA;;AApBnC;;;;;cAwCa,IAAA,GACX,aAAA,EAAe,aAAA,CAAc,IAAA,GAC7B,OAAA,GAAU,WAAA;EAAA,SAED,OAAA,EAAS,oBAAA;EAAA,SACT,QAAA,EAAU,MAAA,CAAO,MAAA,CAAO,YAAA;AAAA"}
|
|
@@ -1,92 +1,76 @@
|
|
|
1
1
|
import { InvalidRequest } from "../domain/AiError.mjs";
|
|
2
|
+
import { isOutputText } from "../domain/Items.mjs";
|
|
3
|
+
import { TurnEvent } from "../domain/Turn.mjs";
|
|
2
4
|
import { LanguageModel } from "../language-model/LanguageModel.mjs";
|
|
3
|
-
import { Effect, Layer, Ref, Schedule, Stream } from "effect";
|
|
5
|
+
import { Array, Effect, Layer, Match, Option, Ref, Schedule, Stream } from "effect";
|
|
4
6
|
//#region src/testing/MockProvider.ts
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
});
|
|
18
|
-
deltas.push({
|
|
19
|
-
type: "tool_call_args_delta",
|
|
20
|
-
call_id: item.call_id,
|
|
21
|
-
delta: item.arguments
|
|
22
|
-
});
|
|
23
|
-
} else if (item.type === "reasoning" && item.summary !== void 0) deltas.push({
|
|
24
|
-
type: "reasoning_delta",
|
|
25
|
-
text: item.summary,
|
|
7
|
+
const itemToDeltas = Match.type().pipe(Match.discriminators("type")({
|
|
8
|
+
message: (m) => m.role === "assistant" ? m.content.filter(isOutputText).map((b) => TurnEvent.TextDelta({ text: b.text })) : [],
|
|
9
|
+
function_call: (fc) => [TurnEvent.ToolCallStart({
|
|
10
|
+
call_id: fc.call_id,
|
|
11
|
+
name: fc.name
|
|
12
|
+
}), TurnEvent.ToolCallArgsDelta({
|
|
13
|
+
call_id: fc.call_id,
|
|
14
|
+
delta: fc.arguments
|
|
15
|
+
})],
|
|
16
|
+
function_call_output: () => [],
|
|
17
|
+
reasoning: (r) => r.summary !== void 0 ? [TurnEvent.ReasoningDelta({
|
|
18
|
+
text: r.summary,
|
|
26
19
|
kind: "summary"
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
turn
|
|
31
|
-
});
|
|
32
|
-
return deltas;
|
|
33
|
-
};
|
|
20
|
+
})] : []
|
|
21
|
+
}), Match.exhaustive);
|
|
22
|
+
const turnToDeltas = (turn) => [...turn.items.flatMap(itemToDeltas), TurnEvent.TurnComplete({ turn })];
|
|
34
23
|
const pacedDeltas = (turn, options) => {
|
|
35
24
|
const base = Stream.fromIterable(turnToDeltas(turn));
|
|
36
25
|
return options?.deltaInterval === void 0 ? base : base.pipe(Stream.schedule(Schedule.spaced(options.deltaInterval)));
|
|
37
26
|
};
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const i = yield* Ref.getAndUpdate(cursor, (n) => n + 1);
|
|
42
|
-
if (i >= scriptedTurns.length) return Stream.fail(new InvalidRequest({
|
|
43
|
-
provider: "mock",
|
|
44
|
-
raw: `MockProvider exhausted: ${scriptedTurns.length} turns scripted, but call ${i + 1} was made`
|
|
45
|
-
}));
|
|
46
|
-
const turn = scriptedTurns[i];
|
|
47
|
-
if (recordCall !== void 0) yield* recordCall(request.history, turn);
|
|
48
|
-
return pacedDeltas(turn, options);
|
|
49
|
-
})) });
|
|
27
|
+
const exhausted = (n, attempt) => new InvalidRequest({
|
|
28
|
+
provider: "mock",
|
|
29
|
+
raw: `MockProvider exhausted: ${n} turns scripted, but call ${attempt} was made`
|
|
50
30
|
});
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
31
|
+
const noRecord = (_) => Effect.void;
|
|
32
|
+
const buildService = (scriptedTurns, options, cursor, record) => ({ streamTurn: (request) => Stream.unwrap(Ref.getAndUpdate(cursor, (n) => n + 1).pipe(Effect.flatMap((i) => Option.match(Array.get(scriptedTurns, i), {
|
|
33
|
+
onNone: () => Effect.succeed(Stream.fail(exhausted(scriptedTurns.length, i + 1))),
|
|
34
|
+
onSome: (turn) => record({
|
|
35
|
+
history: request.history,
|
|
36
|
+
turn
|
|
37
|
+
}).pipe(Effect.as(pacedDeltas(turn, options)))
|
|
38
|
+
})))) });
|
|
39
|
+
const makeRecorderUnsafe = () => {
|
|
40
|
+
const ref = Ref.makeUnsafe([]);
|
|
61
41
|
return {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (i >= scriptedTurns.length) return Stream.fail(new InvalidRequest({
|
|
65
|
-
provider: "mock",
|
|
66
|
-
raw: `MockProvider exhausted: ${scriptedTurns.length} turns scripted, but call ${i + 1} was made`
|
|
67
|
-
}));
|
|
68
|
-
const turn = scriptedTurns[i];
|
|
69
|
-
yield* Ref.update(callsRef, (xs) => [...xs, {
|
|
70
|
-
history: request.history,
|
|
71
|
-
turn
|
|
72
|
-
}]);
|
|
73
|
-
return pacedDeltas(turn, options);
|
|
74
|
-
})) },
|
|
75
|
-
recorder: Ref.get(callsRef).pipe(Effect.map((calls) => ({ calls })))
|
|
42
|
+
record: (call) => Ref.update(ref, Array.append(call)),
|
|
43
|
+
recorder: Ref.get(ref).pipe(Effect.map((calls) => ({ calls })))
|
|
76
44
|
};
|
|
77
45
|
};
|
|
78
46
|
/**
|
|
79
|
-
*
|
|
47
|
+
* Layer that registers a `MockProvider` against the `LanguageModel` tag.
|
|
48
|
+
* Calls beyond the scripted turn count fail with `InvalidRequest`.
|
|
49
|
+
*/
|
|
50
|
+
const layer = (scriptedTurns, options) => Layer.effect(LanguageModel, Ref.make(0).pipe(Effect.map((cursor) => buildService(scriptedTurns, options, cursor, noRecord))));
|
|
51
|
+
/**
|
|
52
|
+
* Like `layer`, but also exposes a recorder that captures every call
|
|
80
53
|
* (history + returned turn).
|
|
81
54
|
*/
|
|
82
55
|
const layerWithRecorder = (scriptedTurns, options) => {
|
|
83
|
-
const
|
|
56
|
+
const { record, recorder } = makeRecorderUnsafe();
|
|
57
|
+
return {
|
|
58
|
+
layer: Layer.effect(LanguageModel, Ref.make(0).pipe(Effect.map((cursor) => buildService(scriptedTurns, options, cursor, record)))),
|
|
59
|
+
recorder
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Build the `LanguageModelService` value directly (no Layer), plus a
|
|
64
|
+
* recorder. Use this when you want to swap models mid-program via
|
|
65
|
+
* `Effect.provideService` instead of providing one model for the whole
|
|
66
|
+
* program via `Layer`.
|
|
67
|
+
*/
|
|
68
|
+
const make = (scriptedTurns, options) => {
|
|
69
|
+
const cursor = Ref.makeUnsafe(0);
|
|
70
|
+
const { record, recorder } = makeRecorderUnsafe();
|
|
84
71
|
return {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
turn
|
|
88
|
-
}]))),
|
|
89
|
-
recorder: Ref.get(callsRef).pipe(Effect.map((calls) => ({ calls })))
|
|
72
|
+
service: buildService(scriptedTurns, options, cursor, record),
|
|
73
|
+
recorder
|
|
90
74
|
};
|
|
91
75
|
};
|
|
92
76
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MockProvider.mjs","names":["AiError.InvalidRequest"],"sources":["../../src/testing/MockProvider.ts"],"sourcesContent":["import { Duration, Effect, Layer, Ref, Schedule, Stream } from \"effect\"\nimport * as AiError from \"../domain/AiError.js\"\nimport type
|
|
1
|
+
{"version":3,"file":"MockProvider.mjs","names":["AiError.InvalidRequest","Arr"],"sources":["../../src/testing/MockProvider.ts"],"sourcesContent":["import { Array as Arr, Duration, Effect, Layer, Match, Option, Ref, Schedule, Stream } from \"effect\"\nimport * as AiError from \"../domain/AiError.js\"\nimport { type Item, isOutputText } from \"../domain/Items.js\"\nimport { LanguageModel, type LanguageModelService } from \"../language-model/LanguageModel.js\"\nimport { type Turn, TurnEvent } from \"../domain/Turn.js\"\n\nexport type MockOptions = {\n /**\n * If set, deltas of each scripted turn are spaced by this duration via\n * `Schedule.spaced`. Combine with `TestClock.adjust` for deterministic\n * timing in tests.\n */\n readonly deltaInterval?: Duration.Input\n}\n\nexport type Call = {\n readonly history: ReadonlyArray<Item>\n readonly turn: Turn\n}\n\n/**\n * A scripted mock provider. Pre-canned `Turn` outputs are returned in order,\n * one per call to `streamTurn`. Each scripted turn is split into synthetic\n * deltas (text → ToolCallStart → ToolCallArgsDelta → ... → TurnComplete)\n * so streaming consumers can see realistic delta shapes.\n */\nexport type MockRecorder = {\n readonly calls: ReadonlyArray<Call>\n}\n\n// ---------------------------------------------------------------------------\n// Pure projection: Turn → ReadonlyArray<TurnEvent>\n// ---------------------------------------------------------------------------\n\nconst itemToDeltas: (item: Item) => ReadonlyArray<TurnEvent> = Match.type<Item>().pipe(\n Match.discriminators(\"type\")({\n message: (m): ReadonlyArray<TurnEvent> =>\n m.role === \"assistant\"\n ? m.content.filter(isOutputText).map((b) => TurnEvent.TextDelta({ text: b.text }))\n : [],\n function_call: (fc) => [\n TurnEvent.ToolCallStart({ call_id: fc.call_id, name: fc.name }),\n TurnEvent.ToolCallArgsDelta({ call_id: fc.call_id, delta: fc.arguments }),\n ],\n function_call_output: () => [],\n reasoning: (r) =>\n r.summary !== undefined\n ? [TurnEvent.ReasoningDelta({ text: r.summary, kind: \"summary\" as const })]\n : [],\n }),\n Match.exhaustive,\n)\n\nconst turnToDeltas = (turn: Turn): ReadonlyArray<TurnEvent> => [\n ...turn.items.flatMap(itemToDeltas),\n TurnEvent.TurnComplete({ turn }),\n]\n\nconst pacedDeltas = (turn: Turn, options?: MockOptions): Stream.Stream<TurnEvent> => {\n const base = Stream.fromIterable(turnToDeltas(turn))\n return options?.deltaInterval === undefined\n ? base\n : base.pipe(Stream.schedule(Schedule.spaced(options.deltaInterval)))\n}\n\n// ---------------------------------------------------------------------------\n// Canonical service factory. One implementation; sync/Layer/recorder\n// variants below are just different ways to wire the cursor + record hook.\n// ---------------------------------------------------------------------------\n\nconst exhausted = (n: number, attempt: number): AiError.AiError =>\n new AiError.InvalidRequest({\n provider: \"mock\",\n raw: `MockProvider exhausted: ${n} turns scripted, but call ${attempt} was made`,\n })\n\nconst noRecord = (_: Call): Effect.Effect<void> => Effect.void\n\nconst buildService = (\n scriptedTurns: ReadonlyArray<Turn>,\n options: MockOptions | undefined,\n cursor: Ref.Ref<number>,\n record: (call: Call) => Effect.Effect<void>,\n): LanguageModelService => ({\n streamTurn: (request) =>\n Stream.unwrap(\n Ref.getAndUpdate(cursor, (n) => n + 1).pipe(\n Effect.flatMap(\n (i): Effect.Effect<Stream.Stream<TurnEvent, AiError.AiError>> =>\n Option.match(Arr.get(scriptedTurns, i), {\n onNone: () => Effect.succeed(Stream.fail(exhausted(scriptedTurns.length, i + 1))),\n onSome: (turn) =>\n record({ history: request.history, turn }).pipe(\n Effect.as(pacedDeltas(turn, options)),\n ),\n }),\n ),\n ),\n ),\n})\n\n// ---------------------------------------------------------------------------\n// Recorder handle. Unsafe Ref is local: it backs both the `record` write\n// hook (called inside the service) and the `recorder` read effect (called\n// by the test). Both close over the same cell.\n// ---------------------------------------------------------------------------\n\ntype RecorderHandle = {\n readonly record: (call: Call) => Effect.Effect<void>\n readonly recorder: Effect.Effect<MockRecorder>\n}\n\nconst makeRecorderUnsafe = (): RecorderHandle => {\n const ref = Ref.makeUnsafe<ReadonlyArray<Call>>([])\n return {\n record: (call) => Ref.update(ref, Arr.append(call)),\n recorder: Ref.get(ref).pipe(Effect.map((calls): MockRecorder => ({ calls }))),\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Layer that registers a `MockProvider` against the `LanguageModel` tag.\n * Calls beyond the scripted turn count fail with `InvalidRequest`.\n */\nexport const layer = (\n scriptedTurns: ReadonlyArray<Turn>,\n options?: MockOptions,\n): Layer.Layer<LanguageModel> =>\n Layer.effect(\n LanguageModel,\n Ref.make(0).pipe(\n Effect.map((cursor) => buildService(scriptedTurns, options, cursor, noRecord)),\n ),\n )\n\n/**\n * Like `layer`, but also exposes a recorder that captures every call\n * (history + returned turn).\n */\nexport const layerWithRecorder = (\n scriptedTurns: ReadonlyArray<Turn>,\n options?: MockOptions,\n): {\n readonly layer: Layer.Layer<LanguageModel>\n readonly recorder: Effect.Effect<MockRecorder>\n} => {\n const { record, recorder } = makeRecorderUnsafe()\n return {\n layer: Layer.effect(\n LanguageModel,\n Ref.make(0).pipe(\n Effect.map((cursor) => buildService(scriptedTurns, options, cursor, record)),\n ),\n ),\n recorder,\n }\n}\n\n/**\n * Build the `LanguageModelService` value directly (no Layer), plus a\n * recorder. Use this when you want to swap models mid-program via\n * `Effect.provideService` instead of providing one model for the whole\n * program via `Layer`.\n */\nexport const make = (\n scriptedTurns: ReadonlyArray<Turn>,\n options?: MockOptions,\n): {\n readonly service: LanguageModelService\n readonly recorder: Effect.Effect<MockRecorder>\n} => {\n const cursor = Ref.makeUnsafe(0)\n const { record, recorder } = makeRecorderUnsafe()\n return {\n service: buildService(scriptedTurns, options, cursor, record),\n recorder,\n }\n}\n"],"mappings":";;;;;;AAkCA,MAAM,eAAyD,MAAM,MAAY,CAAC,KAChF,MAAM,eAAe,OAAO,CAAC;CAC3B,UAAU,MACR,EAAE,SAAS,cACP,EAAE,QAAQ,OAAO,aAAa,CAAC,KAAK,MAAM,UAAU,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,GAChF,EAAE;CACR,gBAAgB,OAAO,CACrB,UAAU,cAAc;EAAE,SAAS,GAAG;EAAS,MAAM,GAAG;EAAM,CAAC,EAC/D,UAAU,kBAAkB;EAAE,SAAS,GAAG;EAAS,OAAO,GAAG;EAAW,CAAC,CAC1E;CACD,4BAA4B,EAAE;CAC9B,YAAY,MACV,EAAE,YAAY,KAAA,IACV,CAAC,UAAU,eAAe;EAAE,MAAM,EAAE;EAAS,MAAM;EAAoB,CAAC,CAAC,GACzE,EAAE;CACT,CAAC,EACF,MAAM,WACP;AAED,MAAM,gBAAgB,SAAyC,CAC7D,GAAG,KAAK,MAAM,QAAQ,aAAa,EACnC,UAAU,aAAa,EAAE,MAAM,CAAC,CACjC;AAED,MAAM,eAAe,MAAY,YAAoD;CACnF,MAAM,OAAO,OAAO,aAAa,aAAa,KAAK,CAAC;AACpD,QAAO,SAAS,kBAAkB,KAAA,IAC9B,OACA,KAAK,KAAK,OAAO,SAAS,SAAS,OAAO,QAAQ,cAAc,CAAC,CAAC;;AAQxE,MAAM,aAAa,GAAW,YAC5B,IAAIA,eAAuB;CACzB,UAAU;CACV,KAAK,2BAA2B,EAAE,4BAA4B,QAAQ;CACvE,CAAC;AAEJ,MAAM,YAAY,MAAiC,OAAO;AAE1D,MAAM,gBACJ,eACA,SACA,QACA,YAC0B,EAC1B,aAAa,YACX,OAAO,OACL,IAAI,aAAa,SAAS,MAAM,IAAI,EAAE,CAAC,KACrC,OAAO,SACJ,MACC,OAAO,MAAMC,MAAI,IAAI,eAAe,EAAE,EAAE;CACtC,cAAc,OAAO,QAAQ,OAAO,KAAK,UAAU,cAAc,QAAQ,IAAI,EAAE,CAAC,CAAC;CACjF,SAAS,SACP,OAAO;EAAE,SAAS,QAAQ;EAAS;EAAM,CAAC,CAAC,KACzC,OAAO,GAAG,YAAY,MAAM,QAAQ,CAAC,CACtC;CACJ,CAAC,CACL,CACF,CACF,EACJ;AAaD,MAAM,2BAA2C;CAC/C,MAAM,MAAM,IAAI,WAAgC,EAAE,CAAC;AACnD,QAAO;EACL,SAAS,SAAS,IAAI,OAAO,KAAKA,MAAI,OAAO,KAAK,CAAC;EACnD,UAAU,IAAI,IAAI,IAAI,CAAC,KAAK,OAAO,KAAK,WAAyB,EAAE,OAAO,EAAE,CAAC;EAC9E;;;;;;AAWH,MAAa,SACX,eACA,YAEA,MAAM,OACJ,eACA,IAAI,KAAK,EAAE,CAAC,KACV,OAAO,KAAK,WAAW,aAAa,eAAe,SAAS,QAAQ,SAAS,CAAC,CAC/E,CACF;;;;;AAMH,MAAa,qBACX,eACA,YAIG;CACH,MAAM,EAAE,QAAQ,aAAa,oBAAoB;AACjD,QAAO;EACL,OAAO,MAAM,OACX,eACA,IAAI,KAAK,EAAE,CAAC,KACV,OAAO,KAAK,WAAW,aAAa,eAAe,SAAS,QAAQ,OAAO,CAAC,CAC7E,CACF;EACD;EACD;;;;;;;;AASH,MAAa,QACX,eACA,YAIG;CACH,MAAM,SAAS,IAAI,WAAW,EAAE;CAChC,MAAM,EAAE,QAAQ,aAAa,oBAAoB;AACjD,QAAO;EACL,SAAS,aAAa,eAAe,SAAS,QAAQ,OAAO;EAC7D;EACD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MockSpeechSynthesizer.d.mts","names":[],"sources":["../../src/testing/MockSpeechSynthesizer.ts"],"mappings":";;;;;KAWY,uBAAA;EAAA,SACD,eAAA,EAAiB,aAAA,CAAc,uBAAA;EAAA,SAC/B,oBAAA,EAAsB,aAAA,CAAc,uBAAA;EAAA,SACpC,wBAAA,EAA0B,aAAA,CAAc,6BAAA;AAAA;AAAA,KAGvC,qBAAA;EALgB,kEAOjB,KAAA,GAAQ,aAAA,CAAc,SAAA,GANA;EAAA,SAQtB,qBAAA,GAAwB,aAAA,CAAc,aAAA,CAAc,UAAA,IAP1B;EAAA,SAS1B,yBAAA,GAA4B,aAAA,CAAc,aAAA,CAAc,UAAA;AAAA;;;;;;
|
|
1
|
+
{"version":3,"file":"MockSpeechSynthesizer.d.mts","names":[],"sources":["../../src/testing/MockSpeechSynthesizer.ts"],"mappings":";;;;;KAWY,uBAAA;EAAA,SACD,eAAA,EAAiB,aAAA,CAAc,uBAAA;EAAA,SAC/B,oBAAA,EAAsB,aAAA,CAAc,uBAAA;EAAA,SACpC,wBAAA,EAA0B,aAAA,CAAc,6BAAA;AAAA;AAAA,KAGvC,qBAAA;EALgB,kEAOjB,KAAA,GAAQ,aAAA,CAAc,SAAA,GANA;EAAA,SAQtB,qBAAA,GAAwB,aAAA,CAAc,aAAA,CAAc,UAAA,IAP1B;EAAA,SAS1B,yBAAA,GAA4B,aAAA,CAAc,aAAA,CAAc,UAAA;AAAA;;;;;;cA8EtD,KAAA,GACX,MAAA,EAAQ,qBAAA;EAAA,SAEC,KAAA,EAAO,KAAA,CAAM,KAAA,CAAM,iBAAA,GAAoB,kBAAA;EAAA,SACvC,QAAA,EAAU,MAAA,CAAO,MAAA,CAAO,uBAAA;AAAA;;;AAxFnC;;;;cAuHa,uBAAA,GACX,MAAA,EAAQ,qBAAA;EAAA,SAEC,KAAA,EAAO,KAAA,CAAM,KAAA,CAAM,iBAAA;EAAA,SACnB,QAAA,EAAU,MAAA,CAAO,MAAA,CAAO,uBAAA;AAAA"}
|
|
@@ -11,10 +11,10 @@ const makeService = (script, record) => Effect.gen(function* () {
|
|
|
11
11
|
yield* record.synthesize(request);
|
|
12
12
|
const i = yield* Ref.getAndUpdate(bCursor, (n) => n + 1);
|
|
13
13
|
const scripted = script.blobs ?? [];
|
|
14
|
-
if (i >= scripted.length) return yield*
|
|
14
|
+
if (i >= scripted.length) return yield* new InvalidRequest({
|
|
15
15
|
provider: "mock",
|
|
16
16
|
raw: `MockSpeechSynthesizer exhausted: ${scripted.length} blobs scripted, but call ${i + 1} was made`
|
|
17
|
-
})
|
|
17
|
+
});
|
|
18
18
|
return scripted[i];
|
|
19
19
|
}),
|
|
20
20
|
streamSynthesis: (request) => Stream.unwrap(Effect.gen(function* () {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MockSpeechSynthesizer.mjs","names":["AiError.InvalidRequest"],"sources":["../../src/testing/MockSpeechSynthesizer.ts"],"sourcesContent":["import { Effect, Layer, Ref, Stream } from \"effect\"\nimport type { AudioBlob, AudioChunk } from \"../domain/Audio.js\"\nimport * as AiError from \"../domain/AiError.js\"\nimport {\n SpeechSynthesizer,\n TtsIncrementalText,\n type CommonStreamSynthesizeRequest,\n type CommonSynthesizeRequest,\n type SpeechSynthesizerService,\n} from \"../speech-synthesizer/SpeechSynthesizer.js\"\n\nexport type MockSynthesizerRecorder = {\n readonly synthesizeCalls: ReadonlyArray<CommonSynthesizeRequest>\n readonly streamSynthesisCalls: ReadonlyArray<CommonSynthesizeRequest>\n readonly streamSynthesisFromCalls: ReadonlyArray<CommonStreamSynthesizeRequest>\n}\n\nexport type MockSynthesizerScript = {\n /** One blob per `synthesize` call, consumed in order. */\n readonly blobs?: ReadonlyArray<AudioBlob>\n /** One chunk-list per `streamSynthesis` call, consumed in order. */\n readonly streamSynthesisChunks?: ReadonlyArray<ReadonlyArray<AudioChunk>>\n /** One chunk-list per `streamSynthesisFrom` call, consumed in order. */\n readonly streamSynthesisFromChunks?: ReadonlyArray<ReadonlyArray<AudioChunk>>\n}\n\nconst makeService = (\n script: MockSynthesizerScript,\n record: {\n readonly synthesize: (req: CommonSynthesizeRequest) => Effect.Effect<void>\n readonly streamSynthesis: (req: CommonSynthesizeRequest) => Effect.Effect<void>\n readonly streamSynthesisFrom: (req: CommonStreamSynthesizeRequest) => Effect.Effect<void>\n },\n) =>\n Effect.gen(function* () {\n const bCursor = yield* Ref.make(0)\n const ssCursor = yield* Ref.make(0)\n const ssfCursor = yield* Ref.make(0)\n const service: SpeechSynthesizerService = {\n synthesize: (request) =>\n Effect.gen(function* () {\n yield* record.synthesize(request)\n const i = yield* Ref.getAndUpdate(bCursor, (n) => n + 1)\n const scripted = script.blobs ?? []\n if (i >= scripted.length) {\n return yield*
|
|
1
|
+
{"version":3,"file":"MockSpeechSynthesizer.mjs","names":["AiError.InvalidRequest"],"sources":["../../src/testing/MockSpeechSynthesizer.ts"],"sourcesContent":["import { Effect, Layer, Ref, Stream } from \"effect\"\nimport type { AudioBlob, AudioChunk } from \"../domain/Audio.js\"\nimport * as AiError from \"../domain/AiError.js\"\nimport {\n SpeechSynthesizer,\n TtsIncrementalText,\n type CommonStreamSynthesizeRequest,\n type CommonSynthesizeRequest,\n type SpeechSynthesizerService,\n} from \"../speech-synthesizer/SpeechSynthesizer.js\"\n\nexport type MockSynthesizerRecorder = {\n readonly synthesizeCalls: ReadonlyArray<CommonSynthesizeRequest>\n readonly streamSynthesisCalls: ReadonlyArray<CommonSynthesizeRequest>\n readonly streamSynthesisFromCalls: ReadonlyArray<CommonStreamSynthesizeRequest>\n}\n\nexport type MockSynthesizerScript = {\n /** One blob per `synthesize` call, consumed in order. */\n readonly blobs?: ReadonlyArray<AudioBlob>\n /** One chunk-list per `streamSynthesis` call, consumed in order. */\n readonly streamSynthesisChunks?: ReadonlyArray<ReadonlyArray<AudioChunk>>\n /** One chunk-list per `streamSynthesisFrom` call, consumed in order. */\n readonly streamSynthesisFromChunks?: ReadonlyArray<ReadonlyArray<AudioChunk>>\n}\n\nconst makeService = (\n script: MockSynthesizerScript,\n record: {\n readonly synthesize: (req: CommonSynthesizeRequest) => Effect.Effect<void>\n readonly streamSynthesis: (req: CommonSynthesizeRequest) => Effect.Effect<void>\n readonly streamSynthesisFrom: (req: CommonStreamSynthesizeRequest) => Effect.Effect<void>\n },\n) =>\n Effect.gen(function* () {\n const bCursor = yield* Ref.make(0)\n const ssCursor = yield* Ref.make(0)\n const ssfCursor = yield* Ref.make(0)\n const service: SpeechSynthesizerService = {\n synthesize: (request) =>\n Effect.gen(function* () {\n yield* record.synthesize(request)\n const i = yield* Ref.getAndUpdate(bCursor, (n) => n + 1)\n const scripted = script.blobs ?? []\n if (i >= scripted.length) {\n return yield* new AiError.InvalidRequest({\n provider: \"mock\",\n raw: `MockSpeechSynthesizer exhausted: ${scripted.length} blobs scripted, but call ${i + 1} was made`,\n })\n }\n return scripted[i]!\n }),\n streamSynthesis: (request) =>\n Stream.unwrap(\n Effect.gen(function* () {\n yield* record.streamSynthesis(request)\n const i = yield* Ref.getAndUpdate(ssCursor, (n) => n + 1)\n const scripted = script.streamSynthesisChunks ?? []\n if (i >= scripted.length) {\n return Stream.fail(\n new AiError.InvalidRequest({\n provider: \"mock\",\n raw: `MockSpeechSynthesizer exhausted: ${scripted.length} streamSynthesis lists scripted, but call ${i + 1} was made`,\n }),\n )\n }\n return Stream.fromIterable(scripted[i]!)\n }),\n ),\n streamSynthesisFrom: <E, R>(\n textIn: Stream.Stream<string, E, R>,\n request: CommonStreamSynthesizeRequest,\n ): Stream.Stream<AudioChunk, AiError.AiError | E, R> =>\n Stream.unwrap(\n Effect.gen(function* () {\n yield* record.streamSynthesisFrom(request)\n const i = yield* Ref.getAndUpdate(ssfCursor, (n) => n + 1)\n const scripted = script.streamSynthesisFromChunks ?? []\n if (i >= scripted.length) {\n const exhausted: Stream.Stream<AudioChunk, AiError.AiError | E, R> = Stream.fail(\n new AiError.InvalidRequest({\n provider: \"mock\",\n raw: `MockSpeechSynthesizer exhausted: ${scripted.length} streamSynthesisFrom lists scripted, but call ${i + 1} was made`,\n }),\n )\n return exhausted\n }\n // Drain the input text fully before emitting scripted audio chunks,\n // so consumers can assert on what text was pushed.\n return Stream.drain(textIn).pipe(Stream.concat(Stream.fromIterable(scripted[i]!)))\n }),\n ),\n }\n return service\n })\n\n/**\n * Layer providing the `SpeechSynthesizer` service AND the\n * `TtsIncrementalText` capability marker. Use for the common case\n * where code under test exercises `streamSynthesisFrom`.\n */\nexport const layer = (\n script: MockSynthesizerScript,\n): {\n readonly layer: Layer.Layer<SpeechSynthesizer | TtsIncrementalText>\n readonly recorder: Effect.Effect<MockSynthesizerRecorder>\n} => {\n const bCalls = Ref.makeUnsafe<ReadonlyArray<CommonSynthesizeRequest>>([])\n const ssCalls = Ref.makeUnsafe<ReadonlyArray<CommonSynthesizeRequest>>([])\n const ssfCalls = Ref.makeUnsafe<ReadonlyArray<CommonStreamSynthesizeRequest>>([])\n const synthesizerLayer = Layer.effect(\n SpeechSynthesizer,\n makeService(script, {\n synthesize: (req) => Ref.update(bCalls, (xs) => [...xs, req]),\n streamSynthesis: (req) => Ref.update(ssCalls, (xs) => [...xs, req]),\n streamSynthesisFrom: (req) => Ref.update(ssfCalls, (xs) => [...xs, req]),\n }),\n )\n const live = Layer.merge(synthesizerLayer, Layer.succeed(TtsIncrementalText, undefined))\n return {\n layer: live,\n recorder: Effect.gen(function* () {\n const synthesizeCalls = yield* Ref.get(bCalls)\n const streamSynthesisCalls = yield* Ref.get(ssCalls)\n const streamSynthesisFromCalls = yield* Ref.get(ssfCalls)\n return { synthesizeCalls, streamSynthesisCalls, streamSynthesisFromCalls }\n }),\n }\n}\n\n/**\n * Variant that omits the `TtsIncrementalText` marker — simulates a\n * provider without incremental-text-in support (e.g. OpenAI, AWS\n * Polly non-Generative). Calls to `streamSynthesisFrom` in code under\n * test should be a compile-time error.\n */\nexport const layerWithoutIncremental = (\n script: MockSynthesizerScript,\n): {\n readonly layer: Layer.Layer<SpeechSynthesizer>\n readonly recorder: Effect.Effect<MockSynthesizerRecorder>\n} => {\n const bCalls = Ref.makeUnsafe<ReadonlyArray<CommonSynthesizeRequest>>([])\n const ssCalls = Ref.makeUnsafe<ReadonlyArray<CommonSynthesizeRequest>>([])\n const ssfCalls = Ref.makeUnsafe<ReadonlyArray<CommonStreamSynthesizeRequest>>([])\n const live = Layer.effect(\n SpeechSynthesizer,\n makeService(script, {\n synthesize: (req) => Ref.update(bCalls, (xs) => [...xs, req]),\n streamSynthesis: (req) => Ref.update(ssCalls, (xs) => [...xs, req]),\n streamSynthesisFrom: (req) => Ref.update(ssfCalls, (xs) => [...xs, req]),\n }),\n )\n return {\n layer: live,\n recorder: Effect.gen(function* () {\n const synthesizeCalls = yield* Ref.get(bCalls)\n const streamSynthesisCalls = yield* Ref.get(ssCalls)\n const streamSynthesisFromCalls = yield* Ref.get(ssfCalls)\n return { synthesizeCalls, streamSynthesisCalls, streamSynthesisFromCalls }\n }),\n }\n}\n"],"mappings":";;;;AA0BA,MAAM,eACJ,QACA,WAMA,OAAO,IAAI,aAAa;CACtB,MAAM,UAAU,OAAO,IAAI,KAAK,EAAE;CAClC,MAAM,WAAW,OAAO,IAAI,KAAK,EAAE;CACnC,MAAM,YAAY,OAAO,IAAI,KAAK,EAAE;AAwDpC,QAAO;EAtDL,aAAa,YACX,OAAO,IAAI,aAAa;AACtB,UAAO,OAAO,WAAW,QAAQ;GACjC,MAAM,IAAI,OAAO,IAAI,aAAa,UAAU,MAAM,IAAI,EAAE;GACxD,MAAM,WAAW,OAAO,SAAS,EAAE;AACnC,OAAI,KAAK,SAAS,OAChB,QAAO,OAAO,IAAIA,eAAuB;IACvC,UAAU;IACV,KAAK,oCAAoC,SAAS,OAAO,4BAA4B,IAAI,EAAE;IAC5F,CAAC;AAEJ,UAAO,SAAS;IAChB;EACJ,kBAAkB,YAChB,OAAO,OACL,OAAO,IAAI,aAAa;AACtB,UAAO,OAAO,gBAAgB,QAAQ;GACtC,MAAM,IAAI,OAAO,IAAI,aAAa,WAAW,MAAM,IAAI,EAAE;GACzD,MAAM,WAAW,OAAO,yBAAyB,EAAE;AACnD,OAAI,KAAK,SAAS,OAChB,QAAO,OAAO,KACZ,IAAIA,eAAuB;IACzB,UAAU;IACV,KAAK,oCAAoC,SAAS,OAAO,4CAA4C,IAAI,EAAE;IAC5G,CAAC,CACH;AAEH,UAAO,OAAO,aAAa,SAAS,GAAI;IACxC,CACH;EACH,sBACE,QACA,YAEA,OAAO,OACL,OAAO,IAAI,aAAa;AACtB,UAAO,OAAO,oBAAoB,QAAQ;GAC1C,MAAM,IAAI,OAAO,IAAI,aAAa,YAAY,MAAM,IAAI,EAAE;GAC1D,MAAM,WAAW,OAAO,6BAA6B,EAAE;AACvD,OAAI,KAAK,SAAS,OAOhB,QANqE,OAAO,KAC1E,IAAIA,eAAuB;IACzB,UAAU;IACV,KAAK,oCAAoC,SAAS,OAAO,gDAAgD,IAAI,EAAE;IAChH,CAAC,CAEY;AAIlB,UAAO,OAAO,MAAM,OAAO,CAAC,KAAK,OAAO,OAAO,OAAO,aAAa,SAAS,GAAI,CAAC,CAAC;IAClF,CACH;EAES;EACd;;;;;;AAOJ,MAAa,SACX,WAIG;CACH,MAAM,SAAS,IAAI,WAAmD,EAAE,CAAC;CACzE,MAAM,UAAU,IAAI,WAAmD,EAAE,CAAC;CAC1E,MAAM,WAAW,IAAI,WAAyD,EAAE,CAAC;CACjF,MAAM,mBAAmB,MAAM,OAC7B,mBACA,YAAY,QAAQ;EAClB,aAAa,QAAQ,IAAI,OAAO,SAAS,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC;EAC7D,kBAAkB,QAAQ,IAAI,OAAO,UAAU,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC;EACnE,sBAAsB,QAAQ,IAAI,OAAO,WAAW,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC;EACzE,CAAC,CACH;AAED,QAAO;EACL,OAFW,MAAM,MAAM,kBAAkB,MAAM,QAAQ,oBAAoB,KAAA,EAAU,CAE1E;EACX,UAAU,OAAO,IAAI,aAAa;AAIhC,UAAO;IAAE,iBAAA,OAHsB,IAAI,IAAI,OAAO;IAGpB,sBAAA,OAFU,IAAI,IAAI,QAAQ;IAEJ,0BAAA,OADR,IAAI,IAAI,SAAS;IACiB;IAC1E;EACH;;;;;;;;AASH,MAAa,2BACX,WAIG;CACH,MAAM,SAAS,IAAI,WAAmD,EAAE,CAAC;CACzE,MAAM,UAAU,IAAI,WAAmD,EAAE,CAAC;CAC1E,MAAM,WAAW,IAAI,WAAyD,EAAE,CAAC;AASjF,QAAO;EACL,OATW,MAAM,OACjB,mBACA,YAAY,QAAQ;GAClB,aAAa,QAAQ,IAAI,OAAO,SAAS,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC;GAC7D,kBAAkB,QAAQ,IAAI,OAAO,UAAU,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC;GACnE,sBAAsB,QAAQ,IAAI,OAAO,WAAW,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC;GACzE,CAAC,CAGS;EACX,UAAU,OAAO,IAAI,aAAa;AAIhC,UAAO;IAAE,iBAAA,OAHsB,IAAI,IAAI,OAAO;IAGpB,sBAAA,OAFU,IAAI,IAAI,QAAQ;IAEJ,0BAAA,OADR,IAAI,IAAI,SAAS;IACiB;IAC1E;EACH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MockTranscriber.d.mts","names":[],"sources":["../../src/testing/MockTranscriber.ts"],"mappings":";;;;;;;AAcA;KAAY,uBAAA;EAAA,SACD,eAAA,EAAiB,aAAA,CAAc,uBAAA;EAAA,SAC/B,WAAA,EAAa,aAAA,CAAc,6BAAA;AAAA;AAAA,KAG1B,qBAAA;EAHY,oEAKb,WAAA,GAAc,aAAA,CAAc,gBAAA,GALF;EAAA,SAO1B,OAAA,GAAU,aAAA,CAAc,aAAA,CAAc,eAAA;AAAA;;;;;;
|
|
1
|
+
{"version":3,"file":"MockTranscriber.d.mts","names":[],"sources":["../../src/testing/MockTranscriber.ts"],"mappings":";;;;;;;AAcA;KAAY,uBAAA;EAAA,SACD,eAAA,EAAiB,aAAA,CAAc,uBAAA;EAAA,SAC/B,WAAA,EAAa,aAAA,CAAc,6BAAA;AAAA;AAAA,KAG1B,qBAAA;EAHY,oEAKb,WAAA,GAAc,aAAA,CAAc,gBAAA,GALF;EAAA,SAO1B,OAAA,GAAU,aAAA,CAAc,aAAA,CAAc,eAAA;AAAA;;;;;;cA2DpC,KAAA,GACX,MAAA,EAAQ,qBAAA;EAAA,SAEC,KAAA,EAAO,KAAA,CAAM,KAAA,CAAM,WAAA,GAAc,YAAA;EAAA,SACjC,QAAA,EAAU,MAAA,CAAO,MAAA,CAAO,uBAAA;AAAA;;;;;;cA2BtB,aAAA,GACX,MAAA,EAAQ,qBAAA;EAAA,SAEC,KAAA,EAAO,KAAA,CAAM,KAAA,CAAM,WAAA;EAAA,SACnB,QAAA,EAAU,MAAA,CAAO,MAAA,CAAO,uBAAA;AAAA"}
|
|
@@ -10,10 +10,10 @@ const makeService = (script, record) => Effect.gen(function* () {
|
|
|
10
10
|
yield* record.transcribe(request);
|
|
11
11
|
const i = yield* Ref.getAndUpdate(tCursor, (n) => n + 1);
|
|
12
12
|
const scripted = script.transcripts ?? [];
|
|
13
|
-
if (i >= scripted.length) return yield*
|
|
13
|
+
if (i >= scripted.length) return yield* new InvalidRequest({
|
|
14
14
|
provider: "mock",
|
|
15
15
|
raw: `MockTranscriber exhausted: ${scripted.length} transcripts scripted, but call ${i + 1} was made`
|
|
16
|
-
})
|
|
16
|
+
});
|
|
17
17
|
return scripted[i];
|
|
18
18
|
}),
|
|
19
19
|
streamTranscriptionFrom: (audioIn, request) => Stream.unwrap(Effect.gen(function* () {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MockTranscriber.mjs","names":["AiError.InvalidRequest"],"sources":["../../src/testing/MockTranscriber.ts"],"sourcesContent":["import { Effect, Layer, Ref, Stream } from \"effect\"\nimport * as AiError from \"../domain/AiError.js\"\nimport type { TranscriptEvent, TranscriptResult } from \"../domain/Transcript.js\"\nimport {\n SttStreaming,\n Transcriber,\n type CommonStreamTranscribeRequest,\n type CommonTranscribeRequest,\n type TranscriberService,\n} from \"../transcriber/Transcriber.js\"\n\n/**\n * Recorder of every call made to the mock.\n */\nexport type MockTranscriberRecorder = {\n readonly transcribeCalls: ReadonlyArray<CommonTranscribeRequest>\n readonly streamCalls: ReadonlyArray<CommonStreamTranscribeRequest>\n}\n\nexport type MockTranscriberScript = {\n /** One result per `transcribe` call, consumed in order. */\n readonly transcripts?: ReadonlyArray<TranscriptResult>\n /** One event-list per `streamTranscriptionFrom` call, consumed in order. */\n readonly streams?: ReadonlyArray<ReadonlyArray<TranscriptEvent>>\n}\n\nconst makeService = (\n script: MockTranscriberScript,\n record: {\n readonly transcribe: (req: CommonTranscribeRequest) => Effect.Effect<void>\n readonly stream: (req: CommonStreamTranscribeRequest) => Effect.Effect<void>\n },\n) =>\n Effect.gen(function* () {\n const tCursor = yield* Ref.make(0)\n const sCursor = yield* Ref.make(0)\n const service: TranscriberService = {\n transcribe: (request) =>\n Effect.gen(function* () {\n yield* record.transcribe(request)\n const i = yield* Ref.getAndUpdate(tCursor, (n) => n + 1)\n const scripted = script.transcripts ?? []\n if (i >= scripted.length) {\n return yield*
|
|
1
|
+
{"version":3,"file":"MockTranscriber.mjs","names":["AiError.InvalidRequest"],"sources":["../../src/testing/MockTranscriber.ts"],"sourcesContent":["import { Effect, Layer, Ref, Stream } from \"effect\"\nimport * as AiError from \"../domain/AiError.js\"\nimport type { TranscriptEvent, TranscriptResult } from \"../domain/Transcript.js\"\nimport {\n SttStreaming,\n Transcriber,\n type CommonStreamTranscribeRequest,\n type CommonTranscribeRequest,\n type TranscriberService,\n} from \"../transcriber/Transcriber.js\"\n\n/**\n * Recorder of every call made to the mock.\n */\nexport type MockTranscriberRecorder = {\n readonly transcribeCalls: ReadonlyArray<CommonTranscribeRequest>\n readonly streamCalls: ReadonlyArray<CommonStreamTranscribeRequest>\n}\n\nexport type MockTranscriberScript = {\n /** One result per `transcribe` call, consumed in order. */\n readonly transcripts?: ReadonlyArray<TranscriptResult>\n /** One event-list per `streamTranscriptionFrom` call, consumed in order. */\n readonly streams?: ReadonlyArray<ReadonlyArray<TranscriptEvent>>\n}\n\nconst makeService = (\n script: MockTranscriberScript,\n record: {\n readonly transcribe: (req: CommonTranscribeRequest) => Effect.Effect<void>\n readonly stream: (req: CommonStreamTranscribeRequest) => Effect.Effect<void>\n },\n) =>\n Effect.gen(function* () {\n const tCursor = yield* Ref.make(0)\n const sCursor = yield* Ref.make(0)\n const service: TranscriberService = {\n transcribe: (request) =>\n Effect.gen(function* () {\n yield* record.transcribe(request)\n const i = yield* Ref.getAndUpdate(tCursor, (n) => n + 1)\n const scripted = script.transcripts ?? []\n if (i >= scripted.length) {\n return yield* new AiError.InvalidRequest({\n provider: \"mock\",\n raw: `MockTranscriber exhausted: ${scripted.length} transcripts scripted, but call ${i + 1} was made`,\n })\n }\n return scripted[i]!\n }),\n streamTranscriptionFrom: <E, R>(\n audioIn: Stream.Stream<Uint8Array, E, R>,\n request: CommonStreamTranscribeRequest,\n ): Stream.Stream<TranscriptEvent, AiError.AiError | E, R> =>\n Stream.unwrap(\n Effect.gen(function* () {\n yield* record.stream(request)\n const i = yield* Ref.getAndUpdate(sCursor, (n) => n + 1)\n const scripted = script.streams ?? []\n if (i >= scripted.length) {\n const exhausted: Stream.Stream<TranscriptEvent, AiError.AiError | E, R> = Stream.fail(\n new AiError.InvalidRequest({\n provider: \"mock\",\n raw: `MockTranscriber exhausted: ${scripted.length} streams scripted, but call ${i + 1} was made`,\n }),\n )\n return exhausted\n }\n // Drain the input audio fully before emitting the scripted events,\n // so consumers can assert on what bytes were pushed.\n return Stream.drain(audioIn).pipe(Stream.concat(Stream.fromIterable(scripted[i]!)))\n }),\n ),\n }\n return service\n })\n\n/**\n * Returns a Layer that provides both the `Transcriber` service and the\n * `SttStreaming` capability marker. Use when the code under test calls\n * `streamTranscriptionFrom`.\n */\nexport const layer = (\n script: MockTranscriberScript,\n): {\n readonly layer: Layer.Layer<Transcriber | SttStreaming>\n readonly recorder: Effect.Effect<MockTranscriberRecorder>\n} => {\n const tCalls = Ref.makeUnsafe<ReadonlyArray<CommonTranscribeRequest>>([])\n const sCalls = Ref.makeUnsafe<ReadonlyArray<CommonStreamTranscribeRequest>>([])\n const transcriberLayer = Layer.effect(\n Transcriber,\n makeService(script, {\n transcribe: (req) => Ref.update(tCalls, (xs) => [...xs, req]),\n stream: (req) => Ref.update(sCalls, (xs) => [...xs, req]),\n }),\n )\n const live = Layer.merge(transcriberLayer, Layer.succeed(SttStreaming, undefined))\n return {\n layer: live,\n recorder: Effect.gen(function* () {\n const transcribeCalls = yield* Ref.get(tCalls)\n const streamCalls = yield* Ref.get(sCalls)\n return { transcribeCalls, streamCalls }\n }),\n }\n}\n\n/**\n * Variant that omits the `SttStreaming` marker — use to test that\n * consumers calling `streamTranscriptionFrom` fail to compile against\n * a non-streaming provider.\n */\nexport const layerSyncOnly = (\n script: MockTranscriberScript,\n): {\n readonly layer: Layer.Layer<Transcriber>\n readonly recorder: Effect.Effect<MockTranscriberRecorder>\n} => {\n const tCalls = Ref.makeUnsafe<ReadonlyArray<CommonTranscribeRequest>>([])\n const sCalls = Ref.makeUnsafe<ReadonlyArray<CommonStreamTranscribeRequest>>([])\n const live = Layer.effect(\n Transcriber,\n makeService(script, {\n transcribe: (req) => Ref.update(tCalls, (xs) => [...xs, req]),\n stream: (req) => Ref.update(sCalls, (xs) => [...xs, req]),\n }),\n )\n return {\n layer: live,\n recorder: Effect.gen(function* () {\n const transcribeCalls = yield* Ref.get(tCalls)\n const streamCalls = yield* Ref.get(sCalls)\n return { transcribeCalls, streamCalls }\n }),\n }\n}\n"],"mappings":";;;;AA0BA,MAAM,eACJ,QACA,WAKA,OAAO,IAAI,aAAa;CACtB,MAAM,UAAU,OAAO,IAAI,KAAK,EAAE;CAClC,MAAM,UAAU,OAAO,IAAI,KAAK,EAAE;AAuClC,QAAO;EArCL,aAAa,YACX,OAAO,IAAI,aAAa;AACtB,UAAO,OAAO,WAAW,QAAQ;GACjC,MAAM,IAAI,OAAO,IAAI,aAAa,UAAU,MAAM,IAAI,EAAE;GACxD,MAAM,WAAW,OAAO,eAAe,EAAE;AACzC,OAAI,KAAK,SAAS,OAChB,QAAO,OAAO,IAAIA,eAAuB;IACvC,UAAU;IACV,KAAK,8BAA8B,SAAS,OAAO,kCAAkC,IAAI,EAAE;IAC5F,CAAC;AAEJ,UAAO,SAAS;IAChB;EACJ,0BACE,SACA,YAEA,OAAO,OACL,OAAO,IAAI,aAAa;AACtB,UAAO,OAAO,OAAO,QAAQ;GAC7B,MAAM,IAAI,OAAO,IAAI,aAAa,UAAU,MAAM,IAAI,EAAE;GACxD,MAAM,WAAW,OAAO,WAAW,EAAE;AACrC,OAAI,KAAK,SAAS,OAOhB,QAN0E,OAAO,KAC/E,IAAIA,eAAuB;IACzB,UAAU;IACV,KAAK,8BAA8B,SAAS,OAAO,8BAA8B,IAAI,EAAE;IACxF,CAAC,CAEY;AAIlB,UAAO,OAAO,MAAM,QAAQ,CAAC,KAAK,OAAO,OAAO,OAAO,aAAa,SAAS,GAAI,CAAC,CAAC;IACnF,CACH;EAES;EACd;;;;;;AAOJ,MAAa,SACX,WAIG;CACH,MAAM,SAAS,IAAI,WAAmD,EAAE,CAAC;CACzE,MAAM,SAAS,IAAI,WAAyD,EAAE,CAAC;CAC/E,MAAM,mBAAmB,MAAM,OAC7B,aACA,YAAY,QAAQ;EAClB,aAAa,QAAQ,IAAI,OAAO,SAAS,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC;EAC7D,SAAS,QAAQ,IAAI,OAAO,SAAS,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC;EAC1D,CAAC,CACH;AAED,QAAO;EACL,OAFW,MAAM,MAAM,kBAAkB,MAAM,QAAQ,cAAc,KAAA,EAAU,CAEpE;EACX,UAAU,OAAO,IAAI,aAAa;AAGhC,UAAO;IAAE,iBAAA,OAFsB,IAAI,IAAI,OAAO;IAEpB,aAAA,OADC,IAAI,IAAI,OAAO;IACH;IACvC;EACH;;;;;;;AAQH,MAAa,iBACX,WAIG;CACH,MAAM,SAAS,IAAI,WAAmD,EAAE,CAAC;CACzE,MAAM,SAAS,IAAI,WAAyD,EAAE,CAAC;AAQ/E,QAAO;EACL,OARW,MAAM,OACjB,aACA,YAAY,QAAQ;GAClB,aAAa,QAAQ,IAAI,OAAO,SAAS,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC;GAC7D,SAAS,QAAQ,IAAI,OAAO,SAAS,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC;GAC1D,CAAC,CAGS;EACX,UAAU,OAAO,IAAI,aAAa;AAGhC,UAAO;IAAE,iBAAA,OAFsB,IAAI,IAAI,OAAO;IAEpB,aAAA,OADC,IAAI,IAAI,OAAO;IACH;IACvC;EACH"}
|
package/dist/tool/Outcome.d.mts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { o as FunctionCall } from "../Items-
|
|
1
|
+
import { o as FunctionCall } from "../Items-BH8xUkoR.mjs";
|
|
2
2
|
import { ToolResult } from "./Outcome.mjs";
|
|
3
3
|
import { ToolEvent } from "./ToolEvent.mjs";
|
|
4
|
-
import { Effect, Queue, Scope, Stream } from "effect";
|
|
4
|
+
import { Data, Effect, Queue, Scope, Stream } from "effect";
|
|
5
|
+
import * as _$effect_Unify0 from "effect/Unify";
|
|
5
6
|
|
|
6
7
|
//#region src/tool/Resolvers.d.ts
|
|
7
8
|
declare namespace Resolvers_d_exports {
|
|
@@ -11,12 +12,68 @@ type ToolCallPlan = {
|
|
|
11
12
|
readonly approved: ReadonlyArray<FunctionCall>;
|
|
12
13
|
readonly rejected: ReadonlyArray<ToolResult>;
|
|
13
14
|
};
|
|
14
|
-
type ToolCallDecision = {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
type ToolCallDecision = Data.TaggedEnum<{
|
|
16
|
+
Approved: {
|
|
17
|
+
readonly call: FunctionCall;
|
|
18
|
+
};
|
|
19
|
+
Rejected: {
|
|
20
|
+
readonly result: ToolResult;
|
|
21
|
+
};
|
|
22
|
+
}>;
|
|
23
|
+
declare const ToolCallDecision: {
|
|
24
|
+
readonly Approved: Data.TaggedEnum.ConstructorFrom<{
|
|
25
|
+
readonly _tag: "Approved";
|
|
26
|
+
readonly call: FunctionCall;
|
|
27
|
+
}, "_tag">;
|
|
28
|
+
readonly Rejected: Data.TaggedEnum.ConstructorFrom<{
|
|
29
|
+
readonly _tag: "Rejected";
|
|
30
|
+
readonly result: ToolResult;
|
|
31
|
+
}, "_tag">;
|
|
32
|
+
readonly $is: <Tag extends "Approved" | "Rejected">(tag: Tag) => (u: unknown) => u is Extract<{
|
|
33
|
+
readonly _tag: "Approved";
|
|
34
|
+
readonly call: FunctionCall;
|
|
35
|
+
}, {
|
|
36
|
+
readonly _tag: Tag;
|
|
37
|
+
}> | Extract<{
|
|
38
|
+
readonly _tag: "Rejected";
|
|
39
|
+
readonly result: ToolResult;
|
|
40
|
+
}, {
|
|
41
|
+
readonly _tag: Tag;
|
|
42
|
+
}>;
|
|
43
|
+
readonly $match: {
|
|
44
|
+
<Cases extends {
|
|
45
|
+
readonly Approved: (args: {
|
|
46
|
+
readonly _tag: "Approved";
|
|
47
|
+
readonly call: FunctionCall;
|
|
48
|
+
}) => any;
|
|
49
|
+
readonly Rejected: (args: {
|
|
50
|
+
readonly _tag: "Rejected";
|
|
51
|
+
readonly result: ToolResult;
|
|
52
|
+
}) => any;
|
|
53
|
+
}>(cases: Cases): (value: {
|
|
54
|
+
readonly _tag: "Approved";
|
|
55
|
+
readonly call: FunctionCall;
|
|
56
|
+
} | {
|
|
57
|
+
readonly _tag: "Rejected";
|
|
58
|
+
readonly result: ToolResult;
|
|
59
|
+
}) => _$effect_Unify0.Unify<ReturnType<Cases["Approved" | "Rejected"]>>;
|
|
60
|
+
<Cases extends {
|
|
61
|
+
readonly Approved: (args: {
|
|
62
|
+
readonly _tag: "Approved";
|
|
63
|
+
readonly call: FunctionCall;
|
|
64
|
+
}) => any;
|
|
65
|
+
readonly Rejected: (args: {
|
|
66
|
+
readonly _tag: "Rejected";
|
|
67
|
+
readonly result: ToolResult;
|
|
68
|
+
}) => any;
|
|
69
|
+
}>(value: {
|
|
70
|
+
readonly _tag: "Approved";
|
|
71
|
+
readonly call: FunctionCall;
|
|
72
|
+
} | {
|
|
73
|
+
readonly _tag: "Rejected";
|
|
74
|
+
readonly result: ToolResult;
|
|
75
|
+
}, cases: Cases): _$effect_Unify0.Unify<ReturnType<Cases["Approved" | "Rejected"]>>;
|
|
76
|
+
};
|
|
20
77
|
};
|
|
21
78
|
declare const approve: (call: FunctionCall) => ToolCallDecision;
|
|
22
79
|
declare const reject: (result: ToolResult) => ToolCallDecision;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Resolvers.d.mts","names":[],"sources":["../../src/tool/Resolvers.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"Resolvers.d.mts","names":[],"sources":["../../src/tool/Resolvers.ts"],"mappings":";;;;;;;;;;KAYY,YAAA;EAAA,SACD,QAAA,EAAU,aAAA,CAAc,YAAA;EAAA,SACxB,QAAA,EAAU,aAAA,CAAc,UAAA;AAAA;AAAA,KAGvB,gBAAA,GAAmB,IAAA,CAAK,UAAA;EAClC,QAAA;IAAA,SAAqB,IAAA,EAAM,YAAA;EAAA;EAC3B,QAAA;IAAA,SAAqB,MAAA,EAAQ,UAAA;EAAA;AAAA;AAAA,cAGlB,gBAAA;EAAA;;mBAJgB,YAAA;EAAA;EAAA;;qBACE,UAAA;EAAA;EAAA;;mBADF,YAAA;EAAA;IAAA;;;qBACE,UAAA;EAAA;IAAA;;;;;;uBADF,YAAA;MAAA;MAAA;;yBACE,UAAA;MAAA;IAAA;;qBADF,YAAA;IAAA;MAAA;uBACE,UAAA;IAAA;;;;uBADF,YAAA;MAAA;MAAA;;yBACE,UAAA;MAAA;IAAA;;qBADF,YAAA;IAAA;MAAA;uBACE,UAAA;IAAA;;;cAKlB,OAAA,GAAW,IAAA,EAAM,YAAA,KAAe,gBAAA;AAAA,cAEhC,MAAA,GAAU,MAAA,EAAQ,UAAA,KAAa,gBAAA;AAAA,cAG/B,sBAAA,GAA0B,SAAA,EAAW,aAAA,CAAc,gBAAA,MAAoB,YAAA;AAAA,cASvE,iBAAA,GAAqB,IAAA,EAAM,YAAA,KAAe,SAAA;AAAA,KAW3C,OAAA;EAAA,SACD,OAAA;EAAA,SACA,QAAA;EAAA,SACA,MAAA;AAAA;;;;;;cAQE,gBAAA,GACV,SAAA,GAAY,IAAA,EAAM,YAAA,cAA0B,QAAA,EAAU,KAAA,CAAM,OAAA,CAAQ,OAAA,OAEnE,KAAA,EAAO,aAAA,CAAc,YAAA,MACpB,MAAA,CAAO,MAAA;EAAA,SAEG,QAAA,EAAU,aAAA,CAAc,YAAA;EAAA,SACxB,SAAA,EAAW,MAAA,CAAO,MAAA,CAAO,gBAAA;EAAA,SACzB,QAAA,EAAU,MAAA,CAAO,MAAA,CAAO,SAAA;AAAA,UAGnC,KAAA,CAAM,KAAA;AAAA,KAkDE,gBAAA;EAAA,SACG,QAAA;AAAA;EAAA,SACA,QAAA;EAAA,SAA2B,MAAA;AAAA;AAAA,cAE7B,eAAA,GACV,SAAA,GAAY,IAAA,EAAM,YAAA,cAA0B,SAAA,EAAW,WAAA,SAAoB,gBAAA,OAC3E,KAAA,EAAO,aAAA,CAAc,YAAA,MAAgB,YAAA"}
|
package/dist/tool/Resolvers.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { n as __exportAll } from "../chunk-uyGKjUfl.mjs";
|
|
2
2
|
import { cancelled, denied } from "./Outcome.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { ToolEvent } from "./ToolEvent.mjs";
|
|
4
|
+
import { Data, Deferred, Effect, Queue, Stream } from "effect";
|
|
4
5
|
//#region src/tool/Resolvers.ts
|
|
5
6
|
/**
|
|
6
7
|
* Approval helpers for the two transport flavors.
|
|
@@ -10,6 +11,7 @@ import { Deferred, Effect, Queue, Stream } from "effect";
|
|
|
10
11
|
* the recipe boundary via `Toolkit.executeAll`.
|
|
11
12
|
*/
|
|
12
13
|
var Resolvers_exports = /* @__PURE__ */ __exportAll({
|
|
14
|
+
ToolCallDecision: () => ToolCallDecision,
|
|
13
15
|
approvalRequested: () => approvalRequested,
|
|
14
16
|
approve: () => approve,
|
|
15
17
|
fromApprovalMap: () => fromApprovalMap,
|
|
@@ -17,14 +19,9 @@ var Resolvers_exports = /* @__PURE__ */ __exportAll({
|
|
|
17
19
|
reject: () => reject,
|
|
18
20
|
splitToolCallDecisions: () => splitToolCallDecisions
|
|
19
21
|
});
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
});
|
|
24
|
-
const reject = (result) => ({
|
|
25
|
-
_tag: "Rejected",
|
|
26
|
-
result
|
|
27
|
-
});
|
|
22
|
+
const ToolCallDecision = Data.taggedEnum();
|
|
23
|
+
const approve = (call) => ToolCallDecision.Approved({ call });
|
|
24
|
+
const reject = (result) => ToolCallDecision.Rejected({ result });
|
|
28
25
|
const splitToolCallDecisions = (decisions) => decisions.reduce((acc, decision) => decision._tag === "Approved" ? {
|
|
29
26
|
...acc,
|
|
30
27
|
approved: [...acc.approved, decision.call]
|
|
@@ -35,8 +32,7 @@ const splitToolCallDecisions = (decisions) => decisions.reduce((acc, decision) =
|
|
|
35
32
|
approved: [],
|
|
36
33
|
rejected: []
|
|
37
34
|
});
|
|
38
|
-
const approvalRequested = (call) => ({
|
|
39
|
-
_tag: "ApprovalRequested",
|
|
35
|
+
const approvalRequested = (call) => ToolEvent.ApprovalRequested({
|
|
40
36
|
call_id: call.call_id,
|
|
41
37
|
tool: call.name,
|
|
42
38
|
arguments: call.arguments
|
|
@@ -72,6 +68,6 @@ const fromApprovalMap = (predicate, approvals) => (calls) => splitToolCallDecisi
|
|
|
72
68
|
return v.decision === "approve" ? approve(call) : reject(denied(call, v.reason));
|
|
73
69
|
}));
|
|
74
70
|
//#endregion
|
|
75
|
-
export { approvalRequested, approve, fromApprovalMap, fromVerdictQueue, reject, splitToolCallDecisions, Resolvers_exports as t };
|
|
71
|
+
export { ToolCallDecision, approvalRequested, approve, fromApprovalMap, fromVerdictQueue, reject, splitToolCallDecisions, Resolvers_exports as t };
|
|
76
72
|
|
|
77
73
|
//# sourceMappingURL=Resolvers.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Resolvers.mjs","names":[],"sources":["../../src/tool/Resolvers.ts"],"sourcesContent":["/**\n * Approval helpers for the two transport flavors.\n *\n * These helpers only decide which calls are approved and which synthetic\n * results must be returned to the model. Tool execution stays explicit at\n * the recipe boundary via `Toolkit.executeAll`.\n */\nimport { Deferred, Effect, Queue, Scope, Stream } from \"effect\"\nimport type { FunctionCall } from \"../domain/Items.js\"\nimport { type ToolResult, cancelled, denied } from \"./Outcome.js\"\nimport
|
|
1
|
+
{"version":3,"file":"Resolvers.mjs","names":[],"sources":["../../src/tool/Resolvers.ts"],"sourcesContent":["/**\n * Approval helpers for the two transport flavors.\n *\n * These helpers only decide which calls are approved and which synthetic\n * results must be returned to the model. Tool execution stays explicit at\n * the recipe boundary via `Toolkit.executeAll`.\n */\nimport { Data, Deferred, Effect, Queue, Scope, Stream } from \"effect\"\nimport type { FunctionCall } from \"../domain/Items.js\"\nimport { type ToolResult, cancelled, denied } from \"./Outcome.js\"\nimport { ToolEvent } from \"./ToolEvent.js\"\n\nexport type ToolCallPlan = {\n readonly approved: ReadonlyArray<FunctionCall>\n readonly rejected: ReadonlyArray<ToolResult>\n}\n\nexport type ToolCallDecision = Data.TaggedEnum<{\n Approved: { readonly call: FunctionCall }\n Rejected: { readonly result: ToolResult }\n}>\n\nexport const ToolCallDecision = Data.taggedEnum<ToolCallDecision>()\n\nexport const approve = (call: FunctionCall): ToolCallDecision => ToolCallDecision.Approved({ call })\n\nexport const reject = (result: ToolResult): ToolCallDecision =>\n ToolCallDecision.Rejected({ result })\n\nexport const splitToolCallDecisions = (decisions: ReadonlyArray<ToolCallDecision>): ToolCallPlan =>\n decisions.reduce<ToolCallPlan>(\n (acc, decision) =>\n decision._tag === \"Approved\"\n ? { ...acc, approved: [...acc.approved, decision.call] }\n : { ...acc, rejected: [...acc.rejected, decision.result] },\n { approved: [], rejected: [] },\n )\n\nexport const approvalRequested = (call: FunctionCall): ToolEvent =>\n ToolEvent.ApprovalRequested({\n call_id: call.call_id,\n tool: call.name,\n arguments: call.arguments,\n })\n\n// ---------------------------------------------------------------------------\n// Verdict queue (WebSocket-style transport).\n// ---------------------------------------------------------------------------\n\nexport type Verdict = {\n readonly call_id: string\n readonly decision: \"approve\" | \"deny\"\n readonly reason?: string\n}\n\n/**\n * Queue-backed approval planner. Safe calls are returned immediately in\n * `approved`; gated calls emit `ApprovalRequested` events and later produce\n * one `ToolCallDecision` when their matching verdict arrives.\n */\nexport const fromVerdictQueue =\n (predicate: (call: FunctionCall) => boolean, verdicts: Queue.Dequeue<Verdict>) =>\n (\n calls: ReadonlyArray<FunctionCall>,\n ): Effect.Effect<\n {\n readonly approved: ReadonlyArray<FunctionCall>\n readonly decisions: Stream.Stream<ToolCallDecision>\n readonly announce: Stream.Stream<ToolEvent>\n },\n never,\n Scope.Scope\n > =>\n Effect.gen(function* () {\n const gated = calls.filter(predicate)\n const approved = calls.filter((call) => !predicate(call))\n\n const entries = yield* Effect.forEach(gated, (call) =>\n Deferred.make<Verdict>().pipe(Effect.map((d) => [call.call_id, d] as const)),\n )\n const deferreds: ReadonlyMap<string, Deferred.Deferred<Verdict>> = new Map(entries)\n\n // Router is forked into the surrounding Scope so it lives as long\n // as the consumer is pulling events. Recipes typically supply the\n // scope by wrapping the events construction in `Stream.unwrap`.\n yield* Effect.forkScoped(\n Effect.forever(\n Effect.gen(function* () {\n const v = yield* Queue.take(verdicts)\n const d = deferreds.get(v.call_id)\n if (d !== undefined) yield* Deferred.succeed(d, v)\n }),\n ),\n )\n\n const decisions = Stream.fromIterable(gated).pipe(\n Stream.flatMap(\n (call) => {\n const d = deferreds.get(call.call_id)!\n return Stream.fromEffect(\n Deferred.await(d).pipe(\n Effect.map((v) =>\n v.decision === \"approve\" ? approve(call) : reject(denied(call, v.reason)),\n ),\n ),\n )\n },\n { concurrency: \"unbounded\" },\n ),\n )\n\n const announce = Stream.fromIterable<ToolEvent>(gated.map(approvalRequested))\n\n return { approved, decisions, announce }\n })\n\n// ---------------------------------------------------------------------------\n// Approval map (HTTP-style transport). Verdicts arrive synchronously\n// bundled in the request payload. Missing entries → cancelled.\n// ---------------------------------------------------------------------------\n\nexport type ApprovalMapEntry =\n | { readonly decision: \"approve\" }\n | { readonly decision: \"deny\"; readonly reason?: string }\n\nexport const fromApprovalMap =\n (predicate: (call: FunctionCall) => boolean, approvals: ReadonlyMap<string, ApprovalMapEntry>) =>\n (calls: ReadonlyArray<FunctionCall>): ToolCallPlan =>\n splitToolCallDecisions(\n calls.map((call) => {\n if (!predicate(call)) return approve(call)\n const v = approvals.get(call.call_id)\n if (v === undefined) return reject(cancelled(call))\n return v.decision === \"approve\" ? approve(call) : reject(denied(call, v.reason))\n }),\n )\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAsBA,MAAa,mBAAmB,KAAK,YAA8B;AAEnE,MAAa,WAAW,SAAyC,iBAAiB,SAAS,EAAE,MAAM,CAAC;AAEpG,MAAa,UAAU,WACrB,iBAAiB,SAAS,EAAE,QAAQ,CAAC;AAEvC,MAAa,0BAA0B,cACrC,UAAU,QACP,KAAK,aACJ,SAAS,SAAS,aACd;CAAE,GAAG;CAAK,UAAU,CAAC,GAAG,IAAI,UAAU,SAAS,KAAK;CAAE,GACtD;CAAE,GAAG;CAAK,UAAU,CAAC,GAAG,IAAI,UAAU,SAAS,OAAO;CAAE,EAC9D;CAAE,UAAU,EAAE;CAAE,UAAU,EAAE;CAAE,CAC/B;AAEH,MAAa,qBAAqB,SAChC,UAAU,kBAAkB;CAC1B,SAAS,KAAK;CACd,MAAM,KAAK;CACX,WAAW,KAAK;CACjB,CAAC;;;;;;AAiBJ,MAAa,oBACV,WAA4C,cAE3C,UAUA,OAAO,IAAI,aAAa;CACtB,MAAM,QAAQ,MAAM,OAAO,UAAU;CACrC,MAAM,WAAW,MAAM,QAAQ,SAAS,CAAC,UAAU,KAAK,CAAC;CAEzD,MAAM,UAAU,OAAO,OAAO,QAAQ,QAAQ,SAC5C,SAAS,MAAe,CAAC,KAAK,OAAO,KAAK,MAAM,CAAC,KAAK,SAAS,EAAE,CAAU,CAAC,CAC7E;CACD,MAAM,YAA6D,IAAI,IAAI,QAAQ;AAKnF,QAAO,OAAO,WACZ,OAAO,QACL,OAAO,IAAI,aAAa;EACtB,MAAM,IAAI,OAAO,MAAM,KAAK,SAAS;EACrC,MAAM,IAAI,UAAU,IAAI,EAAE,QAAQ;AAClC,MAAI,MAAM,KAAA,EAAW,QAAO,SAAS,QAAQ,GAAG,EAAE;GAClD,CACH,CACF;AAoBD,QAAO;EAAE;EAAU,WAlBD,OAAO,aAAa,MAAM,CAAC,KAC3C,OAAO,SACJ,SAAS;GACR,MAAM,IAAI,UAAU,IAAI,KAAK,QAAQ;AACrC,UAAO,OAAO,WACZ,SAAS,MAAM,EAAE,CAAC,KAChB,OAAO,KAAK,MACV,EAAE,aAAa,YAAY,QAAQ,KAAK,GAAG,OAAO,OAAO,MAAM,EAAE,OAAO,CAAC,CAC1E,CACF,CACF;KAEH,EAAE,aAAa,aAAa,CAC7B,CAKyB;EAAE,UAFb,OAAO,aAAwB,MAAM,IAAI,kBAAkB,CAEtC;EAAE;EACxC;AAWN,MAAa,mBACV,WAA4C,eAC5C,UACC,uBACE,MAAM,KAAK,SAAS;AAClB,KAAI,CAAC,UAAU,KAAK,CAAE,QAAO,QAAQ,KAAK;CAC1C,MAAM,IAAI,UAAU,IAAI,KAAK,QAAQ;AACrC,KAAI,MAAM,KAAA,EAAW,QAAO,OAAO,UAAU,KAAK,CAAC;AACnD,QAAO,EAAE,aAAa,YAAY,QAAQ,KAAK,GAAG,OAAO,OAAO,MAAM,EAAE,OAAO,CAAC;EAChF,CACH"}
|