@orkestrel/tool 0.0.18 → 0.0.20
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/src/core/index.cjs +63 -28
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +5 -1
- package/dist/src/core/index.d.ts +5 -1
- package/dist/src/core/index.js +64 -29
- package/dist/src/core/index.js.map +1 -1
- package/package.json +16 -16
package/dist/src/core/index.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
let _orkestrel_contract = require("@orkestrel/contract");
|
|
3
3
|
let _orkestrel_emitter = require("@orkestrel/emitter");
|
|
4
|
-
|
|
4
|
+
// src/core/errors.ts
|
|
5
5
|
/**
|
|
6
6
|
* Reports a schema conflict or argument validation failure with a machine-readable code.
|
|
7
7
|
*
|
|
@@ -19,6 +19,7 @@ var ToolError = class extends Error {
|
|
|
19
19
|
context;
|
|
20
20
|
constructor(code, message, context) {
|
|
21
21
|
super(message);
|
|
22
|
+
_orkestrel_contract.INTRINSICS.define(this, Symbol.for("@orkestrel/tool.ToolError"), { value: this });
|
|
22
23
|
this.code = code;
|
|
23
24
|
if (context !== void 0) this.context = context;
|
|
24
25
|
}
|
|
@@ -26,6 +27,10 @@ var ToolError = class extends Error {
|
|
|
26
27
|
/**
|
|
27
28
|
* Checks whether a value is a tool error, containing hostile prototype access.
|
|
28
29
|
*
|
|
30
|
+
* @remarks
|
|
31
|
+
* Recognizes self-branded errors across package copies and subclasses and refuses
|
|
32
|
+
* transparent proxies. Validates the declared code and optional fault report.
|
|
33
|
+
*
|
|
29
34
|
* @param value - The value to test
|
|
30
35
|
* @returns True if the value is an instance of the tool error class; false otherwise
|
|
31
36
|
*
|
|
@@ -38,10 +43,47 @@ var ToolError = class extends Error {
|
|
|
38
43
|
* ```
|
|
39
44
|
*/
|
|
40
45
|
function isToolError(value) {
|
|
41
|
-
return (0, _orkestrel_contract.
|
|
46
|
+
return (0, _orkestrel_contract.holds)(() => {
|
|
47
|
+
if (!(value instanceof Error) || _orkestrel_contract.INTRINSICS.prototype(value) === Error.prototype) return false;
|
|
48
|
+
if (_orkestrel_contract.INTRINSICS.describe(value, Symbol.for("@orkestrel/tool.ToolError"))?.value !== value) return false;
|
|
49
|
+
const code = _orkestrel_contract.INTRINSICS.reflect.read(value, "code");
|
|
50
|
+
if (code !== "SCHEMA" && code !== "ARGUMENTS") return false;
|
|
51
|
+
const context = _orkestrel_contract.INTRINSICS.reflect.read(value, "context");
|
|
52
|
+
if (context === void 0) return true;
|
|
53
|
+
if (!(0, _orkestrel_contract.isObject)(context) && !(0, _orkestrel_contract.isFunction)(context)) return false;
|
|
54
|
+
const faults = _orkestrel_contract.INTRINSICS.reflect.read(context, "faults");
|
|
55
|
+
if (faults === void 0) return true;
|
|
56
|
+
return (0, _orkestrel_contract.arrayOf)((fault) => {
|
|
57
|
+
if (!(0, _orkestrel_contract.isObject)(fault)) return false;
|
|
58
|
+
const path = _orkestrel_contract.INTRINSICS.reflect.read(fault, "path");
|
|
59
|
+
if (!(0, _orkestrel_contract.isString)(path) && !(0, _orkestrel_contract.arrayOf)(_orkestrel_contract.isString)(path)) return false;
|
|
60
|
+
const reason = _orkestrel_contract.INTRINSICS.reflect.read(fault, "reason");
|
|
61
|
+
if (reason === "variant") return typeof _orkestrel_contract.INTRINSICS.reflect.read(fault, "variants") === "number";
|
|
62
|
+
if (reason === "oneOf") return typeof _orkestrel_contract.INTRINSICS.reflect.read(fault, "matched") === "number";
|
|
63
|
+
const expected = _orkestrel_contract.INTRINSICS.reflect.read(fault, "expected");
|
|
64
|
+
if (![
|
|
65
|
+
"string",
|
|
66
|
+
"number",
|
|
67
|
+
"integer",
|
|
68
|
+
"boolean",
|
|
69
|
+
"null",
|
|
70
|
+
"literal",
|
|
71
|
+
"array",
|
|
72
|
+
"object",
|
|
73
|
+
"union",
|
|
74
|
+
"json"
|
|
75
|
+
].some((candidate) => candidate === expected)) return false;
|
|
76
|
+
if (reason === "missing") return true;
|
|
77
|
+
if (!(0, _orkestrel_contract.isString)(_orkestrel_contract.INTRINSICS.reflect.read(fault, "received"))) return false;
|
|
78
|
+
if (reason === "type") return true;
|
|
79
|
+
if (reason !== "constraint") return false;
|
|
80
|
+
const constraint = _orkestrel_contract.INTRINSICS.reflect.read(fault, "constraint");
|
|
81
|
+
const limit = _orkestrel_contract.INTRINSICS.reflect.read(fault, "limit");
|
|
82
|
+
return (constraint === "min" || constraint === "max" || constraint === "pattern" || constraint === "integer") && (limit === void 0 || typeof limit === "number" || typeof limit === "string");
|
|
83
|
+
})(faults);
|
|
84
|
+
});
|
|
42
85
|
}
|
|
43
|
-
|
|
44
|
-
//#region src/core/validators.ts
|
|
86
|
+
// src/core/validators.ts
|
|
45
87
|
/**
|
|
46
88
|
* Determines whether an unknown value is structurally a {@link ToolCall}, staying total
|
|
47
89
|
* for malformed and adversarial input.
|
|
@@ -64,8 +106,7 @@ function isToolError(value) {
|
|
|
64
106
|
function isToolCall(value) {
|
|
65
107
|
return (0, _orkestrel_contract.holds)(() => (0, _orkestrel_contract.isRecord)(value) && (0, _orkestrel_contract.isString)(value.id) && (0, _orkestrel_contract.isString)(value.name) && (0, _orkestrel_contract.isRecord)(value.arguments));
|
|
66
108
|
}
|
|
67
|
-
|
|
68
|
-
//#region src/core/helpers.ts
|
|
109
|
+
// src/core/helpers.ts
|
|
69
110
|
/**
|
|
70
111
|
* Projects a tool onto the plain definition advertised to a caller, advertising an
|
|
71
112
|
* authored `summary` in place of the full description and carrying `parameters` and
|
|
@@ -97,8 +138,7 @@ function toolToDefinition(tool) {
|
|
|
97
138
|
...tool.annotations === void 0 ? {} : { annotations: tool.annotations }
|
|
98
139
|
};
|
|
99
140
|
}
|
|
100
|
-
|
|
101
|
-
//#region src/core/tools/Tool.ts
|
|
141
|
+
// src/core/tools/Tool.ts
|
|
102
142
|
/**
|
|
103
143
|
* Binds an executable tool definition to a handler.
|
|
104
144
|
*
|
|
@@ -151,16 +191,15 @@ var Tool = class {
|
|
|
151
191
|
execute(args, context) {
|
|
152
192
|
const faults = this.#contract?.explain(args) ?? [];
|
|
153
193
|
const fault = faults[0];
|
|
154
|
-
if (fault !== void 0)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
194
|
+
if (fault !== void 0) throw new ToolError("ARGUMENTS", [
|
|
195
|
+
`${(0, _orkestrel_contract.isString)(fault.path) ? fault.path : fault.path.join(".")}: ${fault.reason}`,
|
|
196
|
+
..."expected" in fault ? [`expected ${fault.expected}`] : [],
|
|
197
|
+
..."received" in fault ? [`received ${fault.received}`] : [],
|
|
198
|
+
..."constraint" in fault ? [`constraint ${fault.constraint}`] : [],
|
|
199
|
+
..."limit" in fault && fault.limit !== void 0 ? [`limit ${fault.limit}`] : [],
|
|
200
|
+
..."variants" in fault ? [`variants ${fault.variants}`] : [],
|
|
201
|
+
..."matched" in fault ? [`matched ${fault.matched}`] : []
|
|
202
|
+
].join("; "), { faults });
|
|
164
203
|
if (this.#contract !== void 0) {
|
|
165
204
|
const parsed = this.#contract.parse(args);
|
|
166
205
|
if (!(0, _orkestrel_contract.isRecord)(parsed)) throw new ToolError("ARGUMENTS", "Arguments did not parse");
|
|
@@ -169,8 +208,7 @@ var Tool = class {
|
|
|
169
208
|
return this.#execute(args, context);
|
|
170
209
|
}
|
|
171
210
|
};
|
|
172
|
-
|
|
173
|
-
//#region src/core/tools/ToolManager.ts
|
|
211
|
+
// src/core/tools/ToolManager.ts
|
|
174
212
|
/**
|
|
175
213
|
* Represents an insertion-ordered tool registry with per-call error isolation.
|
|
176
214
|
*
|
|
@@ -237,11 +275,7 @@ var ToolManager = class {
|
|
|
237
275
|
return this.#run(call, context);
|
|
238
276
|
}
|
|
239
277
|
remove(names) {
|
|
240
|
-
if ((0, _orkestrel_contract.isArray)(names))
|
|
241
|
-
let removed = true;
|
|
242
|
-
for (const name of names) if (!this.remove(name)) removed = false;
|
|
243
|
-
return removed;
|
|
244
|
-
}
|
|
278
|
+
if ((0, _orkestrel_contract.isArray)(names)) return this.#remove(names).reduce((all, removed) => removed && all, true);
|
|
245
279
|
const tool = this.#tools.get(names);
|
|
246
280
|
if (tool === void 0) return false;
|
|
247
281
|
this.#tools.delete(names);
|
|
@@ -258,6 +292,9 @@ var ToolManager = class {
|
|
|
258
292
|
this.#emitter.destroy();
|
|
259
293
|
this.#tools.clear();
|
|
260
294
|
}
|
|
295
|
+
*#remove(names) {
|
|
296
|
+
for (const name of names) yield this.remove(name);
|
|
297
|
+
}
|
|
261
298
|
async #run(call, context) {
|
|
262
299
|
const tool = this.#tools.get(call.name);
|
|
263
300
|
if (tool === void 0) return {
|
|
@@ -294,8 +331,7 @@ var ToolManager = class {
|
|
|
294
331
|
}
|
|
295
332
|
}
|
|
296
333
|
};
|
|
297
|
-
|
|
298
|
-
//#region src/core/factories.ts
|
|
334
|
+
// src/core/factories.ts
|
|
299
335
|
/**
|
|
300
336
|
* Creates an executable tool bound to the supplied handler, returned as a
|
|
301
337
|
* `ToolInterface` so a call site holds the published contract rather than the `Tool`
|
|
@@ -351,7 +387,6 @@ function createTool(options) {
|
|
|
351
387
|
function createToolManager(options) {
|
|
352
388
|
return new ToolManager(options);
|
|
353
389
|
}
|
|
354
|
-
//#endregion
|
|
355
390
|
exports.Tool = Tool;
|
|
356
391
|
exports.ToolError = ToolError;
|
|
357
392
|
exports.ToolManager = ToolManager;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["isInstance","holds","isRecord","isString","createContract","schemaToParameters","isString","isRecord","Emitter","isArray","attempt","isError"],"sources":["../../../src/core/errors.ts","../../../src/core/validators.ts","../../../src/core/helpers.ts","../../../src/core/tools/Tool.ts","../../../src/core/tools/ToolManager.ts","../../../src/core/factories.ts"],"sourcesContent":["import type { ToolErrorCode, ToolErrorContext } from './types.js'\nimport { isInstance } from '@orkestrel/contract'\n\n/**\n * Reports a schema conflict or argument validation failure with a machine-readable code.\n *\n * @example\n * ```ts\n * import { ToolError } from '@orkestrel/tool'\n *\n * const error = new ToolError('SCHEMA', 'Choose contract or parameters')\n * error.code // 'SCHEMA'\n * ```\n */\nexport class ToolError extends Error {\n\toverride readonly name = 'ToolError' as const\n\treadonly code: ToolErrorCode\n\treadonly context?: ToolErrorContext\n\n\tconstructor(code: ToolErrorCode, message: string, context?: ToolErrorContext) {\n\t\tsuper(message)\n\t\tthis.code = code\n\t\tif (context !== undefined) this.context = context\n\t}\n}\n\n/**\n * Checks whether a value is a tool error, containing hostile prototype access.\n *\n * @param value - The value to test\n * @returns True if the value is an instance of the tool error class; false otherwise\n *\n * @example\n * ```ts\n * import { ToolError, isToolError } from '@orkestrel/tool'\n *\n * isToolError(new ToolError('ARGUMENTS', 'Invalid amount')) // true\n * isToolError(new Error('Unrelated')) // false\n * ```\n */\nexport function isToolError(value: unknown): value is ToolError {\n\treturn isInstance(value, ToolError)\n}\n","import type { ToolCall } from './types.js'\nimport { holds, isRecord, isString } from '@orkestrel/contract'\n\n/**\n * Determines whether an unknown value is structurally a {@link ToolCall}, staying total\n * for malformed and adversarial input.\n *\n * @remarks\n * The accepted shape is a plain record with string `id` and `name` fields and a\n * plain-record `arguments` field. Extra fields are not read or verified.\n *\n * @param value - The value to test\n * @returns True if the value has the complete tool-call shape; false otherwise\n *\n * @example\n * ```ts\n * import { isToolCall } from '@orkestrel/tool'\n *\n * isToolCall({ id: '1', name: 'search', arguments: { query: 'birds' } }) // true\n * isToolCall({ id: '1', name: 'search', arguments: [] }) // false\n * ```\n */\nexport function isToolCall(value: unknown): value is ToolCall {\n\treturn holds(\n\t\t() =>\n\t\t\tisRecord(value) && isString(value.id) && isString(value.name) && isRecord(value.arguments),\n\t)\n}\n","import type { ToolDefinition, ToolInterface } from './types.js'\n\n/**\n * Projects a tool onto the plain definition advertised to a caller, advertising an\n * authored `summary` in place of the full description and carrying `parameters` and\n * `annotations` by reference.\n *\n * @remarks\n * The projection carries `name` and present `title`, `description`, `parameters`, and\n * `annotations` fields in that order. The full `description` stays on the tool for\n * direct lookup, and the definition is never a live handle on the tool's handler.\n *\n * @param tool - The tool to project\n * @returns A fresh definition carrying only the fields the tool authored\n *\n * @example\n * ```ts\n * import { Tool, toolToDefinition } from '@orkestrel/tool'\n *\n * const echo = new Tool({ name: 'echo', summary: 'Echo a value.', execute: (args) => args.value })\n * toolToDefinition(echo) // { name: 'echo', description: 'Echo a value.' }\n * ```\n */\nexport function toolToDefinition(tool: ToolInterface): ToolDefinition {\n\tconst description = tool.summary ?? tool.description\n\treturn {\n\t\tname: tool.name,\n\t\t...(tool.title === undefined ? {} : { title: tool.title }),\n\t\t...(description === undefined ? {} : { description }),\n\t\t...(tool.parameters === undefined ? {} : { parameters: tool.parameters }),\n\t\t...(tool.annotations === undefined ? {} : { annotations: tool.annotations }),\n\t}\n}\n","import type { ContractInterface } from '@orkestrel/contract'\nimport type { ToolAnnotations, ToolContext, ToolInterface, ToolOptions } from '../types.js'\nimport { createContract, isRecord, isString, schemaToParameters } from '@orkestrel/contract'\nimport { ToolError } from '../errors.js'\n\n/**\n * Binds an executable tool definition to a handler.\n *\n * @remarks\n * Advertised fields and execution context are forwarded by reference.\n * A contract derives parameters at construction and refuses parse faults before the\n * handler runs, then forwards the parsed arguments; without a contract, arguments\n * retain their identity. Supplying a contract and parameters throws a schema conflict.\n * Caller context is consumer-asserted and is not verified. Handler failures are not\n * caught here; {@link ToolManager} owns per-call error isolation.\n *\n * @example\n * ```ts\n * import { Tool } from '@orkestrel/tool'\n *\n * const tool = new Tool({\n * \tname: 'add',\n * \tdescription: 'Add two numbers',\n * \tparameters: {\n * \t\ttype: 'object',\n * \t\tproperties: { a: { type: 'number' }, b: { type: 'number' } },\n * \t},\n * \texecute: (args) => Number(args.a) + Number(args.b),\n * })\n * ```\n */\nexport class Tool implements ToolInterface {\n\treadonly #contract?: ContractInterface<unknown>\n\treadonly #execute: ToolOptions['execute']\n\n\treadonly name: string\n\treadonly title?: string\n\treadonly description?: string\n\treadonly summary?: string\n\treadonly parameters?: Readonly<Record<string, unknown>>\n\treadonly annotations?: ToolAnnotations\n\n\tconstructor(options: ToolOptions) {\n\t\tif (options.contract !== undefined && options.parameters !== undefined) {\n\t\t\tthrow new ToolError('SCHEMA', 'Choose contract or parameters, not both')\n\t\t}\n\t\tthis.name = options.name\n\t\tif (options.title !== undefined) this.title = options.title\n\t\tif (options.description !== undefined) this.description = options.description\n\t\tif (options.summary !== undefined) this.summary = options.summary\n\t\tif (options.annotations !== undefined) this.annotations = options.annotations\n\t\tif (options.contract !== undefined) {\n\t\t\tthis.#contract = createContract(options.contract)\n\t\t\tconst parameters = schemaToParameters(this.#contract.schema)\n\t\t\tif (parameters !== undefined) this.parameters = parameters\n\t\t} else if (options.parameters !== undefined) this.parameters = options.parameters\n\t\tthis.#execute = options.execute\n\t}\n\n\texecute(\n\t\targs: Readonly<Record<string, unknown>>,\n\t\tcontext: ToolContext,\n\t): Promise<unknown> | unknown {\n\t\tconst faults = this.#contract?.explain(args) ?? []\n\t\tconst fault = faults[0]\n\t\tif (fault !== undefined) {\n\t\t\tconst path = isString(fault.path) ? fault.path : fault.path.join('.')\n\t\t\tlet message = `${path}: ${fault.reason}`\n\t\t\tif ('expected' in fault) message += `; expected ${fault.expected}`\n\t\t\tif ('received' in fault) message += `; received ${fault.received}`\n\t\t\tif ('constraint' in fault) message += `; constraint ${fault.constraint}`\n\t\t\tif ('limit' in fault && fault.limit !== undefined) message += `; limit ${fault.limit}`\n\t\t\tif ('variants' in fault) message += `; variants ${fault.variants}`\n\t\t\tif ('matched' in fault) message += `; matched ${fault.matched}`\n\t\t\tthrow new ToolError('ARGUMENTS', message, { faults })\n\t\t}\n\t\tif (this.#contract !== undefined) {\n\t\t\tconst parsed = this.#contract.parse(args)\n\t\t\tif (!isRecord(parsed)) throw new ToolError('ARGUMENTS', 'Arguments did not parse')\n\t\t\treturn this.#execute(parsed, context)\n\t\t}\n\t\treturn this.#execute(args, context)\n\t}\n}\n","import type { EmitterInterface } from '@orkestrel/emitter'\nimport type {\n\tToolCall,\n\tToolContext,\n\tToolDefinition,\n\tToolInterface,\n\tToolManagerEventMap,\n\tToolManagerInterface,\n\tToolManagerOptions,\n\tToolResult,\n} from '../types.js'\nimport { attempt, isArray, isError } from '@orkestrel/contract'\nimport { Emitter } from '@orkestrel/emitter'\nimport { toolToDefinition } from '../helpers.js'\n\n/**\n * Represents an insertion-ordered tool registry with per-call error isolation.\n *\n * @remarks\n * A repeated name overwrites the registered tool without changing its insertion\n * position. Definitions advertise `summary` in place of `description` when present.\n * Unknown names and handler throws resolve to error results; a call whose `id` or `name`\n * accessor throws when read makes its call, and the batch holding it, reject. Batch\n * execution preserves input order and isolates each call whose members are plain\n * values. Execution context is shared across a batch and forwarded unchanged. An\n * omitted context receives a non-aborted signal. A signal aborted before handler\n * entry produces an error result; later cancellation is the handler's responsibility.\n * Registry changes publish synchronously. Replacements publish `remove`, then `add`\n * if the map still holds that exact replacement after the removal listeners return.\n * Destruction clears the tools before releasing listeners. A destroyed registry\n * publishes nothing, even when later additions update its tool map.\n *\n * @example\n * ```ts\n * import { Tool, ToolManager } from '@orkestrel/tool'\n *\n * const tools = new ToolManager()\n * tools.add(new Tool({ name: 'add', execute: (args) => Number(args.x) + Number(args.y) }))\n * const result = await tools.execute({\n * \tid: '1',\n * \tname: 'add',\n * \targuments: { x: 1, y: 2 },\n * })\n * ```\n */\nexport class ToolManager implements ToolManagerInterface {\n\treadonly #tools = new Map<string, ToolInterface>()\n\treadonly #emitter: Emitter<ToolManagerEventMap>\n\n\tconstructor(options?: ToolManagerOptions) {\n\t\tthis.#emitter = new Emitter<ToolManagerEventMap>(options)\n\t}\n\n\tget count(): number {\n\t\treturn this.#tools.size\n\t}\n\n\tget emitter(): EmitterInterface<ToolManagerEventMap> {\n\t\treturn this.#emitter\n\t}\n\n\tadd(tool: ToolInterface): void\n\tadd(tools: readonly ToolInterface[]): void\n\tadd(tools: ToolInterface | readonly ToolInterface[]): void {\n\t\tif (isArray(tools)) {\n\t\t\tfor (const tool of tools) this.add(tool)\n\t\t\treturn\n\t\t}\n\t\tconst previous = this.#tools.get(tools.name)\n\t\tthis.#tools.set(tools.name, tools)\n\t\tif (previous !== undefined) this.#emitter.emit('remove', previous)\n\t\tif (this.#tools.get(tools.name) === tools) this.#emitter.emit('add', tools)\n\t}\n\n\ttool(name: string): ToolInterface | undefined {\n\t\treturn this.#tools.get(name)\n\t}\n\n\ttools(): readonly ToolInterface[] {\n\t\treturn [...this.#tools.values()]\n\t}\n\n\tdefinitions(): readonly ToolDefinition[] {\n\t\treturn [...this.#tools.values()].map((tool) => toolToDefinition(tool))\n\t}\n\n\texecute(call: ToolCall, context?: ToolContext): Promise<ToolResult>\n\texecute(calls: readonly ToolCall[], context?: ToolContext): Promise<readonly ToolResult[]>\n\texecute(\n\t\tcall: ToolCall | readonly ToolCall[],\n\t\tcontext: ToolContext = { signal: new AbortController().signal },\n\t): Promise<ToolResult | readonly ToolResult[]> {\n\t\tif (isArray(call)) return Promise.all(call.map((one) => this.#run(one, context)))\n\t\treturn this.#run(call, context)\n\t}\n\n\tremove(name: string): boolean\n\tremove(names: readonly string[]): boolean\n\tremove(names: string | readonly string[]): boolean {\n\t\tif (isArray(names)) {\n\t\t\tlet removed = true\n\t\t\tfor (const name of names) {\n\t\t\t\tif (!this.remove(name)) removed = false\n\t\t\t}\n\t\t\treturn removed\n\t\t}\n\t\tconst tool = this.#tools.get(names)\n\t\tif (tool === undefined) return false\n\t\tthis.#tools.delete(names)\n\t\tthis.#emitter.emit('remove', tool)\n\t\treturn true\n\t}\n\n\tclear(): void {\n\t\tconst tools = this.tools()\n\t\tthis.#tools.clear()\n\t\tthis.#emitter.emit('clear', tools)\n\t}\n\n\tdestroy(): void {\n\t\tthis.clear()\n\t\tthis.#emitter.destroy()\n\t\tthis.#tools.clear()\n\t}\n\n\tasync #run(call: ToolCall, context: ToolContext): Promise<ToolResult> {\n\t\tconst tool = this.#tools.get(call.name)\n\t\tif (tool === undefined) {\n\t\t\treturn {\n\t\t\t\tid: call.id,\n\t\t\t\tname: call.name,\n\t\t\t\tsuccess: false,\n\t\t\t\terror: `tool not found: ${call.name}`,\n\t\t\t}\n\t\t}\n\t\ttry {\n\t\t\tif (context.signal.aborted) {\n\t\t\t\tconst reason: unknown = context.signal.reason\n\t\t\t\treturn {\n\t\t\t\t\tid: call.id,\n\t\t\t\t\tname: call.name,\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: reason === undefined ? 'aborted' : String(reason),\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst value = await tool.execute(call.arguments, context)\n\t\t\treturn { id: call.id, name: call.name, success: true, value }\n\t\t} catch (error) {\n\t\t\tconst message = attempt(() => (isError(error) ? error.message : String(error)))\n\t\t\treturn {\n\t\t\t\tid: call.id,\n\t\t\t\tname: call.name,\n\t\t\t\tsuccess: false,\n\t\t\t\terror: message.success ? message.value : 'Unknown thrown value',\n\t\t\t}\n\t\t}\n\t}\n}\n","import type {\n\tToolInterface,\n\tToolManagerInterface,\n\tToolManagerOptions,\n\tToolOptions,\n} from './types.js'\nimport { Tool } from './tools/Tool.js'\nimport { ToolManager } from './tools/ToolManager.js'\n\n/**\n * Creates an executable tool bound to the supplied handler, returned as a\n * `ToolInterface` so a call site holds the published contract rather than the `Tool`\n * class.\n *\n * @param options - The advertised definition and execution handler\n * @returns A tool bound to the supplied handler\n *\n * @example Anatomy of a tool\n * ```ts\n * import { createTool } from '@orkestrel/tool'\n *\n * const add = createTool({\n * \tname: 'add',\n * \tdescription: 'Add two numeric values and return their sum. Both operands are required.',\n * \tsummary: 'Add two numbers.',\n * \tparameters: {\n * \t\ttype: 'object',\n * \t\tproperties: {\n * \t\t\tleft: { type: 'number' },\n * \t\t\tright: { type: 'number' },\n * \t\t},\n * \t\trequired: ['left', 'right'],\n * \t},\n * \texecute: (args) => Number(args.left) + Number(args.right),\n * })\n * ```\n */\nexport function createTool(options: ToolOptions): ToolInterface {\n\treturn new Tool(options)\n}\n\n/**\n * Creates an empty registry that advertises definitions and executes calls with\n * per-call error isolation, returned as a `ToolManagerInterface` so a caller holds the\n * published contract rather than the `ToolManager` class.\n *\n * @param options - The initial registry listeners and listener-error handler\n * @returns A registry bound to no tools\n *\n * @example\n * ```ts\n * import { createTool, createToolManager } from '@orkestrel/tool'\n *\n * const tools = createToolManager()\n * tools.add(createTool({ name: 'echo', execute: (args) => args.value }))\n * const result = await tools.execute({\n * \tid: '1',\n * \tname: 'echo',\n * \targuments: { value: 'hello' },\n * })\n * ```\n */\nexport function createToolManager(options?: ToolManagerOptions): ToolManagerInterface {\n\treturn new ToolManager(options)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["holds","INTRINSICS","isObject","isFunction","arrayOf","isString","holds","isRecord","isString","createContract","schemaToParameters","isString","isRecord","Emitter","isArray","attempt","isError"],"sources":["../../../src/core/errors.ts","../../../src/core/validators.ts","../../../src/core/helpers.ts","../../../src/core/tools/Tool.ts","../../../src/core/tools/ToolManager.ts","../../../src/core/factories.ts"],"sourcesContent":["import type { ToolErrorCode, ToolErrorContext } from './types.js'\nimport { arrayOf, holds, INTRINSICS, isFunction, isObject, isString } from '@orkestrel/contract'\n\n/**\n * Reports a schema conflict or argument validation failure with a machine-readable code.\n *\n * @example\n * ```ts\n * import { ToolError } from '@orkestrel/tool'\n *\n * const error = new ToolError('SCHEMA', 'Choose contract or parameters')\n * error.code // 'SCHEMA'\n * ```\n */\nexport class ToolError extends Error {\n\toverride readonly name = 'ToolError' as const\n\treadonly code: ToolErrorCode\n\treadonly context?: ToolErrorContext\n\n\tconstructor(code: ToolErrorCode, message: string, context?: ToolErrorContext) {\n\t\tsuper(message)\n\t\tINTRINSICS.define(this, Symbol.for('@orkestrel/tool.ToolError'), { value: this })\n\t\tthis.code = code\n\t\tif (context !== undefined) this.context = context\n\t}\n}\n\n/**\n * Checks whether a value is a tool error, containing hostile prototype access.\n *\n * @remarks\n * Recognizes self-branded errors across package copies and subclasses and refuses\n * transparent proxies. Validates the declared code and optional fault report.\n *\n * @param value - The value to test\n * @returns True if the value is an instance of the tool error class; false otherwise\n *\n * @example\n * ```ts\n * import { ToolError, isToolError } from '@orkestrel/tool'\n *\n * isToolError(new ToolError('ARGUMENTS', 'Invalid amount')) // true\n * isToolError(new Error('Unrelated')) // false\n * ```\n */\nexport function isToolError(value: unknown): value is ToolError {\n\treturn holds(() => {\n\t\tif (!(value instanceof Error) || INTRINSICS.prototype(value) === Error.prototype) return false\n\t\tconst brand = INTRINSICS.describe(value, Symbol.for('@orkestrel/tool.ToolError'))\n\t\tif (brand?.value !== value) return false\n\t\tconst code: unknown = INTRINSICS.reflect.read(value, 'code')\n\t\tif (code !== 'SCHEMA' && code !== 'ARGUMENTS') return false\n\t\tconst context: unknown = INTRINSICS.reflect.read(value, 'context')\n\t\tif (context === undefined) return true\n\t\tif (!isObject(context) && !isFunction(context)) return false\n\t\tconst faults: unknown = INTRINSICS.reflect.read(context, 'faults')\n\t\tif (faults === undefined) return true\n\t\treturn arrayOf((fault: unknown) => {\n\t\t\tif (!isObject(fault)) return false\n\t\t\tconst path: unknown = INTRINSICS.reflect.read(fault, 'path')\n\t\t\tif (!isString(path) && !arrayOf(isString)(path)) return false\n\t\t\tconst reason: unknown = INTRINSICS.reflect.read(fault, 'reason')\n\t\t\tif (reason === 'variant') {\n\t\t\t\treturn typeof INTRINSICS.reflect.read(fault, 'variants') === 'number'\n\t\t\t}\n\t\t\tif (reason === 'oneOf') {\n\t\t\t\treturn typeof INTRINSICS.reflect.read(fault, 'matched') === 'number'\n\t\t\t}\n\t\t\tconst expected: unknown = INTRINSICS.reflect.read(fault, 'expected')\n\t\t\tif (\n\t\t\t\t![\n\t\t\t\t\t'string',\n\t\t\t\t\t'number',\n\t\t\t\t\t'integer',\n\t\t\t\t\t'boolean',\n\t\t\t\t\t'null',\n\t\t\t\t\t'literal',\n\t\t\t\t\t'array',\n\t\t\t\t\t'object',\n\t\t\t\t\t'union',\n\t\t\t\t\t'json',\n\t\t\t\t].some((candidate) => candidate === expected)\n\t\t\t)\n\t\t\t\treturn false\n\t\t\tif (reason === 'missing') return true\n\t\t\tif (!isString(INTRINSICS.reflect.read(fault, 'received'))) return false\n\t\t\tif (reason === 'type') return true\n\t\t\tif (reason !== 'constraint') return false\n\t\t\tconst constraint: unknown = INTRINSICS.reflect.read(fault, 'constraint')\n\t\t\tconst limit: unknown = INTRINSICS.reflect.read(fault, 'limit')\n\t\t\treturn (\n\t\t\t\t(constraint === 'min' ||\n\t\t\t\t\tconstraint === 'max' ||\n\t\t\t\t\tconstraint === 'pattern' ||\n\t\t\t\t\tconstraint === 'integer') &&\n\t\t\t\t(limit === undefined || typeof limit === 'number' || typeof limit === 'string')\n\t\t\t)\n\t\t})(faults)\n\t})\n}\n","import type { ToolCall } from './types.js'\nimport { holds, isRecord, isString } from '@orkestrel/contract'\n\n/**\n * Determines whether an unknown value is structurally a {@link ToolCall}, staying total\n * for malformed and adversarial input.\n *\n * @remarks\n * The accepted shape is a plain record with string `id` and `name` fields and a\n * plain-record `arguments` field. Extra fields are not read or verified.\n *\n * @param value - The value to test\n * @returns True if the value has the complete tool-call shape; false otherwise\n *\n * @example\n * ```ts\n * import { isToolCall } from '@orkestrel/tool'\n *\n * isToolCall({ id: '1', name: 'search', arguments: { query: 'birds' } }) // true\n * isToolCall({ id: '1', name: 'search', arguments: [] }) // false\n * ```\n */\nexport function isToolCall(value: unknown): value is ToolCall {\n\treturn holds(\n\t\t() =>\n\t\t\tisRecord(value) && isString(value.id) && isString(value.name) && isRecord(value.arguments),\n\t)\n}\n","import type { ToolDefinition, ToolInterface } from './types.js'\n\n/**\n * Projects a tool onto the plain definition advertised to a caller, advertising an\n * authored `summary` in place of the full description and carrying `parameters` and\n * `annotations` by reference.\n *\n * @remarks\n * The projection carries `name` and present `title`, `description`, `parameters`, and\n * `annotations` fields in that order. The full `description` stays on the tool for\n * direct lookup, and the definition is never a live handle on the tool's handler.\n *\n * @param tool - The tool to project\n * @returns A fresh definition carrying only the fields the tool authored\n *\n * @example\n * ```ts\n * import { Tool, toolToDefinition } from '@orkestrel/tool'\n *\n * const echo = new Tool({ name: 'echo', summary: 'Echo a value.', execute: (args) => args.value })\n * toolToDefinition(echo) // { name: 'echo', description: 'Echo a value.' }\n * ```\n */\nexport function toolToDefinition(tool: ToolInterface): ToolDefinition {\n\tconst description = tool.summary ?? tool.description\n\treturn {\n\t\tname: tool.name,\n\t\t...(tool.title === undefined ? {} : { title: tool.title }),\n\t\t...(description === undefined ? {} : { description }),\n\t\t...(tool.parameters === undefined ? {} : { parameters: tool.parameters }),\n\t\t...(tool.annotations === undefined ? {} : { annotations: tool.annotations }),\n\t}\n}\n","import type { ContractInterface } from '@orkestrel/contract'\nimport type { ToolAnnotations, ToolContext, ToolInterface, ToolOptions } from '../types.js'\nimport { createContract, isRecord, isString, schemaToParameters } from '@orkestrel/contract'\nimport { ToolError } from '../errors.js'\n\n/**\n * Binds an executable tool definition to a handler.\n *\n * @remarks\n * Advertised fields and execution context are forwarded by reference.\n * A contract derives parameters at construction and refuses parse faults before the\n * handler runs, then forwards the parsed arguments; without a contract, arguments\n * retain their identity. Supplying a contract and parameters throws a schema conflict.\n * Caller context is consumer-asserted and is not verified. Handler failures are not\n * caught here; {@link ToolManager} owns per-call error isolation.\n *\n * @example\n * ```ts\n * import { Tool } from '@orkestrel/tool'\n *\n * const tool = new Tool({\n * \tname: 'add',\n * \tdescription: 'Add two numbers',\n * \tparameters: {\n * \t\ttype: 'object',\n * \t\tproperties: { a: { type: 'number' }, b: { type: 'number' } },\n * \t},\n * \texecute: (args) => Number(args.a) + Number(args.b),\n * })\n * ```\n */\nexport class Tool implements ToolInterface {\n\treadonly #contract?: ContractInterface<unknown>\n\treadonly #execute: ToolOptions['execute']\n\n\treadonly name: string\n\treadonly title?: string\n\treadonly description?: string\n\treadonly summary?: string\n\treadonly parameters?: Readonly<Record<string, unknown>>\n\treadonly annotations?: ToolAnnotations\n\n\tconstructor(options: ToolOptions) {\n\t\tif (options.contract !== undefined && options.parameters !== undefined) {\n\t\t\tthrow new ToolError('SCHEMA', 'Choose contract or parameters, not both')\n\t\t}\n\t\tthis.name = options.name\n\t\tif (options.title !== undefined) this.title = options.title\n\t\tif (options.description !== undefined) this.description = options.description\n\t\tif (options.summary !== undefined) this.summary = options.summary\n\t\tif (options.annotations !== undefined) this.annotations = options.annotations\n\t\tif (options.contract !== undefined) {\n\t\t\tthis.#contract = createContract(options.contract)\n\t\t\tconst parameters = schemaToParameters(this.#contract.schema)\n\t\t\tif (parameters !== undefined) this.parameters = parameters\n\t\t} else if (options.parameters !== undefined) this.parameters = options.parameters\n\t\tthis.#execute = options.execute\n\t}\n\n\texecute(\n\t\targs: Readonly<Record<string, unknown>>,\n\t\tcontext: ToolContext,\n\t): Promise<unknown> | unknown {\n\t\tconst faults = this.#contract?.explain(args) ?? []\n\t\tconst fault = faults[0]\n\t\tif (fault !== undefined) {\n\t\t\tconst path = isString(fault.path) ? fault.path : fault.path.join('.')\n\t\t\tconst message = [\n\t\t\t\t`${path}: ${fault.reason}`,\n\t\t\t\t...('expected' in fault ? [`expected ${fault.expected}`] : []),\n\t\t\t\t...('received' in fault ? [`received ${fault.received}`] : []),\n\t\t\t\t...('constraint' in fault ? [`constraint ${fault.constraint}`] : []),\n\t\t\t\t...('limit' in fault && fault.limit !== undefined ? [`limit ${fault.limit}`] : []),\n\t\t\t\t...('variants' in fault ? [`variants ${fault.variants}`] : []),\n\t\t\t\t...('matched' in fault ? [`matched ${fault.matched}`] : []),\n\t\t\t].join('; ')\n\t\t\tthrow new ToolError('ARGUMENTS', message, { faults })\n\t\t}\n\t\tif (this.#contract !== undefined) {\n\t\t\tconst parsed = this.#contract.parse(args)\n\t\t\tif (!isRecord(parsed)) throw new ToolError('ARGUMENTS', 'Arguments did not parse')\n\t\t\treturn this.#execute(parsed, context)\n\t\t}\n\t\treturn this.#execute(args, context)\n\t}\n}\n","import type { EmitterInterface } from '@orkestrel/emitter'\nimport type {\n\tToolCall,\n\tToolContext,\n\tToolDefinition,\n\tToolInterface,\n\tToolManagerEventMap,\n\tToolManagerInterface,\n\tToolManagerOptions,\n\tToolResult,\n} from '../types.js'\nimport { attempt, isArray, isError } from '@orkestrel/contract'\nimport { Emitter } from '@orkestrel/emitter'\nimport { toolToDefinition } from '../helpers.js'\n\n/**\n * Represents an insertion-ordered tool registry with per-call error isolation.\n *\n * @remarks\n * A repeated name overwrites the registered tool without changing its insertion\n * position. Definitions advertise `summary` in place of `description` when present.\n * Unknown names and handler throws resolve to error results; a call whose `id` or `name`\n * accessor throws when read makes its call, and the batch holding it, reject. Batch\n * execution preserves input order and isolates each call whose members are plain\n * values. Execution context is shared across a batch and forwarded unchanged. An\n * omitted context receives a non-aborted signal. A signal aborted before handler\n * entry produces an error result; later cancellation is the handler's responsibility.\n * Registry changes publish synchronously. Replacements publish `remove`, then `add`\n * if the map still holds that exact replacement after the removal listeners return.\n * Destruction clears the tools before releasing listeners. A destroyed registry\n * publishes nothing, even when later additions update its tool map.\n *\n * @example\n * ```ts\n * import { Tool, ToolManager } from '@orkestrel/tool'\n *\n * const tools = new ToolManager()\n * tools.add(new Tool({ name: 'add', execute: (args) => Number(args.x) + Number(args.y) }))\n * const result = await tools.execute({\n * \tid: '1',\n * \tname: 'add',\n * \targuments: { x: 1, y: 2 },\n * })\n * ```\n */\nexport class ToolManager implements ToolManagerInterface {\n\treadonly #tools = new Map<string, ToolInterface>()\n\treadonly #emitter: Emitter<ToolManagerEventMap>\n\n\tconstructor(options?: ToolManagerOptions) {\n\t\tthis.#emitter = new Emitter<ToolManagerEventMap>(options)\n\t}\n\n\tget count(): number {\n\t\treturn this.#tools.size\n\t}\n\n\tget emitter(): EmitterInterface<ToolManagerEventMap> {\n\t\treturn this.#emitter\n\t}\n\n\tadd(tool: ToolInterface): void\n\tadd(tools: readonly ToolInterface[]): void\n\tadd(tools: ToolInterface | readonly ToolInterface[]): void {\n\t\tif (isArray(tools)) {\n\t\t\tfor (const tool of tools) this.add(tool)\n\t\t\treturn\n\t\t}\n\t\tconst previous = this.#tools.get(tools.name)\n\t\tthis.#tools.set(tools.name, tools)\n\t\tif (previous !== undefined) this.#emitter.emit('remove', previous)\n\t\tif (this.#tools.get(tools.name) === tools) this.#emitter.emit('add', tools)\n\t}\n\n\ttool(name: string): ToolInterface | undefined {\n\t\treturn this.#tools.get(name)\n\t}\n\n\ttools(): readonly ToolInterface[] {\n\t\treturn [...this.#tools.values()]\n\t}\n\n\tdefinitions(): readonly ToolDefinition[] {\n\t\treturn [...this.#tools.values()].map((tool) => toolToDefinition(tool))\n\t}\n\n\texecute(call: ToolCall, context?: ToolContext): Promise<ToolResult>\n\texecute(calls: readonly ToolCall[], context?: ToolContext): Promise<readonly ToolResult[]>\n\texecute(\n\t\tcall: ToolCall | readonly ToolCall[],\n\t\tcontext: ToolContext = { signal: new AbortController().signal },\n\t): Promise<ToolResult | readonly ToolResult[]> {\n\t\tif (isArray(call)) return Promise.all(call.map((one) => this.#run(one, context)))\n\t\treturn this.#run(call, context)\n\t}\n\n\tremove(name: string): boolean\n\tremove(names: readonly string[]): boolean\n\tremove(names: string | readonly string[]): boolean {\n\t\tif (isArray(names)) {\n\t\t\treturn this.#remove(names).reduce((all, removed) => removed && all, true)\n\t\t}\n\t\tconst tool = this.#tools.get(names)\n\t\tif (tool === undefined) return false\n\t\tthis.#tools.delete(names)\n\t\tthis.#emitter.emit('remove', tool)\n\t\treturn true\n\t}\n\n\tclear(): void {\n\t\tconst tools = this.tools()\n\t\tthis.#tools.clear()\n\t\tthis.#emitter.emit('clear', tools)\n\t}\n\n\tdestroy(): void {\n\t\tthis.clear()\n\t\tthis.#emitter.destroy()\n\t\tthis.#tools.clear()\n\t}\n\n\t*#remove(names: readonly string[]): Generator<boolean> {\n\t\tfor (const name of names) yield this.remove(name)\n\t}\n\n\tasync #run(call: ToolCall, context: ToolContext): Promise<ToolResult> {\n\t\tconst tool = this.#tools.get(call.name)\n\t\tif (tool === undefined) {\n\t\t\treturn {\n\t\t\t\tid: call.id,\n\t\t\t\tname: call.name,\n\t\t\t\tsuccess: false,\n\t\t\t\terror: `tool not found: ${call.name}`,\n\t\t\t}\n\t\t}\n\t\ttry {\n\t\t\tif (context.signal.aborted) {\n\t\t\t\tconst reason: unknown = context.signal.reason\n\t\t\t\treturn {\n\t\t\t\t\tid: call.id,\n\t\t\t\t\tname: call.name,\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: reason === undefined ? 'aborted' : String(reason),\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst value = await tool.execute(call.arguments, context)\n\t\t\treturn { id: call.id, name: call.name, success: true, value }\n\t\t} catch (error) {\n\t\t\tconst message = attempt(() => (isError(error) ? error.message : String(error)))\n\t\t\treturn {\n\t\t\t\tid: call.id,\n\t\t\t\tname: call.name,\n\t\t\t\tsuccess: false,\n\t\t\t\terror: message.success ? message.value : 'Unknown thrown value',\n\t\t\t}\n\t\t}\n\t}\n}\n","import type {\n\tToolInterface,\n\tToolManagerInterface,\n\tToolManagerOptions,\n\tToolOptions,\n} from './types.js'\nimport { Tool } from './tools/Tool.js'\nimport { ToolManager } from './tools/ToolManager.js'\n\n/**\n * Creates an executable tool bound to the supplied handler, returned as a\n * `ToolInterface` so a call site holds the published contract rather than the `Tool`\n * class.\n *\n * @param options - The advertised definition and execution handler\n * @returns A tool bound to the supplied handler\n *\n * @example Anatomy of a tool\n * ```ts\n * import { createTool } from '@orkestrel/tool'\n *\n * const add = createTool({\n * \tname: 'add',\n * \tdescription: 'Add two numeric values and return their sum. Both operands are required.',\n * \tsummary: 'Add two numbers.',\n * \tparameters: {\n * \t\ttype: 'object',\n * \t\tproperties: {\n * \t\t\tleft: { type: 'number' },\n * \t\t\tright: { type: 'number' },\n * \t\t},\n * \t\trequired: ['left', 'right'],\n * \t},\n * \texecute: (args) => Number(args.left) + Number(args.right),\n * })\n * ```\n */\nexport function createTool(options: ToolOptions): ToolInterface {\n\treturn new Tool(options)\n}\n\n/**\n * Creates an empty registry that advertises definitions and executes calls with\n * per-call error isolation, returned as a `ToolManagerInterface` so a caller holds the\n * published contract rather than the `ToolManager` class.\n *\n * @param options - The initial registry listeners and listener-error handler\n * @returns A registry bound to no tools\n *\n * @example\n * ```ts\n * import { createTool, createToolManager } from '@orkestrel/tool'\n *\n * const tools = createToolManager()\n * tools.add(createTool({ name: 'echo', execute: (args) => args.value }))\n * const result = await tools.execute({\n * \tid: '1',\n * \tname: 'echo',\n * \targuments: { value: 'hello' },\n * })\n * ```\n */\nexport function createToolManager(options?: ToolManagerOptions): ToolManagerInterface {\n\treturn new ToolManager(options)\n}\n"],"mappings":"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"}
|
|
@@ -83,6 +83,10 @@ export declare function isToolCall(value: unknown): value is ToolCall;
|
|
|
83
83
|
/**
|
|
84
84
|
* Checks whether a value is a tool error, containing hostile prototype access.
|
|
85
85
|
*
|
|
86
|
+
* @remarks
|
|
87
|
+
* Recognizes self-branded errors across package copies and subclasses and refuses
|
|
88
|
+
* transparent proxies. Validates the declared code and optional fault report.
|
|
89
|
+
*
|
|
86
90
|
* @param value - The value to test
|
|
87
91
|
* @returns True if the value is an instance of the tool error class; false otherwise
|
|
88
92
|
*
|
|
@@ -200,7 +204,7 @@ export declare interface ToolDefinition {
|
|
|
200
204
|
* ```
|
|
201
205
|
*/
|
|
202
206
|
export declare class ToolError extends Error {
|
|
203
|
-
readonly name:
|
|
207
|
+
readonly name: 'ToolError';
|
|
204
208
|
readonly code: ToolErrorCode;
|
|
205
209
|
readonly context?: ToolErrorContext;
|
|
206
210
|
constructor(code: ToolErrorCode, message: string, context?: ToolErrorContext);
|
package/dist/src/core/index.d.ts
CHANGED
|
@@ -83,6 +83,10 @@ export declare function isToolCall(value: unknown): value is ToolCall;
|
|
|
83
83
|
/**
|
|
84
84
|
* Checks whether a value is a tool error, containing hostile prototype access.
|
|
85
85
|
*
|
|
86
|
+
* @remarks
|
|
87
|
+
* Recognizes self-branded errors across package copies and subclasses and refuses
|
|
88
|
+
* transparent proxies. Validates the declared code and optional fault report.
|
|
89
|
+
*
|
|
86
90
|
* @param value - The value to test
|
|
87
91
|
* @returns True if the value is an instance of the tool error class; false otherwise
|
|
88
92
|
*
|
|
@@ -200,7 +204,7 @@ export declare interface ToolDefinition {
|
|
|
200
204
|
* ```
|
|
201
205
|
*/
|
|
202
206
|
export declare class ToolError extends Error {
|
|
203
|
-
readonly name:
|
|
207
|
+
readonly name: 'ToolError';
|
|
204
208
|
readonly code: ToolErrorCode;
|
|
205
209
|
readonly context?: ToolErrorContext;
|
|
206
210
|
constructor(code: ToolErrorCode, message: string, context?: ToolErrorContext);
|
package/dist/src/core/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { attempt, createContract, holds, isArray, isError,
|
|
1
|
+
import { INTRINSICS, arrayOf, attempt, createContract, holds, isArray, isError, isFunction, isObject, isRecord, isString, schemaToParameters } from "@orkestrel/contract";
|
|
2
2
|
import { Emitter } from "@orkestrel/emitter";
|
|
3
|
-
|
|
3
|
+
// src/core/errors.ts
|
|
4
4
|
/**
|
|
5
5
|
* Reports a schema conflict or argument validation failure with a machine-readable code.
|
|
6
6
|
*
|
|
@@ -18,6 +18,7 @@ var ToolError = class extends Error {
|
|
|
18
18
|
context;
|
|
19
19
|
constructor(code, message, context) {
|
|
20
20
|
super(message);
|
|
21
|
+
INTRINSICS.define(this, Symbol.for("@orkestrel/tool.ToolError"), { value: this });
|
|
21
22
|
this.code = code;
|
|
22
23
|
if (context !== void 0) this.context = context;
|
|
23
24
|
}
|
|
@@ -25,6 +26,10 @@ var ToolError = class extends Error {
|
|
|
25
26
|
/**
|
|
26
27
|
* Checks whether a value is a tool error, containing hostile prototype access.
|
|
27
28
|
*
|
|
29
|
+
* @remarks
|
|
30
|
+
* Recognizes self-branded errors across package copies and subclasses and refuses
|
|
31
|
+
* transparent proxies. Validates the declared code and optional fault report.
|
|
32
|
+
*
|
|
28
33
|
* @param value - The value to test
|
|
29
34
|
* @returns True if the value is an instance of the tool error class; false otherwise
|
|
30
35
|
*
|
|
@@ -37,10 +42,47 @@ var ToolError = class extends Error {
|
|
|
37
42
|
* ```
|
|
38
43
|
*/
|
|
39
44
|
function isToolError(value) {
|
|
40
|
-
return
|
|
45
|
+
return holds(() => {
|
|
46
|
+
if (!(value instanceof Error) || INTRINSICS.prototype(value) === Error.prototype) return false;
|
|
47
|
+
if (INTRINSICS.describe(value, Symbol.for("@orkestrel/tool.ToolError"))?.value !== value) return false;
|
|
48
|
+
const code = INTRINSICS.reflect.read(value, "code");
|
|
49
|
+
if (code !== "SCHEMA" && code !== "ARGUMENTS") return false;
|
|
50
|
+
const context = INTRINSICS.reflect.read(value, "context");
|
|
51
|
+
if (context === void 0) return true;
|
|
52
|
+
if (!isObject(context) && !isFunction(context)) return false;
|
|
53
|
+
const faults = INTRINSICS.reflect.read(context, "faults");
|
|
54
|
+
if (faults === void 0) return true;
|
|
55
|
+
return arrayOf((fault) => {
|
|
56
|
+
if (!isObject(fault)) return false;
|
|
57
|
+
const path = INTRINSICS.reflect.read(fault, "path");
|
|
58
|
+
if (!isString(path) && !arrayOf(isString)(path)) return false;
|
|
59
|
+
const reason = INTRINSICS.reflect.read(fault, "reason");
|
|
60
|
+
if (reason === "variant") return typeof INTRINSICS.reflect.read(fault, "variants") === "number";
|
|
61
|
+
if (reason === "oneOf") return typeof INTRINSICS.reflect.read(fault, "matched") === "number";
|
|
62
|
+
const expected = INTRINSICS.reflect.read(fault, "expected");
|
|
63
|
+
if (![
|
|
64
|
+
"string",
|
|
65
|
+
"number",
|
|
66
|
+
"integer",
|
|
67
|
+
"boolean",
|
|
68
|
+
"null",
|
|
69
|
+
"literal",
|
|
70
|
+
"array",
|
|
71
|
+
"object",
|
|
72
|
+
"union",
|
|
73
|
+
"json"
|
|
74
|
+
].some((candidate) => candidate === expected)) return false;
|
|
75
|
+
if (reason === "missing") return true;
|
|
76
|
+
if (!isString(INTRINSICS.reflect.read(fault, "received"))) return false;
|
|
77
|
+
if (reason === "type") return true;
|
|
78
|
+
if (reason !== "constraint") return false;
|
|
79
|
+
const constraint = INTRINSICS.reflect.read(fault, "constraint");
|
|
80
|
+
const limit = INTRINSICS.reflect.read(fault, "limit");
|
|
81
|
+
return (constraint === "min" || constraint === "max" || constraint === "pattern" || constraint === "integer") && (limit === void 0 || typeof limit === "number" || typeof limit === "string");
|
|
82
|
+
})(faults);
|
|
83
|
+
});
|
|
41
84
|
}
|
|
42
|
-
|
|
43
|
-
//#region src/core/validators.ts
|
|
85
|
+
// src/core/validators.ts
|
|
44
86
|
/**
|
|
45
87
|
* Determines whether an unknown value is structurally a {@link ToolCall}, staying total
|
|
46
88
|
* for malformed and adversarial input.
|
|
@@ -63,8 +105,7 @@ function isToolError(value) {
|
|
|
63
105
|
function isToolCall(value) {
|
|
64
106
|
return holds(() => isRecord(value) && isString(value.id) && isString(value.name) && isRecord(value.arguments));
|
|
65
107
|
}
|
|
66
|
-
|
|
67
|
-
//#region src/core/helpers.ts
|
|
108
|
+
// src/core/helpers.ts
|
|
68
109
|
/**
|
|
69
110
|
* Projects a tool onto the plain definition advertised to a caller, advertising an
|
|
70
111
|
* authored `summary` in place of the full description and carrying `parameters` and
|
|
@@ -96,8 +137,7 @@ function toolToDefinition(tool) {
|
|
|
96
137
|
...tool.annotations === void 0 ? {} : { annotations: tool.annotations }
|
|
97
138
|
};
|
|
98
139
|
}
|
|
99
|
-
|
|
100
|
-
//#region src/core/tools/Tool.ts
|
|
140
|
+
// src/core/tools/Tool.ts
|
|
101
141
|
/**
|
|
102
142
|
* Binds an executable tool definition to a handler.
|
|
103
143
|
*
|
|
@@ -150,16 +190,15 @@ var Tool = class {
|
|
|
150
190
|
execute(args, context) {
|
|
151
191
|
const faults = this.#contract?.explain(args) ?? [];
|
|
152
192
|
const fault = faults[0];
|
|
153
|
-
if (fault !== void 0)
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
193
|
+
if (fault !== void 0) throw new ToolError("ARGUMENTS", [
|
|
194
|
+
`${isString(fault.path) ? fault.path : fault.path.join(".")}: ${fault.reason}`,
|
|
195
|
+
..."expected" in fault ? [`expected ${fault.expected}`] : [],
|
|
196
|
+
..."received" in fault ? [`received ${fault.received}`] : [],
|
|
197
|
+
..."constraint" in fault ? [`constraint ${fault.constraint}`] : [],
|
|
198
|
+
..."limit" in fault && fault.limit !== void 0 ? [`limit ${fault.limit}`] : [],
|
|
199
|
+
..."variants" in fault ? [`variants ${fault.variants}`] : [],
|
|
200
|
+
..."matched" in fault ? [`matched ${fault.matched}`] : []
|
|
201
|
+
].join("; "), { faults });
|
|
163
202
|
if (this.#contract !== void 0) {
|
|
164
203
|
const parsed = this.#contract.parse(args);
|
|
165
204
|
if (!isRecord(parsed)) throw new ToolError("ARGUMENTS", "Arguments did not parse");
|
|
@@ -168,8 +207,7 @@ var Tool = class {
|
|
|
168
207
|
return this.#execute(args, context);
|
|
169
208
|
}
|
|
170
209
|
};
|
|
171
|
-
|
|
172
|
-
//#region src/core/tools/ToolManager.ts
|
|
210
|
+
// src/core/tools/ToolManager.ts
|
|
173
211
|
/**
|
|
174
212
|
* Represents an insertion-ordered tool registry with per-call error isolation.
|
|
175
213
|
*
|
|
@@ -236,11 +274,7 @@ var ToolManager = class {
|
|
|
236
274
|
return this.#run(call, context);
|
|
237
275
|
}
|
|
238
276
|
remove(names) {
|
|
239
|
-
if (isArray(names))
|
|
240
|
-
let removed = true;
|
|
241
|
-
for (const name of names) if (!this.remove(name)) removed = false;
|
|
242
|
-
return removed;
|
|
243
|
-
}
|
|
277
|
+
if (isArray(names)) return this.#remove(names).reduce((all, removed) => removed && all, true);
|
|
244
278
|
const tool = this.#tools.get(names);
|
|
245
279
|
if (tool === void 0) return false;
|
|
246
280
|
this.#tools.delete(names);
|
|
@@ -257,6 +291,9 @@ var ToolManager = class {
|
|
|
257
291
|
this.#emitter.destroy();
|
|
258
292
|
this.#tools.clear();
|
|
259
293
|
}
|
|
294
|
+
*#remove(names) {
|
|
295
|
+
for (const name of names) yield this.remove(name);
|
|
296
|
+
}
|
|
260
297
|
async #run(call, context) {
|
|
261
298
|
const tool = this.#tools.get(call.name);
|
|
262
299
|
if (tool === void 0) return {
|
|
@@ -293,8 +330,7 @@ var ToolManager = class {
|
|
|
293
330
|
}
|
|
294
331
|
}
|
|
295
332
|
};
|
|
296
|
-
|
|
297
|
-
//#region src/core/factories.ts
|
|
333
|
+
// src/core/factories.ts
|
|
298
334
|
/**
|
|
299
335
|
* Creates an executable tool bound to the supplied handler, returned as a
|
|
300
336
|
* `ToolInterface` so a call site holds the published contract rather than the `Tool`
|
|
@@ -350,7 +386,6 @@ function createTool(options) {
|
|
|
350
386
|
function createToolManager(options) {
|
|
351
387
|
return new ToolManager(options);
|
|
352
388
|
}
|
|
353
|
-
//#endregion
|
|
354
389
|
export { Tool, ToolError, ToolManager, createTool, createToolManager, isToolCall, isToolError, toolToDefinition };
|
|
355
390
|
|
|
356
391
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/core/errors.ts","../../../src/core/validators.ts","../../../src/core/helpers.ts","../../../src/core/tools/Tool.ts","../../../src/core/tools/ToolManager.ts","../../../src/core/factories.ts"],"sourcesContent":["import type { ToolErrorCode, ToolErrorContext } from './types.js'\nimport { isInstance } from '@orkestrel/contract'\n\n/**\n * Reports a schema conflict or argument validation failure with a machine-readable code.\n *\n * @example\n * ```ts\n * import { ToolError } from '@orkestrel/tool'\n *\n * const error = new ToolError('SCHEMA', 'Choose contract or parameters')\n * error.code // 'SCHEMA'\n * ```\n */\nexport class ToolError extends Error {\n\toverride readonly name = 'ToolError' as const\n\treadonly code: ToolErrorCode\n\treadonly context?: ToolErrorContext\n\n\tconstructor(code: ToolErrorCode, message: string, context?: ToolErrorContext) {\n\t\tsuper(message)\n\t\tthis.code = code\n\t\tif (context !== undefined) this.context = context\n\t}\n}\n\n/**\n * Checks whether a value is a tool error, containing hostile prototype access.\n *\n * @param value - The value to test\n * @returns True if the value is an instance of the tool error class; false otherwise\n *\n * @example\n * ```ts\n * import { ToolError, isToolError } from '@orkestrel/tool'\n *\n * isToolError(new ToolError('ARGUMENTS', 'Invalid amount')) // true\n * isToolError(new Error('Unrelated')) // false\n * ```\n */\nexport function isToolError(value: unknown): value is ToolError {\n\treturn isInstance(value, ToolError)\n}\n","import type { ToolCall } from './types.js'\nimport { holds, isRecord, isString } from '@orkestrel/contract'\n\n/**\n * Determines whether an unknown value is structurally a {@link ToolCall}, staying total\n * for malformed and adversarial input.\n *\n * @remarks\n * The accepted shape is a plain record with string `id` and `name` fields and a\n * plain-record `arguments` field. Extra fields are not read or verified.\n *\n * @param value - The value to test\n * @returns True if the value has the complete tool-call shape; false otherwise\n *\n * @example\n * ```ts\n * import { isToolCall } from '@orkestrel/tool'\n *\n * isToolCall({ id: '1', name: 'search', arguments: { query: 'birds' } }) // true\n * isToolCall({ id: '1', name: 'search', arguments: [] }) // false\n * ```\n */\nexport function isToolCall(value: unknown): value is ToolCall {\n\treturn holds(\n\t\t() =>\n\t\t\tisRecord(value) && isString(value.id) && isString(value.name) && isRecord(value.arguments),\n\t)\n}\n","import type { ToolDefinition, ToolInterface } from './types.js'\n\n/**\n * Projects a tool onto the plain definition advertised to a caller, advertising an\n * authored `summary` in place of the full description and carrying `parameters` and\n * `annotations` by reference.\n *\n * @remarks\n * The projection carries `name` and present `title`, `description`, `parameters`, and\n * `annotations` fields in that order. The full `description` stays on the tool for\n * direct lookup, and the definition is never a live handle on the tool's handler.\n *\n * @param tool - The tool to project\n * @returns A fresh definition carrying only the fields the tool authored\n *\n * @example\n * ```ts\n * import { Tool, toolToDefinition } from '@orkestrel/tool'\n *\n * const echo = new Tool({ name: 'echo', summary: 'Echo a value.', execute: (args) => args.value })\n * toolToDefinition(echo) // { name: 'echo', description: 'Echo a value.' }\n * ```\n */\nexport function toolToDefinition(tool: ToolInterface): ToolDefinition {\n\tconst description = tool.summary ?? tool.description\n\treturn {\n\t\tname: tool.name,\n\t\t...(tool.title === undefined ? {} : { title: tool.title }),\n\t\t...(description === undefined ? {} : { description }),\n\t\t...(tool.parameters === undefined ? {} : { parameters: tool.parameters }),\n\t\t...(tool.annotations === undefined ? {} : { annotations: tool.annotations }),\n\t}\n}\n","import type { ContractInterface } from '@orkestrel/contract'\nimport type { ToolAnnotations, ToolContext, ToolInterface, ToolOptions } from '../types.js'\nimport { createContract, isRecord, isString, schemaToParameters } from '@orkestrel/contract'\nimport { ToolError } from '../errors.js'\n\n/**\n * Binds an executable tool definition to a handler.\n *\n * @remarks\n * Advertised fields and execution context are forwarded by reference.\n * A contract derives parameters at construction and refuses parse faults before the\n * handler runs, then forwards the parsed arguments; without a contract, arguments\n * retain their identity. Supplying a contract and parameters throws a schema conflict.\n * Caller context is consumer-asserted and is not verified. Handler failures are not\n * caught here; {@link ToolManager} owns per-call error isolation.\n *\n * @example\n * ```ts\n * import { Tool } from '@orkestrel/tool'\n *\n * const tool = new Tool({\n * \tname: 'add',\n * \tdescription: 'Add two numbers',\n * \tparameters: {\n * \t\ttype: 'object',\n * \t\tproperties: { a: { type: 'number' }, b: { type: 'number' } },\n * \t},\n * \texecute: (args) => Number(args.a) + Number(args.b),\n * })\n * ```\n */\nexport class Tool implements ToolInterface {\n\treadonly #contract?: ContractInterface<unknown>\n\treadonly #execute: ToolOptions['execute']\n\n\treadonly name: string\n\treadonly title?: string\n\treadonly description?: string\n\treadonly summary?: string\n\treadonly parameters?: Readonly<Record<string, unknown>>\n\treadonly annotations?: ToolAnnotations\n\n\tconstructor(options: ToolOptions) {\n\t\tif (options.contract !== undefined && options.parameters !== undefined) {\n\t\t\tthrow new ToolError('SCHEMA', 'Choose contract or parameters, not both')\n\t\t}\n\t\tthis.name = options.name\n\t\tif (options.title !== undefined) this.title = options.title\n\t\tif (options.description !== undefined) this.description = options.description\n\t\tif (options.summary !== undefined) this.summary = options.summary\n\t\tif (options.annotations !== undefined) this.annotations = options.annotations\n\t\tif (options.contract !== undefined) {\n\t\t\tthis.#contract = createContract(options.contract)\n\t\t\tconst parameters = schemaToParameters(this.#contract.schema)\n\t\t\tif (parameters !== undefined) this.parameters = parameters\n\t\t} else if (options.parameters !== undefined) this.parameters = options.parameters\n\t\tthis.#execute = options.execute\n\t}\n\n\texecute(\n\t\targs: Readonly<Record<string, unknown>>,\n\t\tcontext: ToolContext,\n\t): Promise<unknown> | unknown {\n\t\tconst faults = this.#contract?.explain(args) ?? []\n\t\tconst fault = faults[0]\n\t\tif (fault !== undefined) {\n\t\t\tconst path = isString(fault.path) ? fault.path : fault.path.join('.')\n\t\t\tlet message = `${path}: ${fault.reason}`\n\t\t\tif ('expected' in fault) message += `; expected ${fault.expected}`\n\t\t\tif ('received' in fault) message += `; received ${fault.received}`\n\t\t\tif ('constraint' in fault) message += `; constraint ${fault.constraint}`\n\t\t\tif ('limit' in fault && fault.limit !== undefined) message += `; limit ${fault.limit}`\n\t\t\tif ('variants' in fault) message += `; variants ${fault.variants}`\n\t\t\tif ('matched' in fault) message += `; matched ${fault.matched}`\n\t\t\tthrow new ToolError('ARGUMENTS', message, { faults })\n\t\t}\n\t\tif (this.#contract !== undefined) {\n\t\t\tconst parsed = this.#contract.parse(args)\n\t\t\tif (!isRecord(parsed)) throw new ToolError('ARGUMENTS', 'Arguments did not parse')\n\t\t\treturn this.#execute(parsed, context)\n\t\t}\n\t\treturn this.#execute(args, context)\n\t}\n}\n","import type { EmitterInterface } from '@orkestrel/emitter'\nimport type {\n\tToolCall,\n\tToolContext,\n\tToolDefinition,\n\tToolInterface,\n\tToolManagerEventMap,\n\tToolManagerInterface,\n\tToolManagerOptions,\n\tToolResult,\n} from '../types.js'\nimport { attempt, isArray, isError } from '@orkestrel/contract'\nimport { Emitter } from '@orkestrel/emitter'\nimport { toolToDefinition } from '../helpers.js'\n\n/**\n * Represents an insertion-ordered tool registry with per-call error isolation.\n *\n * @remarks\n * A repeated name overwrites the registered tool without changing its insertion\n * position. Definitions advertise `summary` in place of `description` when present.\n * Unknown names and handler throws resolve to error results; a call whose `id` or `name`\n * accessor throws when read makes its call, and the batch holding it, reject. Batch\n * execution preserves input order and isolates each call whose members are plain\n * values. Execution context is shared across a batch and forwarded unchanged. An\n * omitted context receives a non-aborted signal. A signal aborted before handler\n * entry produces an error result; later cancellation is the handler's responsibility.\n * Registry changes publish synchronously. Replacements publish `remove`, then `add`\n * if the map still holds that exact replacement after the removal listeners return.\n * Destruction clears the tools before releasing listeners. A destroyed registry\n * publishes nothing, even when later additions update its tool map.\n *\n * @example\n * ```ts\n * import { Tool, ToolManager } from '@orkestrel/tool'\n *\n * const tools = new ToolManager()\n * tools.add(new Tool({ name: 'add', execute: (args) => Number(args.x) + Number(args.y) }))\n * const result = await tools.execute({\n * \tid: '1',\n * \tname: 'add',\n * \targuments: { x: 1, y: 2 },\n * })\n * ```\n */\nexport class ToolManager implements ToolManagerInterface {\n\treadonly #tools = new Map<string, ToolInterface>()\n\treadonly #emitter: Emitter<ToolManagerEventMap>\n\n\tconstructor(options?: ToolManagerOptions) {\n\t\tthis.#emitter = new Emitter<ToolManagerEventMap>(options)\n\t}\n\n\tget count(): number {\n\t\treturn this.#tools.size\n\t}\n\n\tget emitter(): EmitterInterface<ToolManagerEventMap> {\n\t\treturn this.#emitter\n\t}\n\n\tadd(tool: ToolInterface): void\n\tadd(tools: readonly ToolInterface[]): void\n\tadd(tools: ToolInterface | readonly ToolInterface[]): void {\n\t\tif (isArray(tools)) {\n\t\t\tfor (const tool of tools) this.add(tool)\n\t\t\treturn\n\t\t}\n\t\tconst previous = this.#tools.get(tools.name)\n\t\tthis.#tools.set(tools.name, tools)\n\t\tif (previous !== undefined) this.#emitter.emit('remove', previous)\n\t\tif (this.#tools.get(tools.name) === tools) this.#emitter.emit('add', tools)\n\t}\n\n\ttool(name: string): ToolInterface | undefined {\n\t\treturn this.#tools.get(name)\n\t}\n\n\ttools(): readonly ToolInterface[] {\n\t\treturn [...this.#tools.values()]\n\t}\n\n\tdefinitions(): readonly ToolDefinition[] {\n\t\treturn [...this.#tools.values()].map((tool) => toolToDefinition(tool))\n\t}\n\n\texecute(call: ToolCall, context?: ToolContext): Promise<ToolResult>\n\texecute(calls: readonly ToolCall[], context?: ToolContext): Promise<readonly ToolResult[]>\n\texecute(\n\t\tcall: ToolCall | readonly ToolCall[],\n\t\tcontext: ToolContext = { signal: new AbortController().signal },\n\t): Promise<ToolResult | readonly ToolResult[]> {\n\t\tif (isArray(call)) return Promise.all(call.map((one) => this.#run(one, context)))\n\t\treturn this.#run(call, context)\n\t}\n\n\tremove(name: string): boolean\n\tremove(names: readonly string[]): boolean\n\tremove(names: string | readonly string[]): boolean {\n\t\tif (isArray(names)) {\n\t\t\tlet removed = true\n\t\t\tfor (const name of names) {\n\t\t\t\tif (!this.remove(name)) removed = false\n\t\t\t}\n\t\t\treturn removed\n\t\t}\n\t\tconst tool = this.#tools.get(names)\n\t\tif (tool === undefined) return false\n\t\tthis.#tools.delete(names)\n\t\tthis.#emitter.emit('remove', tool)\n\t\treturn true\n\t}\n\n\tclear(): void {\n\t\tconst tools = this.tools()\n\t\tthis.#tools.clear()\n\t\tthis.#emitter.emit('clear', tools)\n\t}\n\n\tdestroy(): void {\n\t\tthis.clear()\n\t\tthis.#emitter.destroy()\n\t\tthis.#tools.clear()\n\t}\n\n\tasync #run(call: ToolCall, context: ToolContext): Promise<ToolResult> {\n\t\tconst tool = this.#tools.get(call.name)\n\t\tif (tool === undefined) {\n\t\t\treturn {\n\t\t\t\tid: call.id,\n\t\t\t\tname: call.name,\n\t\t\t\tsuccess: false,\n\t\t\t\terror: `tool not found: ${call.name}`,\n\t\t\t}\n\t\t}\n\t\ttry {\n\t\t\tif (context.signal.aborted) {\n\t\t\t\tconst reason: unknown = context.signal.reason\n\t\t\t\treturn {\n\t\t\t\t\tid: call.id,\n\t\t\t\t\tname: call.name,\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: reason === undefined ? 'aborted' : String(reason),\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst value = await tool.execute(call.arguments, context)\n\t\t\treturn { id: call.id, name: call.name, success: true, value }\n\t\t} catch (error) {\n\t\t\tconst message = attempt(() => (isError(error) ? error.message : String(error)))\n\t\t\treturn {\n\t\t\t\tid: call.id,\n\t\t\t\tname: call.name,\n\t\t\t\tsuccess: false,\n\t\t\t\terror: message.success ? message.value : 'Unknown thrown value',\n\t\t\t}\n\t\t}\n\t}\n}\n","import type {\n\tToolInterface,\n\tToolManagerInterface,\n\tToolManagerOptions,\n\tToolOptions,\n} from './types.js'\nimport { Tool } from './tools/Tool.js'\nimport { ToolManager } from './tools/ToolManager.js'\n\n/**\n * Creates an executable tool bound to the supplied handler, returned as a\n * `ToolInterface` so a call site holds the published contract rather than the `Tool`\n * class.\n *\n * @param options - The advertised definition and execution handler\n * @returns A tool bound to the supplied handler\n *\n * @example Anatomy of a tool\n * ```ts\n * import { createTool } from '@orkestrel/tool'\n *\n * const add = createTool({\n * \tname: 'add',\n * \tdescription: 'Add two numeric values and return their sum. Both operands are required.',\n * \tsummary: 'Add two numbers.',\n * \tparameters: {\n * \t\ttype: 'object',\n * \t\tproperties: {\n * \t\t\tleft: { type: 'number' },\n * \t\t\tright: { type: 'number' },\n * \t\t},\n * \t\trequired: ['left', 'right'],\n * \t},\n * \texecute: (args) => Number(args.left) + Number(args.right),\n * })\n * ```\n */\nexport function createTool(options: ToolOptions): ToolInterface {\n\treturn new Tool(options)\n}\n\n/**\n * Creates an empty registry that advertises definitions and executes calls with\n * per-call error isolation, returned as a `ToolManagerInterface` so a caller holds the\n * published contract rather than the `ToolManager` class.\n *\n * @param options - The initial registry listeners and listener-error handler\n * @returns A registry bound to no tools\n *\n * @example\n * ```ts\n * import { createTool, createToolManager } from '@orkestrel/tool'\n *\n * const tools = createToolManager()\n * tools.add(createTool({ name: 'echo', execute: (args) => args.value }))\n * const result = await tools.execute({\n * \tid: '1',\n * \tname: 'echo',\n * \targuments: { value: 'hello' },\n * })\n * ```\n */\nexport function createToolManager(options?: ToolManagerOptions): ToolManagerInterface {\n\treturn new ToolManager(options)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/core/errors.ts","../../../src/core/validators.ts","../../../src/core/helpers.ts","../../../src/core/tools/Tool.ts","../../../src/core/tools/ToolManager.ts","../../../src/core/factories.ts"],"sourcesContent":["import type { ToolErrorCode, ToolErrorContext } from './types.js'\nimport { arrayOf, holds, INTRINSICS, isFunction, isObject, isString } from '@orkestrel/contract'\n\n/**\n * Reports a schema conflict or argument validation failure with a machine-readable code.\n *\n * @example\n * ```ts\n * import { ToolError } from '@orkestrel/tool'\n *\n * const error = new ToolError('SCHEMA', 'Choose contract or parameters')\n * error.code // 'SCHEMA'\n * ```\n */\nexport class ToolError extends Error {\n\toverride readonly name = 'ToolError' as const\n\treadonly code: ToolErrorCode\n\treadonly context?: ToolErrorContext\n\n\tconstructor(code: ToolErrorCode, message: string, context?: ToolErrorContext) {\n\t\tsuper(message)\n\t\tINTRINSICS.define(this, Symbol.for('@orkestrel/tool.ToolError'), { value: this })\n\t\tthis.code = code\n\t\tif (context !== undefined) this.context = context\n\t}\n}\n\n/**\n * Checks whether a value is a tool error, containing hostile prototype access.\n *\n * @remarks\n * Recognizes self-branded errors across package copies and subclasses and refuses\n * transparent proxies. Validates the declared code and optional fault report.\n *\n * @param value - The value to test\n * @returns True if the value is an instance of the tool error class; false otherwise\n *\n * @example\n * ```ts\n * import { ToolError, isToolError } from '@orkestrel/tool'\n *\n * isToolError(new ToolError('ARGUMENTS', 'Invalid amount')) // true\n * isToolError(new Error('Unrelated')) // false\n * ```\n */\nexport function isToolError(value: unknown): value is ToolError {\n\treturn holds(() => {\n\t\tif (!(value instanceof Error) || INTRINSICS.prototype(value) === Error.prototype) return false\n\t\tconst brand = INTRINSICS.describe(value, Symbol.for('@orkestrel/tool.ToolError'))\n\t\tif (brand?.value !== value) return false\n\t\tconst code: unknown = INTRINSICS.reflect.read(value, 'code')\n\t\tif (code !== 'SCHEMA' && code !== 'ARGUMENTS') return false\n\t\tconst context: unknown = INTRINSICS.reflect.read(value, 'context')\n\t\tif (context === undefined) return true\n\t\tif (!isObject(context) && !isFunction(context)) return false\n\t\tconst faults: unknown = INTRINSICS.reflect.read(context, 'faults')\n\t\tif (faults === undefined) return true\n\t\treturn arrayOf((fault: unknown) => {\n\t\t\tif (!isObject(fault)) return false\n\t\t\tconst path: unknown = INTRINSICS.reflect.read(fault, 'path')\n\t\t\tif (!isString(path) && !arrayOf(isString)(path)) return false\n\t\t\tconst reason: unknown = INTRINSICS.reflect.read(fault, 'reason')\n\t\t\tif (reason === 'variant') {\n\t\t\t\treturn typeof INTRINSICS.reflect.read(fault, 'variants') === 'number'\n\t\t\t}\n\t\t\tif (reason === 'oneOf') {\n\t\t\t\treturn typeof INTRINSICS.reflect.read(fault, 'matched') === 'number'\n\t\t\t}\n\t\t\tconst expected: unknown = INTRINSICS.reflect.read(fault, 'expected')\n\t\t\tif (\n\t\t\t\t![\n\t\t\t\t\t'string',\n\t\t\t\t\t'number',\n\t\t\t\t\t'integer',\n\t\t\t\t\t'boolean',\n\t\t\t\t\t'null',\n\t\t\t\t\t'literal',\n\t\t\t\t\t'array',\n\t\t\t\t\t'object',\n\t\t\t\t\t'union',\n\t\t\t\t\t'json',\n\t\t\t\t].some((candidate) => candidate === expected)\n\t\t\t)\n\t\t\t\treturn false\n\t\t\tif (reason === 'missing') return true\n\t\t\tif (!isString(INTRINSICS.reflect.read(fault, 'received'))) return false\n\t\t\tif (reason === 'type') return true\n\t\t\tif (reason !== 'constraint') return false\n\t\t\tconst constraint: unknown = INTRINSICS.reflect.read(fault, 'constraint')\n\t\t\tconst limit: unknown = INTRINSICS.reflect.read(fault, 'limit')\n\t\t\treturn (\n\t\t\t\t(constraint === 'min' ||\n\t\t\t\t\tconstraint === 'max' ||\n\t\t\t\t\tconstraint === 'pattern' ||\n\t\t\t\t\tconstraint === 'integer') &&\n\t\t\t\t(limit === undefined || typeof limit === 'number' || typeof limit === 'string')\n\t\t\t)\n\t\t})(faults)\n\t})\n}\n","import type { ToolCall } from './types.js'\nimport { holds, isRecord, isString } from '@orkestrel/contract'\n\n/**\n * Determines whether an unknown value is structurally a {@link ToolCall}, staying total\n * for malformed and adversarial input.\n *\n * @remarks\n * The accepted shape is a plain record with string `id` and `name` fields and a\n * plain-record `arguments` field. Extra fields are not read or verified.\n *\n * @param value - The value to test\n * @returns True if the value has the complete tool-call shape; false otherwise\n *\n * @example\n * ```ts\n * import { isToolCall } from '@orkestrel/tool'\n *\n * isToolCall({ id: '1', name: 'search', arguments: { query: 'birds' } }) // true\n * isToolCall({ id: '1', name: 'search', arguments: [] }) // false\n * ```\n */\nexport function isToolCall(value: unknown): value is ToolCall {\n\treturn holds(\n\t\t() =>\n\t\t\tisRecord(value) && isString(value.id) && isString(value.name) && isRecord(value.arguments),\n\t)\n}\n","import type { ToolDefinition, ToolInterface } from './types.js'\n\n/**\n * Projects a tool onto the plain definition advertised to a caller, advertising an\n * authored `summary` in place of the full description and carrying `parameters` and\n * `annotations` by reference.\n *\n * @remarks\n * The projection carries `name` and present `title`, `description`, `parameters`, and\n * `annotations` fields in that order. The full `description` stays on the tool for\n * direct lookup, and the definition is never a live handle on the tool's handler.\n *\n * @param tool - The tool to project\n * @returns A fresh definition carrying only the fields the tool authored\n *\n * @example\n * ```ts\n * import { Tool, toolToDefinition } from '@orkestrel/tool'\n *\n * const echo = new Tool({ name: 'echo', summary: 'Echo a value.', execute: (args) => args.value })\n * toolToDefinition(echo) // { name: 'echo', description: 'Echo a value.' }\n * ```\n */\nexport function toolToDefinition(tool: ToolInterface): ToolDefinition {\n\tconst description = tool.summary ?? tool.description\n\treturn {\n\t\tname: tool.name,\n\t\t...(tool.title === undefined ? {} : { title: tool.title }),\n\t\t...(description === undefined ? {} : { description }),\n\t\t...(tool.parameters === undefined ? {} : { parameters: tool.parameters }),\n\t\t...(tool.annotations === undefined ? {} : { annotations: tool.annotations }),\n\t}\n}\n","import type { ContractInterface } from '@orkestrel/contract'\nimport type { ToolAnnotations, ToolContext, ToolInterface, ToolOptions } from '../types.js'\nimport { createContract, isRecord, isString, schemaToParameters } from '@orkestrel/contract'\nimport { ToolError } from '../errors.js'\n\n/**\n * Binds an executable tool definition to a handler.\n *\n * @remarks\n * Advertised fields and execution context are forwarded by reference.\n * A contract derives parameters at construction and refuses parse faults before the\n * handler runs, then forwards the parsed arguments; without a contract, arguments\n * retain their identity. Supplying a contract and parameters throws a schema conflict.\n * Caller context is consumer-asserted and is not verified. Handler failures are not\n * caught here; {@link ToolManager} owns per-call error isolation.\n *\n * @example\n * ```ts\n * import { Tool } from '@orkestrel/tool'\n *\n * const tool = new Tool({\n * \tname: 'add',\n * \tdescription: 'Add two numbers',\n * \tparameters: {\n * \t\ttype: 'object',\n * \t\tproperties: { a: { type: 'number' }, b: { type: 'number' } },\n * \t},\n * \texecute: (args) => Number(args.a) + Number(args.b),\n * })\n * ```\n */\nexport class Tool implements ToolInterface {\n\treadonly #contract?: ContractInterface<unknown>\n\treadonly #execute: ToolOptions['execute']\n\n\treadonly name: string\n\treadonly title?: string\n\treadonly description?: string\n\treadonly summary?: string\n\treadonly parameters?: Readonly<Record<string, unknown>>\n\treadonly annotations?: ToolAnnotations\n\n\tconstructor(options: ToolOptions) {\n\t\tif (options.contract !== undefined && options.parameters !== undefined) {\n\t\t\tthrow new ToolError('SCHEMA', 'Choose contract or parameters, not both')\n\t\t}\n\t\tthis.name = options.name\n\t\tif (options.title !== undefined) this.title = options.title\n\t\tif (options.description !== undefined) this.description = options.description\n\t\tif (options.summary !== undefined) this.summary = options.summary\n\t\tif (options.annotations !== undefined) this.annotations = options.annotations\n\t\tif (options.contract !== undefined) {\n\t\t\tthis.#contract = createContract(options.contract)\n\t\t\tconst parameters = schemaToParameters(this.#contract.schema)\n\t\t\tif (parameters !== undefined) this.parameters = parameters\n\t\t} else if (options.parameters !== undefined) this.parameters = options.parameters\n\t\tthis.#execute = options.execute\n\t}\n\n\texecute(\n\t\targs: Readonly<Record<string, unknown>>,\n\t\tcontext: ToolContext,\n\t): Promise<unknown> | unknown {\n\t\tconst faults = this.#contract?.explain(args) ?? []\n\t\tconst fault = faults[0]\n\t\tif (fault !== undefined) {\n\t\t\tconst path = isString(fault.path) ? fault.path : fault.path.join('.')\n\t\t\tconst message = [\n\t\t\t\t`${path}: ${fault.reason}`,\n\t\t\t\t...('expected' in fault ? [`expected ${fault.expected}`] : []),\n\t\t\t\t...('received' in fault ? [`received ${fault.received}`] : []),\n\t\t\t\t...('constraint' in fault ? [`constraint ${fault.constraint}`] : []),\n\t\t\t\t...('limit' in fault && fault.limit !== undefined ? [`limit ${fault.limit}`] : []),\n\t\t\t\t...('variants' in fault ? [`variants ${fault.variants}`] : []),\n\t\t\t\t...('matched' in fault ? [`matched ${fault.matched}`] : []),\n\t\t\t].join('; ')\n\t\t\tthrow new ToolError('ARGUMENTS', message, { faults })\n\t\t}\n\t\tif (this.#contract !== undefined) {\n\t\t\tconst parsed = this.#contract.parse(args)\n\t\t\tif (!isRecord(parsed)) throw new ToolError('ARGUMENTS', 'Arguments did not parse')\n\t\t\treturn this.#execute(parsed, context)\n\t\t}\n\t\treturn this.#execute(args, context)\n\t}\n}\n","import type { EmitterInterface } from '@orkestrel/emitter'\nimport type {\n\tToolCall,\n\tToolContext,\n\tToolDefinition,\n\tToolInterface,\n\tToolManagerEventMap,\n\tToolManagerInterface,\n\tToolManagerOptions,\n\tToolResult,\n} from '../types.js'\nimport { attempt, isArray, isError } from '@orkestrel/contract'\nimport { Emitter } from '@orkestrel/emitter'\nimport { toolToDefinition } from '../helpers.js'\n\n/**\n * Represents an insertion-ordered tool registry with per-call error isolation.\n *\n * @remarks\n * A repeated name overwrites the registered tool without changing its insertion\n * position. Definitions advertise `summary` in place of `description` when present.\n * Unknown names and handler throws resolve to error results; a call whose `id` or `name`\n * accessor throws when read makes its call, and the batch holding it, reject. Batch\n * execution preserves input order and isolates each call whose members are plain\n * values. Execution context is shared across a batch and forwarded unchanged. An\n * omitted context receives a non-aborted signal. A signal aborted before handler\n * entry produces an error result; later cancellation is the handler's responsibility.\n * Registry changes publish synchronously. Replacements publish `remove`, then `add`\n * if the map still holds that exact replacement after the removal listeners return.\n * Destruction clears the tools before releasing listeners. A destroyed registry\n * publishes nothing, even when later additions update its tool map.\n *\n * @example\n * ```ts\n * import { Tool, ToolManager } from '@orkestrel/tool'\n *\n * const tools = new ToolManager()\n * tools.add(new Tool({ name: 'add', execute: (args) => Number(args.x) + Number(args.y) }))\n * const result = await tools.execute({\n * \tid: '1',\n * \tname: 'add',\n * \targuments: { x: 1, y: 2 },\n * })\n * ```\n */\nexport class ToolManager implements ToolManagerInterface {\n\treadonly #tools = new Map<string, ToolInterface>()\n\treadonly #emitter: Emitter<ToolManagerEventMap>\n\n\tconstructor(options?: ToolManagerOptions) {\n\t\tthis.#emitter = new Emitter<ToolManagerEventMap>(options)\n\t}\n\n\tget count(): number {\n\t\treturn this.#tools.size\n\t}\n\n\tget emitter(): EmitterInterface<ToolManagerEventMap> {\n\t\treturn this.#emitter\n\t}\n\n\tadd(tool: ToolInterface): void\n\tadd(tools: readonly ToolInterface[]): void\n\tadd(tools: ToolInterface | readonly ToolInterface[]): void {\n\t\tif (isArray(tools)) {\n\t\t\tfor (const tool of tools) this.add(tool)\n\t\t\treturn\n\t\t}\n\t\tconst previous = this.#tools.get(tools.name)\n\t\tthis.#tools.set(tools.name, tools)\n\t\tif (previous !== undefined) this.#emitter.emit('remove', previous)\n\t\tif (this.#tools.get(tools.name) === tools) this.#emitter.emit('add', tools)\n\t}\n\n\ttool(name: string): ToolInterface | undefined {\n\t\treturn this.#tools.get(name)\n\t}\n\n\ttools(): readonly ToolInterface[] {\n\t\treturn [...this.#tools.values()]\n\t}\n\n\tdefinitions(): readonly ToolDefinition[] {\n\t\treturn [...this.#tools.values()].map((tool) => toolToDefinition(tool))\n\t}\n\n\texecute(call: ToolCall, context?: ToolContext): Promise<ToolResult>\n\texecute(calls: readonly ToolCall[], context?: ToolContext): Promise<readonly ToolResult[]>\n\texecute(\n\t\tcall: ToolCall | readonly ToolCall[],\n\t\tcontext: ToolContext = { signal: new AbortController().signal },\n\t): Promise<ToolResult | readonly ToolResult[]> {\n\t\tif (isArray(call)) return Promise.all(call.map((one) => this.#run(one, context)))\n\t\treturn this.#run(call, context)\n\t}\n\n\tremove(name: string): boolean\n\tremove(names: readonly string[]): boolean\n\tremove(names: string | readonly string[]): boolean {\n\t\tif (isArray(names)) {\n\t\t\treturn this.#remove(names).reduce((all, removed) => removed && all, true)\n\t\t}\n\t\tconst tool = this.#tools.get(names)\n\t\tif (tool === undefined) return false\n\t\tthis.#tools.delete(names)\n\t\tthis.#emitter.emit('remove', tool)\n\t\treturn true\n\t}\n\n\tclear(): void {\n\t\tconst tools = this.tools()\n\t\tthis.#tools.clear()\n\t\tthis.#emitter.emit('clear', tools)\n\t}\n\n\tdestroy(): void {\n\t\tthis.clear()\n\t\tthis.#emitter.destroy()\n\t\tthis.#tools.clear()\n\t}\n\n\t*#remove(names: readonly string[]): Generator<boolean> {\n\t\tfor (const name of names) yield this.remove(name)\n\t}\n\n\tasync #run(call: ToolCall, context: ToolContext): Promise<ToolResult> {\n\t\tconst tool = this.#tools.get(call.name)\n\t\tif (tool === undefined) {\n\t\t\treturn {\n\t\t\t\tid: call.id,\n\t\t\t\tname: call.name,\n\t\t\t\tsuccess: false,\n\t\t\t\terror: `tool not found: ${call.name}`,\n\t\t\t}\n\t\t}\n\t\ttry {\n\t\t\tif (context.signal.aborted) {\n\t\t\t\tconst reason: unknown = context.signal.reason\n\t\t\t\treturn {\n\t\t\t\t\tid: call.id,\n\t\t\t\t\tname: call.name,\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: reason === undefined ? 'aborted' : String(reason),\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst value = await tool.execute(call.arguments, context)\n\t\t\treturn { id: call.id, name: call.name, success: true, value }\n\t\t} catch (error) {\n\t\t\tconst message = attempt(() => (isError(error) ? error.message : String(error)))\n\t\t\treturn {\n\t\t\t\tid: call.id,\n\t\t\t\tname: call.name,\n\t\t\t\tsuccess: false,\n\t\t\t\terror: message.success ? message.value : 'Unknown thrown value',\n\t\t\t}\n\t\t}\n\t}\n}\n","import type {\n\tToolInterface,\n\tToolManagerInterface,\n\tToolManagerOptions,\n\tToolOptions,\n} from './types.js'\nimport { Tool } from './tools/Tool.js'\nimport { ToolManager } from './tools/ToolManager.js'\n\n/**\n * Creates an executable tool bound to the supplied handler, returned as a\n * `ToolInterface` so a call site holds the published contract rather than the `Tool`\n * class.\n *\n * @param options - The advertised definition and execution handler\n * @returns A tool bound to the supplied handler\n *\n * @example Anatomy of a tool\n * ```ts\n * import { createTool } from '@orkestrel/tool'\n *\n * const add = createTool({\n * \tname: 'add',\n * \tdescription: 'Add two numeric values and return their sum. Both operands are required.',\n * \tsummary: 'Add two numbers.',\n * \tparameters: {\n * \t\ttype: 'object',\n * \t\tproperties: {\n * \t\t\tleft: { type: 'number' },\n * \t\t\tright: { type: 'number' },\n * \t\t},\n * \t\trequired: ['left', 'right'],\n * \t},\n * \texecute: (args) => Number(args.left) + Number(args.right),\n * })\n * ```\n */\nexport function createTool(options: ToolOptions): ToolInterface {\n\treturn new Tool(options)\n}\n\n/**\n * Creates an empty registry that advertises definitions and executes calls with\n * per-call error isolation, returned as a `ToolManagerInterface` so a caller holds the\n * published contract rather than the `ToolManager` class.\n *\n * @param options - The initial registry listeners and listener-error handler\n * @returns A registry bound to no tools\n *\n * @example\n * ```ts\n * import { createTool, createToolManager } from '@orkestrel/tool'\n *\n * const tools = createToolManager()\n * tools.add(createTool({ name: 'echo', execute: (args) => args.value }))\n * const result = await tools.execute({\n * \tid: '1',\n * \tname: 'echo',\n * \targuments: { value: 'hello' },\n * })\n * ```\n */\nexport function createToolManager(options?: ToolManagerOptions): ToolManagerInterface {\n\treturn new ToolManager(options)\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orkestrel/tool",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.20",
|
|
4
4
|
"description": "The tool runtime for the @orkestrel line — JSON-Schema tool definitions, calls, results, executable tools, and a registry with per-call error isolation. Part of the @orkestrel line.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -64,28 +64,28 @@
|
|
|
64
64
|
"prepublishOnly": "npm run format:check && npm run lint:check && npm run check && npm run build && npm test && npm run test:distribution -- --mode release",
|
|
65
65
|
"test:distribution": "vitest run --config vite.config.ts --no-cache --reporter=dot --project distribution",
|
|
66
66
|
"test:probe": "vitest run --config vite.config.ts --no-cache --reporter=verbose --project probe",
|
|
67
|
-
"test:bench": "vitest bench --config vite.config.ts --no-cache --project probe",
|
|
67
|
+
"test:bench": "vitest bench --config vite.config.ts --no-cache --project \"probe (bench)\" --mode benchmark",
|
|
68
68
|
"prepack": "npm run build",
|
|
69
69
|
"test:setup": "vitest run --config vite.config.ts --no-cache --reporter=dot --project setup"
|
|
70
70
|
},
|
|
71
71
|
"dependencies": {
|
|
72
|
-
"@orkestrel/contract": "^0.0.
|
|
73
|
-
"@orkestrel/emitter": "^0.0.
|
|
72
|
+
"@orkestrel/contract": "^0.0.20",
|
|
73
|
+
"@orkestrel/emitter": "^0.0.13"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
|
-
"@microsoft/api-extractor": "^7.
|
|
77
|
-
"@orkestrel/guide": "^0.0.
|
|
78
|
-
"@orkestrel/probe": "^0.0.
|
|
79
|
-
"@orkestrel/scaffold": "^0.0.
|
|
80
|
-
"@orkestrel/test": "^0.0.
|
|
81
|
-
"@types/node": "^
|
|
82
|
-
"oxfmt": "^0.
|
|
83
|
-
"oxlint": "^1.
|
|
84
|
-
"typescript": "^
|
|
85
|
-
"vite": "^8.3.
|
|
86
|
-
"vitest": "^
|
|
76
|
+
"@microsoft/api-extractor": "^7.60.0",
|
|
77
|
+
"@orkestrel/guide": "^0.0.25",
|
|
78
|
+
"@orkestrel/probe": "^0.0.25",
|
|
79
|
+
"@orkestrel/scaffold": "^0.0.113",
|
|
80
|
+
"@orkestrel/test": "^0.0.31",
|
|
81
|
+
"@types/node": "^24.19.2",
|
|
82
|
+
"oxfmt": "^0.75.0",
|
|
83
|
+
"oxlint": "^1.90.0",
|
|
84
|
+
"typescript": "^7.0.2",
|
|
85
|
+
"vite": "^8.3.5",
|
|
86
|
+
"vitest": "^5.0.5"
|
|
87
87
|
},
|
|
88
88
|
"engines": {
|
|
89
|
-
"node": ">=22.
|
|
89
|
+
"node": ">=22.18.0"
|
|
90
90
|
}
|
|
91
91
|
}
|