@effect-uai/core 0.17.0 → 0.19.0-dev-20261109183959
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 +5 -5
- package/dist/Content-D_Qfn8Ja.d.mts +48 -0
- package/dist/Content-D_Qfn8Ja.d.mts.map +1 -0
- package/dist/File-B923KvZ9.d.mts +46 -0
- package/dist/File-B923KvZ9.d.mts.map +1 -0
- package/dist/{Image-Cv6iBLTy.d.mts → Image-Bzc1H_UK.d.mts} +19 -8
- package/dist/Image-Bzc1H_UK.d.mts.map +1 -0
- package/dist/{Items-C7MyrYPX.d.mts → Items-Bvx8s9QH.d.mts} +109 -16
- package/dist/Items-Bvx8s9QH.d.mts.map +1 -0
- package/dist/{Loop-ChpaO-ya.d.mts → Loop-CeA0AelN.d.mts} +7 -6
- package/dist/Loop-CeA0AelN.d.mts.map +1 -0
- package/dist/Tool-Bkryw6-h.d.mts +206 -0
- package/dist/Tool-Bkryw6-h.d.mts.map +1 -0
- package/dist/ToolEvent-9inLx5cI.d.mts +22 -0
- package/dist/ToolEvent-9inLx5cI.d.mts.map +1 -0
- package/dist/ToolOutput-BNTIQgGB.d.mts +151 -0
- package/dist/ToolOutput-BNTIQgGB.d.mts.map +1 -0
- package/dist/Toolkit-D2VGeD5R.d.mts +212 -0
- package/dist/Toolkit-D2VGeD5R.d.mts.map +1 -0
- package/dist/browser/BrowserTool.d.mts +84 -22
- package/dist/browser/BrowserTool.d.mts.map +1 -1
- package/dist/browser/BrowserTool.mjs +5 -0
- package/dist/browser/BrowserTool.mjs.map +1 -1
- package/dist/capabilities/Capabilities.d.mts +1 -1
- package/dist/decision-model/Decision.test.mjs +4 -4
- package/dist/dist-B5K-y4Be.mjs +191 -0
- package/dist/dist-B5K-y4Be.mjs.map +1 -0
- package/dist/dist-CorQTQL-.mjs +1533 -0
- package/dist/dist-CorQTQL-.mjs.map +1 -0
- package/dist/dist-DNeuh84d.mjs +7 -0
- package/dist/dist-DNeuh84d.mjs.map +1 -0
- package/dist/dist-DzeSyeYF.mjs +10253 -0
- package/dist/dist-DzeSyeYF.mjs.map +1 -0
- package/dist/domain/File.d.mts +2 -0
- package/dist/domain/File.mjs +51 -0
- package/dist/domain/File.mjs.map +1 -0
- package/dist/domain/Image.d.mts +2 -2
- package/dist/domain/Image.mjs +12 -2
- package/dist/domain/Image.mjs.map +1 -1
- package/dist/domain/Items.d.mts +2 -2
- package/dist/domain/Items.mjs +65 -4
- package/dist/domain/Items.mjs.map +1 -1
- package/dist/domain/Realtime.d.mts +72 -490
- package/dist/domain/Realtime.d.mts.map +1 -1
- package/dist/domain/Research.d.mts +1 -1
- package/dist/domain/Transcript.test.mjs +3 -3
- package/dist/domain/Turn.d.mts +57 -262
- package/dist/domain/Turn.d.mts.map +1 -1
- package/dist/domain/Turn.mjs +1 -1
- package/dist/domain/Turn.test.mjs +2 -2
- package/dist/embedding-model/Embedding.d.mts +1 -1
- package/dist/embedding-model/EmbeddingModel.test.mjs +2 -2
- package/dist/embedding-model/EmbeddingModel.test.mjs.map +1 -1
- package/dist/image-generator/ImageGenerator.d.mts +10 -66
- package/dist/image-generator/ImageGenerator.d.mts.map +1 -1
- package/dist/index.C4tKZ0yW-LuFnBtmu.mjs +7042 -0
- package/dist/index.C4tKZ0yW-LuFnBtmu.mjs.map +1 -0
- package/dist/index.d.mts +10 -9
- package/dist/index.mjs +7 -6
- package/dist/language-model/LanguageModel.d.mts +2 -2
- package/dist/language-model/LanguageModel.test.mjs +2 -2
- package/dist/loop/Loop.d.mts +1 -1
- package/dist/loop/Loop.mjs.map +1 -1
- package/dist/loop/Loop.test.mjs +3 -3
- package/dist/math/Probability.test.mjs +2 -2
- package/dist/messenger/Messenger.d.mts +43 -289
- package/dist/messenger/Messenger.d.mts.map +1 -1
- package/dist/messenger/MessengerAdapter.test.mjs +3 -3
- package/dist/music-generator/MusicGenerator.test.mjs +3 -3
- package/dist/observability/Metrics.d.mts +1 -1
- package/dist/observability/Metrics.test.mjs +3 -3
- package/dist/observability/Telemetry.d.mts +1 -1
- package/dist/observability/Telemetry.mjs +1 -1
- package/dist/observability/Telemetry.mjs.map +1 -1
- package/dist/observability/Telemetry.test.mjs +4 -4
- package/dist/observability/Telemetry.test.mjs.map +1 -1
- package/dist/realtime/RealtimeSession.d.mts +1 -1
- package/dist/realtime/RealtimeSession.test.mjs +4 -4
- package/dist/retry/Retry.test.mjs +2 -2
- package/dist/sandbox/Sandbox.d.mts +32 -237
- package/dist/sandbox/Sandbox.d.mts.map +1 -1
- package/dist/speech-synthesizer/SpeechSynthesizer.test.mjs +3 -3
- package/dist/streaming/JSONL.test.mjs +2 -2
- package/dist/streaming/Multipart.d.mts +1 -1
- package/dist/streaming/Multipart.mjs +3 -2
- package/dist/streaming/Multipart.mjs.map +1 -1
- package/dist/streaming/Multipart.test.mjs +28 -0
- package/dist/streaming/Multipart.test.mjs.map +1 -0
- package/dist/streaming/SSE.d.mts +1 -1
- package/dist/streaming/SSE.mjs +2 -2
- package/dist/streaming/SSE.mjs.map +1 -1
- package/dist/streaming/SSE.test.mjs +2 -2
- package/dist/streaming/Settle.test.mjs +3 -3
- package/dist/streaming/WebSocketSession.d.mts +5 -3
- package/dist/streaming/WebSocketSession.d.mts.map +1 -1
- package/dist/streaming/WebSocketSession.mjs +22 -10
- package/dist/streaming/WebSocketSession.mjs.map +1 -1
- package/dist/streaming/WebSocketSession.test.mjs +41 -5
- package/dist/streaming/WebSocketSession.test.mjs.map +1 -1
- package/dist/structured-format/StructuredFormat.mjs.map +1 -1
- package/dist/structured-format/StructuredFormat.test.mjs +2 -2
- package/dist/testing/FakeWebSocket.d.mts +5 -1
- package/dist/testing/FakeWebSocket.d.mts.map +1 -1
- package/dist/testing/FakeWebSocket.mjs +12 -6
- package/dist/testing/FakeWebSocket.mjs.map +1 -1
- package/dist/testing/MockMessenger.d.mts +19 -134
- package/dist/testing/MockMessenger.d.mts.map +1 -1
- package/dist/testing/MockProvider.d.mts +1 -1
- package/dist/testing/MockSandbox.d.mts +65 -446
- package/dist/testing/MockSandbox.d.mts.map +1 -1
- package/dist/tool/Content.d.mts +2 -0
- package/dist/tool/Content.mjs +46 -0
- package/dist/tool/Content.mjs.map +1 -0
- package/dist/tool/Content.test.d.mts +1 -0
- package/dist/tool/Content.test.mjs +48 -0
- package/dist/tool/Content.test.mjs.map +1 -0
- package/dist/tool/HistoryCheck.d.mts +6 -15
- package/dist/tool/HistoryCheck.d.mts.map +1 -1
- package/dist/tool/HistoryCheck.mjs +10 -24
- package/dist/tool/HistoryCheck.mjs.map +1 -1
- package/dist/tool/Tool.d.mts +2 -2
- package/dist/tool/Tool.mjs +55 -130
- package/dist/tool/Tool.mjs.map +1 -1
- package/dist/tool/Tool.test.mjs +222 -178
- package/dist/tool/Tool.test.mjs.map +1 -1
- package/dist/tool/ToolDecision.d.mts +164 -0
- package/dist/tool/ToolDecision.d.mts.map +1 -0
- package/dist/tool/ToolDecision.mjs +146 -0
- package/dist/tool/ToolDecision.mjs.map +1 -0
- package/dist/tool/ToolDecision.test.d.mts +1 -0
- package/dist/tool/ToolDecision.test.mjs +330 -0
- package/dist/tool/ToolDecision.test.mjs.map +1 -0
- package/dist/tool/ToolEvent.d.mts +2 -2
- package/dist/tool/ToolEvent.mjs +2 -34
- package/dist/tool/ToolEvent.mjs.map +1 -1
- package/dist/tool/ToolOutput.d.mts +2 -0
- package/dist/tool/ToolOutput.mjs +128 -0
- package/dist/tool/ToolOutput.mjs.map +1 -0
- package/dist/tool/ToolOutput.test.d.mts +1 -0
- package/dist/tool/ToolOutput.test.mjs +153 -0
- package/dist/tool/ToolOutput.test.mjs.map +1 -0
- package/dist/tool/Toolkit.d.mts +2 -2
- package/dist/tool/Toolkit.mjs +230 -165
- package/dist/tool/Toolkit.mjs.map +1 -1
- package/dist/tool/Toolkit.test.mjs +433 -391
- package/dist/tool/Toolkit.test.mjs.map +1 -1
- package/dist/transcriber/Transcriber.test.mjs +3 -3
- package/dist/web-read/WebReadTool.d.mts +2 -2
- package/dist/web-read/WebReadTool.mjs +1 -0
- package/dist/web-read/WebReadTool.mjs.map +1 -1
- package/dist/web-search/WebSearchTool.d.mts +2 -2
- package/dist/web-search/WebSearchTool.mjs +1 -0
- package/dist/web-search/WebSearchTool.mjs.map +1 -1
- package/package.json +18 -10
- package/src/browser/BrowserTool.ts +5 -0
- package/src/domain/File.ts +79 -0
- package/src/domain/Image.ts +14 -4
- package/src/domain/Items.ts +39 -3
- package/src/embedding-model/EmbeddingModel.test.ts +2 -2
- package/src/index.ts +3 -2
- package/src/loop/Loop.ts +25 -14
- package/src/observability/Telemetry.test.ts +1 -1
- package/src/observability/Telemetry.ts +2 -2
- package/src/streaming/Multipart.test.ts +26 -0
- package/src/streaming/Multipart.ts +6 -5
- package/src/streaming/SSE.ts +2 -2
- package/src/streaming/WebSocketSession.test.ts +54 -2
- package/src/streaming/WebSocketSession.ts +27 -6
- package/src/structured-format/StructuredFormat.ts +1 -1
- package/src/testing/FakeWebSocket.ts +21 -6
- package/src/tool/Content.test.ts +40 -0
- package/src/tool/Content.ts +38 -0
- package/src/tool/HistoryCheck.ts +21 -26
- package/src/tool/Tool.test.ts +240 -241
- package/src/tool/Tool.ts +204 -309
- package/src/tool/ToolDecision.test.ts +301 -0
- package/src/tool/ToolDecision.ts +360 -0
- package/src/tool/ToolEvent.ts +21 -41
- package/src/tool/ToolOutput.test.ts +132 -0
- package/src/tool/ToolOutput.ts +198 -0
- package/src/tool/Toolkit.test.ts +594 -454
- package/src/tool/Toolkit.ts +631 -356
- package/src/web-read/WebReadTool.ts +2 -1
- package/src/web-search/WebSearchTool.ts +2 -1
- package/dist/Image-Cv6iBLTy.d.mts.map +0 -1
- package/dist/Items-C7MyrYPX.d.mts.map +0 -1
- package/dist/Loop-ChpaO-ya.d.mts.map +0 -1
- package/dist/Tool-BF78QWtn.d.mts +0 -314
- package/dist/Tool-BF78QWtn.d.mts.map +0 -1
- package/dist/ToolEvent-Di4a723B.d.mts +0 -144
- package/dist/ToolEvent-Di4a723B.d.mts.map +0 -1
- package/dist/ToolResult-BsYQRjmW.d.mts +0 -152
- package/dist/ToolResult-BsYQRjmW.d.mts.map +0 -1
- package/dist/Toolkit-tlL0F2YR.d.mts +0 -193
- package/dist/Toolkit-tlL0F2YR.d.mts.map +0 -1
- package/dist/dist-BaenoU1A.mjs +0 -145
- package/dist/dist-BaenoU1A.mjs.map +0 -1
- package/dist/dist-nohcnkD6.mjs +0 -182
- package/dist/dist-nohcnkD6.mjs.map +0 -1
- package/dist/magic-string.es-CB2hOw9v.mjs +0 -1014
- package/dist/magic-string.es-CB2hOw9v.mjs.map +0 -1
- package/dist/test.DNmyFkvJ-YPzDR0Co.mjs +0 -10254
- package/dist/test.DNmyFkvJ-YPzDR0Co.mjs.map +0 -1
- package/dist/tool/Approval.d.mts +0 -113
- package/dist/tool/Approval.d.mts.map +0 -1
- package/dist/tool/Approval.mjs +0 -80
- package/dist/tool/Approval.mjs.map +0 -1
- package/dist/tool/Approval.test.mjs +0 -371
- package/dist/tool/Approval.test.mjs.map +0 -1
- package/dist/tool/ToolResult.d.mts +0 -2
- package/dist/tool/ToolResult.mjs +0 -85
- package/dist/tool/ToolResult.mjs.map +0 -1
- package/dist/utils-CiH_DjFW.mjs +0 -3918
- package/dist/utils-CiH_DjFW.mjs.map +0 -1
- package/src/tool/Approval.test.ts +0 -400
- package/src/tool/Approval.ts +0 -181
- package/src/tool/ToolResult.ts +0 -114
- /package/dist/{tool/Approval.test.d.mts → streaming/Multipart.test.d.mts} +0 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import { make, noInput } from "./Tool.mjs";
|
|
2
|
+
import { toolCallOutput, userText } from "../domain/Items.mjs";
|
|
3
|
+
import { failure, success } from "./ToolOutput.mjs";
|
|
4
|
+
import { decode, make as make$1 } from "./Toolkit.mjs";
|
|
5
|
+
import { Default, Denied, byTool, closesWith, make as make$2 } from "./ToolDecision.mjs";
|
|
6
|
+
import { cancelAllPending, findUnansweredCalls, isReconciled } from "./HistoryCheck.mjs";
|
|
7
|
+
import { m as describe } from "../dist-DzeSyeYF.mjs";
|
|
8
|
+
import { n as globalExpect } from "../index.C4tKZ0yW-LuFnBtmu.mjs";
|
|
9
|
+
import { t as import_dist } from "../dist-DNeuh84d.mjs";
|
|
10
|
+
import { t as it } from "../dist-B5K-y4Be.mjs";
|
|
11
|
+
import { Effect, Match, Result, Schema } from "effect";
|
|
12
|
+
//#region src/tool/ToolDecision.test.ts
|
|
13
|
+
const call = (id) => ({
|
|
14
|
+
type: "function_call",
|
|
15
|
+
call_id: id,
|
|
16
|
+
name: "send_email",
|
|
17
|
+
arguments: "{}"
|
|
18
|
+
});
|
|
19
|
+
const Decisions = make$2({
|
|
20
|
+
Approve: { always: Schema.optionalKey(Schema.Boolean) },
|
|
21
|
+
Deny: { reason: Schema.optionalKey(Schema.String) },
|
|
22
|
+
Respond: { output: Schema.Json }
|
|
23
|
+
});
|
|
24
|
+
describe("ToolDecision.make", () => {
|
|
25
|
+
it("groups waiting calls by answer, ignores answers to other calls, keeps the rest unanswered", () => {
|
|
26
|
+
const waiting = [
|
|
27
|
+
call("a"),
|
|
28
|
+
call("b"),
|
|
29
|
+
call("c"),
|
|
30
|
+
call("d"),
|
|
31
|
+
call("e")
|
|
32
|
+
];
|
|
33
|
+
const resolved = Decisions.resolve(waiting, [
|
|
34
|
+
{
|
|
35
|
+
callId: "a",
|
|
36
|
+
decision: Decisions.Approve({ always: true })
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
callId: "b",
|
|
40
|
+
decision: Decisions.Deny({ reason: "spam" })
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
callId: "c",
|
|
44
|
+
decision: Decisions.Respond({ output: { booked: true } })
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
callId: "e",
|
|
48
|
+
decision: {
|
|
49
|
+
_tag: "Edit",
|
|
50
|
+
subject: "hi"
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
callId: "closed-long-ago",
|
|
55
|
+
decision: Decisions.Approve()
|
|
56
|
+
}
|
|
57
|
+
]);
|
|
58
|
+
const approve = {
|
|
59
|
+
_tag: "Approve",
|
|
60
|
+
call: call("a"),
|
|
61
|
+
decision: {
|
|
62
|
+
_tag: "Approve",
|
|
63
|
+
always: true
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const deny = {
|
|
67
|
+
_tag: "Deny",
|
|
68
|
+
call: call("b"),
|
|
69
|
+
decision: {
|
|
70
|
+
_tag: "Deny",
|
|
71
|
+
reason: "spam"
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const respond = {
|
|
75
|
+
_tag: "Respond",
|
|
76
|
+
call: call("c"),
|
|
77
|
+
decision: {
|
|
78
|
+
_tag: "Respond",
|
|
79
|
+
output: { booked: true }
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
globalExpect(resolved).toEqual({
|
|
83
|
+
Approve: [approve],
|
|
84
|
+
Deny: [deny],
|
|
85
|
+
Respond: [respond],
|
|
86
|
+
answered: [
|
|
87
|
+
approve,
|
|
88
|
+
deny,
|
|
89
|
+
respond
|
|
90
|
+
],
|
|
91
|
+
unanswered: [call("d")],
|
|
92
|
+
invalid: [{
|
|
93
|
+
call: call("e"),
|
|
94
|
+
decision: {
|
|
95
|
+
_tag: "Edit",
|
|
96
|
+
subject: "hi"
|
|
97
|
+
}
|
|
98
|
+
}]
|
|
99
|
+
});
|
|
100
|
+
(0, import_dist.expectTypeOf)(resolved.Deny[0].decision).toEqualTypeOf();
|
|
101
|
+
});
|
|
102
|
+
it.effect("decodes answers from the wire and rejects unknown tags", () => Effect.gen(function* () {
|
|
103
|
+
const decode = Schema.decodeUnknownEffect(Decisions.schema);
|
|
104
|
+
globalExpect(yield* decode({
|
|
105
|
+
_tag: "Deny",
|
|
106
|
+
reason: "no"
|
|
107
|
+
})).toEqual(Decisions.Deny({ reason: "no" }));
|
|
108
|
+
const error = yield* Effect.flip(decode({ _tag: "Maybe" }));
|
|
109
|
+
globalExpect(error._tag).toBe("SchemaError");
|
|
110
|
+
}));
|
|
111
|
+
it("type: constructors need their required fields", () => {
|
|
112
|
+
Decisions.Approve();
|
|
113
|
+
Decisions.Respond();
|
|
114
|
+
});
|
|
115
|
+
it("a closing decision comes with the error it names, built from its fields", () => {
|
|
116
|
+
const { Deny } = Default.resolve([call("a"), call("b")], [{
|
|
117
|
+
callId: "a",
|
|
118
|
+
decision: Default.Deny({ reason: "not today" })
|
|
119
|
+
}, {
|
|
120
|
+
callId: "b",
|
|
121
|
+
decision: Default.Deny()
|
|
122
|
+
}]);
|
|
123
|
+
globalExpect(Deny.map((d) => d.error)).toEqual([new Denied({ message: "not today" }), new Denied({ message: "The user declined." })]);
|
|
124
|
+
(0, import_dist.expectTypeOf)(Deny[0].error).toEqualTypeOf();
|
|
125
|
+
});
|
|
126
|
+
it("answered lists every answered call, tagged by its decision, for an exhaustive Match", () => {
|
|
127
|
+
const { answered } = Default.resolve([call("a"), call("b")], [{
|
|
128
|
+
callId: "a",
|
|
129
|
+
decision: Default.Approve()
|
|
130
|
+
}, {
|
|
131
|
+
callId: "b",
|
|
132
|
+
decision: Default.Deny({ reason: "no" })
|
|
133
|
+
}]);
|
|
134
|
+
const handled = answered.map(Match.type().pipe(Match.tagsExhaustive({
|
|
135
|
+
Approve: ({ call }) => `run ${call.call_id}`,
|
|
136
|
+
Deny: ({ call, error }) => `close ${call.call_id}: ${error.message}`
|
|
137
|
+
})));
|
|
138
|
+
globalExpect(handled).toEqual(["run a", "close b: no"]);
|
|
139
|
+
});
|
|
140
|
+
it("type: Match over answered must handle every decision", () => {
|
|
141
|
+
const { answered } = Default.resolve([], []);
|
|
142
|
+
answered.map(Match.type().pipe(Match.tagsExhaustive({ Approve: () => "run" })));
|
|
143
|
+
});
|
|
144
|
+
it("a call answered twice: the same answer counts once, two different ones are invalid", () => {
|
|
145
|
+
const resolved = Default.resolve([call("a"), call("b")], [
|
|
146
|
+
{
|
|
147
|
+
callId: "a",
|
|
148
|
+
decision: Default.Approve()
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
callId: "a",
|
|
152
|
+
decision: Default.Approve()
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
callId: "b",
|
|
156
|
+
decision: Default.Approve()
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
callId: "b",
|
|
160
|
+
decision: Default.Deny()
|
|
161
|
+
}
|
|
162
|
+
]);
|
|
163
|
+
globalExpect(resolved.answered.map((e) => e.call.call_id)).toEqual(["a"]);
|
|
164
|
+
globalExpect(resolved.invalid).toEqual([{
|
|
165
|
+
call: call("b"),
|
|
166
|
+
decision: [{ _tag: "Approve" }, { _tag: "Deny" }]
|
|
167
|
+
}]);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
describe("ToolDecision.byTool", () => {
|
|
171
|
+
class Escalated extends Schema.TaggedError()("Escalated", { team: Schema.Literals(["finance", "legal"]) }) {}
|
|
172
|
+
const Refund = make$2({
|
|
173
|
+
Approve: {},
|
|
174
|
+
Reduce: { amount: Schema.Number },
|
|
175
|
+
Escalate: closesWith({ team: Schema.Literals(["finance", "legal"]) }, ({ team }) => new Escalated({ team }))
|
|
176
|
+
});
|
|
177
|
+
const Email = make$2({
|
|
178
|
+
Approve: {},
|
|
179
|
+
Edit: { subject: Schema.String },
|
|
180
|
+
Deny: Default.variants.Deny
|
|
181
|
+
});
|
|
182
|
+
const Decisions = byTool({
|
|
183
|
+
issue_refund: Refund,
|
|
184
|
+
send_email: Email
|
|
185
|
+
}, { fallback: Default });
|
|
186
|
+
const named = (name, id) => ({
|
|
187
|
+
type: "function_call",
|
|
188
|
+
call_id: id,
|
|
189
|
+
name,
|
|
190
|
+
arguments: "{}"
|
|
191
|
+
});
|
|
192
|
+
it("resolves each call with its own tool's decisions, the rest with the fallback", () => {
|
|
193
|
+
const waiting = [
|
|
194
|
+
named("issue_refund", "r1"),
|
|
195
|
+
named("issue_refund", "r2"),
|
|
196
|
+
named("send_email", "e1"),
|
|
197
|
+
named("helpdesk__write_ticket", "h1"),
|
|
198
|
+
named("send_email", "e2")
|
|
199
|
+
];
|
|
200
|
+
const resolved = Decisions.resolve(waiting, [
|
|
201
|
+
{
|
|
202
|
+
callId: "r1",
|
|
203
|
+
decision: Refund.Reduce({ amount: 40 })
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
callId: "r2",
|
|
207
|
+
decision: Email.Edit({ subject: "a refund edited like an email" })
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
callId: "e1",
|
|
211
|
+
decision: Email.Edit({ subject: "Your refund" })
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
callId: "h1",
|
|
215
|
+
decision: Default.Deny({ reason: "read only today" })
|
|
216
|
+
}
|
|
217
|
+
]);
|
|
218
|
+
globalExpect(resolved.issue_refund.Reduce.map((r) => r.decision.amount)).toEqual([40]);
|
|
219
|
+
globalExpect(resolved.send_email.Edit.map((r) => r.call.call_id)).toEqual(["e1"]);
|
|
220
|
+
globalExpect(resolved.fallback.Deny.map((r) => r.error)).toEqual([new Denied({ message: "read only today" })]);
|
|
221
|
+
globalExpect(resolved.invalid.map((i) => i.call.call_id)).toEqual(["r2"]);
|
|
222
|
+
globalExpect(resolved.unanswered.map((c) => c.call_id)).toEqual(["e2"]);
|
|
223
|
+
});
|
|
224
|
+
it("without a fallback, a call nobody takes decisions for can't be answered", () => {
|
|
225
|
+
const resolved = byTool({ issue_refund: Refund }).resolve([named("delete_ticket", "d1"), named("delete_ticket", "d2")], [{
|
|
226
|
+
callId: "d1",
|
|
227
|
+
decision: Default.Approve()
|
|
228
|
+
}]);
|
|
229
|
+
globalExpect(resolved.invalid.map((i) => i.call.call_id)).toEqual(["d1"]);
|
|
230
|
+
globalExpect(resolved.unanswered.map((c) => c.call_id)).toEqual(["d2"]);
|
|
231
|
+
});
|
|
232
|
+
it.effect("decodes any set's answer from the wire", () => Effect.gen(function* () {
|
|
233
|
+
const decode = Schema.decodeUnknownEffect(Decisions.schema);
|
|
234
|
+
globalExpect(yield* decode({
|
|
235
|
+
_tag: "Reduce",
|
|
236
|
+
amount: 5
|
|
237
|
+
})).toEqual(Refund.Reduce({ amount: 5 }));
|
|
238
|
+
globalExpect(yield* decode({
|
|
239
|
+
_tag: "Edit",
|
|
240
|
+
subject: "s"
|
|
241
|
+
})).toEqual(Email.Edit({ subject: "s" }));
|
|
242
|
+
}));
|
|
243
|
+
it("type: two sets may share a tag only with the same fields", () => {
|
|
244
|
+
const Always = make$2({ Approve: { always: Schema.Boolean } });
|
|
245
|
+
byTool({
|
|
246
|
+
issue_refund: Refund,
|
|
247
|
+
send_email: Always
|
|
248
|
+
});
|
|
249
|
+
const Maybe = make$2({ Approve: { always: Schema.optionalKey(Schema.Boolean) } });
|
|
250
|
+
byTool({ issue_refund: Refund }, { fallback: Maybe });
|
|
251
|
+
byTool({
|
|
252
|
+
issue_refund: Refund,
|
|
253
|
+
send_email: Email
|
|
254
|
+
}, { fallback: Default });
|
|
255
|
+
});
|
|
256
|
+
it("type: an answer takes its tool's decisions; groups and errors are typed per tool", () => {
|
|
257
|
+
const refund = {
|
|
258
|
+
callId: "r1",
|
|
259
|
+
name: "issue_refund"
|
|
260
|
+
};
|
|
261
|
+
Decisions.answer(refund, Refund.Reduce({ amount: 1 }));
|
|
262
|
+
Decisions.answer(refund, Email.Edit({ subject: "s" }));
|
|
263
|
+
Decisions.answer({
|
|
264
|
+
callId: "h1",
|
|
265
|
+
name: "helpdesk__write_ticket"
|
|
266
|
+
}, Default.Approve());
|
|
267
|
+
(0, import_dist.expectTypeOf)().toEqualTypeOf();
|
|
268
|
+
(0, import_dist.expectTypeOf)().toEqualTypeOf();
|
|
269
|
+
(0, import_dist.expectTypeOf)().toEqualTypeOf();
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
describe("closing waiting calls", () => {
|
|
273
|
+
const Sent = Schema.Struct({ id: Schema.String });
|
|
274
|
+
const sendEmail = make({
|
|
275
|
+
name: "send_email",
|
|
276
|
+
description: "",
|
|
277
|
+
inputSchema: noInput,
|
|
278
|
+
outputSchema: Sent,
|
|
279
|
+
run: () => Effect.succeed({ id: "m1" })
|
|
280
|
+
});
|
|
281
|
+
const kit = make$1(sendEmail);
|
|
282
|
+
it.effect("a user-supplied result reads back like the tool's own output", () => Effect.gen(function* () {
|
|
283
|
+
const answered = yield* success(call("b"), Sent, { id: "u1" });
|
|
284
|
+
globalExpect(yield* decode(kit, "send_email", answered)).toEqual(Result.succeed({ id: "u1" }));
|
|
285
|
+
}));
|
|
286
|
+
it.effect("a denied call carries the tagged error the model sees", () => Effect.gen(function* () {
|
|
287
|
+
const denied = new Denied({ message: "spam" });
|
|
288
|
+
globalExpect(yield* failure(call("a"), Denied, denied)).toEqual({
|
|
289
|
+
type: "function_call_output",
|
|
290
|
+
call_id: "a",
|
|
291
|
+
output: [{
|
|
292
|
+
type: "input_json",
|
|
293
|
+
value: {
|
|
294
|
+
_tag: "Denied",
|
|
295
|
+
message: "spam"
|
|
296
|
+
}
|
|
297
|
+
}],
|
|
298
|
+
isError: true
|
|
299
|
+
});
|
|
300
|
+
}));
|
|
301
|
+
});
|
|
302
|
+
describe("HistoryCheck", () => {
|
|
303
|
+
it.effect("closes every unanswered call with Cancelled, so history is submittable", () => Effect.gen(function* () {
|
|
304
|
+
const history = [
|
|
305
|
+
userText("send both"),
|
|
306
|
+
call("a"),
|
|
307
|
+
call("b"),
|
|
308
|
+
toolCallOutput("a", "sent")
|
|
309
|
+
];
|
|
310
|
+
globalExpect(findUnansweredCalls(history)).toEqual([call("b")]);
|
|
311
|
+
const closing = yield* cancelAllPending(history, "user moved on");
|
|
312
|
+
globalExpect(closing).toEqual([{
|
|
313
|
+
type: "function_call_output",
|
|
314
|
+
call_id: "b",
|
|
315
|
+
output: [{
|
|
316
|
+
type: "input_json",
|
|
317
|
+
value: {
|
|
318
|
+
_tag: "Cancelled",
|
|
319
|
+
message: "user moved on"
|
|
320
|
+
}
|
|
321
|
+
}],
|
|
322
|
+
isError: true
|
|
323
|
+
}]);
|
|
324
|
+
globalExpect(isReconciled([...history, ...closing])).toBe(true);
|
|
325
|
+
}));
|
|
326
|
+
});
|
|
327
|
+
//#endregion
|
|
328
|
+
export {};
|
|
329
|
+
|
|
330
|
+
//# sourceMappingURL=ToolDecision.test.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ToolDecision.test.mjs","names":["ToolDecision.make","ToolDecision.Denied","ToolDecision.closesWith","ToolDecision.byTool","ToolDecision.Default","Tool.make","Tool.noInput","Toolkit.make","ToolOutput.success","Toolkit.decode","ToolOutput.failure","Items.userText","Items.toolCallOutput","HistoryCheck.findUnansweredCalls","HistoryCheck.cancelAllPending","HistoryCheck.isReconciled"],"sources":["../../src/tool/ToolDecision.test.ts"],"sourcesContent":["import { describe, expect, expectTypeOf, it } from \"@effect/vitest\"\nimport { Effect, Match, Result, Schema } from \"effect\"\nimport * as Items from \"../domain/Items.js\"\nimport * as ToolDecision from \"./ToolDecision.js\"\nimport * as HistoryCheck from \"./HistoryCheck.js\"\nimport * as Tool from \"./Tool.js\"\nimport * as Toolkit from \"./Toolkit.js\"\nimport * as ToolOutput from \"./ToolOutput.js\"\n\nconst call = (id: string): Items.ToolCall => ({\n type: \"function_call\",\n call_id: id,\n name: \"send_email\",\n arguments: \"{}\",\n})\n\nconst Decisions = ToolDecision.make({\n Approve: { always: Schema.optionalKey(Schema.Boolean) },\n Deny: { reason: Schema.optionalKey(Schema.String) },\n Respond: { output: Schema.Json },\n})\n\ndescribe(\"ToolDecision.make\", () => {\n it(\"groups waiting calls by answer, ignores answers to other calls, keeps the rest unanswered\", () => {\n const waiting = [call(\"a\"), call(\"b\"), call(\"c\"), call(\"d\"), call(\"e\")]\n const resolved = Decisions.resolve(waiting, [\n { callId: \"a\", decision: Decisions.Approve({ always: true }) },\n { callId: \"b\", decision: Decisions.Deny({ reason: \"spam\" }) },\n { callId: \"c\", decision: Decisions.Respond({ output: { booked: true } }) },\n { callId: \"e\", decision: { _tag: \"Edit\", subject: \"hi\" } },\n { callId: \"closed-long-ago\", decision: Decisions.Approve() },\n ])\n const approve = {\n _tag: \"Approve\",\n call: call(\"a\"),\n decision: { _tag: \"Approve\", always: true },\n }\n const deny = { _tag: \"Deny\", call: call(\"b\"), decision: { _tag: \"Deny\", reason: \"spam\" } }\n const respond = {\n _tag: \"Respond\",\n call: call(\"c\"),\n decision: { _tag: \"Respond\", output: { booked: true } },\n }\n expect(resolved).toEqual({\n Approve: [approve],\n Deny: [deny],\n Respond: [respond],\n answered: [approve, deny, respond],\n unanswered: [call(\"d\")],\n // An answer this definition doesn't accept is reported, not dropped.\n invalid: [{ call: call(\"e\"), decision: { _tag: \"Edit\", subject: \"hi\" } }],\n })\n expectTypeOf(resolved.Deny[0]!.decision).toEqualTypeOf<{\n readonly _tag: \"Deny\"\n readonly reason?: string\n }>()\n })\n\n it.effect(\"decodes answers from the wire and rejects unknown tags\", () =>\n Effect.gen(function* () {\n const decode = Schema.decodeUnknownEffect(Decisions.schema)\n expect(yield* decode({ _tag: \"Deny\", reason: \"no\" })).toEqual(\n Decisions.Deny({ reason: \"no\" }),\n )\n const error = yield* Effect.flip(decode({ _tag: \"Maybe\" }))\n expect(error._tag).toBe(\"SchemaError\")\n }),\n )\n\n it(\"type: constructors need their required fields\", () => {\n Decisions.Approve()\n // @ts-expect-error Respond requires `output`\n Decisions.Respond()\n })\n\n it(\"a closing decision comes with the error it names, built from its fields\", () => {\n const { Deny } = ToolDecision.Default.resolve(\n [call(\"a\"), call(\"b\")],\n [\n { callId: \"a\", decision: ToolDecision.Default.Deny({ reason: \"not today\" }) },\n { callId: \"b\", decision: ToolDecision.Default.Deny() },\n ],\n )\n expect(Deny.map((d) => d.error)).toEqual([\n new ToolDecision.Denied({ message: \"not today\" }),\n new ToolDecision.Denied({ message: \"The user declined.\" }),\n ])\n expectTypeOf(Deny[0]!.error).toEqualTypeOf<ToolDecision.Denied>()\n })\n\n it(\"answered lists every answered call, tagged by its decision, for an exhaustive Match\", () => {\n const { answered } = ToolDecision.Default.resolve(\n [call(\"a\"), call(\"b\")],\n [\n { callId: \"a\", decision: ToolDecision.Default.Approve() },\n { callId: \"b\", decision: ToolDecision.Default.Deny({ reason: \"no\" }) },\n ],\n )\n const handled = answered.map(\n Match.type<(typeof answered)[number]>().pipe(\n Match.tagsExhaustive({\n Approve: ({ call }) => `run ${call.call_id}`,\n Deny: ({ call, error }) => `close ${call.call_id}: ${error.message}`,\n }),\n ),\n )\n expect(handled).toEqual([\"run a\", \"close b: no\"])\n })\n\n it(\"type: Match over answered must handle every decision\", () => {\n const { answered } = ToolDecision.Default.resolve([], [])\n answered.map(\n Match.type<(typeof answered)[number]>().pipe(\n // @ts-expect-error Deny is not handled\n Match.tagsExhaustive({ Approve: () => \"run\" }),\n ),\n )\n })\n\n it(\"a call answered twice: the same answer counts once, two different ones are invalid\", () => {\n const resolved = ToolDecision.Default.resolve(\n [call(\"a\"), call(\"b\")],\n [\n { callId: \"a\", decision: ToolDecision.Default.Approve() },\n { callId: \"a\", decision: ToolDecision.Default.Approve() },\n { callId: \"b\", decision: ToolDecision.Default.Approve() },\n { callId: \"b\", decision: ToolDecision.Default.Deny() },\n ],\n )\n expect(resolved.answered.map((e) => e.call.call_id)).toEqual([\"a\"])\n expect(resolved.invalid).toEqual([\n { call: call(\"b\"), decision: [{ _tag: \"Approve\" }, { _tag: \"Deny\" }] },\n ])\n })\n})\n\ndescribe(\"ToolDecision.byTool\", () => {\n class Escalated extends Schema.TaggedError<Escalated>()(\"Escalated\", {\n team: Schema.Literals([\"finance\", \"legal\"]),\n }) {}\n\n const Refund = ToolDecision.make({\n Approve: {},\n Reduce: { amount: Schema.Number },\n Escalate: ToolDecision.closesWith(\n { team: Schema.Literals([\"finance\", \"legal\"]) },\n ({ team }) => new Escalated({ team }),\n ),\n })\n const Email = ToolDecision.make({\n Approve: {},\n Edit: { subject: Schema.String },\n // A closing decision is reusable across sets.\n Deny: ToolDecision.Default.variants.Deny,\n })\n const Decisions = ToolDecision.byTool(\n { issue_refund: Refund, send_email: Email },\n { fallback: ToolDecision.Default },\n )\n\n const named = (name: string, id: string): Items.ToolCall => ({\n type: \"function_call\",\n call_id: id,\n name,\n arguments: \"{}\",\n })\n\n it(\"resolves each call with its own tool's decisions, the rest with the fallback\", () => {\n const waiting = [\n named(\"issue_refund\", \"r1\"),\n named(\"issue_refund\", \"r2\"),\n named(\"send_email\", \"e1\"),\n named(\"helpdesk__write_ticket\", \"h1\"),\n named(\"send_email\", \"e2\"),\n ]\n const resolved = Decisions.resolve(waiting, [\n { callId: \"r1\", decision: Refund.Reduce({ amount: 40 }) },\n { callId: \"r2\", decision: Email.Edit({ subject: \"a refund edited like an email\" }) },\n { callId: \"e1\", decision: Email.Edit({ subject: \"Your refund\" }) },\n { callId: \"h1\", decision: ToolDecision.Default.Deny({ reason: \"read only today\" }) },\n ])\n expect(resolved.issue_refund.Reduce.map((r) => r.decision.amount)).toEqual([40])\n expect(resolved.send_email.Edit.map((r) => r.call.call_id)).toEqual([\"e1\"])\n expect(resolved.fallback.Deny.map((r) => r.error)).toEqual([\n new ToolDecision.Denied({ message: \"read only today\" }),\n ])\n expect(resolved.invalid.map((i) => i.call.call_id)).toEqual([\"r2\"])\n expect(resolved.unanswered.map((c) => c.call_id)).toEqual([\"e2\"])\n })\n\n it(\"without a fallback, a call nobody takes decisions for can't be answered\", () => {\n const only = ToolDecision.byTool({ issue_refund: Refund })\n const resolved = only.resolve(\n [named(\"delete_ticket\", \"d1\"), named(\"delete_ticket\", \"d2\")],\n [{ callId: \"d1\", decision: ToolDecision.Default.Approve() }],\n )\n expect(resolved.invalid.map((i) => i.call.call_id)).toEqual([\"d1\"])\n expect(resolved.unanswered.map((c) => c.call_id)).toEqual([\"d2\"])\n })\n\n it.effect(\"decodes any set's answer from the wire\", () =>\n Effect.gen(function* () {\n const decode = Schema.decodeUnknownEffect(Decisions.schema)\n expect(yield* decode({ _tag: \"Reduce\", amount: 5 })).toEqual(Refund.Reduce({ amount: 5 }))\n expect(yield* decode({ _tag: \"Edit\", subject: \"s\" })).toEqual(Email.Edit({ subject: \"s\" }))\n }),\n )\n\n it(\"type: two sets may share a tag only with the same fields\", () => {\n const Always = ToolDecision.make({ Approve: { always: Schema.Boolean } })\n // @ts-expect-error \"Approve\" has different fields in issue_refund and send_email\n ToolDecision.byTool({ issue_refund: Refund, send_email: Always })\n // An optional extra field is a conflict too: the union would drop it.\n const Maybe = ToolDecision.make({ Approve: { always: Schema.optionalKey(Schema.Boolean) } })\n // @ts-expect-error \"Approve\" has different fields\n ToolDecision.byTool({ issue_refund: Refund }, { fallback: Maybe })\n // Same fields (or a reused variant) are fine.\n ToolDecision.byTool(\n { issue_refund: Refund, send_email: Email },\n { fallback: ToolDecision.Default },\n )\n })\n\n it(\"type: an answer takes its tool's decisions; groups and errors are typed per tool\", () => {\n const refund = { callId: \"r1\", name: \"issue_refund\" } as const\n Decisions.answer(refund, Refund.Reduce({ amount: 1 }))\n // @ts-expect-error an email decision for a refund call\n Decisions.answer(refund, Email.Edit({ subject: \"s\" }))\n Decisions.answer(\n { callId: \"h1\", name: \"helpdesk__write_ticket\" },\n ToolDecision.Default.Approve(),\n )\n\n type Resolved = ToolDecision.ResolvedByTool<typeof Decisions.tools, typeof ToolDecision.Default>\n expectTypeOf<Resolved[\"issue_refund\"][\"Escalate\"][number][\"error\"]>().toEqualTypeOf<Escalated>()\n expectTypeOf<\n Resolved[\"send_email\"][\"Edit\"][number][\"decision\"][\"subject\"]\n >().toEqualTypeOf<string>()\n expectTypeOf<\n Resolved[\"send_email\"][\"Deny\"][number][\"error\"]\n >().toEqualTypeOf<ToolDecision.Denied>()\n })\n})\n\ndescribe(\"closing waiting calls\", () => {\n const Sent = Schema.Struct({ id: Schema.String })\n const sendEmail = Tool.make({\n name: \"send_email\",\n description: \"\",\n inputSchema: Tool.noInput,\n outputSchema: Sent,\n run: () => Effect.succeed({ id: \"m1\" }),\n })\n const kit = Toolkit.make(sendEmail)\n\n it.effect(\"a user-supplied result reads back like the tool's own output\", () =>\n Effect.gen(function* () {\n const answered = yield* ToolOutput.success(call(\"b\"), Sent, { id: \"u1\" })\n expect(yield* Toolkit.decode(kit, \"send_email\", answered)).toEqual(\n Result.succeed({ id: \"u1\" }),\n )\n }),\n )\n\n it.effect(\"a denied call carries the tagged error the model sees\", () =>\n Effect.gen(function* () {\n const denied = new ToolDecision.Denied({ message: \"spam\" })\n expect(yield* ToolOutput.failure(call(\"a\"), ToolDecision.Denied, denied)).toEqual({\n type: \"function_call_output\",\n call_id: \"a\",\n output: [{ type: \"input_json\", value: { _tag: \"Denied\", message: \"spam\" } }],\n isError: true,\n })\n }),\n )\n})\n\ndescribe(\"HistoryCheck\", () => {\n it.effect(\"closes every unanswered call with Cancelled, so history is submittable\", () =>\n Effect.gen(function* () {\n const history: ReadonlyArray<Items.HistoryItem> = [\n Items.userText(\"send both\"),\n call(\"a\"),\n call(\"b\"),\n Items.toolCallOutput(\"a\", \"sent\"),\n ]\n expect(HistoryCheck.findUnansweredCalls(history)).toEqual([call(\"b\")])\n\n const closing = yield* HistoryCheck.cancelAllPending(history, \"user moved on\")\n expect(closing).toEqual([\n {\n type: \"function_call_output\",\n call_id: \"b\",\n output: [{ type: \"input_json\", value: { _tag: \"Cancelled\", message: \"user moved on\" } }],\n isError: true,\n },\n ])\n expect(HistoryCheck.isReconciled([...history, ...closing])).toBe(true)\n }),\n )\n})\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
1
|
+
import { n as Partial, r as ToolEvent, t as Output } from "../ToolEvent-9inLx5cI.mjs";
|
|
2
|
+
export { Output, Partial, ToolEvent };
|
package/dist/tool/ToolEvent.mjs
CHANGED
|
@@ -1,39 +1,7 @@
|
|
|
1
1
|
import { n as __exportAll } from "../rolldown-runtime-EAsCxrDo.mjs";
|
|
2
|
-
import { Data } from "effect";
|
|
3
2
|
//#region src/tool/ToolEvent.ts
|
|
4
|
-
|
|
5
|
-
* The event type emitted while handling tool calls.
|
|
6
|
-
*
|
|
7
|
-
* - ApprovalRequested : gated calls waiting for approval
|
|
8
|
-
* - Progress : per-element passthrough from a streaming tool's run
|
|
9
|
-
* - Output : terminal result (carries a structured ToolResult)
|
|
10
|
-
*
|
|
11
|
-
* Recipes thread `ToolEvent.Output.result` through `continueWithResults` and
|
|
12
|
-
* apply `toToolCallOutput` when appending to history.
|
|
13
|
-
*
|
|
14
|
-
* Demux note: `Progress` carries `call_id` / `tool` at the top level, while
|
|
15
|
-
* `Output` nests them inside `result` (the `ToolResult` already carries both).
|
|
16
|
-
* A consumer grouping events by call reads `e.call_id` for `Progress` and
|
|
17
|
-
* `e.result.call_id` for `Output`.
|
|
18
|
-
*/
|
|
19
|
-
var ToolEvent_exports = /* @__PURE__ */ __exportAll({
|
|
20
|
-
ToolEvent: () => ToolEvent,
|
|
21
|
-
isApprovalRequested: () => isApprovalRequested,
|
|
22
|
-
isOutput: () => isOutput,
|
|
23
|
-
isProgress: () => isProgress
|
|
24
|
-
});
|
|
25
|
-
/**
|
|
26
|
-
* Namespace of constructors, type guards, and matchers for `ToolEvent`,
|
|
27
|
-
* provided by `Data.taggedEnum`. Use `ToolEvent.Output({ result })` to build
|
|
28
|
-
* an event, `ToolEvent.$is("Output")` for type narrowing,
|
|
29
|
-
* `ToolEvent.$match({ ApprovalRequested, Progress, Output })` for
|
|
30
|
-
* exhaustive pattern matching.
|
|
31
|
-
*/
|
|
32
|
-
const ToolEvent = Data.taggedEnum();
|
|
33
|
-
const isApprovalRequested = ToolEvent.$is("ApprovalRequested");
|
|
34
|
-
const isProgress = ToolEvent.$is("Progress");
|
|
35
|
-
const isOutput = ToolEvent.$is("Output");
|
|
3
|
+
var ToolEvent_exports = /* @__PURE__ */ __exportAll({});
|
|
36
4
|
//#endregion
|
|
37
|
-
export {
|
|
5
|
+
export { ToolEvent_exports as t };
|
|
38
6
|
|
|
39
7
|
//# sourceMappingURL=ToolEvent.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ToolEvent.mjs","names":[],"sources":["../../src/tool/ToolEvent.ts"],"sourcesContent":["/**\n *
|
|
1
|
+
{"version":3,"file":"ToolEvent.mjs","names":[],"sources":["../../src/tool/ToolEvent.ts"],"sourcesContent":["/**\n * What `Toolkit.run` emits, typed per tool: `tool` is the discriminant, so\n * `e.tool === \"search\"` narrows `value` / `result` to that tool's types. Values\n * are decoded (the tool's own types); `Toolkit.output` encodes them for history.\n */\nimport type { Result } from \"effect\"\n\n/** An intermediate value of a `Stream` tool. */\nexport type Partial<Name extends string = string, Output = unknown> = {\n readonly _tag: \"Partial\"\n readonly call_id: string\n readonly tool: Name\n readonly value: Output\n}\n\n/** The call ended: `Success` with its value, or `Failure` with a declared or core error. */\nexport type Output<Name extends string = string, Out = unknown, E = unknown> = {\n readonly _tag: \"Output\"\n readonly call_id: string\n readonly tool: Name\n readonly result: Result.Result<Out, E>\n}\n\nexport type ToolEvent<Name extends string = string, Out = unknown, E = unknown> =\n | Partial<Name, Out>\n | Output<Name, Out, E>\n"],"mappings":""}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { _ as success, a as PartJson, b as valueParts, c as UnknownTool, d as failure, f as isTextual, g as single, h as parts, i as OutputInvalid, l as ValueParts, m as partText, n as CoreError, o as RunError, p as make, r as InputInvalid, t as Cancelled, u as encoded, v as text, y as value } from "../ToolOutput-BNTIQgGB.mjs";
|
|
2
|
+
export { Cancelled, CoreError, InputInvalid, OutputInvalid, PartJson, RunError, UnknownTool, ValueParts, encoded, failure, isTextual, make, partText, parts, single, success, text, value, valueParts };
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { n as __exportAll } from "../rolldown-runtime-EAsCxrDo.mjs";
|
|
2
|
+
import { FileSourceJson } from "../domain/File.mjs";
|
|
3
|
+
import { ImageSourceJson } from "../domain/Image.mjs";
|
|
4
|
+
import { InputJson, InputText, file, image, json, text as text$1 } from "../domain/Items.mjs";
|
|
5
|
+
import { Media, Value, file as file$1, image as image$1, isMedia } from "./Content.mjs";
|
|
6
|
+
import { Array as Array$1, Effect, Match, Option, Predicate, Schema, SchemaTransformation } from "effect";
|
|
7
|
+
//#region src/tool/ToolOutput.ts
|
|
8
|
+
/**
|
|
9
|
+
* Tool values to history parts, and back. One rule, top level only:
|
|
10
|
+
* string → text · media → its part · array with media → one part per element ·
|
|
11
|
+
* anything else → one JSON value.
|
|
12
|
+
* Every value goes through a schema (the tool's `outputSchema`, else `Content.Value`):
|
|
13
|
+
* the decoded side decides the shape and supplies media, the encoded side the JSON.
|
|
14
|
+
*/
|
|
15
|
+
var ToolOutput_exports = /* @__PURE__ */ __exportAll({
|
|
16
|
+
Cancelled: () => Cancelled,
|
|
17
|
+
CoreError: () => CoreError,
|
|
18
|
+
InputInvalid: () => InputInvalid,
|
|
19
|
+
OutputInvalid: () => OutputInvalid,
|
|
20
|
+
PartJson: () => PartJson,
|
|
21
|
+
RunError: () => RunError,
|
|
22
|
+
UnknownTool: () => UnknownTool,
|
|
23
|
+
ValueParts: () => ValueParts,
|
|
24
|
+
encoded: () => encoded,
|
|
25
|
+
failure: () => failure,
|
|
26
|
+
isTextual: () => isTextual,
|
|
27
|
+
make: () => make,
|
|
28
|
+
partText: () => partText,
|
|
29
|
+
parts: () => parts,
|
|
30
|
+
single: () => single,
|
|
31
|
+
success: () => success,
|
|
32
|
+
text: () => text,
|
|
33
|
+
value: () => value,
|
|
34
|
+
valueParts: () => valueParts
|
|
35
|
+
});
|
|
36
|
+
const toPart = Match.type().pipe(Match.tagsExhaustive({
|
|
37
|
+
Image: (m) => image(m.source),
|
|
38
|
+
File: (m) => file(m.source, m.filename)
|
|
39
|
+
}));
|
|
40
|
+
const fromPart = Match.type().pipe(Match.discriminatorsExhaustive("type")({
|
|
41
|
+
input_text: (p) => p.text,
|
|
42
|
+
input_json: (p) => p.value,
|
|
43
|
+
input_image: (p) => image$1(p.source),
|
|
44
|
+
input_file: (p) => file$1(p.source, p.filename)
|
|
45
|
+
}));
|
|
46
|
+
const isMixed = (u) => Array.isArray(u) && u.some(isMedia);
|
|
47
|
+
const zipMixed = (decoded, encoded) => Array.isArray(encoded) && encoded.length === decoded.length ? Array$1.zipWith(decoded, encoded, (d, e) => Match.value(d).pipe(Match.when(isMedia, toPart), Match.orElse(() => Predicate.isString(e) ? text$1(e) : json(e)))) : [json(encoded)];
|
|
48
|
+
/** `decoded` decides the shape and supplies media; `encoded` supplies the JSON. */
|
|
49
|
+
const parts = (decoded, encoded) => Match.value(decoded).pipe(Match.when(Predicate.isString, (text) => [text$1(text)]), Match.when(isMedia, (media) => [toPart(media)]), Match.when(isMixed, (values) => zipMixed(values, encoded)), Match.orElse(() => [json(encoded)]));
|
|
50
|
+
/** A value, encoded by `schema`, as the call's output. */
|
|
51
|
+
const success = (call, schema, value) => Effect.map(Schema.encodeEffect(schema)(value), (encoded) => ({
|
|
52
|
+
type: "function_call_output",
|
|
53
|
+
call_id: call.call_id,
|
|
54
|
+
output: parts(value, encoded)
|
|
55
|
+
}));
|
|
56
|
+
/** An error, encoded by `schema`, as the call's output, flagged `isError`. */
|
|
57
|
+
const failure = (call, schema, error) => Effect.map(Schema.encodeEffect(schema)(error), (encoded) => ({
|
|
58
|
+
type: "function_call_output",
|
|
59
|
+
call_id: call.call_id,
|
|
60
|
+
output: [json(encoded)],
|
|
61
|
+
isError: true
|
|
62
|
+
}));
|
|
63
|
+
/** Any `Content.Value` as the call's output, for calls the app closes itself. */
|
|
64
|
+
const make = (call, value) => success(call, Value, value);
|
|
65
|
+
/** One value as itself, several (or none) as the array: how parts read back as a value. */
|
|
66
|
+
const single = (values) => Array$1.isReadonlyArrayNonEmpty(values) && values.length === 1 ? Array$1.headNonEmpty(values) : values;
|
|
67
|
+
/** A text or JSON part. */
|
|
68
|
+
const isTextual = (p) => p.type === "input_text" || p.type === "input_json";
|
|
69
|
+
/** A text part verbatim, a JSON part serialized. */
|
|
70
|
+
const partText = (p) => p.type === "input_text" ? p.text : JSON.stringify(p.value);
|
|
71
|
+
/**
|
|
72
|
+
* The parts as one string joined by newlines, for wires (or wire paths) whose
|
|
73
|
+
* tool outputs carry text only. `None` when a part is media.
|
|
74
|
+
*/
|
|
75
|
+
const text = (output) => Array$1.every(output, isTextual) ? Option.some(output.map(partText).join("\n")) : Option.none();
|
|
76
|
+
/** Parts back to a `Content.Value`, read from the parts' own types. */
|
|
77
|
+
const value = (output) => single(output.map(fromPart));
|
|
78
|
+
/** Parts back to the encoded form an `outputSchema` decodes (media re-encoded). */
|
|
79
|
+
const encoded = (output) => Effect.forEach(output.map(fromPart), (v) => isMedia(v) ? Schema.encodeEffect(Media)(v) : Effect.succeed(v)).pipe(Effect.map(single));
|
|
80
|
+
const isJson = Schema.is(Schema.Json);
|
|
81
|
+
const elementPart = Match.type().pipe(Match.when(isMedia, toPart), Match.when(Predicate.isString, text$1), Match.orElse(json));
|
|
82
|
+
/** A `Content.Value` as history parts, by the same rule as `parts` (its JSON is itself). */
|
|
83
|
+
const valueParts = (v) => Match.value(v).pipe(Match.when(Predicate.isString, (text) => [text$1(text)]), Match.when(isMedia, (media) => [toPart(media)]), Match.when(isMixed, (values) => values.map(elementPart)), Match.when(isJson, (json$1) => [json(json$1)]), Match.orElse(() => []));
|
|
84
|
+
/** A history part with JSON sources (bytes as base64), as it travels to a UI. */
|
|
85
|
+
const PartJson = Schema.Union([
|
|
86
|
+
InputText,
|
|
87
|
+
InputJson,
|
|
88
|
+
Schema.Struct({
|
|
89
|
+
type: Schema.Literal("input_image"),
|
|
90
|
+
source: ImageSourceJson
|
|
91
|
+
}),
|
|
92
|
+
Schema.Struct({
|
|
93
|
+
type: Schema.Literal("input_file"),
|
|
94
|
+
source: FileSourceJson,
|
|
95
|
+
filename: Schema.optionalKey(Schema.String)
|
|
96
|
+
})
|
|
97
|
+
]);
|
|
98
|
+
/**
|
|
99
|
+
* A `Content.Value` on the wire to a UI: typed parts instead of its plain JSON,
|
|
100
|
+
* so JSON shaped like `{ _tag: "Image", source }` can't come back as an image.
|
|
101
|
+
*/
|
|
102
|
+
const ValueParts = Schema.Array(PartJson).pipe(Schema.decodeTo(Schema.toType(Value), SchemaTransformation.transform({
|
|
103
|
+
decode: value,
|
|
104
|
+
encode: valueParts
|
|
105
|
+
})));
|
|
106
|
+
/** The model's arguments are not JSON or fail `inputSchema`. */
|
|
107
|
+
var InputInvalid = class extends Schema.TaggedError("@betalyra/effect-uai/InputInvalid")("InputInvalid", { message: Schema.String }) {};
|
|
108
|
+
/** `run` returned a value its `outputSchema` rejects: a bug in the tool. */
|
|
109
|
+
var OutputInvalid = class extends Schema.TaggedError("@betalyra/effect-uai/OutputInvalid")("OutputInvalid", { message: Schema.String }) {};
|
|
110
|
+
/** No local tool by that name (hallucinated, removed, or a declared or provider tool). */
|
|
111
|
+
var UnknownTool = class extends Schema.TaggedError("@betalyra/effect-uai/UnknownTool")("UnknownTool", { message: Schema.String }) {};
|
|
112
|
+
/** The call was left unanswered and closed before the next model call. */
|
|
113
|
+
var Cancelled = class extends Schema.TaggedError("@betalyra/effect-uai/Cancelled")("Cancelled", { message: Schema.String }) {};
|
|
114
|
+
const RunError = Schema.Union([
|
|
115
|
+
InputInvalid,
|
|
116
|
+
OutputInvalid,
|
|
117
|
+
UnknownTool
|
|
118
|
+
]);
|
|
119
|
+
const CoreError = Schema.Union([
|
|
120
|
+
InputInvalid,
|
|
121
|
+
OutputInvalid,
|
|
122
|
+
UnknownTool,
|
|
123
|
+
Cancelled
|
|
124
|
+
]);
|
|
125
|
+
//#endregion
|
|
126
|
+
export { Cancelled, CoreError, InputInvalid, OutputInvalid, PartJson, RunError, UnknownTool, ValueParts, encoded, failure, isTextual, make, partText, parts, single, success, ToolOutput_exports as t, text, value, valueParts };
|
|
127
|
+
|
|
128
|
+
//# sourceMappingURL=ToolOutput.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ToolOutput.mjs","names":["Items.image","Items.file","Content.image","Content.file","Content.isMedia","Arr","Items.text","Items.json","Content.Value","Content.Media","json","Items.InputText","Items.InputJson"],"sources":["../../src/tool/ToolOutput.ts"],"sourcesContent":["/**\n * Tool values to history parts, and back. One rule, top level only:\n * string → text · media → its part · array with media → one part per element ·\n * anything else → one JSON value.\n * Every value goes through a schema (the tool's `outputSchema`, else `Content.Value`):\n * the decoded side decides the shape and supplies media, the encoded side the JSON.\n */\nimport {\n Array as Arr,\n Effect,\n Match,\n Option,\n Predicate,\n Schema,\n SchemaTransformation,\n} from \"effect\"\nimport { FileSourceJson } from \"../domain/File.js\"\nimport { ImageSourceJson } from \"../domain/Image.js\"\nimport * as Items from \"../domain/Items.js\"\nimport type { ToolCall, ToolCallOutput, ToolOutputContent } from \"../domain/Items.js\"\nimport * as Content from \"./Content.js\"\n\ntype CallId = Pick<ToolCall, \"call_id\">\n\nconst toPart = Match.type<Content.Media>().pipe(\n Match.tagsExhaustive({\n Image: (m) => Items.image(m.source),\n File: (m) => Items.file(m.source, m.filename),\n }),\n)\n\nconst fromPart = Match.type<ToolOutputContent>().pipe(\n Match.discriminatorsExhaustive(\"type\")({\n input_text: (p) => p.text,\n input_json: (p) => p.value,\n input_image: (p) => Content.image(p.source),\n input_file: (p) => Content.file(p.source, p.filename),\n }),\n)\n\nconst isMixed = (u: unknown): u is ReadonlyArray<unknown> =>\n Array.isArray(u) && u.some(Content.isMedia)\n\nconst zipMixed = (\n decoded: ReadonlyArray<unknown>,\n encoded: Schema.Json,\n): ReadonlyArray<ToolOutputContent> =>\n Array.isArray(encoded) && encoded.length === decoded.length\n ? Arr.zipWith(decoded, encoded, (d, e) =>\n Match.value(d).pipe(\n Match.when(Content.isMedia, toPart),\n Match.orElse(() => (Predicate.isString(e) ? Items.text(e) : Items.json(e))),\n ),\n )\n : [Items.json(encoded)] // the schema reshaped the array: keep its JSON whole\n\n/** `decoded` decides the shape and supplies media; `encoded` supplies the JSON. */\nexport const parts = (decoded: unknown, encoded: Schema.Json): ReadonlyArray<ToolOutputContent> =>\n Match.value(decoded).pipe(\n Match.when(Predicate.isString, (text) => [Items.text(text)]),\n Match.when(Content.isMedia, (media) => [toPart(media)]),\n Match.when(isMixed, (values) => zipMixed(values, encoded)),\n Match.orElse(() => [Items.json(encoded)]),\n )\n\n/** A value, encoded by `schema`, as the call's output. */\nexport const success = <A>(\n call: CallId,\n schema: Schema.Codec<A, Schema.Json>,\n value: A,\n): Effect.Effect<ToolCallOutput, Schema.SchemaError> =>\n Effect.map(Schema.encodeEffect(schema)(value), (encoded) => ({\n type: \"function_call_output\",\n call_id: call.call_id,\n output: parts(value, encoded),\n }))\n\n/** An error, encoded by `schema`, as the call's output, flagged `isError`. */\nexport const failure = <E>(\n call: CallId,\n schema: Schema.Codec<E, Schema.Json>,\n error: E,\n): Effect.Effect<ToolCallOutput, Schema.SchemaError> =>\n Effect.map(Schema.encodeEffect(schema)(error), (encoded) => ({\n type: \"function_call_output\",\n call_id: call.call_id,\n output: [Items.json(encoded)],\n isError: true,\n }))\n\n/** Any `Content.Value` as the call's output, for calls the app closes itself. */\nexport const make = (call: CallId, value: Content.Value) => success(call, Content.Value, value)\n\n/** One value as itself, several (or none) as the array: how parts read back as a value. */\nexport const single = <A>(values: ReadonlyArray<A>): A | ReadonlyArray<A> =>\n Arr.isReadonlyArrayNonEmpty(values) && values.length === 1 ? Arr.headNonEmpty(values) : values\n\n/** A text or JSON part. */\nexport const isTextual = (p: ToolOutputContent): p is Items.InputText | Items.InputJson =>\n p.type === \"input_text\" || p.type === \"input_json\"\n\n/** A text part verbatim, a JSON part serialized. */\nexport const partText = (p: Items.InputText | Items.InputJson): string =>\n p.type === \"input_text\" ? p.text : JSON.stringify(p.value)\n\n/**\n * The parts as one string joined by newlines, for wires (or wire paths) whose\n * tool outputs carry text only. `None` when a part is media.\n */\nexport const text = (output: ReadonlyArray<ToolOutputContent>): Option.Option<string> =>\n Arr.every(output, isTextual) ? Option.some(output.map(partText).join(\"\\n\")) : Option.none()\n\n/** Parts back to a `Content.Value`, read from the parts' own types. */\nexport const value = (output: ReadonlyArray<ToolOutputContent>): Content.Value =>\n single(output.map(fromPart))\n\n/** Parts back to the encoded form an `outputSchema` decodes (media re-encoded). */\nexport const encoded = (\n output: ReadonlyArray<ToolOutputContent>,\n): Effect.Effect<unknown, Schema.SchemaError> =>\n Effect.forEach(output.map(fromPart), (v) =>\n Content.isMedia(v) ? Schema.encodeEffect(Content.Media)(v) : Effect.succeed(v),\n ).pipe(Effect.map(single))\n\n// --- a Content.Value as typed parts, for the wire to a UI -------------------------\n\nconst isJson = Schema.is(Schema.Json)\n\nconst elementPart = Match.type<Content.Media | Schema.Json>().pipe(\n Match.when(Content.isMedia, toPart),\n Match.when(Predicate.isString, Items.text),\n Match.orElse(Items.json),\n)\n\n/** A `Content.Value` as history parts, by the same rule as `parts` (its JSON is itself). */\nexport const valueParts = (v: Content.Value): ReadonlyArray<ToolOutputContent> =>\n Match.value(v).pipe(\n Match.when(Predicate.isString, (text) => [Items.text(text)]),\n Match.when(Content.isMedia, (media) => [toPart(media)]),\n Match.when(isMixed, (values) => values.map(elementPart)),\n Match.when(isJson, (json) => [Items.json(json)]),\n // Unreachable: a Content.Value is text, JSON, media or a mix of them.\n Match.orElse(() => []),\n )\n\n/** A history part with JSON sources (bytes as base64), as it travels to a UI. */\nexport const PartJson = Schema.Union([\n Items.InputText,\n Items.InputJson,\n Schema.Struct({ type: Schema.Literal(\"input_image\"), source: ImageSourceJson }),\n Schema.Struct({\n type: Schema.Literal(\"input_file\"),\n source: FileSourceJson,\n filename: Schema.optionalKey(Schema.String),\n }),\n])\n\n/**\n * A `Content.Value` on the wire to a UI: typed parts instead of its plain JSON,\n * so JSON shaped like `{ _tag: \"Image\", source }` can't come back as an image.\n */\nexport const ValueParts = Schema.Array(PartJson).pipe(\n Schema.decodeTo(\n Schema.toType(Content.Value),\n SchemaTransformation.transform({ decode: value, encode: valueParts }),\n ),\n)\n\n// --- core's own failures -------------------------------------------------------\n\n/** The model's arguments are not JSON or fail `inputSchema`. */\nexport class InputInvalid extends Schema.TaggedError<InputInvalid>(\n \"@betalyra/effect-uai/InputInvalid\",\n)(\"InputInvalid\", { message: Schema.String }) {}\n\n/** `run` returned a value its `outputSchema` rejects: a bug in the tool. */\nexport class OutputInvalid extends Schema.TaggedError<OutputInvalid>(\n \"@betalyra/effect-uai/OutputInvalid\",\n)(\"OutputInvalid\", { message: Schema.String }) {}\n\n/** No local tool by that name (hallucinated, removed, or a declared or provider tool). */\nexport class UnknownTool extends Schema.TaggedError<UnknownTool>(\n \"@betalyra/effect-uai/UnknownTool\",\n)(\"UnknownTool\", { message: Schema.String }) {}\n\n/** The call was left unanswered and closed before the next model call. */\nexport class Cancelled extends Schema.TaggedError<Cancelled>(\"@betalyra/effect-uai/Cancelled\")(\n \"Cancelled\",\n { message: Schema.String },\n) {}\n\n/** What `Toolkit.run` produces itself. */\nexport type RunError = InputInvalid | OutputInvalid | UnknownTool\nexport const RunError = Schema.Union([InputInvalid, OutputInvalid, UnknownTool])\n\n/** Every error core writes into history; what history and the UI always decode. */\nexport type CoreError = RunError | Cancelled\nexport const CoreError = Schema.Union([InputInvalid, OutputInvalid, UnknownTool, Cancelled])\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|