@effect-uai/core 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{AiError-CAX_48RU.d.mts → AiError-CrdR1EQi.d.mts} +2 -2
- package/dist/{AiError-CAX_48RU.d.mts.map → AiError-CrdR1EQi.d.mts.map} +1 -1
- package/dist/{Audio-DPlTsNdz.d.mts → Audio-DDSKCkSe.d.mts} +2 -2
- package/dist/{Audio-DPlTsNdz.d.mts.map → Audio-DDSKCkSe.d.mts.map} +1 -1
- package/dist/{Image-HNmMpMTh.d.mts → Image-C_Zjpwhc.d.mts} +2 -2
- package/dist/{Image-HNmMpMTh.d.mts.map → Image-C_Zjpwhc.d.mts.map} +1 -1
- package/dist/{Items-D8UaXQx_.d.mts → Items-CgKD6Npw.d.mts} +5 -5
- package/dist/{Items-D8UaXQx_.d.mts.map → Items-CgKD6Npw.d.mts.map} +1 -1
- package/dist/Loop-B5_cqumi.d.mts +173 -0
- package/dist/Loop-B5_cqumi.d.mts.map +1 -0
- package/dist/{Media-D_CpcM1Z.d.mts → Media-DOLyaygI.d.mts} +1 -1
- package/dist/{Media-D_CpcM1Z.d.mts.map → Media-DOLyaygI.d.mts.map} +1 -1
- package/dist/{SandboxError-BvKypkJm.d.mts → SandboxError-CIrVDOMV.d.mts} +2 -2
- package/dist/{SandboxError-BvKypkJm.d.mts.map → SandboxError-CIrVDOMV.d.mts.map} +1 -1
- package/dist/{StructuredFormat-BbN4dosH.d.mts → StructuredFormat-CMJFD3sn.d.mts} +2 -2
- package/dist/{StructuredFormat-BbN4dosH.d.mts.map → StructuredFormat-CMJFD3sn.d.mts.map} +1 -1
- package/dist/Tool-0XUIT2A2.d.mts +232 -0
- package/dist/Tool-0XUIT2A2.d.mts.map +1 -0
- package/dist/ToolEvent-DYrAUJGs.d.mts +151 -0
- package/dist/ToolEvent-DYrAUJGs.d.mts.map +1 -0
- package/dist/ToolResult-BZd-cEWW.d.mts +151 -0
- package/dist/ToolResult-BZd-cEWW.d.mts.map +1 -0
- package/dist/Toolkit-BKC8b8Xo.d.mts +155 -0
- package/dist/Toolkit-BKC8b8Xo.d.mts.map +1 -0
- package/dist/{dist-DV5ISja1.mjs → dist-B04dlFHg.mjs} +3 -3
- package/dist/{dist-DV5ISja1.mjs.map → dist-B04dlFHg.mjs.map} +1 -1
- package/dist/dist-DWgRbDN3.mjs +140 -0
- package/dist/dist-DWgRbDN3.mjs.map +1 -0
- package/dist/domain/AiError.d.mts +1 -1
- package/dist/domain/Audio.d.mts +1 -1
- package/dist/domain/Image.d.mts +1 -1
- package/dist/domain/Items.d.mts +1 -1
- package/dist/domain/Media.d.mts +1 -1
- package/dist/domain/Music.d.mts +1 -1
- package/dist/domain/Turn.d.mts +4 -4
- package/dist/domain/Turn.test.mjs +1 -1
- package/dist/embedding-model/Embedding.d.mts +1 -1
- package/dist/embedding-model/EmbeddingModel.d.mts +1 -1
- package/dist/embedding-model/EmbeddingModel.test.mjs +1 -1
- package/dist/embedding-model/EmbeddingModel.test.mjs.map +1 -1
- package/dist/index.d.mts +16 -13
- package/dist/index.mjs +5 -2
- package/dist/language-model/LanguageModel.d.mts +10 -5
- package/dist/language-model/LanguageModel.d.mts.map +1 -1
- package/dist/language-model/LanguageModel.mjs.map +1 -1
- package/dist/language-model/LanguageModel.test.mjs +1 -1
- package/dist/loop/Loop.d.mts +2 -141
- package/dist/loop/Loop.mjs +16 -20
- package/dist/loop/Loop.mjs.map +1 -1
- package/dist/loop/Loop.test.mjs +18 -1
- package/dist/loop/Loop.test.mjs.map +1 -1
- package/dist/{magic-string.es-BgIV5Mu3.mjs → magic-string.es-Dk-MMA0F.mjs} +1 -1
- package/dist/{magic-string.es-BgIV5Mu3.mjs.map → magic-string.es-Dk-MMA0F.mjs.map} +1 -1
- package/dist/music-generator/MusicGenerator.d.mts +2 -2
- package/dist/music-generator/MusicGenerator.test.mjs +1 -1
- package/dist/observability/Metrics.d.mts +116 -28
- package/dist/observability/Metrics.d.mts.map +1 -1
- package/dist/observability/Metrics.mjs +250 -38
- package/dist/observability/Metrics.mjs.map +1 -1
- package/dist/observability/Metrics.test.d.mts +1 -0
- package/dist/observability/Metrics.test.mjs +187 -0
- package/dist/observability/Metrics.test.mjs.map +1 -0
- package/dist/observability/Telemetry.d.mts +37 -0
- package/dist/observability/Telemetry.d.mts.map +1 -0
- package/dist/observability/Telemetry.mjs +57 -0
- package/dist/observability/Telemetry.mjs.map +1 -0
- package/dist/observability/Telemetry.test.d.mts +1 -0
- package/dist/observability/Telemetry.test.mjs +92 -0
- package/dist/observability/Telemetry.test.mjs.map +1 -0
- package/dist/retry/Retry.d.mts +2 -2
- package/dist/retry/Retry.test.mjs +1 -1
- package/dist/sandbox/Sandbox.d.mts +1 -1
- package/dist/sandbox/SandboxError.d.mts +1 -1
- package/dist/speech-synthesizer/SpeechSynthesizer.d.mts +2 -2
- package/dist/speech-synthesizer/SpeechSynthesizer.test.mjs +1 -1
- package/dist/streaming/JSONL.d.mts +1 -1
- package/dist/streaming/JSONL.test.mjs +1 -1
- package/dist/streaming/SSE.test.mjs +1 -1
- package/dist/structured-format/StructuredFormat.d.mts +1 -1
- package/dist/structured-format/StructuredFormat.mjs.map +1 -1
- package/dist/structured-format/StructuredFormat.test.mjs +1 -1
- package/dist/testing/MockMusicGenerator.d.mts +1 -1
- package/dist/testing/MockProvider.d.mts +1 -1
- package/dist/testing/MockSpeechSynthesizer.d.mts +1 -1
- package/dist/tool/Approval.d.mts +3 -3
- package/dist/tool/Approval.test.mjs +35 -34
- 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 -22
- package/dist/tool/Tool.mjs.map +1 -1
- package/dist/tool/Tool.test.mjs +41 -3
- package/dist/tool/Tool.test.mjs.map +1 -1
- package/dist/tool/ToolEvent.d.mts +2 -151
- package/dist/tool/ToolResult.d.mts +2 -138
- package/dist/tool/ToolResult.mjs +18 -3
- package/dist/tool/ToolResult.mjs.map +1 -1
- package/dist/tool/Toolkit.d.mts +2 -73
- package/dist/tool/Toolkit.mjs +106 -28
- package/dist/tool/Toolkit.mjs.map +1 -1
- package/dist/tool/Toolkit.test.mjs +113 -7
- package/dist/tool/Toolkit.test.mjs.map +1 -1
- package/dist/transcriber/Transcriber.d.mts +2 -2
- package/dist/transcriber/Transcriber.test.mjs +1 -1
- package/dist/web-search/WebSearch.d.mts +119 -0
- package/dist/web-search/WebSearch.d.mts.map +1 -0
- package/dist/web-search/WebSearch.mjs +42 -0
- package/dist/web-search/WebSearch.mjs.map +1 -0
- package/dist/web-search/WebSearchTool.d.mts +58 -0
- package/dist/web-search/WebSearchTool.d.mts.map +1 -0
- package/dist/web-search/WebSearchTool.mjs +69 -0
- package/dist/web-search/WebSearchTool.mjs.map +1 -0
- package/package.json +9 -1
- package/src/embedding-model/EmbeddingModel.test.ts +4 -0
- package/src/index.ts +3 -0
- package/src/language-model/LanguageModel.ts +7 -2
- package/src/loop/Loop.test.ts +22 -0
- package/src/loop/Loop.ts +27 -4
- package/src/observability/Metrics.test.ts +266 -0
- package/src/observability/Metrics.ts +505 -65
- package/src/observability/Telemetry.test.ts +123 -0
- package/src/observability/Telemetry.ts +96 -0
- package/src/structured-format/StructuredFormat.ts +4 -0
- package/src/tool/Approval.test.ts +22 -21
- package/src/tool/Tool.test.ts +55 -2
- package/src/tool/Tool.ts +221 -55
- package/src/tool/ToolResult.ts +22 -1
- package/src/tool/Toolkit.test.ts +168 -7
- package/src/tool/Toolkit.ts +336 -99
- package/src/web-search/WebSearch.ts +150 -0
- package/src/web-search/WebSearchTool.ts +106 -0
- package/dist/Tool-CiFXYjmR.d.mts +0 -102
- package/dist/Tool-CiFXYjmR.d.mts.map +0 -1
- package/dist/loop/Loop.d.mts.map +0 -1
- package/dist/tool/ToolEvent.d.mts.map +0 -1
- package/dist/tool/ToolResult.d.mts.map +0 -1
- package/dist/tool/Toolkit.d.mts.map +0 -1
package/dist/tool/ToolResult.mjs
CHANGED
|
@@ -23,7 +23,9 @@ var ToolResult_exports = /* @__PURE__ */ __exportAll({
|
|
|
23
23
|
failed: () => failed,
|
|
24
24
|
isFailure: () => isFailure,
|
|
25
25
|
isOk: () => isOk,
|
|
26
|
-
|
|
26
|
+
nonLocalTool: () => nonLocalTool,
|
|
27
|
+
toToolCallOutput: () => toToolCallOutput,
|
|
28
|
+
validationError: () => validationError
|
|
27
29
|
});
|
|
28
30
|
/**
|
|
29
31
|
* Namespace of constructors, type guards, and matchers for `ToolResult`,
|
|
@@ -46,8 +48,21 @@ const failed = (call, kind, reason) => ToolResult.Failure({
|
|
|
46
48
|
const denied = (call, reason) => failed(call, "denied", reason);
|
|
47
49
|
/** Implicit non-answer (follow-up, inactivity, abort). */
|
|
48
50
|
const cancelled = (call, reason) => failed(call, "cancelled", reason);
|
|
49
|
-
/** Tool's own execution failed (parse error,
|
|
51
|
+
/** Tool's own execution failed (JSON parse error, runtime crash). */
|
|
50
52
|
const executionError = (call, reason) => failed(call, "execution_error", reason);
|
|
53
|
+
/**
|
|
54
|
+
* The model called a tool that is visible to it but has no local executor — a
|
|
55
|
+
* provider-hosted, signal, or interaction tool routed through `Toolkit.run`.
|
|
56
|
+
* Distinct from `unknown_tool` (no such tool at all): the loop is expected to
|
|
57
|
+
* intercept these kinds before execution, so this kind flags a missing handler.
|
|
58
|
+
*/
|
|
59
|
+
const nonLocalTool = (call, reason) => failed(call, "non_local_tool", reason ?? `Tool "${call.name}" is visible to the model but has no local executor`);
|
|
60
|
+
/**
|
|
61
|
+
* The model's `arguments` failed the tool's input schema. Distinct kind from
|
|
62
|
+
* `execution_error` so recipes can tell a contract violation apart from a
|
|
63
|
+
* runtime crash.
|
|
64
|
+
*/
|
|
65
|
+
const validationError = (call, reason) => failed(call, "input_validation_error", reason ?? "Tool input failed schema validation");
|
|
51
66
|
const toToolCallOutput = (r) => ToolResult.$match(r, {
|
|
52
67
|
Ok: (v) => toolCallOutput(v.call_id, JSON.stringify(v.value)),
|
|
53
68
|
Failure: (f) => toolCallOutput(f.call_id, JSON.stringify(f.reason !== void 0 ? {
|
|
@@ -56,6 +71,6 @@ const toToolCallOutput = (r) => ToolResult.$match(r, {
|
|
|
56
71
|
} : { kind: f.kind }))
|
|
57
72
|
});
|
|
58
73
|
//#endregion
|
|
59
|
-
export { ToolResult, cancelled, denied, executionError, failed, isFailure, isOk, ToolResult_exports as t, toToolCallOutput };
|
|
74
|
+
export { ToolResult, cancelled, denied, executionError, failed, isFailure, isOk, nonLocalTool, ToolResult_exports as t, toToolCallOutput, validationError };
|
|
60
75
|
|
|
61
76
|
//# sourceMappingURL=ToolResult.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ToolResult.mjs","names":[],"sources":["../../src/tool/ToolResult.ts"],"sourcesContent":["/**\n * Post-execution and synthetic tool results.\n *\n * - Executed tools emit ToolResult.Ok.\n * - Approval/cancellation policy emits synthetic ToolResult.Failure.\n *\n * Wire conversion stays at the recipe boundary via `toToolCallOutput`\n * so recipes can inspect, redact, or audit values before serialization.\n *\n * `output` and `reason` are `string`, not `unknown`: the wire wants strings,\n * and `unknown` would invite non-serializable values (Date, Map, BigInt,\n * fn). Recipes that want structured detail JSON.stringify themselves.\n */\nimport { Data } from \"effect\"\nimport type { ToolCall, ToolCallOutput } from \"../domain/Items.js\"\nimport { toolCallOutput } from \"../domain/Items.js\"\n\n// ---------------------------------------------------------------------------\n// ToolResult\n// ---------------------------------------------------------------------------\n\nexport type ToolResult = Data.TaggedEnum<{\n Ok: {\n readonly call_id: string\n readonly tool: string\n readonly value: unknown\n }\n Failure: {\n readonly call_id: string\n readonly tool: string\n readonly kind: string\n readonly reason?: string\n }\n}>\n\n/**\n * Namespace of constructors, type guards, and matchers for `ToolResult`,\n * provided by `Data.taggedEnum`. Use `ToolResult.$is(\"Ok\")` for type\n * narrowing and `ToolResult.$match({ Ok, Failure })` for exhaustive\n * pattern matching. Synthetic-result helpers (`denied`, `cancelled`,\n * `executionError`, `failed`) below are kinder constructors than the\n * raw `ToolResult.Failure(...)`.\n */\nexport const ToolResult = Data.taggedEnum<ToolResult>()\n\nexport const isOk = ToolResult.$is(\"Ok\")\nexport const isFailure = ToolResult.$is(\"Failure\")\n\n// Synthesizers. `denied` and `cancelled` are operationally distinct;\n// anything else is just a recipe-chosen `kind` via `failed`.\n// ---------------------------------------------------------------------------\n\nexport const failed = (call: ToolCall, kind: string, reason?: string): ToolResult =>\n ToolResult.Failure({\n call_id: call.call_id,\n tool: call.name,\n kind,\n ...(reason !== undefined ? { reason } : {}),\n })\n\n/** Explicit user/policy rejection. */\nexport const denied = (call: ToolCall, reason?: string): ToolResult =>\n failed(call, \"denied\", reason)\n\n/** Implicit non-answer (follow-up, inactivity, abort). */\nexport const cancelled = (call: ToolCall, reason?: string): ToolResult =>\n failed(call, \"cancelled\", reason)\n\n/** Tool's own execution failed (parse error,
|
|
1
|
+
{"version":3,"file":"ToolResult.mjs","names":[],"sources":["../../src/tool/ToolResult.ts"],"sourcesContent":["/**\n * Post-execution and synthetic tool results.\n *\n * - Executed tools emit ToolResult.Ok.\n * - Approval/cancellation policy emits synthetic ToolResult.Failure.\n *\n * Wire conversion stays at the recipe boundary via `toToolCallOutput`\n * so recipes can inspect, redact, or audit values before serialization.\n *\n * `output` and `reason` are `string`, not `unknown`: the wire wants strings,\n * and `unknown` would invite non-serializable values (Date, Map, BigInt,\n * fn). Recipes that want structured detail JSON.stringify themselves.\n */\nimport { Data } from \"effect\"\nimport type { ToolCall, ToolCallOutput } from \"../domain/Items.js\"\nimport { toolCallOutput } from \"../domain/Items.js\"\n\n// ---------------------------------------------------------------------------\n// ToolResult\n// ---------------------------------------------------------------------------\n\nexport type ToolResult = Data.TaggedEnum<{\n Ok: {\n readonly call_id: string\n readonly tool: string\n readonly value: unknown\n }\n Failure: {\n readonly call_id: string\n readonly tool: string\n readonly kind: string\n readonly reason?: string\n }\n}>\n\n/**\n * Namespace of constructors, type guards, and matchers for `ToolResult`,\n * provided by `Data.taggedEnum`. Use `ToolResult.$is(\"Ok\")` for type\n * narrowing and `ToolResult.$match({ Ok, Failure })` for exhaustive\n * pattern matching. Synthetic-result helpers (`denied`, `cancelled`,\n * `executionError`, `failed`) below are kinder constructors than the\n * raw `ToolResult.Failure(...)`.\n */\nexport const ToolResult = Data.taggedEnum<ToolResult>()\n\nexport const isOk = ToolResult.$is(\"Ok\")\nexport const isFailure = ToolResult.$is(\"Failure\")\n\n// Synthesizers. `denied` and `cancelled` are operationally distinct;\n// anything else is just a recipe-chosen `kind` via `failed`.\n// ---------------------------------------------------------------------------\n\nexport const failed = (call: ToolCall, kind: string, reason?: string): ToolResult =>\n ToolResult.Failure({\n call_id: call.call_id,\n tool: call.name,\n kind,\n ...(reason !== undefined ? { reason } : {}),\n })\n\n/** Explicit user/policy rejection. */\nexport const denied = (call: ToolCall, reason?: string): ToolResult =>\n failed(call, \"denied\", reason)\n\n/** Implicit non-answer (follow-up, inactivity, abort). */\nexport const cancelled = (call: ToolCall, reason?: string): ToolResult =>\n failed(call, \"cancelled\", reason)\n\n/** Tool's own execution failed (JSON parse error, runtime crash). */\nexport const executionError = (call: ToolCall, reason: string): ToolResult =>\n failed(call, \"execution_error\", reason)\n\n/**\n * The model called a tool that is visible to it but has no local executor — a\n * provider-hosted, signal, or interaction tool routed through `Toolkit.run`.\n * Distinct from `unknown_tool` (no such tool at all): the loop is expected to\n * intercept these kinds before execution, so this kind flags a missing handler.\n */\nexport const nonLocalTool = (call: ToolCall, reason?: string): ToolResult =>\n failed(\n call,\n \"non_local_tool\",\n reason ?? `Tool \"${call.name}\" is visible to the model but has no local executor`,\n )\n\n/**\n * The model's `arguments` failed the tool's input schema. Distinct kind from\n * `execution_error` so recipes can tell a contract violation apart from a\n * runtime crash.\n */\nexport const validationError = (call: ToolCall, reason?: string): ToolResult =>\n failed(call, \"input_validation_error\", reason ?? \"Tool input failed schema validation\")\n\n// ---------------------------------------------------------------------------\n// Wire conversion - the one place structured → string happens.\n// ---------------------------------------------------------------------------\n\nexport const toToolCallOutput = (r: ToolResult): ToolCallOutput =>\n ToolResult.$match(r, {\n Ok: (v) => toolCallOutput(v.call_id, JSON.stringify(v.value)),\n Failure: (f) =>\n toolCallOutput(\n f.call_id,\n JSON.stringify(\n f.reason !== undefined ? { kind: f.kind, reason: f.reason } : { kind: f.kind },\n ),\n ),\n })\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2CA,MAAa,aAAa,KAAK,YAAwB;AAEvD,MAAa,OAAO,WAAW,IAAI,KAAK;AACxC,MAAa,YAAY,WAAW,IAAI,UAAU;AAMlD,MAAa,UAAU,MAAgB,MAAc,WACnD,WAAW,QAAQ;CACjB,SAAS,KAAK;CACd,MAAM,KAAK;CACX;CACA,GAAI,WAAW,KAAA,IAAY,EAAE,QAAQ,GAAG,EAAE;CAC3C,CAAC;;AAGJ,MAAa,UAAU,MAAgB,WACrC,OAAO,MAAM,UAAU,OAAO;;AAGhC,MAAa,aAAa,MAAgB,WACxC,OAAO,MAAM,aAAa,OAAO;;AAGnC,MAAa,kBAAkB,MAAgB,WAC7C,OAAO,MAAM,mBAAmB,OAAO;;;;;;;AAQzC,MAAa,gBAAgB,MAAgB,WAC3C,OACE,MACA,kBACA,UAAU,SAAS,KAAK,KAAK,qDAC9B;;;;;;AAOH,MAAa,mBAAmB,MAAgB,WAC9C,OAAO,MAAM,0BAA0B,UAAU,sCAAsC;AAMzF,MAAa,oBAAoB,MAC/B,WAAW,OAAO,GAAG;CACnB,KAAK,MAAM,eAAe,EAAE,SAAS,KAAK,UAAU,EAAE,MAAM,CAAC;CAC7D,UAAU,MACR,eACE,EAAE,SACF,KAAK,UACH,EAAE,WAAW,KAAA,IAAY;EAAE,MAAM,EAAE;EAAM,QAAQ,EAAE;EAAQ,GAAG,EAAE,MAAM,EAAE,MAAM,CAC/E,CACF;CACJ,CAAC"}
|
package/dist/tool/Toolkit.d.mts
CHANGED
|
@@ -1,73 +1,2 @@
|
|
|
1
|
-
import { o as
|
|
2
|
-
|
|
3
|
-
import { a as Tool, i as StreamingTool, r as AnyTool } from "../Tool-CiFXYjmR.mjs";
|
|
4
|
-
import { Step } from "../loop/Loop.mjs";
|
|
5
|
-
import { ToolResult } from "./ToolResult.mjs";
|
|
6
|
-
import { ToolEvent } from "./ToolEvent.mjs";
|
|
7
|
-
import { Effect, Stream } from "effect";
|
|
8
|
-
|
|
9
|
-
//#region src/tool/Toolkit.d.ts
|
|
10
|
-
declare namespace Toolkit_d_exports {
|
|
11
|
-
export { ExecuteOptions, ToolKindR, appendToolResults, collectResults, continueWithResults, run };
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Union of every tool's `R` requirements in a mixed plain + streaming array.
|
|
15
|
-
* Used by `run` to surface the services tools need at the recipe level, so
|
|
16
|
-
* the loop's stream type carries them through to `Effect.provide`.
|
|
17
|
-
*/
|
|
18
|
-
type ToolKindR<Tools extends ReadonlyArray<AnyTool<any>>> = Tools[number] extends StreamingTool<any, any, any, any, infer R> ? R : Tools[number] extends Tool<any, any, any, infer R> ? R : never;
|
|
19
|
-
type ExecuteOptions = {
|
|
20
|
-
readonly concurrency?: number | "unbounded";
|
|
21
|
-
};
|
|
22
|
-
/** Execute every provided call. Approval/rejection policy belongs upstream. */
|
|
23
|
-
declare const run: <Tools extends ReadonlyArray<AnyTool<any>>>(tools: Tools, calls: ReadonlyArray<ToolCall>, options?: ExecuteOptions) => Stream.Stream<ToolEvent, never, ToolKindR<Tools>>;
|
|
24
|
-
/**
|
|
25
|
-
* Append a completed turn plus tool results to a state's history, converting
|
|
26
|
-
* the results to wire-format `ToolCallOutput`s. Curried so it slots directly
|
|
27
|
-
* into `continueWithResults`:
|
|
28
|
-
*
|
|
29
|
-
* Toolkit.run(tools, calls).pipe(
|
|
30
|
-
* Toolkit.continueWithResults(Toolkit.appendToolResults(state, turn)),
|
|
31
|
-
* )
|
|
32
|
-
*
|
|
33
|
-
* Equivalent to `appendToHistory(state, turn, results.map(toToolCallOutput))`
|
|
34
|
-
* — the helper just hides the wire-conversion step.
|
|
35
|
-
*/
|
|
36
|
-
declare const appendToolResults: <S extends {
|
|
37
|
-
readonly history: ReadonlyArray<HistoryItem>;
|
|
38
|
-
}>(state: S, turn: Turn) => (results: ReadonlyArray<ToolResult>) => S;
|
|
39
|
-
/**
|
|
40
|
-
* Drain a `Stream<ToolEvent>` and return the accumulated `ToolResult`s
|
|
41
|
-
* from every `Output` event. One-shot — the type is `Effect<results>`,
|
|
42
|
-
* not a one-element stream — so it composes naturally with `Effect.map`
|
|
43
|
-
* to build state.
|
|
44
|
-
*
|
|
45
|
-
* Counterpart to `continueWithResults`: `continueWithResults` bundles
|
|
46
|
-
* drain + emit Next into one bridge; this one is the right arm of a
|
|
47
|
-
* broadcast/fork-and-merge for callers that want to vary the left arm
|
|
48
|
-
* (e.g., filter/tap ToolEvents before forwarding).
|
|
49
|
-
*/
|
|
50
|
-
declare const collectResults: <E, R>(stream: Stream.Stream<ToolEvent, E, R>) => Effect.Effect<ReadonlyArray<ToolResult>, E, R>;
|
|
51
|
-
/**
|
|
52
|
-
* Bridge from a `Stream<ToolEvent>` to the loop's emit shape. Forwards
|
|
53
|
-
* every ToolEvent downstream as a `Loop.value` and at end-of-stream emits
|
|
54
|
-
* one `Loop.next(build(results))` carrying the accumulated `ToolResult`s
|
|
55
|
-
* from terminal `Output` events.
|
|
56
|
-
*
|
|
57
|
-
* Conceptually a broadcast fork (one arm passes events through, the other
|
|
58
|
-
* drains them into a state). Implemented as a single-pass Ref tap + concat
|
|
59
|
-
* for zero buffering — the broadcast version is observationally equivalent
|
|
60
|
-
* but holds events in a PubSub until both arms drain. The public primitives
|
|
61
|
-
* (`Loop.value`, `collectResults`, `Loop.next`) compose the same pattern
|
|
62
|
-
* when you need to vary an arm.
|
|
63
|
-
*
|
|
64
|
-
* Dual: data-first `continueWithResults(stream, build)` and data-last
|
|
65
|
-
* `stream.pipe(continueWithResults(build))` both work.
|
|
66
|
-
*/
|
|
67
|
-
declare const continueWithResults: {
|
|
68
|
-
<S>(build: (results: ReadonlyArray<ToolResult>) => S): <R>(stream: Stream.Stream<ToolEvent, never, R>) => Stream.Stream<Step<ToolEvent, S>, never, R>;
|
|
69
|
-
<S, R>(stream: Stream.Stream<ToolEvent, never, R>, build: (results: ReadonlyArray<ToolResult>) => S): Stream.Stream<Step<ToolEvent, S>, never, R>;
|
|
70
|
-
};
|
|
71
|
-
//#endregion
|
|
72
|
-
export { ExecuteOptions, ToolKindR, appendToolResults, collectResults, continueWithResults, run, Toolkit_d_exports as t };
|
|
73
|
-
//# sourceMappingURL=Toolkit.d.mts.map
|
|
1
|
+
import { _ as makeNamespaced, a as ToolMap, b as wrap, d as collectResults, f as compose, g as make, h as fromArray, i as NoDuplicateNames, l as UniqueTools, m as descriptors, n as ExecuteOptions, o as Toolkit, p as continueWithResults, r as Middleware, s as ToolkitR, t as DuplicateToolName, u as appendToolResults, v as namespace, y as run } from "../Toolkit-BKC8b8Xo.mjs";
|
|
2
|
+
export { DuplicateToolName, ExecuteOptions, Middleware, NoDuplicateNames, ToolMap, Toolkit, ToolkitR, UniqueTools, appendToolResults, collectResults, compose, continueWithResults, descriptors, fromArray, make, makeNamespaced, namespace, run, wrap };
|
package/dist/tool/Toolkit.mjs
CHANGED
|
@@ -1,55 +1,133 @@
|
|
|
1
1
|
import { n as __exportAll } from "../chunk-uyGKjUfl.mjs";
|
|
2
2
|
import { appendToHistory } from "../domain/Turn.mjs";
|
|
3
|
+
import { toDescriptors } from "./Tool.mjs";
|
|
3
4
|
import { next, value } from "../loop/Loop.mjs";
|
|
4
|
-
import {
|
|
5
|
-
import { ToolResult, executionError, failed, toToolCallOutput } from "./ToolResult.mjs";
|
|
5
|
+
import { ToolResult, executionError, failed, nonLocalTool, toToolCallOutput, validationError } from "./ToolResult.mjs";
|
|
6
6
|
import { ToolEvent, isOutput } from "./ToolEvent.mjs";
|
|
7
|
-
import { Array, Effect, Function, Ref, Schema, Stream } from "effect";
|
|
7
|
+
import { Array, Data, Effect, Fiber, Function, Match, Option, Queue, Record, Ref, Schema, Stream } from "effect";
|
|
8
8
|
//#region src/tool/Toolkit.ts
|
|
9
9
|
var Toolkit_exports = /* @__PURE__ */ __exportAll({
|
|
10
|
+
DuplicateToolName: () => DuplicateToolName,
|
|
10
11
|
appendToolResults: () => appendToolResults,
|
|
11
12
|
collectResults: () => collectResults,
|
|
13
|
+
compose: () => compose,
|
|
12
14
|
continueWithResults: () => continueWithResults,
|
|
13
|
-
|
|
15
|
+
descriptors: () => descriptors,
|
|
16
|
+
fromArray: () => fromArray,
|
|
17
|
+
make: () => make,
|
|
18
|
+
makeNamespaced: () => makeNamespaced,
|
|
19
|
+
namespace: () => namespace,
|
|
20
|
+
run: () => run,
|
|
21
|
+
wrap: () => wrap
|
|
14
22
|
});
|
|
23
|
+
const indexByName = (tools) => Object.fromEntries(tools.map((tool) => [tool.name, tool]));
|
|
24
|
+
/**
|
|
25
|
+
* Index literal tools by their `name`. Duplicate names are a compile error
|
|
26
|
+
* (`UniqueTools`). Names are not otherwise checked: a malformed name is a
|
|
27
|
+
* developer typo in a literal that the provider rejects clearly on first use,
|
|
28
|
+
* and there is no single cross-provider name rule to enforce here anyway.
|
|
29
|
+
*/
|
|
30
|
+
const buildStatic = (tools) => indexByName(tools);
|
|
31
|
+
const make = (...tools) => buildStatic(tools);
|
|
32
|
+
/**
|
|
33
|
+
* Build a toolkit from a dynamically-sourced array (MCP server, plugin). Names
|
|
34
|
+
* are not validated (MCP legitimately uses dots / >64 chars — sanitize at the
|
|
35
|
+
* provider adapter); same-named tools resolve last-wins. Cross-source clashes
|
|
36
|
+
* are caught by `compose`.
|
|
37
|
+
*/
|
|
38
|
+
const fromArray = (tools) => indexByName(tools);
|
|
39
|
+
/** Render the provider-facing descriptors for a toolkit on demand. */
|
|
40
|
+
const descriptors = (toolkit) => toDescriptors(Object.values(toolkit));
|
|
41
|
+
/**
|
|
42
|
+
* Two composed toolkits resolved to the same final name. Carries the colliding
|
|
43
|
+
* `sources` (the positions of the clashing toolkits) so the error says *which*
|
|
44
|
+
* inputs collided. Raised at runtime by `compose` for dynamic sources; static
|
|
45
|
+
* collisions are caught earlier at compile time.
|
|
46
|
+
*/
|
|
47
|
+
var DuplicateToolName = class extends Data.TaggedError("DuplicateToolName") {};
|
|
48
|
+
/**
|
|
49
|
+
* Prefix every tool name with `<namespace>__`, returning a new toolkit. Use it
|
|
50
|
+
* to keep generic names (`search`) from two sources distinct before `compose`.
|
|
51
|
+
* If two namespaces collide at the `__` separator, the duplicate final name
|
|
52
|
+
* surfaces from `compose` as a `DuplicateToolName`.
|
|
53
|
+
*/
|
|
54
|
+
const namespace = (prefix, toolkit) => {
|
|
55
|
+
return indexByName(Array.map(Record.values(toolkit), (tool) => ({
|
|
56
|
+
...tool,
|
|
57
|
+
name: `${prefix}__${tool.name}`
|
|
58
|
+
})));
|
|
59
|
+
};
|
|
60
|
+
/** `namespace(prefix, make(...tools))` in one step, for static namespaced tools. */
|
|
61
|
+
const makeNamespaced = (prefix, ...tools) => namespace(prefix, buildStatic(tools));
|
|
62
|
+
/**
|
|
63
|
+
* Combine independently-built toolkits into one. This is where names from
|
|
64
|
+
* separate sources can collide, so it is the one effectful constructor: a
|
|
65
|
+
* duplicate final name fails with `DuplicateToolName` (naming the colliding
|
|
66
|
+
* input positions) instead of silently overwriting or 400-ing later at the
|
|
67
|
+
* provider. Statically-known collisions are additionally a compile error.
|
|
68
|
+
*/
|
|
69
|
+
const compose = (...toolkits) => Effect.gen(function* () {
|
|
70
|
+
const kits = toolkits;
|
|
71
|
+
const labelled = Array.flatMap(kits, (kit, i) => Array.map(Record.keys(kit), (name) => ({
|
|
72
|
+
name,
|
|
73
|
+
source: `toolkit-${i}`
|
|
74
|
+
})));
|
|
75
|
+
const collision = Array.findFirst(Record.toEntries(Array.groupBy(labelled, (entry) => entry.name)), ([, group]) => group.length > 1);
|
|
76
|
+
if (Option.isSome(collision)) {
|
|
77
|
+
const [name, group] = collision.value;
|
|
78
|
+
return yield* new DuplicateToolName({
|
|
79
|
+
name,
|
|
80
|
+
sources: Array.map(group, (e) => e.source)
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return Array.reduce(kits, {}, (acc, kit) => ({
|
|
84
|
+
...acc,
|
|
85
|
+
...kit
|
|
86
|
+
}));
|
|
87
|
+
});
|
|
88
|
+
/**
|
|
89
|
+
* Wrap every local tool's `run` with a middleware, preserving each tool's
|
|
90
|
+
* `Input`/`Output` and unioning the middleware's requirement `R2` into the
|
|
91
|
+
* toolkit's `ToolkitR`. Non-local kinds pass through unchanged. Stack with
|
|
92
|
+
* `pipe`:
|
|
93
|
+
*
|
|
94
|
+
* const observed = pipe(toolkit, Toolkit.wrap(logging), Toolkit.wrap(authz))
|
|
95
|
+
* Toolkit.run(observed, calls) // R = ToolkitR<typeof toolkit> | <authz R>
|
|
96
|
+
*
|
|
97
|
+
* A middleware that `Effect.fail`s surfaces as a `ToolResult.Failure` from the
|
|
98
|
+
* executor like any run failure; use the `Approval` flow for a distinct
|
|
99
|
+
* "denied" verdict.
|
|
100
|
+
*/
|
|
101
|
+
const wrap = Function.dual(2, (toolkit, middleware) => Record.map(toolkit, (tool) => tool._tag === "LocalTool" ? {
|
|
102
|
+
...tool,
|
|
103
|
+
run: middleware(tool.run, tool.name)
|
|
104
|
+
} : tool));
|
|
15
105
|
/** Execute every provided call. Approval/rejection policy belongs upstream. */
|
|
16
|
-
const run = (
|
|
106
|
+
const run = (toolkit, calls, options) => Stream.fromIterable(calls).pipe(Stream.flatMap((call) => runOne(toolkit, call), { concurrency: options?.concurrency ?? "unbounded" }));
|
|
17
107
|
const okResult = (call, tool, value) => ToolResult.Ok({
|
|
18
108
|
call_id: call.call_id,
|
|
19
109
|
tool,
|
|
20
110
|
value
|
|
21
111
|
});
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
if (tool === void 0) return Stream.succeed(ToolEvent.Output({ result: failed(call, "unknown_tool", `No tool registered with name "${call.name}"`) }));
|
|
25
|
-
if (isStreamingTool(tool)) return runStreaming(tool, call);
|
|
26
|
-
return runPlain(tool, call);
|
|
27
|
-
};
|
|
112
|
+
const outputEvent = (result) => Stream.succeed(ToolEvent.Output({ result }));
|
|
113
|
+
const runOne = (toolkit, call) => Match.value(toolkit[call.name]).pipe(Match.when(Match.undefined, () => outputEvent(failed(call, "unknown_tool", `No tool registered with name "${call.name}"`))), Match.tag("LocalTool", (local) => runTool(local, call)), Match.orElse(() => outputEvent(nonLocalTool(call))));
|
|
28
114
|
const parseJsonUnknown = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown));
|
|
29
|
-
const
|
|
115
|
+
const runTool = (tool, call) => Stream.unwrap(Effect.gen(function* () {
|
|
30
116
|
const parsed = yield* parseJsonUnknown(call.arguments).pipe(Effect.mapError(() => "json_parse_error"));
|
|
31
117
|
const validated = yield* Effect.tryPromise({
|
|
32
118
|
try: () => Promise.resolve(tool.inputSchema["~standard"].validate(parsed)),
|
|
33
119
|
catch: () => "validation_threw"
|
|
34
120
|
});
|
|
35
|
-
if (validated.issues !== void 0) return
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
const parsed = yield* parseJsonUnknown(call.arguments).pipe(Effect.mapError(() => "json_parse_error"));
|
|
41
|
-
const validated = yield* Effect.tryPromise({
|
|
42
|
-
try: () => Promise.resolve(tool.inputSchema["~standard"].validate(parsed)),
|
|
43
|
-
catch: () => "validation_threw"
|
|
44
|
-
});
|
|
45
|
-
if (validated.issues !== void 0) return Stream.succeed(ToolEvent.Output({ result: executionError(call, "Tool input failed schema validation") }));
|
|
46
|
-
const ref = yield* Ref.make([]);
|
|
47
|
-
const progress = tool.run(validated.value).pipe(Stream.tap((event) => Ref.update(ref, Array.append(event))), Stream.map((data) => ToolEvent.Progress({
|
|
121
|
+
if (validated.issues !== void 0) return Stream.succeed(ToolEvent.Output({ result: validationError(call) }));
|
|
122
|
+
const queue = yield* Queue.make({ capacity: tool.emitBufferSize });
|
|
123
|
+
const emit = (event) => Effect.asVoid(Queue.offer(queue, event));
|
|
124
|
+
const fiber = yield* tool.run(validated.value, emit).pipe(Effect.ensuring(Queue.end(queue)), Effect.forkScoped);
|
|
125
|
+
const progress = Stream.fromQueue(queue).pipe(Stream.map((data) => ToolEvent.Progress({
|
|
48
126
|
call_id: call.call_id,
|
|
49
127
|
tool: tool.name,
|
|
50
128
|
data
|
|
51
129
|
})));
|
|
52
|
-
const output = Stream.fromEffect(
|
|
130
|
+
const output = Stream.fromEffect(Fiber.join(fiber).pipe(Effect.map((value) => ToolEvent.Output({ result: okResult(call, tool.name, value) })), Effect.catchCause(() => Effect.succeed(ToolEvent.Output({ result: executionError(call, "Tool execution failed") })))));
|
|
53
131
|
return progress.pipe(Stream.concat(output));
|
|
54
132
|
})).pipe(Stream.catchCause(() => Stream.succeed(ToolEvent.Output({ result: executionError(call, "Tool execution failed") }))));
|
|
55
133
|
/**
|
|
@@ -57,7 +135,7 @@ const runStreaming = (tool, call) => Stream.unwrap(Effect.gen(function* () {
|
|
|
57
135
|
* the results to wire-format `ToolCallOutput`s. Curried so it slots directly
|
|
58
136
|
* into `continueWithResults`:
|
|
59
137
|
*
|
|
60
|
-
* Toolkit.run(
|
|
138
|
+
* Toolkit.run(toolkit, calls).pipe(
|
|
61
139
|
* Toolkit.continueWithResults(Toolkit.appendToolResults(state, turn)),
|
|
62
140
|
* )
|
|
63
141
|
*
|
|
@@ -100,6 +178,6 @@ const continueWithResults = Function.dual(2, (stream, build) => Stream.unwrap(Ef
|
|
|
100
178
|
return tapped.pipe(Stream.concat(continuation));
|
|
101
179
|
})));
|
|
102
180
|
//#endregion
|
|
103
|
-
export { appendToolResults, collectResults, continueWithResults, run, Toolkit_exports as t };
|
|
181
|
+
export { DuplicateToolName, appendToolResults, collectResults, compose, continueWithResults, descriptors, fromArray, make, makeNamespaced, namespace, run, Toolkit_exports as t, wrap };
|
|
104
182
|
|
|
105
183
|
//# sourceMappingURL=Toolkit.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Toolkit.mjs","names":["Arr","Loop.value","Loop.next"],"sources":["../../src/tool/Toolkit.ts"],"sourcesContent":["import { Array as Arr, Effect, Function, Ref, Schema, Stream } from \"effect\"\nimport * as Loop from \"../loop/Loop.js\"\nimport type { ToolCall, HistoryItem } from \"../domain/Items.js\"\nimport { appendToHistory, type Turn } from \"../domain/Turn.js\"\nimport {\n type AnyTool,\n type AnyPlainTool,\n type AnyStreamingTool,\n isStreamingTool,\n type StreamingTool,\n type Tool,\n} from \"./Tool.js\"\nimport { ToolResult, executionError, failed, toToolCallOutput } from \"./ToolResult.js\"\nimport { ToolEvent } from \"./ToolEvent.js\"\nimport { isOutput } from \"./ToolEvent.js\"\n\n/**\n * Union of every tool's `R` requirements in a mixed plain + streaming array.\n * Used by `run` to surface the services tools need at the recipe level, so\n * the loop's stream type carries them through to `Effect.provide`.\n */\nexport type ToolKindR<Tools extends ReadonlyArray<AnyTool<any>>> =\n Tools[number] extends StreamingTool<any, any, any, any, infer R>\n ? R\n : Tools[number] extends Tool<any, any, any, infer R>\n ? R\n : never\n\n// ---------------------------------------------------------------------------\n// Tool executor. Streams `ToolEvent`s in real time and dispatches streaming\n// and plain tools uniformly. Policy stays outside this module: callers pass\n// only the calls they have already decided should run.\n// ---------------------------------------------------------------------------\n\nexport type ExecuteOptions = {\n readonly concurrency?: number | \"unbounded\"\n}\n\n/** Execute every provided call. Approval/rejection policy belongs upstream. */\nexport const run = <Tools extends ReadonlyArray<AnyTool<any>>>(\n tools: Tools,\n calls: ReadonlyArray<ToolCall>,\n options?: ExecuteOptions,\n): Stream.Stream<ToolEvent, never, ToolKindR<Tools>> =>\n Stream.fromIterable(calls).pipe(\n Stream.flatMap((call) => runOne(tools, call), {\n concurrency: options?.concurrency ?? \"unbounded\",\n }),\n )\n\nconst okResult = (call: ToolCall, tool: string, value: unknown): ToolResult =>\n ToolResult.Ok({\n call_id: call.call_id,\n tool,\n value,\n })\n\nconst runOne = <R>(\n tools: ReadonlyArray<AnyTool<R>>,\n call: ToolCall,\n): Stream.Stream<ToolEvent, never, R> => {\n const tool = tools.find((t) => t.name === call.name)\n if (tool === undefined) {\n // Graceful: emit a synthetic Failure so OTHER calls in this turn\n // still execute. LLMs hallucinate tool names; MCP tools come and go.\n return Stream.succeed(\n ToolEvent.Output({\n result: failed(call, \"unknown_tool\", `No tool registered with name \"${call.name}\"`),\n }),\n )\n }\n if (isStreamingTool(tool)) return runStreaming(tool, call)\n return runPlain(tool, call)\n}\n\nconst parseJsonUnknown = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown))\n\nconst runPlain = <R>(tool: AnyPlainTool<R>, call: ToolCall): Stream.Stream<ToolEvent, never, R> =>\n Stream.fromEffect(\n Effect.gen(function* () {\n const parsed = yield* parseJsonUnknown(call.arguments).pipe(\n Effect.mapError(() => \"json_parse_error\" as const),\n )\n const validated = yield* Effect.tryPromise({\n try: () => Promise.resolve(tool.inputSchema[\"~standard\"].validate(parsed)),\n catch: () => \"validation_threw\" as const,\n })\n if (validated.issues !== undefined) {\n return executionError(call, \"Tool input failed schema validation\")\n }\n const output = yield* tool.run(validated.value)\n return okResult(call, tool.name, output)\n }).pipe(\n Effect.catchCause(() => Effect.succeed(executionError(call, \"Tool execution failed\"))),\n Effect.map((result) => ToolEvent.Output({ result })),\n ),\n )\n\nconst runStreaming = <R>(\n tool: AnyStreamingTool<R>,\n call: ToolCall,\n): Stream.Stream<ToolEvent, never, R> =>\n Stream.unwrap(\n Effect.gen(function* () {\n const parsed = yield* parseJsonUnknown(call.arguments).pipe(\n Effect.mapError(() => \"json_parse_error\" as const),\n )\n const validated = yield* Effect.tryPromise({\n try: () => Promise.resolve(tool.inputSchema[\"~standard\"].validate(parsed)),\n catch: () => \"validation_threw\" as const,\n })\n if (validated.issues !== undefined) {\n return Stream.succeed<ToolEvent>(\n ToolEvent.Output({\n result: executionError(call, \"Tool input failed schema validation\"),\n }),\n )\n }\n\n // Real-time: tap each event into a Ref as it flows; emit one\n // Progress per event; then concat one synthetic Output element\n // built from the accumulated Ref via `finalize`.\n const ref = yield* Ref.make<Array<unknown>>([])\n const progress = tool.run(validated.value).pipe(\n Stream.tap((event) => Ref.update(ref, Arr.append(event))),\n Stream.map((data) =>\n ToolEvent.Progress({\n call_id: call.call_id,\n tool: tool.name,\n data,\n }),\n ),\n )\n const output = Stream.fromEffect(\n Ref.get(ref).pipe(\n Effect.map((events) =>\n ToolEvent.Output({\n result: okResult(call, tool.name, tool.finalize(events)),\n }),\n ),\n ),\n )\n return progress.pipe(Stream.concat(output))\n }),\n ).pipe(\n Stream.catchCause(() =>\n Stream.succeed(\n ToolEvent.Output({\n result: executionError(call, \"Tool execution failed\"),\n }),\n ),\n ),\n )\n\n// ---------------------------------------------------------------------------\n// `continueWithResults` - bridge from a `Stream<ToolEvent>` to the loop's\n// emit shape. Drains the stream to the consumer in real-time, taps every\n// `Output` into an internal Ref, and at end-of-stream emits\n// `Loop.next(build(results))`. Recipe never sees the Ref.\n//\n// Dual: data-first `continueWithResults(stream, build)` and data-last\n// `stream.pipe(continueWithResults(build))` both work.\n// ---------------------------------------------------------------------------\n\n/**\n * Append a completed turn plus tool results to a state's history, converting\n * the results to wire-format `ToolCallOutput`s. Curried so it slots directly\n * into `continueWithResults`:\n *\n * Toolkit.run(tools, calls).pipe(\n * Toolkit.continueWithResults(Toolkit.appendToolResults(state, turn)),\n * )\n *\n * Equivalent to `appendToHistory(state, turn, results.map(toToolCallOutput))`\n * — the helper just hides the wire-conversion step.\n */\nexport const appendToolResults =\n <S extends { readonly history: ReadonlyArray<HistoryItem> }>(state: S, turn: Turn) =>\n (results: ReadonlyArray<ToolResult>): S =>\n appendToHistory(state, turn, results.map(toToolCallOutput))\n\n/**\n * Drain a `Stream<ToolEvent>` and return the accumulated `ToolResult`s\n * from every `Output` event. One-shot — the type is `Effect<results>`,\n * not a one-element stream — so it composes naturally with `Effect.map`\n * to build state.\n *\n * Counterpart to `continueWithResults`: `continueWithResults` bundles\n * drain + emit Next into one bridge; this one is the right arm of a\n * broadcast/fork-and-merge for callers that want to vary the left arm\n * (e.g., filter/tap ToolEvents before forwarding).\n */\nexport const collectResults = <E, R>(\n stream: Stream.Stream<ToolEvent, E, R>,\n): Effect.Effect<ReadonlyArray<ToolResult>, E, R> =>\n stream.pipe(\n Stream.filter(isOutput),\n Stream.map((e) => e.result),\n Stream.runCollect,\n )\n\n/**\n * Bridge from a `Stream<ToolEvent>` to the loop's emit shape. Forwards\n * every ToolEvent downstream as a `Loop.value` and at end-of-stream emits\n * one `Loop.next(build(results))` carrying the accumulated `ToolResult`s\n * from terminal `Output` events.\n *\n * Conceptually a broadcast fork (one arm passes events through, the other\n * drains them into a state). Implemented as a single-pass Ref tap + concat\n * for zero buffering — the broadcast version is observationally equivalent\n * but holds events in a PubSub until both arms drain. The public primitives\n * (`Loop.value`, `collectResults`, `Loop.next`) compose the same pattern\n * when you need to vary an arm.\n *\n * Dual: data-first `continueWithResults(stream, build)` and data-last\n * `stream.pipe(continueWithResults(build))` both work.\n */\nexport const continueWithResults: {\n <S>(\n build: (results: ReadonlyArray<ToolResult>) => S,\n ): <R>(\n stream: Stream.Stream<ToolEvent, never, R>,\n ) => Stream.Stream<Loop.Step<ToolEvent, S>, never, R>\n <S, R>(\n stream: Stream.Stream<ToolEvent, never, R>,\n build: (results: ReadonlyArray<ToolResult>) => S,\n ): Stream.Stream<Loop.Step<ToolEvent, S>, never, R>\n} = Function.dual(\n 2,\n <S, R>(\n stream: Stream.Stream<ToolEvent, never, R>,\n build: (results: ReadonlyArray<ToolResult>) => S,\n ): Stream.Stream<Loop.Step<ToolEvent, S>, never, R> =>\n Stream.unwrap(\n Effect.gen(function* () {\n const ref = yield* Ref.make<ReadonlyArray<ToolResult>>([])\n const tapped = stream.pipe(\n Stream.tap((e) =>\n isOutput(e) ? Ref.update(ref, (acc) => Arr.append(acc, e.result)) : Effect.void,\n ),\n Stream.map(Loop.value),\n )\n const continuation = Stream.unwrap(\n Ref.get(ref).pipe(Effect.map((acc) => Loop.next(build(acc)))),\n )\n return tapped.pipe(Stream.concat(continuation))\n }),\n ),\n)\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Toolkit.mjs","names":["Arr","Loop.value","Loop.next"],"sources":["../../src/tool/Toolkit.ts"],"sourcesContent":["import {\n Array as Arr,\n Cause,\n Data,\n Effect,\n Fiber,\n Function,\n Match,\n Option,\n Queue,\n Record,\n Ref,\n Schema,\n Stream,\n} from \"effect\"\nimport * as Loop from \"../loop/Loop.js\"\nimport type { HistoryItem, ToolCall } from \"../domain/Items.js\"\nimport { appendToHistory, type Turn } from \"../domain/Turn.js\"\nimport {\n type AnyLocalTool,\n type AnyTool,\n type Emit,\n type InteractionTool,\n type ProviderTool,\n type SignalTool,\n type Tool,\n type ToolDescriptor,\n type ToolR,\n toDescriptors,\n} from \"./Tool.js\"\nimport {\n ToolResult,\n executionError,\n failed,\n nonLocalTool,\n toToolCallOutput,\n validationError,\n} from \"./ToolResult.js\"\nimport { ToolEvent } from \"./ToolEvent.js\"\nimport { isOutput } from \"./ToolEvent.js\"\n\n// ---------------------------------------------------------------------------\n// Toolkit: a name-indexed record of tools — \"what the model sees\". The record\n// gives O(1) dispatch and within-toolkit name-uniqueness for free; descriptors\n// are derived, not stored. Build a static one with `make` (compile-time\n// duplicate-name check), a dynamic one with `fromArray` (trusted single source,\n// last-wins), and combine independent ones with `compose` (the application\n// boundary where cross-source names can clash).\n// ---------------------------------------------------------------------------\n\nexport type ToolMap = Record<string, AnyTool>\n\nexport type Toolkit<Tools extends ToolMap = ToolMap> = Tools\n\nconst indexByName = (tools: ReadonlyArray<AnyTool>): ToolMap =>\n Object.fromEntries(tools.map((tool) => [tool.name, tool]))\n\n/**\n * Index literal tools by their `name`. Duplicate names are a compile error\n * (`UniqueTools`). Names are not otherwise checked: a malformed name is a\n * developer typo in a literal that the provider rejects clearly on first use,\n * and there is no single cross-provider name rule to enforce here anyway.\n */\nconst buildStatic = <Tools extends ReadonlyArray<AnyTool>>(\n tools: Tools,\n): Toolkit<{ [T in Tools[number] as T[\"name\"]]: T }> =>\n indexByName(tools) as Toolkit<{ [T in Tools[number] as T[\"name\"]]: T }>\n\nexport const make = <const Tools extends ReadonlyArray<AnyTool>>(\n ...tools: UniqueTools<Tools>\n): Toolkit<{ [T in Tools[number] as T[\"name\"]]: T }> => buildStatic(tools as unknown as Tools)\n\n/**\n * Build a toolkit from a dynamically-sourced array (MCP server, plugin). Names\n * are not validated (MCP legitimately uses dots / >64 chars — sanitize at the\n * provider adapter); same-named tools resolve last-wins. Cross-source clashes\n * are caught by `compose`.\n */\nexport const fromArray = (tools: ReadonlyArray<AnyTool>): Toolkit => indexByName(tools)\n\n/** Render the provider-facing descriptors for a toolkit on demand. */\nexport const descriptors = (toolkit: Toolkit): ReadonlyArray<ToolDescriptor> =>\n toDescriptors(Object.values(toolkit))\n\n/**\n * Union of every tool's `R` requirements in a toolkit. Surfaced by `run` so the\n * loop's stream type carries the services tools need through to `Effect.provide`.\n *\n * The `ToolMap extends T` guard yields `never` for the wide `Toolkit` type (a\n * toolkit passed through an untyped boundary, where the requirements aren't\n * statically known) instead of `any`, which would otherwise poison the\n * resulting stream's `R`. Concrete toolkits from `make` keep their precise R.\n */\nexport type ToolkitR<T extends Toolkit> = ToolMap extends T ? never : ToolR<T[keyof T]>\n\n// ---------------------------------------------------------------------------\n// Uniqueness & composition. `make` rejects duplicate literal names at compile\n// time; `compose` rejects cross-source collisions — at compile time when the\n// names are statically known, and at runtime (`DuplicateToolName`) for dynamic\n// sources whose keys are only `string`. No silent last-wins overwrite, no late\n// provider 400.\n// ---------------------------------------------------------------------------\n\n// --- compile-time duplicate detection --------------------------------------\n\ntype DuplicateName<\n Tools extends ReadonlyArray<AnyTool>,\n Seen extends string = never,\n> = Tools extends readonly [\n infer Head extends AnyTool,\n ...infer Tail extends ReadonlyArray<AnyTool>,\n]\n ? Head[\"name\"] extends Seen\n ? Head[\"name\"]\n : DuplicateName<Tail, Seen | Head[\"name\"]>\n : never\n\ntype DuplicateNameError<Name extends string> = {\n readonly __error: `Duplicate tool name: ${Name}`\n}\n\nexport type NoDuplicateNames<Tools extends ReadonlyArray<AnyTool>> = [\n DuplicateName<Tools>,\n] extends [never]\n ? unknown\n : DuplicateNameError<DuplicateName<Tools>>\n\n/** `Tools` constrained so a repeated literal name is a compile error in `make`. */\nexport type UniqueTools<Tools extends ReadonlyArray<AnyTool>> = Tools & NoDuplicateNames<Tools>\n\n// Cross-toolkit literal-key collision, skipping wide (`string`-keyed, dynamic)\n// toolkits so the compile-time check never false-positives on `fromArray`.\ntype ComposeDuplicateKey<\n Kits extends ReadonlyArray<Toolkit>,\n Seen extends string = never,\n> = Kits extends readonly [infer Head extends Toolkit, ...infer Tail extends ReadonlyArray<Toolkit>]\n ? string extends keyof Head\n ? ComposeDuplicateKey<Tail, Seen>\n : Extract<keyof Head & string, Seen> extends never\n ? ComposeDuplicateKey<Tail, Seen | (keyof Head & string)>\n : Extract<keyof Head & string, Seen>\n : never\n\ntype NoDuplicateComposedKeys<Kits extends ReadonlyArray<Toolkit>> = [\n ComposeDuplicateKey<Kits>,\n] extends [never]\n ? unknown\n : DuplicateNameError<ComposeDuplicateKey<Kits>>\n\ntype UnionToIntersection<U> = (U extends unknown ? (x: U) => void : never) extends (\n x: infer I,\n) => void\n ? I\n : never\n\ntype Composed<Kits extends ReadonlyArray<Toolkit>> =\n UnionToIntersection<Kits[number]> extends infer M ? (M extends ToolMap ? M : ToolMap) : ToolMap\n\n// --- uniqueness error ------------------------------------------------------\n\n/**\n * Two composed toolkits resolved to the same final name. Carries the colliding\n * `sources` (the positions of the clashing toolkits) so the error says *which*\n * inputs collided. Raised at runtime by `compose` for dynamic sources; static\n * collisions are caught earlier at compile time.\n */\nexport class DuplicateToolName extends Data.TaggedError(\"DuplicateToolName\")<{\n readonly name: string\n readonly sources: ReadonlyArray<string>\n}> {}\n\n// --- namespacing -----------------------------------------------------------\n\ntype PrefixName<Prefix extends string, Name extends string> = `${Prefix}__${Name}`\n\ntype PrefixTool<Prefix extends string, T extends AnyTool> =\n T extends Tool<infer N, infer I, infer Ev, infer O, infer R>\n ? Tool<PrefixName<Prefix, N>, I, Ev, O, R>\n : T extends ProviderTool<infer N, infer I, infer P, infer C>\n ? ProviderTool<PrefixName<Prefix, N>, I, P, C>\n : T extends SignalTool<infer N, infer I>\n ? SignalTool<PrefixName<Prefix, N>, I>\n : T extends InteractionTool<infer N, infer I>\n ? InteractionTool<PrefixName<Prefix, N>, I>\n : T\n\ntype Namespaced<Prefix extends string, T extends ToolMap> = {\n readonly [K in keyof T & string as PrefixName<Prefix, K>]: T[K] extends AnyTool\n ? PrefixTool<Prefix, T[K]>\n : never\n}\n\n/**\n * Prefix every tool name with `<namespace>__`, returning a new toolkit. Use it\n * to keep generic names (`search`) from two sources distinct before `compose`.\n * If two namespaces collide at the `__` separator, the duplicate final name\n * surfaces from `compose` as a `DuplicateToolName`.\n */\nexport const namespace = <const Prefix extends string, const T extends ToolMap>(\n prefix: Prefix,\n toolkit: Toolkit<T>,\n): Toolkit<Namespaced<Prefix, T>> => {\n const renamed = Arr.map(Record.values(toolkit), (tool) => ({\n ...tool,\n name: `${prefix}__${tool.name}`,\n }))\n return indexByName(renamed) as Toolkit<Namespaced<Prefix, T>>\n}\n\n/** `namespace(prefix, make(...tools))` in one step, for static namespaced tools. */\nexport const makeNamespaced = <\n const Prefix extends string,\n const Tools extends ReadonlyArray<AnyTool>,\n>(\n prefix: Prefix,\n ...tools: UniqueTools<Tools>\n): Toolkit<Namespaced<Prefix, { [T in Tools[number] as T[\"name\"]]: T }>> =>\n namespace(prefix, buildStatic(tools as unknown as Tools))\n\n// --- composition (the application boundary) --------------------------------\n\n/**\n * Combine independently-built toolkits into one. This is where names from\n * separate sources can collide, so it is the one effectful constructor: a\n * duplicate final name fails with `DuplicateToolName` (naming the colliding\n * input positions) instead of silently overwriting or 400-ing later at the\n * provider. Statically-known collisions are additionally a compile error.\n */\nexport const compose = <const Kits extends ReadonlyArray<Toolkit>>(\n ...toolkits: Kits & NoDuplicateComposedKeys<Kits>\n): Effect.Effect<Toolkit<Composed<Kits>>, DuplicateToolName> =>\n Effect.gen(function* () {\n const kits = toolkits as unknown as ReadonlyArray<Toolkit>\n const labelled = Arr.flatMap(kits, (kit, i) =>\n Arr.map(Record.keys(kit), (name) => ({ name, source: `toolkit-${i}` })),\n )\n const collision = Arr.findFirst(\n Record.toEntries(Arr.groupBy(labelled, (entry) => entry.name)),\n ([, group]) => group.length > 1,\n )\n if (Option.isSome(collision)) {\n const [name, group] = collision.value\n return yield* new DuplicateToolName({ name, sources: Arr.map(group, (e) => e.source) })\n }\n return Arr.reduce(kits, {} as ToolMap, (acc, kit) => ({ ...acc, ...kit })) as Toolkit<\n Composed<Kits>\n >\n })\n\n// ---------------------------------------------------------------------------\n// Middleware: a `Toolkit -> Toolkit` transform applied up front, then the\n// wrapped toolkit is run. A middleware transforms one local tool's `run` given\n// its name (logging, retry, auth, metrics); `wrap` lifts it over the whole\n// toolkit, leaving provider/signal/interaction kinds untouched. The one reason\n// this is a combinator rather than native `Record.map`: it tracks the\n// middleware's added requirement `R2`, unioning it into `ToolkitR` instead of\n// widening every tool's type.\n// ---------------------------------------------------------------------------\n\ntype Handler<Input, Event, Output, R> = (\n input: Input,\n emit: Emit<Event>,\n) => Effect.Effect<Output, unknown, R>\n\nexport type Middleware<R2 = never> = <Input, Event, Output, R>(\n run: Handler<Input, Event, Output, R>,\n name: string,\n) => Handler<Input, Event, Output, R | R2>\n\ntype WrapTool<T extends AnyTool, R2> =\n T extends Tool<infer N, infer I, infer Ev, infer O, infer R> ? Tool<N, I, Ev, O, R | R2> : T\n\ntype WrapToolkit<T extends ToolMap, R2> = {\n readonly [K in keyof T]: T[K] extends AnyTool ? WrapTool<T[K], R2> : T[K]\n}\n\n/**\n * Wrap every local tool's `run` with a middleware, preserving each tool's\n * `Input`/`Output` and unioning the middleware's requirement `R2` into the\n * toolkit's `ToolkitR`. Non-local kinds pass through unchanged. Stack with\n * `pipe`:\n *\n * const observed = pipe(toolkit, Toolkit.wrap(logging), Toolkit.wrap(authz))\n * Toolkit.run(observed, calls) // R = ToolkitR<typeof toolkit> | <authz R>\n *\n * A middleware that `Effect.fail`s surfaces as a `ToolResult.Failure` from the\n * executor like any run failure; use the `Approval` flow for a distinct\n * \"denied\" verdict.\n */\nexport const wrap: {\n <R2>(middleware: Middleware<R2>): <T extends Toolkit>(toolkit: T) => WrapToolkit<T, R2>\n <T extends Toolkit, R2>(toolkit: T, middleware: Middleware<R2>): WrapToolkit<T, R2>\n} = Function.dual(\n 2,\n <T extends Toolkit, R2>(toolkit: T, middleware: Middleware<R2>): WrapToolkit<T, R2> =>\n Record.map(toolkit, (tool) =>\n tool._tag === \"LocalTool\" ? { ...tool, run: middleware(tool.run, tool.name) } : tool,\n ) as WrapToolkit<T, R2>,\n)\n\n// ---------------------------------------------------------------------------\n// Tool executor. Streams `ToolEvent`s in real time. Policy stays outside this\n// module: callers pass only the calls they have already decided should run.\n// ---------------------------------------------------------------------------\n\nexport type ExecuteOptions = {\n readonly concurrency?: number | \"unbounded\"\n}\n\n/** Execute every provided call. Approval/rejection policy belongs upstream. */\nexport const run = <T extends Toolkit>(\n toolkit: T,\n calls: ReadonlyArray<ToolCall>,\n options?: ExecuteOptions,\n): Stream.Stream<ToolEvent, never, ToolkitR<T>> =>\n Stream.fromIterable(calls).pipe(\n Stream.flatMap((call) => runOne(toolkit, call), {\n concurrency: options?.concurrency ?? \"unbounded\",\n }),\n ) as Stream.Stream<ToolEvent, never, ToolkitR<T>>\n\nconst okResult = (call: ToolCall, tool: string, value: unknown): ToolResult =>\n ToolResult.Ok({ call_id: call.call_id, tool, value })\n\nconst outputEvent = (result: ToolResult): Stream.Stream<ToolEvent, never, any> =>\n Stream.succeed(ToolEvent.Output({ result }))\n\nconst runOne = (toolkit: ToolMap, call: ToolCall): Stream.Stream<ToolEvent, never, any> =>\n Match.value(toolkit[call.name]).pipe(\n // No such tool. Graceful: emit a synthetic Failure so OTHER calls in this\n // turn still execute. LLMs hallucinate tool names; MCP tools come and go.\n Match.when(Match.undefined, () =>\n outputEvent(failed(call, \"unknown_tool\", `No tool registered with name \"${call.name}\"`)),\n ),\n Match.tag(\"LocalTool\", (local) => runTool(local, call)),\n // Provider/signal/interaction tools are model-visible but not locally\n // executable. The loop is expected to intercept these (decode + act) before\n // execution; reaching here means it didn't, so report it rather than crash.\n Match.orElse(() => outputEvent(nonLocalTool(call))),\n )\n\nconst parseJsonUnknown = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown))\n\nconst runTool = (tool: AnyLocalTool, call: ToolCall): Stream.Stream<ToolEvent, never, any> =>\n Stream.unwrap(\n Effect.gen(function* () {\n const parsed = yield* parseJsonUnknown(call.arguments).pipe(\n Effect.mapError(() => \"json_parse_error\" as const),\n )\n const validated = yield* Effect.tryPromise({\n try: () => Promise.resolve(tool.inputSchema[\"~standard\"].validate(parsed)),\n catch: () => \"validation_threw\" as const,\n })\n if (validated.issues !== undefined) {\n return Stream.succeed<ToolEvent>(ToolEvent.Output({ result: validationError(call) }))\n }\n\n // Per-call queue: `emit` offers events as `run` produces them; the\n // progress stream drains them in real time; `Queue.end` (in the\n // `ensuring`) flushes pending events then signals a clean end, which\n // `Stream.fromQueue` treats as completion. `Queue.shutdown` would clear\n // queued items and interrupt pending takes — wrong for graceful teardown.\n const queue = yield* Queue.make<unknown, Cause.Done>({ capacity: tool.emitBufferSize })\n const emit = (event: unknown) => Effect.asVoid(Queue.offer(queue, event))\n const fiber = yield* tool\n .run(validated.value, emit)\n .pipe(Effect.ensuring(Queue.end(queue)), Effect.forkScoped)\n\n const progress = Stream.fromQueue(queue).pipe(\n Stream.map((data) => ToolEvent.Progress({ call_id: call.call_id, tool: tool.name, data })),\n )\n const output = Stream.fromEffect(\n Fiber.join(fiber).pipe(\n Effect.map((value) => ToolEvent.Output({ result: okResult(call, tool.name, value) })),\n Effect.catchCause(() =>\n Effect.succeed(\n ToolEvent.Output({ result: executionError(call, \"Tool execution failed\") }),\n ),\n ),\n ),\n )\n return progress.pipe(Stream.concat(output))\n }),\n ).pipe(\n // Backstop for the input-parsing failures (`json_parse_error`,\n // `validation_threw`) and any defect before the run fiber is forked.\n Stream.catchCause(() =>\n Stream.succeed(ToolEvent.Output({ result: executionError(call, \"Tool execution failed\") })),\n ),\n )\n\n// ---------------------------------------------------------------------------\n// `continueWithResults` - bridge from a `Stream<ToolEvent>` to the loop's\n// emit shape. Drains the stream to the consumer in real-time, taps every\n// `Output` into an internal Ref, and at end-of-stream emits\n// `Loop.next(build(results))`. Recipe never sees the Ref.\n//\n// Dual: data-first `continueWithResults(stream, build)` and data-last\n// `stream.pipe(continueWithResults(build))` both work.\n// ---------------------------------------------------------------------------\n\n/**\n * Append a completed turn plus tool results to a state's history, converting\n * the results to wire-format `ToolCallOutput`s. Curried so it slots directly\n * into `continueWithResults`:\n *\n * Toolkit.run(toolkit, calls).pipe(\n * Toolkit.continueWithResults(Toolkit.appendToolResults(state, turn)),\n * )\n *\n * Equivalent to `appendToHistory(state, turn, results.map(toToolCallOutput))`\n * — the helper just hides the wire-conversion step.\n */\nexport const appendToolResults =\n <S extends { readonly history: ReadonlyArray<HistoryItem> }>(state: S, turn: Turn) =>\n (results: ReadonlyArray<ToolResult>): S =>\n appendToHistory(state, turn, results.map(toToolCallOutput))\n\n/**\n * Drain a `Stream<ToolEvent>` and return the accumulated `ToolResult`s\n * from every `Output` event. One-shot — the type is `Effect<results>`,\n * not a one-element stream — so it composes naturally with `Effect.map`\n * to build state.\n *\n * Counterpart to `continueWithResults`: `continueWithResults` bundles\n * drain + emit Next into one bridge; this one is the right arm of a\n * broadcast/fork-and-merge for callers that want to vary the left arm\n * (e.g., filter/tap ToolEvents before forwarding).\n */\nexport const collectResults = <E, R>(\n stream: Stream.Stream<ToolEvent, E, R>,\n): Effect.Effect<ReadonlyArray<ToolResult>, E, R> =>\n stream.pipe(\n Stream.filter(isOutput),\n Stream.map((e) => e.result),\n Stream.runCollect,\n )\n\n/**\n * Bridge from a `Stream<ToolEvent>` to the loop's emit shape. Forwards\n * every ToolEvent downstream as a `Loop.value` and at end-of-stream emits\n * one `Loop.next(build(results))` carrying the accumulated `ToolResult`s\n * from terminal `Output` events.\n *\n * Conceptually a broadcast fork (one arm passes events through, the other\n * drains them into a state). Implemented as a single-pass Ref tap + concat\n * for zero buffering — the broadcast version is observationally equivalent\n * but holds events in a PubSub until both arms drain. The public primitives\n * (`Loop.value`, `collectResults`, `Loop.next`) compose the same pattern\n * when you need to vary an arm.\n *\n * Dual: data-first `continueWithResults(stream, build)` and data-last\n * `stream.pipe(continueWithResults(build))` both work.\n */\nexport const continueWithResults: {\n <S>(\n build: (results: ReadonlyArray<ToolResult>) => S,\n ): <R>(\n stream: Stream.Stream<ToolEvent, never, R>,\n ) => Stream.Stream<Loop.Step<ToolEvent, S>, never, R>\n <S, R>(\n stream: Stream.Stream<ToolEvent, never, R>,\n build: (results: ReadonlyArray<ToolResult>) => S,\n ): Stream.Stream<Loop.Step<ToolEvent, S>, never, R>\n} = Function.dual(\n 2,\n <S, R>(\n stream: Stream.Stream<ToolEvent, never, R>,\n build: (results: ReadonlyArray<ToolResult>) => S,\n ): Stream.Stream<Loop.Step<ToolEvent, S>, never, R> =>\n Stream.unwrap(\n Effect.gen(function* () {\n const ref = yield* Ref.make<ReadonlyArray<ToolResult>>([])\n const tapped = stream.pipe(\n Stream.tap((e) =>\n isOutput(e) ? Ref.update(ref, (acc) => Arr.append(acc, e.result)) : Effect.void,\n ),\n Stream.map(Loop.value),\n )\n const continuation = Stream.unwrap(\n Ref.get(ref).pipe(Effect.map((acc) => Loop.next(build(acc)))),\n )\n return tapped.pipe(Stream.concat(continuation))\n }),\n ),\n)\n"],"mappings":"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"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { fromEffectSchema, make, toDescriptors } from "./Tool.mjs";
|
|
1
|
+
import { fromEffectSchema, make, provider, signal, toDescriptors } from "./Tool.mjs";
|
|
2
2
|
import { isOk } from "./ToolResult.mjs";
|
|
3
3
|
import { isOutput } from "./ToolEvent.mjs";
|
|
4
|
-
import { run } from "./Toolkit.mjs";
|
|
5
|
-
import {
|
|
6
|
-
import { Context, Effect, Layer, Schema, Stream } from "effect";
|
|
4
|
+
import { compose, fromArray, make as make$1, namespace, run, wrap } from "./Toolkit.mjs";
|
|
5
|
+
import { a as describe, n as globalExpect, o as it, t as import_dist } from "../dist-B04dlFHg.mjs";
|
|
6
|
+
import { Context, Effect, Layer, Schema, Stream, pipe } from "effect";
|
|
7
7
|
//#region src/tool/Toolkit.test.ts
|
|
8
8
|
describe("Tool.toDescriptors", () => {
|
|
9
9
|
const GetWeatherInput = Schema.Struct({ city: Schema.String });
|
|
@@ -78,11 +78,11 @@ describe("Toolkit.run - tools with R requirements", () => {
|
|
|
78
78
|
arguments: "{}"
|
|
79
79
|
});
|
|
80
80
|
it("propagates each tool's R into the resulting Stream's requirements", () => {
|
|
81
|
-
(0, import_dist.expectTypeOf)(run(
|
|
81
|
+
(0, import_dist.expectTypeOf)(run(make$1(getWeather, getCoords), [])).toEqualTypeOf();
|
|
82
82
|
});
|
|
83
83
|
it("runs each tool with its own service injected", async () => {
|
|
84
84
|
const layer = Layer.mergeAll(Layer.succeed(WeatherApiKey, { key: "weather-123" }), Layer.succeed(GeoApiKey, { key: "geo-456" }));
|
|
85
|
-
const program = run(
|
|
85
|
+
const program = run(make$1(getWeather, getCoords), [call("get_weather", "c1"), call("get_coords", "c2")]).pipe(Stream.runCollect, Effect.provide(layer));
|
|
86
86
|
const events = await Effect.runPromise(program);
|
|
87
87
|
const outputs = Array.from(events).filter(isOutput);
|
|
88
88
|
const byCall = new Map(outputs.map((e) => [e.result.call_id, e.result]));
|
|
@@ -97,6 +97,31 @@ describe("Toolkit.run - tools with R requirements", () => {
|
|
|
97
97
|
key: "geo-456"
|
|
98
98
|
});
|
|
99
99
|
});
|
|
100
|
+
it("reports non-local kinds as non_local_tool while local calls in the same turn still run", async () => {
|
|
101
|
+
const escalate = signal({
|
|
102
|
+
name: "escalate",
|
|
103
|
+
description: "",
|
|
104
|
+
inputSchema: fromEffectSchema(Empty)
|
|
105
|
+
});
|
|
106
|
+
const localPlain = make({
|
|
107
|
+
name: "plain",
|
|
108
|
+
description: "",
|
|
109
|
+
inputSchema: fromEffectSchema(Empty),
|
|
110
|
+
run: () => Effect.succeed({ ok: true })
|
|
111
|
+
});
|
|
112
|
+
const events = await Effect.runPromise(run(make$1(localPlain, escalate), [
|
|
113
|
+
call("escalate", "s1"),
|
|
114
|
+
call("plain", "p1"),
|
|
115
|
+
call("ghost", "g1")
|
|
116
|
+
]).pipe(Stream.runCollect));
|
|
117
|
+
const byCall = new Map(Array.from(events).filter(isOutput).map((e) => [e.result.call_id, e.result]));
|
|
118
|
+
const s = byCall.get("s1");
|
|
119
|
+
globalExpect(s?._tag === "Failure" && s.kind).toBe("non_local_tool");
|
|
120
|
+
const g = byCall.get("g1");
|
|
121
|
+
globalExpect(g?._tag === "Failure" && g.kind).toBe("unknown_tool");
|
|
122
|
+
const p = byCall.get("p1");
|
|
123
|
+
globalExpect(p !== void 0 && isOk(p) && p.value).toEqual({ ok: true });
|
|
124
|
+
});
|
|
100
125
|
it("with no service-needing tools, R is never", () => {
|
|
101
126
|
const plain = make({
|
|
102
127
|
name: "plain",
|
|
@@ -104,7 +129,88 @@ describe("Toolkit.run - tools with R requirements", () => {
|
|
|
104
129
|
inputSchema: fromEffectSchema(Empty),
|
|
105
130
|
run: () => Effect.succeed(0)
|
|
106
131
|
});
|
|
107
|
-
(0, import_dist.expectTypeOf)(run(
|
|
132
|
+
(0, import_dist.expectTypeOf)(run(make$1(plain), [])).toEqualTypeOf();
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
describe("Toolkit.make / compose / namespace - uniqueness", () => {
|
|
136
|
+
const Empty = Schema.Struct({});
|
|
137
|
+
const tool = (name) => make({
|
|
138
|
+
name,
|
|
139
|
+
description: "",
|
|
140
|
+
inputSchema: fromEffectSchema(Empty),
|
|
141
|
+
run: () => Effect.succeed(0)
|
|
142
|
+
});
|
|
143
|
+
it("rejects a duplicate literal name at compile time", () => {
|
|
144
|
+
make$1(tool("search"), provider({
|
|
145
|
+
name: "search",
|
|
146
|
+
description: "",
|
|
147
|
+
inputSchema: fromEffectSchema(Empty),
|
|
148
|
+
provider: "demo",
|
|
149
|
+
config: {}
|
|
150
|
+
}));
|
|
151
|
+
});
|
|
152
|
+
it("fails compose with DuplicateToolName naming the colliding sources (dynamic)", async () => {
|
|
153
|
+
const github = fromArray([tool("search")]);
|
|
154
|
+
const linear = fromArray([tool("search")]);
|
|
155
|
+
const exit = await Effect.runPromiseExit(compose(github, linear));
|
|
156
|
+
globalExpect(exit._tag).toBe("Failure");
|
|
157
|
+
if (exit._tag === "Failure") {
|
|
158
|
+
const json = JSON.stringify(exit.cause);
|
|
159
|
+
globalExpect(json).toContain("DuplicateToolName");
|
|
160
|
+
globalExpect(json).toContain("toolkit-0");
|
|
161
|
+
globalExpect(json).toContain("toolkit-1");
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
it("namespaces names so generic names from two sources compose cleanly", async () => {
|
|
165
|
+
const github = namespace("github", fromArray([tool("search")]));
|
|
166
|
+
const linear = namespace("linear", fromArray([tool("search")]));
|
|
167
|
+
const composed = await Effect.runPromise(compose(github, linear));
|
|
168
|
+
globalExpect(Object.keys(composed).sort()).toEqual(["github__search", "linear__search"]);
|
|
169
|
+
});
|
|
170
|
+
it("preserves each tool's R through compose", () => {
|
|
171
|
+
(0, import_dist.expectTypeOf)(Effect.gen(function* () {
|
|
172
|
+
return run(yield* compose(make$1(tool("a")), make$1(tool("b"))), []);
|
|
173
|
+
})).toEqualTypeOf();
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
describe("Toolkit.wrap - middleware", () => {
|
|
177
|
+
const Empty = Schema.Struct({});
|
|
178
|
+
class Audit extends Context.Service()("test/Audit") {}
|
|
179
|
+
const getWeather = make({
|
|
180
|
+
name: "get_weather",
|
|
181
|
+
description: "",
|
|
182
|
+
inputSchema: fromEffectSchema(Empty),
|
|
183
|
+
run: () => Effect.succeed({ tempC: 18 })
|
|
184
|
+
});
|
|
185
|
+
const escalate = signal({
|
|
186
|
+
name: "escalate",
|
|
187
|
+
description: "",
|
|
188
|
+
inputSchema: fromEffectSchema(Empty)
|
|
189
|
+
});
|
|
190
|
+
const audited = (run, name) => (input, emit) => Effect.gen(function* () {
|
|
191
|
+
yield* (yield* Audit).record(name);
|
|
192
|
+
return yield* run(input, emit);
|
|
193
|
+
});
|
|
194
|
+
const call = (name, id) => ({
|
|
195
|
+
type: "function_call",
|
|
196
|
+
call_id: id,
|
|
197
|
+
name,
|
|
198
|
+
arguments: "{}"
|
|
199
|
+
});
|
|
200
|
+
it("unions the middleware's R2 into the toolkit's requirements", () => {
|
|
201
|
+
const wrapped = wrap(make$1(getWeather), audited);
|
|
202
|
+
(0, import_dist.expectTypeOf)(run(wrapped, [])).toEqualTypeOf();
|
|
203
|
+
});
|
|
204
|
+
it("runs the wrapped handler and leaves non-local kinds untouched", async () => {
|
|
205
|
+
const recorded = [];
|
|
206
|
+
const wrapped = pipe(make$1(getWeather, escalate), wrap(audited));
|
|
207
|
+
const events = await Effect.runPromise(run(wrapped, [call("get_weather", "w1"), call("escalate", "s1")]).pipe(Stream.runCollect, Effect.provideService(Audit, { record: (n) => Effect.sync(() => void recorded.push(n)) })));
|
|
208
|
+
const byCall = new Map(Array.from(events).filter(isOutput).map((e) => [e.result.call_id, e.result]));
|
|
209
|
+
globalExpect(recorded).toEqual(["get_weather"]);
|
|
210
|
+
const w = byCall.get("w1");
|
|
211
|
+
globalExpect(w !== void 0 && isOk(w) && w.value).toEqual({ tempC: 18 });
|
|
212
|
+
const s = byCall.get("s1");
|
|
213
|
+
globalExpect(s?._tag === "Failure" && s.kind).toBe("non_local_tool");
|
|
108
214
|
});
|
|
109
215
|
});
|
|
110
216
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Toolkit.test.mjs","names":["Tool.make","Tool.fromEffectSchema","Tool.toDescriptors","Toolkit.run"],"sources":["../../src/tool/Toolkit.test.ts"],"sourcesContent":["import { Context, Effect, Layer, Schema, Stream } from \"effect\"\nimport { describe, expect, expectTypeOf, it } from \"vitest\"\nimport type { ToolCall } from \"../domain/Items.js\"\nimport { isOutput } from \"./ToolEvent.js\"\nimport { isOk } from \"./ToolResult.js\"\nimport * as Tool from \"./Tool.js\"\nimport * as Toolkit from \"./Toolkit.js\"\n\ndescribe(\"Tool.toDescriptors\", () => {\n const GetWeatherInput = Schema.Struct({ city: Schema.String })\n\n const getWeather = Tool.make({\n name: \"get_weather\",\n description: \"Look up the current temperature for a city.\",\n inputSchema: Tool.fromEffectSchema(GetWeatherInput),\n run: ({ city }) => Effect.succeed({ city, tempC: 18 }),\n })\n\n it(\"renders the input schema as a JSON Schema document\", () => {\n const [desc] = Tool.toDescriptors([getWeather])\n expect(desc?.name).toBe(\"get_weather\")\n expect(desc?.description).toBe(\"Look up the current temperature for a city.\")\n expect(desc?.inputSchema).toMatchObject({\n type: \"object\",\n properties: { city: { type: \"string\" } },\n required: [\"city\"],\n })\n })\n\n it(\"includes strict flag only when set on the tool\", () => {\n const strictTool = Tool.make({\n name: \"strict_one\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(GetWeatherInput),\n run: () => Effect.succeed({}),\n strict: true,\n })\n const looseTool = Tool.make({\n name: \"loose_one\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(GetWeatherInput),\n run: () => Effect.succeed({}),\n })\n const [s, l] = Tool.toDescriptors([strictTool, looseTool])\n expect(s?.strict).toBe(true)\n expect(l).not.toHaveProperty(\"strict\")\n })\n})\n\ndescribe(\"Toolkit.run - tools with R requirements\", () => {\n // Two distinct services, modelling the \"typed per-tool context\" use case\n // (cf. AI SDK 7's `toolsContext`). In Effect each tool declares its R, the\n // compiler enforces it, and `run` surfaces the union for the caller\n // to provide via Layer.\n type WeatherApiKeyShape = { readonly key: string }\n class WeatherApiKey extends Context.Service<WeatherApiKey, WeatherApiKeyShape>()(\n \"test/WeatherApiKey\",\n ) {}\n\n type GeoApiKeyShape = { readonly key: string }\n class GeoApiKey extends Context.Service<GeoApiKey, GeoApiKeyShape>()(\"test/GeoApiKey\") {}\n\n const Empty = Schema.Struct({})\n\n const getWeather = Tool.make({\n name: \"get_weather\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () =>\n Effect.gen(function* () {\n const { key } = yield* WeatherApiKey\n return { source: \"weather\", key }\n }),\n })\n\n const getCoords = Tool.make({\n name: \"get_coords\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () =>\n Effect.gen(function* () {\n const { key } = yield* GeoApiKey\n return { source: \"geo\", key }\n }),\n })\n\n const call = (name: string, id: string): ToolCall => ({\n type: \"function_call\",\n call_id: id,\n name,\n arguments: \"{}\",\n })\n\n it(\"propagates each tool's R into the resulting Stream's requirements\", () => {\n const stream = Toolkit.run([getWeather, getCoords], [])\n expectTypeOf(stream).toEqualTypeOf<\n Stream.Stream<import(\"./ToolEvent.js\").ToolEvent, never, WeatherApiKey | GeoApiKey>\n >()\n })\n\n it(\"runs each tool with its own service injected\", async () => {\n const layer = Layer.mergeAll(\n Layer.succeed(WeatherApiKey, { key: \"weather-123\" }),\n Layer.succeed(GeoApiKey, { key: \"geo-456\" }),\n )\n\n const program = Toolkit.run(\n [getWeather, getCoords],\n [call(\"get_weather\", \"c1\"), call(\"get_coords\", \"c2\")],\n ).pipe(Stream.runCollect, Effect.provide(layer))\n\n const events = await Effect.runPromise(program)\n const outputs = Array.from(events).filter(isOutput)\n const byCall = new Map(outputs.map((e) => [e.result.call_id, e.result]))\n\n const w = byCall.get(\"c1\")\n const g = byCall.get(\"c2\")\n expect(w !== undefined && isOk(w) && w.value).toEqual({\n source: \"weather\",\n key: \"weather-123\",\n })\n expect(g !== undefined && isOk(g) && g.value).toEqual({\n source: \"geo\",\n key: \"geo-456\",\n })\n })\n\n it(\"with no service-needing tools, R is never\", () => {\n const plain = Tool.make({\n name: \"plain\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () => Effect.succeed(0),\n })\n const stream = Toolkit.run([plain], [])\n expectTypeOf(stream).toEqualTypeOf<\n Stream.Stream<import(\"./ToolEvent.js\").ToolEvent, never, never>\n >()\n })\n})\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Toolkit.test.mjs","names":["Tool.make","Tool.fromEffectSchema","Tool.toDescriptors","Toolkit.run","Toolkit.make","Tool.signal","Tool.provider","Toolkit.fromArray","Toolkit.compose","Toolkit.namespace","Toolkit.wrap"],"sources":["../../src/tool/Toolkit.test.ts"],"sourcesContent":["import { Context, Effect, Layer, pipe, Schema, Stream } from \"effect\"\nimport { describe, expect, expectTypeOf, it } from \"vitest\"\nimport type { ToolCall } from \"../domain/Items.js\"\nimport { isOutput } from \"./ToolEvent.js\"\nimport { isOk } from \"./ToolResult.js\"\nimport * as Tool from \"./Tool.js\"\nimport * as Toolkit from \"./Toolkit.js\"\n\ndescribe(\"Tool.toDescriptors\", () => {\n const GetWeatherInput = Schema.Struct({ city: Schema.String })\n\n const getWeather = Tool.make({\n name: \"get_weather\",\n description: \"Look up the current temperature for a city.\",\n inputSchema: Tool.fromEffectSchema(GetWeatherInput),\n run: ({ city }) => Effect.succeed({ city, tempC: 18 }),\n })\n\n it(\"renders the input schema as a JSON Schema document\", () => {\n const [desc] = Tool.toDescriptors([getWeather])\n expect(desc?.name).toBe(\"get_weather\")\n expect(desc?.description).toBe(\"Look up the current temperature for a city.\")\n expect(desc?.inputSchema).toMatchObject({\n type: \"object\",\n properties: { city: { type: \"string\" } },\n required: [\"city\"],\n })\n })\n\n it(\"includes strict flag only when set on the tool\", () => {\n const strictTool = Tool.make({\n name: \"strict_one\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(GetWeatherInput),\n run: () => Effect.succeed({}),\n strict: true,\n })\n const looseTool = Tool.make({\n name: \"loose_one\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(GetWeatherInput),\n run: () => Effect.succeed({}),\n })\n const [s, l] = Tool.toDescriptors([strictTool, looseTool])\n expect(s?.strict).toBe(true)\n expect(l).not.toHaveProperty(\"strict\")\n })\n})\n\ndescribe(\"Toolkit.run - tools with R requirements\", () => {\n // Two distinct services, modelling the \"typed per-tool context\" use case\n // (cf. AI SDK 7's `toolsContext`). In Effect each tool declares its R, the\n // compiler enforces it, and `run` surfaces the union for the caller\n // to provide via Layer.\n type WeatherApiKeyShape = { readonly key: string }\n class WeatherApiKey extends Context.Service<WeatherApiKey, WeatherApiKeyShape>()(\n \"test/WeatherApiKey\",\n ) {}\n\n type GeoApiKeyShape = { readonly key: string }\n class GeoApiKey extends Context.Service<GeoApiKey, GeoApiKeyShape>()(\"test/GeoApiKey\") {}\n\n const Empty = Schema.Struct({})\n\n const getWeather = Tool.make({\n name: \"get_weather\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () =>\n Effect.gen(function* () {\n const { key } = yield* WeatherApiKey\n return { source: \"weather\", key }\n }),\n })\n\n const getCoords = Tool.make({\n name: \"get_coords\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () =>\n Effect.gen(function* () {\n const { key } = yield* GeoApiKey\n return { source: \"geo\", key }\n }),\n })\n\n const call = (name: string, id: string): ToolCall => ({\n type: \"function_call\",\n call_id: id,\n name,\n arguments: \"{}\",\n })\n\n it(\"propagates each tool's R into the resulting Stream's requirements\", () => {\n const stream = Toolkit.run(Toolkit.make(getWeather, getCoords), [])\n expectTypeOf(stream).toEqualTypeOf<\n Stream.Stream<import(\"./ToolEvent.js\").ToolEvent, never, WeatherApiKey | GeoApiKey>\n >()\n })\n\n it(\"runs each tool with its own service injected\", async () => {\n const layer = Layer.mergeAll(\n Layer.succeed(WeatherApiKey, { key: \"weather-123\" }),\n Layer.succeed(GeoApiKey, { key: \"geo-456\" }),\n )\n\n const program = Toolkit.run(Toolkit.make(getWeather, getCoords), [\n call(\"get_weather\", \"c1\"),\n call(\"get_coords\", \"c2\"),\n ]).pipe(Stream.runCollect, Effect.provide(layer))\n\n const events = await Effect.runPromise(program)\n const outputs = Array.from(events).filter(isOutput)\n const byCall = new Map(outputs.map((e) => [e.result.call_id, e.result]))\n\n const w = byCall.get(\"c1\")\n const g = byCall.get(\"c2\")\n expect(w !== undefined && isOk(w) && w.value).toEqual({\n source: \"weather\",\n key: \"weather-123\",\n })\n expect(g !== undefined && isOk(g) && g.value).toEqual({\n source: \"geo\",\n key: \"geo-456\",\n })\n })\n\n it(\"reports non-local kinds as non_local_tool while local calls in the same turn still run\", async () => {\n const escalate = Tool.signal({\n name: \"escalate\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n })\n const localPlain = Tool.make({\n name: \"plain\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () => Effect.succeed({ ok: true }),\n })\n\n const events = await Effect.runPromise(\n Toolkit.run(Toolkit.make(localPlain, escalate), [\n call(\"escalate\", \"s1\"),\n call(\"plain\", \"p1\"),\n call(\"ghost\", \"g1\"),\n ]).pipe(Stream.runCollect),\n )\n const byCall = new Map(\n Array.from(events)\n .filter(isOutput)\n .map((e) => [e.result.call_id, e.result] as const),\n )\n\n // Signal tool: model-visible but no local executor.\n const s = byCall.get(\"s1\")\n expect(s?._tag === \"Failure\" && s.kind).toBe(\"non_local_tool\")\n // Unknown tool stays distinct from non-local.\n const g = byCall.get(\"g1\")\n expect(g?._tag === \"Failure\" && g.kind).toBe(\"unknown_tool\")\n // The local call still executed despite the non-local sibling.\n const p = byCall.get(\"p1\")\n expect(p !== undefined && isOk(p) && p.value).toEqual({ ok: true })\n })\n\n it(\"with no service-needing tools, R is never\", () => {\n const plain = Tool.make({\n name: \"plain\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () => Effect.succeed(0),\n })\n const stream = Toolkit.run(Toolkit.make(plain), [])\n expectTypeOf(stream).toEqualTypeOf<\n Stream.Stream<import(\"./ToolEvent.js\").ToolEvent, never, never>\n >()\n })\n})\n\ndescribe(\"Toolkit.make / compose / namespace - uniqueness\", () => {\n const Empty = Schema.Struct({})\n const tool = (name: string) =>\n Tool.make({\n name,\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () => Effect.succeed(0),\n })\n\n it(\"rejects a duplicate literal name at compile time\", () => {\n const a = tool(\"search\")\n const b = Tool.provider({\n name: \"search\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n provider: \"demo\",\n config: {},\n })\n // @ts-expect-error Duplicate tool name: search\n Toolkit.make(a, b)\n })\n\n it(\"fails compose with DuplicateToolName naming the colliding sources (dynamic)\", async () => {\n const github = Toolkit.fromArray([tool(\"search\")])\n const linear = Toolkit.fromArray([tool(\"search\")])\n const exit = await Effect.runPromiseExit(Toolkit.compose(github, linear))\n expect(exit._tag).toBe(\"Failure\")\n if (exit._tag === \"Failure\") {\n const json = JSON.stringify(exit.cause)\n expect(json).toContain(\"DuplicateToolName\")\n expect(json).toContain(\"toolkit-0\")\n expect(json).toContain(\"toolkit-1\")\n }\n })\n\n it(\"namespaces names so generic names from two sources compose cleanly\", async () => {\n const github = Toolkit.namespace(\"github\", Toolkit.fromArray([tool(\"search\")]))\n const linear = Toolkit.namespace(\"linear\", Toolkit.fromArray([tool(\"search\")]))\n const composed = await Effect.runPromise(Toolkit.compose(github, linear))\n expect(Object.keys(composed).sort()).toEqual([\"github__search\", \"linear__search\"])\n })\n\n it(\"preserves each tool's R through compose\", () => {\n const stream = Effect.gen(function* () {\n const composed = yield* Toolkit.compose(Toolkit.make(tool(\"a\")), Toolkit.make(tool(\"b\")))\n return Toolkit.run(composed, [])\n })\n // No service-needing tools here -> R stays never (compose didn't poison it).\n expectTypeOf(stream).toEqualTypeOf<\n Effect.Effect<\n Stream.Stream<import(\"./ToolEvent.js\").ToolEvent, never, never>,\n Toolkit.DuplicateToolName\n >\n >()\n })\n})\n\ndescribe(\"Toolkit.wrap - middleware\", () => {\n const Empty = Schema.Struct({})\n type AuditShape = { readonly record: (name: string) => Effect.Effect<void> }\n class Audit extends Context.Service<Audit, AuditShape>()(\"test/Audit\") {}\n\n const getWeather = Tool.make({\n name: \"get_weather\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n run: () => Effect.succeed({ tempC: 18 }),\n })\n const escalate = Tool.signal({\n name: \"escalate\",\n description: \"\",\n inputSchema: Tool.fromEffectSchema(Empty),\n })\n\n // A middleware that needs the Audit service -> R2 = Audit.\n const audited: Toolkit.Middleware<Audit> = (run, name) => (input, emit) =>\n Effect.gen(function* () {\n const a = yield* Audit\n yield* a.record(name)\n return yield* run(input, emit)\n })\n\n const call = (name: string, id: string): ToolCall => ({\n type: \"function_call\",\n call_id: id,\n name,\n arguments: \"{}\",\n })\n\n it(\"unions the middleware's R2 into the toolkit's requirements\", () => {\n const wrapped = Toolkit.wrap(Toolkit.make(getWeather), audited)\n const stream = Toolkit.run(wrapped, [])\n expectTypeOf(stream).toEqualTypeOf<\n Stream.Stream<import(\"./ToolEvent.js\").ToolEvent, never, Audit>\n >()\n })\n\n it(\"runs the wrapped handler and leaves non-local kinds untouched\", async () => {\n const recorded: Array<string> = []\n const wrapped = pipe(Toolkit.make(getWeather, escalate), Toolkit.wrap(audited))\n\n const events = await Effect.runPromise(\n Toolkit.run(wrapped, [call(\"get_weather\", \"w1\"), call(\"escalate\", \"s1\")]).pipe(\n Stream.runCollect,\n Effect.provideService(Audit, { record: (n) => Effect.sync(() => void recorded.push(n)) }),\n ),\n )\n const byCall = new Map(\n Array.from(events)\n .filter(isOutput)\n .map((e) => [e.result.call_id, e.result] as const),\n )\n\n // Local tool ran through the middleware (audit fired).\n expect(recorded).toEqual([\"get_weather\"])\n const w = byCall.get(\"w1\")\n expect(w !== undefined && isOk(w) && w.value).toEqual({ tempC: 18 })\n // Signal kind is left alone by wrap -> still reported non-local.\n const s = byCall.get(\"s1\")\n expect(s?._tag === \"Failure\" && s.kind).toBe(\"non_local_tool\")\n })\n})\n"],"mappings":"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"}
|