@orkestrel/tool 0.0.3 → 0.0.5
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/README.md +1 -1
- package/dist/src/core/index.cjs +529 -81
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +349 -24
- package/dist/src/core/index.d.ts +349 -24
- package/dist/src/core/index.js +524 -83
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +225 -160
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +167 -3
- package/dist/src/server/index.d.ts +167 -3
- package/dist/src/server/index.js +225 -162
- package/dist/src/server/index.js.map +1 -1
- package/package.json +23 -21
- package/dist/src/server/constants.d.ts +0 -12
- package/dist/src/server/factories.d.ts +0 -44
- package/dist/src/server/types.d.ts +0 -60
package/dist/src/core/index.cjs
CHANGED
|
@@ -425,6 +425,108 @@ var RELATION_TOOL_DESCRIPTION = [
|
|
|
425
425
|
var RELATION_TOOL_LIMIT = 1e3;
|
|
426
426
|
/** The default cap on how many `include` path segments deep a `load` / `find` call may traverse — the relation tool's default include-depth ceiling. */
|
|
427
427
|
var RELATION_TOOL_DEPTH = 3;
|
|
428
|
+
/**
|
|
429
|
+
* The name {@link import('./factories.js').createInferTool} advertises by default — the key a
|
|
430
|
+
* model calls and the `ToolManagerInterface` (`@orkestrel/agent`) registers under.
|
|
431
|
+
*/
|
|
432
|
+
var INFER_TOOL_NAME = "infer";
|
|
433
|
+
/**
|
|
434
|
+
* The lean {@link import('@orkestrel/agent').ToolInterface.summary} {@link import('./factories.js').createInferTool}
|
|
435
|
+
* advertises in place of {@link INFER_TOOL_DESCRIPTION} — a `ToolManagerInterface.definitions()`
|
|
436
|
+
* (`@orkestrel/agent`) advertises `summary ?? description`, so this one-sentence text stands in
|
|
437
|
+
* for the full teaching description; the full text stays retrievable via
|
|
438
|
+
* {@link import('./factories.js').createDescribeTool}.
|
|
439
|
+
*/
|
|
440
|
+
var INFER_TOOL_SUMMARY = "Infer a JSON Schema (as advertised tool parameters) from one or more example values. Call describe('infer') for the required fields.";
|
|
441
|
+
var INFER_TOOL_DESCRIPTION = [
|
|
442
|
+
"Infer a JSON Schema from example values, returned in the same shape a tool advertises its parameters.",
|
|
443
|
+
"",
|
|
444
|
+
"Required:",
|
|
445
|
+
" samples - an array of at least one example value to infer the schema from.",
|
|
446
|
+
"Optional:",
|
|
447
|
+
" format - infer string formats (date-time, email, ...) from the samples. Defaults to false.",
|
|
448
|
+
" enum - infer enum constraints from repeated literal values across the samples. Defaults to false.",
|
|
449
|
+
" candidates - values to check against the freshly inferred schema. When present, the result",
|
|
450
|
+
" is wrapped as { parameters, checks } instead of the bare parameters record, one",
|
|
451
|
+
" check per candidate (same index). Every check has the uniform shape",
|
|
452
|
+
" { index, valid, coercible, faults? }. `valid` is a STRICT verdict (no coercion)",
|
|
453
|
+
" — e.g. the number 7 is NOT valid against a string slot. `coercible` answers a",
|
|
454
|
+
" separate question: would the SAME value be accepted by an endpoint tool call,",
|
|
455
|
+
" whose enforcement NORMALIZES args (7 coerces to '7')? So 7 against a string slot",
|
|
456
|
+
" yields { valid: false, coercible: true, faults: [] } — a strict mismatch that",
|
|
457
|
+
" normalization would silently accept, so faults is EMPTY. `faults` only ever",
|
|
458
|
+
" populates for a non-coercible mismatch (a wrong type normalization cannot fix,",
|
|
459
|
+
" a missing required key, an out-of-enum value); checks never throw, regardless of",
|
|
460
|
+
" candidate shape.",
|
|
461
|
+
"Example (no candidates):",
|
|
462
|
+
` in: ${JSON.stringify({ samples: [{
|
|
463
|
+
id: 1,
|
|
464
|
+
name: "Ada"
|
|
465
|
+
}, {
|
|
466
|
+
id: 2,
|
|
467
|
+
name: "Bob"
|
|
468
|
+
}] })}`,
|
|
469
|
+
` out: ${JSON.stringify({
|
|
470
|
+
type: "object",
|
|
471
|
+
properties: {
|
|
472
|
+
id: { type: "integer" },
|
|
473
|
+
name: { type: "string" }
|
|
474
|
+
},
|
|
475
|
+
required: ["id", "name"],
|
|
476
|
+
additionalProperties: false
|
|
477
|
+
})}`,
|
|
478
|
+
"Example (with candidates):",
|
|
479
|
+
` in: ${JSON.stringify({
|
|
480
|
+
samples: [{
|
|
481
|
+
id: 1,
|
|
482
|
+
name: "Ada"
|
|
483
|
+
}],
|
|
484
|
+
candidates: [
|
|
485
|
+
{
|
|
486
|
+
id: 3,
|
|
487
|
+
name: "Cy"
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
id: "x",
|
|
491
|
+
name: "Cy"
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
id: 1,
|
|
495
|
+
name: 7
|
|
496
|
+
}
|
|
497
|
+
]
|
|
498
|
+
})}`,
|
|
499
|
+
` out: ${JSON.stringify({
|
|
500
|
+
parameters: {
|
|
501
|
+
type: "object",
|
|
502
|
+
properties: {
|
|
503
|
+
id: { type: "integer" },
|
|
504
|
+
name: { type: "string" }
|
|
505
|
+
},
|
|
506
|
+
required: ["id", "name"],
|
|
507
|
+
additionalProperties: false
|
|
508
|
+
},
|
|
509
|
+
checks: [
|
|
510
|
+
{
|
|
511
|
+
index: 0,
|
|
512
|
+
valid: true,
|
|
513
|
+
coercible: true
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
index: 1,
|
|
517
|
+
valid: false,
|
|
518
|
+
coercible: false,
|
|
519
|
+
faults: "<structured faults>"
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
index: 2,
|
|
523
|
+
valid: false,
|
|
524
|
+
coercible: true,
|
|
525
|
+
faults: []
|
|
526
|
+
}
|
|
527
|
+
]
|
|
528
|
+
})}`
|
|
529
|
+
].join("\n");
|
|
428
530
|
//#endregion
|
|
429
531
|
//#region src/core/errors.ts
|
|
430
532
|
/**
|
|
@@ -458,12 +560,11 @@ var RELATION_TOOL_DEPTH = 3;
|
|
|
458
560
|
*/
|
|
459
561
|
var AgentToolError = class extends Error {
|
|
460
562
|
code;
|
|
461
|
-
context;
|
|
462
563
|
constructor(code, message, context) {
|
|
463
564
|
super(message);
|
|
464
565
|
this.name = "AgentToolError";
|
|
465
566
|
this.code = code;
|
|
466
|
-
this.context = context;
|
|
567
|
+
if (context !== void 0) this.context = context;
|
|
467
568
|
}
|
|
468
569
|
};
|
|
469
570
|
/**
|
|
@@ -1106,6 +1207,28 @@ var relationToolShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contr
|
|
|
1106
1207
|
description: "The \"through\" relation name."
|
|
1107
1208
|
})
|
|
1108
1209
|
}));
|
|
1210
|
+
/**
|
|
1211
|
+
* The shape of {@link import('./factories.js').createInferTool}'s call arguments — one or more
|
|
1212
|
+
* example `samples` to infer a JSON Schema from, plus per-call `format` / `enum` toggles and an
|
|
1213
|
+
* optional `candidates` array to check against the inferred schema.
|
|
1214
|
+
*
|
|
1215
|
+
* @remarks
|
|
1216
|
+
* `samples` requires at least one element (`min: 1`) — an empty array parses to `undefined`,
|
|
1217
|
+
* surfaced by the handler as a typed `TOOL` {@link import('./errors.js').AgentToolError}. When
|
|
1218
|
+
* `candidates` is present (any array, including empty), the handler compiles a contract from the
|
|
1219
|
+
* freshly inferred schema and checks each candidate against it with a STRICT guard (`.is`, no
|
|
1220
|
+
* coercion) — the opposite of {@link import('./factories.js').createEndpointTool}'s NORMALIZING
|
|
1221
|
+
* `.parse` enforcement.
|
|
1222
|
+
*/
|
|
1223
|
+
var inferToolShape = (0, _orkestrel_contract.objectShape)({
|
|
1224
|
+
samples: (0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.jsonShape)(), {
|
|
1225
|
+
min: 1,
|
|
1226
|
+
description: "The example values to infer a JSON Schema from (at least one)."
|
|
1227
|
+
}),
|
|
1228
|
+
format: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Infer string formats (date-time, email, ...) from the samples. Defaults to false." })),
|
|
1229
|
+
enum: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Infer enum constraints from repeated literal values. Defaults to false." })),
|
|
1230
|
+
candidates: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.jsonShape)(), { description: "Optional values to check against the freshly inferred schema. When present, the tool returns a per-candidate verdict (strict — no coercion) alongside the inferred parameters." }))
|
|
1231
|
+
});
|
|
1109
1232
|
//#endregion
|
|
1110
1233
|
//#region src/core/helpers.ts
|
|
1111
1234
|
/**
|
|
@@ -1420,19 +1543,6 @@ function relationToolCode(error) {
|
|
|
1420
1543
|
* ```
|
|
1421
1544
|
*/
|
|
1422
1545
|
function expandInclude(paths, depth) {
|
|
1423
|
-
function merge(base, segments) {
|
|
1424
|
-
const [head, ...rest] = segments;
|
|
1425
|
-
const existing = base[head];
|
|
1426
|
-
if (rest.length === 0) return {
|
|
1427
|
-
...base,
|
|
1428
|
-
[head]: existing === void 0 ? true : existing
|
|
1429
|
-
};
|
|
1430
|
-
const nextBase = typeof existing === "object" ? existing : {};
|
|
1431
|
-
return {
|
|
1432
|
-
...base,
|
|
1433
|
-
[head]: merge(nextBase, rest)
|
|
1434
|
-
};
|
|
1435
|
-
}
|
|
1436
1546
|
let include = {};
|
|
1437
1547
|
for (const path of paths ?? []) {
|
|
1438
1548
|
const segments = path.split(".");
|
|
@@ -1440,7 +1550,42 @@ function expandInclude(paths, depth) {
|
|
|
1440
1550
|
path,
|
|
1441
1551
|
depth
|
|
1442
1552
|
});
|
|
1443
|
-
|
|
1553
|
+
const ancestors = [];
|
|
1554
|
+
let branch = include;
|
|
1555
|
+
const last = segments.length - 1;
|
|
1556
|
+
for (let index = 0; index < last; index++) {
|
|
1557
|
+
const segment = segments[index];
|
|
1558
|
+
if (segment === void 0) throw new AgentToolError("TOOL", `malformed include path '${path}'`, {
|
|
1559
|
+
path,
|
|
1560
|
+
depth
|
|
1561
|
+
});
|
|
1562
|
+
ancestors.push(branch);
|
|
1563
|
+
const existing = branch[segment];
|
|
1564
|
+
branch = typeof existing === "object" ? existing : {};
|
|
1565
|
+
}
|
|
1566
|
+
const leaf = segments[last];
|
|
1567
|
+
if (leaf === void 0) throw new AgentToolError("TOOL", `malformed include path '${path}'`, {
|
|
1568
|
+
path,
|
|
1569
|
+
depth
|
|
1570
|
+
});
|
|
1571
|
+
const existing = branch[leaf];
|
|
1572
|
+
let merged = {
|
|
1573
|
+
...branch,
|
|
1574
|
+
[leaf]: existing === void 0 ? true : existing
|
|
1575
|
+
};
|
|
1576
|
+
for (let index = last - 1; index >= 0; index--) {
|
|
1577
|
+
const ancestor = ancestors[index];
|
|
1578
|
+
const segment = segments[index];
|
|
1579
|
+
if (ancestor === void 0 || segment === void 0) throw new AgentToolError("TOOL", `malformed include path '${path}'`, {
|
|
1580
|
+
path,
|
|
1581
|
+
depth
|
|
1582
|
+
});
|
|
1583
|
+
merged = {
|
|
1584
|
+
...ancestor,
|
|
1585
|
+
[segment]: merged
|
|
1586
|
+
};
|
|
1587
|
+
}
|
|
1588
|
+
include = merged;
|
|
1444
1589
|
}
|
|
1445
1590
|
return include;
|
|
1446
1591
|
}
|
|
@@ -1468,7 +1613,11 @@ function relationManagerOf(managers, name) {
|
|
|
1468
1613
|
return manager;
|
|
1469
1614
|
}
|
|
1470
1615
|
const names = Object.keys(managers);
|
|
1471
|
-
|
|
1616
|
+
const [single] = names;
|
|
1617
|
+
if (names.length === 1 && single !== void 0) {
|
|
1618
|
+
const manager = managers[single];
|
|
1619
|
+
if (manager !== void 0) return manager;
|
|
1620
|
+
}
|
|
1472
1621
|
throw new AgentToolError("TOOL", "no relation manager resolved for the call", { managers: names });
|
|
1473
1622
|
}
|
|
1474
1623
|
/**
|
|
@@ -1696,6 +1845,105 @@ var DatabaseDefinitionStore = class {
|
|
|
1696
1845
|
}
|
|
1697
1846
|
};
|
|
1698
1847
|
//#endregion
|
|
1848
|
+
//#region src/core/databases/DatabaseResolver.ts
|
|
1849
|
+
/**
|
|
1850
|
+
* Resolve database definitions into cached live handles for database tools.
|
|
1851
|
+
*
|
|
1852
|
+
* @example
|
|
1853
|
+
* ```ts
|
|
1854
|
+
* import { DatabaseResolver } from '@orkestrel/tool'
|
|
1855
|
+
*
|
|
1856
|
+
* const resolver = new DatabaseResolver(handles, drivers, key, store)
|
|
1857
|
+
* const database = await resolver.resolve('shop')
|
|
1858
|
+
* ```
|
|
1859
|
+
*/
|
|
1860
|
+
var DatabaseResolver = class {
|
|
1861
|
+
#handles;
|
|
1862
|
+
#drivers;
|
|
1863
|
+
#key;
|
|
1864
|
+
#store;
|
|
1865
|
+
/**
|
|
1866
|
+
* Create a database resolver over the tool's live state and optional definition store.
|
|
1867
|
+
*
|
|
1868
|
+
* @param handles - Initial live database handles cached by id
|
|
1869
|
+
* @param drivers - Driver factories keyed by definition driver name
|
|
1870
|
+
* @param key - Key generator supplied to newly created databases
|
|
1871
|
+
* @param store - Optional persistent definition store
|
|
1872
|
+
*/
|
|
1873
|
+
constructor(handles, drivers, key, store) {
|
|
1874
|
+
this.#handles = new Map(handles);
|
|
1875
|
+
this.#drivers = drivers;
|
|
1876
|
+
this.#key = key;
|
|
1877
|
+
this.#store = store;
|
|
1878
|
+
}
|
|
1879
|
+
/**
|
|
1880
|
+
* Determine whether a live database is cached by id.
|
|
1881
|
+
*
|
|
1882
|
+
* @param id - Database id
|
|
1883
|
+
* @returns Whether a live handle is cached
|
|
1884
|
+
*/
|
|
1885
|
+
has(id) {
|
|
1886
|
+
return this.#handles.has(id);
|
|
1887
|
+
}
|
|
1888
|
+
/**
|
|
1889
|
+
* Read a cached database without consulting the definition store.
|
|
1890
|
+
*
|
|
1891
|
+
* @param id - Database id
|
|
1892
|
+
* @returns The cached live database, or `undefined`
|
|
1893
|
+
*/
|
|
1894
|
+
get(id) {
|
|
1895
|
+
return this.#handles.get(id);
|
|
1896
|
+
}
|
|
1897
|
+
/**
|
|
1898
|
+
* Cache a live database by id.
|
|
1899
|
+
*
|
|
1900
|
+
* @param id - Database id
|
|
1901
|
+
* @param database - Live database handle
|
|
1902
|
+
* @returns Nothing
|
|
1903
|
+
*/
|
|
1904
|
+
set(id, database) {
|
|
1905
|
+
this.#handles.set(id, database);
|
|
1906
|
+
}
|
|
1907
|
+
/**
|
|
1908
|
+
* Remove a cached live database by id.
|
|
1909
|
+
*
|
|
1910
|
+
* @param id - Database id
|
|
1911
|
+
* @returns Nothing
|
|
1912
|
+
*/
|
|
1913
|
+
delete(id) {
|
|
1914
|
+
this.#handles.delete(id);
|
|
1915
|
+
}
|
|
1916
|
+
/**
|
|
1917
|
+
* Resolve a cached or stored database by id.
|
|
1918
|
+
*
|
|
1919
|
+
* @param id - Database definition id
|
|
1920
|
+
* @returns The cached or newly constructed live database
|
|
1921
|
+
*/
|
|
1922
|
+
async resolve(id) {
|
|
1923
|
+
const cached = this.#handles.get(id);
|
|
1924
|
+
if (cached !== void 0) return cached;
|
|
1925
|
+
if (this.#store !== void 0) {
|
|
1926
|
+
const definition = await this.#store.get(id);
|
|
1927
|
+
if (definition !== void 0) {
|
|
1928
|
+
const factory = this.#drivers[definition.driver];
|
|
1929
|
+
if (factory === void 0) throw new AgentToolError("TOOL", `unknown driver '${definition.driver}'`, {
|
|
1930
|
+
id,
|
|
1931
|
+
driver: definition.driver
|
|
1932
|
+
});
|
|
1933
|
+
const handle = (0, _orkestrel_database.createDatabase)({
|
|
1934
|
+
driver: factory(),
|
|
1935
|
+
tables: expandTables(definition.tables),
|
|
1936
|
+
...definition.keys === void 0 ? {} : { keys: definition.keys },
|
|
1937
|
+
key: this.#key
|
|
1938
|
+
});
|
|
1939
|
+
this.set(id, handle);
|
|
1940
|
+
return handle;
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
throw new AgentToolError("TOOL", `unknown database '${id}'`, { id });
|
|
1944
|
+
}
|
|
1945
|
+
};
|
|
1946
|
+
//#endregion
|
|
1699
1947
|
//#region src/core/factories.ts
|
|
1700
1948
|
/**
|
|
1701
1949
|
* Wrap a registered tool as a {@link WorkflowFunction} (`@orkestrel/workflow`) — the OPT-IN
|
|
@@ -1811,7 +2059,9 @@ function createAgentFunction(agent, options) {
|
|
|
1811
2059
|
}));
|
|
1812
2060
|
}
|
|
1813
2061
|
const signal = controller.signal;
|
|
1814
|
-
const onAbort = ()
|
|
2062
|
+
const onAbort = { handleEvent() {
|
|
2063
|
+
agent.abort(signal.reason);
|
|
2064
|
+
} };
|
|
1815
2065
|
if (signal.aborted) agent.abort(signal.reason);
|
|
1816
2066
|
else signal.addEventListener("abort", onAbort, { once: true });
|
|
1817
2067
|
try {
|
|
@@ -1916,12 +2166,13 @@ function createWorkflowTool(definition, runner, options) {
|
|
|
1916
2166
|
const depth = options?.depth ?? 0;
|
|
1917
2167
|
const ancestry = options?.ancestry ?? [];
|
|
1918
2168
|
const store = options?.store;
|
|
2169
|
+
const parameters = (0, _orkestrel_contract.schemaToParameters)(steps.schema);
|
|
1919
2170
|
return (0, _orkestrel_agent.createTool)({
|
|
1920
2171
|
name: WORKFLOW_TOOL_NAME,
|
|
1921
2172
|
description: WORKFLOW_TOOL_DESCRIPTION,
|
|
1922
2173
|
summary: WORKFLOW_TOOL_SUMMARY,
|
|
1923
|
-
parameters
|
|
1924
|
-
|
|
2174
|
+
...parameters === void 0 ? {} : { parameters },
|
|
2175
|
+
async execute(args) {
|
|
1925
2176
|
let target;
|
|
1926
2177
|
if (Object.keys(args).length === 0) target = definition;
|
|
1927
2178
|
else if (Array.isArray(args.steps)) {
|
|
@@ -2005,8 +2256,8 @@ function createWorkspaceTool(options) {
|
|
|
2005
2256
|
name: options?.name ?? "workspace",
|
|
2006
2257
|
description: options?.description ?? WORKSPACE_TOOL_DESCRIPTION,
|
|
2007
2258
|
summary: WORKSPACE_TOOL_SUMMARY,
|
|
2008
|
-
parameters,
|
|
2009
|
-
execute
|
|
2259
|
+
...parameters === void 0 ? {} : { parameters },
|
|
2260
|
+
execute(args) {
|
|
2010
2261
|
const op = contract.parse(args);
|
|
2011
2262
|
if (op === void 0) throw new _orkestrel_agent.WorkspaceError("TOOL", `unknown or malformed operation`, { args });
|
|
2012
2263
|
if (op.operation === "workspaces") {
|
|
@@ -2040,14 +2291,14 @@ function createWorkspaceTool(options) {
|
|
|
2040
2291
|
}));
|
|
2041
2292
|
case "has": return active?.has(op.path) ?? false;
|
|
2042
2293
|
case "search": return active?.search(op.query, {
|
|
2043
|
-
regex: op.regex,
|
|
2044
|
-
exact: op.exact,
|
|
2045
|
-
limit: op.limit
|
|
2294
|
+
...op.regex === void 0 ? {} : { regex: op.regex },
|
|
2295
|
+
...op.exact === void 0 ? {} : { exact: op.exact },
|
|
2296
|
+
...op.limit === void 0 ? {} : { limit: op.limit }
|
|
2046
2297
|
}) ?? [];
|
|
2047
2298
|
case "replace": return (active ?? manager.add()).replace(op.query, op.replacement, {
|
|
2048
|
-
regex: op.regex,
|
|
2049
|
-
exact: op.exact,
|
|
2050
|
-
limit: op.limit
|
|
2299
|
+
...op.regex === void 0 ? {} : { regex: op.regex },
|
|
2300
|
+
...op.exact === void 0 ? {} : { exact: op.exact },
|
|
2301
|
+
...op.limit === void 0 ? {} : { limit: op.limit }
|
|
2051
2302
|
});
|
|
2052
2303
|
case "write": {
|
|
2053
2304
|
const workspace = active ?? manager.add();
|
|
@@ -2147,8 +2398,8 @@ function createAgentTool(registry, options) {
|
|
|
2147
2398
|
name: options?.name ?? "agent",
|
|
2148
2399
|
description: options?.description ?? AGENT_TOOL_DESCRIPTION,
|
|
2149
2400
|
summary: AGENT_TOOL_SUMMARY,
|
|
2150
|
-
parameters,
|
|
2151
|
-
|
|
2401
|
+
...parameters === void 0 ? {} : { parameters },
|
|
2402
|
+
async execute(args) {
|
|
2152
2403
|
const call = contract.parse(args);
|
|
2153
2404
|
if (call === void 0) throw new AgentToolError("TOOL", "malformed agent-delegation call", { args });
|
|
2154
2405
|
const provider = call.provider ?? options?.provider;
|
|
@@ -2219,12 +2470,13 @@ function createAgentTool(registry, options) {
|
|
|
2219
2470
|
*/
|
|
2220
2471
|
function createDescribeTool(tools) {
|
|
2221
2472
|
const contract = (0, _orkestrel_contract.createContract)(describeToolShape);
|
|
2473
|
+
const parameters = (0, _orkestrel_contract.schemaToParameters)(contract.schema);
|
|
2222
2474
|
return (0, _orkestrel_agent.createTool)({
|
|
2223
2475
|
name: DESCRIBE_TOOL_NAME,
|
|
2224
2476
|
description: DESCRIBE_TOOL_DESCRIPTION,
|
|
2225
2477
|
summary: DESCRIBE_TOOL_SUMMARY,
|
|
2226
|
-
parameters
|
|
2227
|
-
|
|
2478
|
+
...parameters === void 0 ? {} : { parameters },
|
|
2479
|
+
async execute(args) {
|
|
2228
2480
|
const call = contract.parse(args);
|
|
2229
2481
|
if (call === void 0) throw new AgentToolError("TOOL", "malformed describe call", { args });
|
|
2230
2482
|
const tool = tools.tool(call.name);
|
|
@@ -2274,8 +2526,8 @@ function createPromptTool(options) {
|
|
|
2274
2526
|
name: options.name ?? "ask",
|
|
2275
2527
|
description: options.description ?? PROMPT_TOOL_DESCRIPTION,
|
|
2276
2528
|
summary: PROMPT_TOOL_SUMMARY,
|
|
2277
|
-
parameters,
|
|
2278
|
-
|
|
2529
|
+
...parameters === void 0 ? {} : { parameters },
|
|
2530
|
+
async execute(args) {
|
|
2279
2531
|
const call = contract.parse(args);
|
|
2280
2532
|
if (call === void 0) throw new AgentToolError("TOOL", "malformed ask call", { args });
|
|
2281
2533
|
if ((call.form === "select" || call.form === "checkbox") && (call.choices ?? []).length === 0) throw new AgentToolError("TOOL", "select/checkbox requires at least one choice", {
|
|
@@ -2376,8 +2628,8 @@ function createAnswerTool(options) {
|
|
|
2376
2628
|
name: options.name ?? "answer",
|
|
2377
2629
|
description: options.description ?? ANSWER_TOOL_DESCRIPTION,
|
|
2378
2630
|
summary: ANSWER_TOOL_SUMMARY,
|
|
2379
|
-
parameters,
|
|
2380
|
-
|
|
2631
|
+
...parameters === void 0 ? {} : { parameters },
|
|
2632
|
+
async execute(args) {
|
|
2381
2633
|
const call = contract.parse(args);
|
|
2382
2634
|
if (call === void 0) throw new AgentToolError("TOOL", "malformed answer call", { args });
|
|
2383
2635
|
if (call.operation === "pending") return options.manager.pending(options.to).map((prompt) => ({
|
|
@@ -2509,40 +2761,17 @@ function createDatabaseTool(options = {}) {
|
|
|
2509
2761
|
const parameters = (0, _orkestrel_contract.schemaToParameters)(contract.schema);
|
|
2510
2762
|
const handles = new Map(Object.entries(options.databases ?? {}));
|
|
2511
2763
|
const definitions = /* @__PURE__ */ new Map();
|
|
2512
|
-
const drivers = options.drivers ?? { memory:
|
|
2764
|
+
const drivers = options.drivers ?? { memory: _orkestrel_database.createMemoryDriver };
|
|
2513
2765
|
const key = options.key ?? _orkestrel_database.generateUUID;
|
|
2514
2766
|
const cap = options.limit ?? 1e3;
|
|
2515
2767
|
const store = options.store;
|
|
2516
|
-
|
|
2517
|
-
const cached = handles.get(id);
|
|
2518
|
-
if (cached !== void 0) return cached;
|
|
2519
|
-
if (store !== void 0) {
|
|
2520
|
-
const definition = await store.get(id);
|
|
2521
|
-
if (definition !== void 0) {
|
|
2522
|
-
const factory = drivers[definition.driver];
|
|
2523
|
-
if (factory === void 0) throw new AgentToolError("TOOL", `unknown driver '${definition.driver}'`, {
|
|
2524
|
-
id,
|
|
2525
|
-
driver: definition.driver
|
|
2526
|
-
});
|
|
2527
|
-
const handle = (0, _orkestrel_database.createDatabase)({
|
|
2528
|
-
driver: factory(),
|
|
2529
|
-
tables: expandTables(definition.tables),
|
|
2530
|
-
...definition.keys === void 0 ? {} : { keys: definition.keys },
|
|
2531
|
-
key
|
|
2532
|
-
});
|
|
2533
|
-
handles.set(id, handle);
|
|
2534
|
-
definitions.set(id, definition);
|
|
2535
|
-
return handle;
|
|
2536
|
-
}
|
|
2537
|
-
}
|
|
2538
|
-
throw new AgentToolError("TOOL", `unknown database '${id}'`, { id });
|
|
2539
|
-
}
|
|
2768
|
+
const resolver = store === void 0 ? new DatabaseResolver(handles, drivers, key) : new DatabaseResolver(handles, drivers, key, store);
|
|
2540
2769
|
return (0, _orkestrel_agent.createTool)({
|
|
2541
2770
|
name: options.name ?? "database",
|
|
2542
2771
|
description: options.description ?? DATABASE_TOOL_DESCRIPTION,
|
|
2543
2772
|
summary: DATABASE_TOOL_SUMMARY,
|
|
2544
|
-
parameters,
|
|
2545
|
-
|
|
2773
|
+
...parameters === void 0 ? {} : { parameters },
|
|
2774
|
+
async execute(args) {
|
|
2546
2775
|
const call = contract.parse(args);
|
|
2547
2776
|
if (call === void 0) throw new AgentToolError("TOOL", "malformed database call", { args });
|
|
2548
2777
|
if (options.readonly === true && DATABASE_TOOL_MUTATIONS.has(call.operation)) throw new AgentToolError("TOOL", `operation '${call.operation}' is disabled in readonly mode`, { operation: call.operation });
|
|
@@ -2550,7 +2779,7 @@ function createDatabaseTool(options = {}) {
|
|
|
2550
2779
|
try {
|
|
2551
2780
|
switch (call.operation) {
|
|
2552
2781
|
case "create": {
|
|
2553
|
-
if (
|
|
2782
|
+
if (resolver.has(call.id) || store !== void 0 && await store.get(call.id) !== void 0) throw new AgentToolError("TOOL", `database '${call.id}' already exists`, { id: call.id });
|
|
2554
2783
|
const name = call.driver ?? "memory";
|
|
2555
2784
|
const factory = drivers[name];
|
|
2556
2785
|
if (factory === void 0) throw new AgentToolError("TOOL", `unknown driver '${name}'`, {
|
|
@@ -2565,7 +2794,7 @@ function createDatabaseTool(options = {}) {
|
|
|
2565
2794
|
...keys === void 0 ? {} : { keys },
|
|
2566
2795
|
key
|
|
2567
2796
|
});
|
|
2568
|
-
|
|
2797
|
+
resolver.set(call.id, handle);
|
|
2569
2798
|
const definition = {
|
|
2570
2799
|
id: call.id,
|
|
2571
2800
|
driver: name,
|
|
@@ -2580,7 +2809,7 @@ function createDatabaseTool(options = {}) {
|
|
|
2580
2809
|
};
|
|
2581
2810
|
}
|
|
2582
2811
|
case "tables": {
|
|
2583
|
-
const handle = await resolve(call.id);
|
|
2812
|
+
const handle = await resolver.resolve(call.id);
|
|
2584
2813
|
return { tables: Object.keys(handle.export()).map((name) => {
|
|
2585
2814
|
const table = handle.table(name);
|
|
2586
2815
|
return {
|
|
@@ -2591,14 +2820,14 @@ function createDatabaseTool(options = {}) {
|
|
|
2591
2820
|
}) };
|
|
2592
2821
|
}
|
|
2593
2822
|
case "get": {
|
|
2594
|
-
const table = (await resolve(call.id)).table(call.table);
|
|
2823
|
+
const table = (await resolver.resolve(call.id)).table(call.table);
|
|
2595
2824
|
const many = Array.isArray(call.key);
|
|
2596
2825
|
const keys = Array.isArray(call.key) ? call.key : [call.key];
|
|
2597
2826
|
const rows = await table.get(keys);
|
|
2598
2827
|
return many ? { rows } : { row: rows[0] };
|
|
2599
2828
|
}
|
|
2600
2829
|
case "records": {
|
|
2601
|
-
const table = (await resolve(call.id)).table(call.table);
|
|
2830
|
+
const table = (await resolver.resolve(call.id)).table(call.table);
|
|
2602
2831
|
const { criteria: probe, limit } = clampCriteria(criteriaOf(call.criteria), cap);
|
|
2603
2832
|
const rows = await table.records(probe, read);
|
|
2604
2833
|
const truncated = rows.length > limit;
|
|
@@ -2610,24 +2839,24 @@ function createDatabaseTool(options = {}) {
|
|
|
2610
2839
|
limit
|
|
2611
2840
|
};
|
|
2612
2841
|
}
|
|
2613
|
-
case "count": return { count: await (await resolve(call.id)).table(call.table).count(criteriaOf(call.criteria), read) };
|
|
2614
|
-
case "aggregate": return { value: await (await resolve(call.id)).table(call.table).aggregate(call.function, call.column, criteriaOf(call.criteria), read) };
|
|
2842
|
+
case "count": return { count: await (await resolver.resolve(call.id)).table(call.table).count(criteriaOf(call.criteria), read) };
|
|
2843
|
+
case "aggregate": return { value: await (await resolver.resolve(call.id)).table(call.table).aggregate(call.function, call.column, criteriaOf(call.criteria), read) };
|
|
2615
2844
|
case "add": {
|
|
2616
|
-
const table = (await resolve(call.id)).table(call.table);
|
|
2845
|
+
const table = (await resolver.resolve(call.id)).table(call.table);
|
|
2617
2846
|
const many = Array.isArray(call.row);
|
|
2618
2847
|
const rows = Array.isArray(call.row) ? call.row : [call.row];
|
|
2619
2848
|
const keys = await table.add(rows, read);
|
|
2620
2849
|
return many ? { keys } : { key: keys[0] };
|
|
2621
2850
|
}
|
|
2622
2851
|
case "set": {
|
|
2623
|
-
const table = (await resolve(call.id)).table(call.table);
|
|
2852
|
+
const table = (await resolver.resolve(call.id)).table(call.table);
|
|
2624
2853
|
const many = Array.isArray(call.row);
|
|
2625
2854
|
const rows = Array.isArray(call.row) ? call.row : [call.row];
|
|
2626
2855
|
const keys = await table.set(rows, read);
|
|
2627
2856
|
return many ? { keys } : { key: keys[0] };
|
|
2628
2857
|
}
|
|
2629
2858
|
case "update": {
|
|
2630
|
-
const table = (await resolve(call.id)).table(call.table);
|
|
2859
|
+
const table = (await resolver.resolve(call.id)).table(call.table);
|
|
2631
2860
|
const changes = call.changes;
|
|
2632
2861
|
const many = Array.isArray(call.key);
|
|
2633
2862
|
const keys = Array.isArray(call.key) ? call.key : [call.key];
|
|
@@ -2635,14 +2864,14 @@ function createDatabaseTool(options = {}) {
|
|
|
2635
2864
|
return many ? { updated } : { updated: updated[0] };
|
|
2636
2865
|
}
|
|
2637
2866
|
case "remove": {
|
|
2638
|
-
const table = (await resolve(call.id)).table(call.table);
|
|
2867
|
+
const table = (await resolver.resolve(call.id)).table(call.table);
|
|
2639
2868
|
const many = Array.isArray(call.key);
|
|
2640
2869
|
const keys = Array.isArray(call.key) ? call.key : [call.key];
|
|
2641
2870
|
const removed = await table.remove(keys, read);
|
|
2642
2871
|
return many ? { removed } : { removed: removed[0] };
|
|
2643
2872
|
}
|
|
2644
2873
|
case "migrate": {
|
|
2645
|
-
const handle = await resolve(call.id);
|
|
2874
|
+
const handle = await resolver.resolve(call.id);
|
|
2646
2875
|
const previous = handle.export();
|
|
2647
2876
|
const deployed = Object.entries(previous).map(([name, table]) => tableSchema(name, table));
|
|
2648
2877
|
const tables = call.tables;
|
|
@@ -2654,8 +2883,8 @@ function createDatabaseTool(options = {}) {
|
|
|
2654
2883
|
const declared = expandTables(tables);
|
|
2655
2884
|
const migrated = handle.import(declared, Object.keys(keys).length > 0 ? keys : void 0);
|
|
2656
2885
|
const migration = await migrated.migrate(deployed, read);
|
|
2657
|
-
|
|
2658
|
-
const tracked = definitions.get(call.id);
|
|
2886
|
+
resolver.set(call.id, migrated);
|
|
2887
|
+
const tracked = definitions.get(call.id) ?? (store === void 0 ? void 0 : await store.get(call.id));
|
|
2659
2888
|
if (tracked !== void 0) {
|
|
2660
2889
|
const updated = {
|
|
2661
2890
|
id: call.id,
|
|
@@ -2669,11 +2898,11 @@ function createDatabaseTool(options = {}) {
|
|
|
2669
2898
|
return { migration };
|
|
2670
2899
|
}
|
|
2671
2900
|
case "destroy": {
|
|
2672
|
-
const cached =
|
|
2901
|
+
const cached = resolver.get(call.id);
|
|
2673
2902
|
const persisted = store !== void 0 && cached === void 0 ? await store.get(call.id) !== void 0 : false;
|
|
2674
2903
|
if (cached !== void 0) {
|
|
2675
2904
|
await cached.close();
|
|
2676
|
-
|
|
2905
|
+
resolver.delete(call.id);
|
|
2677
2906
|
}
|
|
2678
2907
|
definitions.delete(call.id);
|
|
2679
2908
|
if (store !== void 0) await store.delete(call.id);
|
|
@@ -2753,8 +2982,8 @@ function createRelationTool(options) {
|
|
|
2753
2982
|
name: options.name ?? "relation",
|
|
2754
2983
|
description: options.description ?? RELATION_TOOL_DESCRIPTION,
|
|
2755
2984
|
summary: RELATION_TOOL_SUMMARY,
|
|
2756
|
-
parameters,
|
|
2757
|
-
|
|
2985
|
+
...parameters === void 0 ? {} : { parameters },
|
|
2986
|
+
async execute(args) {
|
|
2758
2987
|
const call = contract.parse(args);
|
|
2759
2988
|
if (call === void 0) throw new AgentToolError("TOOL", "malformed relation call", { args });
|
|
2760
2989
|
try {
|
|
@@ -2820,6 +3049,218 @@ function createRelationTool(options) {
|
|
|
2820
3049
|
}
|
|
2821
3050
|
});
|
|
2822
3051
|
}
|
|
3052
|
+
/**
|
|
3053
|
+
* Build a standalone LLM-callable tool that infers a JSON Schema from example values — the
|
|
3054
|
+
* utility half of the "existing API/DB → MCP tool" bridge (the other half,
|
|
3055
|
+
* {@link createEndpointTool}, wraps one CONCRETE endpoint).
|
|
3056
|
+
*
|
|
3057
|
+
* @remarks
|
|
3058
|
+
* The universal tool-handler contract (AGENTS §14): validates the call args against
|
|
3059
|
+
* {@link import('./shapers.js').inferToolShape} (`samples` non-empty, `format` / `enum` optional
|
|
3060
|
+
* booleans, `candidates` an optional array), infers a schema via `@orkestrel/contract`'s
|
|
3061
|
+
* `samplesToSchema`, wraps a non-object root as `{ value: <schema> }` via `schemaToObject` (mirrors
|
|
3062
|
+
* the tool-parameters convention every other `create*Tool` factory advertises), and RETURNS the
|
|
3063
|
+
* resulting parameters record. An empty `samples` array fails `inferToolShape`'s `min: 1` bound —
|
|
3064
|
+
* `contract.parse` returns `undefined` and the handler throws a typed `TOOL`
|
|
3065
|
+
* {@link import('./errors.js').AgentToolError}.
|
|
3066
|
+
*
|
|
3067
|
+
* When `candidates` is ABSENT, the return is the bare parameters record — unchanged from before
|
|
3068
|
+
* this array existed. When `candidates` is PRESENT (any array, including empty), the handler
|
|
3069
|
+
* compiles a SEPARATE per-call contract from the RAW inferred schema (via `@orkestrel/contract`'s
|
|
3070
|
+
* `schemaToShape`, NOT the `schemaToObject`-wrapped parameters — a bare-value sample checks a
|
|
3071
|
+
* bare-value candidate) and returns `{ parameters, checks }`, one check per candidate at the same
|
|
3072
|
+
* index. Every entry has a UNIFORM shape — `{ index, valid, coercible }`, with `faults` added ONLY
|
|
3073
|
+
* when `valid` is `false`: `valid` is the STRICT guard verdict (`checker.is(candidate)`), the
|
|
3074
|
+
* OPPOSITE of {@link createEndpointTool}'s enforcement, which coerces (`7` becomes `'7'` for a
|
|
3075
|
+
* string slot) — here a conformance report answers "does this value conform AS-IS": `7` against a
|
|
3076
|
+
* string slot is `valid: false`, full stop. `coercible` answers a SEPARATE question — "would the
|
|
3077
|
+
* NORMALIZING parse accept this value", i.e. would {@link createEndpointTool}'s default enforcement
|
|
3078
|
+
* admit it (`checker.parse(candidate) !== undefined`) — computed for every candidate regardless of
|
|
3079
|
+
* `valid`; by the house parse/guard round-trip guarantee (AGENTS §14), a `valid: true` entry is
|
|
3080
|
+
* ALWAYS also `coercible: true`. `@orkestrel/contract` 0.0.7's `explain` mirrors the normalizing
|
|
3081
|
+
* `parse`'s leniency, not `is`'s strictness — so a strictly-invalid but coercible candidate (`7`
|
|
3082
|
+
* against a string slot) yields `{ valid: false, coercible: true, faults: [] }`: EMPTY faults, since
|
|
3083
|
+
* the mismatch the normalizing parse would silently fix is not one `explain` reports. `faults`
|
|
3084
|
+
* therefore only ever populates for a NON-coercible mismatch — a wrong type the parse can't coerce
|
|
3085
|
+
* (a boolean in a string slot), a missing required key, or an out-of-enum value — where
|
|
3086
|
+
* `coercible: false`. `checker.is` / `.parse` / `.explain` are all total over JSON-safe input — a
|
|
3087
|
+
* JSON-safe hostile candidate (a `__proto__`-carrying object, deeply nested data) reaches all three
|
|
3088
|
+
* and yields a bounded, non-throwing per-candidate verdict; a NON-JSON-safe candidate (e.g. a
|
|
3089
|
+
* throwing-getter `Proxy`) never reaches the checker at all — it fails the OUTER `args` parse
|
|
3090
|
+
* against {@link import('./shapers.js').inferToolShape} and rejects the WHOLE call with the same
|
|
3091
|
+
* `TOOL` {@link import('./errors.js').AgentToolError} a malformed `samples`/`format`/`enum` throws,
|
|
3092
|
+
* with no per-candidate verdict produced.
|
|
3093
|
+
*
|
|
3094
|
+
* @param options - Advertised `name` / `description` overrides (see
|
|
3095
|
+
* {@link import('./types.js').InferToolOptions})
|
|
3096
|
+
* @returns A `ToolInterface` (named {@link import('./constants.js').INFER_TOOL_NAME} by default)
|
|
3097
|
+
*
|
|
3098
|
+
* @example
|
|
3099
|
+
* ```ts
|
|
3100
|
+
* import { createInferTool } from '@src/core'
|
|
3101
|
+
* import { createToolManager } from '@orkestrel/agent'
|
|
3102
|
+
*
|
|
3103
|
+
* const tool = createInferTool()
|
|
3104
|
+
* const tools = createToolManager()
|
|
3105
|
+
* tools.add(tool)
|
|
3106
|
+
*
|
|
3107
|
+
* const result = await tools.execute({
|
|
3108
|
+
* id: 'call-1',
|
|
3109
|
+
* name: 'infer',
|
|
3110
|
+
* arguments: { samples: [{ id: 1, name: 'Ada' }, { id: 2, name: 'Bob' }] },
|
|
3111
|
+
* })
|
|
3112
|
+
* // result.value -> { type: 'object', properties: { id: {...}, name: {...} }, ... }
|
|
3113
|
+
*
|
|
3114
|
+
* // with candidates, the result is wrapped with per-candidate verdicts
|
|
3115
|
+
* const checked = await tools.execute({
|
|
3116
|
+
* id: 'call-2',
|
|
3117
|
+
* name: 'infer',
|
|
3118
|
+
* arguments: {
|
|
3119
|
+
* samples: [{ id: 1, name: 'Ada' }],
|
|
3120
|
+
* candidates: [{ id: 2, name: 'Bob' }, { id: 'x', name: 'Cy' }],
|
|
3121
|
+
* },
|
|
3122
|
+
* })
|
|
3123
|
+
* // checked.value -> { parameters: {...}, checks: [
|
|
3124
|
+
* // { index: 0, valid: true, coercible: true },
|
|
3125
|
+
* // { index: 1, valid: false, coercible: false, faults: [...] },
|
|
3126
|
+
* // ] }
|
|
3127
|
+
* ```
|
|
3128
|
+
*/
|
|
3129
|
+
function createInferTool(options) {
|
|
3130
|
+
const contract = (0, _orkestrel_contract.createContract)(inferToolShape);
|
|
3131
|
+
const parameters = (0, _orkestrel_contract.schemaToParameters)(contract.schema);
|
|
3132
|
+
return (0, _orkestrel_agent.createTool)({
|
|
3133
|
+
name: options?.name ?? "infer",
|
|
3134
|
+
description: options?.description ?? INFER_TOOL_DESCRIPTION,
|
|
3135
|
+
summary: INFER_TOOL_SUMMARY,
|
|
3136
|
+
...parameters === void 0 ? {} : { parameters },
|
|
3137
|
+
async execute(args) {
|
|
3138
|
+
const parsed = contract.parse(args);
|
|
3139
|
+
if (parsed === void 0) throw new AgentToolError("TOOL", "malformed infer arguments", { args });
|
|
3140
|
+
const schema = (0, _orkestrel_contract.samplesToSchema)(parsed.samples, {
|
|
3141
|
+
format: parsed.format ?? false,
|
|
3142
|
+
enum: parsed.enum ?? false
|
|
3143
|
+
});
|
|
3144
|
+
const result = (0, _orkestrel_contract.schemaToParameters)((0, _orkestrel_contract.schemaToObject)(schema));
|
|
3145
|
+
if (result === void 0) throw new AgentToolError("TOOL", "could not infer a schema", { args });
|
|
3146
|
+
if (parsed.candidates === void 0) return result;
|
|
3147
|
+
const checker = (0, _orkestrel_contract.createContract)((0, _orkestrel_contract.schemaToShape)(schema));
|
|
3148
|
+
return {
|
|
3149
|
+
parameters: result,
|
|
3150
|
+
checks: parsed.candidates.map((candidate, index) => {
|
|
3151
|
+
const valid = checker.is(candidate);
|
|
3152
|
+
const coercible = checker.parse(candidate) !== void 0;
|
|
3153
|
+
return valid ? {
|
|
3154
|
+
index,
|
|
3155
|
+
valid,
|
|
3156
|
+
coercible
|
|
3157
|
+
} : {
|
|
3158
|
+
index,
|
|
3159
|
+
valid,
|
|
3160
|
+
coercible,
|
|
3161
|
+
faults: checker.explain(candidate)
|
|
3162
|
+
};
|
|
3163
|
+
})
|
|
3164
|
+
};
|
|
3165
|
+
}
|
|
3166
|
+
});
|
|
3167
|
+
}
|
|
3168
|
+
/**
|
|
3169
|
+
* Wrap one CONCRETE endpoint ({@link import('./types.js').EndpointDefinition}) as an LLM-callable
|
|
3170
|
+
* `ToolInterface` — the endpoint half of the "existing API/DB → MCP tool" bridge (the other half,
|
|
3171
|
+
* {@link createInferTool}, is a standalone inference utility).
|
|
3172
|
+
*
|
|
3173
|
+
* @remarks
|
|
3174
|
+
* `parameters` is inferred ONCE at construction from `definition.samples` via
|
|
3175
|
+
* `@orkestrel/contract`'s `samplesToSchema` (tuned by {@link import('./types.js').EndpointToolOptions}'s
|
|
3176
|
+
* `format` / `enum`), wrapping a non-object root as `{ value: <schema> }` via `schemaToObject` —
|
|
3177
|
+
* the SAME object-rooted schema is both the ADVERTISED `parameters` and, by default
|
|
3178
|
+
* ({@link import('./types.js').EndpointToolOptions.validate} `true`), the ENFORCED contract:
|
|
3179
|
+
* `@orkestrel/contract` 0.0.7's `schemaToShape` compiles it ONCE (via `createContract`) into a
|
|
3180
|
+
* `ContractInterface` whose `.parse` runs on every call's `args` before `definition.invoke` — a
|
|
3181
|
+
* NORMALIZING parse, not a strict type check: a scalar is COERCED to its inferred type where the
|
|
3182
|
+
* house parsers coerce (a number to/from a numeric string, a boolean from `'1'`/`'0'`/`'true'`/
|
|
3183
|
+
* `'false'`/`1`/`0`), so `definition.invoke` receives the COERCED value (e.g. `7` sent for a
|
|
3184
|
+
* string slot arrives as `'7'`), not the raw call value. A call whose `args` fails to parse into
|
|
3185
|
+
* a record — a required key missing, or a value not coercible to its slot's type — THROWS a
|
|
3186
|
+
* typed `TOOL` {@link import('./errors.js').AgentToolError} carrying the compiled contract's
|
|
3187
|
+
* structured `explain` faults, and `definition.invoke` is never called. `format` annotations are
|
|
3188
|
+
* NEVER asserted, and a key outside the closed inferred schema is SILENTLY DROPPED rather than
|
|
3189
|
+
* rejected (see {@link import('./types.js').EndpointToolOptions.validate}). With
|
|
3190
|
+
* `validate: false`, `execute` PASSES THROUGH the model-supplied `args` to `definition.invoke`
|
|
3191
|
+
* WITHOUT re-validation — the pre-0.0.7 behavior, preserved as an explicit opt-out. Either way,
|
|
3192
|
+
* `invoke`'s return flows back as the tool call's plain result; a throw PROPAGATES uncaught,
|
|
3193
|
+
* isolated by the `ToolManagerInterface` (`@orkestrel/agent`) into the canonical error envelope
|
|
3194
|
+
* (AGENTS §14) — never caught or re-wrapped here.
|
|
3195
|
+
*
|
|
3196
|
+
* @param definition - The endpoint's identity, non-empty samples, and local handler (see
|
|
3197
|
+
* {@link import('./types.js').EndpointDefinition})
|
|
3198
|
+
* @param options - Construction-time inference tuning + the validate opt-out (see
|
|
3199
|
+
* {@link import('./types.js').EndpointToolOptions})
|
|
3200
|
+
* @returns A `ToolInterface` named `definition.name`
|
|
3201
|
+
*
|
|
3202
|
+
* @example
|
|
3203
|
+
* ```ts
|
|
3204
|
+
* import { createEndpointTool } from '@src/core'
|
|
3205
|
+
* import { createToolManager } from '@orkestrel/agent'
|
|
3206
|
+
*
|
|
3207
|
+
* const tool = createEndpointTool({
|
|
3208
|
+
* name: 'lookupUser',
|
|
3209
|
+
* description: 'Look up a user by id.',
|
|
3210
|
+
* samples: [{ id: '1', name: 'Ada' }, { id: '2', name: 'Bob' }],
|
|
3211
|
+
* invoke: (args) => ({ id: args.id, name: 'Ada' }),
|
|
3212
|
+
* })
|
|
3213
|
+
* const tools = createToolManager()
|
|
3214
|
+
* tools.add(tool)
|
|
3215
|
+
*
|
|
3216
|
+
* // conforming args (all required keys present) parse and reach `invoke`
|
|
3217
|
+
* const result = await tools.execute({
|
|
3218
|
+
* id: 'call-1',
|
|
3219
|
+
* name: 'lookupUser',
|
|
3220
|
+
* arguments: { id: '1', name: 'Ada' },
|
|
3221
|
+
* })
|
|
3222
|
+
* // result.value -> { id: '1', name: 'Ada' }
|
|
3223
|
+
*
|
|
3224
|
+
* // a nonconforming call (id is not coercible to the required string) is rejected before
|
|
3225
|
+
* // `invoke` runs
|
|
3226
|
+
* const rejected = await tools.execute({
|
|
3227
|
+
* id: 'call-2',
|
|
3228
|
+
* name: 'lookupUser',
|
|
3229
|
+
* arguments: { id: true, name: 'Ada' },
|
|
3230
|
+
* })
|
|
3231
|
+
* // rejected.error -> the TOOL AgentToolError message
|
|
3232
|
+
* ```
|
|
3233
|
+
*/
|
|
3234
|
+
function createEndpointTool(definition, options) {
|
|
3235
|
+
if (definition.samples.length === 0) throw new AgentToolError("TOOL", "endpoint requires at least one sample", { name: definition.name });
|
|
3236
|
+
const objectSchema = (0, _orkestrel_contract.schemaToObject)((0, _orkestrel_contract.samplesToSchema)(definition.samples, {
|
|
3237
|
+
format: options?.format ?? false,
|
|
3238
|
+
enum: options?.enum ?? false
|
|
3239
|
+
}));
|
|
3240
|
+
const parameters = (0, _orkestrel_contract.schemaToParameters)(objectSchema);
|
|
3241
|
+
if (!(options?.validate ?? true)) return (0, _orkestrel_agent.createTool)({
|
|
3242
|
+
name: definition.name,
|
|
3243
|
+
description: definition.description,
|
|
3244
|
+
...parameters === void 0 ? {} : { parameters },
|
|
3245
|
+
execute(args) {
|
|
3246
|
+
return definition.invoke(args);
|
|
3247
|
+
}
|
|
3248
|
+
});
|
|
3249
|
+
const contract = (0, _orkestrel_contract.createContract)((0, _orkestrel_contract.schemaToShape)(objectSchema));
|
|
3250
|
+
return (0, _orkestrel_agent.createTool)({
|
|
3251
|
+
name: definition.name,
|
|
3252
|
+
description: definition.description,
|
|
3253
|
+
...parameters === void 0 ? {} : { parameters },
|
|
3254
|
+
execute(args) {
|
|
3255
|
+
const parsed = contract.parse(args);
|
|
3256
|
+
if (parsed === void 0 || !(0, _orkestrel_contract.isRecord)(parsed)) throw new AgentToolError("TOOL", "malformed endpoint call arguments", {
|
|
3257
|
+
name: definition.name,
|
|
3258
|
+
faults: contract.explain(args)
|
|
3259
|
+
});
|
|
3260
|
+
return definition.invoke(parsed);
|
|
3261
|
+
}
|
|
3262
|
+
});
|
|
3263
|
+
}
|
|
2823
3264
|
//#endregion
|
|
2824
3265
|
exports.AGENT_TOOL_DEPTH = AGENT_TOOL_DEPTH;
|
|
2825
3266
|
exports.AGENT_TOOL_DESCRIPTION = AGENT_TOOL_DESCRIPTION;
|
|
@@ -2838,6 +3279,10 @@ exports.DESCRIBE_TOOL_DESCRIPTION = DESCRIBE_TOOL_DESCRIPTION;
|
|
|
2838
3279
|
exports.DESCRIBE_TOOL_NAME = DESCRIBE_TOOL_NAME;
|
|
2839
3280
|
exports.DESCRIBE_TOOL_SUMMARY = DESCRIBE_TOOL_SUMMARY;
|
|
2840
3281
|
exports.DatabaseDefinitionStore = DatabaseDefinitionStore;
|
|
3282
|
+
exports.DatabaseResolver = DatabaseResolver;
|
|
3283
|
+
exports.INFER_TOOL_DESCRIPTION = INFER_TOOL_DESCRIPTION;
|
|
3284
|
+
exports.INFER_TOOL_NAME = INFER_TOOL_NAME;
|
|
3285
|
+
exports.INFER_TOOL_SUMMARY = INFER_TOOL_SUMMARY;
|
|
2841
3286
|
exports.MAX_WORKFLOW_DEPTH = MAX_WORKFLOW_DEPTH;
|
|
2842
3287
|
exports.MemoryDefinitionStore = MemoryDefinitionStore;
|
|
2843
3288
|
exports.PROMPT_TOOL_DESCRIPTION = PROMPT_TOOL_DESCRIPTION;
|
|
@@ -2876,6 +3321,8 @@ exports.createAnswerTool = createAnswerTool;
|
|
|
2876
3321
|
exports.createDatabaseDefinitionStore = createDatabaseDefinitionStore;
|
|
2877
3322
|
exports.createDatabaseTool = createDatabaseTool;
|
|
2878
3323
|
exports.createDescribeTool = createDescribeTool;
|
|
3324
|
+
exports.createEndpointTool = createEndpointTool;
|
|
3325
|
+
exports.createInferTool = createInferTool;
|
|
2879
3326
|
exports.createMemoryDefinitionStore = createMemoryDefinitionStore;
|
|
2880
3327
|
exports.createPromptTool = createPromptTool;
|
|
2881
3328
|
exports.createRelationTool = createRelationTool;
|
|
@@ -2892,6 +3339,7 @@ exports.expandInclude = expandInclude;
|
|
|
2892
3339
|
exports.expandSteps = expandSteps;
|
|
2893
3340
|
exports.expandTables = expandTables;
|
|
2894
3341
|
exports.includeShape = includeShape;
|
|
3342
|
+
exports.inferToolShape = inferToolShape;
|
|
2895
3343
|
exports.isAgentToolError = isAgentToolError;
|
|
2896
3344
|
exports.isColumnKind = isColumnKind;
|
|
2897
3345
|
exports.isColumnSpec = isColumnSpec;
|