@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
package/src/tool/Tool.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { StandardJSONSchemaV1, StandardSchemaV1 } from "@standard-schema/spec"
|
|
2
|
-
import { Effect, Schema } from "effect"
|
|
2
|
+
import { Effect, Match, Schema } from "effect"
|
|
3
3
|
import type { ToolCall, ToolCallOutput } from "../domain/Items.js"
|
|
4
4
|
import { toolCallOutput } from "../domain/Items.js"
|
|
5
|
+
import { serializeValue } from "./ToolResult.js"
|
|
5
6
|
|
|
6
7
|
export class ToolError extends Schema.TaggedErrorClass<ToolError>("@betalyra/effect-uai/ToolError")(
|
|
7
8
|
"ToolError",
|
|
@@ -37,6 +38,35 @@ export class ToolValidationError extends Schema.TaggedErrorClass<ToolValidationE
|
|
|
37
38
|
issues: Schema.Array(ToolValidationIssue),
|
|
38
39
|
}) {}
|
|
39
40
|
|
|
41
|
+
/**
|
|
42
|
+
* The deliberate "speak to the model" failure sentinel. A tool's `run` that
|
|
43
|
+
* fails with a `ToolFailed` (or a bare `string`, its sugar) is absorbed by
|
|
44
|
+
* `Toolkit.run` into a `ToolResult.Failure` the model reads and adapts to,
|
|
45
|
+
* instead of ending the run. Every other error type propagates typed on the
|
|
46
|
+
* executor's stream. `kind` overrides the synthesized `Failure.kind`
|
|
47
|
+
* (default `"tool_failed"`) so a tool can hand the model a distinguishable
|
|
48
|
+
* category (`"not_found"`, `"rate_limited"`) without a new result variant.
|
|
49
|
+
*/
|
|
50
|
+
export class ToolFailed extends Schema.TaggedErrorClass<ToolFailed>(
|
|
51
|
+
"@betalyra/effect-uai/ToolFailed",
|
|
52
|
+
)("ToolFailed", {
|
|
53
|
+
message: Schema.String,
|
|
54
|
+
kind: Schema.optional(Schema.String),
|
|
55
|
+
}) {}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Fail a tool's `run` with a model-visible message (optionally a `kind`).
|
|
59
|
+
* `yield* Tool.fail("page not found", { kind: "not_found" })` inside a
|
|
60
|
+
* `run`'s `Effect.gen`; the executor absorbs it as `ToolResult.Failure`.
|
|
61
|
+
*/
|
|
62
|
+
export const fail = (
|
|
63
|
+
message: string,
|
|
64
|
+
options?: { readonly kind?: string },
|
|
65
|
+
): Effect.Effect<never, ToolFailed> =>
|
|
66
|
+
Effect.fail(
|
|
67
|
+
new ToolFailed({ message, ...(options?.kind !== undefined ? { kind: options.kind } : {}) }),
|
|
68
|
+
)
|
|
69
|
+
|
|
40
70
|
/**
|
|
41
71
|
* Schemas accepted on `Tool.inputSchema`. Must implement both Standard
|
|
42
72
|
* Schema validation and JSON Schema conversion (for rendering tool
|
|
@@ -79,6 +109,25 @@ export const fromStandardSchema = <S extends StandardSchemaV1 & StandardJSONSche
|
|
|
79
109
|
): S & ToolInputSchema<StandardSchemaV1.InferOutput<S>> =>
|
|
80
110
|
schema as S & ToolInputSchema<StandardSchemaV1.InferOutput<S>>
|
|
81
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Input schema for a tool that takes no arguments. Renders a clean
|
|
114
|
+
* `{ type: "object", properties: {} }`; validation accepts anything and
|
|
115
|
+
* yields `{}`. Use it instead of `fromEffectSchema(Schema.Struct({}))`,
|
|
116
|
+
* which renders a degenerate `anyOf` (object-or-array) that strict
|
|
117
|
+
* providers like Gemini reject.
|
|
118
|
+
*/
|
|
119
|
+
export const noInput: ToolInputSchema<Record<string, never>> = {
|
|
120
|
+
"~standard": {
|
|
121
|
+
version: 1,
|
|
122
|
+
vendor: "effect-uai",
|
|
123
|
+
validate: () => ({ value: {} }),
|
|
124
|
+
jsonSchema: {
|
|
125
|
+
input: () => ({ type: "object", properties: {} }),
|
|
126
|
+
output: () => ({ type: "object", properties: {} }),
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
}
|
|
130
|
+
|
|
82
131
|
/**
|
|
83
132
|
* Emit one intermediate `Event` from inside a tool's `run`. Backed by the
|
|
84
133
|
* executor's per-call queue, so it returns an `Effect` (backpressure) and
|
|
@@ -98,12 +147,12 @@ export type Emit<Event> = (event: Event) => Effect.Effect<void>
|
|
|
98
147
|
* no local executor — see their constructors below. `Tool` keeps its name (and
|
|
99
148
|
* the `Tool.make` constructor) because the local kind is by far the common one.
|
|
100
149
|
*/
|
|
101
|
-
export type Tool<Name extends string, Input, Event, Output, R = never> = {
|
|
150
|
+
export type Tool<Name extends string, Input, Event, Output, E = unknown, R = never> = {
|
|
102
151
|
readonly _tag: "LocalTool"
|
|
103
152
|
readonly name: Name
|
|
104
153
|
readonly description: string
|
|
105
154
|
readonly inputSchema: ToolInputSchema<Input>
|
|
106
|
-
readonly run: (input: Input, emit: Emit<Event>) => Effect.Effect<Output,
|
|
155
|
+
readonly run: (input: Input, emit: Emit<Event>) => Effect.Effect<Output, E, R>
|
|
107
156
|
/**
|
|
108
157
|
* Bound on this tool's emit queue. Unbounded when omitted. Per-tool because
|
|
109
158
|
* backpressure depends on how a given tool emits, unlike `concurrency`
|
|
@@ -162,11 +211,11 @@ export type InteractionTool<Name extends string, Input> = {
|
|
|
162
211
|
}
|
|
163
212
|
|
|
164
213
|
/** A local (executable) tool of any shape. Readability alias. */
|
|
165
|
-
export type AnyLocalTool<R = any> = Tool<string, any, any, any, R>
|
|
214
|
+
export type AnyLocalTool<E = any, R = any> = Tool<string, any, any, any, E, R>
|
|
166
215
|
|
|
167
216
|
/** Any of the four model-visible tool kinds. */
|
|
168
|
-
export type AnyTool<R = any> =
|
|
169
|
-
| Tool<string, any, any, any, R>
|
|
217
|
+
export type AnyTool<E = any, R = any> =
|
|
218
|
+
| Tool<string, any, any, any, E, R>
|
|
170
219
|
| ProviderTool<string, any, string, any>
|
|
171
220
|
| SignalTool<string, any>
|
|
172
221
|
| InteractionTool<string, any>
|
|
@@ -178,7 +227,10 @@ export type DecodableTool<Input> = {
|
|
|
178
227
|
}
|
|
179
228
|
|
|
180
229
|
/** Extract the `R` of a local tool; non-local kinds contribute `never`. */
|
|
181
|
-
export type ToolR<T> = T extends Tool<string, any, any, any, infer R> ? R : never
|
|
230
|
+
export type ToolR<T> = T extends Tool<string, any, any, any, any, infer R> ? R : never
|
|
231
|
+
|
|
232
|
+
/** Extract the `E` of a local tool; non-local kinds contribute `never`. */
|
|
233
|
+
export type ToolE<T> = T extends Tool<string, any, any, any, infer E, any> ? E : never
|
|
182
234
|
|
|
183
235
|
/**
|
|
184
236
|
* Provider-agnostic tool descriptor. Each provider maps `inputSchema`
|
|
@@ -192,37 +244,114 @@ export type ToolDescriptor = {
|
|
|
192
244
|
readonly strict?: boolean
|
|
193
245
|
}
|
|
194
246
|
|
|
247
|
+
const resolveInputSchema = (
|
|
248
|
+
schema: ToolInputSchema<any> | Schema.Codec<any, any, never, any>,
|
|
249
|
+
): ToolInputSchema<any> =>
|
|
250
|
+
Schema.isSchema(schema)
|
|
251
|
+
? fromEffectSchema(schema as Schema.Codec<any, any, never, any>)
|
|
252
|
+
: (schema as ToolInputSchema<any>)
|
|
253
|
+
|
|
195
254
|
/** A model-visible tool with a local `Effect` executor. */
|
|
196
|
-
export const make
|
|
197
|
-
|
|
198
|
-
|
|
255
|
+
export const make: {
|
|
256
|
+
<
|
|
257
|
+
Name extends string,
|
|
258
|
+
S extends Schema.Codec<any, any, never, any>,
|
|
259
|
+
Event,
|
|
260
|
+
Output,
|
|
261
|
+
E = never,
|
|
262
|
+
R = never,
|
|
263
|
+
>(
|
|
264
|
+
spec: Omit<Tool<Name, S["Type"], Event, Output, E, R>, "_tag" | "inputSchema"> & {
|
|
265
|
+
readonly inputSchema: S
|
|
266
|
+
},
|
|
267
|
+
): Tool<Name, S["Type"], Event, Output, E, R>
|
|
268
|
+
<Name extends string, Input, Event, Output, E = never, R = never>(
|
|
269
|
+
spec: Omit<Tool<Name, Input, Event, Output, E, R>, "_tag">,
|
|
270
|
+
): Tool<Name, Input, Event, Output, E, R>
|
|
271
|
+
} = (
|
|
272
|
+
spec: Omit<AnyLocalTool, "_tag" | "inputSchema"> & {
|
|
273
|
+
readonly inputSchema: ToolInputSchema<any> | Schema.Codec<any, any, never, any>
|
|
274
|
+
},
|
|
275
|
+
): AnyLocalTool => ({
|
|
276
|
+
_tag: "LocalTool",
|
|
277
|
+
...spec,
|
|
278
|
+
inputSchema: resolveInputSchema(spec.inputSchema),
|
|
279
|
+
})
|
|
199
280
|
|
|
200
281
|
/**
|
|
201
282
|
* A provider-hosted tool (native web search, code execution, RAG grounding).
|
|
202
283
|
* No local executor — `provider`/`config` drive the provider adapter's
|
|
203
284
|
* rendering. The model sees it; `Toolkit.run` reports it as `non_local_tool`.
|
|
204
285
|
*/
|
|
205
|
-
export const provider
|
|
206
|
-
|
|
207
|
-
|
|
286
|
+
export const provider: {
|
|
287
|
+
<
|
|
288
|
+
Name extends string,
|
|
289
|
+
S extends Schema.Codec<any, any, never, any>,
|
|
290
|
+
Provider extends string,
|
|
291
|
+
Config,
|
|
292
|
+
>(
|
|
293
|
+
spec: Omit<ProviderTool<Name, S["Type"], Provider, Config>, "_tag" | "inputSchema"> & {
|
|
294
|
+
readonly inputSchema: S
|
|
295
|
+
},
|
|
296
|
+
): ProviderTool<Name, S["Type"], Provider, Config>
|
|
297
|
+
<Name extends string, Input, Provider extends string, Config>(
|
|
298
|
+
spec: Omit<ProviderTool<Name, Input, Provider, Config>, "_tag">,
|
|
299
|
+
): ProviderTool<Name, Input, Provider, Config>
|
|
300
|
+
} = (
|
|
301
|
+
spec: Omit<ProviderTool<string, any, string, any>, "_tag" | "inputSchema"> & {
|
|
302
|
+
readonly inputSchema: ToolInputSchema<any> | Schema.Codec<any, any, never, any>
|
|
303
|
+
},
|
|
304
|
+
): ProviderTool<string, any, string, any> => ({
|
|
305
|
+
_tag: "ProviderTool",
|
|
306
|
+
...spec,
|
|
307
|
+
inputSchema: resolveInputSchema(spec.inputSchema),
|
|
308
|
+
})
|
|
208
309
|
|
|
209
310
|
/**
|
|
210
311
|
* A typed control-flow signal to the agent loop (escalate, pause, schedule,
|
|
211
312
|
* hand off). Decode-only: the loop interprets the call rather than executing a
|
|
212
313
|
* handler, so there is no `run`.
|
|
213
314
|
*/
|
|
214
|
-
export const signal
|
|
215
|
-
|
|
216
|
-
|
|
315
|
+
export const signal: {
|
|
316
|
+
<Name extends string, S extends Schema.Codec<any, any, never, any>>(
|
|
317
|
+
spec: Omit<SignalTool<Name, S["Type"]>, "_tag" | "inputSchema"> & {
|
|
318
|
+
readonly inputSchema: S
|
|
319
|
+
},
|
|
320
|
+
): SignalTool<Name, S["Type"]>
|
|
321
|
+
<Name extends string, Input>(spec: Omit<SignalTool<Name, Input>, "_tag">): SignalTool<Name, Input>
|
|
322
|
+
} = (
|
|
323
|
+
spec: Omit<SignalTool<string, any>, "_tag" | "inputSchema"> & {
|
|
324
|
+
readonly inputSchema: ToolInputSchema<any> | Schema.Codec<any, any, never, any>
|
|
325
|
+
},
|
|
326
|
+
): SignalTool<string, any> => ({
|
|
327
|
+
_tag: "SignalTool",
|
|
328
|
+
...spec,
|
|
329
|
+
inputSchema: resolveInputSchema(spec.inputSchema),
|
|
330
|
+
})
|
|
217
331
|
|
|
218
332
|
/**
|
|
219
333
|
* A typed request for an external actor to respond before the loop resumes
|
|
220
334
|
* (frontend, Telegram, CLI prompt). Decode-only; the loop stops/pauses and
|
|
221
335
|
* resumes by appending the actor's `function_call_output`.
|
|
222
336
|
*/
|
|
223
|
-
export const interaction
|
|
224
|
-
|
|
225
|
-
|
|
337
|
+
export const interaction: {
|
|
338
|
+
<Name extends string, S extends Schema.Codec<any, any, never, any>>(
|
|
339
|
+
spec: Omit<InteractionTool<Name, S["Type"]>, "_tag" | "inputSchema"> & {
|
|
340
|
+
readonly inputSchema: S
|
|
341
|
+
},
|
|
342
|
+
): InteractionTool<Name, S["Type"]>
|
|
343
|
+
<Name extends string, Input>(
|
|
344
|
+
spec: Omit<InteractionTool<Name, Input>, "_tag">,
|
|
345
|
+
): InteractionTool<Name, Input>
|
|
346
|
+
} = (
|
|
347
|
+
spec: Omit<InteractionTool<string, any>, "_tag" | "inputSchema"> & {
|
|
348
|
+
readonly inputSchema: ToolInputSchema<any> | Schema.Codec<any, any, never, any>
|
|
349
|
+
},
|
|
350
|
+
): InteractionTool<string, any> => ({
|
|
351
|
+
_tag: "InteractionTool",
|
|
352
|
+
...spec,
|
|
353
|
+
inputSchema: resolveInputSchema(spec.inputSchema),
|
|
354
|
+
})
|
|
226
355
|
|
|
227
356
|
/**
|
|
228
357
|
* Return a copy of a tool under a new `name`. Useful for resolving a name
|
|
@@ -245,23 +374,54 @@ export const withName = <N extends string, T extends AnyTool>(
|
|
|
245
374
|
* Descriptors stay identical to production, so a mocked toolkit can't drift
|
|
246
375
|
* from the contract the model sees.
|
|
247
376
|
*/
|
|
248
|
-
export const withRun = <
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
377
|
+
export const withRun = <
|
|
378
|
+
Name extends string,
|
|
379
|
+
Input,
|
|
380
|
+
Event,
|
|
381
|
+
Output,
|
|
382
|
+
E,
|
|
383
|
+
R,
|
|
384
|
+
Output2,
|
|
385
|
+
E2 = never,
|
|
386
|
+
R2 = never,
|
|
387
|
+
>(
|
|
388
|
+
tool: Tool<Name, Input, Event, Output, E, R>,
|
|
389
|
+
run: (input: Input, emit: Emit<Event>) => Effect.Effect<Output2, E2, R2>,
|
|
390
|
+
): Tool<Name, Input, Event, Output2, E2, R2> => ({ ...tool, run })
|
|
391
|
+
|
|
392
|
+
/** True for provider-hosted tools — the kind a provider renders natively. */
|
|
393
|
+
export const isProviderTool = (tool: AnyTool): tool is ProviderTool<string, any, string, any> =>
|
|
394
|
+
tool._tag === "ProviderTool"
|
|
252
395
|
|
|
253
|
-
/**
|
|
396
|
+
/**
|
|
397
|
+
* The `ProviderTool`s in a toolkit, for a provider adapter to render as its
|
|
398
|
+
* native hosted-tool entries (web search, code execution, grounding). The other
|
|
399
|
+
* kinds render as function declarations via `descriptorsOf`.
|
|
400
|
+
*/
|
|
401
|
+
export const providerToolsOf = (
|
|
402
|
+
toolkit?: Readonly<Record<string, AnyTool>>,
|
|
403
|
+
): ReadonlyArray<ProviderTool<string, any, string, any>> =>
|
|
404
|
+
toolkit === undefined ? [] : Object.values(toolkit).filter(isProviderTool)
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Render tools to provider-agnostic descriptors. `ProviderTool`s are excluded:
|
|
408
|
+
* they are provider-hosted and must never leak into the function-declaration
|
|
409
|
+
* list — each adapter renders them natively via `providerToolsOf`. Every other
|
|
410
|
+
* kind is model-visible as a declaration.
|
|
411
|
+
*/
|
|
254
412
|
export const toDescriptors = (tools: ReadonlyArray<AnyTool>): ReadonlyArray<ToolDescriptor> =>
|
|
255
|
-
tools
|
|
256
|
-
|
|
257
|
-
|
|
413
|
+
tools
|
|
414
|
+
.filter((tool) => !isProviderTool(tool))
|
|
415
|
+
.map((tool) => {
|
|
416
|
+
const inputSchema = tool.inputSchema["~standard"].jsonSchema.input({
|
|
417
|
+
target: "draft-2020-12",
|
|
418
|
+
})
|
|
419
|
+
// `strict` only exists on local/provider kinds; signals/interactions omit it.
|
|
420
|
+
const strict = "strict" in tool ? tool.strict : undefined
|
|
421
|
+
return strict !== undefined
|
|
422
|
+
? { name: tool.name, description: tool.description, inputSchema, strict }
|
|
423
|
+
: { name: tool.name, description: tool.description, inputSchema }
|
|
258
424
|
})
|
|
259
|
-
// `strict` only exists on local/provider kinds; signals/interactions omit it.
|
|
260
|
-
const strict = "strict" in tool ? tool.strict : undefined
|
|
261
|
-
return strict !== undefined
|
|
262
|
-
? { name: tool.name, description: tool.description, inputSchema, strict }
|
|
263
|
-
: { name: tool.name, description: tool.description, inputSchema }
|
|
264
|
-
})
|
|
265
425
|
|
|
266
426
|
/**
|
|
267
427
|
* Render a `Toolkit` (a name-indexed record of tools) to descriptors, treating
|
|
@@ -275,8 +435,51 @@ export const descriptorsOf = (
|
|
|
275
435
|
): ReadonlyArray<ToolDescriptor> =>
|
|
276
436
|
toolkit === undefined ? [] : toDescriptors(Object.values(toolkit))
|
|
277
437
|
|
|
278
|
-
|
|
279
|
-
|
|
438
|
+
/**
|
|
439
|
+
* Outcome of decoding a call's `arguments` against a tool's `inputSchema`,
|
|
440
|
+
* as data so both the typed (`decodeArgs`) and result-synthesizing
|
|
441
|
+
* (`Toolkit.run`) callers can translate it their own way.
|
|
442
|
+
*/
|
|
443
|
+
export type DecodeResult<Input> =
|
|
444
|
+
| { readonly _tag: "ok"; readonly input: Input }
|
|
445
|
+
| { readonly _tag: "parseError" }
|
|
446
|
+
| { readonly _tag: "invalid"; readonly issues: ReadonlyArray<StandardSchemaV1.Issue> }
|
|
447
|
+
|
|
448
|
+
const VALIDATION_THREW: ReadonlyArray<StandardSchemaV1.Issue> = [
|
|
449
|
+
{ message: "input schema validation threw" },
|
|
450
|
+
]
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Parse and validate a call's `arguments`. Empty arguments normalize to
|
|
454
|
+
* `{}` (some models emit `""` for zero-arg tools); a throwing validator is
|
|
455
|
+
* captured, not propagated. Never fails.
|
|
456
|
+
*/
|
|
457
|
+
export const decodeCallInput = <Input>(
|
|
458
|
+
tool: DecodableTool<Input>,
|
|
459
|
+
call: ToolCall,
|
|
460
|
+
): Effect.Effect<DecodeResult<Input>> => {
|
|
461
|
+
const raw = call.arguments.trim() === "" ? "{}" : call.arguments
|
|
462
|
+
return Effect.try({
|
|
463
|
+
// @effect-diagnostics-next-line effect/preferSchemaOverJson:off
|
|
464
|
+
try: () => JSON.parse(raw) as unknown,
|
|
465
|
+
catch: (): DecodeResult<Input> => ({ _tag: "parseError" }),
|
|
466
|
+
}).pipe(
|
|
467
|
+
Effect.flatMap((parsed) =>
|
|
468
|
+
Effect.tryPromise({
|
|
469
|
+
try: () => Promise.resolve(tool.inputSchema["~standard"].validate(parsed)),
|
|
470
|
+
catch: (): DecodeResult<Input> => ({ _tag: "invalid", issues: VALIDATION_THREW }),
|
|
471
|
+
}).pipe(
|
|
472
|
+
Effect.map(
|
|
473
|
+
(result): DecodeResult<Input> =>
|
|
474
|
+
result.issues !== undefined
|
|
475
|
+
? { _tag: "invalid", issues: result.issues }
|
|
476
|
+
: { _tag: "ok", input: result.value },
|
|
477
|
+
),
|
|
478
|
+
),
|
|
479
|
+
),
|
|
480
|
+
Effect.catch((captured: DecodeResult<Input>) => Effect.succeed(captured)),
|
|
481
|
+
)
|
|
482
|
+
}
|
|
280
483
|
|
|
281
484
|
/**
|
|
282
485
|
* Decode and validate a function_call's JSON `arguments` against the tool's
|
|
@@ -291,28 +494,26 @@ export const decodeArgs = <Input>(
|
|
|
291
494
|
tool: DecodableTool<Input>,
|
|
292
495
|
call: ToolCall,
|
|
293
496
|
): Effect.Effect<Input, ToolError | ToolValidationError> =>
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
return result.value
|
|
315
|
-
})
|
|
497
|
+
decodeCallInput(tool, call).pipe(
|
|
498
|
+
Effect.flatMap(
|
|
499
|
+
Match.type<DecodeResult<Input>>().pipe(
|
|
500
|
+
Match.tag("ok", ({ input }) => Effect.succeed(input)),
|
|
501
|
+
Match.tag("parseError", () =>
|
|
502
|
+
Effect.fail(
|
|
503
|
+
new ToolError({
|
|
504
|
+
call_id: call.call_id,
|
|
505
|
+
tool: tool.name,
|
|
506
|
+
message: "Failed to parse JSON arguments",
|
|
507
|
+
}),
|
|
508
|
+
),
|
|
509
|
+
),
|
|
510
|
+
Match.tag("invalid", ({ issues }) =>
|
|
511
|
+
Effect.fail(new ToolValidationError({ call_id: call.call_id, tool: tool.name, issues })),
|
|
512
|
+
),
|
|
513
|
+
Match.exhaustive,
|
|
514
|
+
),
|
|
515
|
+
),
|
|
516
|
+
)
|
|
316
517
|
|
|
317
518
|
/**
|
|
318
519
|
* Decode and validate the JSON arguments of a function_call against the
|
|
@@ -320,15 +521,11 @@ export const decodeArgs = <Input>(
|
|
|
320
521
|
* function_call_output item. Single-shot: intermediate events emitted by
|
|
321
522
|
* `run` are discarded (use `Toolkit.run` to stream them).
|
|
322
523
|
*/
|
|
323
|
-
export const execute = <Name extends string, Input, Event, Output, R>(
|
|
324
|
-
tool: Tool<Name, Input, Event, Output, R>,
|
|
524
|
+
export const execute = <Name extends string, Input, Event, Output, E, R>(
|
|
525
|
+
tool: Tool<Name, Input, Event, Output, E, R>,
|
|
325
526
|
call: ToolCall,
|
|
326
|
-
): Effect.Effect<ToolCallOutput, ToolError | ToolValidationError, R> =>
|
|
527
|
+
): Effect.Effect<ToolCallOutput, E | ToolError | ToolValidationError, R> =>
|
|
327
528
|
decodeArgs(tool, call).pipe(
|
|
328
|
-
Effect.flatMap((input) =>
|
|
329
|
-
|
|
330
|
-
.run(input, () => Effect.void)
|
|
331
|
-
.pipe(Effect.mapError(toToolError(call, tool.name, "Tool execution failed"))),
|
|
332
|
-
),
|
|
333
|
-
Effect.map((output) => toolCallOutput(call.call_id, JSON.stringify(output))),
|
|
529
|
+
Effect.flatMap((input) => tool.run(input, () => Effect.void)),
|
|
530
|
+
Effect.map((output) => toolCallOutput(call.call_id, serializeValue(output))),
|
|
334
531
|
)
|
package/src/tool/ToolEvent.ts
CHANGED
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Recipes thread `ToolEvent.Output.result` through `continueWithResults` and
|
|
9
9
|
* apply `toToolCallOutput` when appending to history.
|
|
10
|
+
*
|
|
11
|
+
* Demux note: `Progress` carries `call_id` / `tool` at the top level, while
|
|
12
|
+
* `Output` nests them inside `result` (the `ToolResult` already carries both).
|
|
13
|
+
* A consumer grouping events by call reads `e.call_id` for `Progress` and
|
|
14
|
+
* `e.result.call_id` for `Output`.
|
|
10
15
|
*/
|
|
11
16
|
import { Data } from "effect"
|
|
12
17
|
import type { ToolResult } from "./ToolResult.js"
|
package/src/tool/ToolResult.ts
CHANGED
|
@@ -95,14 +95,20 @@ export const validationError = (call: ToolCall, reason?: string): ToolResult =>
|
|
|
95
95
|
// Wire conversion - the one place structured → string happens.
|
|
96
96
|
// ---------------------------------------------------------------------------
|
|
97
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Serialize a success value for the wire. A `string` passes through raw
|
|
100
|
+
* (models read plain text best; JSON.stringify would quote and escape it);
|
|
101
|
+
* `undefined` becomes `"null"`; anything else is JSON.
|
|
102
|
+
*/
|
|
103
|
+
export const serializeValue = (value: unknown): string =>
|
|
104
|
+
typeof value === "string" ? value : JSON.stringify(value ?? null)
|
|
105
|
+
|
|
106
|
+
/** The one failure shape the model sees: `{"error":{"kind","message"}}`. */
|
|
107
|
+
const failureBody = (kind: string, message?: string): string =>
|
|
108
|
+
JSON.stringify({ error: message !== undefined ? { kind, message } : { kind } })
|
|
109
|
+
|
|
98
110
|
export const toToolCallOutput = (r: ToolResult): ToolCallOutput =>
|
|
99
111
|
ToolResult.$match(r, {
|
|
100
|
-
Ok: (v) => toolCallOutput(v.call_id,
|
|
101
|
-
Failure: (f) =>
|
|
102
|
-
toolCallOutput(
|
|
103
|
-
f.call_id,
|
|
104
|
-
JSON.stringify(
|
|
105
|
-
f.reason !== undefined ? { kind: f.kind, reason: f.reason } : { kind: f.kind },
|
|
106
|
-
),
|
|
107
|
-
),
|
|
112
|
+
Ok: (v) => toolCallOutput(v.call_id, serializeValue(v.value)),
|
|
113
|
+
Failure: (f) => toolCallOutput(f.call_id, failureBody(f.kind, f.reason)),
|
|
108
114
|
})
|