@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.
@@ -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
- include = merge(include, segments);
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
- if (names.length === 1) return managers[names[0]];
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 = () => agent.abort(signal.reason);
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: (0, _orkestrel_contract.schemaToParameters)(steps.schema),
1924
- execute: async (args) => {
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: (args) => {
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
- execute: async (args) => {
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: (0, _orkestrel_contract.schemaToParameters)(contract.schema),
2227
- execute: async (args) => {
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
- execute: async (args) => {
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
- execute: async (args) => {
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: () => (0, _orkestrel_database.createMemoryDriver)() };
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
- async function resolve(id) {
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
- execute: async (args) => {
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 (handles.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 });
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
- handles.set(call.id, handle);
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
- handles.set(call.id, migrated);
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 = handles.get(call.id);
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
- handles.delete(call.id);
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
- execute: async (args) => {
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;