@orkestrel/tool 0.0.2 → 0.0.4
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 +1619 -0
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +1172 -30
- package/dist/src/core/index.d.ts +1172 -30
- package/dist/src/core/index.js +1569 -2
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.d.cts +125 -3
- package/dist/src/server/index.d.ts +125 -3
- package/package.json +13 -9
- 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
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
let _orkestrel_contract = require("@orkestrel/contract");
|
|
3
3
|
let _orkestrel_terminal = require("@orkestrel/terminal");
|
|
4
|
+
let _orkestrel_database = require("@orkestrel/database");
|
|
5
|
+
let _orkestrel_relation = require("@orkestrel/relation");
|
|
4
6
|
let _orkestrel_agent = require("@orkestrel/agent");
|
|
5
7
|
let _orkestrel_workflow = require("@orkestrel/workflow");
|
|
6
8
|
//#region src/core/constants.ts
|
|
@@ -296,6 +298,235 @@ var ANSWER_TOOL_DESCRIPTION = [
|
|
|
296
298
|
value: true
|
|
297
299
|
})
|
|
298
300
|
].join("\n");
|
|
301
|
+
/**
|
|
302
|
+
* The name the upcoming `createDatabaseTool` factory will advertise by default — the key a model
|
|
303
|
+
* calls and the `ToolManagerInterface` (`@orkestrel/agent`) registers under.
|
|
304
|
+
*
|
|
305
|
+
* @remarks
|
|
306
|
+
* SRC-1 of a 3-unit spine: this unit lands the persistence + schema foundation
|
|
307
|
+
* ({@link import('./types.js').DatabaseDefinition}, {@link import('./types.js').DefinitionStoreInterface},
|
|
308
|
+
* {@link import('./helpers.js').expandTables}); `createDatabaseTool` itself is built in a later unit.
|
|
309
|
+
*/
|
|
310
|
+
var DATABASE_TOOL_NAME = "database";
|
|
311
|
+
/**
|
|
312
|
+
* The lean {@link import('@orkestrel/agent').ToolInterface.summary} the upcoming database tool
|
|
313
|
+
* will advertise in place of {@link DATABASE_TOOL_DESCRIPTION}.
|
|
314
|
+
*/
|
|
315
|
+
var DATABASE_TOOL_SUMMARY = "Create and query a database — one operation per call (create, tables, get, records, count, aggregate, add, set, update, remove, migrate, destroy), chosen by the 'operation' field. Call describe('database') for the full operation list, the criteria form, and the column DSL.";
|
|
316
|
+
/**
|
|
317
|
+
* The DESCRIPTION the upcoming database tool will advertise — a multi-line guide that teaches a
|
|
318
|
+
* small model the operation list, the SERIALIZED criteria form, and the {@link import('./types.js').TableSpec}
|
|
319
|
+
* column DSL.
|
|
320
|
+
*
|
|
321
|
+
* @remarks
|
|
322
|
+
* The criteria form is deliberately SERIALIZED (never fluent) — every condition is a flat object
|
|
323
|
+
* `{ column, operator, values, connector? }` where `values` is ALWAYS an array, even for a
|
|
324
|
+
* single-value operator (`{ column: 'age', operator: 'from', values: [18] }`), so a small model
|
|
325
|
+
* never has to chain method calls or guess whether a value is scalar or a list.
|
|
326
|
+
*/
|
|
327
|
+
var DATABASE_TOOL_DESCRIPTION = [
|
|
328
|
+
"Create and query a database. Every call is ONE operation, chosen by the \"operation\" field.",
|
|
329
|
+
"",
|
|
330
|
+
"Operations (each takes the fields listed):",
|
|
331
|
+
"- create { \"operation\": \"create\", \"id\": \"<database id>\", \"tables\": { \"<table>\": { \"columns\": { \"<column>\": \"string\" | \"integer\" | \"number\" | \"boolean\" | { \"type\": \"string\", \"optional\": true } } } } } — define a new database.",
|
|
332
|
+
"- tables { \"operation\": \"tables\", \"id\": \"<database id>\" } — list a database's table names.",
|
|
333
|
+
"- get { \"operation\": \"get\", \"id\": \"<database id>\", \"table\": \"<table>\", \"key\": \"<row key>\" } — fetch one row by its primary key.",
|
|
334
|
+
"- records { \"operation\": \"records\", \"id\": \"<database id>\", \"table\": \"<table>\", \"criteria\"?: <Criteria> } — list rows matching criteria.",
|
|
335
|
+
"- count { \"operation\": \"count\", \"id\": \"<database id>\", \"table\": \"<table>\", \"criteria\"?: <Criteria> } — count rows matching criteria.",
|
|
336
|
+
"- aggregate { \"operation\": \"aggregate\", \"id\": \"<database id>\", \"table\": \"<table>\", \"column\": \"<column>\", \"function\": \"count\" | \"sum\" | \"average\" | \"minimum\" | \"maximum\", \"criteria\"?: <Criteria> } — compute an aggregate.",
|
|
337
|
+
"- add { \"operation\": \"add\", \"id\": \"<database id>\", \"table\": \"<table>\", \"row\": { ... } } — insert a row (fails on a duplicate key).",
|
|
338
|
+
"- set { \"operation\": \"set\", \"id\": \"<database id>\", \"table\": \"<table>\", \"row\": { ... } } — upsert a row.",
|
|
339
|
+
"- update { \"operation\": \"update\", \"id\": \"<database id>\", \"table\": \"<table>\", \"key\": \"<row key>\", \"row\": { ... } } — patch an existing row.",
|
|
340
|
+
"- remove { \"operation\": \"remove\", \"id\": \"<database id>\", \"table\": \"<table>\", \"key\": \"<row key>\" } — delete a row by key.",
|
|
341
|
+
"- migrate { \"operation\": \"migrate\", \"id\": \"<database id>\", \"tables\": { ... } } — replace the table layout in place.",
|
|
342
|
+
"- destroy { \"operation\": \"destroy\", \"id\": \"<database id>\" } — drop a database entirely.",
|
|
343
|
+
"",
|
|
344
|
+
"Criteria form — SERIALIZED, never fluent. A condition is a flat object; \"values\" is ALWAYS an array, even for one value:",
|
|
345
|
+
" { \"conditions\": [ { \"column\": \"age\", \"operator\": \"from\", \"values\": [18], \"connector\": \"and\" } ], \"order\"?: [...], \"offset\"?: 0, \"limit\"?: 100 }",
|
|
346
|
+
" operators: equals, not, above, below, from, to, between, like, glob, starts, ends, any, none, absent, present.",
|
|
347
|
+
" \"connector\" joins this condition to the next (\"and\" | \"or\"); omit on the last condition.",
|
|
348
|
+
"",
|
|
349
|
+
"Column DSL (used by \"create\"/\"migrate\" \"tables\"): a column is either a bare type string (\"string\" | \"integer\" | \"number\" | \"boolean\"), or { \"type\": \"<type>\", \"optional\": true } when the column may be absent from a row.",
|
|
350
|
+
"Example — create a database:",
|
|
351
|
+
JSON.stringify({
|
|
352
|
+
operation: "create",
|
|
353
|
+
id: "shop",
|
|
354
|
+
tables: { products: { columns: {
|
|
355
|
+
name: "string",
|
|
356
|
+
price: "number",
|
|
357
|
+
notes: {
|
|
358
|
+
type: "string",
|
|
359
|
+
optional: true
|
|
360
|
+
}
|
|
361
|
+
} } }
|
|
362
|
+
}),
|
|
363
|
+
"Example — query with criteria:",
|
|
364
|
+
JSON.stringify({
|
|
365
|
+
operation: "records",
|
|
366
|
+
id: "shop",
|
|
367
|
+
table: "products",
|
|
368
|
+
criteria: { conditions: [{
|
|
369
|
+
column: "price",
|
|
370
|
+
operator: "below",
|
|
371
|
+
values: [50]
|
|
372
|
+
}] }
|
|
373
|
+
})
|
|
374
|
+
].join("\n");
|
|
375
|
+
/** The default cap on rows a `records` / `remove` call returns (or acts on) when the caller omits `criteria.limit` — the upcoming database tool's default row ceiling. */
|
|
376
|
+
var DATABASE_TOOL_LIMIT = 1e3;
|
|
377
|
+
/** The database tool's mutating operations — disabled by `DatabaseToolOptions.readonly`. */
|
|
378
|
+
var DATABASE_TOOL_MUTATIONS = /* @__PURE__ */ new Set([
|
|
379
|
+
"create",
|
|
380
|
+
"add",
|
|
381
|
+
"set",
|
|
382
|
+
"update",
|
|
383
|
+
"remove",
|
|
384
|
+
"migrate",
|
|
385
|
+
"destroy"
|
|
386
|
+
]);
|
|
387
|
+
/**
|
|
388
|
+
* The name `createRelationTool` advertises by default — the key a model calls and the
|
|
389
|
+
* `ToolManagerInterface` (`@orkestrel/agent`) registers under.
|
|
390
|
+
*/
|
|
391
|
+
var RELATION_TOOL_NAME = "relation";
|
|
392
|
+
/**
|
|
393
|
+
* The lean {@link import('@orkestrel/agent').ToolInterface.summary} the relation tool advertises
|
|
394
|
+
* in place of {@link RELATION_TOOL_DESCRIPTION}.
|
|
395
|
+
*/
|
|
396
|
+
var RELATION_TOOL_SUMMARY = "Traverse and edit relationships between database rows — one operation per call (load, find, link, unlink, links), chosen by the 'operation' field. Call describe('relation') for the include-path syntax.";
|
|
397
|
+
/**
|
|
398
|
+
* The DESCRIPTION the relation tool advertises — a multi-line guide that teaches a small model
|
|
399
|
+
* the operation list and the flat dot-path `include` syntax.
|
|
400
|
+
*
|
|
401
|
+
* @remarks
|
|
402
|
+
* An include path is a FLAT dot-separated string (`'contacts.account'`), never a nested object —
|
|
403
|
+
* the same small-model ergonomic lever the other tools in this package use for flat args.
|
|
404
|
+
*/
|
|
405
|
+
var RELATION_TOOL_DESCRIPTION = [
|
|
406
|
+
"Traverse and edit relationships between database rows. Every call is ONE operation, chosen by the \"operation\" field. \"manager\" is optional (omit it when only one relation manager is registered).",
|
|
407
|
+
"",
|
|
408
|
+
"Operations (each takes the fields listed):",
|
|
409
|
+
"- load { \"operation\": \"load\", \"model\": \"<model>\", \"key\": \"<row key>\", \"include\"?: [\"<path>\", ...] } — fetch one (or, with an array key, several) row(s) with related rows attached.",
|
|
410
|
+
"- find { \"operation\": \"find\", \"model\": \"<model>\", \"include\"?: [\"<path>\", ...], \"limit\"?: <n>, \"offset\"?: <n>, \"sort\"?: \"<column>\", \"direction\"?: \"ascending\"|\"descending\" } — list rows, each with related rows attached.",
|
|
411
|
+
"- link { \"operation\": \"link\", \"model\": \"<model>\", \"key\": \"<row key>\", \"relation\": \"<relation>\", \"target\": \"<related row key>\" } — connect two rows through a \"through\" relation.",
|
|
412
|
+
"- unlink { \"operation\": \"unlink\", \"model\": \"<model>\", \"key\": \"<row key>\", \"relation\": \"<relation>\", \"target\": \"<related row key>\" } — disconnect two rows.",
|
|
413
|
+
"- links { \"operation\": \"links\", \"model\": \"<model>\", \"key\": \"<row key>\", \"relation\": \"<relation>\" } — list every key linked to a row through a \"through\" relation.",
|
|
414
|
+
"",
|
|
415
|
+
"\"include\" is a FLAT dot-path array (not nested objects) — each string names a chain of relations to attach, up to the configured depth cap. Example: \"contacts.account\" attaches each row's contacts, and each contact's account.",
|
|
416
|
+
"Example — load a row with two levels of relations:",
|
|
417
|
+
JSON.stringify({
|
|
418
|
+
operation: "load",
|
|
419
|
+
model: "orders",
|
|
420
|
+
key: "1",
|
|
421
|
+
include: ["contacts.account"]
|
|
422
|
+
})
|
|
423
|
+
].join("\n");
|
|
424
|
+
/** The default cap on rows a `find` / `links` call returns when the caller omits `limit` — the relation tool's default row ceiling. */
|
|
425
|
+
var RELATION_TOOL_LIMIT = 1e3;
|
|
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
|
+
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");
|
|
299
530
|
//#endregion
|
|
300
531
|
//#region src/core/errors.ts
|
|
301
532
|
/**
|
|
@@ -306,6 +537,9 @@ var ANSWER_TOOL_DESCRIPTION = [
|
|
|
306
537
|
* (`DEADLOCK`), a prompt that expired before it was answered (`EXPIRE`), or an answer that
|
|
307
538
|
* failed to apply (`ANSWER`) — the last three thrown by
|
|
308
539
|
* {@link import('./factories.js').createPromptTool} / {@link import('./factories.js').createAnswerTool}.
|
|
540
|
+
* The upcoming database / relation tools (SRC-1's later units) will throw it too: a typed
|
|
541
|
+
* `@orkestrel/database` failure re-surfaces as `DATABASE`, a typed `@orkestrel/relation` failure
|
|
542
|
+
* as `RELATION` — each carrying the package's own granular error code in `context`.
|
|
309
543
|
*
|
|
310
544
|
* @remarks
|
|
311
545
|
* Carries a machine-readable `code` (see {@link import('./types.js').AgentToolErrorCode}) and
|
|
@@ -676,6 +910,326 @@ var workspaceToolShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_cont
|
|
|
676
910
|
operation: (0, _orkestrel_contract.literalShape)(["switch"], { description: "Switch the active workspace to the one with this id (get ids from the \"workspaces\" operation). Edit and read operations then target it." }),
|
|
677
911
|
id: (0, _orkestrel_contract.stringShape)({ description: "The id of the workspace to make active (from the \"workspaces\" listing)." })
|
|
678
912
|
}));
|
|
913
|
+
/** A {@link import('./types.js').ColumnKind} literal — the leaf {@link columnSpecShape} wraps. */
|
|
914
|
+
var columnKindShape = (0, _orkestrel_contract.literalShape)([
|
|
915
|
+
"string",
|
|
916
|
+
"integer",
|
|
917
|
+
"number",
|
|
918
|
+
"boolean"
|
|
919
|
+
], { description: "A column type: \"string\" | \"integer\" | \"number\" | \"boolean\"." });
|
|
920
|
+
/** A {@link import('./types.js').ColumnSpec} — a bare {@link columnKindShape}, or `{ type, optional }`. */
|
|
921
|
+
var columnSpecShape = (0, _orkestrel_contract.unionShape)(columnKindShape, (0, _orkestrel_contract.objectShape)({
|
|
922
|
+
type: columnKindShape,
|
|
923
|
+
optional: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Whether the column may be absent from a row." }))
|
|
924
|
+
}));
|
|
925
|
+
/** A {@link import('./types.js').TableSpec} — table name to `{ columns }`, each column a {@link columnSpecShape}. */
|
|
926
|
+
var tableSpecShape = (0, _orkestrel_contract.recordShape)((0, _orkestrel_contract.objectShape)({ columns: (0, _orkestrel_contract.recordShape)(columnSpecShape, { description: "Column name to its type." }) }), { description: "Table name to its column layout." });
|
|
927
|
+
/** One key value — a string or number; the array form (multiple keys, positional) resolves FIRST per AGENTS §9.2. */
|
|
928
|
+
var keyShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.stringShape)(), (0, _orkestrel_contract.numberShape)()), { description: "Multiple row keys, positional — a miss at an index is undefined there." }), (0, _orkestrel_contract.stringShape)({ description: "One row key." }), (0, _orkestrel_contract.numberShape)({ description: "One row key." }));
|
|
929
|
+
/** A loose row — a flat object of column name to JSON value; the array form (multiple rows) resolves FIRST per AGENTS §9.2. */
|
|
930
|
+
var rowShape = (0, _orkestrel_contract.recordShape)((0, _orkestrel_contract.jsonShape)(), { description: "A row as a flat object of column name to value." });
|
|
931
|
+
/** One or many loose rows — the array form resolves FIRST per AGENTS §9.2. */
|
|
932
|
+
var rowsShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.arrayShape)(rowShape, { description: "Multiple rows." }), rowShape);
|
|
933
|
+
/** One SERIALIZED WHERE condition — `values` is ALWAYS an array, even for a single-value operator. */
|
|
934
|
+
var conditionShape = (0, _orkestrel_contract.objectShape)({
|
|
935
|
+
column: (0, _orkestrel_contract.stringShape)({ description: "The column this condition applies to." }),
|
|
936
|
+
operator: (0, _orkestrel_contract.literalShape)([
|
|
937
|
+
"equals",
|
|
938
|
+
"not",
|
|
939
|
+
"above",
|
|
940
|
+
"below",
|
|
941
|
+
"from",
|
|
942
|
+
"to",
|
|
943
|
+
"between",
|
|
944
|
+
"like",
|
|
945
|
+
"glob",
|
|
946
|
+
"starts",
|
|
947
|
+
"ends",
|
|
948
|
+
"any",
|
|
949
|
+
"none",
|
|
950
|
+
"absent",
|
|
951
|
+
"present"
|
|
952
|
+
], { description: "The comparison operator." }),
|
|
953
|
+
values: (0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.jsonShape)(), { description: "The operand values the operator needs (always an array, even for one value)." }),
|
|
954
|
+
connector: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.literalShape)(["and", "or"], { description: "Joins this condition to the next; omit on the last condition." }))
|
|
955
|
+
});
|
|
956
|
+
/** One sort term. */
|
|
957
|
+
var orderShape = (0, _orkestrel_contract.objectShape)({
|
|
958
|
+
column: (0, _orkestrel_contract.stringShape)({ description: "The column to sort by." }),
|
|
959
|
+
direction: (0, _orkestrel_contract.literalShape)(["ascending", "descending"], { description: "The sort direction." })
|
|
960
|
+
});
|
|
961
|
+
/** The SERIALIZED criteria form — conditions, order, and pagination. */
|
|
962
|
+
var criteriaShape = (0, _orkestrel_contract.objectShape)({
|
|
963
|
+
conditions: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)(conditionShape, { description: "The WHERE conditions, folded left to right." })),
|
|
964
|
+
order: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)(orderShape, { description: "The sort terms, applied in order." })),
|
|
965
|
+
limit: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
|
|
966
|
+
min: 0,
|
|
967
|
+
description: "Max rows to return."
|
|
968
|
+
})),
|
|
969
|
+
offset: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
|
|
970
|
+
min: 0,
|
|
971
|
+
description: "Rows to skip before returning."
|
|
972
|
+
}))
|
|
973
|
+
});
|
|
974
|
+
/**
|
|
975
|
+
* The shape of {@link import('./factories.js').createDatabaseTool}'s call arguments —
|
|
976
|
+
* discriminated by `operation` into the 12 database operations (`'create'` / `'tables'` /
|
|
977
|
+
* `'get'` / `'records'` / `'count'` / `'aggregate'` / `'add'` / `'set'` / `'update'` /
|
|
978
|
+
* `'remove'` / `'migrate'` / `'destroy'`).
|
|
979
|
+
*
|
|
980
|
+
* @remarks
|
|
981
|
+
* Every arm carries `id` (the database id). `'create'` / `'migrate'` carry `tables` (the
|
|
982
|
+
* {@link import('./types.js').TableSpec} column DSL, compiled via
|
|
983
|
+
* {@link import('./helpers.js').expandTables}); `'get'` / `'update'` / `'remove'` carry `key`
|
|
984
|
+
* (one key or an array of keys, positional); `'add'` / `'set'` carry `row` (one row or an array of
|
|
985
|
+
* rows); `'update'` also carries `changes` (a loose partial row); `'records'` / `'count'` /
|
|
986
|
+
* `'aggregate'` carry an optional `criteria` (the SERIALIZED form — `values` is ALWAYS an array,
|
|
987
|
+
* even for a single-value operator, so a caller never chains method calls or guesses arity).
|
|
988
|
+
*/
|
|
989
|
+
var databaseToolShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.objectShape)({
|
|
990
|
+
operation: (0, _orkestrel_contract.literalShape)(["create"], { description: "Define a new database." }),
|
|
991
|
+
id: (0, _orkestrel_contract.stringShape)({
|
|
992
|
+
min: 1,
|
|
993
|
+
description: "The database id."
|
|
994
|
+
}),
|
|
995
|
+
tables: tableSpecShape,
|
|
996
|
+
driver: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({
|
|
997
|
+
min: 1,
|
|
998
|
+
description: "The registered driver key. Defaults to \"memory\"."
|
|
999
|
+
})),
|
|
1000
|
+
keys: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.recordShape)((0, _orkestrel_contract.stringShape)(), { description: "Table name to its primary-key column." }))
|
|
1001
|
+
}), (0, _orkestrel_contract.objectShape)({
|
|
1002
|
+
operation: (0, _orkestrel_contract.literalShape)(["tables"], { description: "List a database's table names." }),
|
|
1003
|
+
id: (0, _orkestrel_contract.stringShape)({
|
|
1004
|
+
min: 1,
|
|
1005
|
+
description: "The database id."
|
|
1006
|
+
})
|
|
1007
|
+
}), (0, _orkestrel_contract.objectShape)({
|
|
1008
|
+
operation: (0, _orkestrel_contract.literalShape)(["get"], { description: "Fetch one or more rows by primary key." }),
|
|
1009
|
+
id: (0, _orkestrel_contract.stringShape)({
|
|
1010
|
+
min: 1,
|
|
1011
|
+
description: "The database id."
|
|
1012
|
+
}),
|
|
1013
|
+
table: (0, _orkestrel_contract.stringShape)({
|
|
1014
|
+
min: 1,
|
|
1015
|
+
description: "The table name."
|
|
1016
|
+
}),
|
|
1017
|
+
key: keyShape
|
|
1018
|
+
}), (0, _orkestrel_contract.objectShape)({
|
|
1019
|
+
operation: (0, _orkestrel_contract.literalShape)(["records"], { description: "List rows matching criteria." }),
|
|
1020
|
+
id: (0, _orkestrel_contract.stringShape)({
|
|
1021
|
+
min: 1,
|
|
1022
|
+
description: "The database id."
|
|
1023
|
+
}),
|
|
1024
|
+
table: (0, _orkestrel_contract.stringShape)({
|
|
1025
|
+
min: 1,
|
|
1026
|
+
description: "The table name."
|
|
1027
|
+
}),
|
|
1028
|
+
criteria: (0, _orkestrel_contract.optionalShape)(criteriaShape)
|
|
1029
|
+
}), (0, _orkestrel_contract.objectShape)({
|
|
1030
|
+
operation: (0, _orkestrel_contract.literalShape)(["count"], { description: "Count rows matching criteria." }),
|
|
1031
|
+
id: (0, _orkestrel_contract.stringShape)({
|
|
1032
|
+
min: 1,
|
|
1033
|
+
description: "The database id."
|
|
1034
|
+
}),
|
|
1035
|
+
table: (0, _orkestrel_contract.stringShape)({
|
|
1036
|
+
min: 1,
|
|
1037
|
+
description: "The table name."
|
|
1038
|
+
}),
|
|
1039
|
+
criteria: (0, _orkestrel_contract.optionalShape)(criteriaShape)
|
|
1040
|
+
}), (0, _orkestrel_contract.objectShape)({
|
|
1041
|
+
operation: (0, _orkestrel_contract.literalShape)(["aggregate"], { description: "Compute an aggregate over a column." }),
|
|
1042
|
+
id: (0, _orkestrel_contract.stringShape)({
|
|
1043
|
+
min: 1,
|
|
1044
|
+
description: "The database id."
|
|
1045
|
+
}),
|
|
1046
|
+
table: (0, _orkestrel_contract.stringShape)({
|
|
1047
|
+
min: 1,
|
|
1048
|
+
description: "The table name."
|
|
1049
|
+
}),
|
|
1050
|
+
function: (0, _orkestrel_contract.literalShape)([
|
|
1051
|
+
"count",
|
|
1052
|
+
"sum",
|
|
1053
|
+
"average",
|
|
1054
|
+
"minimum",
|
|
1055
|
+
"maximum"
|
|
1056
|
+
], { description: "The aggregate function." }),
|
|
1057
|
+
column: (0, _orkestrel_contract.stringShape)({
|
|
1058
|
+
min: 1,
|
|
1059
|
+
description: "The column to aggregate."
|
|
1060
|
+
}),
|
|
1061
|
+
criteria: (0, _orkestrel_contract.optionalShape)(criteriaShape)
|
|
1062
|
+
}), (0, _orkestrel_contract.objectShape)({
|
|
1063
|
+
operation: (0, _orkestrel_contract.literalShape)(["add"], { description: "Insert one or more rows (fails on a duplicate key)." }),
|
|
1064
|
+
id: (0, _orkestrel_contract.stringShape)({
|
|
1065
|
+
min: 1,
|
|
1066
|
+
description: "The database id."
|
|
1067
|
+
}),
|
|
1068
|
+
table: (0, _orkestrel_contract.stringShape)({
|
|
1069
|
+
min: 1,
|
|
1070
|
+
description: "The table name."
|
|
1071
|
+
}),
|
|
1072
|
+
row: rowsShape
|
|
1073
|
+
}), (0, _orkestrel_contract.objectShape)({
|
|
1074
|
+
operation: (0, _orkestrel_contract.literalShape)(["set"], { description: "Upsert one or more rows." }),
|
|
1075
|
+
id: (0, _orkestrel_contract.stringShape)({
|
|
1076
|
+
min: 1,
|
|
1077
|
+
description: "The database id."
|
|
1078
|
+
}),
|
|
1079
|
+
table: (0, _orkestrel_contract.stringShape)({
|
|
1080
|
+
min: 1,
|
|
1081
|
+
description: "The table name."
|
|
1082
|
+
}),
|
|
1083
|
+
row: rowsShape
|
|
1084
|
+
}), (0, _orkestrel_contract.objectShape)({
|
|
1085
|
+
operation: (0, _orkestrel_contract.literalShape)(["update"], { description: "Patch one or more existing rows." }),
|
|
1086
|
+
id: (0, _orkestrel_contract.stringShape)({
|
|
1087
|
+
min: 1,
|
|
1088
|
+
description: "The database id."
|
|
1089
|
+
}),
|
|
1090
|
+
table: (0, _orkestrel_contract.stringShape)({
|
|
1091
|
+
min: 1,
|
|
1092
|
+
description: "The table name."
|
|
1093
|
+
}),
|
|
1094
|
+
key: keyShape,
|
|
1095
|
+
changes: rowShape
|
|
1096
|
+
}), (0, _orkestrel_contract.objectShape)({
|
|
1097
|
+
operation: (0, _orkestrel_contract.literalShape)(["remove"], { description: "Delete one or more rows by key." }),
|
|
1098
|
+
id: (0, _orkestrel_contract.stringShape)({
|
|
1099
|
+
min: 1,
|
|
1100
|
+
description: "The database id."
|
|
1101
|
+
}),
|
|
1102
|
+
table: (0, _orkestrel_contract.stringShape)({
|
|
1103
|
+
min: 1,
|
|
1104
|
+
description: "The table name."
|
|
1105
|
+
}),
|
|
1106
|
+
key: keyShape
|
|
1107
|
+
}), (0, _orkestrel_contract.objectShape)({
|
|
1108
|
+
operation: (0, _orkestrel_contract.literalShape)(["migrate"], { description: "Replace the table layout in place." }),
|
|
1109
|
+
id: (0, _orkestrel_contract.stringShape)({
|
|
1110
|
+
min: 1,
|
|
1111
|
+
description: "The database id."
|
|
1112
|
+
}),
|
|
1113
|
+
tables: tableSpecShape
|
|
1114
|
+
}), (0, _orkestrel_contract.objectShape)({
|
|
1115
|
+
operation: (0, _orkestrel_contract.literalShape)(["destroy"], { description: "Drop a database entirely." }),
|
|
1116
|
+
id: (0, _orkestrel_contract.stringShape)({
|
|
1117
|
+
min: 1,
|
|
1118
|
+
description: "The database id."
|
|
1119
|
+
})
|
|
1120
|
+
}));
|
|
1121
|
+
/** One key value — a string or number; the array form (multiple keys, positional) resolves FIRST per AGENTS §9.2. */
|
|
1122
|
+
var relationKeyShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.stringShape)(), (0, _orkestrel_contract.numberShape)()), { description: "Multiple row keys, positional — a miss at an index is undefined there." }), (0, _orkestrel_contract.stringShape)({ description: "One row key." }), (0, _orkestrel_contract.numberShape)({ description: "One row key." }));
|
|
1123
|
+
/** A single row key (not an array) — used by `'link'` / `'unlink'` / `'links'`, which address exactly one owning row. */
|
|
1124
|
+
var singleKeyShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.stringShape)({ description: "The owning row key." }), (0, _orkestrel_contract.numberShape)({ description: "The owning row key." }));
|
|
1125
|
+
/** Flat dot-path relation include list, expanded via {@link import('./helpers.js').expandInclude}. */
|
|
1126
|
+
var includeShape = (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.stringShape)({ description: "A dot-separated chain of relation names, e.g. \"contacts.account\"." }), { description: "Which relations to attach, as flat dot-paths." }));
|
|
1127
|
+
/** Which registered relation manager to address — omitted resolves to the sole registered manager. */
|
|
1128
|
+
var managerShape = (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({
|
|
1129
|
+
min: 1,
|
|
1130
|
+
description: "Which registered relation manager to address."
|
|
1131
|
+
}));
|
|
1132
|
+
/**
|
|
1133
|
+
* The shape of {@link import('./factories.js').createRelationTool}'s call arguments —
|
|
1134
|
+
* discriminated by `operation` into the 5 relation operations (`'load'` / `'find'` / `'link'` /
|
|
1135
|
+
* `'unlink'` / `'links'`).
|
|
1136
|
+
*
|
|
1137
|
+
* @remarks
|
|
1138
|
+
* `'load'` fetches one or more rows (positional key/array) with `include` attached. `'find'`
|
|
1139
|
+
* fetches rows (pagination / sort only) with `include` attached. `'link'` / `'unlink'` write /
|
|
1140
|
+
* remove a `through` junction row; `'links'` lists a `through` relation's linked keys.
|
|
1141
|
+
*/
|
|
1142
|
+
var relationToolShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.objectShape)({
|
|
1143
|
+
operation: (0, _orkestrel_contract.literalShape)(["load"], { description: "Fetch one or more rows by key, with related rows attached." }),
|
|
1144
|
+
manager: managerShape,
|
|
1145
|
+
model: (0, _orkestrel_contract.stringShape)({
|
|
1146
|
+
min: 1,
|
|
1147
|
+
description: "The model (table) name."
|
|
1148
|
+
}),
|
|
1149
|
+
key: relationKeyShape,
|
|
1150
|
+
include: includeShape
|
|
1151
|
+
}), (0, _orkestrel_contract.objectShape)({
|
|
1152
|
+
operation: (0, _orkestrel_contract.literalShape)(["find"], { description: "List rows, with related rows attached." }),
|
|
1153
|
+
manager: managerShape,
|
|
1154
|
+
model: (0, _orkestrel_contract.stringShape)({
|
|
1155
|
+
min: 1,
|
|
1156
|
+
description: "The model (table) name."
|
|
1157
|
+
}),
|
|
1158
|
+
include: includeShape,
|
|
1159
|
+
limit: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
|
|
1160
|
+
min: 0,
|
|
1161
|
+
description: "Max rows to return."
|
|
1162
|
+
})),
|
|
1163
|
+
offset: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
|
|
1164
|
+
min: 0,
|
|
1165
|
+
description: "Rows to skip before returning."
|
|
1166
|
+
})),
|
|
1167
|
+
sort: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({
|
|
1168
|
+
min: 1,
|
|
1169
|
+
description: "The column to sort by."
|
|
1170
|
+
})),
|
|
1171
|
+
direction: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.literalShape)(["ascending", "descending"], { description: "The sort direction." }))
|
|
1172
|
+
}), (0, _orkestrel_contract.objectShape)({
|
|
1173
|
+
operation: (0, _orkestrel_contract.literalShape)(["link"], { description: "Connect two rows through a \"through\" relation." }),
|
|
1174
|
+
manager: managerShape,
|
|
1175
|
+
model: (0, _orkestrel_contract.stringShape)({
|
|
1176
|
+
min: 1,
|
|
1177
|
+
description: "The model (table) name."
|
|
1178
|
+
}),
|
|
1179
|
+
key: singleKeyShape,
|
|
1180
|
+
relation: (0, _orkestrel_contract.stringShape)({
|
|
1181
|
+
min: 1,
|
|
1182
|
+
description: "The \"through\" relation name."
|
|
1183
|
+
}),
|
|
1184
|
+
target: singleKeyShape
|
|
1185
|
+
}), (0, _orkestrel_contract.objectShape)({
|
|
1186
|
+
operation: (0, _orkestrel_contract.literalShape)(["unlink"], { description: "Disconnect two rows previously linked through a \"through\" relation." }),
|
|
1187
|
+
manager: managerShape,
|
|
1188
|
+
model: (0, _orkestrel_contract.stringShape)({
|
|
1189
|
+
min: 1,
|
|
1190
|
+
description: "The model (table) name."
|
|
1191
|
+
}),
|
|
1192
|
+
key: singleKeyShape,
|
|
1193
|
+
relation: (0, _orkestrel_contract.stringShape)({
|
|
1194
|
+
min: 1,
|
|
1195
|
+
description: "The \"through\" relation name."
|
|
1196
|
+
}),
|
|
1197
|
+
target: singleKeyShape
|
|
1198
|
+
}), (0, _orkestrel_contract.objectShape)({
|
|
1199
|
+
operation: (0, _orkestrel_contract.literalShape)(["links"], { description: "List every key linked to a row through a \"through\" relation." }),
|
|
1200
|
+
manager: managerShape,
|
|
1201
|
+
model: (0, _orkestrel_contract.stringShape)({
|
|
1202
|
+
min: 1,
|
|
1203
|
+
description: "The model (table) name."
|
|
1204
|
+
}),
|
|
1205
|
+
key: singleKeyShape,
|
|
1206
|
+
relation: (0, _orkestrel_contract.stringShape)({
|
|
1207
|
+
min: 1,
|
|
1208
|
+
description: "The \"through\" relation name."
|
|
1209
|
+
})
|
|
1210
|
+
}));
|
|
1211
|
+
/**
|
|
1212
|
+
* The shape of {@link import('./factories.js').createInferTool}'s call arguments — one or more
|
|
1213
|
+
* example `samples` to infer a JSON Schema from, plus per-call `format` / `enum` toggles and an
|
|
1214
|
+
* optional `candidates` array to check against the inferred schema.
|
|
1215
|
+
*
|
|
1216
|
+
* @remarks
|
|
1217
|
+
* `samples` requires at least one element (`min: 1`) — an empty array parses to `undefined`,
|
|
1218
|
+
* surfaced by the handler as a typed `TOOL` {@link import('./errors.js').AgentToolError}. When
|
|
1219
|
+
* `candidates` is present (any array, including empty), the handler compiles a contract from the
|
|
1220
|
+
* freshly inferred schema and checks each candidate against it with a STRICT guard (`.is`, no
|
|
1221
|
+
* coercion) — the opposite of {@link import('./factories.js').createEndpointTool}'s NORMALIZING
|
|
1222
|
+
* `.parse` enforcement.
|
|
1223
|
+
*/
|
|
1224
|
+
var inferToolShape = (0, _orkestrel_contract.objectShape)({
|
|
1225
|
+
samples: (0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.jsonShape)(), {
|
|
1226
|
+
min: 1,
|
|
1227
|
+
description: "The example values to infer a JSON Schema from (at least one)."
|
|
1228
|
+
}),
|
|
1229
|
+
format: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Infer string formats (date-time, email, ...) from the samples. Defaults to false." })),
|
|
1230
|
+
enum: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Infer enum constraints from repeated literal values. Defaults to false." })),
|
|
1231
|
+
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." }))
|
|
1232
|
+
});
|
|
679
1233
|
//#endregion
|
|
680
1234
|
//#region src/core/helpers.ts
|
|
681
1235
|
/**
|
|
@@ -881,6 +1435,390 @@ function terminalToolCode(error) {
|
|
|
881
1435
|
if (error.code === "EXPIRE") return "EXPIRE";
|
|
882
1436
|
return "TOOL";
|
|
883
1437
|
}
|
|
1438
|
+
/** Narrow an unknown value to a {@link ColumnSpec} — a valid {@link import('./types.js').ColumnKind} shorthand, or `{ type, optional }` with a valid `type`. */
|
|
1439
|
+
function isColumnSpec(value) {
|
|
1440
|
+
if (isColumnKind(value)) return true;
|
|
1441
|
+
if (!(0, _orkestrel_contract.isRecord)(value)) return false;
|
|
1442
|
+
return isColumnKind(value.type) && (value.optional === void 0 || typeof value.optional === "boolean");
|
|
1443
|
+
}
|
|
1444
|
+
/** Narrow an unknown value to a {@link import('./types.js').ColumnKind}. */
|
|
1445
|
+
function isColumnKind(value) {
|
|
1446
|
+
return value === "string" || value === "integer" || value === "number" || value === "boolean";
|
|
1447
|
+
}
|
|
1448
|
+
/**
|
|
1449
|
+
* Compile a {@link TableSpec} into the `@orkestrel/database` {@link TablesShape} it configures —
|
|
1450
|
+
* each {@link ColumnSpec} maps to the matching primitive shaper (`'string'` → `stringShape()`,
|
|
1451
|
+
* `'integer'` → `integerShape()`, `'number'` → `numberShape()`, `'boolean'` → `booleanShape()`),
|
|
1452
|
+
* wrapped in `optionalShape` when the column declares `optional: true`. Total, pure.
|
|
1453
|
+
*
|
|
1454
|
+
* @param spec - The small-model-facing table layout
|
|
1455
|
+
* @returns The compiled `TablesShape` a `@orkestrel/database` `createDatabase` call accepts
|
|
1456
|
+
*/
|
|
1457
|
+
function expandTables(spec) {
|
|
1458
|
+
const tables = {};
|
|
1459
|
+
for (const [table, definition] of Object.entries(spec)) {
|
|
1460
|
+
const columns = {};
|
|
1461
|
+
for (const [column, kind] of Object.entries(definition.columns)) columns[column] = columnShape(kind);
|
|
1462
|
+
tables[table] = columns;
|
|
1463
|
+
}
|
|
1464
|
+
return tables;
|
|
1465
|
+
}
|
|
1466
|
+
/** Compile one {@link ColumnSpec} into its `@orkestrel/database` column shape — the per-column leaf {@link expandTables} maps over. */
|
|
1467
|
+
function columnShape(spec) {
|
|
1468
|
+
const kind = (0, _orkestrel_contract.isString)(spec) ? spec : spec.type;
|
|
1469
|
+
const optional = !(0, _orkestrel_contract.isString)(spec) && spec.optional === true;
|
|
1470
|
+
const shape = kindShape(kind);
|
|
1471
|
+
return optional ? (0, _orkestrel_contract.optionalShape)(shape) : shape;
|
|
1472
|
+
}
|
|
1473
|
+
/** Map one {@link import('./types.js').ColumnKind} to its primitive `@orkestrel/database` shape — the leaf {@link columnShape} wraps. */
|
|
1474
|
+
function kindShape(kind) {
|
|
1475
|
+
if (kind === "string") return (0, _orkestrel_contract.stringShape)();
|
|
1476
|
+
if (kind === "integer") return (0, _orkestrel_contract.integerShape)();
|
|
1477
|
+
if (kind === "number") return (0, _orkestrel_contract.numberShape)();
|
|
1478
|
+
return (0, _orkestrel_contract.booleanShape)();
|
|
1479
|
+
}
|
|
1480
|
+
/**
|
|
1481
|
+
* Narrow an unknown value to a {@link DatabaseDefinition} — a non-empty `id` + `driver`, a
|
|
1482
|
+
* `tables` record whose every value is `{ columns: record of valid ColumnSpec }`, and an optional
|
|
1483
|
+
* `keys` record of strings. The boundary guard a {@link import('./types.js').DefinitionStoreInterface}
|
|
1484
|
+
* applies to an untrusted persisted blob before trusting it as a definition (never an `as`).
|
|
1485
|
+
*/
|
|
1486
|
+
function isDatabaseDefinition(value) {
|
|
1487
|
+
if (!(0, _orkestrel_contract.isRecord)(value)) return false;
|
|
1488
|
+
if (!(0, _orkestrel_contract.isNonEmptyString)(value.id) || !(0, _orkestrel_contract.isNonEmptyString)(value.driver)) return false;
|
|
1489
|
+
if (!(0, _orkestrel_contract.isRecord)(value.tables)) return false;
|
|
1490
|
+
for (const table of Object.values(value.tables)) {
|
|
1491
|
+
if (!(0, _orkestrel_contract.isRecord)(table) || !(0, _orkestrel_contract.isRecord)(table.columns)) return false;
|
|
1492
|
+
for (const column of Object.values(table.columns)) if (!isColumnSpec(column)) return false;
|
|
1493
|
+
}
|
|
1494
|
+
if (value.keys !== void 0) {
|
|
1495
|
+
if (!(0, _orkestrel_contract.isRecord)(value.keys)) return false;
|
|
1496
|
+
for (const key of Object.values(value.keys)) if (!(0, _orkestrel_contract.isString)(key)) return false;
|
|
1497
|
+
}
|
|
1498
|
+
return true;
|
|
1499
|
+
}
|
|
1500
|
+
/**
|
|
1501
|
+
* Map a caught error to the {@link AgentToolErrorCode} the upcoming database tool should throw
|
|
1502
|
+
* with — the pure classification step of that factory's error handling, mirroring
|
|
1503
|
+
* {@link terminalToolCode}'s idiom for `@orkestrel/database`.
|
|
1504
|
+
*
|
|
1505
|
+
* @param error - The value caught from a `@orkestrel/database` table operation
|
|
1506
|
+
* @returns The granular {@link DatabaseErrorCode}, or `undefined` if `error` is not a `DatabaseError`
|
|
1507
|
+
*/
|
|
1508
|
+
function databaseToolCode(error) {
|
|
1509
|
+
return (0, _orkestrel_database.isDatabaseError)(error) ? error.code : void 0;
|
|
1510
|
+
}
|
|
1511
|
+
/**
|
|
1512
|
+
* Map a caught error to the {@link AgentToolErrorCode} the upcoming relation tool should throw
|
|
1513
|
+
* with — the pure classification step of that factory's error handling, mirroring
|
|
1514
|
+
* {@link terminalToolCode}'s idiom for `@orkestrel/relation`.
|
|
1515
|
+
*
|
|
1516
|
+
* @param error - The value caught from a `@orkestrel/relation` operation
|
|
1517
|
+
* @returns The granular {@link RelationErrorCode}, or `undefined` if `error` is not a `RelationError`
|
|
1518
|
+
*/
|
|
1519
|
+
function relationToolCode(error) {
|
|
1520
|
+
return (0, _orkestrel_relation.isRelationError)(error) ? error.code : void 0;
|
|
1521
|
+
}
|
|
1522
|
+
/**
|
|
1523
|
+
* Expand the relation tool's FLAT dot-path `include` list into a live `@orkestrel/relation`
|
|
1524
|
+
* {@link Include} tree — the pure leaf {@link import('./factories.js').createRelationTool} calls
|
|
1525
|
+
* before a `'load'` / `'find'` call.
|
|
1526
|
+
*
|
|
1527
|
+
* @remarks
|
|
1528
|
+
* Each path splits on `'.'` into a chain of relation names, deep-merged into one nested
|
|
1529
|
+
* `Include` object with a leaf `true`. A longer path SUBSUMES a shorter sibling's bare `true` —
|
|
1530
|
+
* `'contacts'` followed by `'contacts.account'` yields `{ contacts: { account: true } }`, never
|
|
1531
|
+
* overwriting the deeper chain. An EMPTY segment (`''`, from a leading/trailing/doubled `.`) or a
|
|
1532
|
+
* path whose segment count exceeds `depth` throws a typed `TOOL` {@link AgentToolError}.
|
|
1533
|
+
*
|
|
1534
|
+
* @param paths - The flat dot-path `include` list (or `undefined` — yields `{}`)
|
|
1535
|
+
* @param depth - The max segment count a single path may reach
|
|
1536
|
+
* @returns The equivalent nested {@link Include}
|
|
1537
|
+
*
|
|
1538
|
+
* @example
|
|
1539
|
+
* ```ts
|
|
1540
|
+
* import { expandInclude } from '@src/core'
|
|
1541
|
+
*
|
|
1542
|
+
* expandInclude(['contacts', 'contacts.account'], 3)
|
|
1543
|
+
* // { contacts: { account: true } }
|
|
1544
|
+
* ```
|
|
1545
|
+
*/
|
|
1546
|
+
function expandInclude(paths, depth) {
|
|
1547
|
+
function merge(base, segments) {
|
|
1548
|
+
const [head, ...rest] = segments;
|
|
1549
|
+
const existing = base[head];
|
|
1550
|
+
if (rest.length === 0) return {
|
|
1551
|
+
...base,
|
|
1552
|
+
[head]: existing === void 0 ? true : existing
|
|
1553
|
+
};
|
|
1554
|
+
const nextBase = typeof existing === "object" ? existing : {};
|
|
1555
|
+
return {
|
|
1556
|
+
...base,
|
|
1557
|
+
[head]: merge(nextBase, rest)
|
|
1558
|
+
};
|
|
1559
|
+
}
|
|
1560
|
+
let include = {};
|
|
1561
|
+
for (const path of paths ?? []) {
|
|
1562
|
+
const segments = path.split(".");
|
|
1563
|
+
if (segments.length > depth || segments.some((segment) => segment.length === 0)) throw new AgentToolError("TOOL", `malformed include path '${path}'`, {
|
|
1564
|
+
path,
|
|
1565
|
+
depth
|
|
1566
|
+
});
|
|
1567
|
+
include = merge(include, segments);
|
|
1568
|
+
}
|
|
1569
|
+
return include;
|
|
1570
|
+
}
|
|
1571
|
+
/**
|
|
1572
|
+
* Resolve which registered {@link RelationManagerInterface} a relation-tool call addresses — the
|
|
1573
|
+
* pure manager-resolution leaf {@link import('./factories.js').createRelationTool} calls on
|
|
1574
|
+
* every operation.
|
|
1575
|
+
*
|
|
1576
|
+
* @remarks
|
|
1577
|
+
* An explicit `name` must match a key of `managers` (a miss throws a typed `TOOL`
|
|
1578
|
+
* {@link AgentToolError} naming the registered managers). An OMITTED `name` resolves to the sole
|
|
1579
|
+
* registered manager when exactly one is registered, else throws the same typed error.
|
|
1580
|
+
*
|
|
1581
|
+
* @param managers - The tool's registered `RelationManagerInterface` map
|
|
1582
|
+
* @param name - The call's optional `manager` field
|
|
1583
|
+
* @returns The resolved {@link RelationManagerInterface}
|
|
1584
|
+
*/
|
|
1585
|
+
function relationManagerOf(managers, name) {
|
|
1586
|
+
if (name !== void 0) {
|
|
1587
|
+
const manager = managers[name];
|
|
1588
|
+
if (manager === void 0) throw new AgentToolError("TOOL", `unknown relation manager '${name}'`, {
|
|
1589
|
+
manager: name,
|
|
1590
|
+
managers: Object.keys(managers)
|
|
1591
|
+
});
|
|
1592
|
+
return manager;
|
|
1593
|
+
}
|
|
1594
|
+
const names = Object.keys(managers);
|
|
1595
|
+
if (names.length === 1) return managers[names[0]];
|
|
1596
|
+
throw new AgentToolError("TOOL", "no relation manager resolved for the call", { managers: names });
|
|
1597
|
+
}
|
|
1598
|
+
/**
|
|
1599
|
+
* Resolve a `model` name against a live {@link RelationManagerInterface} — the pure model-lookup
|
|
1600
|
+
* leaf {@link import('./factories.js').createRelationTool} calls on every operation, mirroring
|
|
1601
|
+
* {@link relationManagerOf}'s guard shape.
|
|
1602
|
+
*
|
|
1603
|
+
* @param manager - The resolved {@link RelationManagerInterface}
|
|
1604
|
+
* @param name - The call's `model` field
|
|
1605
|
+
* @returns The model's {@link ModelInterface}
|
|
1606
|
+
*/
|
|
1607
|
+
function relationModelOf(manager, name) {
|
|
1608
|
+
if (!manager.has(name)) throw new AgentToolError("TOOL", `unknown model '${name}'`, {
|
|
1609
|
+
model: name,
|
|
1610
|
+
models: manager.models()
|
|
1611
|
+
});
|
|
1612
|
+
return manager.model(name);
|
|
1613
|
+
}
|
|
1614
|
+
/**
|
|
1615
|
+
* Normalize the database tool's parsed SERIALIZED criteria into a live `@orkestrel/database`
|
|
1616
|
+
* {@link Criteria} — default each condition's OMITTED `connector` to `'and'`.
|
|
1617
|
+
*
|
|
1618
|
+
* @remarks
|
|
1619
|
+
* The wire form ({@link import('./shapers.js').databaseToolShape}) lets a caller drop `connector`
|
|
1620
|
+
* on the last condition (it has nothing to join FORWARD to); the compiled `Condition` a live
|
|
1621
|
+
* `@orkestrel/database` table call accepts always carries one, so this fills the gap. `order` /
|
|
1622
|
+
* `limit` / `offset` pass through unchanged. Pure and total.
|
|
1623
|
+
*
|
|
1624
|
+
* @param criteria - The parsed criteria (or `undefined`)
|
|
1625
|
+
* @returns The equivalent live `Criteria`, or `undefined` when `criteria` is `undefined`
|
|
1626
|
+
*/
|
|
1627
|
+
function criteriaOf(criteria) {
|
|
1628
|
+
if (criteria === void 0) return void 0;
|
|
1629
|
+
const conditions = criteria.conditions?.map((condition) => ({
|
|
1630
|
+
...condition,
|
|
1631
|
+
connector: condition.connector ?? "and"
|
|
1632
|
+
}));
|
|
1633
|
+
return {
|
|
1634
|
+
...conditions === void 0 ? {} : { conditions },
|
|
1635
|
+
...criteria.order === void 0 ? {} : { order: criteria.order },
|
|
1636
|
+
...criteria.limit === void 0 ? {} : { limit: criteria.limit },
|
|
1637
|
+
...criteria.offset === void 0 ? {} : { offset: criteria.offset }
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1640
|
+
/**
|
|
1641
|
+
* Clamp a `'records'` call's criteria to a row cap, and build the PROBE criteria the caller reads
|
|
1642
|
+
* with — the pure leaf {@link import('./factories.js').createDatabaseTool}'s `'records'` operation
|
|
1643
|
+
* uses to detect truncation without a separate `count` round trip.
|
|
1644
|
+
*
|
|
1645
|
+
* @remarks
|
|
1646
|
+
* The effective limit is `min(criteria?.limit ?? cap, cap)`, floored at `0` (so a caller can never
|
|
1647
|
+
* exceed the configured cap by supplying a larger `criteria.limit`). The returned probe criteria
|
|
1648
|
+
* requests ONE MORE row than the effective limit (`limit: effective + 1`) — if storage returns
|
|
1649
|
+
* that many, the caller knows the true result was truncated (`rows.length > effective`) and slices
|
|
1650
|
+
* back down to `effective` before returning.
|
|
1651
|
+
*
|
|
1652
|
+
* @example
|
|
1653
|
+
* ```ts
|
|
1654
|
+
* import { clampCriteria } from '@src/core'
|
|
1655
|
+
*
|
|
1656
|
+
* const { criteria, limit } = clampCriteria(undefined, 100)
|
|
1657
|
+
* // limit === 100, criteria.limit === 101 — a probe fetching one extra row
|
|
1658
|
+
* const rows = await table.records(criteria)
|
|
1659
|
+
* const truncated = rows.length > limit // true when storage had more than `limit` rows
|
|
1660
|
+
* ```
|
|
1661
|
+
*
|
|
1662
|
+
* @param criteria - The live criteria to clamp (or `undefined`)
|
|
1663
|
+
* @param cap - The row-count ceiling
|
|
1664
|
+
* @returns The PROBE criteria (`limit` bumped by one) and the effective `limit`
|
|
1665
|
+
*/
|
|
1666
|
+
function clampCriteria(criteria, cap) {
|
|
1667
|
+
const limit = Math.max(0, Math.min(criteria?.limit ?? cap, cap));
|
|
1668
|
+
return {
|
|
1669
|
+
criteria: {
|
|
1670
|
+
...criteria,
|
|
1671
|
+
limit: limit + 1
|
|
1672
|
+
},
|
|
1673
|
+
limit
|
|
1674
|
+
};
|
|
1675
|
+
}
|
|
1676
|
+
/** Map a column NAME + its live `@orkestrel/database` `ContractShape` to a {@link ColumnSchema} — the leaf {@link tableSchema} maps over. */
|
|
1677
|
+
function columnSchema(name, shape) {
|
|
1678
|
+
return {
|
|
1679
|
+
name,
|
|
1680
|
+
type: (0, _orkestrel_database.shapeToColumnType)(shape),
|
|
1681
|
+
nullable: shape.type === "optional" || shape.type === "nullable"
|
|
1682
|
+
};
|
|
1683
|
+
}
|
|
1684
|
+
/**
|
|
1685
|
+
* Build one {@link TableSchema} from a table NAME and its `@orkestrel/database` `TableExport` —
|
|
1686
|
+
* the "deployed" schema shape `DatabaseInterface.migrate` diffs against, derived from a LIVE
|
|
1687
|
+
* handle's `export()` rather than a re-declared {@link TableSpec}, so it works for ANY handle
|
|
1688
|
+
* (config-tracked or caller-supplied).
|
|
1689
|
+
*
|
|
1690
|
+
* @param name - The table name
|
|
1691
|
+
* @param table - The table's `TableExport` (`{ key, columns }`, `@orkestrel/database`)
|
|
1692
|
+
* @returns The equivalent {@link TableSchema} (`indexes` empty — this package declares none)
|
|
1693
|
+
*/
|
|
1694
|
+
function tableSchema(name, table) {
|
|
1695
|
+
return {
|
|
1696
|
+
name,
|
|
1697
|
+
primary: table.key,
|
|
1698
|
+
columns: Object.entries(table.columns).map(([column, shape]) => columnSchema(column, shape)),
|
|
1699
|
+
indexes: []
|
|
1700
|
+
};
|
|
1701
|
+
}
|
|
1702
|
+
//#endregion
|
|
1703
|
+
//#region src/core/stores/MemoryDefinitionStore.ts
|
|
1704
|
+
/**
|
|
1705
|
+
* The in-memory {@link DefinitionStoreInterface} — a process-lifetime `Map` of
|
|
1706
|
+
* {@link DatabaseDefinition}s keyed by database id, the DEFAULT store
|
|
1707
|
+
* {@link import('../factories.js').createMemoryDefinitionStore} builds. The EXACT twin of
|
|
1708
|
+
* {@link import('./DatabaseDefinitionStore.js').DatabaseDefinitionStore}.
|
|
1709
|
+
*
|
|
1710
|
+
* @remarks
|
|
1711
|
+
* A plain `Map<string, DatabaseDefinition>` (AGENTS §21 — the definition is already pure,
|
|
1712
|
+
* self-contained CONFIG-only JSON, so no encoding is needed for the memory tier). There is NO
|
|
1713
|
+
* idle-TTL and NO eviction: a persisted definition lives until an explicit `delete`. A durable
|
|
1714
|
+
* backend (JSON / SQLite / IndexedDB) swaps in through the SAME interface without touching a
|
|
1715
|
+
* consumer — its driver-pluggable twin is
|
|
1716
|
+
* {@link import('./DatabaseDefinitionStore.js').DatabaseDefinitionStore} (the definition as one
|
|
1717
|
+
* opaque JSON column).
|
|
1718
|
+
*
|
|
1719
|
+
* - **`get` resolves the persisted definition for an id**, or `undefined` if none is stored.
|
|
1720
|
+
* - **`set` inserts / replaces under the definition's OWN `id`** (no separate id param).
|
|
1721
|
+
* - **`delete` drops a definition by id**; an absent id is a no-op (no throw).
|
|
1722
|
+
*
|
|
1723
|
+
* The public surface is EXACTLY `get` / `set` / `delete` — no extra members (the §22 method
|
|
1724
|
+
* bijection with {@link DefinitionStoreInterface}).
|
|
1725
|
+
*
|
|
1726
|
+
* @example
|
|
1727
|
+
* ```ts
|
|
1728
|
+
* import { createMemoryDefinitionStore } from '@src/core'
|
|
1729
|
+
*
|
|
1730
|
+
* const store = createMemoryDefinitionStore()
|
|
1731
|
+
* await store.set({ id: 'shop', driver: 'memory', tables: {} })
|
|
1732
|
+
* const definition = await store.get('shop')
|
|
1733
|
+
* await store.delete('shop')
|
|
1734
|
+
* ```
|
|
1735
|
+
*/
|
|
1736
|
+
var MemoryDefinitionStore = class {
|
|
1737
|
+
#definitions = /* @__PURE__ */ new Map();
|
|
1738
|
+
get(id) {
|
|
1739
|
+
return Promise.resolve(this.#definitions.get(id));
|
|
1740
|
+
}
|
|
1741
|
+
set(definition) {
|
|
1742
|
+
this.#definitions.set(definition.id, definition);
|
|
1743
|
+
return Promise.resolve();
|
|
1744
|
+
}
|
|
1745
|
+
delete(id) {
|
|
1746
|
+
this.#definitions.delete(id);
|
|
1747
|
+
return Promise.resolve();
|
|
1748
|
+
}
|
|
1749
|
+
};
|
|
1750
|
+
//#endregion
|
|
1751
|
+
//#region src/core/stores/DatabaseDefinitionStore.ts
|
|
1752
|
+
/**
|
|
1753
|
+
* A {@link DefinitionStoreInterface} backed by one table of the `@orkestrel/database` layer — a
|
|
1754
|
+
* database's durable CONFIG state IS a row, so persistence reduces to keyed point-access
|
|
1755
|
+
* (`get` / `set` / `delete`) over a {@link TableInterface}, the driver-pluggable twin of the
|
|
1756
|
+
* plain-`Map` {@link import('./MemoryDefinitionStore.js').MemoryDefinitionStore}.
|
|
1757
|
+
*
|
|
1758
|
+
* @remarks
|
|
1759
|
+
* The store is driver-agnostic: it holds a single {@link TableInterface} whose backend (memory,
|
|
1760
|
+
* JSON, SQLite, IndexedDB) is chosen by whoever builds it (the factories), so a JSON / SQLite /
|
|
1761
|
+
* IndexedDB backend swaps in WITHOUT touching a consumer — the same seam as
|
|
1762
|
+
* {@link import('./MemoryDefinitionStore.js').MemoryDefinitionStore}. The driver defaults to
|
|
1763
|
+
* memory ({@link import('../factories.js').createDatabaseDefinitionStore} passes
|
|
1764
|
+
* `createMemoryDriver()`), so it ALSO works in memory out of the box; you opt into the durable
|
|
1765
|
+
* plumbing by passing a JSON / SQLite / IndexedDB driver.
|
|
1766
|
+
*
|
|
1767
|
+
* The {@link DatabaseDefinition} is stored as ONE OPAQUE JSON COLUMN — the table is a row of
|
|
1768
|
+
* `{ id; definition }` ({@link DatabaseDefinitionRow}). The definition is already a COMPLETE,
|
|
1769
|
+
* self-contained, pure-JSON CONFIG payload (never a live handle), so storing it whole is lossless
|
|
1770
|
+
* AND keeps the row type flat (`definition` reads back as `unknown`).
|
|
1771
|
+
*
|
|
1772
|
+
* - **`set(definition)` upserts under the definition's OWN `id`** (no separate id param) — it
|
|
1773
|
+
* writes the row `{ id: definition.id, definition }`.
|
|
1774
|
+
* - **`get(id)` resolves the stored definition for an id**, narrowing the opaque JSON column back
|
|
1775
|
+
* to a {@link DatabaseDefinition} ({@link import('../helpers.js').isDatabaseDefinition} — the
|
|
1776
|
+
* AGENTS §14 boundary narrow for an untrusted storage read), or `undefined` if none is stored
|
|
1777
|
+
* or the stored blob is malformed.
|
|
1778
|
+
* - **`delete(id)` drops a definition by id**; an absent id is a no-op (no throw).
|
|
1779
|
+
*
|
|
1780
|
+
* The public surface is EXACTLY `get` / `set` / `delete` — no extra members (the §22 method
|
|
1781
|
+
* bijection with {@link DefinitionStoreInterface}).
|
|
1782
|
+
*
|
|
1783
|
+
* @example
|
|
1784
|
+
* ```ts
|
|
1785
|
+
* import { createDatabaseDefinitionStore, createMemoryDriver } from '@src/core'
|
|
1786
|
+
*
|
|
1787
|
+
* const store = createDatabaseDefinitionStore(createMemoryDriver()) // a durable driver swaps in here
|
|
1788
|
+
* await store.set({ id: 'shop', driver: 'memory', tables: {} }) // persist the config (one JSON column)
|
|
1789
|
+
* const definition = await store.get('shop')
|
|
1790
|
+
* await store.delete('shop')
|
|
1791
|
+
* ```
|
|
1792
|
+
*/
|
|
1793
|
+
var DatabaseDefinitionStore = class {
|
|
1794
|
+
#table;
|
|
1795
|
+
/**
|
|
1796
|
+
* Wrap a table as a definition store.
|
|
1797
|
+
*
|
|
1798
|
+
* @param table - The {@link TableInterface} holding the definitions — its row is the
|
|
1799
|
+
* {@link DatabaseDefinitionRow} `{ id; definition }` shape (the definition one opaque JSON column)
|
|
1800
|
+
*/
|
|
1801
|
+
constructor(table) {
|
|
1802
|
+
this.#table = table;
|
|
1803
|
+
}
|
|
1804
|
+
/** Resolve the persisted definition for `id`, narrowing the opaque JSON column back to a `DatabaseDefinition`. */
|
|
1805
|
+
async get(id) {
|
|
1806
|
+
const row = await this.#table.get(id);
|
|
1807
|
+
if (row === void 0) return void 0;
|
|
1808
|
+
return isDatabaseDefinition(row.definition) ? row.definition : void 0;
|
|
1809
|
+
}
|
|
1810
|
+
/** Insert or replace under the definition's OWN `id` (no separate id param) — the row is `{ id, definition }`. */
|
|
1811
|
+
async set(definition) {
|
|
1812
|
+
await this.#table.set({
|
|
1813
|
+
id: definition.id,
|
|
1814
|
+
definition
|
|
1815
|
+
});
|
|
1816
|
+
}
|
|
1817
|
+
/** Drop a definition by id; an absent id is a no-op (no throw). */
|
|
1818
|
+
async delete(id) {
|
|
1819
|
+
await this.#table.remove(id);
|
|
1820
|
+
}
|
|
1821
|
+
};
|
|
884
1822
|
//#endregion
|
|
885
1823
|
//#region src/core/factories.ts
|
|
886
1824
|
/**
|
|
@@ -1587,6 +2525,635 @@ function createAnswerTool(options) {
|
|
|
1587
2525
|
}
|
|
1588
2526
|
});
|
|
1589
2527
|
}
|
|
2528
|
+
/**
|
|
2529
|
+
* Create the in-memory {@link DefinitionStoreInterface} — a process-lifetime `Map` of database
|
|
2530
|
+
* definitions, the DEFAULT store the upcoming database / relation tools will persist their
|
|
2531
|
+
* `DatabaseDefinition` configs through.
|
|
2532
|
+
*
|
|
2533
|
+
* @returns A {@link DefinitionStoreInterface}
|
|
2534
|
+
*
|
|
2535
|
+
* @example
|
|
2536
|
+
* ```ts
|
|
2537
|
+
* import { createMemoryDefinitionStore } from '@src/core'
|
|
2538
|
+
*
|
|
2539
|
+
* const store = createMemoryDefinitionStore()
|
|
2540
|
+
* ```
|
|
2541
|
+
*/
|
|
2542
|
+
function createMemoryDefinitionStore() {
|
|
2543
|
+
return new MemoryDefinitionStore();
|
|
2544
|
+
}
|
|
2545
|
+
/**
|
|
2546
|
+
* Create a {@link DefinitionStoreInterface} backed by one table of the `@orkestrel/database`
|
|
2547
|
+
* layer — the driver-pluggable twin of {@link createMemoryDefinitionStore}, storing each
|
|
2548
|
+
* database's definition as one opaque JSON column.
|
|
2549
|
+
*
|
|
2550
|
+
* @param driver - The {@link DriverInterface} backing the table (default an in-memory driver)
|
|
2551
|
+
* @returns A {@link DefinitionStoreInterface}
|
|
2552
|
+
*
|
|
2553
|
+
* @example
|
|
2554
|
+
* ```ts
|
|
2555
|
+
* import { createDatabaseDefinitionStore } from '@src/core'
|
|
2556
|
+
*
|
|
2557
|
+
* const store = createDatabaseDefinitionStore() // in-memory by default
|
|
2558
|
+
* ```
|
|
2559
|
+
*/
|
|
2560
|
+
function createDatabaseDefinitionStore(driver = (0, _orkestrel_database.createMemoryDriver)()) {
|
|
2561
|
+
return new DatabaseDefinitionStore((0, _orkestrel_database.createDatabase)({
|
|
2562
|
+
driver,
|
|
2563
|
+
tables: { definitions: {
|
|
2564
|
+
id: (0, _orkestrel_contract.stringShape)(),
|
|
2565
|
+
definition: (0, _orkestrel_contract.rawShape)({})
|
|
2566
|
+
} }
|
|
2567
|
+
}).table("definitions"));
|
|
2568
|
+
}
|
|
2569
|
+
/**
|
|
2570
|
+
* Build an LLM-callable database tool — create, query, and mutate `@orkestrel/database`
|
|
2571
|
+
* databases through one `operation`-discriminated call (AGENTS §14, matching
|
|
2572
|
+
* {@link createWorkspaceTool}'s single-tool-many-operations shape).
|
|
2573
|
+
*
|
|
2574
|
+
* @remarks
|
|
2575
|
+
* The universal tool-handler contract (AGENTS §14): validates the call args against
|
|
2576
|
+
* {@link import('./shapers.js').databaseToolShape}, dispatches to the matching operation, and
|
|
2577
|
+
* RETURNS a plain result on success. A database is resolved lazily and cached for the tool's
|
|
2578
|
+
* lifetime — `'create'` mints one from `tables` ({@link import('./helpers.js').expandTables}) and
|
|
2579
|
+
* a registered `driver` key ({@link import('./types.js').DatabaseToolOptions.drivers}, default
|
|
2580
|
+
* `{ memory: () => createMemoryDriver() }`); any other operation addressing an uncached id falls
|
|
2581
|
+
* back to {@link import('./types.js').DatabaseToolOptions.store} (an unknown id throws a typed
|
|
2582
|
+
* `TOOL` {@link import('./errors.js').AgentToolError}). When a `store` is configured, `'create'`
|
|
2583
|
+
* persists the new {@link import('./types.js').DatabaseDefinition} and `'destroy'` deletes it.
|
|
2584
|
+
*
|
|
2585
|
+
* `'migrate'` re-declares a LIVE handle's tables via `DatabaseInterface.import` (the SAME driver
|
|
2586
|
+
* and storage, a NEW typed view) and calls its `migrate` against the OLD deployed schema —
|
|
2587
|
+
* derived from the handle's OWN `export()` (via {@link import('./helpers.js').tableSchema}), so it
|
|
2588
|
+
* works for any handle, config-tracked or caller-supplied via
|
|
2589
|
+
* {@link import('./types.js').DatabaseToolOptions.databases}. `'records'` clamps its `criteria` to
|
|
2590
|
+
* {@link import('./types.js').DatabaseToolOptions.limit} (default
|
|
2591
|
+
* {@link import('./constants.js').DATABASE_TOOL_LIMIT}) via
|
|
2592
|
+
* {@link import('./helpers.js').clampCriteria}, reporting `truncated` when storage held more rows
|
|
2593
|
+
* than the cap. Every operation's `criteria` is normalized via
|
|
2594
|
+
* {@link import('./helpers.js').criteriaOf} (defaults an omitted condition `connector` to `'and'`).
|
|
2595
|
+
* When {@link import('./types.js').DatabaseToolOptions.readonly} is `true`, every mutating
|
|
2596
|
+
* operation throws a typed `TOOL` `AgentToolError` before doing anything. When
|
|
2597
|
+
* {@link import('./types.js').DatabaseToolOptions.timeout} is set, every `@orkestrel/database` call
|
|
2598
|
+
* this tool makes is given a fresh `AbortSignal.timeout(timeout)`. A typed `@orkestrel/database`
|
|
2599
|
+
* failure (`DatabaseError`) re-surfaces as a typed `DATABASE` `AgentToolError` carrying the
|
|
2600
|
+
* original {@link import('@orkestrel/database').DatabaseErrorCode} in `context.code`
|
|
2601
|
+
* ({@link import('./helpers.js').databaseToolCode}); an `AgentToolError` thrown by this tool's own
|
|
2602
|
+
* guards passes through unwrapped.
|
|
2603
|
+
*
|
|
2604
|
+
* A lazily re-minted database over the DEFAULT in-memory driver yields an EMPTY database — only
|
|
2605
|
+
* the {@link import('./types.js').DatabaseDefinition} schema persists in `store`, never rows;
|
|
2606
|
+
* durable rows need a persistent driver factory registered in
|
|
2607
|
+
* {@link import('./types.js').DatabaseToolOptions.drivers}. `'destroy'` closes whatever handle is
|
|
2608
|
+
* cached for the id, including an embedder-supplied
|
|
2609
|
+
* {@link import('./types.js').DatabaseToolOptions.databases} handle — the embedder relinquishes
|
|
2610
|
+
* that handle's lifecycle to this tool for any id it wires in. This tool assumes the
|
|
2611
|
+
* single-writer, non-reentrant model `@orkestrel/database` itself assumes — concurrent calls
|
|
2612
|
+
* against one id are NOT serialized by this tool. `'get'` is uncapped by
|
|
2613
|
+
* {@link import('./types.js').DatabaseToolOptions.limit} (bounded only by the caller's `key` array
|
|
2614
|
+
* size), unlike `'records'` / `'find'` / `'links'`.
|
|
2615
|
+
*
|
|
2616
|
+
* @param options - The tool's configuration (see {@link import('./types.js').DatabaseToolOptions})
|
|
2617
|
+
* @returns A `ToolInterface` (named {@link import('./constants.js').DATABASE_TOOL_NAME} by default)
|
|
2618
|
+
*
|
|
2619
|
+
* @example
|
|
2620
|
+
* ```ts
|
|
2621
|
+
* import { createDatabaseTool } from '@src/core'
|
|
2622
|
+
*
|
|
2623
|
+
* const tool = createDatabaseTool()
|
|
2624
|
+
* await tool.execute({
|
|
2625
|
+
* operation: 'create',
|
|
2626
|
+
* id: 'shop',
|
|
2627
|
+
* tables: { products: { columns: { name: 'string', price: 'number' } } },
|
|
2628
|
+
* })
|
|
2629
|
+
* ```
|
|
2630
|
+
*/
|
|
2631
|
+
function createDatabaseTool(options = {}) {
|
|
2632
|
+
const contract = (0, _orkestrel_contract.createContract)(databaseToolShape);
|
|
2633
|
+
const parameters = (0, _orkestrel_contract.schemaToParameters)(contract.schema);
|
|
2634
|
+
const handles = new Map(Object.entries(options.databases ?? {}));
|
|
2635
|
+
const definitions = /* @__PURE__ */ new Map();
|
|
2636
|
+
const drivers = options.drivers ?? { memory: () => (0, _orkestrel_database.createMemoryDriver)() };
|
|
2637
|
+
const key = options.key ?? _orkestrel_database.generateUUID;
|
|
2638
|
+
const cap = options.limit ?? 1e3;
|
|
2639
|
+
const store = options.store;
|
|
2640
|
+
async function resolve(id) {
|
|
2641
|
+
const cached = handles.get(id);
|
|
2642
|
+
if (cached !== void 0) return cached;
|
|
2643
|
+
if (store !== void 0) {
|
|
2644
|
+
const definition = await store.get(id);
|
|
2645
|
+
if (definition !== void 0) {
|
|
2646
|
+
const factory = drivers[definition.driver];
|
|
2647
|
+
if (factory === void 0) throw new AgentToolError("TOOL", `unknown driver '${definition.driver}'`, {
|
|
2648
|
+
id,
|
|
2649
|
+
driver: definition.driver
|
|
2650
|
+
});
|
|
2651
|
+
const handle = (0, _orkestrel_database.createDatabase)({
|
|
2652
|
+
driver: factory(),
|
|
2653
|
+
tables: expandTables(definition.tables),
|
|
2654
|
+
...definition.keys === void 0 ? {} : { keys: definition.keys },
|
|
2655
|
+
key
|
|
2656
|
+
});
|
|
2657
|
+
handles.set(id, handle);
|
|
2658
|
+
definitions.set(id, definition);
|
|
2659
|
+
return handle;
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
throw new AgentToolError("TOOL", `unknown database '${id}'`, { id });
|
|
2663
|
+
}
|
|
2664
|
+
return (0, _orkestrel_agent.createTool)({
|
|
2665
|
+
name: options.name ?? "database",
|
|
2666
|
+
description: options.description ?? DATABASE_TOOL_DESCRIPTION,
|
|
2667
|
+
summary: DATABASE_TOOL_SUMMARY,
|
|
2668
|
+
parameters,
|
|
2669
|
+
execute: async (args) => {
|
|
2670
|
+
const call = contract.parse(args);
|
|
2671
|
+
if (call === void 0) throw new AgentToolError("TOOL", "malformed database call", { args });
|
|
2672
|
+
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 });
|
|
2673
|
+
const read = options.timeout === void 0 ? void 0 : { signal: AbortSignal.timeout(options.timeout) };
|
|
2674
|
+
try {
|
|
2675
|
+
switch (call.operation) {
|
|
2676
|
+
case "create": {
|
|
2677
|
+
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 });
|
|
2678
|
+
const name = call.driver ?? "memory";
|
|
2679
|
+
const factory = drivers[name];
|
|
2680
|
+
if (factory === void 0) throw new AgentToolError("TOOL", `unknown driver '${name}'`, {
|
|
2681
|
+
id: call.id,
|
|
2682
|
+
driver: name
|
|
2683
|
+
});
|
|
2684
|
+
const tables = call.tables;
|
|
2685
|
+
const keys = call.keys;
|
|
2686
|
+
const handle = (0, _orkestrel_database.createDatabase)({
|
|
2687
|
+
driver: factory(),
|
|
2688
|
+
tables: expandTables(tables),
|
|
2689
|
+
...keys === void 0 ? {} : { keys },
|
|
2690
|
+
key
|
|
2691
|
+
});
|
|
2692
|
+
handles.set(call.id, handle);
|
|
2693
|
+
const definition = {
|
|
2694
|
+
id: call.id,
|
|
2695
|
+
driver: name,
|
|
2696
|
+
tables,
|
|
2697
|
+
...keys === void 0 ? {} : { keys }
|
|
2698
|
+
};
|
|
2699
|
+
definitions.set(call.id, definition);
|
|
2700
|
+
if (store !== void 0) await store.set(definition);
|
|
2701
|
+
return {
|
|
2702
|
+
id: call.id,
|
|
2703
|
+
tables: Object.keys(tables)
|
|
2704
|
+
};
|
|
2705
|
+
}
|
|
2706
|
+
case "tables": {
|
|
2707
|
+
const handle = await resolve(call.id);
|
|
2708
|
+
return { tables: Object.keys(handle.export()).map((name) => {
|
|
2709
|
+
const table = handle.table(name);
|
|
2710
|
+
return {
|
|
2711
|
+
name,
|
|
2712
|
+
primary: table.primary,
|
|
2713
|
+
columns: table.contract.schema
|
|
2714
|
+
};
|
|
2715
|
+
}) };
|
|
2716
|
+
}
|
|
2717
|
+
case "get": {
|
|
2718
|
+
const table = (await resolve(call.id)).table(call.table);
|
|
2719
|
+
const many = Array.isArray(call.key);
|
|
2720
|
+
const keys = Array.isArray(call.key) ? call.key : [call.key];
|
|
2721
|
+
const rows = await table.get(keys);
|
|
2722
|
+
return many ? { rows } : { row: rows[0] };
|
|
2723
|
+
}
|
|
2724
|
+
case "records": {
|
|
2725
|
+
const table = (await resolve(call.id)).table(call.table);
|
|
2726
|
+
const { criteria: probe, limit } = clampCriteria(criteriaOf(call.criteria), cap);
|
|
2727
|
+
const rows = await table.records(probe, read);
|
|
2728
|
+
const truncated = rows.length > limit;
|
|
2729
|
+
const sliced = rows.slice(0, limit);
|
|
2730
|
+
return {
|
|
2731
|
+
rows: sliced,
|
|
2732
|
+
count: sliced.length,
|
|
2733
|
+
truncated,
|
|
2734
|
+
limit
|
|
2735
|
+
};
|
|
2736
|
+
}
|
|
2737
|
+
case "count": return { count: await (await resolve(call.id)).table(call.table).count(criteriaOf(call.criteria), read) };
|
|
2738
|
+
case "aggregate": return { value: await (await resolve(call.id)).table(call.table).aggregate(call.function, call.column, criteriaOf(call.criteria), read) };
|
|
2739
|
+
case "add": {
|
|
2740
|
+
const table = (await resolve(call.id)).table(call.table);
|
|
2741
|
+
const many = Array.isArray(call.row);
|
|
2742
|
+
const rows = Array.isArray(call.row) ? call.row : [call.row];
|
|
2743
|
+
const keys = await table.add(rows, read);
|
|
2744
|
+
return many ? { keys } : { key: keys[0] };
|
|
2745
|
+
}
|
|
2746
|
+
case "set": {
|
|
2747
|
+
const table = (await resolve(call.id)).table(call.table);
|
|
2748
|
+
const many = Array.isArray(call.row);
|
|
2749
|
+
const rows = Array.isArray(call.row) ? call.row : [call.row];
|
|
2750
|
+
const keys = await table.set(rows, read);
|
|
2751
|
+
return many ? { keys } : { key: keys[0] };
|
|
2752
|
+
}
|
|
2753
|
+
case "update": {
|
|
2754
|
+
const table = (await resolve(call.id)).table(call.table);
|
|
2755
|
+
const changes = call.changes;
|
|
2756
|
+
const many = Array.isArray(call.key);
|
|
2757
|
+
const keys = Array.isArray(call.key) ? call.key : [call.key];
|
|
2758
|
+
const updated = await table.update(keys, changes, read);
|
|
2759
|
+
return many ? { updated } : { updated: updated[0] };
|
|
2760
|
+
}
|
|
2761
|
+
case "remove": {
|
|
2762
|
+
const table = (await resolve(call.id)).table(call.table);
|
|
2763
|
+
const many = Array.isArray(call.key);
|
|
2764
|
+
const keys = Array.isArray(call.key) ? call.key : [call.key];
|
|
2765
|
+
const removed = await table.remove(keys, read);
|
|
2766
|
+
return many ? { removed } : { removed: removed[0] };
|
|
2767
|
+
}
|
|
2768
|
+
case "migrate": {
|
|
2769
|
+
const handle = await resolve(call.id);
|
|
2770
|
+
const previous = handle.export();
|
|
2771
|
+
const deployed = Object.entries(previous).map(([name, table]) => tableSchema(name, table));
|
|
2772
|
+
const tables = call.tables;
|
|
2773
|
+
const keys = {};
|
|
2774
|
+
for (const name of Object.keys(tables)) {
|
|
2775
|
+
const existing = previous[name];
|
|
2776
|
+
if (existing !== void 0) keys[name] = existing.key;
|
|
2777
|
+
}
|
|
2778
|
+
const declared = expandTables(tables);
|
|
2779
|
+
const migrated = handle.import(declared, Object.keys(keys).length > 0 ? keys : void 0);
|
|
2780
|
+
const migration = await migrated.migrate(deployed, read);
|
|
2781
|
+
handles.set(call.id, migrated);
|
|
2782
|
+
const tracked = definitions.get(call.id);
|
|
2783
|
+
if (tracked !== void 0) {
|
|
2784
|
+
const updated = {
|
|
2785
|
+
id: call.id,
|
|
2786
|
+
driver: tracked.driver,
|
|
2787
|
+
tables,
|
|
2788
|
+
...Object.keys(keys).length > 0 ? { keys } : {}
|
|
2789
|
+
};
|
|
2790
|
+
definitions.set(call.id, updated);
|
|
2791
|
+
if (store !== void 0) await store.set(updated);
|
|
2792
|
+
}
|
|
2793
|
+
return { migration };
|
|
2794
|
+
}
|
|
2795
|
+
case "destroy": {
|
|
2796
|
+
const cached = handles.get(call.id);
|
|
2797
|
+
const persisted = store !== void 0 && cached === void 0 ? await store.get(call.id) !== void 0 : false;
|
|
2798
|
+
if (cached !== void 0) {
|
|
2799
|
+
await cached.close();
|
|
2800
|
+
handles.delete(call.id);
|
|
2801
|
+
}
|
|
2802
|
+
definitions.delete(call.id);
|
|
2803
|
+
if (store !== void 0) await store.delete(call.id);
|
|
2804
|
+
return {
|
|
2805
|
+
id: call.id,
|
|
2806
|
+
destroyed: cached !== void 0 || persisted
|
|
2807
|
+
};
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
} catch (error) {
|
|
2811
|
+
if (isAgentToolError(error)) throw error;
|
|
2812
|
+
const code = databaseToolCode(error);
|
|
2813
|
+
if (code === void 0) throw error;
|
|
2814
|
+
throw new AgentToolError("DATABASE", error instanceof Error ? error.message : String(error), {
|
|
2815
|
+
code,
|
|
2816
|
+
operation: call.operation,
|
|
2817
|
+
id: call.id,
|
|
2818
|
+
..."table" in call ? { table: call.table } : {}
|
|
2819
|
+
});
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
});
|
|
2823
|
+
}
|
|
2824
|
+
/**
|
|
2825
|
+
* Build an LLM-callable relation tool — traverse and edit `@orkestrel/relation` relationships
|
|
2826
|
+
* through one `operation`-discriminated call (AGENTS §14, matching {@link createDatabaseTool}'s
|
|
2827
|
+
* single-tool-many-operations shape).
|
|
2828
|
+
*
|
|
2829
|
+
* @remarks
|
|
2830
|
+
* The universal tool-handler contract (AGENTS §14): validates the call args against
|
|
2831
|
+
* {@link import('./shapers.js').relationToolShape}, resolves the addressed
|
|
2832
|
+
* {@link import('@orkestrel/relation').RelationManagerInterface} — an explicit `manager` field
|
|
2833
|
+
* must match a key of {@link import('./types.js').RelationToolOptions.managers}, an OMITTED one
|
|
2834
|
+
* resolves to the SOLE registered manager, either miss throwing a typed `TOOL`
|
|
2835
|
+
* {@link import('./errors.js').AgentToolError}
|
|
2836
|
+
* ({@link import('./helpers.js').relationManagerOf}) — then resolves `model` against it
|
|
2837
|
+
* ({@link import('./helpers.js').relationModelOf}, same typed-`TOOL`-on-miss shape), and
|
|
2838
|
+
* dispatches to the matched operation, RETURNING a plain result on success.
|
|
2839
|
+
*
|
|
2840
|
+
* `'load'` / `'find'` expand the call's FLAT dot-path `include` list into a live
|
|
2841
|
+
* `@orkestrel/relation` `Include` tree via {@link import('./helpers.js').expandInclude}, capped
|
|
2842
|
+
* at {@link import('./types.js').RelationToolOptions.depth} (default
|
|
2843
|
+
* {@link import('./constants.js').RELATION_TOOL_DEPTH}) — a path exceeding the cap, or carrying an
|
|
2844
|
+
* empty segment, throws a typed `TOOL` error. `'load'` dispatches on whether `key` is an array
|
|
2845
|
+
* (positional many-key form, AGENTS §9.2) or a single key. `'find'` and `'links'` clamp their
|
|
2846
|
+
* result to {@link import('./types.js').RelationToolOptions.limit} (default
|
|
2847
|
+
* {@link import('./constants.js').RELATION_TOOL_LIMIT}) — `'find'` probes one row past the
|
|
2848
|
+
* effective limit (mirroring {@link import('./helpers.js').clampCriteria}'s idiom) to report
|
|
2849
|
+
* `truncated`; `'links'` (which has no upstream pagination) fetches the FULL linked-key list and
|
|
2850
|
+
* slices/truncates it the same way. `'link'` / `'unlink'` write / remove one `through` junction
|
|
2851
|
+
* row.
|
|
2852
|
+
*
|
|
2853
|
+
* A typed `@orkestrel/relation` failure (`RelationError`) re-surfaces as a typed `RELATION`
|
|
2854
|
+
* `AgentToolError` carrying the original {@link import('@orkestrel/relation').RelationErrorCode}
|
|
2855
|
+
* in `context.code`; a typed `@orkestrel/database` failure underneath it (`DatabaseError`)
|
|
2856
|
+
* re-surfaces as a typed `DATABASE` `AgentToolError`, mirroring {@link createDatabaseTool}'s error
|
|
2857
|
+
* mapping; an `AgentToolError` thrown by this tool's own guards (malformed args, an unknown
|
|
2858
|
+
* manager/model) passes through unwrapped.
|
|
2859
|
+
*
|
|
2860
|
+
* @param options - The tool's configuration (see {@link import('./types.js').RelationToolOptions})
|
|
2861
|
+
* @returns A `ToolInterface` (named {@link import('./constants.js').RELATION_TOOL_NAME} by default)
|
|
2862
|
+
*
|
|
2863
|
+
* @example
|
|
2864
|
+
* ```ts
|
|
2865
|
+
* import { createRelationTool } from '@src/core'
|
|
2866
|
+
*
|
|
2867
|
+
* const tool = createRelationTool({ managers: { shop: manager } })
|
|
2868
|
+
* await tool.execute({ operation: 'load', model: 'accounts', key: 'acc1', include: ['contacts'] })
|
|
2869
|
+
* ```
|
|
2870
|
+
*/
|
|
2871
|
+
function createRelationTool(options) {
|
|
2872
|
+
const contract = (0, _orkestrel_contract.createContract)(relationToolShape);
|
|
2873
|
+
const parameters = (0, _orkestrel_contract.schemaToParameters)(contract.schema);
|
|
2874
|
+
const depth = options.depth ?? 3;
|
|
2875
|
+
const cap = options.limit ?? 1e3;
|
|
2876
|
+
return (0, _orkestrel_agent.createTool)({
|
|
2877
|
+
name: options.name ?? "relation",
|
|
2878
|
+
description: options.description ?? RELATION_TOOL_DESCRIPTION,
|
|
2879
|
+
summary: RELATION_TOOL_SUMMARY,
|
|
2880
|
+
parameters,
|
|
2881
|
+
execute: async (args) => {
|
|
2882
|
+
const call = contract.parse(args);
|
|
2883
|
+
if (call === void 0) throw new AgentToolError("TOOL", "malformed relation call", { args });
|
|
2884
|
+
try {
|
|
2885
|
+
const model = relationModelOf(relationManagerOf(options.managers, call.manager), call.model);
|
|
2886
|
+
switch (call.operation) {
|
|
2887
|
+
case "load": {
|
|
2888
|
+
const include = expandInclude(call.include, depth);
|
|
2889
|
+
if (typeof call.key === "string" || typeof call.key === "number") return { row: await model.load(call.key, include) };
|
|
2890
|
+
return { rows: await model.load(call.key, include) };
|
|
2891
|
+
}
|
|
2892
|
+
case "find": {
|
|
2893
|
+
const include = expandInclude(call.include, depth);
|
|
2894
|
+
const effective = Math.min(call.limit ?? cap, cap);
|
|
2895
|
+
const rows = await model.find(include, {
|
|
2896
|
+
limit: effective + 1,
|
|
2897
|
+
...call.offset === void 0 ? {} : { offset: call.offset },
|
|
2898
|
+
...call.sort === void 0 ? {} : { sort: call.sort },
|
|
2899
|
+
...call.direction === void 0 ? {} : { direction: call.direction }
|
|
2900
|
+
});
|
|
2901
|
+
const truncated = rows.length > effective;
|
|
2902
|
+
const sliced = rows.slice(0, effective);
|
|
2903
|
+
return {
|
|
2904
|
+
rows: sliced,
|
|
2905
|
+
count: sliced.length,
|
|
2906
|
+
truncated,
|
|
2907
|
+
limit: effective
|
|
2908
|
+
};
|
|
2909
|
+
}
|
|
2910
|
+
case "link":
|
|
2911
|
+
await model.link(call.key, call.relation, call.target);
|
|
2912
|
+
return { linked: true };
|
|
2913
|
+
case "unlink":
|
|
2914
|
+
await model.unlink(call.key, call.relation, call.target);
|
|
2915
|
+
return { unlinked: true };
|
|
2916
|
+
case "links": {
|
|
2917
|
+
const keys = await model.links(call.key, call.relation);
|
|
2918
|
+
const truncated = keys.length > cap;
|
|
2919
|
+
const sliced = keys.slice(0, cap);
|
|
2920
|
+
return {
|
|
2921
|
+
keys: sliced,
|
|
2922
|
+
count: sliced.length,
|
|
2923
|
+
truncated,
|
|
2924
|
+
limit: cap
|
|
2925
|
+
};
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2928
|
+
} catch (error) {
|
|
2929
|
+
if (isAgentToolError(error)) throw error;
|
|
2930
|
+
const relation = relationToolCode(error);
|
|
2931
|
+
if (relation !== void 0) throw new AgentToolError("RELATION", error instanceof Error ? error.message : String(error), {
|
|
2932
|
+
code: relation,
|
|
2933
|
+
operation: call.operation,
|
|
2934
|
+
model: call.model,
|
|
2935
|
+
..."relation" in call ? { relation: call.relation } : {}
|
|
2936
|
+
});
|
|
2937
|
+
const database = databaseToolCode(error);
|
|
2938
|
+
if (database === void 0) throw error;
|
|
2939
|
+
throw new AgentToolError("DATABASE", error instanceof Error ? error.message : String(error), {
|
|
2940
|
+
code: database,
|
|
2941
|
+
operation: call.operation
|
|
2942
|
+
});
|
|
2943
|
+
}
|
|
2944
|
+
}
|
|
2945
|
+
});
|
|
2946
|
+
}
|
|
2947
|
+
/**
|
|
2948
|
+
* Build a standalone LLM-callable tool that infers a JSON Schema from example values — the
|
|
2949
|
+
* utility half of the "existing API/DB → MCP tool" bridge (the other half,
|
|
2950
|
+
* {@link createEndpointTool}, wraps one CONCRETE endpoint).
|
|
2951
|
+
*
|
|
2952
|
+
* @remarks
|
|
2953
|
+
* The universal tool-handler contract (AGENTS §14): validates the call args against
|
|
2954
|
+
* {@link import('./shapers.js').inferToolShape} (`samples` non-empty, `format` / `enum` optional
|
|
2955
|
+
* booleans, `candidates` an optional array), infers a schema via `@orkestrel/contract`'s
|
|
2956
|
+
* `samplesToSchema`, wraps a non-object root as `{ value: <schema> }` via `schemaToObject` (mirrors
|
|
2957
|
+
* the tool-parameters convention every other `create*Tool` factory advertises), and RETURNS the
|
|
2958
|
+
* resulting parameters record. An empty `samples` array fails `inferToolShape`'s `min: 1` bound —
|
|
2959
|
+
* `contract.parse` returns `undefined` and the handler throws a typed `TOOL`
|
|
2960
|
+
* {@link import('./errors.js').AgentToolError}.
|
|
2961
|
+
*
|
|
2962
|
+
* When `candidates` is ABSENT, the return is the bare parameters record — unchanged from before
|
|
2963
|
+
* this array existed. When `candidates` is PRESENT (any array, including empty), the handler
|
|
2964
|
+
* compiles a SEPARATE per-call contract from the RAW inferred schema (via `@orkestrel/contract`'s
|
|
2965
|
+
* `schemaToShape`, NOT the `schemaToObject`-wrapped parameters — a bare-value sample checks a
|
|
2966
|
+
* bare-value candidate) and returns `{ parameters, checks }`, one check per candidate at the same
|
|
2967
|
+
* index. Every entry has a UNIFORM shape — `{ index, valid, coercible }`, with `faults` added ONLY
|
|
2968
|
+
* when `valid` is `false`: `valid` is the STRICT guard verdict (`checker.is(candidate)`), the
|
|
2969
|
+
* OPPOSITE of {@link createEndpointTool}'s enforcement, which coerces (`7` becomes `'7'` for a
|
|
2970
|
+
* string slot) — here a conformance report answers "does this value conform AS-IS": `7` against a
|
|
2971
|
+
* string slot is `valid: false`, full stop. `coercible` answers a SEPARATE question — "would the
|
|
2972
|
+
* NORMALIZING parse accept this value", i.e. would {@link createEndpointTool}'s default enforcement
|
|
2973
|
+
* admit it (`checker.parse(candidate) !== undefined`) — computed for every candidate regardless of
|
|
2974
|
+
* `valid`; by the house parse/guard round-trip guarantee (AGENTS §14), a `valid: true` entry is
|
|
2975
|
+
* ALWAYS also `coercible: true`. `@orkestrel/contract` 0.0.7's `explain` mirrors the normalizing
|
|
2976
|
+
* `parse`'s leniency, not `is`'s strictness — so a strictly-invalid but coercible candidate (`7`
|
|
2977
|
+
* against a string slot) yields `{ valid: false, coercible: true, faults: [] }`: EMPTY faults, since
|
|
2978
|
+
* the mismatch the normalizing parse would silently fix is not one `explain` reports. `faults`
|
|
2979
|
+
* therefore only ever populates for a NON-coercible mismatch — a wrong type the parse can't coerce
|
|
2980
|
+
* (a boolean in a string slot), a missing required key, or an out-of-enum value — where
|
|
2981
|
+
* `coercible: false`. `checker.is` / `.parse` / `.explain` are all total over JSON-safe input — a
|
|
2982
|
+
* JSON-safe hostile candidate (a `__proto__`-carrying object, deeply nested data) reaches all three
|
|
2983
|
+
* and yields a bounded, non-throwing per-candidate verdict; a NON-JSON-safe candidate (e.g. a
|
|
2984
|
+
* throwing-getter `Proxy`) never reaches the checker at all — it fails the OUTER `args` parse
|
|
2985
|
+
* against {@link import('./shapers.js').inferToolShape} and rejects the WHOLE call with the same
|
|
2986
|
+
* `TOOL` {@link import('./errors.js').AgentToolError} a malformed `samples`/`format`/`enum` throws,
|
|
2987
|
+
* with no per-candidate verdict produced.
|
|
2988
|
+
*
|
|
2989
|
+
* @param options - Advertised `name` / `description` overrides (see
|
|
2990
|
+
* {@link import('./types.js').InferToolOptions})
|
|
2991
|
+
* @returns A `ToolInterface` (named {@link import('./constants.js').INFER_TOOL_NAME} by default)
|
|
2992
|
+
*
|
|
2993
|
+
* @example
|
|
2994
|
+
* ```ts
|
|
2995
|
+
* import { createInferTool } from '@src/core'
|
|
2996
|
+
* import { createToolManager } from '@orkestrel/agent'
|
|
2997
|
+
*
|
|
2998
|
+
* const tool = createInferTool()
|
|
2999
|
+
* const tools = createToolManager()
|
|
3000
|
+
* tools.add(tool)
|
|
3001
|
+
*
|
|
3002
|
+
* const result = await tools.execute({
|
|
3003
|
+
* id: 'call-1',
|
|
3004
|
+
* name: 'infer',
|
|
3005
|
+
* arguments: { samples: [{ id: 1, name: 'Ada' }, { id: 2, name: 'Bob' }] },
|
|
3006
|
+
* })
|
|
3007
|
+
* // result.value -> { type: 'object', properties: { id: {...}, name: {...} }, ... }
|
|
3008
|
+
*
|
|
3009
|
+
* // with candidates, the result is wrapped with per-candidate verdicts
|
|
3010
|
+
* const checked = await tools.execute({
|
|
3011
|
+
* id: 'call-2',
|
|
3012
|
+
* name: 'infer',
|
|
3013
|
+
* arguments: {
|
|
3014
|
+
* samples: [{ id: 1, name: 'Ada' }],
|
|
3015
|
+
* candidates: [{ id: 2, name: 'Bob' }, { id: 'x', name: 'Cy' }],
|
|
3016
|
+
* },
|
|
3017
|
+
* })
|
|
3018
|
+
* // checked.value -> { parameters: {...}, checks: [
|
|
3019
|
+
* // { index: 0, valid: true, coercible: true },
|
|
3020
|
+
* // { index: 1, valid: false, coercible: false, faults: [...] },
|
|
3021
|
+
* // ] }
|
|
3022
|
+
* ```
|
|
3023
|
+
*/
|
|
3024
|
+
function createInferTool(options) {
|
|
3025
|
+
const contract = (0, _orkestrel_contract.createContract)(inferToolShape);
|
|
3026
|
+
const parameters = (0, _orkestrel_contract.schemaToParameters)(contract.schema);
|
|
3027
|
+
return (0, _orkestrel_agent.createTool)({
|
|
3028
|
+
name: options?.name ?? "infer",
|
|
3029
|
+
description: options?.description ?? INFER_TOOL_DESCRIPTION,
|
|
3030
|
+
summary: INFER_TOOL_SUMMARY,
|
|
3031
|
+
parameters,
|
|
3032
|
+
execute: async (args) => {
|
|
3033
|
+
const parsed = contract.parse(args);
|
|
3034
|
+
if (parsed === void 0) throw new AgentToolError("TOOL", "malformed infer arguments", { args });
|
|
3035
|
+
const schema = (0, _orkestrel_contract.samplesToSchema)(parsed.samples, {
|
|
3036
|
+
format: parsed.format ?? false,
|
|
3037
|
+
enum: parsed.enum ?? false
|
|
3038
|
+
});
|
|
3039
|
+
const result = (0, _orkestrel_contract.schemaToParameters)((0, _orkestrel_contract.schemaToObject)(schema));
|
|
3040
|
+
if (result === void 0) throw new AgentToolError("TOOL", "could not infer a schema", { args });
|
|
3041
|
+
if (parsed.candidates === void 0) return result;
|
|
3042
|
+
const checker = (0, _orkestrel_contract.createContract)((0, _orkestrel_contract.schemaToShape)(schema));
|
|
3043
|
+
return {
|
|
3044
|
+
parameters: result,
|
|
3045
|
+
checks: parsed.candidates.map((candidate, index) => {
|
|
3046
|
+
const valid = checker.is(candidate);
|
|
3047
|
+
const coercible = checker.parse(candidate) !== void 0;
|
|
3048
|
+
return valid ? {
|
|
3049
|
+
index,
|
|
3050
|
+
valid,
|
|
3051
|
+
coercible
|
|
3052
|
+
} : {
|
|
3053
|
+
index,
|
|
3054
|
+
valid,
|
|
3055
|
+
coercible,
|
|
3056
|
+
faults: checker.explain(candidate)
|
|
3057
|
+
};
|
|
3058
|
+
})
|
|
3059
|
+
};
|
|
3060
|
+
}
|
|
3061
|
+
});
|
|
3062
|
+
}
|
|
3063
|
+
/**
|
|
3064
|
+
* Wrap one CONCRETE endpoint ({@link import('./types.js').EndpointDefinition}) as an LLM-callable
|
|
3065
|
+
* `ToolInterface` — the endpoint half of the "existing API/DB → MCP tool" bridge (the other half,
|
|
3066
|
+
* {@link createInferTool}, is a standalone inference utility).
|
|
3067
|
+
*
|
|
3068
|
+
* @remarks
|
|
3069
|
+
* `parameters` is inferred ONCE at construction from `definition.samples` via
|
|
3070
|
+
* `@orkestrel/contract`'s `samplesToSchema` (tuned by {@link import('./types.js').EndpointToolOptions}'s
|
|
3071
|
+
* `format` / `enum`), wrapping a non-object root as `{ value: <schema> }` via `schemaToObject` —
|
|
3072
|
+
* the SAME object-rooted schema is both the ADVERTISED `parameters` and, by default
|
|
3073
|
+
* ({@link import('./types.js').EndpointToolOptions.validate} `true`), the ENFORCED contract:
|
|
3074
|
+
* `@orkestrel/contract` 0.0.7's `schemaToShape` compiles it ONCE (via `createContract`) into a
|
|
3075
|
+
* `ContractInterface` whose `.parse` runs on every call's `args` before `definition.invoke` — a
|
|
3076
|
+
* NORMALIZING parse, not a strict type check: a scalar is COERCED to its inferred type where the
|
|
3077
|
+
* house parsers coerce (a number to/from a numeric string, a boolean from `'1'`/`'0'`/`'true'`/
|
|
3078
|
+
* `'false'`/`1`/`0`), so `definition.invoke` receives the COERCED value (e.g. `7` sent for a
|
|
3079
|
+
* string slot arrives as `'7'`), not the raw call value. A call whose `args` fails to parse into
|
|
3080
|
+
* a record — a required key missing, or a value not coercible to its slot's type — THROWS a
|
|
3081
|
+
* typed `TOOL` {@link import('./errors.js').AgentToolError} carrying the compiled contract's
|
|
3082
|
+
* structured `explain` faults, and `definition.invoke` is never called. `format` annotations are
|
|
3083
|
+
* NEVER asserted, and a key outside the closed inferred schema is SILENTLY DROPPED rather than
|
|
3084
|
+
* rejected (see {@link import('./types.js').EndpointToolOptions.validate}). With
|
|
3085
|
+
* `validate: false`, `execute` PASSES THROUGH the model-supplied `args` to `definition.invoke`
|
|
3086
|
+
* WITHOUT re-validation — the pre-0.0.7 behavior, preserved as an explicit opt-out. Either way,
|
|
3087
|
+
* `invoke`'s return flows back as the tool call's plain result; a throw PROPAGATES uncaught,
|
|
3088
|
+
* isolated by the `ToolManagerInterface` (`@orkestrel/agent`) into the canonical error envelope
|
|
3089
|
+
* (AGENTS §14) — never caught or re-wrapped here.
|
|
3090
|
+
*
|
|
3091
|
+
* @param definition - The endpoint's identity, non-empty samples, and local handler (see
|
|
3092
|
+
* {@link import('./types.js').EndpointDefinition})
|
|
3093
|
+
* @param options - Construction-time inference tuning + the validate opt-out (see
|
|
3094
|
+
* {@link import('./types.js').EndpointToolOptions})
|
|
3095
|
+
* @returns A `ToolInterface` named `definition.name`
|
|
3096
|
+
*
|
|
3097
|
+
* @example
|
|
3098
|
+
* ```ts
|
|
3099
|
+
* import { createEndpointTool } from '@src/core'
|
|
3100
|
+
* import { createToolManager } from '@orkestrel/agent'
|
|
3101
|
+
*
|
|
3102
|
+
* const tool = createEndpointTool({
|
|
3103
|
+
* name: 'lookupUser',
|
|
3104
|
+
* description: 'Look up a user by id.',
|
|
3105
|
+
* samples: [{ id: '1', name: 'Ada' }, { id: '2', name: 'Bob' }],
|
|
3106
|
+
* invoke: (args) => ({ id: args.id, name: 'Ada' }),
|
|
3107
|
+
* })
|
|
3108
|
+
* const tools = createToolManager()
|
|
3109
|
+
* tools.add(tool)
|
|
3110
|
+
*
|
|
3111
|
+
* // conforming args (all required keys present) parse and reach `invoke`
|
|
3112
|
+
* const result = await tools.execute({
|
|
3113
|
+
* id: 'call-1',
|
|
3114
|
+
* name: 'lookupUser',
|
|
3115
|
+
* arguments: { id: '1', name: 'Ada' },
|
|
3116
|
+
* })
|
|
3117
|
+
* // result.value -> { id: '1', name: 'Ada' }
|
|
3118
|
+
*
|
|
3119
|
+
* // a nonconforming call (id is not coercible to the required string) is rejected before
|
|
3120
|
+
* // `invoke` runs
|
|
3121
|
+
* const rejected = await tools.execute({
|
|
3122
|
+
* id: 'call-2',
|
|
3123
|
+
* name: 'lookupUser',
|
|
3124
|
+
* arguments: { id: true, name: 'Ada' },
|
|
3125
|
+
* })
|
|
3126
|
+
* // rejected.error -> the TOOL AgentToolError message
|
|
3127
|
+
* ```
|
|
3128
|
+
*/
|
|
3129
|
+
function createEndpointTool(definition, options) {
|
|
3130
|
+
if (definition.samples.length === 0) throw new AgentToolError("TOOL", "endpoint requires at least one sample", { name: definition.name });
|
|
3131
|
+
const objectSchema = (0, _orkestrel_contract.schemaToObject)((0, _orkestrel_contract.samplesToSchema)(definition.samples, {
|
|
3132
|
+
format: options?.format ?? false,
|
|
3133
|
+
enum: options?.enum ?? false
|
|
3134
|
+
}));
|
|
3135
|
+
const parameters = (0, _orkestrel_contract.schemaToParameters)(objectSchema);
|
|
3136
|
+
if (!(options?.validate ?? true)) return (0, _orkestrel_agent.createTool)({
|
|
3137
|
+
name: definition.name,
|
|
3138
|
+
description: definition.description,
|
|
3139
|
+
parameters,
|
|
3140
|
+
execute: (args) => definition.invoke(args)
|
|
3141
|
+
});
|
|
3142
|
+
const contract = (0, _orkestrel_contract.createContract)((0, _orkestrel_contract.schemaToShape)(objectSchema));
|
|
3143
|
+
return (0, _orkestrel_agent.createTool)({
|
|
3144
|
+
name: definition.name,
|
|
3145
|
+
description: definition.description,
|
|
3146
|
+
parameters,
|
|
3147
|
+
execute: (args) => {
|
|
3148
|
+
const parsed = contract.parse(args);
|
|
3149
|
+
if (parsed === void 0 || !(0, _orkestrel_contract.isRecord)(parsed)) throw new AgentToolError("TOOL", "malformed endpoint call arguments", {
|
|
3150
|
+
name: definition.name,
|
|
3151
|
+
faults: contract.explain(args)
|
|
3152
|
+
});
|
|
3153
|
+
return definition.invoke(parsed);
|
|
3154
|
+
}
|
|
3155
|
+
});
|
|
3156
|
+
}
|
|
1590
3157
|
//#endregion
|
|
1591
3158
|
exports.AGENT_TOOL_DEPTH = AGENT_TOOL_DEPTH;
|
|
1592
3159
|
exports.AGENT_TOOL_DESCRIPTION = AGENT_TOOL_DESCRIPTION;
|
|
@@ -1596,13 +3163,28 @@ exports.ANSWER_TOOL_DESCRIPTION = ANSWER_TOOL_DESCRIPTION;
|
|
|
1596
3163
|
exports.ANSWER_TOOL_NAME = ANSWER_TOOL_NAME;
|
|
1597
3164
|
exports.ANSWER_TOOL_SUMMARY = ANSWER_TOOL_SUMMARY;
|
|
1598
3165
|
exports.AgentToolError = AgentToolError;
|
|
3166
|
+
exports.DATABASE_TOOL_DESCRIPTION = DATABASE_TOOL_DESCRIPTION;
|
|
3167
|
+
exports.DATABASE_TOOL_LIMIT = DATABASE_TOOL_LIMIT;
|
|
3168
|
+
exports.DATABASE_TOOL_MUTATIONS = DATABASE_TOOL_MUTATIONS;
|
|
3169
|
+
exports.DATABASE_TOOL_NAME = DATABASE_TOOL_NAME;
|
|
3170
|
+
exports.DATABASE_TOOL_SUMMARY = DATABASE_TOOL_SUMMARY;
|
|
1599
3171
|
exports.DESCRIBE_TOOL_DESCRIPTION = DESCRIBE_TOOL_DESCRIPTION;
|
|
1600
3172
|
exports.DESCRIBE_TOOL_NAME = DESCRIBE_TOOL_NAME;
|
|
1601
3173
|
exports.DESCRIBE_TOOL_SUMMARY = DESCRIBE_TOOL_SUMMARY;
|
|
3174
|
+
exports.DatabaseDefinitionStore = DatabaseDefinitionStore;
|
|
3175
|
+
exports.INFER_TOOL_DESCRIPTION = INFER_TOOL_DESCRIPTION;
|
|
3176
|
+
exports.INFER_TOOL_NAME = INFER_TOOL_NAME;
|
|
3177
|
+
exports.INFER_TOOL_SUMMARY = INFER_TOOL_SUMMARY;
|
|
1602
3178
|
exports.MAX_WORKFLOW_DEPTH = MAX_WORKFLOW_DEPTH;
|
|
3179
|
+
exports.MemoryDefinitionStore = MemoryDefinitionStore;
|
|
1603
3180
|
exports.PROMPT_TOOL_DESCRIPTION = PROMPT_TOOL_DESCRIPTION;
|
|
1604
3181
|
exports.PROMPT_TOOL_NAME = PROMPT_TOOL_NAME;
|
|
1605
3182
|
exports.PROMPT_TOOL_SUMMARY = PROMPT_TOOL_SUMMARY;
|
|
3183
|
+
exports.RELATION_TOOL_DEPTH = RELATION_TOOL_DEPTH;
|
|
3184
|
+
exports.RELATION_TOOL_DESCRIPTION = RELATION_TOOL_DESCRIPTION;
|
|
3185
|
+
exports.RELATION_TOOL_LIMIT = RELATION_TOOL_LIMIT;
|
|
3186
|
+
exports.RELATION_TOOL_NAME = RELATION_TOOL_NAME;
|
|
3187
|
+
exports.RELATION_TOOL_SUMMARY = RELATION_TOOL_SUMMARY;
|
|
1606
3188
|
exports.WORKFLOW_TOOL_DESCRIPTION = WORKFLOW_TOOL_DESCRIPTION;
|
|
1607
3189
|
exports.WORKFLOW_TOOL_FLAT_EXAMPLE = WORKFLOW_TOOL_FLAT_EXAMPLE;
|
|
1608
3190
|
exports.WORKFLOW_TOOL_NAME = WORKFLOW_TOOL_NAME;
|
|
@@ -1615,25 +3197,62 @@ exports.WORKSPACE_TOOL_SUMMARY = WORKSPACE_TOOL_SUMMARY;
|
|
|
1615
3197
|
exports.agentTag = agentTag;
|
|
1616
3198
|
exports.agentToolShape = agentToolShape;
|
|
1617
3199
|
exports.answerToolShape = answerToolShape;
|
|
3200
|
+
exports.clampCriteria = clampCriteria;
|
|
1618
3201
|
exports.coerceAnswer = coerceAnswer;
|
|
3202
|
+
exports.columnKindShape = columnKindShape;
|
|
3203
|
+
exports.columnSchema = columnSchema;
|
|
3204
|
+
exports.columnShape = columnShape;
|
|
3205
|
+
exports.columnSpecShape = columnSpecShape;
|
|
1619
3206
|
exports.completeDraft = completeDraft;
|
|
1620
3207
|
exports.completePhaseDraft = completePhaseDraft;
|
|
1621
3208
|
exports.completeTaskDraft = completeTaskDraft;
|
|
3209
|
+
exports.conditionShape = conditionShape;
|
|
1622
3210
|
exports.createAgentFunction = createAgentFunction;
|
|
1623
3211
|
exports.createAgentTool = createAgentTool;
|
|
1624
3212
|
exports.createAnswerTool = createAnswerTool;
|
|
3213
|
+
exports.createDatabaseDefinitionStore = createDatabaseDefinitionStore;
|
|
3214
|
+
exports.createDatabaseTool = createDatabaseTool;
|
|
1625
3215
|
exports.createDescribeTool = createDescribeTool;
|
|
3216
|
+
exports.createEndpointTool = createEndpointTool;
|
|
3217
|
+
exports.createInferTool = createInferTool;
|
|
3218
|
+
exports.createMemoryDefinitionStore = createMemoryDefinitionStore;
|
|
1626
3219
|
exports.createPromptTool = createPromptTool;
|
|
3220
|
+
exports.createRelationTool = createRelationTool;
|
|
1627
3221
|
exports.createToolFunction = createToolFunction;
|
|
1628
3222
|
exports.createWorkflowDraftContract = createWorkflowDraftContract;
|
|
1629
3223
|
exports.createWorkflowTool = createWorkflowTool;
|
|
1630
3224
|
exports.createWorkspaceTool = createWorkspaceTool;
|
|
3225
|
+
exports.criteriaOf = criteriaOf;
|
|
3226
|
+
exports.criteriaShape = criteriaShape;
|
|
3227
|
+
exports.databaseToolCode = databaseToolCode;
|
|
3228
|
+
exports.databaseToolShape = databaseToolShape;
|
|
1631
3229
|
exports.describeToolShape = describeToolShape;
|
|
3230
|
+
exports.expandInclude = expandInclude;
|
|
1632
3231
|
exports.expandSteps = expandSteps;
|
|
3232
|
+
exports.expandTables = expandTables;
|
|
3233
|
+
exports.includeShape = includeShape;
|
|
3234
|
+
exports.inferToolShape = inferToolShape;
|
|
1633
3235
|
exports.isAgentToolError = isAgentToolError;
|
|
3236
|
+
exports.isColumnKind = isColumnKind;
|
|
3237
|
+
exports.isColumnSpec = isColumnSpec;
|
|
3238
|
+
exports.isDatabaseDefinition = isDatabaseDefinition;
|
|
3239
|
+
exports.keyShape = keyShape;
|
|
3240
|
+
exports.kindShape = kindShape;
|
|
3241
|
+
exports.managerShape = managerShape;
|
|
3242
|
+
exports.orderShape = orderShape;
|
|
1634
3243
|
exports.phaseDraftShape = phaseDraftShape;
|
|
1635
3244
|
exports.promptToolShape = promptToolShape;
|
|
3245
|
+
exports.relationKeyShape = relationKeyShape;
|
|
3246
|
+
exports.relationManagerOf = relationManagerOf;
|
|
3247
|
+
exports.relationModelOf = relationModelOf;
|
|
3248
|
+
exports.relationToolCode = relationToolCode;
|
|
3249
|
+
exports.relationToolShape = relationToolShape;
|
|
3250
|
+
exports.rowShape = rowShape;
|
|
3251
|
+
exports.rowsShape = rowsShape;
|
|
3252
|
+
exports.singleKeyShape = singleKeyShape;
|
|
1636
3253
|
exports.stepShape = stepShape;
|
|
3254
|
+
exports.tableSchema = tableSchema;
|
|
3255
|
+
exports.tableSpecShape = tableSpecShape;
|
|
1637
3256
|
exports.taskDraftShape = taskDraftShape;
|
|
1638
3257
|
exports.terminalToolCode = terminalToolCode;
|
|
1639
3258
|
exports.workflowDraftShape = workflowDraftShape;
|