@effect-uai/core 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{AiError-CAX_48RU.d.mts → AiError-Bf1pq4BB.d.mts} +9 -4
- package/dist/{AiError-CAX_48RU.d.mts.map → AiError-Bf1pq4BB.d.mts.map} +1 -1
- package/dist/{Audio-DPlTsNdz.d.mts → Audio-CR7CSljJ.d.mts} +1 -1
- package/dist/{Audio-DPlTsNdz.d.mts.map → Audio-CR7CSljJ.d.mts.map} +1 -1
- package/dist/{Image-HNmMpMTh.d.mts → Image-BZmKfIdq.d.mts} +1 -1
- package/dist/{Image-HNmMpMTh.d.mts.map → Image-BZmKfIdq.d.mts.map} +1 -1
- package/dist/{Items-7ljaVxH_.d.mts → Items-BnIduEV2.d.mts} +7 -7
- package/dist/{Items-7ljaVxH_.d.mts.map → Items-BnIduEV2.d.mts.map} +1 -1
- package/dist/Loop-D8nWpowc.d.mts +173 -0
- package/dist/Loop-D8nWpowc.d.mts.map +1 -0
- package/dist/{SandboxError-BvKypkJm.d.mts → SandboxError-CIrVDOMV.d.mts} +2 -2
- package/dist/{SandboxError-BvKypkJm.d.mts.map → SandboxError-CIrVDOMV.d.mts.map} +1 -1
- package/dist/{StructuredFormat-BbN4dosH.d.mts → StructuredFormat-DHaUJBdd.d.mts} +2 -2
- package/dist/{StructuredFormat-BbN4dosH.d.mts.map → StructuredFormat-DHaUJBdd.d.mts.map} +1 -1
- package/dist/Tool-2lC_XMth.d.mts +284 -0
- package/dist/Tool-2lC_XMth.d.mts.map +1 -0
- package/dist/ToolEvent-cWZ2BnbS.d.mts +151 -0
- package/dist/ToolEvent-cWZ2BnbS.d.mts.map +1 -0
- package/dist/ToolResult-CL4hK_9z.d.mts +157 -0
- package/dist/ToolResult-CL4hK_9z.d.mts.map +1 -0
- package/dist/Toolkit-Dr9l_hNy.d.mts +196 -0
- package/dist/Toolkit-Dr9l_hNy.d.mts.map +1 -0
- package/dist/browser/Browser.d.mts +190 -0
- package/dist/browser/Browser.d.mts.map +1 -0
- package/dist/browser/Browser.mjs +34 -0
- package/dist/browser/Browser.mjs.map +1 -0
- package/dist/browser/BrowserError.d.mts +141 -0
- package/dist/browser/BrowserError.d.mts.map +1 -0
- package/dist/browser/BrowserError.mjs +89 -0
- package/dist/browser/BrowserError.mjs.map +1 -0
- package/dist/browser/BrowserTool.d.mts +58 -0
- package/dist/browser/BrowserTool.d.mts.map +1 -0
- package/dist/browser/BrowserTool.mjs +86 -0
- package/dist/browser/BrowserTool.mjs.map +1 -0
- package/dist/{dist-DV5ISja1.mjs → dist-B04dlFHg.mjs} +3 -3
- package/dist/{dist-DV5ISja1.mjs.map → dist-B04dlFHg.mjs.map} +1 -1
- package/dist/dist-DWgRbDN3.mjs +140 -0
- package/dist/dist-DWgRbDN3.mjs.map +1 -0
- package/dist/domain/AiError.d.mts +2 -2
- package/dist/domain/AiError.mjs +22 -3
- package/dist/domain/AiError.mjs.map +1 -1
- package/dist/domain/Audio.d.mts +1 -1
- package/dist/domain/Image.d.mts +1 -1
- package/dist/domain/Items.d.mts +1 -1
- package/dist/domain/Music.d.mts +1 -1
- package/dist/domain/Turn.d.mts +4 -4
- package/dist/domain/Turn.test.mjs +1 -1
- package/dist/embedding-model/Embedding.d.mts +1 -1
- package/dist/embedding-model/EmbeddingModel.d.mts +1 -1
- package/dist/embedding-model/EmbeddingModel.test.mjs +1 -1
- package/dist/embedding-model/EmbeddingModel.test.mjs.map +1 -1
- package/dist/index.d.mts +19 -13
- package/dist/index.mjs +15 -9
- package/dist/language-model/LanguageModel.d.mts +10 -5
- package/dist/language-model/LanguageModel.d.mts.map +1 -1
- package/dist/language-model/LanguageModel.mjs.map +1 -1
- package/dist/language-model/LanguageModel.test.mjs +1 -1
- package/dist/loop/Loop.d.mts +2 -141
- package/dist/loop/Loop.mjs +16 -20
- package/dist/loop/Loop.mjs.map +1 -1
- package/dist/loop/Loop.test.mjs +18 -1
- package/dist/loop/Loop.test.mjs.map +1 -1
- package/dist/{magic-string.es-BgIV5Mu3.mjs → magic-string.es-Dk-MMA0F.mjs} +1 -1
- package/dist/{magic-string.es-BgIV5Mu3.mjs.map → magic-string.es-Dk-MMA0F.mjs.map} +1 -1
- package/dist/music-generator/MusicGenerator.d.mts +2 -2
- package/dist/music-generator/MusicGenerator.test.mjs +1 -1
- package/dist/observability/Metrics.d.mts +116 -28
- package/dist/observability/Metrics.d.mts.map +1 -1
- package/dist/observability/Metrics.mjs +250 -38
- package/dist/observability/Metrics.mjs.map +1 -1
- package/dist/observability/Metrics.test.d.mts +1 -0
- package/dist/observability/Metrics.test.mjs +187 -0
- package/dist/observability/Metrics.test.mjs.map +1 -0
- package/dist/observability/Telemetry.d.mts +37 -0
- package/dist/observability/Telemetry.d.mts.map +1 -0
- package/dist/observability/Telemetry.mjs +57 -0
- package/dist/observability/Telemetry.mjs.map +1 -0
- package/dist/observability/Telemetry.test.d.mts +1 -0
- package/dist/observability/Telemetry.test.mjs +92 -0
- package/dist/observability/Telemetry.test.mjs.map +1 -0
- package/dist/retry/Retry.d.mts +2 -2
- package/dist/retry/Retry.test.mjs +1 -1
- package/dist/sandbox/Sandbox.d.mts +1 -1
- package/dist/sandbox/SandboxError.d.mts +1 -1
- package/dist/speech-synthesizer/SpeechSynthesizer.d.mts +2 -2
- package/dist/speech-synthesizer/SpeechSynthesizer.test.mjs +1 -1
- package/dist/streaming/JSONL.d.mts +1 -1
- package/dist/streaming/JSONL.test.mjs +1 -1
- package/dist/streaming/SSE.test.mjs +1 -1
- package/dist/structured-format/StructuredFormat.d.mts +1 -1
- package/dist/structured-format/StructuredFormat.mjs.map +1 -1
- package/dist/structured-format/StructuredFormat.test.mjs +1 -1
- package/dist/testing/MockMusicGenerator.d.mts +1 -1
- package/dist/testing/MockProvider.d.mts +1 -1
- package/dist/testing/MockSpeechSynthesizer.d.mts +1 -1
- package/dist/tool/Approval.d.mts +19 -7
- package/dist/tool/Approval.d.mts.map +1 -1
- package/dist/tool/Approval.mjs +18 -11
- package/dist/tool/Approval.mjs.map +1 -1
- package/dist/tool/Approval.test.mjs +80 -40
- package/dist/tool/Approval.test.mjs.map +1 -1
- package/dist/tool/HistoryCheck.d.mts +2 -2
- package/dist/tool/Tool.d.mts +2 -2
- package/dist/tool/Tool.mjs +190 -36
- package/dist/tool/Tool.mjs.map +1 -1
- package/dist/tool/Tool.test.mjs +41 -3
- package/dist/tool/Tool.test.mjs.map +1 -1
- package/dist/tool/ToolEvent.d.mts +2 -151
- package/dist/tool/ToolEvent.mjs +5 -0
- package/dist/tool/ToolEvent.mjs.map +1 -1
- package/dist/tool/ToolResult.d.mts +2 -138
- package/dist/tool/ToolResult.mjs +32 -8
- package/dist/tool/ToolResult.mjs.map +1 -1
- package/dist/tool/Toolkit.d.mts +2 -73
- package/dist/tool/Toolkit.mjs +153 -35
- package/dist/tool/Toolkit.mjs.map +1 -1
- package/dist/tool/Toolkit.test.mjs +271 -11
- package/dist/tool/Toolkit.test.mjs.map +1 -1
- package/dist/transcriber/Transcriber.d.mts +2 -2
- package/dist/transcriber/Transcriber.test.mjs +1 -1
- package/dist/web-read/WebRead.d.mts +44 -0
- package/dist/web-read/WebRead.d.mts.map +1 -0
- package/dist/web-read/WebRead.mjs +18 -0
- package/dist/web-read/WebRead.mjs.map +1 -0
- package/dist/web-read/WebReadTool.d.mts +57 -0
- package/dist/web-read/WebReadTool.d.mts.map +1 -0
- package/dist/web-read/WebReadTool.mjs +59 -0
- package/dist/web-read/WebReadTool.mjs.map +1 -0
- package/dist/web-search/WebSearch.d.mts +1 -1
- package/dist/web-search/WebSearchTool.d.mts +12 -6
- package/dist/web-search/WebSearchTool.d.mts.map +1 -1
- package/dist/web-search/WebSearchTool.mjs +10 -5
- package/dist/web-search/WebSearchTool.mjs.map +1 -1
- package/package.json +21 -1
- package/src/browser/Browser.ts +268 -0
- package/src/browser/BrowserError.ts +145 -0
- package/src/browser/BrowserTool.ts +161 -0
- package/src/domain/AiError.ts +27 -1
- package/src/embedding-model/EmbeddingModel.test.ts +4 -0
- package/src/index.ts +6 -0
- package/src/language-model/LanguageModel.ts +7 -2
- package/src/loop/Loop.test.ts +22 -0
- package/src/loop/Loop.ts +27 -4
- package/src/observability/Metrics.test.ts +266 -0
- package/src/observability/Metrics.ts +505 -65
- package/src/observability/Telemetry.test.ts +123 -0
- package/src/observability/Telemetry.ts +96 -0
- package/src/structured-format/StructuredFormat.ts +4 -0
- package/src/tool/Approval.test.ts +77 -26
- package/src/tool/Approval.ts +72 -27
- package/src/tool/Tool.test.ts +55 -2
- package/src/tool/Tool.ts +335 -70
- package/src/tool/ToolEvent.ts +5 -0
- package/src/tool/ToolResult.ts +36 -9
- package/src/tool/Toolkit.test.ts +339 -9
- package/src/tool/Toolkit.ts +470 -120
- package/src/web-read/WebRead.ts +70 -0
- package/src/web-read/WebReadTool.ts +96 -0
- package/src/web-search/WebSearchTool.ts +11 -5
- package/dist/Tool-BKiieFXk.d.mts +0 -102
- package/dist/Tool-BKiieFXk.d.mts.map +0 -1
- package/dist/loop/Loop.d.mts.map +0 -1
- package/dist/tool/ToolEvent.d.mts.map +0 -1
- package/dist/tool/ToolResult.d.mts.map +0 -1
- package/dist/tool/Toolkit.d.mts.map +0 -1
|
@@ -1,11 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { isOk } from "./ToolResult.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
import { RateLimited, describe } from "../domain/AiError.mjs";
|
|
2
|
+
import { isFailure, isOk, toToolCallOutput } from "./ToolResult.mjs";
|
|
3
|
+
import { fail, fromEffectSchema, make, provider, signal, toDescriptors } from "./Tool.mjs";
|
|
4
|
+
import { isOutput, isProgress } from "./ToolEvent.mjs";
|
|
5
|
+
import { compose, describeFailures, fromArray, make as make$1, namespace, run, wrap } from "./Toolkit.mjs";
|
|
6
|
+
import { a as describe$1, n as globalExpect, o as it, t as import_dist } from "../dist-B04dlFHg.mjs";
|
|
7
|
+
import { Context, Effect, Layer, Schema, Stream, pipe } from "effect";
|
|
7
8
|
//#region src/tool/Toolkit.test.ts
|
|
8
|
-
|
|
9
|
+
const Empty = Schema.Struct({});
|
|
10
|
+
const toolCall = (name, id, args = "{}") => ({
|
|
11
|
+
type: "function_call",
|
|
12
|
+
call_id: id,
|
|
13
|
+
name,
|
|
14
|
+
arguments: args
|
|
15
|
+
});
|
|
16
|
+
const runToArray = (toolkit, calls, options) => run(toolkit, calls, options).pipe(Stream.runCollect, Effect.map((events) => Array.from(events)));
|
|
17
|
+
describe$1("Tool.toDescriptors", () => {
|
|
9
18
|
const GetWeatherInput = Schema.Struct({ city: Schema.String });
|
|
10
19
|
const getWeather = make({
|
|
11
20
|
name: "get_weather",
|
|
@@ -43,7 +52,7 @@ describe("Tool.toDescriptors", () => {
|
|
|
43
52
|
globalExpect(l).not.toHaveProperty("strict");
|
|
44
53
|
});
|
|
45
54
|
});
|
|
46
|
-
describe("Toolkit.run - tools with R requirements", () => {
|
|
55
|
+
describe$1("Toolkit.run - tools with R requirements", () => {
|
|
47
56
|
class WeatherApiKey extends Context.Service()("test/WeatherApiKey") {}
|
|
48
57
|
class GeoApiKey extends Context.Service()("test/GeoApiKey") {}
|
|
49
58
|
const Empty = Schema.Struct({});
|
|
@@ -78,11 +87,11 @@ describe("Toolkit.run - tools with R requirements", () => {
|
|
|
78
87
|
arguments: "{}"
|
|
79
88
|
});
|
|
80
89
|
it("propagates each tool's R into the resulting Stream's requirements", () => {
|
|
81
|
-
(0, import_dist.expectTypeOf)(run(
|
|
90
|
+
(0, import_dist.expectTypeOf)(run(make$1(getWeather, getCoords), [])).toEqualTypeOf();
|
|
82
91
|
});
|
|
83
92
|
it("runs each tool with its own service injected", async () => {
|
|
84
93
|
const layer = Layer.mergeAll(Layer.succeed(WeatherApiKey, { key: "weather-123" }), Layer.succeed(GeoApiKey, { key: "geo-456" }));
|
|
85
|
-
const program = run(
|
|
94
|
+
const program = run(make$1(getWeather, getCoords), [call("get_weather", "c1"), call("get_coords", "c2")]).pipe(Stream.runCollect, Effect.provide(layer));
|
|
86
95
|
const events = await Effect.runPromise(program);
|
|
87
96
|
const outputs = Array.from(events).filter(isOutput);
|
|
88
97
|
const byCall = new Map(outputs.map((e) => [e.result.call_id, e.result]));
|
|
@@ -97,6 +106,31 @@ describe("Toolkit.run - tools with R requirements", () => {
|
|
|
97
106
|
key: "geo-456"
|
|
98
107
|
});
|
|
99
108
|
});
|
|
109
|
+
it("reports non-local kinds as non_local_tool while local calls in the same turn still run", async () => {
|
|
110
|
+
const escalate = signal({
|
|
111
|
+
name: "escalate",
|
|
112
|
+
description: "",
|
|
113
|
+
inputSchema: fromEffectSchema(Empty)
|
|
114
|
+
});
|
|
115
|
+
const localPlain = make({
|
|
116
|
+
name: "plain",
|
|
117
|
+
description: "",
|
|
118
|
+
inputSchema: fromEffectSchema(Empty),
|
|
119
|
+
run: () => Effect.succeed({ ok: true })
|
|
120
|
+
});
|
|
121
|
+
const events = await Effect.runPromise(run(make$1(localPlain, escalate), [
|
|
122
|
+
call("escalate", "s1"),
|
|
123
|
+
call("plain", "p1"),
|
|
124
|
+
call("ghost", "g1")
|
|
125
|
+
]).pipe(Stream.runCollect));
|
|
126
|
+
const byCall = new Map(Array.from(events).filter(isOutput).map((e) => [e.result.call_id, e.result]));
|
|
127
|
+
const s = byCall.get("s1");
|
|
128
|
+
globalExpect(s?._tag === "Failure" && s.kind).toBe("non_local_tool");
|
|
129
|
+
const g = byCall.get("g1");
|
|
130
|
+
globalExpect(g?._tag === "Failure" && g.kind).toBe("unknown_tool");
|
|
131
|
+
const p = byCall.get("p1");
|
|
132
|
+
globalExpect(p !== void 0 && isOk(p) && p.value).toEqual({ ok: true });
|
|
133
|
+
});
|
|
100
134
|
it("with no service-needing tools, R is never", () => {
|
|
101
135
|
const plain = make({
|
|
102
136
|
name: "plain",
|
|
@@ -104,7 +138,233 @@ describe("Toolkit.run - tools with R requirements", () => {
|
|
|
104
138
|
inputSchema: fromEffectSchema(Empty),
|
|
105
139
|
run: () => Effect.succeed(0)
|
|
106
140
|
});
|
|
107
|
-
(0, import_dist.expectTypeOf)(run(
|
|
141
|
+
(0, import_dist.expectTypeOf)(run(make$1(plain), [])).toEqualTypeOf();
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
describe$1("Toolkit.make / compose / namespace - uniqueness", () => {
|
|
145
|
+
const Empty = Schema.Struct({});
|
|
146
|
+
const tool = (name) => make({
|
|
147
|
+
name,
|
|
148
|
+
description: "",
|
|
149
|
+
inputSchema: fromEffectSchema(Empty),
|
|
150
|
+
run: () => Effect.succeed(0)
|
|
151
|
+
});
|
|
152
|
+
it("rejects a duplicate literal name at compile time", () => {
|
|
153
|
+
make$1(tool("search"), provider({
|
|
154
|
+
name: "search",
|
|
155
|
+
description: "",
|
|
156
|
+
inputSchema: fromEffectSchema(Empty),
|
|
157
|
+
provider: "demo",
|
|
158
|
+
config: {}
|
|
159
|
+
}));
|
|
160
|
+
});
|
|
161
|
+
it("fails compose with DuplicateToolName naming the colliding sources (dynamic)", async () => {
|
|
162
|
+
const github = fromArray([tool("search")]);
|
|
163
|
+
const linear = fromArray([tool("search")]);
|
|
164
|
+
const exit = await Effect.runPromiseExit(compose(github, linear));
|
|
165
|
+
globalExpect(exit._tag).toBe("Failure");
|
|
166
|
+
if (exit._tag === "Failure") {
|
|
167
|
+
const json = JSON.stringify(exit.cause);
|
|
168
|
+
globalExpect(json).toContain("DuplicateToolName");
|
|
169
|
+
globalExpect(json).toContain("toolkit-0");
|
|
170
|
+
globalExpect(json).toContain("toolkit-1");
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
it("namespaces names so generic names from two sources compose cleanly", async () => {
|
|
174
|
+
const github = namespace("github", fromArray([tool("search")]));
|
|
175
|
+
const linear = namespace("linear", fromArray([tool("search")]));
|
|
176
|
+
const composed = await Effect.runPromise(compose(github, linear));
|
|
177
|
+
globalExpect(Object.keys(composed).sort()).toEqual(["github__search", "linear__search"]);
|
|
178
|
+
});
|
|
179
|
+
it("preserves each tool's R through compose", () => {
|
|
180
|
+
(0, import_dist.expectTypeOf)(Effect.gen(function* () {
|
|
181
|
+
return run(yield* compose(make$1(tool("a")), make$1(tool("b"))), []);
|
|
182
|
+
})).toEqualTypeOf();
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
describe$1("Toolkit.wrap - middleware", () => {
|
|
186
|
+
const Empty = Schema.Struct({});
|
|
187
|
+
class Audit extends Context.Service()("test/Audit") {}
|
|
188
|
+
const getWeather = make({
|
|
189
|
+
name: "get_weather",
|
|
190
|
+
description: "",
|
|
191
|
+
inputSchema: fromEffectSchema(Empty),
|
|
192
|
+
run: () => Effect.succeed({ tempC: 18 })
|
|
193
|
+
});
|
|
194
|
+
const escalate = signal({
|
|
195
|
+
name: "escalate",
|
|
196
|
+
description: "",
|
|
197
|
+
inputSchema: fromEffectSchema(Empty)
|
|
198
|
+
});
|
|
199
|
+
const audited = (run, name) => (input, emit) => Effect.gen(function* () {
|
|
200
|
+
yield* (yield* Audit).record(name);
|
|
201
|
+
return yield* run(input, emit);
|
|
202
|
+
});
|
|
203
|
+
const call = (name, id) => ({
|
|
204
|
+
type: "function_call",
|
|
205
|
+
call_id: id,
|
|
206
|
+
name,
|
|
207
|
+
arguments: "{}"
|
|
208
|
+
});
|
|
209
|
+
it("unions the middleware's R2 into the toolkit's requirements", () => {
|
|
210
|
+
const wrapped = wrap(make$1(getWeather), audited);
|
|
211
|
+
(0, import_dist.expectTypeOf)(run(wrapped, [])).toEqualTypeOf();
|
|
212
|
+
});
|
|
213
|
+
it("runs the wrapped handler and leaves non-local kinds untouched", async () => {
|
|
214
|
+
const recorded = [];
|
|
215
|
+
const wrapped = pipe(make$1(getWeather, escalate), wrap(audited));
|
|
216
|
+
const events = await Effect.runPromise(run(wrapped, [call("get_weather", "w1"), call("escalate", "s1")]).pipe(Stream.runCollect, Effect.provideService(Audit, { record: (n) => Effect.sync(() => void recorded.push(n)) })));
|
|
217
|
+
const byCall = new Map(Array.from(events).filter(isOutput).map((e) => [e.result.call_id, e.result]));
|
|
218
|
+
globalExpect(recorded).toEqual(["get_weather"]);
|
|
219
|
+
const w = byCall.get("w1");
|
|
220
|
+
globalExpect(w !== void 0 && isOk(w) && w.value).toEqual({ tempC: 18 });
|
|
221
|
+
const s = byCall.get("s1");
|
|
222
|
+
globalExpect(s?._tag === "Failure" && s.kind).toBe("non_local_tool");
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
describe$1("Toolkit.run - executor outcomes", () => {
|
|
226
|
+
const plain = (name, out) => make({
|
|
227
|
+
name,
|
|
228
|
+
description: "",
|
|
229
|
+
inputSchema: fromEffectSchema(Empty),
|
|
230
|
+
run: () => Effect.succeed(out)
|
|
231
|
+
});
|
|
232
|
+
it("absorbs a bare string failure as a tool_failed result", async () => {
|
|
233
|
+
const t = make({
|
|
234
|
+
name: "t",
|
|
235
|
+
description: "",
|
|
236
|
+
inputSchema: fromEffectSchema(Empty),
|
|
237
|
+
run: () => Effect.fail("nope")
|
|
238
|
+
});
|
|
239
|
+
const result = (await Effect.runPromise(runToArray(make$1(t), [toolCall("t", "1")]))).filter(isOutput)[0]?.result;
|
|
240
|
+
globalExpect(result).toMatchObject({
|
|
241
|
+
_tag: "Failure",
|
|
242
|
+
kind: "tool_failed",
|
|
243
|
+
reason: "nope"
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
it("absorbs Tool.fail with a custom kind", async () => {
|
|
247
|
+
const t = make({
|
|
248
|
+
name: "t",
|
|
249
|
+
description: "",
|
|
250
|
+
inputSchema: fromEffectSchema(Empty),
|
|
251
|
+
run: () => fail("missing", { kind: "not_found" })
|
|
252
|
+
});
|
|
253
|
+
const result = (await Effect.runPromise(runToArray(make$1(t), [toolCall("t", "1")]))).filter(isOutput)[0]?.result;
|
|
254
|
+
globalExpect(result).toMatchObject({
|
|
255
|
+
_tag: "Failure",
|
|
256
|
+
kind: "not_found",
|
|
257
|
+
reason: "missing"
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
it("propagates a typed non-sentinel failure on the stream", async () => {
|
|
261
|
+
const t = make({
|
|
262
|
+
name: "t",
|
|
263
|
+
description: "",
|
|
264
|
+
inputSchema: fromEffectSchema(Empty),
|
|
265
|
+
run: () => Effect.fail(new RateLimited({
|
|
266
|
+
provider: "x",
|
|
267
|
+
raw: null
|
|
268
|
+
}))
|
|
269
|
+
});
|
|
270
|
+
globalExpect((await Effect.runPromiseExit(runToArray(make$1(t), [toolCall("t", "1")])))._tag).toBe("Failure");
|
|
271
|
+
});
|
|
272
|
+
it("dies on a defect instead of masking it as a result", async () => {
|
|
273
|
+
const t = make({
|
|
274
|
+
name: "t",
|
|
275
|
+
description: "",
|
|
276
|
+
inputSchema: fromEffectSchema(Empty),
|
|
277
|
+
run: () => Effect.die(/* @__PURE__ */ new Error("boom"))
|
|
278
|
+
});
|
|
279
|
+
globalExpect((await Effect.runPromiseExit(runToArray(make$1(t), [toolCall("t", "1")])))._tag).toBe("Failure");
|
|
280
|
+
});
|
|
281
|
+
it("streams progress events strictly before the terminal output", async () => {
|
|
282
|
+
const t = make({
|
|
283
|
+
name: "t",
|
|
284
|
+
description: "",
|
|
285
|
+
inputSchema: fromEffectSchema(Empty),
|
|
286
|
+
run: (_input, emit) => emit("a").pipe(Effect.andThen(emit("b")), Effect.andThen(Effect.succeed("done")))
|
|
287
|
+
});
|
|
288
|
+
const events = await Effect.runPromise(runToArray(make$1(t), [toolCall("t", "1")]));
|
|
289
|
+
globalExpect(events.map((e) => e._tag)).toEqual([
|
|
290
|
+
"Progress",
|
|
291
|
+
"Progress",
|
|
292
|
+
"Output"
|
|
293
|
+
]);
|
|
294
|
+
globalExpect(events.filter(isProgress).map((p) => p.data)).toEqual(["a", "b"]);
|
|
295
|
+
});
|
|
296
|
+
it("reports input validation failures as a result carrying the issue detail", async () => {
|
|
297
|
+
const t = make({
|
|
298
|
+
name: "t",
|
|
299
|
+
description: "",
|
|
300
|
+
inputSchema: fromEffectSchema(Schema.Struct({ x: Schema.Number })),
|
|
301
|
+
run: () => Effect.succeed(1)
|
|
302
|
+
});
|
|
303
|
+
const result = (await Effect.runPromise(runToArray(make$1(t), [toolCall("t", "1", "{\"x\":\"nope\"}")]))).filter(isOutput)[0]?.result;
|
|
304
|
+
globalExpect(result).toMatchObject({
|
|
305
|
+
_tag: "Failure",
|
|
306
|
+
kind: "input_validation_error"
|
|
307
|
+
});
|
|
308
|
+
globalExpect(result !== void 0 && isFailure(result) && result.reason).toBeTruthy();
|
|
309
|
+
});
|
|
310
|
+
it("normalizes empty arguments to {} for a zero-arg tool", async () => {
|
|
311
|
+
const result = (await Effect.runPromise(runToArray(make$1(plain("t", "ok")), [toolCall("t", "1", "")]))).filter(isOutput)[0]?.result;
|
|
312
|
+
globalExpect(result).toMatchObject({
|
|
313
|
+
_tag: "Ok",
|
|
314
|
+
value: "ok"
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
it("reports unparseable arguments as input_validation_error", async () => {
|
|
318
|
+
const result = (await Effect.runPromise(runToArray(make$1(plain("t", "ok")), [toolCall("t", "1", "not json")]))).filter(isOutput)[0]?.result;
|
|
319
|
+
globalExpect(result).toMatchObject({
|
|
320
|
+
_tag: "Failure",
|
|
321
|
+
kind: "input_validation_error"
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
it("a call named constructor is unknown_tool, not non_local_tool", async () => {
|
|
325
|
+
const result = (await Effect.runPromise(runToArray(make$1(plain("t", "ok")), [toolCall("constructor", "1")]))).filter(isOutput)[0]?.result;
|
|
326
|
+
globalExpect(result).toMatchObject({
|
|
327
|
+
_tag: "Failure",
|
|
328
|
+
kind: "unknown_tool"
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
it("serializes an undefined output as \"null\" on the wire", async () => {
|
|
332
|
+
const t = make({
|
|
333
|
+
name: "t",
|
|
334
|
+
description: "",
|
|
335
|
+
inputSchema: fromEffectSchema(Empty),
|
|
336
|
+
run: () => Effect.void
|
|
337
|
+
});
|
|
338
|
+
const result = (await Effect.runPromise(runToArray(make$1(t), [toolCall("t", "1")]))).filter(isOutput)[0]?.result;
|
|
339
|
+
globalExpect(result !== void 0 && toToolCallOutput(result).output).toBe("null");
|
|
340
|
+
});
|
|
341
|
+
it("preserves call order with concurrency 1", async () => {
|
|
342
|
+
const kit = make$1(plain("a", "A"), plain("b", "B"));
|
|
343
|
+
globalExpect((await Effect.runPromise(runToArray(kit, [toolCall("a", "a1"), toolCall("b", "b1")], { concurrency: 1 }))).filter(isOutput).map((e) => e.result.call_id)).toEqual(["a1", "b1"]);
|
|
344
|
+
});
|
|
345
|
+
it("type: run of a describeFailures toolkit has no stream error", () => {
|
|
346
|
+
const kit = describeFailures(make$1(make({
|
|
347
|
+
name: "t",
|
|
348
|
+
description: "",
|
|
349
|
+
inputSchema: fromEffectSchema(Empty),
|
|
350
|
+
run: () => Effect.fail(new RateLimited({
|
|
351
|
+
provider: "x",
|
|
352
|
+
raw: null
|
|
353
|
+
}))
|
|
354
|
+
})), describe);
|
|
355
|
+
(0, import_dist.expectTypeOf)(run(kit, [])).toEqualTypeOf();
|
|
356
|
+
});
|
|
357
|
+
it("type: run of a typed toolkit propagates the tool's E", () => {
|
|
358
|
+
const t = make({
|
|
359
|
+
name: "t",
|
|
360
|
+
description: "",
|
|
361
|
+
inputSchema: fromEffectSchema(Empty),
|
|
362
|
+
run: () => Effect.fail(new RateLimited({
|
|
363
|
+
provider: "x",
|
|
364
|
+
raw: null
|
|
365
|
+
}))
|
|
366
|
+
});
|
|
367
|
+
(0, import_dist.expectTypeOf)(run(make$1(t), [])).toEqualTypeOf();
|
|
108
368
|
});
|
|
109
369
|
});
|
|
110
370
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Toolkit.test.mjs","names":["Tool.make","Tool.fromEffectSchema","Tool.toDescriptors","Toolkit.run"],"sources":["../../src/tool/Toolkit.test.ts"],"sourcesContent":["import { Context, Effect, Layer, Schema, Stream } from \"effect\"\nimport { describe, expect, expectTypeOf, it } from \"vitest\"\nimport type { ToolCall } from \"../domain/Items.js\"\nimport { isOutput } from \"./ToolEvent.js\"\nimport { isOk } from \"./ToolResult.js\"\nimport * as Tool from \"./Tool.js\"\nimport * as Toolkit from \"./Toolkit.js\"\n\ndescribe(\"Tool.toDescriptors\", () => {\n const GetWeatherInput = Schema.Struct({ city: Schema.String })\n\n const getWeather = Tool.make({\n name: \"get_weather\",\n description: \"Look up the current temperature for a city.\",\n inputSchema: Tool.fromEffectSchema(GetWeatherInput),\n run: ({ city }) => Effect.succeed({ city, tempC: 18 }),\n })\n\n it(\"renders the input schema as a JSON Schema document\", () => {\n const [desc] = Tool.toDescriptors([getWeather])\n expect(desc?.name).toBe(\"get_weather\")\n expect(desc?.description).toBe(\"Look up the current temperature for a city.\")\n expect(desc?.inputSchema).toMatchObject({\n type: \"object\",\n properties: { city: { type: \"string\" } },\n required: [\"city\"],\n })\n })\n\n it(\"includes strict flag only when set on the tool\", () => {\n const strictTool = Tool.make({\n name: \"strict_one\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(GetWeatherInput),\n run: () => Effect.succeed({}),\n strict: true,\n })\n const looseTool = Tool.make({\n name: \"loose_one\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(GetWeatherInput),\n run: () => Effect.succeed({}),\n })\n const [s, l] = Tool.toDescriptors([strictTool, looseTool])\n expect(s?.strict).toBe(true)\n expect(l).not.toHaveProperty(\"strict\")\n })\n})\n\ndescribe(\"Toolkit.run - tools with R requirements\", () => {\n // Two distinct services, modelling the \"typed per-tool context\" use case\n // (cf. AI SDK 7's `toolsContext`). In Effect each tool declares its R, the\n // compiler enforces it, and `run` surfaces the union for the caller\n // to provide via Layer.\n type WeatherApiKeyShape = { readonly key: string }\n class WeatherApiKey extends Context.Service<WeatherApiKey, WeatherApiKeyShape>()(\n \"test/WeatherApiKey\",\n ) {}\n\n type GeoApiKeyShape = { readonly key: string }\n class GeoApiKey extends Context.Service<GeoApiKey, GeoApiKeyShape>()(\"test/GeoApiKey\") {}\n\n const Empty = Schema.Struct({})\n\n const getWeather = Tool.make({\n name: \"get_weather\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () =>\n Effect.gen(function* () {\n const { key } = yield* WeatherApiKey\n return { source: \"weather\", key }\n }),\n })\n\n const getCoords = Tool.make({\n name: \"get_coords\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () =>\n Effect.gen(function* () {\n const { key } = yield* GeoApiKey\n return { source: \"geo\", key }\n }),\n })\n\n const call = (name: string, id: string): ToolCall => ({\n type: \"function_call\",\n call_id: id,\n name,\n arguments: \"{}\",\n })\n\n it(\"propagates each tool's R into the resulting Stream's requirements\", () => {\n const stream = Toolkit.run([getWeather, getCoords], [])\n expectTypeOf(stream).toEqualTypeOf<\n Stream.Stream<import(\"./ToolEvent.js\").ToolEvent, never, WeatherApiKey | GeoApiKey>\n >()\n })\n\n it(\"runs each tool with its own service injected\", async () => {\n const layer = Layer.mergeAll(\n Layer.succeed(WeatherApiKey, { key: \"weather-123\" }),\n Layer.succeed(GeoApiKey, { key: \"geo-456\" }),\n )\n\n const program = Toolkit.run(\n [getWeather, getCoords],\n [call(\"get_weather\", \"c1\"), call(\"get_coords\", \"c2\")],\n ).pipe(Stream.runCollect, Effect.provide(layer))\n\n const events = await Effect.runPromise(program)\n const outputs = Array.from(events).filter(isOutput)\n const byCall = new Map(outputs.map((e) => [e.result.call_id, e.result]))\n\n const w = byCall.get(\"c1\")\n const g = byCall.get(\"c2\")\n expect(w !== undefined && isOk(w) && w.value).toEqual({\n source: \"weather\",\n key: \"weather-123\",\n })\n expect(g !== undefined && isOk(g) && g.value).toEqual({\n source: \"geo\",\n key: \"geo-456\",\n })\n })\n\n it(\"with no service-needing tools, R is never\", () => {\n const plain = Tool.make({\n name: \"plain\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () => Effect.succeed(0),\n })\n const stream = Toolkit.run([plain], [])\n expectTypeOf(stream).toEqualTypeOf<\n Stream.Stream<import(\"./ToolEvent.js\").ToolEvent, never, never>\n >()\n })\n})\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Toolkit.test.mjs","names":["Toolkit.run","describe","Tool.make","Tool.fromEffectSchema","Tool.toDescriptors","Toolkit.make","Tool.signal","Tool.provider","Toolkit.fromArray","Toolkit.compose","Toolkit.namespace","Toolkit.wrap","Tool.fail","AiError.RateLimited","Toolkit.describeFailures","AiError.describe"],"sources":["../../src/tool/Toolkit.test.ts"],"sourcesContent":["import { Context, Effect, Layer, pipe, Schema, Stream } from \"effect\"\nimport { describe, expect, expectTypeOf, it } from \"vitest\"\nimport * as AiError from \"../domain/AiError.js\"\nimport type { ToolCall } from \"../domain/Items.js\"\nimport { type ToolEvent, isOutput, isProgress } from \"./ToolEvent.js\"\nimport { isFailure, isOk, toToolCallOutput } from \"./ToolResult.js\"\nimport * as Tool from \"./Tool.js\"\nimport * as Toolkit from \"./Toolkit.js\"\n\nconst Empty = Schema.Struct({})\nconst toolCall = (name: string, id: string, args = \"{}\"): ToolCall => ({\n type: \"function_call\",\n call_id: id,\n name,\n arguments: args,\n})\nconst runToArray = <T extends Toolkit.Toolkit>(\n toolkit: T,\n calls: ReadonlyArray<ToolCall>,\n options?: Toolkit.ExecuteOptions,\n) =>\n Toolkit.run(toolkit, calls, options).pipe(\n Stream.runCollect,\n Effect.map((events): ReadonlyArray<ToolEvent> => Array.from(events)),\n )\n\ndescribe(\"Tool.toDescriptors\", () => {\n const GetWeatherInput = Schema.Struct({ city: Schema.String })\n\n const getWeather = Tool.make({\n name: \"get_weather\",\n description: \"Look up the current temperature for a city.\",\n inputSchema: Tool.fromEffectSchema(GetWeatherInput),\n run: ({ city }) => Effect.succeed({ city, tempC: 18 }),\n })\n\n it(\"renders the input schema as a JSON Schema document\", () => {\n const [desc] = Tool.toDescriptors([getWeather])\n expect(desc?.name).toBe(\"get_weather\")\n expect(desc?.description).toBe(\"Look up the current temperature for a city.\")\n expect(desc?.inputSchema).toMatchObject({\n type: \"object\",\n properties: { city: { type: \"string\" } },\n required: [\"city\"],\n })\n })\n\n it(\"includes strict flag only when set on the tool\", () => {\n const strictTool = Tool.make({\n name: \"strict_one\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(GetWeatherInput),\n run: () => Effect.succeed({}),\n strict: true,\n })\n const looseTool = Tool.make({\n name: \"loose_one\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(GetWeatherInput),\n run: () => Effect.succeed({}),\n })\n const [s, l] = Tool.toDescriptors([strictTool, looseTool])\n expect(s?.strict).toBe(true)\n expect(l).not.toHaveProperty(\"strict\")\n })\n})\n\ndescribe(\"Toolkit.run - tools with R requirements\", () => {\n // Two distinct services, modelling the \"typed per-tool context\" use case\n // (cf. AI SDK 7's `toolsContext`). In Effect each tool declares its R, the\n // compiler enforces it, and `run` surfaces the union for the caller\n // to provide via Layer.\n type WeatherApiKeyShape = { readonly key: string }\n class WeatherApiKey extends Context.Service<WeatherApiKey, WeatherApiKeyShape>()(\n \"test/WeatherApiKey\",\n ) {}\n\n type GeoApiKeyShape = { readonly key: string }\n class GeoApiKey extends Context.Service<GeoApiKey, GeoApiKeyShape>()(\"test/GeoApiKey\") {}\n\n const Empty = Schema.Struct({})\n\n const getWeather = Tool.make({\n name: \"get_weather\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () =>\n Effect.gen(function* () {\n const { key } = yield* WeatherApiKey\n return { source: \"weather\", key }\n }),\n })\n\n const getCoords = Tool.make({\n name: \"get_coords\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () =>\n Effect.gen(function* () {\n const { key } = yield* GeoApiKey\n return { source: \"geo\", key }\n }),\n })\n\n const call = (name: string, id: string): ToolCall => ({\n type: \"function_call\",\n call_id: id,\n name,\n arguments: \"{}\",\n })\n\n it(\"propagates each tool's R into the resulting Stream's requirements\", () => {\n const stream = Toolkit.run(Toolkit.make(getWeather, getCoords), [])\n expectTypeOf(stream).toEqualTypeOf<\n Stream.Stream<import(\"./ToolEvent.js\").ToolEvent, never, WeatherApiKey | GeoApiKey>\n >()\n })\n\n it(\"runs each tool with its own service injected\", async () => {\n const layer = Layer.mergeAll(\n Layer.succeed(WeatherApiKey, { key: \"weather-123\" }),\n Layer.succeed(GeoApiKey, { key: \"geo-456\" }),\n )\n\n const program = Toolkit.run(Toolkit.make(getWeather, getCoords), [\n call(\"get_weather\", \"c1\"),\n call(\"get_coords\", \"c2\"),\n ]).pipe(Stream.runCollect, Effect.provide(layer))\n\n const events = await Effect.runPromise(program)\n const outputs = Array.from(events).filter(isOutput)\n const byCall = new Map(outputs.map((e) => [e.result.call_id, e.result]))\n\n const w = byCall.get(\"c1\")\n const g = byCall.get(\"c2\")\n expect(w !== undefined && isOk(w) && w.value).toEqual({\n source: \"weather\",\n key: \"weather-123\",\n })\n expect(g !== undefined && isOk(g) && g.value).toEqual({\n source: \"geo\",\n key: \"geo-456\",\n })\n })\n\n it(\"reports non-local kinds as non_local_tool while local calls in the same turn still run\", async () => {\n const escalate = Tool.signal({\n name: \"escalate\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n })\n const localPlain = Tool.make({\n name: \"plain\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () => Effect.succeed({ ok: true }),\n })\n\n const events = await Effect.runPromise(\n Toolkit.run(Toolkit.make(localPlain, escalate), [\n call(\"escalate\", \"s1\"),\n call(\"plain\", \"p1\"),\n call(\"ghost\", \"g1\"),\n ]).pipe(Stream.runCollect),\n )\n const byCall = new Map(\n Array.from(events)\n .filter(isOutput)\n .map((e) => [e.result.call_id, e.result] as const),\n )\n\n // Signal tool: model-visible but no local executor.\n const s = byCall.get(\"s1\")\n expect(s?._tag === \"Failure\" && s.kind).toBe(\"non_local_tool\")\n // Unknown tool stays distinct from non-local.\n const g = byCall.get(\"g1\")\n expect(g?._tag === \"Failure\" && g.kind).toBe(\"unknown_tool\")\n // The local call still executed despite the non-local sibling.\n const p = byCall.get(\"p1\")\n expect(p !== undefined && isOk(p) && p.value).toEqual({ ok: true })\n })\n\n it(\"with no service-needing tools, R is never\", () => {\n const plain = Tool.make({\n name: \"plain\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () => Effect.succeed(0),\n })\n const stream = Toolkit.run(Toolkit.make(plain), [])\n expectTypeOf(stream).toEqualTypeOf<\n Stream.Stream<import(\"./ToolEvent.js\").ToolEvent, never, never>\n >()\n })\n})\n\ndescribe(\"Toolkit.make / compose / namespace - uniqueness\", () => {\n const Empty = Schema.Struct({})\n const tool = (name: string) =>\n Tool.make({\n name,\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () => Effect.succeed(0),\n })\n\n it(\"rejects a duplicate literal name at compile time\", () => {\n const a = tool(\"search\")\n const b = Tool.provider({\n name: \"search\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n provider: \"demo\",\n config: {},\n })\n // @ts-expect-error Duplicate tool name: search\n Toolkit.make(a, b)\n })\n\n it(\"fails compose with DuplicateToolName naming the colliding sources (dynamic)\", async () => {\n const github = Toolkit.fromArray([tool(\"search\")])\n const linear = Toolkit.fromArray([tool(\"search\")])\n const exit = await Effect.runPromiseExit(Toolkit.compose(github, linear))\n expect(exit._tag).toBe(\"Failure\")\n if (exit._tag === \"Failure\") {\n const json = JSON.stringify(exit.cause)\n expect(json).toContain(\"DuplicateToolName\")\n expect(json).toContain(\"toolkit-0\")\n expect(json).toContain(\"toolkit-1\")\n }\n })\n\n it(\"namespaces names so generic names from two sources compose cleanly\", async () => {\n const github = Toolkit.namespace(\"github\", Toolkit.fromArray([tool(\"search\")]))\n const linear = Toolkit.namespace(\"linear\", Toolkit.fromArray([tool(\"search\")]))\n const composed = await Effect.runPromise(Toolkit.compose(github, linear))\n expect(Object.keys(composed).sort()).toEqual([\"github__search\", \"linear__search\"])\n })\n\n it(\"preserves each tool's R through compose\", () => {\n const stream = Effect.gen(function* () {\n const composed = yield* Toolkit.compose(Toolkit.make(tool(\"a\")), Toolkit.make(tool(\"b\")))\n return Toolkit.run(composed, [])\n })\n // No service-needing tools here -> R stays never (compose didn't poison it).\n expectTypeOf(stream).toEqualTypeOf<\n Effect.Effect<\n Stream.Stream<import(\"./ToolEvent.js\").ToolEvent, never, never>,\n Toolkit.DuplicateToolName\n >\n >()\n })\n})\n\ndescribe(\"Toolkit.wrap - middleware\", () => {\n const Empty = Schema.Struct({})\n type AuditShape = { readonly record: (name: string) => Effect.Effect<void> }\n class Audit extends Context.Service<Audit, AuditShape>()(\"test/Audit\") {}\n\n const getWeather = Tool.make({\n name: \"get_weather\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () => Effect.succeed({ tempC: 18 }),\n })\n const escalate = Tool.signal({\n name: \"escalate\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n })\n\n // A middleware that needs the Audit service -> R2 = Audit.\n const audited: Toolkit.Middleware<Audit> = (run, name) => (input, emit) =>\n Effect.gen(function* () {\n const a = yield* Audit\n yield* a.record(name)\n return yield* run(input, emit)\n })\n\n const call = (name: string, id: string): ToolCall => ({\n type: \"function_call\",\n call_id: id,\n name,\n arguments: \"{}\",\n })\n\n it(\"unions the middleware's R2 into the toolkit's requirements\", () => {\n const wrapped = Toolkit.wrap(Toolkit.make(getWeather), audited)\n const stream = Toolkit.run(wrapped, [])\n expectTypeOf(stream).toEqualTypeOf<\n Stream.Stream<import(\"./ToolEvent.js\").ToolEvent, never, Audit>\n >()\n })\n\n it(\"runs the wrapped handler and leaves non-local kinds untouched\", async () => {\n const recorded: Array<string> = []\n const wrapped = pipe(Toolkit.make(getWeather, escalate), Toolkit.wrap(audited))\n\n const events = await Effect.runPromise(\n Toolkit.run(wrapped, [call(\"get_weather\", \"w1\"), call(\"escalate\", \"s1\")]).pipe(\n Stream.runCollect,\n Effect.provideService(Audit, { record: (n) => Effect.sync(() => void recorded.push(n)) }),\n ),\n )\n const byCall = new Map(\n Array.from(events)\n .filter(isOutput)\n .map((e) => [e.result.call_id, e.result] as const),\n )\n\n // Local tool ran through the middleware (audit fired).\n expect(recorded).toEqual([\"get_weather\"])\n const w = byCall.get(\"w1\")\n expect(w !== undefined && isOk(w) && w.value).toEqual({ tempC: 18 })\n // Signal kind is left alone by wrap -> still reported non-local.\n const s = byCall.get(\"s1\")\n expect(s?._tag === \"Failure\" && s.kind).toBe(\"non_local_tool\")\n })\n})\n\ndescribe(\"Toolkit.run - executor outcomes\", () => {\n const plain = <N extends string>(name: N, out: unknown) =>\n Tool.make({\n name,\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () => Effect.succeed(out),\n })\n\n it(\"absorbs a bare string failure as a tool_failed result\", async () => {\n const t = Tool.make({\n name: \"t\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () => Effect.fail(\"nope\"),\n })\n const events = await Effect.runPromise(runToArray(Toolkit.make(t), [toolCall(\"t\", \"1\")]))\n const result = events.filter(isOutput)[0]?.result\n expect(result).toMatchObject({ _tag: \"Failure\", kind: \"tool_failed\", reason: \"nope\" })\n })\n\n it(\"absorbs Tool.fail with a custom kind\", async () => {\n const t = Tool.make({\n name: \"t\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () => Tool.fail(\"missing\", { kind: \"not_found\" }),\n })\n const events = await Effect.runPromise(runToArray(Toolkit.make(t), [toolCall(\"t\", \"1\")]))\n const result = events.filter(isOutput)[0]?.result\n expect(result).toMatchObject({ _tag: \"Failure\", kind: \"not_found\", reason: \"missing\" })\n })\n\n it(\"propagates a typed non-sentinel failure on the stream\", async () => {\n const t = Tool.make({\n name: \"t\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () => Effect.fail(new AiError.RateLimited({ provider: \"x\", raw: null })),\n })\n const exit = await Effect.runPromiseExit(runToArray(Toolkit.make(t), [toolCall(\"t\", \"1\")]))\n expect(exit._tag).toBe(\"Failure\")\n })\n\n it(\"dies on a defect instead of masking it as a result\", async () => {\n const t = Tool.make({\n name: \"t\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () => Effect.die(new Error(\"boom\")),\n })\n const exit = await Effect.runPromiseExit(runToArray(Toolkit.make(t), [toolCall(\"t\", \"1\")]))\n expect(exit._tag).toBe(\"Failure\")\n })\n\n it(\"streams progress events strictly before the terminal output\", async () => {\n const t = Tool.make({\n name: \"t\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: (_input, emit) =>\n emit(\"a\").pipe(Effect.andThen(emit(\"b\")), Effect.andThen(Effect.succeed(\"done\"))),\n })\n const events = await Effect.runPromise(runToArray(Toolkit.make(t), [toolCall(\"t\", \"1\")]))\n expect(events.map((e) => e._tag)).toEqual([\"Progress\", \"Progress\", \"Output\"])\n expect(events.filter(isProgress).map((p) => p.data)).toEqual([\"a\", \"b\"])\n })\n\n it(\"reports input validation failures as a result carrying the issue detail\", async () => {\n const t = Tool.make({\n name: \"t\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Schema.Struct({ x: Schema.Number })),\n run: () => Effect.succeed(1),\n })\n const events = await Effect.runPromise(\n runToArray(Toolkit.make(t), [toolCall(\"t\", \"1\", '{\"x\":\"nope\"}')]),\n )\n const result = events.filter(isOutput)[0]?.result\n expect(result).toMatchObject({ _tag: \"Failure\", kind: \"input_validation_error\" })\n expect(result !== undefined && isFailure(result) && result.reason).toBeTruthy()\n })\n\n it(\"normalizes empty arguments to {} for a zero-arg tool\", async () => {\n const events = await Effect.runPromise(\n runToArray(Toolkit.make(plain(\"t\", \"ok\")), [toolCall(\"t\", \"1\", \"\")]),\n )\n const result = events.filter(isOutput)[0]?.result\n expect(result).toMatchObject({ _tag: \"Ok\", value: \"ok\" })\n })\n\n it(\"reports unparseable arguments as input_validation_error\", async () => {\n const events = await Effect.runPromise(\n runToArray(Toolkit.make(plain(\"t\", \"ok\")), [toolCall(\"t\", \"1\", \"not json\")]),\n )\n const result = events.filter(isOutput)[0]?.result\n expect(result).toMatchObject({ _tag: \"Failure\", kind: \"input_validation_error\" })\n })\n\n it(\"a call named constructor is unknown_tool, not non_local_tool\", async () => {\n const events = await Effect.runPromise(\n runToArray(Toolkit.make(plain(\"t\", \"ok\")), [toolCall(\"constructor\", \"1\")]),\n )\n const result = events.filter(isOutput)[0]?.result\n expect(result).toMatchObject({ _tag: \"Failure\", kind: \"unknown_tool\" })\n })\n\n it('serializes an undefined output as \"null\" on the wire', async () => {\n const t = Tool.make({\n name: \"t\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () => Effect.void,\n })\n const events = await Effect.runPromise(runToArray(Toolkit.make(t), [toolCall(\"t\", \"1\")]))\n const result = events.filter(isOutput)[0]?.result\n expect(result !== undefined && toToolCallOutput(result).output).toBe(\"null\")\n })\n\n it(\"preserves call order with concurrency 1\", async () => {\n const kit = Toolkit.make(plain(\"a\", \"A\"), plain(\"b\", \"B\"))\n const events = await Effect.runPromise(\n runToArray(kit, [toolCall(\"a\", \"a1\"), toolCall(\"b\", \"b1\")], { concurrency: 1 }),\n )\n expect(events.filter(isOutput).map((e) => e.result.call_id)).toEqual([\"a1\", \"b1\"])\n })\n\n it(\"type: run of a describeFailures toolkit has no stream error\", () => {\n const t = Tool.make({\n name: \"t\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () => Effect.fail(new AiError.RateLimited({ provider: \"x\", raw: null })),\n })\n const kit = Toolkit.describeFailures(Toolkit.make(t), AiError.describe)\n const stream = Toolkit.run(kit, [])\n expectTypeOf(stream).toEqualTypeOf<Stream.Stream<ToolEvent, never, never>>()\n })\n\n it(\"type: run of a typed toolkit propagates the tool's E\", () => {\n const t = Tool.make({\n name: \"t\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () => Effect.fail(new AiError.RateLimited({ provider: \"x\", raw: null })),\n })\n const stream = Toolkit.run(Toolkit.make(t), [])\n expectTypeOf(stream).toEqualTypeOf<Stream.Stream<ToolEvent, AiError.RateLimited, never>>()\n })\n})\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as AiError } from "../AiError-
|
|
2
|
-
import { a as AudioSource, r as AudioFormat } from "../Audio-
|
|
1
|
+
import { t as AiError } from "../AiError-Bf1pq4BB.mjs";
|
|
2
|
+
import { a as AudioSource, r as AudioFormat } from "../Audio-CR7CSljJ.mjs";
|
|
3
3
|
import { TranscriptEvent, TranscriptResult } from "../domain/Transcript.mjs";
|
|
4
4
|
import { Context, Effect, Stream } from "effect";
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { streamTranscriptionFrom, transcribe } from "./Transcriber.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { a as describe, n as globalExpect, o as it, t as import_dist } from "../dist-B04dlFHg.mjs";
|
|
3
3
|
import { layer, layerSyncOnly } from "../testing/MockTranscriber.mjs";
|
|
4
4
|
import { Duration, Effect, Stream } from "effect";
|
|
5
5
|
//#region src/transcriber/Transcriber.test.ts
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { t as AiError } from "../AiError-Bf1pq4BB.mjs";
|
|
2
|
+
import { Context, Duration, Effect } from "effect";
|
|
3
|
+
|
|
4
|
+
//#region src/web-read/WebRead.d.ts
|
|
5
|
+
declare namespace WebRead_d_exports {
|
|
6
|
+
export { CommonReadRequest, ReadFormat, ReadResponse, WebRead, WebReadService, read };
|
|
7
|
+
}
|
|
8
|
+
/** Representation to return for a page. `markdown` is the default. */
|
|
9
|
+
type ReadFormat = "markdown" | "html";
|
|
10
|
+
/**
|
|
11
|
+
* Cross-provider read request: one URL in, clean content out. Provider-specific
|
|
12
|
+
* knobs live on the provider-typed request.
|
|
13
|
+
*/
|
|
14
|
+
type CommonReadRequest = {
|
|
15
|
+
/** The page to read. */readonly url: string; /** Output representation. Defaults to `markdown`. See {@link ReadFormat}. */
|
|
16
|
+
readonly format?: ReadFormat; /** Upper bound on time spent fetching the page. */
|
|
17
|
+
readonly timeout?: Duration.Duration;
|
|
18
|
+
};
|
|
19
|
+
/** One normalized read result. */
|
|
20
|
+
type ReadResponse = {
|
|
21
|
+
/** The URL that was read. */readonly url: string; /** Extracted content in the requested {@link ReadFormat}. */
|
|
22
|
+
readonly content: string; /** Page title, when reported. */
|
|
23
|
+
readonly title?: string; /** Links discovered on the page, when returned. */
|
|
24
|
+
readonly links?: ReadonlyArray<string>; /** The provider-native response. */
|
|
25
|
+
readonly raw: unknown;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* The portable read surface: one operation, `read`. Every implementor can
|
|
29
|
+
* answer, so this capability needs no marker tags.
|
|
30
|
+
*/
|
|
31
|
+
type WebReadService = {
|
|
32
|
+
readonly read: (request: CommonReadRequest) => Effect.Effect<ReadResponse, AiError>;
|
|
33
|
+
};
|
|
34
|
+
declare const WebRead_base: Context.ServiceClass<WebRead, "@betalyra/effect-uai/WebRead", WebReadService>;
|
|
35
|
+
/**
|
|
36
|
+
* Generic read service tag. Yield this for provider-portable code; yield a
|
|
37
|
+
* provider tag for provider-specific knobs. A provider `layer` registers both.
|
|
38
|
+
*/
|
|
39
|
+
declare class WebRead extends WebRead_base {}
|
|
40
|
+
/** Read one URL against whichever provider Layer is in scope. */
|
|
41
|
+
declare const read: (request: CommonReadRequest) => Effect.Effect<ReadResponse, AiError, WebRead>;
|
|
42
|
+
//#endregion
|
|
43
|
+
export { CommonReadRequest, ReadFormat, ReadResponse, WebRead, WebReadService, read, WebRead_d_exports as t };
|
|
44
|
+
//# sourceMappingURL=WebRead.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebRead.d.mts","names":[],"sources":["../../src/web-read/WebRead.ts"],"mappings":";;;;;;;;KAQY,UAAA;;;;;KAUA,iBAAA;mCAED,GAAA;WAEA,MAAA,GAAS,UAAA,EAdR;EAAA,SAgBD,OAAA,GAAU,QAAA,CAAS,QAAA;AAAA;;KAQlB,YAAA;EAdA,sCAgBD,GAAA;WAEA,OAAA,UAhBA;EAAA,SAkBA,KAAA,WAhBS;EAAA,SAkBT,KAAA,GAAQ,aAAA,UAhBE;EAAA,SAkBV,GAAA;AAAA;;AAVX;;;KAqBY,cAAA;EAAA,SACD,IAAA,GAAO,OAAA,EAAS,iBAAA,KAAsB,MAAA,CAAO,MAAA,CAAO,YAAA,EAAc,OAAA;AAAA;AAAA,cAC5E,YAAA;;;;;cAMY,OAAA,SAAgB,YAAA;;cAKhB,IAAA,GACX,OAAA,EAAS,iBAAA,KACR,MAAA,CAAO,MAAA,CAAO,YAAA,EAAc,OAAA,EAAiB,OAAA"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { n as __exportAll } from "../chunk-uyGKjUfl.mjs";
|
|
2
|
+
import { Context, Effect } from "effect";
|
|
3
|
+
//#region src/web-read/WebRead.ts
|
|
4
|
+
var WebRead_exports = /* @__PURE__ */ __exportAll({
|
|
5
|
+
WebRead: () => WebRead,
|
|
6
|
+
read: () => read
|
|
7
|
+
});
|
|
8
|
+
/**
|
|
9
|
+
* Generic read service tag. Yield this for provider-portable code; yield a
|
|
10
|
+
* provider tag for provider-specific knobs. A provider `layer` registers both.
|
|
11
|
+
*/
|
|
12
|
+
var WebRead = class extends Context.Service()("@betalyra/effect-uai/WebRead") {};
|
|
13
|
+
/** Read one URL against whichever provider Layer is in scope. */
|
|
14
|
+
const read = (request) => Effect.flatMap(WebRead.asEffect(), (s) => s.read(request));
|
|
15
|
+
//#endregion
|
|
16
|
+
export { WebRead, read, WebRead_exports as t };
|
|
17
|
+
|
|
18
|
+
//# sourceMappingURL=WebRead.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebRead.mjs","names":[],"sources":["../../src/web-read/WebRead.ts"],"sourcesContent":["import { Context, type Duration, Effect } from \"effect\"\nimport type * as AiError from \"../domain/AiError.js\"\n\n// ---------------------------------------------------------------------------\n// Format\n// ---------------------------------------------------------------------------\n\n/** Representation to return for a page. `markdown` is the default. */\nexport type ReadFormat = \"markdown\" | \"html\"\n\n// ---------------------------------------------------------------------------\n// Request\n// ---------------------------------------------------------------------------\n\n/**\n * Cross-provider read request: one URL in, clean content out. Provider-specific\n * knobs live on the provider-typed request.\n */\nexport type CommonReadRequest = {\n /** The page to read. */\n readonly url: string\n /** Output representation. Defaults to `markdown`. See {@link ReadFormat}. */\n readonly format?: ReadFormat\n /** Upper bound on time spent fetching the page. */\n readonly timeout?: Duration.Duration\n}\n\n// ---------------------------------------------------------------------------\n// Response\n// ---------------------------------------------------------------------------\n\n/** One normalized read result. */\nexport type ReadResponse = {\n /** The URL that was read. */\n readonly url: string\n /** Extracted content in the requested {@link ReadFormat}. */\n readonly content: string\n /** Page title, when reported. */\n readonly title?: string\n /** Links discovered on the page, when returned. */\n readonly links?: ReadonlyArray<string>\n /** The provider-native response. */\n readonly raw: unknown\n}\n\n// ---------------------------------------------------------------------------\n// Service + helper\n// ---------------------------------------------------------------------------\n\n/**\n * The portable read surface: one operation, `read`. Every implementor can\n * answer, so this capability needs no marker tags.\n */\nexport type WebReadService = {\n readonly read: (request: CommonReadRequest) => Effect.Effect<ReadResponse, AiError.AiError>\n}\n\n/**\n * Generic read service tag. Yield this for provider-portable code; yield a\n * provider tag for provider-specific knobs. A provider `layer` registers both.\n */\nexport class WebRead extends Context.Service<WebRead, WebReadService>()(\n \"@betalyra/effect-uai/WebRead\",\n) {}\n\n/** Read one URL against whichever provider Layer is in scope. */\nexport const read = (\n request: CommonReadRequest,\n): Effect.Effect<ReadResponse, AiError.AiError, WebRead> =>\n Effect.flatMap(WebRead.asEffect(), (s) => s.read(request))\n"],"mappings":";;;;;;;;;;;AA6DA,IAAa,UAAb,cAA6B,QAAQ,SAAkC,CACrE,+BACD,CAAC;;AAGF,MAAa,QACX,YAEA,OAAO,QAAQ,QAAQ,UAAU,GAAG,MAAM,EAAE,KAAK,QAAQ,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { l as Tool } from "../Tool-2lC_XMth.mjs";
|
|
2
|
+
import { t as AiError } from "../AiError-Bf1pq4BB.mjs";
|
|
3
|
+
import { ReadFormat, ReadResponse, WebRead } from "./WebRead.mjs";
|
|
4
|
+
import { Schema } from "effect";
|
|
5
|
+
|
|
6
|
+
//#region src/web-read/WebReadTool.d.ts
|
|
7
|
+
declare namespace WebReadTool_d_exports {
|
|
8
|
+
export { WebReadToolArgs, WebReadToolOptions, webReadTool };
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Model-facing argument schema. Deliberately narrow: the URL is the only
|
|
12
|
+
* thing that is genuinely the model's call. Output format and the content
|
|
13
|
+
* cost ceiling are app policy, pinned on the constructor instead, so they
|
|
14
|
+
* are guaranteed rather than left to the model to remember each call.
|
|
15
|
+
*/
|
|
16
|
+
declare const WebReadToolArgs: Schema.Struct<{
|
|
17
|
+
readonly url: Schema.String;
|
|
18
|
+
}>;
|
|
19
|
+
type WebReadToolArgs = typeof WebReadToolArgs.Type;
|
|
20
|
+
type WebReadToolOptions = {
|
|
21
|
+
/** Tool name the model sees. Default `"web_read"`. */readonly name?: string;
|
|
22
|
+
/**
|
|
23
|
+
* App-fixed representation for every read - a policy choice, not a model
|
|
24
|
+
* knob. Default `"markdown"` (what models read best).
|
|
25
|
+
*/
|
|
26
|
+
readonly format?: ReadFormat;
|
|
27
|
+
/**
|
|
28
|
+
* App-fixed ceiling on rendered content characters per call - a context
|
|
29
|
+
* cost guard, not exposed to the model. Default `20000`.
|
|
30
|
+
*/
|
|
31
|
+
readonly maxChars?: number;
|
|
32
|
+
/**
|
|
33
|
+
* Override how a read response is rendered into the model-facing string.
|
|
34
|
+
* Default: `title / url` header plus the content, truncated at `maxChars`.
|
|
35
|
+
*/
|
|
36
|
+
readonly render?: (response: ReadResponse) => string;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* The canonical web-read tool. Its `R` is just `WebRead`, so providing any
|
|
40
|
+
* read-provider Layer satisfies it and the model-facing contract (name,
|
|
41
|
+
* description, schema) is identical no matter which backend answers - swap
|
|
42
|
+
* `FirecrawlRead.layer` for `JinaRead.layer` and the tool the model sees
|
|
43
|
+
* does not change. Drops straight into a `Toolkit` next to `webSearchTool`
|
|
44
|
+
* (search finds the URLs, read fetches the ones worth reading in full).
|
|
45
|
+
*
|
|
46
|
+
* `Output` is the rendered page; failures stay typed on the `AiError`
|
|
47
|
+
* channel. To let the model recover from a failed read, wrap the toolkit in
|
|
48
|
+
* `Toolkit.describeFailures(AiError.describe)`.
|
|
49
|
+
*
|
|
50
|
+
* This is a simple default implementation for quick use. For more elaborate
|
|
51
|
+
* cases (a different output contract, more model-facing knobs), build your
|
|
52
|
+
* own tool with `Tool.make` over the same `read` helper.
|
|
53
|
+
*/
|
|
54
|
+
declare const webReadTool: (options?: WebReadToolOptions) => Tool<string, WebReadToolArgs, never, string, AiError, WebRead>;
|
|
55
|
+
//#endregion
|
|
56
|
+
export { WebReadToolArgs, WebReadToolOptions, WebReadTool_d_exports as t, webReadTool };
|
|
57
|
+
//# sourceMappingURL=WebReadTool.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebReadTool.d.mts","names":[],"sources":["../../src/web-read/WebReadTool.ts"],"mappings":";;;;;;;;;;;;;;;cAWM,eAAA,EAAe,MAAA,CAAA,MAAA;EAAA,cAInB,MAAA,CAAA,MAAA;AAAA;AAAA,KAEU,eAAA,UAAyB,eAAA,CAAgB,IAAA;AAAA,KAYzC,kBAAA;EAlBS,+DAoBV,IAAA;EApBU;;;;EAAA,SAyBV,MAAA,GAAS,UAAA;;AAnBpB;;;WAwBW,QAAA;EAxB8C;AAYzD;;;EAZyD,SA6B9C,MAAA,IAAU,QAAA,EAAU,YAAA;AAAA;;;;;;;;;AAmB/B;;;;;;;;cAAa,WAAA,GACX,OAAA,GAAU,kBAAA,KACT,IAAA,SAAkB,eAAA,iBAAgC,OAAA,EAAiB,OAAA"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { n as __exportAll } from "../chunk-uyGKjUfl.mjs";
|
|
2
|
+
import { fromEffectSchema, make } from "../tool/Tool.mjs";
|
|
3
|
+
import { read } from "./WebRead.mjs";
|
|
4
|
+
import { Effect, Schema } from "effect";
|
|
5
|
+
//#region src/web-read/WebReadTool.ts
|
|
6
|
+
var WebReadTool_exports = /* @__PURE__ */ __exportAll({ webReadTool: () => webReadTool });
|
|
7
|
+
/**
|
|
8
|
+
* Model-facing argument schema. Deliberately narrow: the URL is the only
|
|
9
|
+
* thing that is genuinely the model's call. Output format and the content
|
|
10
|
+
* cost ceiling are app policy, pinned on the constructor instead, so they
|
|
11
|
+
* are guaranteed rather than left to the model to remember each call.
|
|
12
|
+
*/
|
|
13
|
+
const WebReadToolArgs = Schema.Struct({ url: Schema.String.annotate({ description: "Absolute URL of the page to read." }) });
|
|
14
|
+
/** `title / url` header plus the extracted content - what a model reads best. */
|
|
15
|
+
const defaultRender = (response, maxChars) => {
|
|
16
|
+
return `${response.title === void 0 ? response.url : `${response.title}\n${response.url}`}\n\n${response.content.length > maxChars ? `${response.content.slice(0, maxChars)}\n[content truncated at ${maxChars} characters]` : response.content}`;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* The canonical web-read tool. Its `R` is just `WebRead`, so providing any
|
|
20
|
+
* read-provider Layer satisfies it and the model-facing contract (name,
|
|
21
|
+
* description, schema) is identical no matter which backend answers - swap
|
|
22
|
+
* `FirecrawlRead.layer` for `JinaRead.layer` and the tool the model sees
|
|
23
|
+
* does not change. Drops straight into a `Toolkit` next to `webSearchTool`
|
|
24
|
+
* (search finds the URLs, read fetches the ones worth reading in full).
|
|
25
|
+
*
|
|
26
|
+
* `Output` is the rendered page; failures stay typed on the `AiError`
|
|
27
|
+
* channel. To let the model recover from a failed read, wrap the toolkit in
|
|
28
|
+
* `Toolkit.describeFailures(AiError.describe)`.
|
|
29
|
+
*
|
|
30
|
+
* This is a simple default implementation for quick use. For more elaborate
|
|
31
|
+
* cases (a different output contract, more model-facing knobs), build your
|
|
32
|
+
* own tool with `Tool.make` over the same `read` helper.
|
|
33
|
+
*/
|
|
34
|
+
const webReadTool = (options) => {
|
|
35
|
+
const name = options?.name ?? "web_read";
|
|
36
|
+
const format = options?.format ?? "markdown";
|
|
37
|
+
const maxChars = options?.maxChars ?? 2e4;
|
|
38
|
+
const render = options?.render ?? ((response) => defaultRender(response, maxChars));
|
|
39
|
+
return make({
|
|
40
|
+
name,
|
|
41
|
+
description: "Read a web page and return its content as clean text. Use it to read a promising URL in full (e.g. one found via search).",
|
|
42
|
+
inputSchema: fromEffectSchema(WebReadToolArgs),
|
|
43
|
+
run: (args) => read({
|
|
44
|
+
url: args.url,
|
|
45
|
+
format
|
|
46
|
+
}).pipe(Effect.tap((r) => Effect.annotateCurrentSpan("web_read.content_chars", r.content.length)), Effect.map(render), Effect.withSpan(name, {
|
|
47
|
+
kind: "client",
|
|
48
|
+
attributes: {
|
|
49
|
+
"web_read.url": args.url,
|
|
50
|
+
"web_read.format": format,
|
|
51
|
+
"web_read.max_chars": maxChars
|
|
52
|
+
}
|
|
53
|
+
}))
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
//#endregion
|
|
57
|
+
export { WebReadTool_exports as t, webReadTool };
|
|
58
|
+
|
|
59
|
+
//# sourceMappingURL=WebReadTool.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebReadTool.mjs","names":["Tool.make","Tool.fromEffectSchema"],"sources":["../../src/web-read/WebReadTool.ts"],"sourcesContent":["import { Effect, Schema } from \"effect\"\nimport type * as AiError from \"../domain/AiError.js\"\nimport * as Tool from \"../tool/Tool.js\"\nimport { read, type ReadFormat, type ReadResponse, WebRead } from \"./WebRead.js\"\n\n/**\n * Model-facing argument schema. Deliberately narrow: the URL is the only\n * thing that is genuinely the model's call. Output format and the content\n * cost ceiling are app policy, pinned on the constructor instead, so they\n * are guaranteed rather than left to the model to remember each call.\n */\nconst WebReadToolArgs = Schema.Struct({\n url: Schema.String.annotate({\n description: \"Absolute URL of the page to read.\",\n }),\n})\n\nexport type WebReadToolArgs = typeof WebReadToolArgs.Type\n\n/** `title / url` header plus the extracted content - what a model reads best. */\nconst defaultRender = (response: ReadResponse, maxChars: number): string => {\n const header = response.title === undefined ? response.url : `${response.title}\\n${response.url}`\n const body =\n response.content.length > maxChars\n ? `${response.content.slice(0, maxChars)}\\n[content truncated at ${maxChars} characters]`\n : response.content\n return `${header}\\n\\n${body}`\n}\n\nexport type WebReadToolOptions = {\n /** Tool name the model sees. Default `\"web_read\"`. */\n readonly name?: string\n /**\n * App-fixed representation for every read - a policy choice, not a model\n * knob. Default `\"markdown\"` (what models read best).\n */\n readonly format?: ReadFormat\n /**\n * App-fixed ceiling on rendered content characters per call - a context\n * cost guard, not exposed to the model. Default `20000`.\n */\n readonly maxChars?: number\n /**\n * Override how a read response is rendered into the model-facing string.\n * Default: `title / url` header plus the content, truncated at `maxChars`.\n */\n readonly render?: (response: ReadResponse) => string\n}\n\n/**\n * The canonical web-read tool. Its `R` is just `WebRead`, so providing any\n * read-provider Layer satisfies it and the model-facing contract (name,\n * description, schema) is identical no matter which backend answers - swap\n * `FirecrawlRead.layer` for `JinaRead.layer` and the tool the model sees\n * does not change. Drops straight into a `Toolkit` next to `webSearchTool`\n * (search finds the URLs, read fetches the ones worth reading in full).\n *\n * `Output` is the rendered page; failures stay typed on the `AiError`\n * channel. To let the model recover from a failed read, wrap the toolkit in\n * `Toolkit.describeFailures(AiError.describe)`.\n *\n * This is a simple default implementation for quick use. For more elaborate\n * cases (a different output contract, more model-facing knobs), build your\n * own tool with `Tool.make` over the same `read` helper.\n */\nexport const webReadTool = (\n options?: WebReadToolOptions,\n): Tool.Tool<string, WebReadToolArgs, never, string, AiError.AiError, WebRead> => {\n const name = options?.name ?? \"web_read\"\n const format = options?.format ?? \"markdown\"\n const maxChars = options?.maxChars ?? 20_000\n const render = options?.render ?? ((response: ReadResponse) => defaultRender(response, maxChars))\n return Tool.make({\n name,\n description:\n \"Read a web page and return its content as clean text. Use it to read a promising URL in full (e.g. one found via search).\",\n inputSchema: Tool.fromEffectSchema(WebReadToolArgs),\n // The call is wrapped in a tracing span so agent traces show the tool\n // invocation (url, format, content length) nested under the model turn.\n // `withSpan` is a no-op until a Tracer is installed, so this is free by\n // default and adds nothing to the requirements.\n run: (args) =>\n read({ url: args.url, format }).pipe(\n Effect.tap((r) => Effect.annotateCurrentSpan(\"web_read.content_chars\", r.content.length)),\n Effect.map(render),\n Effect.withSpan(name, {\n kind: \"client\",\n attributes: {\n \"web_read.url\": args.url,\n \"web_read.format\": format,\n \"web_read.max_chars\": maxChars,\n },\n }),\n ),\n })\n}\n"],"mappings":";;;;;;;;;;;;AAWA,MAAM,kBAAkB,OAAO,OAAO,EACpC,KAAK,OAAO,OAAO,SAAS,EAC1B,aAAa,qCACd,CAAC,EACH,CAAC;;AAKF,MAAM,iBAAiB,UAAwB,aAA6B;AAM1E,QAAO,GALQ,SAAS,UAAU,KAAA,IAAY,SAAS,MAAM,GAAG,SAAS,MAAM,IAAI,SAAS,MAK3E,MAHf,SAAS,QAAQ,SAAS,WACtB,GAAG,SAAS,QAAQ,MAAM,GAAG,SAAS,CAAC,0BAA0B,SAAS,gBAC1E,SAAS;;;;;;;;;;;;;;;;;;AAwCjB,MAAa,eACX,YACgF;CAChF,MAAM,OAAO,SAAS,QAAQ;CAC9B,MAAM,SAAS,SAAS,UAAU;CAClC,MAAM,WAAW,SAAS,YAAY;CACtC,MAAM,SAAS,SAAS,YAAY,aAA2B,cAAc,UAAU,SAAS;AAChG,QAAOA,KAAU;EACf;EACA,aACE;EACF,aAAaC,iBAAsB,gBAAgB;EAKnD,MAAM,SACJ,KAAK;GAAE,KAAK,KAAK;GAAK;GAAQ,CAAC,CAAC,KAC9B,OAAO,KAAK,MAAM,OAAO,oBAAoB,0BAA0B,EAAE,QAAQ,OAAO,CAAC,EACzF,OAAO,IAAI,OAAO,EAClB,OAAO,SAAS,MAAM;GACpB,MAAM;GACN,YAAY;IACV,gBAAgB,KAAK;IACrB,mBAAmB;IACnB,sBAAsB;IACvB;GACF,CAAC,CACH;EACJ,CAAC"}
|