@effect-uai/core 0.9.0 → 0.11.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-CrdR1EQi.d.mts → AiError-Bf1pq4BB.d.mts} +8 -3
- package/dist/{AiError-CrdR1EQi.d.mts.map → AiError-Bf1pq4BB.d.mts.map} +1 -1
- package/dist/{Audio-DDSKCkSe.d.mts → Audio-CR7CSljJ.d.mts} +2 -2
- package/dist/{Audio-DDSKCkSe.d.mts.map → Audio-CR7CSljJ.d.mts.map} +1 -1
- package/dist/{Image-C_Zjpwhc.d.mts → Image-C4vuBmu0.d.mts} +12 -12
- package/dist/{Image-C_Zjpwhc.d.mts.map → Image-C4vuBmu0.d.mts.map} +1 -1
- package/dist/{Items-CgKD6Npw.d.mts → Items-BWNu6VGp.d.mts} +118 -92
- package/dist/Items-BWNu6VGp.d.mts.map +1 -0
- package/dist/{Loop-B5_cqumi.d.mts → Loop-D8nWpowc.d.mts} +2 -2
- package/dist/{Loop-B5_cqumi.d.mts.map → Loop-D8nWpowc.d.mts.map} +1 -1
- package/dist/{Media-DOLyaygI.d.mts → Media-D_CpcM1Z.d.mts} +1 -1
- package/dist/{Media-DOLyaygI.d.mts.map → Media-D_CpcM1Z.d.mts.map} +1 -1
- package/dist/{StructuredFormat-CMJFD3sn.d.mts → StructuredFormat-DHaUJBdd.d.mts} +1 -1
- package/dist/{StructuredFormat-CMJFD3sn.d.mts.map → StructuredFormat-DHaUJBdd.d.mts.map} +1 -1
- package/dist/{Tool-0XUIT2A2.d.mts → Tool-CXFHCQrT.d.mts} +101 -16
- package/dist/Tool-CXFHCQrT.d.mts.map +1 -0
- package/dist/{ToolEvent-DYrAUJGs.d.mts → ToolEvent-nVijCrvm.d.mts} +2 -2
- package/dist/ToolEvent-nVijCrvm.d.mts.map +1 -0
- package/dist/{ToolResult-BZd-cEWW.d.mts → ToolResult-CQqKkcDa.d.mts} +10 -4
- package/dist/ToolResult-CQqKkcDa.d.mts.map +1 -0
- package/dist/{Toolkit-BKC8b8Xo.d.mts → Toolkit-BYPwwu-j.d.mts} +65 -24
- package/dist/Toolkit-BYPwwu-j.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 +59 -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-DWgRbDN3.mjs → dist-C0m0QnUP.mjs} +71 -30
- package/dist/dist-C0m0QnUP.mjs.map +1 -0
- package/dist/{dist-B04dlFHg.mjs → dist-CcRV3-A5.mjs} +8 -8
- package/dist/{dist-B04dlFHg.mjs.map → dist-CcRV3-A5.mjs.map} +1 -1
- 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/Citation.d.mts +60 -0
- package/dist/domain/Citation.d.mts.map +1 -0
- package/dist/domain/Citation.mjs +7 -0
- package/dist/domain/Citation.mjs.map +1 -0
- package/dist/domain/Image.d.mts +1 -1
- package/dist/domain/Items.d.mts +1 -1
- package/dist/domain/Items.mjs +13 -3
- package/dist/domain/Items.mjs.map +1 -1
- package/dist/domain/Media.d.mts +1 -1
- package/dist/domain/Music.d.mts +1 -1
- package/dist/domain/Research.d.mts +37 -0
- package/dist/domain/Research.d.mts.map +1 -0
- package/dist/domain/Research.mjs +7 -0
- package/dist/domain/Research.mjs.map +1 -0
- package/dist/domain/Turn.d.mts +92 -10
- package/dist/domain/Turn.d.mts.map +1 -1
- package/dist/domain/Turn.mjs +8 -1
- package/dist/domain/Turn.mjs.map +1 -1
- 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.mjs +2 -2
- package/dist/embedding-model/EmbeddingModel.mjs.map +1 -1
- package/dist/embedding-model/EmbeddingModel.test.mjs +1 -1
- package/dist/index.d.mts +22 -13
- package/dist/index.mjs +18 -9
- package/dist/job/Job.d.mts +80 -0
- package/dist/job/Job.d.mts.map +1 -0
- package/dist/job/Job.mjs +49 -0
- package/dist/job/Job.mjs.map +1 -0
- package/dist/language-model/LanguageModel.d.mts +4 -4
- package/dist/language-model/LanguageModel.mjs +2 -2
- package/dist/language-model/LanguageModel.mjs.map +1 -1
- package/dist/language-model/LanguageModel.test.mjs +1 -1
- package/dist/loop/Loop.d.mts +1 -1
- package/dist/loop/Loop.test.mjs +1 -1
- package/dist/music-generator/MusicGenerator.d.mts +2 -2
- package/dist/music-generator/MusicGenerator.mjs +4 -4
- package/dist/music-generator/MusicGenerator.mjs.map +1 -1
- package/dist/music-generator/MusicGenerator.test.mjs +1 -1
- package/dist/observability/Metrics.d.mts +1 -1
- package/dist/observability/Metrics.test.mjs +2 -2
- package/dist/observability/Telemetry.test.mjs +2 -2
- package/dist/research/DeepResearch.d.mts +71 -0
- package/dist/research/DeepResearch.d.mts.map +1 -0
- package/dist/research/DeepResearch.mjs +61 -0
- package/dist/research/DeepResearch.mjs.map +1 -0
- package/dist/retry/Retry.d.mts +1 -1
- package/dist/retry/Retry.test.mjs +1 -1
- package/dist/sandbox/Sandbox.mjs +18 -18
- package/dist/sandbox/Sandbox.mjs.map +1 -1
- package/dist/speech-synthesizer/SpeechSynthesizer.d.mts +2 -2
- package/dist/speech-synthesizer/SpeechSynthesizer.mjs +8 -8
- package/dist/speech-synthesizer/SpeechSynthesizer.mjs.map +1 -1
- package/dist/speech-synthesizer/SpeechSynthesizer.test.mjs +1 -1
- package/dist/streaming/JSONL.d.mts +3 -1
- package/dist/streaming/JSONL.d.mts.map +1 -1
- package/dist/streaming/JSONL.mjs +5 -11
- package/dist/streaming/JSONL.mjs.map +1 -1
- package/dist/streaming/JSONL.test.mjs +10 -1
- package/dist/streaming/JSONL.test.mjs.map +1 -1
- package/dist/streaming/SSE.d.mts +5 -3
- package/dist/streaming/SSE.d.mts.map +1 -1
- package/dist/streaming/SSE.mjs +28 -38
- package/dist/streaming/SSE.mjs.map +1 -1
- package/dist/streaming/SSE.test.mjs +54 -4
- package/dist/streaming/SSE.test.mjs.map +1 -1
- package/dist/structured-format/StructuredFormat.d.mts +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 +47 -8
- 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 +112 -28
- package/dist/tool/Tool.mjs.map +1 -1
- package/dist/tool/Tool.test.mjs +127 -3
- package/dist/tool/Tool.test.mjs.map +1 -1
- package/dist/tool/ToolEvent.d.mts +1 -1
- package/dist/tool/ToolEvent.mjs +5 -0
- package/dist/tool/ToolEvent.mjs.map +1 -1
- package/dist/tool/ToolResult.d.mts +2 -2
- package/dist/tool/ToolResult.mjs +15 -6
- package/dist/tool/ToolResult.mjs.map +1 -1
- package/dist/tool/Toolkit.d.mts +2 -2
- package/dist/tool/Toolkit.mjs +62 -22
- package/dist/tool/Toolkit.mjs.map +1 -1
- package/dist/tool/Toolkit.test.mjs +207 -9
- package/dist/tool/Toolkit.test.mjs.map +1 -1
- package/dist/transcriber/Transcriber.d.mts +2 -2
- package/dist/transcriber/Transcriber.mjs +3 -3
- package/dist/transcriber/Transcriber.mjs.map +1 -1
- 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/WebSearch.mjs +1 -1
- package/dist/web-search/WebSearch.mjs.map +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 +39 -3
- 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/domain/Citation.ts +64 -0
- package/src/domain/Items.ts +14 -4
- package/src/domain/Research.ts +42 -0
- package/src/domain/Turn.ts +26 -0
- package/src/embedding-model/EmbeddingModel.ts +2 -2
- package/src/index.ts +9 -0
- package/src/job/Job.ts +125 -0
- package/src/language-model/LanguageModel.ts +2 -2
- package/src/music-generator/MusicGenerator.ts +4 -4
- package/src/research/DeepResearch.ts +137 -0
- package/src/sandbox/Sandbox.ts +18 -18
- package/src/speech-synthesizer/SpeechSynthesizer.ts +8 -8
- package/src/streaming/JSONL.test.ts +19 -0
- package/src/streaming/JSONL.ts +6 -38
- package/src/streaming/SSE.test.ts +86 -3
- package/src/streaming/SSE.ts +36 -80
- package/src/tool/Approval.test.ts +55 -5
- package/src/tool/Approval.ts +72 -27
- package/src/tool/Tool.test.ts +151 -1
- package/src/tool/Tool.ts +263 -66
- package/src/tool/ToolEvent.ts +5 -0
- package/src/tool/ToolResult.ts +14 -8
- package/src/tool/Toolkit.test.ts +231 -2
- package/src/tool/Toolkit.ts +175 -62
- package/src/transcriber/Transcriber.ts +3 -3
- package/src/web-read/WebRead.ts +70 -0
- package/src/web-read/WebReadTool.ts +96 -0
- package/src/web-search/WebSearch.ts +1 -1
- package/src/web-search/WebSearchTool.ts +11 -5
- package/dist/Items-CgKD6Npw.d.mts.map +0 -1
- package/dist/Tool-0XUIT2A2.d.mts.map +0 -1
- package/dist/ToolEvent-DYrAUJGs.d.mts.map +0 -1
- package/dist/ToolResult-BZd-cEWW.d.mts.map +0 -1
- package/dist/Toolkit-BKC8b8Xo.d.mts.map +0 -1
- package/dist/dist-DWgRbDN3.mjs.map +0 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { toolCallOutput, userText } from "../domain/Items.mjs";
|
|
2
|
-
import { fromEffectSchema, make } from "./Tool.mjs";
|
|
3
2
|
import { isFailure, isOk, toToolCallOutput } from "./ToolResult.mjs";
|
|
3
|
+
import { fromEffectSchema, make } from "./Tool.mjs";
|
|
4
4
|
import { ToolEvent, isApprovalRequested, isOutput, isProgress } from "./ToolEvent.mjs";
|
|
5
5
|
import { make as make$1, run } from "./Toolkit.mjs";
|
|
6
6
|
import { fromMap, fromQueue } from "./Approval.mjs";
|
|
7
7
|
import { cancelAllPending, findUnansweredCalls, isReconciled } from "./HistoryCheck.mjs";
|
|
8
|
-
import {
|
|
8
|
+
import { c as it, n as globalExpect, o as describe } from "../dist-CcRV3-A5.mjs";
|
|
9
9
|
import { Effect, Queue, Schema, Stream } from "effect";
|
|
10
10
|
//#region src/tool/Approval.test.ts
|
|
11
11
|
/**
|
|
@@ -225,6 +225,45 @@ describe("fromQueue + run", () => {
|
|
|
225
225
|
reason: "too risky"
|
|
226
226
|
});
|
|
227
227
|
});
|
|
228
|
+
it("router retires per round, so sequential rounds over one queue both resolve", async () => {
|
|
229
|
+
const decisions = await Effect.runPromise(Effect.scoped(Effect.gen(function* () {
|
|
230
|
+
const verdicts = yield* Queue.unbounded();
|
|
231
|
+
const round = (id) => [fc(id, "delete_database", { name: "prod" })];
|
|
232
|
+
const r1 = yield* fromQueue(isSensitive, verdicts)(round("c1"));
|
|
233
|
+
yield* Queue.offer(verdicts, {
|
|
234
|
+
call_id: "c1",
|
|
235
|
+
decision: "approve"
|
|
236
|
+
});
|
|
237
|
+
const d1 = Array.from(yield* Stream.runCollect(r1.decisions));
|
|
238
|
+
const r2 = yield* fromQueue(isSensitive, verdicts)(round("c2"));
|
|
239
|
+
yield* Queue.offer(verdicts, {
|
|
240
|
+
call_id: "c2",
|
|
241
|
+
decision: "deny",
|
|
242
|
+
reason: "no"
|
|
243
|
+
});
|
|
244
|
+
return {
|
|
245
|
+
d1,
|
|
246
|
+
d2: Array.from(yield* Stream.runCollect(r2.decisions))
|
|
247
|
+
};
|
|
248
|
+
})));
|
|
249
|
+
globalExpect(decisions.d1[0]).toMatchObject({ _tag: "Approved" });
|
|
250
|
+
globalExpect(decisions.d2[0]).toMatchObject({
|
|
251
|
+
_tag: "Rejected",
|
|
252
|
+
result: { kind: "denied" }
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
it("timeout resolves an unanswered gated call as cancelled", async () => {
|
|
256
|
+
globalExpect((await Effect.runPromise(Effect.scoped(Effect.gen(function* () {
|
|
257
|
+
const { decisions } = yield* fromQueue(isSensitive, yield* Queue.unbounded(), { timeout: "10 millis" })([fc("c1", "delete_database", { name: "prod" })]);
|
|
258
|
+
return Array.from(yield* Stream.runCollect(decisions));
|
|
259
|
+
}))))[0]).toMatchObject({
|
|
260
|
+
_tag: "Rejected",
|
|
261
|
+
result: {
|
|
262
|
+
_tag: "Failure",
|
|
263
|
+
kind: "cancelled"
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
});
|
|
228
267
|
});
|
|
229
268
|
describe("findUnansweredCalls / cancelAllPending / isReconciled", () => {
|
|
230
269
|
const orphan = fc("c99", "delete_database", { name: "prod" });
|
|
@@ -283,7 +322,7 @@ describe("toToolCallOutput", () => {
|
|
|
283
322
|
globalExpect(out.call_id).toBe("c1");
|
|
284
323
|
globalExpect(JSON.parse(out.output)).toEqual({ count: 3 });
|
|
285
324
|
});
|
|
286
|
-
it("
|
|
325
|
+
it("renders a Failure with reason as a nested error object", () => {
|
|
287
326
|
const out = toToolCallOutput({
|
|
288
327
|
_tag: "Failure",
|
|
289
328
|
call_id: "c2",
|
|
@@ -291,19 +330,19 @@ describe("toToolCallOutput", () => {
|
|
|
291
330
|
kind: "denied",
|
|
292
331
|
reason: "spam concern"
|
|
293
332
|
});
|
|
294
|
-
globalExpect(JSON.parse(out.output)).toEqual({
|
|
333
|
+
globalExpect(JSON.parse(out.output)).toEqual({ error: {
|
|
295
334
|
kind: "denied",
|
|
296
|
-
|
|
297
|
-
});
|
|
335
|
+
message: "spam concern"
|
|
336
|
+
} });
|
|
298
337
|
});
|
|
299
|
-
it("
|
|
338
|
+
it("renders a Failure without reason (omits message)", () => {
|
|
300
339
|
const out = toToolCallOutput({
|
|
301
340
|
_tag: "Failure",
|
|
302
341
|
call_id: "c3",
|
|
303
342
|
tool: "delete_database",
|
|
304
343
|
kind: "cancelled"
|
|
305
344
|
});
|
|
306
|
-
globalExpect(JSON.parse(out.output)).toEqual({ kind: "cancelled" });
|
|
345
|
+
globalExpect(JSON.parse(out.output)).toEqual({ error: { kind: "cancelled" } });
|
|
307
346
|
});
|
|
308
347
|
});
|
|
309
348
|
describe("run", () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Approval.test.mjs","names":["makeToolkit","makeTool","Items.toolCallOutput","Items.userText"],"sources":["../../src/tool/Approval.test.ts"],"sourcesContent":["/**\n * Tests for approval planners + history-reconciliation primitives. Exercises\n * the full HITL + streaming-tool stack end-to-end by composing approval plans\n * with `run`, with the four wire-shaped scenarios:\n *\n * 1. Approval : gated calls approved → tools execute, structured Values\n * 2. Denial : gated calls denied → Failure(denied) results\n * 3. Cancellation : missing verdicts → Failure(cancelled) results\n * 4. Mixed + history : reconciliation via cancelAllPending\n *\n * Plus: hallucinated tool name (graceful Failure), unknown_tool kind.\n */\nimport { Effect, Queue, Schema, Stream } from \"effect\"\nimport { describe, expect, it } from \"vitest\"\nimport * as Items from \"../domain/Items.js\"\nimport { findUnansweredCalls, cancelAllPending, isReconciled } from \"./HistoryCheck.js\"\nimport { type ToolResult, isFailure, isOk, toToolCallOutput } from \"./ToolResult.js\"\nimport { type ApprovalMapEntry, type ApprovalDecision, fromMap, fromQueue } from \"./Approval.js\"\nimport { fromEffectSchema, make as makeTool } from \"./Tool.js\"\nimport { make as makeToolkit, run } from \"./Toolkit.js\"\nimport { ToolEvent, isApprovalRequested, isProgress, isOutput } from \"./ToolEvent.js\"\n\n// ---------------------------------------------------------------------------\n// Three demo tools covering the matrix:\n// - web_search : streaming, no approval\n// - bulk_email : streaming, requires approval\n// - delete_database : non-streaming, requires approval\n// ---------------------------------------------------------------------------\n\nconst webSearch = makeTool({\n name: \"web_search\",\n description: \"search\",\n inputSchema: fromEffectSchema(Schema.Struct({ query: Schema.String })),\n run: ({ query }, emit) =>\n Effect.gen(function* () {\n const hits = [\n { url: \"a\", title: `${query} 1` },\n { url: \"b\", title: `${query} 2` },\n { url: \"c\", title: `${query} 3` },\n ]\n yield* Stream.runForEach(Stream.fromIterable(hits), emit)\n return { count: hits.length }\n }),\n})\n\nconst bulkEmail = makeTool({\n name: \"bulk_email\",\n description: \"send\",\n inputSchema: fromEffectSchema(\n Schema.Struct({ recipients: Schema.Array(Schema.String), subject: Schema.String }),\n ),\n run: ({ recipients }, emit) =>\n Effect.gen(function* () {\n yield* Effect.forEach(recipients, (_, i) =>\n emit({ type: \"progress\" as const, sent: i + 1, total: recipients.length }),\n )\n return { status: \"sent\" as const, delivered: recipients.length }\n }),\n})\n\nconst deleteDatabase = makeTool({\n name: \"delete_database\",\n description: \"drop\",\n inputSchema: fromEffectSchema(Schema.Struct({ name: Schema.String })),\n run: ({ name }) => Effect.succeed({ status: \"dropped\", name }),\n})\n\nconst allTools = makeToolkit(webSearch, bulkEmail, deleteDatabase)\nconst SENSITIVE = new Set([\"bulk_email\", \"delete_database\"])\nconst isSensitive = (call: Items.ToolCall) => SENSITIVE.has(call.name)\n\nconst fc = (call_id: string, name: string, args: unknown): Items.ToolCall => ({\n type: \"function_call\",\n call_id,\n name,\n arguments: JSON.stringify(args),\n})\n\nconst calls = [\n fc(\"c1\", \"web_search\", { query: \"effect\" }),\n fc(\"c2\", \"bulk_email\", { recipients: [\"a@x\", \"b@x\"], subject: \"Hi\" }),\n fc(\"c3\", \"delete_database\", { name: \"prod\" }),\n]\n\nconst resultsFrom = (collected: ReadonlyArray<ToolEvent>): ReadonlyArray<ToolResult> =>\n collected.filter(isOutput).map((e) => e.result)\n\nconst byCallId = (results: ReadonlyArray<ToolResult>) => new Map(results.map((r) => [r.call_id, r]))\n\nconst rejectedStream = (rejected: ReadonlyArray<ToolResult>) =>\n Stream.fromIterable(rejected.map((result) => ToolEvent.Output({ result })))\n\nconst eventsFromApprovalMap = (approvals: ReadonlyMap<string, ApprovalMapEntry>) => {\n const plan = fromMap(isSensitive, approvals)(calls)\n return Stream.merge(run(allTools, plan.approved), rejectedStream(plan.rejected))\n}\n\nconst eventsFromDecision = (decision: ApprovalDecision): Stream.Stream<ToolEvent> =>\n decision._tag === \"Approved\"\n ? run(allTools, [decision.call])\n : Stream.succeed(ToolEvent.Output({ result: decision.result }))\n\n// ---------------------------------------------------------------------------\n// fromMap: HTTP-style scenarios\n// ---------------------------------------------------------------------------\n\ndescribe(\"fromMap + run\", () => {\n it(\"approval: all gated approved → tools execute, structured Values\", async () => {\n const approvals = new Map<string, ApprovalMapEntry>([\n [\"c2\", { decision: \"approve\" }],\n [\"c3\", { decision: \"approve\" }],\n ])\n const collected = await Effect.runPromise(Stream.runCollect(eventsFromApprovalMap(approvals)))\n const by = byCallId(resultsFrom(collected))\n expect(by.get(\"c1\")).toMatchObject({ _tag: \"Ok\", value: { count: 3 } })\n expect(by.get(\"c2\")).toMatchObject({\n _tag: \"Ok\",\n value: { status: \"sent\", delivered: 2 },\n })\n expect(by.get(\"c3\")).toMatchObject({\n _tag: \"Ok\",\n value: { status: \"dropped\", name: \"prod\" },\n })\n\n // No ApprovalRequested events from the pure HTTP flow.\n expect(collected.filter(isApprovalRequested)).toHaveLength(0)\n })\n\n it(\"denial: gated denied → Failure(denied), no execution\", async () => {\n const approvals = new Map<string, ApprovalMapEntry>([\n [\"c2\", { decision: \"deny\", reason: \"spam concern\" }],\n [\"c3\", { decision: \"deny\", reason: \"prod is sacred\" }],\n ])\n const collected = await Effect.runPromise(Stream.runCollect(eventsFromApprovalMap(approvals)))\n\n // bulk_email never ran.\n expect(collected.filter(isProgress).filter((e) => e.tool === \"bulk_email\")).toHaveLength(0)\n\n const by = byCallId(resultsFrom(collected))\n expect(by.get(\"c2\")).toMatchObject({\n _tag: \"Failure\",\n kind: \"denied\",\n reason: \"spam concern\",\n })\n expect(by.get(\"c3\")).toMatchObject({\n _tag: \"Failure\",\n kind: \"denied\",\n reason: \"prod is sacred\",\n })\n })\n\n it(\"cancellation: missing verdicts → Failure(cancelled)\", async () => {\n const collected = await Effect.runPromise(Stream.runCollect(eventsFromApprovalMap(new Map())))\n const by = byCallId(resultsFrom(collected))\n expect(by.get(\"c1\")).toMatchObject({ _tag: \"Ok\", value: { count: 3 } })\n expect(by.get(\"c2\")).toMatchObject({ _tag: \"Failure\", kind: \"cancelled\" })\n expect(by.get(\"c3\")).toMatchObject({ _tag: \"Failure\", kind: \"cancelled\" })\n })\n\n it(\"mixed: approve + deny + omit → all three kinds\", async () => {\n const approvals = new Map<string, ApprovalMapEntry>([\n [\"c2\", { decision: \"approve\" }],\n // c3 omitted → cancelled\n ])\n const collected = await Effect.runPromise(Stream.runCollect(eventsFromApprovalMap(approvals)))\n const by = byCallId(resultsFrom(collected))\n expect(by.get(\"c1\")).toMatchObject({ _tag: \"Ok\", value: { count: 3 } })\n expect(by.get(\"c2\")).toMatchObject({ _tag: \"Ok\", value: { status: \"sent\" } })\n expect(by.get(\"c3\")).toMatchObject({ _tag: \"Failure\", kind: \"cancelled\" })\n })\n})\n\n// ---------------------------------------------------------------------------\n// Graceful degradation: hallucinated tool name doesn't kill the turn.\n// ---------------------------------------------------------------------------\n\ndescribe(\"run: graceful degradation\", () => {\n it(\"unknown tool name → Failure(unknown_tool); other calls still execute\", async () => {\n const callsWithBogus = [\n fc(\"c1\", \"web_search\", { query: \"x\" }),\n fc(\"c2\", \"does_not_exist\", {}),\n fc(\"c3\", \"delete_database\", { name: \"prod\" }),\n ]\n const collected = await Effect.runPromise(\n Stream.runCollect(\n (() => {\n const plan = fromMap(\n isSensitive,\n new Map([[\"c3\", { decision: \"approve\" }]]),\n )(callsWithBogus)\n return Stream.merge(run(allTools, plan.approved), rejectedStream(plan.rejected))\n })(),\n ),\n )\n const by = byCallId(resultsFrom(collected))\n expect(by.get(\"c1\")).toMatchObject({ _tag: \"Ok\" })\n expect(by.get(\"c2\")).toMatchObject({ _tag: \"Failure\", kind: \"unknown_tool\" })\n expect(by.get(\"c3\")).toMatchObject({ _tag: \"Ok\", value: { status: \"dropped\" } })\n })\n})\n\n// ---------------------------------------------------------------------------\n// fromQueue: WebSocket-style scenarios\n// ---------------------------------------------------------------------------\n\ndescribe(\"fromQueue + run\", () => {\n it(\"queue-driven: approve + deny resolve correctly with ApprovalRequested events\", async () => {\n const collected = await Effect.runPromise(\n Effect.gen(function* () {\n const verdicts = yield* Queue.unbounded<{\n readonly call_id: string\n readonly decision: \"approve\" | \"deny\"\n readonly reason?: string\n }>()\n yield* Queue.offer(verdicts, { call_id: \"c2\", decision: \"approve\" })\n yield* Queue.offer(verdicts, {\n call_id: \"c3\",\n decision: \"deny\",\n reason: \"too risky\",\n })\n\n // Stream.unwrap supplies the Scope for fromQueue's router.\n const events = Stream.unwrap(\n Effect.gen(function* () {\n const { approved, decisions, approvalRequests } = yield* fromQueue(\n isSensitive,\n verdicts,\n )(calls)\n return Stream.merge(\n approvalRequests,\n Stream.merge(\n run(allTools, approved),\n decisions.pipe(Stream.flatMap(eventsFromDecision)),\n ),\n )\n }),\n )\n return yield* Stream.runCollect(events)\n }),\n )\n\n expect(collected.filter(isApprovalRequested)).toHaveLength(2)\n\n const by = byCallId(resultsFrom(collected))\n expect(by.get(\"c2\")).toMatchObject({ _tag: \"Ok\", value: { status: \"sent\" } })\n expect(by.get(\"c3\")).toMatchObject({\n _tag: \"Failure\",\n kind: \"denied\",\n reason: \"too risky\",\n })\n })\n})\n\n// ---------------------------------------------------------------------------\n// History reconciliation\n// ---------------------------------------------------------------------------\n\ndescribe(\"findUnansweredCalls / cancelAllPending / isReconciled\", () => {\n const orphan = fc(\"c99\", \"delete_database\", { name: \"prod\" })\n const answered = fc(\"c98\", \"web_search\", { query: \"x\" })\n const answeredOutput = Items.toolCallOutput(\"c98\", JSON.stringify({ count: 0 }))\n\n it(\"findUnansweredCalls returns only orphans\", () => {\n const history = [Items.userText(\"hi\"), answered, orphan, answeredOutput]\n const unanswered = findUnansweredCalls(history)\n expect(unanswered).toHaveLength(1)\n expect(unanswered[0]!.call_id).toBe(\"c99\")\n })\n\n it(\"isReconciled is false when orphans exist, true otherwise\", () => {\n const stale = [Items.userText(\"hi\"), orphan]\n expect(isReconciled(stale)).toBe(false)\n const reconciled = [...stale, ...cancelAllPending(stale).map(toToolCallOutput)]\n expect(isReconciled(reconciled)).toBe(true)\n })\n\n it(\"cancelAllPending synthesizes one Failure(cancelled) per orphan\", () => {\n const history = [Items.userText(\"hi\"), answered, orphan, answeredOutput]\n const closures = cancelAllPending(history, \"user moved on\")\n expect(closures).toHaveLength(1)\n const c = closures[0]!\n expect(isFailure(c)).toBe(true)\n expect(c).toMatchObject({\n _tag: \"Failure\",\n call_id: \"c99\",\n kind: \"cancelled\",\n reason: \"user moved on\",\n })\n })\n\n it(\"follow-up: map closures to ToolCallOutput before appending new user message\", () => {\n const stale = [Items.userText(\"first request\"), orphan]\n const closures = cancelAllPending(stale, \"user redirected\")\n const reconciled = [...stale, ...closures.map(toToolCallOutput), Items.userText(\"never mind\")]\n expect(findUnansweredCalls(reconciled)).toHaveLength(0)\n })\n})\n\n// ---------------------------------------------------------------------------\n// Wire conversion\n// ---------------------------------------------------------------------------\n\ndescribe(\"toToolCallOutput\", () => {\n it(\"round-trips a Value result\", () => {\n const r: ToolResult = {\n _tag: \"Ok\",\n call_id: \"c1\",\n tool: \"web_search\",\n value: { count: 3 },\n }\n const out = toToolCallOutput(r)\n expect(out.call_id).toBe(\"c1\")\n expect(JSON.parse(out.output)).toEqual({ count: 3 })\n })\n\n it(\"round-trips a Failure result with reason\", () => {\n const r: ToolResult = {\n _tag: \"Failure\",\n call_id: \"c2\",\n tool: \"bulk_email\",\n kind: \"denied\",\n reason: \"spam concern\",\n }\n const out = toToolCallOutput(r)\n expect(JSON.parse(out.output)).toEqual({ kind: \"denied\", reason: \"spam concern\" })\n })\n\n it(\"round-trips a Failure result without reason (omits the field)\", () => {\n const r: ToolResult = {\n _tag: \"Failure\",\n call_id: \"c3\",\n tool: \"delete_database\",\n kind: \"cancelled\",\n }\n const out = toToolCallOutput(r)\n expect(JSON.parse(out.output)).toEqual({ kind: \"cancelled\" })\n })\n})\n\n// ---------------------------------------------------------------------------\n// run\n// ---------------------------------------------------------------------------\n\ndescribe(\"run\", () => {\n it(\"runs all calls passed to it\", async () => {\n const collected = await Effect.runPromise(Stream.runCollect(run(allTools, calls)))\n expect(collected.filter(isOutput)).toHaveLength(3)\n expect(collected.filter(isOutput).every((e) => isOk(e.result))).toBe(true)\n })\n})\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Approval.test.mjs","names":["makeToolkit","makeTool","Items.toolCallOutput","Items.userText"],"sources":["../../src/tool/Approval.test.ts"],"sourcesContent":["/**\n * Tests for approval planners + history-reconciliation primitives. Exercises\n * the full HITL + streaming-tool stack end-to-end by composing approval plans\n * with `run`, with the four wire-shaped scenarios:\n *\n * 1. Approval : gated calls approved → tools execute, structured Values\n * 2. Denial : gated calls denied → Failure(denied) results\n * 3. Cancellation : missing verdicts → Failure(cancelled) results\n * 4. Mixed + history : reconciliation via cancelAllPending\n *\n * Plus: hallucinated tool name (graceful Failure), unknown_tool kind.\n */\nimport { Effect, Queue, Schema, Stream } from \"effect\"\nimport { describe, expect, it } from \"vitest\"\nimport * as Items from \"../domain/Items.js\"\nimport { findUnansweredCalls, cancelAllPending, isReconciled } from \"./HistoryCheck.js\"\nimport { type ToolResult, isFailure, isOk, toToolCallOutput } from \"./ToolResult.js\"\nimport {\n type ApprovalMapEntry,\n type ApprovalDecision,\n type Verdict,\n fromMap,\n fromQueue,\n} from \"./Approval.js\"\nimport { fromEffectSchema, make as makeTool } from \"./Tool.js\"\nimport { make as makeToolkit, run } from \"./Toolkit.js\"\nimport { ToolEvent, isApprovalRequested, isProgress, isOutput } from \"./ToolEvent.js\"\n\n// ---------------------------------------------------------------------------\n// Three demo tools covering the matrix:\n// - web_search : streaming, no approval\n// - bulk_email : streaming, requires approval\n// - delete_database : non-streaming, requires approval\n// ---------------------------------------------------------------------------\n\nconst webSearch = makeTool({\n name: \"web_search\",\n description: \"search\",\n inputSchema: fromEffectSchema(Schema.Struct({ query: Schema.String })),\n run: ({ query }, emit) =>\n Effect.gen(function* () {\n const hits = [\n { url: \"a\", title: `${query} 1` },\n { url: \"b\", title: `${query} 2` },\n { url: \"c\", title: `${query} 3` },\n ]\n yield* Stream.runForEach(Stream.fromIterable(hits), emit)\n return { count: hits.length }\n }),\n})\n\nconst bulkEmail = makeTool({\n name: \"bulk_email\",\n description: \"send\",\n inputSchema: fromEffectSchema(\n Schema.Struct({ recipients: Schema.Array(Schema.String), subject: Schema.String }),\n ),\n run: ({ recipients }, emit) =>\n Effect.gen(function* () {\n yield* Effect.forEach(recipients, (_, i) =>\n emit({ type: \"progress\" as const, sent: i + 1, total: recipients.length }),\n )\n return { status: \"sent\" as const, delivered: recipients.length }\n }),\n})\n\nconst deleteDatabase = makeTool({\n name: \"delete_database\",\n description: \"drop\",\n inputSchema: fromEffectSchema(Schema.Struct({ name: Schema.String })),\n run: ({ name }) => Effect.succeed({ status: \"dropped\", name }),\n})\n\nconst allTools = makeToolkit(webSearch, bulkEmail, deleteDatabase)\nconst SENSITIVE = new Set([\"bulk_email\", \"delete_database\"])\nconst isSensitive = (call: Items.ToolCall) => SENSITIVE.has(call.name)\n\nconst fc = (call_id: string, name: string, args: unknown): Items.ToolCall => ({\n type: \"function_call\",\n call_id,\n name,\n arguments: JSON.stringify(args),\n})\n\nconst calls = [\n fc(\"c1\", \"web_search\", { query: \"effect\" }),\n fc(\"c2\", \"bulk_email\", { recipients: [\"a@x\", \"b@x\"], subject: \"Hi\" }),\n fc(\"c3\", \"delete_database\", { name: \"prod\" }),\n]\n\nconst resultsFrom = (collected: ReadonlyArray<ToolEvent>): ReadonlyArray<ToolResult> =>\n collected.filter(isOutput).map((e) => e.result)\n\nconst byCallId = (results: ReadonlyArray<ToolResult>) => new Map(results.map((r) => [r.call_id, r]))\n\nconst rejectedStream = (rejected: ReadonlyArray<ToolResult>) =>\n Stream.fromIterable(rejected.map((result) => ToolEvent.Output({ result })))\n\nconst eventsFromApprovalMap = (approvals: ReadonlyMap<string, ApprovalMapEntry>) => {\n const plan = fromMap(isSensitive, approvals)(calls)\n return Stream.merge(run(allTools, plan.approved), rejectedStream(plan.rejected))\n}\n\nconst eventsFromDecision = (decision: ApprovalDecision): Stream.Stream<ToolEvent> =>\n decision._tag === \"Approved\"\n ? run(allTools, [decision.call])\n : Stream.succeed(ToolEvent.Output({ result: decision.result }))\n\n// ---------------------------------------------------------------------------\n// fromMap: HTTP-style scenarios\n// ---------------------------------------------------------------------------\n\ndescribe(\"fromMap + run\", () => {\n it(\"approval: all gated approved → tools execute, structured Values\", async () => {\n const approvals = new Map<string, ApprovalMapEntry>([\n [\"c2\", { decision: \"approve\" }],\n [\"c3\", { decision: \"approve\" }],\n ])\n const collected = await Effect.runPromise(Stream.runCollect(eventsFromApprovalMap(approvals)))\n const by = byCallId(resultsFrom(collected))\n expect(by.get(\"c1\")).toMatchObject({ _tag: \"Ok\", value: { count: 3 } })\n expect(by.get(\"c2\")).toMatchObject({\n _tag: \"Ok\",\n value: { status: \"sent\", delivered: 2 },\n })\n expect(by.get(\"c3\")).toMatchObject({\n _tag: \"Ok\",\n value: { status: \"dropped\", name: \"prod\" },\n })\n\n // No ApprovalRequested events from the pure HTTP flow.\n expect(collected.filter(isApprovalRequested)).toHaveLength(0)\n })\n\n it(\"denial: gated denied → Failure(denied), no execution\", async () => {\n const approvals = new Map<string, ApprovalMapEntry>([\n [\"c2\", { decision: \"deny\", reason: \"spam concern\" }],\n [\"c3\", { decision: \"deny\", reason: \"prod is sacred\" }],\n ])\n const collected = await Effect.runPromise(Stream.runCollect(eventsFromApprovalMap(approvals)))\n\n // bulk_email never ran.\n expect(collected.filter(isProgress).filter((e) => e.tool === \"bulk_email\")).toHaveLength(0)\n\n const by = byCallId(resultsFrom(collected))\n expect(by.get(\"c2\")).toMatchObject({\n _tag: \"Failure\",\n kind: \"denied\",\n reason: \"spam concern\",\n })\n expect(by.get(\"c3\")).toMatchObject({\n _tag: \"Failure\",\n kind: \"denied\",\n reason: \"prod is sacred\",\n })\n })\n\n it(\"cancellation: missing verdicts → Failure(cancelled)\", async () => {\n const collected = await Effect.runPromise(Stream.runCollect(eventsFromApprovalMap(new Map())))\n const by = byCallId(resultsFrom(collected))\n expect(by.get(\"c1\")).toMatchObject({ _tag: \"Ok\", value: { count: 3 } })\n expect(by.get(\"c2\")).toMatchObject({ _tag: \"Failure\", kind: \"cancelled\" })\n expect(by.get(\"c3\")).toMatchObject({ _tag: \"Failure\", kind: \"cancelled\" })\n })\n\n it(\"mixed: approve + deny + omit → all three kinds\", async () => {\n const approvals = new Map<string, ApprovalMapEntry>([\n [\"c2\", { decision: \"approve\" }],\n // c3 omitted → cancelled\n ])\n const collected = await Effect.runPromise(Stream.runCollect(eventsFromApprovalMap(approvals)))\n const by = byCallId(resultsFrom(collected))\n expect(by.get(\"c1\")).toMatchObject({ _tag: \"Ok\", value: { count: 3 } })\n expect(by.get(\"c2\")).toMatchObject({ _tag: \"Ok\", value: { status: \"sent\" } })\n expect(by.get(\"c3\")).toMatchObject({ _tag: \"Failure\", kind: \"cancelled\" })\n })\n})\n\n// ---------------------------------------------------------------------------\n// Graceful degradation: hallucinated tool name doesn't kill the turn.\n// ---------------------------------------------------------------------------\n\ndescribe(\"run: graceful degradation\", () => {\n it(\"unknown tool name → Failure(unknown_tool); other calls still execute\", async () => {\n const callsWithBogus = [\n fc(\"c1\", \"web_search\", { query: \"x\" }),\n fc(\"c2\", \"does_not_exist\", {}),\n fc(\"c3\", \"delete_database\", { name: \"prod\" }),\n ]\n const collected = await Effect.runPromise(\n Stream.runCollect(\n (() => {\n const plan = fromMap(\n isSensitive,\n new Map([[\"c3\", { decision: \"approve\" }]]),\n )(callsWithBogus)\n return Stream.merge(run(allTools, plan.approved), rejectedStream(plan.rejected))\n })(),\n ),\n )\n const by = byCallId(resultsFrom(collected))\n expect(by.get(\"c1\")).toMatchObject({ _tag: \"Ok\" })\n expect(by.get(\"c2\")).toMatchObject({ _tag: \"Failure\", kind: \"unknown_tool\" })\n expect(by.get(\"c3\")).toMatchObject({ _tag: \"Ok\", value: { status: \"dropped\" } })\n })\n})\n\n// ---------------------------------------------------------------------------\n// fromQueue: WebSocket-style scenarios\n// ---------------------------------------------------------------------------\n\ndescribe(\"fromQueue + run\", () => {\n it(\"queue-driven: approve + deny resolve correctly with ApprovalRequested events\", async () => {\n const collected = await Effect.runPromise(\n Effect.gen(function* () {\n const verdicts = yield* Queue.unbounded<{\n readonly call_id: string\n readonly decision: \"approve\" | \"deny\"\n readonly reason?: string\n }>()\n yield* Queue.offer(verdicts, { call_id: \"c2\", decision: \"approve\" })\n yield* Queue.offer(verdicts, {\n call_id: \"c3\",\n decision: \"deny\",\n reason: \"too risky\",\n })\n\n // Stream.unwrap supplies the Scope for fromQueue's router.\n const events = Stream.unwrap(\n Effect.gen(function* () {\n const { approved, decisions, approvalRequests } = yield* fromQueue(\n isSensitive,\n verdicts,\n )(calls)\n return Stream.merge(\n approvalRequests,\n Stream.merge(\n run(allTools, approved),\n decisions.pipe(Stream.flatMap(eventsFromDecision)),\n ),\n )\n }),\n )\n return yield* Stream.runCollect(events)\n }),\n )\n\n expect(collected.filter(isApprovalRequested)).toHaveLength(2)\n\n const by = byCallId(resultsFrom(collected))\n expect(by.get(\"c2\")).toMatchObject({ _tag: \"Ok\", value: { status: \"sent\" } })\n expect(by.get(\"c3\")).toMatchObject({\n _tag: \"Failure\",\n kind: \"denied\",\n reason: \"too risky\",\n })\n })\n\n it(\"router retires per round, so sequential rounds over one queue both resolve\", async () => {\n // A verdict-per-round over one shared queue and scope. The old\n // never-terminating router would still be alive for round 1 and steal\n // round 2's verdict (not in its map), hanging round 2.\n const decisions = await Effect.runPromise(\n Effect.scoped(\n Effect.gen(function* () {\n const verdicts = yield* Queue.unbounded<Verdict>()\n const round = (id: string) => [fc(id, \"delete_database\", { name: \"prod\" })]\n\n const r1 = yield* fromQueue(isSensitive, verdicts)(round(\"c1\"))\n yield* Queue.offer(verdicts, { call_id: \"c1\", decision: \"approve\" })\n const d1 = Array.from(yield* Stream.runCollect(r1.decisions))\n\n const r2 = yield* fromQueue(isSensitive, verdicts)(round(\"c2\"))\n yield* Queue.offer(verdicts, { call_id: \"c2\", decision: \"deny\", reason: \"no\" })\n const d2 = Array.from(yield* Stream.runCollect(r2.decisions))\n\n return { d1, d2 }\n }),\n ),\n )\n expect(decisions.d1[0]).toMatchObject({ _tag: \"Approved\" })\n expect(decisions.d2[0]).toMatchObject({ _tag: \"Rejected\", result: { kind: \"denied\" } })\n })\n\n it(\"timeout resolves an unanswered gated call as cancelled\", async () => {\n const decisions = await Effect.runPromise(\n Effect.scoped(\n Effect.gen(function* () {\n const verdicts = yield* Queue.unbounded<Verdict>()\n const { decisions } = yield* fromQueue(isSensitive, verdicts, { timeout: \"10 millis\" })([\n fc(\"c1\", \"delete_database\", { name: \"prod\" }),\n ])\n return Array.from(yield* Stream.runCollect(decisions))\n }),\n ),\n )\n expect(decisions[0]).toMatchObject({\n _tag: \"Rejected\",\n result: { _tag: \"Failure\", kind: \"cancelled\" },\n })\n })\n})\n\n// ---------------------------------------------------------------------------\n// History reconciliation\n// ---------------------------------------------------------------------------\n\ndescribe(\"findUnansweredCalls / cancelAllPending / isReconciled\", () => {\n const orphan = fc(\"c99\", \"delete_database\", { name: \"prod\" })\n const answered = fc(\"c98\", \"web_search\", { query: \"x\" })\n const answeredOutput = Items.toolCallOutput(\"c98\", JSON.stringify({ count: 0 }))\n\n it(\"findUnansweredCalls returns only orphans\", () => {\n const history = [Items.userText(\"hi\"), answered, orphan, answeredOutput]\n const unanswered = findUnansweredCalls(history)\n expect(unanswered).toHaveLength(1)\n expect(unanswered[0]!.call_id).toBe(\"c99\")\n })\n\n it(\"isReconciled is false when orphans exist, true otherwise\", () => {\n const stale = [Items.userText(\"hi\"), orphan]\n expect(isReconciled(stale)).toBe(false)\n const reconciled = [...stale, ...cancelAllPending(stale).map(toToolCallOutput)]\n expect(isReconciled(reconciled)).toBe(true)\n })\n\n it(\"cancelAllPending synthesizes one Failure(cancelled) per orphan\", () => {\n const history = [Items.userText(\"hi\"), answered, orphan, answeredOutput]\n const closures = cancelAllPending(history, \"user moved on\")\n expect(closures).toHaveLength(1)\n const c = closures[0]!\n expect(isFailure(c)).toBe(true)\n expect(c).toMatchObject({\n _tag: \"Failure\",\n call_id: \"c99\",\n kind: \"cancelled\",\n reason: \"user moved on\",\n })\n })\n\n it(\"follow-up: map closures to ToolCallOutput before appending new user message\", () => {\n const stale = [Items.userText(\"first request\"), orphan]\n const closures = cancelAllPending(stale, \"user redirected\")\n const reconciled = [...stale, ...closures.map(toToolCallOutput), Items.userText(\"never mind\")]\n expect(findUnansweredCalls(reconciled)).toHaveLength(0)\n })\n})\n\n// ---------------------------------------------------------------------------\n// Wire conversion\n// ---------------------------------------------------------------------------\n\ndescribe(\"toToolCallOutput\", () => {\n it(\"round-trips a Value result\", () => {\n const r: ToolResult = {\n _tag: \"Ok\",\n call_id: \"c1\",\n tool: \"web_search\",\n value: { count: 3 },\n }\n const out = toToolCallOutput(r)\n expect(out.call_id).toBe(\"c1\")\n expect(JSON.parse(out.output)).toEqual({ count: 3 })\n })\n\n it(\"renders a Failure with reason as a nested error object\", () => {\n const r: ToolResult = {\n _tag: \"Failure\",\n call_id: \"c2\",\n tool: \"bulk_email\",\n kind: \"denied\",\n reason: \"spam concern\",\n }\n const out = toToolCallOutput(r)\n expect(JSON.parse(out.output)).toEqual({ error: { kind: \"denied\", message: \"spam concern\" } })\n })\n\n it(\"renders a Failure without reason (omits message)\", () => {\n const r: ToolResult = {\n _tag: \"Failure\",\n call_id: \"c3\",\n tool: \"delete_database\",\n kind: \"cancelled\",\n }\n const out = toToolCallOutput(r)\n expect(JSON.parse(out.output)).toEqual({ error: { kind: \"cancelled\" } })\n })\n})\n\n// ---------------------------------------------------------------------------\n// run\n// ---------------------------------------------------------------------------\n\ndescribe(\"run\", () => {\n it(\"runs all calls passed to it\", async () => {\n const collected = await Effect.runPromise(Stream.runCollect(run(allTools, calls)))\n expect(collected.filter(isOutput)).toHaveLength(3)\n expect(collected.filter(isOutput).every((e) => isOk(e.result))).toBe(true)\n })\n})\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { o as HistoryItem, v as ToolCall } from "../Items-
|
|
2
|
-
import { t as ToolResult } from "../ToolResult-
|
|
1
|
+
import { o as HistoryItem, v as ToolCall } from "../Items-BWNu6VGp.mjs";
|
|
2
|
+
import { t as ToolResult } from "../ToolResult-CQqKkcDa.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/tool/HistoryCheck.d.ts
|
|
5
5
|
declare namespace HistoryCheck_d_exports {
|
package/dist/tool/Tool.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { C as
|
|
2
|
-
export { AnyLocalTool, AnyTool, DecodableTool, Emit, InteractionTool, ProviderTool, SignalTool, Tool, ToolDescriptor, ToolError, ToolInputSchema, ToolR, ToolValidationError, decodeArgs, descriptorsOf, execute, fromEffectSchema, fromStandardSchema, interaction, make, provider, signal, toDescriptors, withName, withRun };
|
|
1
|
+
import { A as providerToolsOf, C as fromEffectSchema, D as make, E as isProviderTool, M as toDescriptors, N as withName, O as noInput, P as withRun, S as fail, T as interaction, a as Emit, b as descriptorsOf, c as SignalTool, d as ToolE, f as ToolError, g as ToolValidationError, h as ToolR, i as DecodeResult, j as signal, k as provider, l as Tool, m as ToolInputSchema, n as AnyTool, o as InteractionTool, p as ToolFailed, r as DecodableTool, s as ProviderTool, t as AnyLocalTool, u as ToolDescriptor, v as decodeArgs, w as fromStandardSchema, x as execute, y as decodeCallInput } from "../Tool-CXFHCQrT.mjs";
|
|
2
|
+
export { AnyLocalTool, AnyTool, DecodableTool, DecodeResult, Emit, InteractionTool, ProviderTool, SignalTool, Tool, ToolDescriptor, ToolE, ToolError, ToolFailed, ToolInputSchema, ToolR, ToolValidationError, decodeArgs, decodeCallInput, descriptorsOf, execute, fail, fromEffectSchema, fromStandardSchema, interaction, isProviderTool, make, noInput, provider, providerToolsOf, signal, toDescriptors, withName, withRun };
|
package/dist/tool/Tool.mjs
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
import { n as __exportAll } from "../chunk-uyGKjUfl.mjs";
|
|
2
2
|
import { toolCallOutput } from "../domain/Items.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { serializeValue } from "./ToolResult.mjs";
|
|
4
|
+
import { Effect, Match, Schema } from "effect";
|
|
4
5
|
//#region src/tool/Tool.ts
|
|
5
6
|
var Tool_exports = /* @__PURE__ */ __exportAll({
|
|
6
7
|
ToolError: () => ToolError,
|
|
8
|
+
ToolFailed: () => ToolFailed,
|
|
7
9
|
ToolValidationError: () => ToolValidationError,
|
|
8
10
|
decodeArgs: () => decodeArgs,
|
|
11
|
+
decodeCallInput: () => decodeCallInput,
|
|
9
12
|
descriptorsOf: () => descriptorsOf,
|
|
10
13
|
execute: () => execute,
|
|
14
|
+
fail: () => fail,
|
|
11
15
|
fromEffectSchema: () => fromEffectSchema,
|
|
12
16
|
fromStandardSchema: () => fromStandardSchema,
|
|
13
17
|
interaction: () => interaction,
|
|
18
|
+
isProviderTool: () => isProviderTool,
|
|
14
19
|
make: () => make,
|
|
20
|
+
noInput: () => noInput,
|
|
15
21
|
provider: () => provider,
|
|
22
|
+
providerToolsOf: () => providerToolsOf,
|
|
16
23
|
signal: () => signal,
|
|
17
24
|
toDescriptors: () => toDescriptors,
|
|
18
25
|
withName: () => withName,
|
|
@@ -45,6 +52,28 @@ var ToolValidationError = class extends Schema.TaggedErrorClass("@betalyra/effec
|
|
|
45
52
|
issues: Schema.Array(ToolValidationIssue)
|
|
46
53
|
}) {};
|
|
47
54
|
/**
|
|
55
|
+
* The deliberate "speak to the model" failure sentinel. A tool's `run` that
|
|
56
|
+
* fails with a `ToolFailed` (or a bare `string`, its sugar) is absorbed by
|
|
57
|
+
* `Toolkit.run` into a `ToolResult.Failure` the model reads and adapts to,
|
|
58
|
+
* instead of ending the run. Every other error type propagates typed on the
|
|
59
|
+
* executor's stream. `kind` overrides the synthesized `Failure.kind`
|
|
60
|
+
* (default `"tool_failed"`) so a tool can hand the model a distinguishable
|
|
61
|
+
* category (`"not_found"`, `"rate_limited"`) without a new result variant.
|
|
62
|
+
*/
|
|
63
|
+
var ToolFailed = class extends Schema.TaggedErrorClass("@betalyra/effect-uai/ToolFailed")("ToolFailed", {
|
|
64
|
+
message: Schema.String,
|
|
65
|
+
kind: Schema.optional(Schema.String)
|
|
66
|
+
}) {};
|
|
67
|
+
/**
|
|
68
|
+
* Fail a tool's `run` with a model-visible message (optionally a `kind`).
|
|
69
|
+
* `yield* Tool.fail("page not found", { kind: "not_found" })` inside a
|
|
70
|
+
* `run`'s `Effect.gen`; the executor absorbs it as `ToolResult.Failure`.
|
|
71
|
+
*/
|
|
72
|
+
const fail = (message, options) => Effect.fail(new ToolFailed({
|
|
73
|
+
message,
|
|
74
|
+
...options?.kind !== void 0 ? { kind: options.kind } : {}
|
|
75
|
+
}));
|
|
76
|
+
/**
|
|
48
77
|
* Convenience wrapper for Effect Schema users - adds both the
|
|
49
78
|
* `validate` and `jsonSchema` extensions to a plain Effect Schema so it
|
|
50
79
|
* can be used as a `Tool.inputSchema`.
|
|
@@ -66,10 +95,34 @@ const fromEffectSchema = (schema) => Schema.toStandardJSONSchemaV1(Schema.toStan
|
|
|
66
95
|
* helper makes the input type inference explicit at the call site.
|
|
67
96
|
*/
|
|
68
97
|
const fromStandardSchema = (schema) => schema;
|
|
98
|
+
/**
|
|
99
|
+
* Input schema for a tool that takes no arguments. Renders a clean
|
|
100
|
+
* `{ type: "object", properties: {} }`; validation accepts anything and
|
|
101
|
+
* yields `{}`. Use it instead of `fromEffectSchema(Schema.Struct({}))`,
|
|
102
|
+
* which renders a degenerate `anyOf` (object-or-array) that strict
|
|
103
|
+
* providers like Gemini reject.
|
|
104
|
+
*/
|
|
105
|
+
const noInput = { "~standard": {
|
|
106
|
+
version: 1,
|
|
107
|
+
vendor: "effect-uai",
|
|
108
|
+
validate: () => ({ value: {} }),
|
|
109
|
+
jsonSchema: {
|
|
110
|
+
input: () => ({
|
|
111
|
+
type: "object",
|
|
112
|
+
properties: {}
|
|
113
|
+
}),
|
|
114
|
+
output: () => ({
|
|
115
|
+
type: "object",
|
|
116
|
+
properties: {}
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
} };
|
|
120
|
+
const resolveInputSchema = (schema) => Schema.isSchema(schema) ? fromEffectSchema(schema) : schema;
|
|
69
121
|
/** A model-visible tool with a local `Effect` executor. */
|
|
70
122
|
const make = (spec) => ({
|
|
71
123
|
_tag: "LocalTool",
|
|
72
|
-
...spec
|
|
124
|
+
...spec,
|
|
125
|
+
inputSchema: resolveInputSchema(spec.inputSchema)
|
|
73
126
|
});
|
|
74
127
|
/**
|
|
75
128
|
* A provider-hosted tool (native web search, code execution, RAG grounding).
|
|
@@ -78,7 +131,8 @@ const make = (spec) => ({
|
|
|
78
131
|
*/
|
|
79
132
|
const provider = (spec) => ({
|
|
80
133
|
_tag: "ProviderTool",
|
|
81
|
-
...spec
|
|
134
|
+
...spec,
|
|
135
|
+
inputSchema: resolveInputSchema(spec.inputSchema)
|
|
82
136
|
});
|
|
83
137
|
/**
|
|
84
138
|
* A typed control-flow signal to the agent loop (escalate, pause, schedule,
|
|
@@ -87,7 +141,8 @@ const provider = (spec) => ({
|
|
|
87
141
|
*/
|
|
88
142
|
const signal = (spec) => ({
|
|
89
143
|
_tag: "SignalTool",
|
|
90
|
-
...spec
|
|
144
|
+
...spec,
|
|
145
|
+
inputSchema: resolveInputSchema(spec.inputSchema)
|
|
91
146
|
});
|
|
92
147
|
/**
|
|
93
148
|
* A typed request for an external actor to respond before the loop resumes
|
|
@@ -96,7 +151,8 @@ const signal = (spec) => ({
|
|
|
96
151
|
*/
|
|
97
152
|
const interaction = (spec) => ({
|
|
98
153
|
_tag: "InteractionTool",
|
|
99
|
-
...spec
|
|
154
|
+
...spec,
|
|
155
|
+
inputSchema: resolveInputSchema(spec.inputSchema)
|
|
100
156
|
});
|
|
101
157
|
/**
|
|
102
158
|
* Return a copy of a tool under a new `name`. Useful for resolving a name
|
|
@@ -122,8 +178,21 @@ const withRun = (tool, run) => ({
|
|
|
122
178
|
...tool,
|
|
123
179
|
run
|
|
124
180
|
});
|
|
125
|
-
/**
|
|
126
|
-
const
|
|
181
|
+
/** True for provider-hosted tools — the kind a provider renders natively. */
|
|
182
|
+
const isProviderTool = (tool) => tool._tag === "ProviderTool";
|
|
183
|
+
/**
|
|
184
|
+
* The `ProviderTool`s in a toolkit, for a provider adapter to render as its
|
|
185
|
+
* native hosted-tool entries (web search, code execution, grounding). The other
|
|
186
|
+
* kinds render as function declarations via `descriptorsOf`.
|
|
187
|
+
*/
|
|
188
|
+
const providerToolsOf = (toolkit) => toolkit === void 0 ? [] : Object.values(toolkit).filter(isProviderTool);
|
|
189
|
+
/**
|
|
190
|
+
* Render tools to provider-agnostic descriptors. `ProviderTool`s are excluded:
|
|
191
|
+
* they are provider-hosted and must never leak into the function-declaration
|
|
192
|
+
* list — each adapter renders them natively via `providerToolsOf`. Every other
|
|
193
|
+
* kind is model-visible as a declaration.
|
|
194
|
+
*/
|
|
195
|
+
const toDescriptors = (tools) => tools.filter((tool) => !isProviderTool(tool)).map((tool) => {
|
|
127
196
|
const inputSchema = tool.inputSchema["~standard"].jsonSchema.input({ target: "draft-2020-12" });
|
|
128
197
|
const strict = "strict" in tool ? tool.strict : void 0;
|
|
129
198
|
return strict !== void 0 ? {
|
|
@@ -145,12 +214,31 @@ const toDescriptors = (tools) => tools.map((tool) => {
|
|
|
145
214
|
* free of a dependency on the toolkit module — it only needs the tool values.
|
|
146
215
|
*/
|
|
147
216
|
const descriptorsOf = (toolkit) => toolkit === void 0 ? [] : toDescriptors(Object.values(toolkit));
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
217
|
+
const VALIDATION_THREW = [{ message: "input schema validation threw" }];
|
|
218
|
+
/**
|
|
219
|
+
* Parse and validate a call's `arguments`. Empty arguments normalize to
|
|
220
|
+
* `{}` (some models emit `""` for zero-arg tools); a throwing validator is
|
|
221
|
+
* captured, not propagated. Never fails.
|
|
222
|
+
*/
|
|
223
|
+
const decodeCallInput = (tool, call) => {
|
|
224
|
+
const raw = call.arguments.trim() === "" ? "{}" : call.arguments;
|
|
225
|
+
return Effect.try({
|
|
226
|
+
try: () => JSON.parse(raw),
|
|
227
|
+
catch: () => ({ _tag: "parseError" })
|
|
228
|
+
}).pipe(Effect.flatMap((parsed) => Effect.tryPromise({
|
|
229
|
+
try: () => Promise.resolve(tool.inputSchema["~standard"].validate(parsed)),
|
|
230
|
+
catch: () => ({
|
|
231
|
+
_tag: "invalid",
|
|
232
|
+
issues: VALIDATION_THREW
|
|
233
|
+
})
|
|
234
|
+
}).pipe(Effect.map((result) => result.issues !== void 0 ? {
|
|
235
|
+
_tag: "invalid",
|
|
236
|
+
issues: result.issues
|
|
237
|
+
} : {
|
|
238
|
+
_tag: "ok",
|
|
239
|
+
input: result.value
|
|
240
|
+
}))), Effect.catch((captured) => Effect.succeed(captured)));
|
|
241
|
+
};
|
|
154
242
|
/**
|
|
155
243
|
* Decode and validate a function_call's JSON `arguments` against the tool's
|
|
156
244
|
* own `inputSchema`, yielding the typed input. The decode-only half of
|
|
@@ -160,27 +248,23 @@ const toToolError = (call, toolName, message) => (cause) => new ToolError({
|
|
|
160
248
|
* Malformed JSON fails with `ToolError`; a parsed body that violates
|
|
161
249
|
* `inputSchema` fails with `ToolValidationError` carrying the issues.
|
|
162
250
|
*/
|
|
163
|
-
const decodeArgs = (tool, call) => Effect.
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
issues: result.issues
|
|
173
|
-
});
|
|
174
|
-
return result.value;
|
|
175
|
-
});
|
|
251
|
+
const decodeArgs = (tool, call) => decodeCallInput(tool, call).pipe(Effect.flatMap(Match.type().pipe(Match.tag("ok", ({ input }) => Effect.succeed(input)), Match.tag("parseError", () => Effect.fail(new ToolError({
|
|
252
|
+
call_id: call.call_id,
|
|
253
|
+
tool: tool.name,
|
|
254
|
+
message: "Failed to parse JSON arguments"
|
|
255
|
+
}))), Match.tag("invalid", ({ issues }) => Effect.fail(new ToolValidationError({
|
|
256
|
+
call_id: call.call_id,
|
|
257
|
+
tool: tool.name,
|
|
258
|
+
issues
|
|
259
|
+
}))), Match.exhaustive)));
|
|
176
260
|
/**
|
|
177
261
|
* Decode and validate the JSON arguments of a function_call against the
|
|
178
262
|
* tool's input schema, run the tool, and serialize the output into a
|
|
179
263
|
* function_call_output item. Single-shot: intermediate events emitted by
|
|
180
264
|
* `run` are discarded (use `Toolkit.run` to stream them).
|
|
181
265
|
*/
|
|
182
|
-
const execute = (tool, call) => decodeArgs(tool, call).pipe(Effect.flatMap((input) => tool.run(input, () => Effect.void)
|
|
266
|
+
const execute = (tool, call) => decodeArgs(tool, call).pipe(Effect.flatMap((input) => tool.run(input, () => Effect.void)), Effect.map((output) => toolCallOutput(call.call_id, serializeValue(output))));
|
|
183
267
|
//#endregion
|
|
184
|
-
export { ToolError, ToolValidationError, decodeArgs, descriptorsOf, execute, fromEffectSchema, fromStandardSchema, interaction, make, provider, signal, Tool_exports as t, toDescriptors, withName, withRun };
|
|
268
|
+
export { ToolError, ToolFailed, ToolValidationError, decodeArgs, decodeCallInput, descriptorsOf, execute, fail, fromEffectSchema, fromStandardSchema, interaction, isProviderTool, make, noInput, provider, providerToolsOf, signal, Tool_exports as t, toDescriptors, withName, withRun };
|
|
185
269
|
|
|
186
270
|
//# sourceMappingURL=Tool.mjs.map
|
package/dist/tool/Tool.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Tool.mjs","names":[],"sources":["../../src/tool/Tool.ts"],"sourcesContent":["import type { StandardJSONSchemaV1, StandardSchemaV1 } from \"@standard-schema/spec\"\nimport { Effect, Schema } from \"effect\"\nimport type { ToolCall, ToolCallOutput } from \"../domain/Items.js\"\nimport { toolCallOutput } from \"../domain/Items.js\"\n\nexport class ToolError extends Schema.TaggedErrorClass<ToolError>(\"@betalyra/effect-uai/ToolError\")(\n \"ToolError\",\n {\n call_id: Schema.String,\n tool: Schema.String,\n message: Schema.String,\n cause: Schema.optional(Schema.Unknown),\n },\n) {}\n\n/**\n * One Standard Schema validation issue, carried on `ToolValidationError`.\n * Mirrors `StandardSchemaV1.Issue`; `path` is left as `unknown` elements\n * because Standard Schema path segments may be symbols or `{ key }` objects.\n */\nconst ToolValidationIssue = Schema.Struct({\n message: Schema.String,\n path: Schema.optional(Schema.Array(Schema.Unknown)),\n})\n\n/**\n * The model's `arguments` for a call failed the tool's `inputSchema`. Distinct\n * from `ToolError` (malformed JSON, runtime crash) so callers can tell a\n * contract violation apart from a parse or execution failure, and so the\n * structured `issues` survive instead of being flattened into a message string.\n */\nexport class ToolValidationError extends Schema.TaggedErrorClass<ToolValidationError>(\n \"@betalyra/effect-uai/ToolValidationError\",\n)(\"ToolValidationError\", {\n call_id: Schema.String,\n tool: Schema.String,\n issues: Schema.Array(ToolValidationIssue),\n}) {}\n\n/**\n * Schemas accepted on `Tool.inputSchema`. Must implement both Standard\n * Schema validation and JSON Schema conversion (for rendering tool\n * descriptors to provider request bodies).\n *\n * Any Standard-Schema-compliant library that exposes both interfaces\n * works directly: Zod 4+, Valibot, ArkType, Effect Schema (after\n * `fromEffectSchema`), etc.\n */\nexport type ToolInputSchema<Input = unknown> = StandardSchemaV1<unknown, Input> &\n StandardJSONSchemaV1<unknown, Input>\n\n/**\n * Convenience wrapper for Effect Schema users - adds both the\n * `validate` and `jsonSchema` extensions to a plain Effect Schema so it\n * can be used as a `Tool.inputSchema`.\n */\nexport const fromEffectSchema = <S extends Schema.Codec<any, any, never, any>>(\n schema: S,\n): S & ToolInputSchema<S[\"Type\"]> =>\n Schema.toStandardJSONSchemaV1(Schema.toStandardSchemaV1(schema))\n\n/**\n * Use any schema library that implements both Standard Schema (validation)\n * and Standard JSON Schema (JSON Schema generation) as a `Tool.inputSchema`.\n * Covers Zod 4.2+, Valibot 1.2+, and ArkType 2.1.28+ in one helper.\n *\n * Effect Schema doesn't implement Standard JSON Schema natively — use\n * `fromEffectSchema` for those.\n *\n * The intersection constraint catches missing interfaces at compile time:\n * a Zod v3 schema (no Standard JSON Schema) produces a precise type error\n * pointing at the missing interface rather than a runtime surprise. The\n * helper itself is a thin type-narrowing identity — schemas that satisfy\n * both standards already structurally satisfy `ToolInputSchema`; the\n * helper makes the input type inference explicit at the call site.\n */\nexport const fromStandardSchema = <S extends StandardSchemaV1 & StandardJSONSchemaV1>(\n schema: S,\n): S & ToolInputSchema<StandardSchemaV1.InferOutput<S>> =>\n schema as S & ToolInputSchema<StandardSchemaV1.InferOutput<S>>\n\n/**\n * Emit one intermediate `Event` from inside a tool's `run`. Backed by the\n * executor's per-call queue, so it returns an `Effect` (backpressure) and\n * composes with `Stream.runForEach(emit)`.\n */\nexport type Emit<Event> = (event: Event) => Effect.Effect<void>\n\n/**\n * A model-visible tool with a local `Effect` executor. `run` computes the\n * model-facing `Output`; emitting progress is a side channel via `emit`. A\n * plain tool never calls `emit` (its `Event` is irrelevant); a streaming tool\n * emits intermediate `Event`s that flow to the consumer as\n * `ToolEvent.Progress` in real time.\n *\n * This is one of four tool *kinds*, discriminated by `_tag`. The other three\n * (`ProviderTool`, `SignalTool`, `InteractionTool`) are model-visible but have\n * no local executor — see their constructors below. `Tool` keeps its name (and\n * the `Tool.make` constructor) because the local kind is by far the common one.\n */\nexport type Tool<Name extends string, Input, Event, Output, R = never> = {\n readonly _tag: \"LocalTool\"\n readonly name: Name\n readonly description: string\n readonly inputSchema: ToolInputSchema<Input>\n readonly run: (input: Input, emit: Emit<Event>) => Effect.Effect<Output, unknown, R>\n /**\n * Bound on this tool's emit queue. Unbounded when omitted. Per-tool because\n * backpressure depends on how a given tool emits, unlike `concurrency`\n * (cross-tool, on `Toolkit.run`'s options).\n */\n readonly emitBufferSize?: number\n /**\n * Whether the provider should render this tool with its strict-mode\n * flag (OpenAI's `strict: true`, etc). Default: true. The framework\n * never rewrites the schema; if the rendered JSON Schema isn't\n * compatible, the provider returns an error.\n */\n readonly strict?: boolean\n}\n\n/**\n * A provider-hosted tool: visible to the model and executed by the *provider*\n * (native web search, code execution, RAG grounding), never by this process.\n * It has no local `run`; `provider`/`config` tell the provider adapter how to\n * render it. Passing it to `Toolkit.run` yields a `non_local_tool` result.\n */\nexport type ProviderTool<Name extends string, Input, Provider extends string, Config> = {\n readonly _tag: \"ProviderTool\"\n readonly name: Name\n readonly description: string\n readonly inputSchema: ToolInputSchema<Input>\n readonly provider: Provider\n readonly config: Config\n readonly strict?: boolean\n}\n\n/**\n * A typed request for the agent loop to change control flow (escalate, pause,\n * schedule, hand off). Model-visible and decodable (`decodeArgs`) but never\n * locally executed — the loop intercepts the call in `onTurnComplete` and acts\n * on it, so there is no fake `run`. `Toolkit.run` reports it as `non_local_tool`.\n */\nexport type SignalTool<Name extends string, Input> = {\n readonly _tag: \"SignalTool\"\n readonly name: Name\n readonly description: string\n readonly inputSchema: ToolInputSchema<Input>\n}\n\n/**\n * A typed request for an external actor (user, channel, frontend) to respond\n * before the loop resumes. Like `SignalTool` it is decode-only, but its\n * lifecycle differs: the loop usually stops/pauses and resumes later by\n * appending a `function_call_output` once the actor replies.\n */\nexport type InteractionTool<Name extends string, Input> = {\n readonly _tag: \"InteractionTool\"\n readonly name: Name\n readonly description: string\n readonly inputSchema: ToolInputSchema<Input>\n}\n\n/** A local (executable) tool of any shape. Readability alias. */\nexport type AnyLocalTool<R = any> = Tool<string, any, any, any, R>\n\n/** Any of the four model-visible tool kinds. */\nexport type AnyTool<R = any> =\n | Tool<string, any, any, any, R>\n | ProviderTool<string, any, string, any>\n | SignalTool<string, any>\n | InteractionTool<string, any>\n\n/** A model-visible tool that carries a decodable input schema (every kind). */\nexport type DecodableTool<Input> = {\n readonly name: string\n readonly inputSchema: ToolInputSchema<Input>\n}\n\n/** Extract the `R` of a local tool; non-local kinds contribute `never`. */\nexport type ToolR<T> = T extends Tool<string, any, any, any, infer R> ? R : never\n\n/**\n * Provider-agnostic tool descriptor. Each provider maps `inputSchema`\n * to its own wire field (OpenAI → `parameters`, Anthropic →\n * `input_schema`). Built from a `Tool` by `Tool.toDescriptors`.\n */\nexport type ToolDescriptor = {\n readonly name: string\n readonly description: string\n readonly inputSchema: Record<string, unknown>\n readonly strict?: boolean\n}\n\n/** A model-visible tool with a local `Effect` executor. */\nexport const make = <Name extends string, Input, Event, Output, R = never>(\n spec: Omit<Tool<Name, Input, Event, Output, R>, \"_tag\">,\n): Tool<Name, Input, Event, Output, R> => ({ _tag: \"LocalTool\", ...spec })\n\n/**\n * A provider-hosted tool (native web search, code execution, RAG grounding).\n * No local executor — `provider`/`config` drive the provider adapter's\n * rendering. The model sees it; `Toolkit.run` reports it as `non_local_tool`.\n */\nexport const provider = <Name extends string, Input, Provider extends string, Config>(\n spec: Omit<ProviderTool<Name, Input, Provider, Config>, \"_tag\">,\n): ProviderTool<Name, Input, Provider, Config> => ({ _tag: \"ProviderTool\", ...spec })\n\n/**\n * A typed control-flow signal to the agent loop (escalate, pause, schedule,\n * hand off). Decode-only: the loop interprets the call rather than executing a\n * handler, so there is no `run`.\n */\nexport const signal = <Name extends string, Input>(\n spec: Omit<SignalTool<Name, Input>, \"_tag\">,\n): SignalTool<Name, Input> => ({ _tag: \"SignalTool\", ...spec })\n\n/**\n * A typed request for an external actor to respond before the loop resumes\n * (frontend, Telegram, CLI prompt). Decode-only; the loop stops/pauses and\n * resumes by appending the actor's `function_call_output`.\n */\nexport const interaction = <Name extends string, Input>(\n spec: Omit<InteractionTool<Name, Input>, \"_tag\">,\n): InteractionTool<Name, Input> => ({ _tag: \"InteractionTool\", ...spec })\n\n/**\n * Return a copy of a tool under a new `name`. Useful for resolving a name\n * clash before combining tools into one `Toolkit`. Literal-preserving, so the\n * renamed tool keys the toolkit record under the new name statically.\n */\nexport const withName = <N extends string, T extends AnyTool>(\n tool: T,\n name: N,\n): Omit<T, \"name\"> & { readonly name: N } => ({ ...tool, name })\n\n/**\n * Replace a local tool's `run`, keeping its model-facing definition (name,\n * description, schema, `_tag`) identical. The new `run`'s `input` is typed from\n * the original tool, so override/mock need no annotation:\n *\n * const safe = { ...toolkit, send_email: Tool.withRun(toolkit.send_email,\n * ({ to }) => Effect.succeed({ status: \"dry-run\", to })) }\n *\n * Descriptors stay identical to production, so a mocked toolkit can't drift\n * from the contract the model sees.\n */\nexport const withRun = <Name extends string, Input, Event, Output, R, Output2, R2 = never>(\n tool: Tool<Name, Input, Event, Output, R>,\n run: (input: Input, emit: Emit<Event>) => Effect.Effect<Output2, unknown, R2>,\n): Tool<Name, Input, Event, Output2, R2> => ({ ...tool, run })\n\n/** Render tools to provider-agnostic descriptors. Every kind is model-visible. */\nexport const toDescriptors = (tools: ReadonlyArray<AnyTool>): ReadonlyArray<ToolDescriptor> =>\n tools.map((tool) => {\n const inputSchema = tool.inputSchema[\"~standard\"].jsonSchema.input({\n target: \"draft-2020-12\",\n })\n // `strict` only exists on local/provider kinds; signals/interactions omit it.\n const strict = \"strict\" in tool ? tool.strict : undefined\n return strict !== undefined\n ? { name: tool.name, description: tool.description, inputSchema, strict }\n : { name: tool.name, description: tool.description, inputSchema }\n })\n\n/**\n * Render a `Toolkit` (a name-indexed record of tools) to descriptors, treating\n * an absent toolkit as no tools. The normalization point the `LanguageModel`\n * boundary uses so a request can carry the toolkit itself instead of a\n * pre-rendered descriptor array. Lives here (not on `Toolkit`) to keep providers\n * free of a dependency on the toolkit module — it only needs the tool values.\n */\nexport const descriptorsOf = (\n toolkit?: Readonly<Record<string, AnyTool>>,\n): ReadonlyArray<ToolDescriptor> =>\n toolkit === undefined ? [] : toDescriptors(Object.values(toolkit))\n\nconst toToolError = (call: ToolCall, toolName: string, message: string) => (cause: unknown) =>\n new ToolError({ call_id: call.call_id, tool: toolName, message, cause })\n\n/**\n * Decode and validate a function_call's JSON `arguments` against the tool's\n * own `inputSchema`, yielding the typed input. The decode-only half of\n * `execute` - reach for it when you intercept a call to translate it into a\n * control-flow decision rather than running the tool's `run`.\n *\n * Malformed JSON fails with `ToolError`; a parsed body that violates\n * `inputSchema` fails with `ToolValidationError` carrying the issues.\n */\nexport const decodeArgs = <Input>(\n tool: DecodableTool<Input>,\n call: ToolCall,\n): Effect.Effect<Input, ToolError | ToolValidationError> =>\n Effect.gen(function* () {\n const parsed = yield* Effect.try({\n // inputSchema validates through Standard Schema, not Effect Schema, so its\n // JSON codecs don't apply; the parse is separate to keep ToolError (bad\n // JSON) distinct from ToolValidationError (bad shape).\n // @effect-diagnostics-next-line effect/preferSchemaOverJson:off\n try: () => JSON.parse(call.arguments) as unknown,\n catch: toToolError(call, tool.name, \"Failed to parse JSON arguments\"),\n })\n\n const result = yield* Effect.promise(() =>\n Promise.resolve(tool.inputSchema[\"~standard\"].validate(parsed)),\n )\n if (result.issues !== undefined) {\n return yield* new ToolValidationError({\n call_id: call.call_id,\n tool: tool.name,\n issues: result.issues,\n })\n }\n return result.value\n })\n\n/**\n * Decode and validate the JSON arguments of a function_call against the\n * tool's input schema, run the tool, and serialize the output into a\n * function_call_output item. Single-shot: intermediate events emitted by\n * `run` are discarded (use `Toolkit.run` to stream them).\n */\nexport const execute = <Name extends string, Input, Event, Output, R>(\n tool: Tool<Name, Input, Event, Output, R>,\n call: ToolCall,\n): Effect.Effect<ToolCallOutput, ToolError | ToolValidationError, R> =>\n decodeArgs(tool, call).pipe(\n Effect.flatMap((input) =>\n tool\n .run(input, () => Effect.void)\n .pipe(Effect.mapError(toToolError(call, tool.name, \"Tool execution failed\"))),\n ),\n Effect.map((output) => toolCallOutput(call.call_id, JSON.stringify(output))),\n )\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAKA,IAAa,YAAb,cAA+B,OAAO,iBAA4B,iCAAiC,CACjG,aACA;CACE,SAAS,OAAO;CAChB,MAAM,OAAO;CACb,SAAS,OAAO;CAChB,OAAO,OAAO,SAAS,OAAO,QAAQ;CACvC,CACF,CAAC;;;;;;AAOF,MAAM,sBAAsB,OAAO,OAAO;CACxC,SAAS,OAAO;CAChB,MAAM,OAAO,SAAS,OAAO,MAAM,OAAO,QAAQ,CAAC;CACpD,CAAC;;;;;;;AAQF,IAAa,sBAAb,cAAyC,OAAO,iBAC9C,2CACD,CAAC,uBAAuB;CACvB,SAAS,OAAO;CAChB,MAAM,OAAO;CACb,QAAQ,OAAO,MAAM,oBAAoB;CAC1C,CAAC,CAAC;;;;;;AAmBH,MAAa,oBACX,WAEA,OAAO,uBAAuB,OAAO,mBAAmB,OAAO,CAAC;;;;;;;;;;;;;;;;AAiBlE,MAAa,sBACX,WAEA;;AAoHF,MAAa,QACX,UACyC;CAAE,MAAM;CAAa,GAAG;CAAM;;;;;;AAOzE,MAAa,YACX,UACiD;CAAE,MAAM;CAAgB,GAAG;CAAM;;;;;;AAOpF,MAAa,UACX,UAC6B;CAAE,MAAM;CAAc,GAAG;CAAM;;;;;;AAO9D,MAAa,eACX,UACkC;CAAE,MAAM;CAAmB,GAAG;CAAM;;;;;;AAOxE,MAAa,YACX,MACA,UAC4C;CAAE,GAAG;CAAM;CAAM;;;;;;;;;;;;AAa/D,MAAa,WACX,MACA,SAC2C;CAAE,GAAG;CAAM;CAAK;;AAG7D,MAAa,iBAAiB,UAC5B,MAAM,KAAK,SAAS;CAClB,MAAM,cAAc,KAAK,YAAY,aAAa,WAAW,MAAM,EACjE,QAAQ,iBACT,CAAC;CAEF,MAAM,SAAS,YAAY,OAAO,KAAK,SAAS,KAAA;AAChD,QAAO,WAAW,KAAA,IACd;EAAE,MAAM,KAAK;EAAM,aAAa,KAAK;EAAa;EAAa;EAAQ,GACvE;EAAE,MAAM,KAAK;EAAM,aAAa,KAAK;EAAa;EAAa;EACnE;;;;;;;;AASJ,MAAa,iBACX,YAEA,YAAY,KAAA,IAAY,EAAE,GAAG,cAAc,OAAO,OAAO,QAAQ,CAAC;AAEpE,MAAM,eAAe,MAAgB,UAAkB,aAAqB,UAC1E,IAAI,UAAU;CAAE,SAAS,KAAK;CAAS,MAAM;CAAU;CAAS;CAAO,CAAC;;;;;;;;;;AAW1E,MAAa,cACX,MACA,SAEA,OAAO,IAAI,aAAa;CACtB,MAAM,SAAS,OAAO,OAAO,IAAI;EAK/B,WAAW,KAAK,MAAM,KAAK,UAAU;EACrC,OAAO,YAAY,MAAM,KAAK,MAAM,iCAAiC;EACtE,CAAC;CAEF,MAAM,SAAS,OAAO,OAAO,cAC3B,QAAQ,QAAQ,KAAK,YAAY,aAAa,SAAS,OAAO,CAAC,CAChE;AACD,KAAI,OAAO,WAAW,KAAA,EACpB,QAAO,OAAO,IAAI,oBAAoB;EACpC,SAAS,KAAK;EACd,MAAM,KAAK;EACX,QAAQ,OAAO;EAChB,CAAC;AAEJ,QAAO,OAAO;EACd;;;;;;;AAQJ,MAAa,WACX,MACA,SAEA,WAAW,MAAM,KAAK,CAAC,KACrB,OAAO,SAAS,UACd,KACG,IAAI,aAAa,OAAO,KAAK,CAC7B,KAAK,OAAO,SAAS,YAAY,MAAM,KAAK,MAAM,wBAAwB,CAAC,CAAC,CAChF,EACD,OAAO,KAAK,WAAW,eAAe,KAAK,SAAS,KAAK,UAAU,OAAO,CAAC,CAAC,CAC7E"}
|
|
1
|
+
{"version":3,"file":"Tool.mjs","names":[],"sources":["../../src/tool/Tool.ts"],"sourcesContent":["import type { StandardJSONSchemaV1, StandardSchemaV1 } from \"@standard-schema/spec\"\nimport { Effect, Match, Schema } from \"effect\"\nimport type { ToolCall, ToolCallOutput } from \"../domain/Items.js\"\nimport { toolCallOutput } from \"../domain/Items.js\"\nimport { serializeValue } from \"./ToolResult.js\"\n\nexport class ToolError extends Schema.TaggedErrorClass<ToolError>(\"@betalyra/effect-uai/ToolError\")(\n \"ToolError\",\n {\n call_id: Schema.String,\n tool: Schema.String,\n message: Schema.String,\n cause: Schema.optional(Schema.Unknown),\n },\n) {}\n\n/**\n * One Standard Schema validation issue, carried on `ToolValidationError`.\n * Mirrors `StandardSchemaV1.Issue`; `path` is left as `unknown` elements\n * because Standard Schema path segments may be symbols or `{ key }` objects.\n */\nconst ToolValidationIssue = Schema.Struct({\n message: Schema.String,\n path: Schema.optional(Schema.Array(Schema.Unknown)),\n})\n\n/**\n * The model's `arguments` for a call failed the tool's `inputSchema`. Distinct\n * from `ToolError` (malformed JSON, runtime crash) so callers can tell a\n * contract violation apart from a parse or execution failure, and so the\n * structured `issues` survive instead of being flattened into a message string.\n */\nexport class ToolValidationError extends Schema.TaggedErrorClass<ToolValidationError>(\n \"@betalyra/effect-uai/ToolValidationError\",\n)(\"ToolValidationError\", {\n call_id: Schema.String,\n tool: Schema.String,\n issues: Schema.Array(ToolValidationIssue),\n}) {}\n\n/**\n * The deliberate \"speak to the model\" failure sentinel. A tool's `run` that\n * fails with a `ToolFailed` (or a bare `string`, its sugar) is absorbed by\n * `Toolkit.run` into a `ToolResult.Failure` the model reads and adapts to,\n * instead of ending the run. Every other error type propagates typed on the\n * executor's stream. `kind` overrides the synthesized `Failure.kind`\n * (default `\"tool_failed\"`) so a tool can hand the model a distinguishable\n * category (`\"not_found\"`, `\"rate_limited\"`) without a new result variant.\n */\nexport class ToolFailed extends Schema.TaggedErrorClass<ToolFailed>(\n \"@betalyra/effect-uai/ToolFailed\",\n)(\"ToolFailed\", {\n message: Schema.String,\n kind: Schema.optional(Schema.String),\n}) {}\n\n/**\n * Fail a tool's `run` with a model-visible message (optionally a `kind`).\n * `yield* Tool.fail(\"page not found\", { kind: \"not_found\" })` inside a\n * `run`'s `Effect.gen`; the executor absorbs it as `ToolResult.Failure`.\n */\nexport const fail = (\n message: string,\n options?: { readonly kind?: string },\n): Effect.Effect<never, ToolFailed> =>\n Effect.fail(\n new ToolFailed({ message, ...(options?.kind !== undefined ? { kind: options.kind } : {}) }),\n )\n\n/**\n * Schemas accepted on `Tool.inputSchema`. Must implement both Standard\n * Schema validation and JSON Schema conversion (for rendering tool\n * descriptors to provider request bodies).\n *\n * Any Standard-Schema-compliant library that exposes both interfaces\n * works directly: Zod 4+, Valibot, ArkType, Effect Schema (after\n * `fromEffectSchema`), etc.\n */\nexport type ToolInputSchema<Input = unknown> = StandardSchemaV1<unknown, Input> &\n StandardJSONSchemaV1<unknown, Input>\n\n/**\n * Convenience wrapper for Effect Schema users - adds both the\n * `validate` and `jsonSchema` extensions to a plain Effect Schema so it\n * can be used as a `Tool.inputSchema`.\n */\nexport const fromEffectSchema = <S extends Schema.Codec<any, any, never, any>>(\n schema: S,\n): S & ToolInputSchema<S[\"Type\"]> =>\n Schema.toStandardJSONSchemaV1(Schema.toStandardSchemaV1(schema))\n\n/**\n * Use any schema library that implements both Standard Schema (validation)\n * and Standard JSON Schema (JSON Schema generation) as a `Tool.inputSchema`.\n * Covers Zod 4.2+, Valibot 1.2+, and ArkType 2.1.28+ in one helper.\n *\n * Effect Schema doesn't implement Standard JSON Schema natively — use\n * `fromEffectSchema` for those.\n *\n * The intersection constraint catches missing interfaces at compile time:\n * a Zod v3 schema (no Standard JSON Schema) produces a precise type error\n * pointing at the missing interface rather than a runtime surprise. The\n * helper itself is a thin type-narrowing identity — schemas that satisfy\n * both standards already structurally satisfy `ToolInputSchema`; the\n * helper makes the input type inference explicit at the call site.\n */\nexport const fromStandardSchema = <S extends StandardSchemaV1 & StandardJSONSchemaV1>(\n schema: S,\n): S & ToolInputSchema<StandardSchemaV1.InferOutput<S>> =>\n schema as S & ToolInputSchema<StandardSchemaV1.InferOutput<S>>\n\n/**\n * Input schema for a tool that takes no arguments. Renders a clean\n * `{ type: \"object\", properties: {} }`; validation accepts anything and\n * yields `{}`. Use it instead of `fromEffectSchema(Schema.Struct({}))`,\n * which renders a degenerate `anyOf` (object-or-array) that strict\n * providers like Gemini reject.\n */\nexport const noInput: ToolInputSchema<Record<string, never>> = {\n \"~standard\": {\n version: 1,\n vendor: \"effect-uai\",\n validate: () => ({ value: {} }),\n jsonSchema: {\n input: () => ({ type: \"object\", properties: {} }),\n output: () => ({ type: \"object\", properties: {} }),\n },\n },\n}\n\n/**\n * Emit one intermediate `Event` from inside a tool's `run`. Backed by the\n * executor's per-call queue, so it returns an `Effect` (backpressure) and\n * composes with `Stream.runForEach(emit)`.\n */\nexport type Emit<Event> = (event: Event) => Effect.Effect<void>\n\n/**\n * A model-visible tool with a local `Effect` executor. `run` computes the\n * model-facing `Output`; emitting progress is a side channel via `emit`. A\n * plain tool never calls `emit` (its `Event` is irrelevant); a streaming tool\n * emits intermediate `Event`s that flow to the consumer as\n * `ToolEvent.Progress` in real time.\n *\n * This is one of four tool *kinds*, discriminated by `_tag`. The other three\n * (`ProviderTool`, `SignalTool`, `InteractionTool`) are model-visible but have\n * no local executor — see their constructors below. `Tool` keeps its name (and\n * the `Tool.make` constructor) because the local kind is by far the common one.\n */\nexport type Tool<Name extends string, Input, Event, Output, E = unknown, R = never> = {\n readonly _tag: \"LocalTool\"\n readonly name: Name\n readonly description: string\n readonly inputSchema: ToolInputSchema<Input>\n readonly run: (input: Input, emit: Emit<Event>) => Effect.Effect<Output, E, R>\n /**\n * Bound on this tool's emit queue. Unbounded when omitted. Per-tool because\n * backpressure depends on how a given tool emits, unlike `concurrency`\n * (cross-tool, on `Toolkit.run`'s options).\n */\n readonly emitBufferSize?: number\n /**\n * Whether the provider should render this tool with its strict-mode\n * flag (OpenAI's `strict: true`, etc). Default: true. The framework\n * never rewrites the schema; if the rendered JSON Schema isn't\n * compatible, the provider returns an error.\n */\n readonly strict?: boolean\n}\n\n/**\n * A provider-hosted tool: visible to the model and executed by the *provider*\n * (native web search, code execution, RAG grounding), never by this process.\n * It has no local `run`; `provider`/`config` tell the provider adapter how to\n * render it. Passing it to `Toolkit.run` yields a `non_local_tool` result.\n */\nexport type ProviderTool<Name extends string, Input, Provider extends string, Config> = {\n readonly _tag: \"ProviderTool\"\n readonly name: Name\n readonly description: string\n readonly inputSchema: ToolInputSchema<Input>\n readonly provider: Provider\n readonly config: Config\n readonly strict?: boolean\n}\n\n/**\n * A typed request for the agent loop to change control flow (escalate, pause,\n * schedule, hand off). Model-visible and decodable (`decodeArgs`) but never\n * locally executed — the loop intercepts the call in `onTurnComplete` and acts\n * on it, so there is no fake `run`. `Toolkit.run` reports it as `non_local_tool`.\n */\nexport type SignalTool<Name extends string, Input> = {\n readonly _tag: \"SignalTool\"\n readonly name: Name\n readonly description: string\n readonly inputSchema: ToolInputSchema<Input>\n}\n\n/**\n * A typed request for an external actor (user, channel, frontend) to respond\n * before the loop resumes. Like `SignalTool` it is decode-only, but its\n * lifecycle differs: the loop usually stops/pauses and resumes later by\n * appending a `function_call_output` once the actor replies.\n */\nexport type InteractionTool<Name extends string, Input> = {\n readonly _tag: \"InteractionTool\"\n readonly name: Name\n readonly description: string\n readonly inputSchema: ToolInputSchema<Input>\n}\n\n/** A local (executable) tool of any shape. Readability alias. */\nexport type AnyLocalTool<E = any, R = any> = Tool<string, any, any, any, E, R>\n\n/** Any of the four model-visible tool kinds. */\nexport type AnyTool<E = any, R = any> =\n | Tool<string, any, any, any, E, R>\n | ProviderTool<string, any, string, any>\n | SignalTool<string, any>\n | InteractionTool<string, any>\n\n/** A model-visible tool that carries a decodable input schema (every kind). */\nexport type DecodableTool<Input> = {\n readonly name: string\n readonly inputSchema: ToolInputSchema<Input>\n}\n\n/** Extract the `R` of a local tool; non-local kinds contribute `never`. */\nexport type ToolR<T> = T extends Tool<string, any, any, any, any, infer R> ? R : never\n\n/** Extract the `E` of a local tool; non-local kinds contribute `never`. */\nexport type ToolE<T> = T extends Tool<string, any, any, any, infer E, any> ? E : never\n\n/**\n * Provider-agnostic tool descriptor. Each provider maps `inputSchema`\n * to its own wire field (OpenAI → `parameters`, Anthropic →\n * `input_schema`). Built from a `Tool` by `Tool.toDescriptors`.\n */\nexport type ToolDescriptor = {\n readonly name: string\n readonly description: string\n readonly inputSchema: Record<string, unknown>\n readonly strict?: boolean\n}\n\nconst resolveInputSchema = (\n schema: ToolInputSchema<any> | Schema.Codec<any, any, never, any>,\n): ToolInputSchema<any> =>\n Schema.isSchema(schema)\n ? fromEffectSchema(schema as Schema.Codec<any, any, never, any>)\n : (schema as ToolInputSchema<any>)\n\n/** A model-visible tool with a local `Effect` executor. */\nexport const make: {\n <\n Name extends string,\n S extends Schema.Codec<any, any, never, any>,\n Event,\n Output,\n E = never,\n R = never,\n >(\n spec: Omit<Tool<Name, S[\"Type\"], Event, Output, E, R>, \"_tag\" | \"inputSchema\"> & {\n readonly inputSchema: S\n },\n ): Tool<Name, S[\"Type\"], Event, Output, E, R>\n <Name extends string, Input, Event, Output, E = never, R = never>(\n spec: Omit<Tool<Name, Input, Event, Output, E, R>, \"_tag\">,\n ): Tool<Name, Input, Event, Output, E, R>\n} = (\n spec: Omit<AnyLocalTool, \"_tag\" | \"inputSchema\"> & {\n readonly inputSchema: ToolInputSchema<any> | Schema.Codec<any, any, never, any>\n },\n): AnyLocalTool => ({\n _tag: \"LocalTool\",\n ...spec,\n inputSchema: resolveInputSchema(spec.inputSchema),\n})\n\n/**\n * A provider-hosted tool (native web search, code execution, RAG grounding).\n * No local executor — `provider`/`config` drive the provider adapter's\n * rendering. The model sees it; `Toolkit.run` reports it as `non_local_tool`.\n */\nexport const provider: {\n <\n Name extends string,\n S extends Schema.Codec<any, any, never, any>,\n Provider extends string,\n Config,\n >(\n spec: Omit<ProviderTool<Name, S[\"Type\"], Provider, Config>, \"_tag\" | \"inputSchema\"> & {\n readonly inputSchema: S\n },\n ): ProviderTool<Name, S[\"Type\"], Provider, Config>\n <Name extends string, Input, Provider extends string, Config>(\n spec: Omit<ProviderTool<Name, Input, Provider, Config>, \"_tag\">,\n ): ProviderTool<Name, Input, Provider, Config>\n} = (\n spec: Omit<ProviderTool<string, any, string, any>, \"_tag\" | \"inputSchema\"> & {\n readonly inputSchema: ToolInputSchema<any> | Schema.Codec<any, any, never, any>\n },\n): ProviderTool<string, any, string, any> => ({\n _tag: \"ProviderTool\",\n ...spec,\n inputSchema: resolveInputSchema(spec.inputSchema),\n})\n\n/**\n * A typed control-flow signal to the agent loop (escalate, pause, schedule,\n * hand off). Decode-only: the loop interprets the call rather than executing a\n * handler, so there is no `run`.\n */\nexport const signal: {\n <Name extends string, S extends Schema.Codec<any, any, never, any>>(\n spec: Omit<SignalTool<Name, S[\"Type\"]>, \"_tag\" | \"inputSchema\"> & {\n readonly inputSchema: S\n },\n ): SignalTool<Name, S[\"Type\"]>\n <Name extends string, Input>(spec: Omit<SignalTool<Name, Input>, \"_tag\">): SignalTool<Name, Input>\n} = (\n spec: Omit<SignalTool<string, any>, \"_tag\" | \"inputSchema\"> & {\n readonly inputSchema: ToolInputSchema<any> | Schema.Codec<any, any, never, any>\n },\n): SignalTool<string, any> => ({\n _tag: \"SignalTool\",\n ...spec,\n inputSchema: resolveInputSchema(spec.inputSchema),\n})\n\n/**\n * A typed request for an external actor to respond before the loop resumes\n * (frontend, Telegram, CLI prompt). Decode-only; the loop stops/pauses and\n * resumes by appending the actor's `function_call_output`.\n */\nexport const interaction: {\n <Name extends string, S extends Schema.Codec<any, any, never, any>>(\n spec: Omit<InteractionTool<Name, S[\"Type\"]>, \"_tag\" | \"inputSchema\"> & {\n readonly inputSchema: S\n },\n ): InteractionTool<Name, S[\"Type\"]>\n <Name extends string, Input>(\n spec: Omit<InteractionTool<Name, Input>, \"_tag\">,\n ): InteractionTool<Name, Input>\n} = (\n spec: Omit<InteractionTool<string, any>, \"_tag\" | \"inputSchema\"> & {\n readonly inputSchema: ToolInputSchema<any> | Schema.Codec<any, any, never, any>\n },\n): InteractionTool<string, any> => ({\n _tag: \"InteractionTool\",\n ...spec,\n inputSchema: resolveInputSchema(spec.inputSchema),\n})\n\n/**\n * Return a copy of a tool under a new `name`. Useful for resolving a name\n * clash before combining tools into one `Toolkit`. Literal-preserving, so the\n * renamed tool keys the toolkit record under the new name statically.\n */\nexport const withName = <N extends string, T extends AnyTool>(\n tool: T,\n name: N,\n): Omit<T, \"name\"> & { readonly name: N } => ({ ...tool, name })\n\n/**\n * Replace a local tool's `run`, keeping its model-facing definition (name,\n * description, schema, `_tag`) identical. The new `run`'s `input` is typed from\n * the original tool, so override/mock need no annotation:\n *\n * const safe = { ...toolkit, send_email: Tool.withRun(toolkit.send_email,\n * ({ to }) => Effect.succeed({ status: \"dry-run\", to })) }\n *\n * Descriptors stay identical to production, so a mocked toolkit can't drift\n * from the contract the model sees.\n */\nexport const withRun = <\n Name extends string,\n Input,\n Event,\n Output,\n E,\n R,\n Output2,\n E2 = never,\n R2 = never,\n>(\n tool: Tool<Name, Input, Event, Output, E, R>,\n run: (input: Input, emit: Emit<Event>) => Effect.Effect<Output2, E2, R2>,\n): Tool<Name, Input, Event, Output2, E2, R2> => ({ ...tool, run })\n\n/** True for provider-hosted tools — the kind a provider renders natively. */\nexport const isProviderTool = (tool: AnyTool): tool is ProviderTool<string, any, string, any> =>\n tool._tag === \"ProviderTool\"\n\n/**\n * The `ProviderTool`s in a toolkit, for a provider adapter to render as its\n * native hosted-tool entries (web search, code execution, grounding). The other\n * kinds render as function declarations via `descriptorsOf`.\n */\nexport const providerToolsOf = (\n toolkit?: Readonly<Record<string, AnyTool>>,\n): ReadonlyArray<ProviderTool<string, any, string, any>> =>\n toolkit === undefined ? [] : Object.values(toolkit).filter(isProviderTool)\n\n/**\n * Render tools to provider-agnostic descriptors. `ProviderTool`s are excluded:\n * they are provider-hosted and must never leak into the function-declaration\n * list — each adapter renders them natively via `providerToolsOf`. Every other\n * kind is model-visible as a declaration.\n */\nexport const toDescriptors = (tools: ReadonlyArray<AnyTool>): ReadonlyArray<ToolDescriptor> =>\n tools\n .filter((tool) => !isProviderTool(tool))\n .map((tool) => {\n const inputSchema = tool.inputSchema[\"~standard\"].jsonSchema.input({\n target: \"draft-2020-12\",\n })\n // `strict` only exists on local/provider kinds; signals/interactions omit it.\n const strict = \"strict\" in tool ? tool.strict : undefined\n return strict !== undefined\n ? { name: tool.name, description: tool.description, inputSchema, strict }\n : { name: tool.name, description: tool.description, inputSchema }\n })\n\n/**\n * Render a `Toolkit` (a name-indexed record of tools) to descriptors, treating\n * an absent toolkit as no tools. The normalization point the `LanguageModel`\n * boundary uses so a request can carry the toolkit itself instead of a\n * pre-rendered descriptor array. Lives here (not on `Toolkit`) to keep providers\n * free of a dependency on the toolkit module — it only needs the tool values.\n */\nexport const descriptorsOf = (\n toolkit?: Readonly<Record<string, AnyTool>>,\n): ReadonlyArray<ToolDescriptor> =>\n toolkit === undefined ? [] : toDescriptors(Object.values(toolkit))\n\n/**\n * Outcome of decoding a call's `arguments` against a tool's `inputSchema`,\n * as data so both the typed (`decodeArgs`) and result-synthesizing\n * (`Toolkit.run`) callers can translate it their own way.\n */\nexport type DecodeResult<Input> =\n | { readonly _tag: \"ok\"; readonly input: Input }\n | { readonly _tag: \"parseError\" }\n | { readonly _tag: \"invalid\"; readonly issues: ReadonlyArray<StandardSchemaV1.Issue> }\n\nconst VALIDATION_THREW: ReadonlyArray<StandardSchemaV1.Issue> = [\n { message: \"input schema validation threw\" },\n]\n\n/**\n * Parse and validate a call's `arguments`. Empty arguments normalize to\n * `{}` (some models emit `\"\"` for zero-arg tools); a throwing validator is\n * captured, not propagated. Never fails.\n */\nexport const decodeCallInput = <Input>(\n tool: DecodableTool<Input>,\n call: ToolCall,\n): Effect.Effect<DecodeResult<Input>> => {\n const raw = call.arguments.trim() === \"\" ? \"{}\" : call.arguments\n return Effect.try({\n // @effect-diagnostics-next-line effect/preferSchemaOverJson:off\n try: () => JSON.parse(raw) as unknown,\n catch: (): DecodeResult<Input> => ({ _tag: \"parseError\" }),\n }).pipe(\n Effect.flatMap((parsed) =>\n Effect.tryPromise({\n try: () => Promise.resolve(tool.inputSchema[\"~standard\"].validate(parsed)),\n catch: (): DecodeResult<Input> => ({ _tag: \"invalid\", issues: VALIDATION_THREW }),\n }).pipe(\n Effect.map(\n (result): DecodeResult<Input> =>\n result.issues !== undefined\n ? { _tag: \"invalid\", issues: result.issues }\n : { _tag: \"ok\", input: result.value },\n ),\n ),\n ),\n Effect.catch((captured: DecodeResult<Input>) => Effect.succeed(captured)),\n )\n}\n\n/**\n * Decode and validate a function_call's JSON `arguments` against the tool's\n * own `inputSchema`, yielding the typed input. The decode-only half of\n * `execute` - reach for it when you intercept a call to translate it into a\n * control-flow decision rather than running the tool's `run`.\n *\n * Malformed JSON fails with `ToolError`; a parsed body that violates\n * `inputSchema` fails with `ToolValidationError` carrying the issues.\n */\nexport const decodeArgs = <Input>(\n tool: DecodableTool<Input>,\n call: ToolCall,\n): Effect.Effect<Input, ToolError | ToolValidationError> =>\n decodeCallInput(tool, call).pipe(\n Effect.flatMap(\n Match.type<DecodeResult<Input>>().pipe(\n Match.tag(\"ok\", ({ input }) => Effect.succeed(input)),\n Match.tag(\"parseError\", () =>\n Effect.fail(\n new ToolError({\n call_id: call.call_id,\n tool: tool.name,\n message: \"Failed to parse JSON arguments\",\n }),\n ),\n ),\n Match.tag(\"invalid\", ({ issues }) =>\n Effect.fail(new ToolValidationError({ call_id: call.call_id, tool: tool.name, issues })),\n ),\n Match.exhaustive,\n ),\n ),\n )\n\n/**\n * Decode and validate the JSON arguments of a function_call against the\n * tool's input schema, run the tool, and serialize the output into a\n * function_call_output item. Single-shot: intermediate events emitted by\n * `run` are discarded (use `Toolkit.run` to stream them).\n */\nexport const execute = <Name extends string, Input, Event, Output, E, R>(\n tool: Tool<Name, Input, Event, Output, E, R>,\n call: ToolCall,\n): Effect.Effect<ToolCallOutput, E | ToolError | ToolValidationError, R> =>\n decodeArgs(tool, call).pipe(\n Effect.flatMap((input) => tool.run(input, () => Effect.void)),\n Effect.map((output) => toolCallOutput(call.call_id, serializeValue(output))),\n )\n"],"mappings":"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"}
|