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