@orkestrel/tool 0.0.6 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3372 +1,208 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  let _orkestrel_contract = require("@orkestrel/contract");
3
- let _orkestrel_terminal = require("@orkestrel/terminal");
4
- let _orkestrel_database = require("@orkestrel/database");
5
- let _orkestrel_relation = require("@orkestrel/relation");
6
- let _orkestrel_agent = require("@orkestrel/agent");
7
- let _orkestrel_workflow = require("@orkestrel/workflow");
8
- //#region src/core/constants.ts
9
- /**
10
- * The name {@link import('./factories.js').createAgentTool} advertises by default — the key a
11
- * model calls and the `ToolManagerInterface` (`@orkestrel/agent`) registers under.
12
- */
13
- var AGENT_TOOL_NAME = "agent";
14
- /**
15
- * The maximum nesting depth a delegation chain (agent tool → sub-agent → agent tool → …) may
16
- * reach — the bound {@link import('./factories.js').createAgentTool}'s depth/cycle guard
17
- * enforces.
18
- *
19
- * @remarks
20
- * Deliberately a SEPARATE constant from {@link MAX_WORKFLOW_DEPTH} (rather than the two guards
21
- * sharing one reference): the two guards bound DIFFERENT chains (workflow nesting vs. agent
22
- * delegation) that happen to share a value today, and keeping this bound decoupled means a
23
- * future change to one never silently shifts the other. Same numeric value by convention, not
24
- * by shared reference.
25
- */
26
- var AGENT_TOOL_DEPTH = 8;
27
- /**
28
- * The DESCRIPTION {@link import('./factories.js').createAgentTool} advertises — a short guide
29
- * that teaches a model how to delegate a task to a sub-agent.
30
- *
31
- * @remarks
32
- * Mirrors {@link WORKFLOW_TOOL_DESCRIPTION} / `WORKSPACE_TOOL_DESCRIPTION`'s teaching style: names
33
- * the required `task` field, and documents the optional per-call `provider` / `tools` /
34
- * `system` overrides.
35
- */
36
- /**
37
- * The lean {@link import('@orkestrel/agent').ToolInterface.summary} {@link import('./factories.js').createAgentTool}
38
- * advertises in place of {@link AGENT_TOOL_DESCRIPTION} — a `ToolManagerInterface.definitions()`
39
- * (`@orkestrel/agent`) advertises `summary ?? description`, so this one-sentence text stands in
40
- * for the full teaching description; the full text stays retrievable via
41
- * {@link import('./factories.js').createDescribeTool}.
42
- */
43
- var AGENT_TOOL_SUMMARY = "Delegate a task to a sub-agent and return its result; each call runs one sub-agent turn to completion. Call describe('agent') for the optional provider/tools/system overrides.";
44
- var AGENT_TOOL_DESCRIPTION = [
45
- "Delegate a task to a sub-agent and return its result. Every call runs ONE sub-agent turn to completion.",
46
- "",
47
- "Required:",
48
- " task - the instructions the sub-agent should carry out.",
49
- "Optional overrides (default to the values this tool was configured with):",
50
- " provider - the registry key of the model/provider the sub-agent runs against.",
51
- " tools - registry keys of the tools loaded into the sub-agent (replaces the default list, not merged).",
52
- " system - a system prompt seeding the sub-agent's context (replaces the default).",
53
- "Example:",
54
- JSON.stringify({ task: "Summarize the attached notes in three bullet points." })
55
- ].join("\n");
56
- /**
57
- * The maximum nesting depth a workflow → agent → workflow chain may reach — the bound
58
- * {@link import('./factories.js').createAgentFunction} and
59
- * {@link import('./factories.js').createWorkflowTool}'s depth/cycle guards enforce.
60
- *
61
- * @remarks
62
- * OWNED here now (ported from `@orkestrel/workflow`, whose engine no longer uses it — only the
63
- * tool-authoring guards this package now owns consume it). The limit lives in ONE place: an
64
- * agent-function-wrapped agent running at this depth can no longer author + run a NESTED
65
- * workflow through its bound workflow tool (that would be depth `MAX_WORKFLOW_DEPTH + 1`), so
66
- * the over-deep invocation is REJECTED (a typed `DEPTH` `WorkflowError` throw, `@orkestrel/workflow`).
67
- */
68
- var MAX_WORKFLOW_DEPTH = 8;
69
- /**
70
- * The name {@link import('./factories.js').createWorkflowTool} advertises by default — the key a
71
- * model calls and the `ToolManagerInterface` (`@orkestrel/agent`) registers under, and the name
72
- * {@link import('./factories.js').createAgentFunction} binds the depth/cycle-aware workflow tool
73
- * under onto a wrapped agent's `context.tools`.
74
- *
75
- * @remarks
76
- * OWNED here now (ported from `@orkestrel/workflow`). The propagation seam's well-known key: when
77
- * `createAgentFunction`'s `runner` option is supplied, it adds a `createWorkflowTool`-built tool
78
- * under this name to the agent's `context.tools`, so it can author + run a NESTED workflow
79
- * (bounded by {@link MAX_WORKFLOW_DEPTH}).
80
- */
81
- var WORKFLOW_TOOL_NAME = "workflow";
82
- /**
83
- * A complete FLAT authoring example — the PRIMARY way a small model authors a workflow through
84
- * {@link import('./factories.js').createWorkflowTool}: `{ name, steps: [{ name }] }`.
85
- *
86
- * @remarks
87
- * OWNED here now (ported from `@orkestrel/workflow`). Each step becomes a one-task phase, in
88
- * order; a step's `name` is a REGISTERED behavior name (not a label) — the registry key its
89
- * task's `run` resolves against. The tool expands this
90
- * ({@link import('./helpers.js').expandSteps}) into a valid `WorkflowDefinition`
91
- * (`@orkestrel/workflow`). It is embedded VERBATIM in {@link WORKFLOW_TOOL_DESCRIPTION}.
92
- */
93
- var WORKFLOW_TOOL_FLAT_EXAMPLE = Object.freeze({
94
- name: "release",
95
- steps: Object.freeze([Object.freeze({ name: "compile" }), Object.freeze({ name: "publish" })])
96
- });
97
- /**
98
- * A minimal NESTED authoring example — the ADVANCED escape-hatch form a model may use instead of
99
- * the flat shape: a full `WorkflowDefinition` (`@orkestrel/workflow`).
100
- *
101
- * @remarks
102
- * OWNED here now (ported from `@orkestrel/workflow`). The full four-level form, documented in
103
- * {@link WORKFLOW_TOOL_DESCRIPTION} as the advanced alternative. It is embedded VERBATIM.
104
- */
105
- var WORKFLOW_TOOL_NESTED_EXAMPLE = Object.freeze({
106
- id: "release",
107
- name: "Release",
108
- phases: Object.freeze([Object.freeze({
109
- id: "build",
110
- name: "Build",
111
- tasks: Object.freeze([Object.freeze({
112
- id: "compile",
113
- name: "Compile",
114
- run: "compile"
115
- })])
116
- })])
117
- });
118
- /**
119
- * The DESCRIPTION {@link import('./factories.js').createWorkflowTool} advertises — a multi-line
120
- * guide that teaches a small model how to author a complete workflow tree.
121
- *
122
- * @remarks
123
- * OWNED here now (ported from `@orkestrel/workflow`). Presents the SIMPLE flat shape
124
- * (`{ name, steps: [{ name }] }`) as the PRIMARY way with one complete worked example
125
- * ({@link WORKFLOW_TOOL_FLAT_EXAMPLE}), names that a step's `name` is a REGISTERED name (not a
126
- * human label), and documents the full nested `WorkflowDefinition` as the ADVANCED form with a
127
- * minimal example ({@link WORKFLOW_TOOL_NESTED_EXAMPLE}). The `parameters` the tool advertises
128
- * are the FLAT shape's schema; the nested form is the documented escape-hatch (the tool accepts
129
- * both).
130
- */
131
- /**
132
- * The lean {@link import('@orkestrel/agent').ToolInterface.summary} {@link import('./factories.js').createWorkflowTool}
133
- * advertises in place of {@link WORKFLOW_TOOL_DESCRIPTION} — a `ToolManagerInterface.definitions()`
134
- * (`@orkestrel/agent`) advertises `summary ?? description`, so this one-sentence text stands in
135
- * for the full teaching description; the full text stays retrievable via
136
- * {@link import('./factories.js').createDescribeTool}.
137
- */
138
- var WORKFLOW_TOOL_SUMMARY = "Author and run a multi-phase workflow in one call — phases run in sequence, tasks within a phase run concurrently. Call describe('workflow') for the full authoring schema and examples.";
139
- var WORKFLOW_TOOL_DESCRIPTION = [
140
- "Author and run a workflow (phases run sequentially, the tasks within a phase run concurrently) in one call.",
141
- "",
142
- "SIMPLEST way — a flat list of steps. Each step runs one registered behavior; steps run one after another:",
143
- " { \"name\": \"<workflow name>\", \"steps\": [ { \"name\": \"<registered name>\" }, ... ] }",
144
- "- a step's \"name\" is a REGISTERED behavior name (a registry key), NOT a human label.",
145
- "- the top-level \"name\" (the workflow name) is optional. Ids are filled in for you.",
146
- "Example:",
147
- JSON.stringify(WORKFLOW_TOOL_FLAT_EXAMPLE),
148
- "",
149
- "ADVANCED — the full nested form, for multi-task phases or explicit ids. A workflow has phases; a phase has tasks; a task has a \"run\" (a registered behavior name):",
150
- JSON.stringify(WORKFLOW_TOOL_NESTED_EXAMPLE),
151
- "In the nested form you may omit any \"id\"/\"name\" and they are filled in positionally; a provided one is kept."
152
- ].join("\n");
153
- /**
154
- * The name {@link import('./factories.js').createWorkspaceTool} advertises by default — the key a
155
- * model calls and the `ToolManagerInterface` (`@orkestrel/agent`) registers under.
156
- *
157
- * @remarks
158
- * OWNED here now (ported from `@orkestrel/agent`).
159
- */
160
- var WORKSPACE_TOOL_NAME = "workspace";
161
- /**
162
- * A valid `WorkspaceOperation` (`@orkestrel/agent`) object — the canonical example embedded
163
- * VERBATIM in {@link WORKSPACE_TOOL_DESCRIPTION}.
164
- *
165
- * @remarks
166
- * OWNED here now (ported from `@orkestrel/agent`). A `'write'` op (the most common authoring
167
- * action): create or overwrite `notes.txt` with `hello`. Frozen so it cannot be mutated in
168
- * place.
169
- */
170
- var WORKSPACE_TOOL_EXAMPLE = Object.freeze({
171
- operation: "write",
172
- path: "notes.txt",
173
- content: "hello"
174
- });
175
- /**
176
- * The DESCRIPTION {@link import('./factories.js').createWorkspaceTool} advertises — a multi-line
177
- * guide that teaches a small model how to drive a workspace through the single
178
- * `operation`-keyed tool.
179
- *
180
- * @remarks
181
- * OWNED here now (ported from `@orkestrel/agent`). Mirrors {@link WORKFLOW_TOOL_DESCRIPTION}'s
182
- * teaching style: names the `operation` discriminant field, enumerates all 13 operations with
183
- * their FLAT fields, gives a worked example for the common ones, and embeds
184
- * {@link WORKSPACE_TOOL_EXAMPLE} verbatim.
185
- */
186
- /**
187
- * The lean {@link import('@orkestrel/agent').ToolInterface.summary} {@link import('./factories.js').createWorkspaceTool}
188
- * advertises in place of {@link WORKSPACE_TOOL_DESCRIPTION} — a `ToolManagerInterface.definitions()`
189
- * (`@orkestrel/agent`) advertises `summary ?? description`, so this one-sentence text stands in
190
- * for the full teaching description; the full text stays retrievable via
191
- * {@link import('./factories.js').createDescribeTool}.
192
- */
193
- var WORKSPACE_TOOL_SUMMARY = "Read and edit files in a workspace — one operation per call (read, write, list, search, replace, splice, move, remove, plus workspace switching), chosen by the 'operation' field. Call describe('workspace') for the full operation list and fields.";
194
- var WORKSPACE_TOOL_DESCRIPTION = [
195
- "Read and edit files in a workspace. Every call is ONE operation, chosen by the \"operation\" field.",
196
- "All file operations act on the ACTIVE workspace; use \"workspaces\" then \"switch\" to move between workspaces.",
197
- "",
198
- "Operations (each takes the fields listed):",
199
- "- read { \"operation\": \"read\", \"path\": \"<file>\" } — return the file's text.",
200
- "- list { \"operation\": \"list\" } — list every file in the active workspace (path, state, size, lines, kind).",
201
- "- has { \"operation\": \"has\", \"path\": \"<file>\" } — whether the file exists.",
202
- "- search { \"operation\": \"search\", \"query\": \"<text>\", \"regex\"?: bool, \"exact\"?: bool, \"limit\"?: int } — find lines matching the query across all files.",
203
- "- replace { \"operation\": \"replace\", \"query\": \"<text>\", \"replacement\": \"<text>\", \"regex\"?: bool, \"exact\"?: bool, \"limit\"?: int } — replace matches across all files.",
204
- "- write { \"operation\": \"write\", \"path\": \"<file>\", \"content\": \"<text>\" } — create or overwrite the whole file.",
205
- "- splice { \"operation\": \"splice\", \"path\": \"<file>\", \"content\": \"<text>\", \"fromLine\": int, \"fromColumn\": int, \"toLine\": int, \"toColumn\": int } — replace a 1-based range (from inclusive, to exclusive) with content.",
206
- "- prepend { \"operation\": \"prepend\", \"path\": \"<file>\", \"content\": \"<text>\" } — add content to the start of the file.",
207
- "- append { \"operation\": \"append\", \"path\": \"<file>\", \"content\": \"<text>\" } — add content to the end of the file.",
208
- "- move { \"operation\": \"move\", \"from\": \"<file>\", \"to\": \"<file>\" } — rename / move a file.",
209
- "- remove { \"operation\": \"remove\", \"path\": \"<file>\" } — delete a file.",
210
- "- workspaces { \"operation\": \"workspaces\" } — list the workspaces you can switch between (each id, file count, active).",
211
- "- switch { \"operation\": \"switch\", \"id\": \"<id>\" } — make the workspace with that id active (ids come from \"workspaces\").",
212
- "",
213
- "Notes: lines and columns are 1-based (column 1 is the first character). \"regex\" defaults to false (a literal substring), \"exact\" defaults to true (case-sensitive). \"search\"/\"replace\"/\"splice\" act only on text files. Editing with no active workspace auto-creates one.",
214
- "",
215
- "Example — write a file:",
216
- JSON.stringify(WORKSPACE_TOOL_EXAMPLE)
217
- ].join("\n");
218
- /**
219
- * The name {@link import('./factories.js').createDescribeTool} advertises by default — the key a
220
- * model calls and the `ToolManagerInterface` (`@orkestrel/agent`) registers under.
221
- *
222
- * @remarks
223
- * Net-new: pairs with the other three tools' lean {@link AGENT_TOOL_SUMMARY} /
224
- * {@link WORKFLOW_TOOL_SUMMARY} / {@link WORKSPACE_TOOL_SUMMARY} — a model that reads only the
225
- * advertised summary can call `describe` with that tool's registered name to get its full
226
- * teaching description back.
227
- */
228
- var DESCRIBE_TOOL_NAME = "describe";
229
- /**
230
- * The lean {@link import('@orkestrel/agent').ToolInterface.summary} {@link import('./factories.js').createDescribeTool}
231
- * advertises — this tool needs no teaching of its own, so its summary and description are both
232
- * short.
233
- */
234
- var DESCRIBE_TOOL_SUMMARY = "Return the full description of a named registered tool.";
235
- /**
236
- * The DESCRIPTION {@link import('./factories.js').createDescribeTool} advertises.
237
- *
238
- * @remarks
239
- * Deliberately short — unlike the workflow / workspace / agent tools, this one has no authoring
240
- * schema or multi-step protocol to teach.
241
- */
242
- var DESCRIBE_TOOL_DESCRIPTION = "Return the full description of a registered tool by its name. Required: name - the registered tool name (see another tool listing for available names).";
243
- /**
244
- * The name {@link import('./factories.js').createPromptTool} advertises by default — the key a
245
- * model calls and the `ToolManagerInterface` (`@orkestrel/agent`) registers under.
246
- */
247
- var PROMPT_TOOL_NAME = "ask";
248
- /**
249
- * The lean {@link import('@orkestrel/agent').ToolInterface.summary} {@link import('./factories.js').createPromptTool}
250
- * advertises in place of {@link PROMPT_TOOL_DESCRIPTION} — a `ToolManagerInterface.definitions()`
251
- * (`@orkestrel/agent`) advertises `summary ?? description`, so this one-sentence text stands in
252
- * for the full teaching description; the full text stays retrievable via
253
- * {@link import('./factories.js').createDescribeTool}.
254
- */
255
- var PROMPT_TOOL_SUMMARY = "Ask another terminal a question and BLOCK until it answers; the call resolves with the answered value. Call describe('ask') for the required fields.";
256
- var PROMPT_TOOL_DESCRIPTION = [
257
- "Ask another terminal a question and block until it answers. This call does not return until the addressed terminal answers, or the prompt fails.",
258
- "",
259
- "Required:",
260
- " to - the terminal name to ask.",
261
- " form - the prompt kind: one of \"input\", \"password\", \"confirm\", \"select\", \"checkbox\", \"editor\".",
262
- " message - the question shown to the answering terminal.",
263
- "Optional:",
264
- " options - form-specific options (e.g. choices for \"select\"/\"checkbox\").",
265
- "A cycle (two terminals asking each other) or an expired prompt fails the call with a typed error.",
266
- "Example:",
267
- JSON.stringify({
268
- to: "reviewer",
269
- form: "confirm",
270
- message: "Approve the release?"
271
- })
272
- ].join("\n");
273
- /**
274
- * The name {@link import('./factories.js').createAnswerTool} advertises by default — the key a
275
- * model calls and the `ToolManagerInterface` (`@orkestrel/agent`) registers under.
276
- */
277
- var ANSWER_TOOL_NAME = "answer";
278
- /**
279
- * The lean {@link import('@orkestrel/agent').ToolInterface.summary} {@link import('./factories.js').createAnswerTool}
280
- * advertises in place of {@link ANSWER_TOOL_DESCRIPTION} — a `ToolManagerInterface.definitions()`
281
- * (`@orkestrel/agent`) advertises `summary ?? description`, so this one-sentence text stands in
282
- * for the full teaching description; the full text stays retrievable via
283
- * {@link import('./factories.js').createDescribeTool}.
284
- */
285
- var ANSWER_TOOL_SUMMARY = "List prompts addressed to this terminal, or answer one by id. Call describe('answer') for the required fields.";
286
- var ANSWER_TOOL_DESCRIPTION = [
287
- "List the prompts currently addressed to this terminal, or answer one of them by id. Every call is ONE operation, chosen by the \"operation\" field.",
288
- "",
289
- "Operations:",
290
- "- pending { \"operation\": \"pending\" } — list every prompt currently addressed to this terminal (id, form, message, options, time).",
291
- "- answer { \"operation\": \"answer\", \"id\": \"<prompt id>\", \"value\": <answer value> } — answer the prompt with that id; \"value\" must match the prompt's form (a string for \"input\"/\"password\"/\"editor\", a boolean for \"confirm\", a choice for \"select\", an array of choices for \"checkbox\").",
292
- "Example — list pending prompts:",
293
- JSON.stringify({ operation: "pending" }),
294
- "Example — answer one:",
295
- JSON.stringify({
296
- operation: "answer",
297
- id: "abc123",
298
- value: true
299
- })
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");
530
- //#endregion
531
- //#region src/core/errors.ts
532
- /**
533
- * Thrown by {@link import('./factories.js').createAgentTool}'s and
534
- * {@link import('./factories.js').createDescribeTool}'s handlers on every failure path — a
535
- * malformed / unresolvable call or an unknown tool name (`TOOL`), a delegation that would
536
- * exceed the configured depth bound or re-enter an ancestor (`DEPTH`), a prompt cycle
537
- * (`DEADLOCK`), a prompt that expired before it was answered (`EXPIRE`), or an answer that
538
- * failed to apply (`ANSWER`) — the last three thrown by
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`.
543
- *
544
- * @remarks
545
- * Carries a machine-readable `code` (see {@link import('./types.js').AgentToolErrorCode}) and
546
- * an optional `context` bag for structured diagnostics. The `ToolManagerInterface`
547
- * (`@orkestrel/agent`) isolates every throw into the canonical tool result's top-level `error`
548
- * (AGENTS §14) — nothing escapes the run.
549
- *
550
- * @example
551
- * ```ts
552
- * import { AgentToolError, isAgentToolError } from '@src/core'
553
- *
554
- * try {
555
- * throw new AgentToolError('TOOL', 'task is required')
556
- * } catch (error) {
557
- * if (isAgentToolError(error)) console.log(error.code) // 'TOOL'
558
- * }
559
- * ```
560
- */
561
- var AgentToolError = class extends Error {
562
- code;
563
- constructor(code, message, context) {
564
- super(message);
565
- this.name = "AgentToolError";
566
- this.code = code;
567
- if (context !== void 0) this.context = context;
568
- }
569
- };
570
- /**
571
- * Type guard narrowing an unknown caught value to an {@link AgentToolError}.
572
- *
573
- * @param value - The value to test (typically a `catch` binding)
574
- * @returns `true` when `value` is an {@link AgentToolError}
575
- *
576
- * @example
577
- * ```ts
578
- * import { isAgentToolError } from '@src/core'
579
- *
580
- * try {
581
- * // ...
582
- * } catch (error) {
583
- * if (isAgentToolError(error)) console.log(error.code)
584
- * }
585
- * ```
586
- */
587
- function isAgentToolError(value) {
588
- return value instanceof AgentToolError;
589
- }
590
- //#endregion
591
- //#region src/core/shapers.ts
592
- /**
593
- * The shape of {@link import('./factories.js').createPromptTool}'s call arguments — `to` (the
594
- * terminal identity to address), `form` (which of the six {@link import('@orkestrel/terminal').PromptType}
595
- * forms to ask), `message`, an optional `timeout` override, and every per-form optional field
596
- * FLATTENED onto one object (mirrors `workspaceToolShape`'s flat-arm style, but a single shared
597
- * shape rather than a discriminated union — `form` alone does not vary the REQUIRED fields, only
598
- * which of the optional ones apply, so a flat shape stays faithful without duplicating `to` /
599
- * `message` / `timeout` across six near-identical arms).
600
- *
601
- * @remarks
602
- * `choices` backs `'select'` / `'checkbox'`; `default` backs `'input'` / `'confirm'` / `'select'`
603
- * (a string for the first two forms' text default, `'true'`/`'false'` string for confirm — the
604
- * contract layer cannot vary a field's type by a sibling field's value, so `default` stays a
605
- * string and the handler coerces per form); `mask` backs `'password'`; `min` / `max` backs
606
- * `'checkbox'`; `validate` (declarative only) backs the four text-shaped forms
607
- * (`'input'` / `'password'` / `'confirm'` / `'editor'`).
608
- */
609
- var promptToolShape = (0, _orkestrel_contract.objectShape)({
610
- to: (0, _orkestrel_contract.stringShape)({
611
- min: 1,
612
- description: "The terminal identity to address the prompt to."
613
- }),
614
- form: (0, _orkestrel_contract.literalShape)([
615
- "input",
616
- "password",
617
- "confirm",
618
- "select",
619
- "checkbox",
620
- "editor"
621
- ], { description: "Which prompt form to ask." }),
622
- message: (0, _orkestrel_contract.stringShape)({
623
- min: 1,
624
- description: "The prompt's question."
625
- }),
626
- default: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({ description: "The default answer if the responder submits blank — 'input' / 'editor' text, 'confirm' 'true'/'false', or a 'select' choice value." })),
627
- choices: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.objectShape)({
628
- name: (0, _orkestrel_contract.stringShape)({
629
- min: 1,
630
- description: "The choice label shown to the answering party."
631
- }),
632
- value: (0, _orkestrel_contract.stringShape)({
633
- min: 1,
634
- description: "The value submitted when this choice is picked."
635
- }),
636
- description: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({ description: "An optional one-line elaboration." }))
637
- }), { description: "The selectable choices for 'select' / 'checkbox'." })),
638
- mask: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({
639
- min: 1,
640
- description: "The mask character 'password' renders in place of input."
641
- })),
642
- min: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
643
- min: 0,
644
- description: "The minimum number of 'checkbox' selections required."
645
- })),
646
- max: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
647
- min: 0,
648
- description: "The maximum number of 'checkbox' selections allowed."
649
- })),
650
- validate: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.objectShape)({
651
- required: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Reject an empty (trimmed) input." })),
652
- minimum: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
653
- min: 0,
654
- description: "Reject an input shorter than this many characters."
655
- })),
656
- maximum: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
657
- min: 0,
658
- description: "Reject an input longer than this many characters."
659
- })),
660
- pattern: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({ description: "Reject an input that fails this regular-expression source." })),
661
- email: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Require a valid email-address shape." })),
662
- url: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Require a valid URL shape." })),
663
- numeric: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Require a numeric value." })),
664
- integer: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Require an integer value." })),
665
- alphanumeric: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Require letters and digits only." }))
666
- })),
667
- timeout: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
668
- min: 0,
669
- description: "Milliseconds to wait before the prompt expires."
670
- }))
671
- });
672
- /**
673
- * The shape of {@link import('./factories.js').createAnswerTool}'s call arguments — discriminated
674
- * by `operation`: `'pending'` lists the prompts addressed to this tool's terminal, `'answer'`
675
- * resolves one by `id` with a `value`.
676
- *
677
- * @remarks
678
- * `value`'s type varies by the ORIGINAL prompt's form (`string` for `'input'` / `'password'` /
679
- * `'select'` / `'editor'`, `boolean` for `'confirm'`, `readonly string[]` for `'checkbox'`) —
680
- * `unionShape(stringShape(), booleanShape(), arrayShape(stringShape()))` expresses that
681
- * union directly, so `value` is typed as the full `string | boolean | readonly string[]` union
682
- * here (no lossy string-only fallback needed).
683
- */
684
- var answerToolShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.objectShape)({ operation: (0, _orkestrel_contract.literalShape)(["pending"], { description: "List the prompts currently addressed to this terminal." }) }), (0, _orkestrel_contract.objectShape)({
685
- operation: (0, _orkestrel_contract.literalShape)(["answer"], { description: "Answer one pending prompt by id." }),
686
- id: (0, _orkestrel_contract.stringShape)({
687
- min: 1,
688
- description: "The id of the pending prompt to answer."
689
- }),
690
- value: (0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.stringShape)({ description: "A text / select / editor answer." }), (0, _orkestrel_contract.booleanShape)({ description: "A confirm answer." }), (0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.stringShape)(), { description: "A checkbox answer — the checked values." }))
691
- }));
692
- /**
693
- * The shape of {@link import('./types.js').AgentToolArguments} —
694
- * {@link import('./factories.js').createAgentTool}'s advertised `parameters`.
695
- *
696
- * @remarks
697
- * `task` is the only required field (a non-empty string); `provider` / `tools` / `system`
698
- * are per-call overrides of the tool's own configured defaults.
699
- */
700
- var agentToolShape = (0, _orkestrel_contract.objectShape)({
701
- task: (0, _orkestrel_contract.stringShape)({
702
- min: 1,
703
- description: "The instructions the sub-agent should carry out."
704
- }),
705
- provider: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({
706
- min: 1,
707
- description: "Registry key of the provider to run the sub-agent against (overrides the default)."
708
- })),
709
- tools: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.stringShape)({ min: 1 }), { description: "Registry keys of the tools loaded into the sub-agent (replaces the default list)." })),
710
- system: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({ description: "A system prompt seeding the sub-agent's context (overrides the default)." }))
711
- });
712
- /**
713
- * The shape of {@link import('./types.js').DescribeToolArguments} —
714
- * {@link import('./factories.js').createDescribeTool}'s advertised `parameters`.
715
- *
716
- * @remarks
717
- * `name` is the only field (a non-empty string) — the registered tool name to look up.
718
- */
719
- var describeToolShape = (0, _orkestrel_contract.objectShape)({ name: (0, _orkestrel_contract.stringShape)({
720
- min: 1,
721
- description: "The registered name of the tool whose full description to return."
722
- }) });
723
- /**
724
- * The shape of a {@link import('./types.js').TaskDraft} — identical to a strict task shape
725
- * EXCEPT `id` and `name` are OPTIONAL.
726
- */
727
- var taskDraftShape = (0, _orkestrel_contract.objectShape)({
728
- id: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({
729
- min: 1,
730
- description: "Task id; auto-filled when omitted."
731
- })),
732
- name: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({
733
- min: 1,
734
- description: "Task name; defaults to the id when omitted."
735
- })),
736
- description: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({ description: "Optional task description." })),
737
- run: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({
738
- min: 1,
739
- description: "The registered behavior name to invoke (a registry key, not a label); omitted has no handler."
740
- })),
741
- retries: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
742
- min: 0,
743
- description: "Extra attempts after the first on failure; overrides the phase default. Omitted means none."
744
- })),
745
- timeout: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
746
- min: 0,
747
- description: "Per-attempt deadline in milliseconds; overrides the phase default. Omitted means no deadline."
748
- }))
749
- });
750
- /**
751
- * The shape of a PHASE in a draft workflow — identical to a strict phase shape EXCEPT `id` and
752
- * `name` are OPTIONAL, and its tasks are {@link taskDraftShape}s.
753
- */
754
- var phaseDraftShape = (0, _orkestrel_contract.objectShape)({
755
- id: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({
756
- min: 1,
757
- description: "Phase id; auto-filled when omitted."
758
- })),
759
- name: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({
760
- min: 1,
761
- description: "Phase name; defaults to the id when omitted."
762
- })),
763
- description: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({ description: "Optional phase description." })),
764
- tasks: (0, _orkestrel_contract.arrayShape)(taskDraftShape, { description: "The phase tasks; they run CONCURRENTLY." }),
765
- concurrency: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
766
- min: 1,
767
- description: "Max tasks in flight at once (a resource throttle); omitted means unbounded."
768
- })),
769
- bail: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.literalShape)([true, false], { description: "Per-phase failure-policy override; omitted inherits the workflow bail." }))
770
- });
771
- /**
772
- * The shape of a DRAFT workflow — identical to a strict workflow shape EXCEPT `id` and `name`
773
- * are OPTIONAL at all three levels (workflow / phase / task), so a small model can omit the six
774
- * identity strings and let the tool synthesize them positionally.
775
- *
776
- * @remarks
777
- * The lenient counterpart {@link import('./factories.js').createWorkflowDraftContract} compiles.
778
- * `run` stays required on the strict form; a provided `id` / `name` still has `minLength: 1` (so
779
- * an explicitly-empty `id: ''` is REJECTED, not auto-filled). After
780
- * {@link import('./helpers.js').completeDraft} fills the missing ids/names, the result is
781
- * validated against the STRICT `createWorkflowContract` (`@orkestrel/workflow`) gate before
782
- * running.
783
- */
784
- var workflowDraftShape = (0, _orkestrel_contract.objectShape)({
785
- id: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({
786
- min: 1,
787
- description: "Workflow id; auto-filled when omitted."
788
- })),
789
- name: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({
790
- min: 1,
791
- description: "Workflow name; defaults to the id when omitted."
792
- })),
793
- description: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({ description: "Optional workflow description." })),
794
- phases: (0, _orkestrel_contract.arrayShape)(phaseDraftShape, { description: "The workflow phases; they run SEQUENTIALLY, in order." }),
795
- bail: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.literalShape)([true, false], { description: "Failure policy: false (default) continues gracefully, true halts on the first failure." }))
796
- });
797
- /**
798
- * The shape of ONE flat step — `{ name }` — the building block of {@link workflowStepsShape}.
799
- *
800
- * @remarks
801
- * `name` is the REGISTERED behavior name the step runs (it becomes the task's `run`). The tool
802
- * expands each step into a one-task phase, in order ({@link import('./helpers.js').expandSteps}).
803
- */
804
- var stepShape = (0, _orkestrel_contract.objectShape)({ name: (0, _orkestrel_contract.stringShape)({
805
- min: 1,
806
- description: "The registered behavior name this step runs (becomes the task run)."
807
- }) });
808
- /**
809
- * The FLAT authoring shape {@link import('./factories.js').createWorkflowTool} advertises as its
810
- * `parameters` — the simplest surface a small model can fill: `{ name?, steps: [{ name }] }`.
811
- *
812
- * @remarks
813
- * A deliberately-reduced surface: a flat ordered list of steps, each a `{ name }`. The tool
814
- * EXPANDS it ({@link import('./helpers.js').expandSteps}) into a full
815
- * {@link import('./types.js').WorkflowDefinition} — one one-task phase per step, in order —
816
- * then validates against the STRICT `createWorkflowContract` (`@orkestrel/workflow`) gate. The
817
- * full nested form is STILL accepted by the tool (it branches on the args' shape) and is
818
- * documented as the advanced escape-hatch in the tool's description — but THIS is what
819
- * `parameters` advertises.
820
- */
821
- var workflowStepsShape = (0, _orkestrel_contract.objectShape)({
822
- name: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({
823
- min: 1,
824
- description: "Optional workflow name."
825
- })),
826
- steps: (0, _orkestrel_contract.arrayShape)(stepShape, { description: "The ordered steps to run, one after another (each becomes a one-task phase)." })
827
- });
828
- /**
829
- * The shape of a {@link import('./types.js').WorkspaceOperation} — a descriptive tagged union
830
- * over the 13 workspace edit / read / navigation operations, discriminated by the `operation`
831
- * literal (never a bare `kind`; AGENTS §4.4). Each variant leads with its `operation`
832
- * discriminant then its FLAT fields, every field via `stringShape` / `optionalShape` /
833
- * `integerShape({ min: 1 })` / `booleanShape`, each carrying a strong field-level `description`.
834
- *
835
- * @remarks
836
- * The union compiles to an `anyOf` JSON Schema + a `unionOf` guard + a first-match parser
837
- * automatically ({@link import('./factories.js').createWorkspaceTool} types the result to the
838
- * hand-written {@link import('./types.js').WorkspaceOperation}). `limit` and the four `'splice'`
839
- * caret components are POSITIVE integers (`integerShape({ min: 1 })`); `regex` / `exact` are
840
- * `optionalShape(booleanShape(...))`. The two REGISTRY arms — `workspaces` (list the workspaces
841
- * the model can move between) and `switch` (re-point the active one by `id`) — let a model
842
- * DISCOVER then CHOOSE which workspace the edit / read arms target.
843
- */
844
- var workspaceToolShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.objectShape)({
845
- operation: (0, _orkestrel_contract.literalShape)(["read"], { description: "Read a whole text file's text by path." }),
846
- path: (0, _orkestrel_contract.stringShape)({ description: "The path of the file to read." })
847
- }), (0, _orkestrel_contract.objectShape)({ operation: (0, _orkestrel_contract.literalShape)(["list"], { description: "List every file in the workspace." }) }), (0, _orkestrel_contract.objectShape)({
848
- operation: (0, _orkestrel_contract.literalShape)(["has"], { description: "Check whether a file exists at the path." }),
849
- path: (0, _orkestrel_contract.stringShape)({ description: "The path to check for." })
850
- }), (0, _orkestrel_contract.objectShape)({
851
- operation: (0, _orkestrel_contract.literalShape)(["search"], { description: "Search every text file for a query, returning each hit." }),
852
- query: (0, _orkestrel_contract.stringShape)({ description: "The text (or regular-expression source) to search for." }),
853
- regex: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Treat the query as a regular expression. Defaults to false (a literal substring)." })),
854
- exact: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Match case-sensitively. Defaults to true (set false for case-insensitive)." })),
855
- limit: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
856
- min: 1,
857
- description: "Stop after this many matches across all files. Omitted means unlimited."
858
- }))
859
- }), (0, _orkestrel_contract.objectShape)({
860
- operation: (0, _orkestrel_contract.literalShape)(["replace"], { description: "Replace a query with a replacement across every text file." }),
861
- query: (0, _orkestrel_contract.stringShape)({ description: "The text (or regular-expression source) to replace." }),
862
- replacement: (0, _orkestrel_contract.stringShape)({ description: "The text to substitute for each match." }),
863
- regex: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Treat the query as a regular expression. Defaults to false (a literal substring)." })),
864
- exact: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Match case-sensitively. Defaults to true (set false for case-insensitive)." })),
865
- limit: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
866
- min: 1,
867
- description: "Stop after this many replacements across all files. Omitted means unlimited."
868
- }))
869
- }), (0, _orkestrel_contract.objectShape)({
870
- operation: (0, _orkestrel_contract.literalShape)(["write"], { description: "Create or overwrite a whole file with content." }),
871
- path: (0, _orkestrel_contract.stringShape)({ description: "The path of the file to write." }),
872
- content: (0, _orkestrel_contract.stringShape)({ description: "The full new contents of the file." })
873
- }), (0, _orkestrel_contract.objectShape)({
874
- operation: (0, _orkestrel_contract.literalShape)(["splice"], { description: "Replace a 1-based range of an existing text file (from inclusive, to exclusive) with content." }),
875
- path: (0, _orkestrel_contract.stringShape)({ description: "The path of the text file to edit." }),
876
- content: (0, _orkestrel_contract.stringShape)({ description: "The text to splice in place of the range." }),
877
- fromLine: (0, _orkestrel_contract.integerShape)({
878
- min: 1,
879
- description: "The 1-based start line of the range (inclusive)."
880
- }),
881
- fromColumn: (0, _orkestrel_contract.integerShape)({
882
- min: 1,
883
- description: "The 1-based start column of the range (inclusive; column 1 is the first character)."
884
- }),
885
- toLine: (0, _orkestrel_contract.integerShape)({
886
- min: 1,
887
- description: "The 1-based end line of the range (exclusive)."
888
- }),
889
- toColumn: (0, _orkestrel_contract.integerShape)({
890
- min: 1,
891
- description: "The 1-based end column of the range (exclusive)."
892
- })
893
- }), (0, _orkestrel_contract.objectShape)({
894
- operation: (0, _orkestrel_contract.literalShape)(["prepend"], { description: "Add content to the start of a file (creating it when absent)." }),
895
- path: (0, _orkestrel_contract.stringShape)({ description: "The path of the file to prepend to." }),
896
- content: (0, _orkestrel_contract.stringShape)({ description: "The text to add at the start of the file." })
897
- }), (0, _orkestrel_contract.objectShape)({
898
- operation: (0, _orkestrel_contract.literalShape)(["append"], { description: "Add content to the end of a file (creating it when absent)." }),
899
- path: (0, _orkestrel_contract.stringShape)({ description: "The path of the file to append to." }),
900
- content: (0, _orkestrel_contract.stringShape)({ description: "The text to add at the end of the file." })
901
- }), (0, _orkestrel_contract.objectShape)({
902
- operation: (0, _orkestrel_contract.literalShape)(["move"], { description: "Rename or move a file (overwriting an occupied target)." }),
903
- from: (0, _orkestrel_contract.stringShape)({ description: "The current path of the file." }),
904
- to: (0, _orkestrel_contract.stringShape)({ description: "The new path for the file." })
905
- }), (0, _orkestrel_contract.objectShape)({
906
- operation: (0, _orkestrel_contract.literalShape)(["remove"], { description: "Delete a file from the workspace." }),
907
- path: (0, _orkestrel_contract.stringShape)({ description: "The path of the file to remove." })
908
- }), (0, _orkestrel_contract.objectShape)({ operation: (0, _orkestrel_contract.literalShape)(["workspaces"], { description: "List the workspaces you can move between (each id, file count, and whether it is active), so you can pick an id to switch to." }) }), (0, _orkestrel_contract.objectShape)({
909
- 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." }),
910
- id: (0, _orkestrel_contract.stringShape)({ description: "The id of the workspace to make active (from the \"workspaces\" listing)." })
911
- }));
912
- /** A {@link import('./types.js').ColumnKind} literal — the leaf {@link columnSpecShape} wraps. */
913
- var columnKindShape = (0, _orkestrel_contract.literalShape)([
914
- "string",
915
- "integer",
916
- "number",
917
- "boolean"
918
- ], { description: "A column type: \"string\" | \"integer\" | \"number\" | \"boolean\"." });
919
- /** A {@link import('./types.js').ColumnSpec} — a bare {@link columnKindShape}, or `{ type, optional }`. */
920
- var columnSpecShape = (0, _orkestrel_contract.unionShape)(columnKindShape, (0, _orkestrel_contract.objectShape)({
921
- type: columnKindShape,
922
- optional: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Whether the column may be absent from a row." }))
923
- }));
924
- /** A {@link import('./types.js').TableSpec} — table name to `{ columns }`, each column a {@link columnSpecShape}. */
925
- 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." });
926
- /** One key value — a string or number; the array form (multiple keys, positional) resolves FIRST per AGENTS §9.2. */
927
- 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." }));
928
- /** A loose row — a flat object of column name to JSON value; the array form (multiple rows) resolves FIRST per AGENTS §9.2. */
929
- var rowShape = (0, _orkestrel_contract.recordShape)((0, _orkestrel_contract.jsonShape)(), { description: "A row as a flat object of column name to value." });
930
- /** One or many loose rows — the array form resolves FIRST per AGENTS §9.2. */
931
- var rowsShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.arrayShape)(rowShape, { description: "Multiple rows." }), rowShape);
932
- /** One SERIALIZED WHERE condition — `values` is ALWAYS an array, even for a single-value operator. */
933
- var conditionShape = (0, _orkestrel_contract.objectShape)({
934
- column: (0, _orkestrel_contract.stringShape)({ description: "The column this condition applies to." }),
935
- operator: (0, _orkestrel_contract.literalShape)([
936
- "equals",
937
- "not",
938
- "above",
939
- "below",
940
- "from",
941
- "to",
942
- "between",
943
- "like",
944
- "glob",
945
- "starts",
946
- "ends",
947
- "any",
948
- "none",
949
- "absent",
950
- "present"
951
- ], { description: "The comparison operator." }),
952
- values: (0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.jsonShape)(), { description: "The operand values the operator needs (always an array, even for one value)." }),
953
- connector: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.literalShape)(["and", "or"], { description: "Joins this condition to the next; omit on the last condition." }))
954
- });
955
- /** One sort term. */
956
- var orderShape = (0, _orkestrel_contract.objectShape)({
957
- column: (0, _orkestrel_contract.stringShape)({ description: "The column to sort by." }),
958
- direction: (0, _orkestrel_contract.literalShape)(["ascending", "descending"], { description: "The sort direction." })
959
- });
960
- /** The SERIALIZED criteria form — conditions, order, and pagination. */
961
- var criteriaShape = (0, _orkestrel_contract.objectShape)({
962
- conditions: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)(conditionShape, { description: "The WHERE conditions, folded left to right." })),
963
- order: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)(orderShape, { description: "The sort terms, applied in order." })),
964
- limit: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
965
- min: 0,
966
- description: "Max rows to return."
967
- })),
968
- offset: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
969
- min: 0,
970
- description: "Rows to skip before returning."
971
- }))
972
- });
973
- /**
974
- * The shape of {@link import('./factories.js').createDatabaseTool}'s call arguments —
975
- * discriminated by `operation` into the 12 database operations (`'create'` / `'tables'` /
976
- * `'get'` / `'records'` / `'count'` / `'aggregate'` / `'add'` / `'set'` / `'update'` /
977
- * `'remove'` / `'migrate'` / `'destroy'`).
978
- *
979
- * @remarks
980
- * Every arm carries `id` (the database id). `'create'` / `'migrate'` carry `tables` (the
981
- * {@link import('./types.js').TableSpec} column DSL, compiled via
982
- * {@link import('./helpers.js').expandTables}); `'get'` / `'update'` / `'remove'` carry `key`
983
- * (one key or an array of keys, positional); `'add'` / `'set'` carry `row` (one row or an array of
984
- * rows); `'update'` also carries `changes` (a loose partial row); `'records'` / `'count'` /
985
- * `'aggregate'` carry an optional `criteria` (the SERIALIZED form — `values` is ALWAYS an array,
986
- * even for a single-value operator, so a caller never chains method calls or guesses arity).
987
- */
988
- var databaseToolShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.objectShape)({
989
- operation: (0, _orkestrel_contract.literalShape)(["create"], { description: "Define a new database." }),
990
- id: (0, _orkestrel_contract.stringShape)({
991
- min: 1,
992
- description: "The database id."
993
- }),
994
- tables: tableSpecShape,
995
- driver: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({
996
- min: 1,
997
- description: "The registered driver key. Defaults to \"memory\"."
998
- })),
999
- keys: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.recordShape)((0, _orkestrel_contract.stringShape)(), { description: "Table name to its primary-key column." }))
1000
- }), (0, _orkestrel_contract.objectShape)({
1001
- operation: (0, _orkestrel_contract.literalShape)(["tables"], { description: "List a database's table names." }),
1002
- id: (0, _orkestrel_contract.stringShape)({
1003
- min: 1,
1004
- description: "The database id."
1005
- })
1006
- }), (0, _orkestrel_contract.objectShape)({
1007
- operation: (0, _orkestrel_contract.literalShape)(["get"], { description: "Fetch one or more rows by primary key." }),
1008
- id: (0, _orkestrel_contract.stringShape)({
1009
- min: 1,
1010
- description: "The database id."
1011
- }),
1012
- table: (0, _orkestrel_contract.stringShape)({
1013
- min: 1,
1014
- description: "The table name."
1015
- }),
1016
- key: keyShape
1017
- }), (0, _orkestrel_contract.objectShape)({
1018
- operation: (0, _orkestrel_contract.literalShape)(["records"], { description: "List rows matching criteria." }),
1019
- id: (0, _orkestrel_contract.stringShape)({
1020
- min: 1,
1021
- description: "The database id."
1022
- }),
1023
- table: (0, _orkestrel_contract.stringShape)({
1024
- min: 1,
1025
- description: "The table name."
1026
- }),
1027
- criteria: (0, _orkestrel_contract.optionalShape)(criteriaShape)
1028
- }), (0, _orkestrel_contract.objectShape)({
1029
- operation: (0, _orkestrel_contract.literalShape)(["count"], { description: "Count rows matching criteria." }),
1030
- id: (0, _orkestrel_contract.stringShape)({
1031
- min: 1,
1032
- description: "The database id."
1033
- }),
1034
- table: (0, _orkestrel_contract.stringShape)({
1035
- min: 1,
1036
- description: "The table name."
1037
- }),
1038
- criteria: (0, _orkestrel_contract.optionalShape)(criteriaShape)
1039
- }), (0, _orkestrel_contract.objectShape)({
1040
- operation: (0, _orkestrel_contract.literalShape)(["aggregate"], { description: "Compute an aggregate over a column." }),
1041
- id: (0, _orkestrel_contract.stringShape)({
1042
- min: 1,
1043
- description: "The database id."
1044
- }),
1045
- table: (0, _orkestrel_contract.stringShape)({
1046
- min: 1,
1047
- description: "The table name."
1048
- }),
1049
- function: (0, _orkestrel_contract.literalShape)([
1050
- "count",
1051
- "sum",
1052
- "average",
1053
- "minimum",
1054
- "maximum"
1055
- ], { description: "The aggregate function." }),
1056
- column: (0, _orkestrel_contract.stringShape)({
1057
- min: 1,
1058
- description: "The column to aggregate."
1059
- }),
1060
- criteria: (0, _orkestrel_contract.optionalShape)(criteriaShape)
1061
- }), (0, _orkestrel_contract.objectShape)({
1062
- operation: (0, _orkestrel_contract.literalShape)(["add"], { description: "Insert one or more rows (fails on a duplicate key)." }),
1063
- id: (0, _orkestrel_contract.stringShape)({
1064
- min: 1,
1065
- description: "The database id."
1066
- }),
1067
- table: (0, _orkestrel_contract.stringShape)({
1068
- min: 1,
1069
- description: "The table name."
1070
- }),
1071
- row: rowsShape
1072
- }), (0, _orkestrel_contract.objectShape)({
1073
- operation: (0, _orkestrel_contract.literalShape)(["set"], { description: "Upsert one or more rows." }),
1074
- id: (0, _orkestrel_contract.stringShape)({
1075
- min: 1,
1076
- description: "The database id."
1077
- }),
1078
- table: (0, _orkestrel_contract.stringShape)({
1079
- min: 1,
1080
- description: "The table name."
1081
- }),
1082
- row: rowsShape
1083
- }), (0, _orkestrel_contract.objectShape)({
1084
- operation: (0, _orkestrel_contract.literalShape)(["update"], { description: "Patch one or more existing rows." }),
1085
- id: (0, _orkestrel_contract.stringShape)({
1086
- min: 1,
1087
- description: "The database id."
1088
- }),
1089
- table: (0, _orkestrel_contract.stringShape)({
1090
- min: 1,
1091
- description: "The table name."
1092
- }),
1093
- key: keyShape,
1094
- changes: rowShape
1095
- }), (0, _orkestrel_contract.objectShape)({
1096
- operation: (0, _orkestrel_contract.literalShape)(["remove"], { description: "Delete one or more rows by key." }),
1097
- id: (0, _orkestrel_contract.stringShape)({
1098
- min: 1,
1099
- description: "The database id."
1100
- }),
1101
- table: (0, _orkestrel_contract.stringShape)({
1102
- min: 1,
1103
- description: "The table name."
1104
- }),
1105
- key: keyShape
1106
- }), (0, _orkestrel_contract.objectShape)({
1107
- operation: (0, _orkestrel_contract.literalShape)(["migrate"], { description: "Replace the table layout in place." }),
1108
- id: (0, _orkestrel_contract.stringShape)({
1109
- min: 1,
1110
- description: "The database id."
1111
- }),
1112
- tables: tableSpecShape
1113
- }), (0, _orkestrel_contract.objectShape)({
1114
- operation: (0, _orkestrel_contract.literalShape)(["destroy"], { description: "Drop a database entirely." }),
1115
- id: (0, _orkestrel_contract.stringShape)({
1116
- min: 1,
1117
- description: "The database id."
1118
- })
1119
- }));
1120
- /** One key value — a string or number; the array form (multiple keys, positional) resolves FIRST per AGENTS §9.2. */
1121
- 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." }));
1122
- /** A single row key (not an array) — used by `'link'` / `'unlink'` / `'links'`, which address exactly one owning row. */
1123
- var singleKeyShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.stringShape)({ description: "The owning row key." }), (0, _orkestrel_contract.numberShape)({ description: "The owning row key." }));
1124
- /** Flat dot-path relation include list, expanded via {@link import('./helpers.js').expandInclude}. */
1125
- 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." }));
1126
- /** Which registered relation manager to address — omitted resolves to the sole registered manager. */
1127
- var managerShape = (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({
1128
- min: 1,
1129
- description: "Which registered relation manager to address."
1130
- }));
1131
- /**
1132
- * The shape of {@link import('./factories.js').createRelationTool}'s call arguments —
1133
- * discriminated by `operation` into the 5 relation operations (`'load'` / `'find'` / `'link'` /
1134
- * `'unlink'` / `'links'`).
1135
- *
1136
- * @remarks
1137
- * `'load'` fetches one or more rows (positional key/array) with `include` attached. `'find'`
1138
- * fetches rows (pagination / sort only) with `include` attached. `'link'` / `'unlink'` write /
1139
- * remove a `through` junction row; `'links'` lists a `through` relation's linked keys.
1140
- */
1141
- var relationToolShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.objectShape)({
1142
- operation: (0, _orkestrel_contract.literalShape)(["load"], { description: "Fetch one or more rows by key, with related rows attached." }),
1143
- manager: managerShape,
1144
- model: (0, _orkestrel_contract.stringShape)({
1145
- min: 1,
1146
- description: "The model (table) name."
1147
- }),
1148
- key: relationKeyShape,
1149
- include: includeShape
1150
- }), (0, _orkestrel_contract.objectShape)({
1151
- operation: (0, _orkestrel_contract.literalShape)(["find"], { description: "List rows, with related rows attached." }),
1152
- manager: managerShape,
1153
- model: (0, _orkestrel_contract.stringShape)({
1154
- min: 1,
1155
- description: "The model (table) name."
1156
- }),
1157
- include: includeShape,
1158
- limit: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
1159
- min: 0,
1160
- description: "Max rows to return."
1161
- })),
1162
- offset: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
1163
- min: 0,
1164
- description: "Rows to skip before returning."
1165
- })),
1166
- sort: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({
1167
- min: 1,
1168
- description: "The column to sort by."
1169
- })),
1170
- direction: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.literalShape)(["ascending", "descending"], { description: "The sort direction." }))
1171
- }), (0, _orkestrel_contract.objectShape)({
1172
- operation: (0, _orkestrel_contract.literalShape)(["link"], { description: "Connect two rows through a \"through\" relation." }),
1173
- manager: managerShape,
1174
- model: (0, _orkestrel_contract.stringShape)({
1175
- min: 1,
1176
- description: "The model (table) name."
1177
- }),
1178
- key: singleKeyShape,
1179
- relation: (0, _orkestrel_contract.stringShape)({
1180
- min: 1,
1181
- description: "The \"through\" relation name."
1182
- }),
1183
- target: singleKeyShape
1184
- }), (0, _orkestrel_contract.objectShape)({
1185
- operation: (0, _orkestrel_contract.literalShape)(["unlink"], { description: "Disconnect two rows previously linked through a \"through\" relation." }),
1186
- manager: managerShape,
1187
- model: (0, _orkestrel_contract.stringShape)({
1188
- min: 1,
1189
- description: "The model (table) name."
1190
- }),
1191
- key: singleKeyShape,
1192
- relation: (0, _orkestrel_contract.stringShape)({
1193
- min: 1,
1194
- description: "The \"through\" relation name."
1195
- }),
1196
- target: singleKeyShape
1197
- }), (0, _orkestrel_contract.objectShape)({
1198
- operation: (0, _orkestrel_contract.literalShape)(["links"], { description: "List every key linked to a row through a \"through\" relation." }),
1199
- manager: managerShape,
1200
- model: (0, _orkestrel_contract.stringShape)({
1201
- min: 1,
1202
- description: "The model (table) name."
1203
- }),
1204
- key: singleKeyShape,
1205
- relation: (0, _orkestrel_contract.stringShape)({
1206
- min: 1,
1207
- description: "The \"through\" relation name."
1208
- })
1209
- }));
1210
- /**
1211
- * The shape of {@link import('./factories.js').createInferTool}'s call arguments — one or more
1212
- * example `samples` to infer a JSON Schema from, plus per-call `format` / `enum` toggles and an
1213
- * optional `candidates` array to check against the inferred schema.
1214
- *
1215
- * @remarks
1216
- * `samples` requires at least one element (`min: 1`) — an empty array parses to `undefined`,
1217
- * surfaced by the handler as a typed `TOOL` {@link import('./errors.js').AgentToolError}. When
1218
- * `candidates` is present (any array, including empty), the handler compiles a contract from the
1219
- * freshly inferred schema and checks each candidate against it with a STRICT guard (`.is`, no
1220
- * coercion) — the opposite of {@link import('./factories.js').createEndpointTool}'s NORMALIZING
1221
- * `.parse` enforcement.
1222
- */
1223
- var inferToolShape = (0, _orkestrel_contract.objectShape)({
1224
- samples: (0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.jsonShape)(), {
1225
- min: 1,
1226
- description: "The example values to infer a JSON Schema from (at least one)."
1227
- }),
1228
- format: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Infer string formats (date-time, email, ...) from the samples. Defaults to false." })),
1229
- enum: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Infer enum constraints from repeated literal values. Defaults to false." })),
1230
- candidates: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.jsonShape)(), { description: "Optional values to check against the freshly inferred schema. When present, the tool returns a per-candidate verdict (strict — no coercion) alongside the inferred parameters." }))
1231
- });
1232
- //#endregion
1233
3
  //#region src/core/helpers.ts
1234
4
  /**
1235
- * The ancestry identifier of a workflow in a run chain — `workflow:<id>`.
1236
- *
1237
- * @remarks
1238
- * Namespacing keeps a workflow id and an {@link agentTag} agent name in ONE set without
1239
- * collision, so re-entering a workflow OR an agent already in the chain is a single `includes`
1240
- * check.
1241
- *
1242
- * @param id - The workflow definition's `id`
1243
- * @returns The namespaced ancestry tag (`workflow:<id>`)
1244
- */
1245
- function workflowTag(id) {
1246
- return `workflow:${id}`;
1247
- }
1248
- /**
1249
- * The ancestry identifier of an agent in a run chain — `agent:<name>`.
1250
- *
1251
- * @remarks
1252
- * The agent counterpart of {@link workflowTag}: {@link import('./factories.js').createAgentFunction}
1253
- * / {@link import('./factories.js').createWorkflowTool} guard against re-entering an agent or
1254
- * workflow already in the chain (a typed `DEPTH` `WorkflowError`, `@orkestrel/workflow`). The
1255
- * `agent:` namespace keeps it distinct from a same-string workflow id.
1256
- *
1257
- * @param name - The agent's identifier / registry name
1258
- * @returns The namespaced ancestry tag (`agent:<name>`)
1259
- */
1260
- function agentTag(name) {
1261
- return `agent:${name}`;
1262
- }
1263
- /**
1264
- * Build the plain success summary {@link import('./factories.js').createWorkflowTool} returns on
1265
- * a completed run — the universal tool-handler contract (AGENTS §14): return a plain value on
1266
- * success, appearing identically over BOTH the agent loop and MCP.
1267
- *
1268
- * @remarks
1269
- * The summary is LEAN: the workflow's terminal `status` and the COUNT of settled task results —
1270
- * enough for a caller / model to react without serializing the whole live tree. (It carries no
1271
- * synthetic `id` / `name`: a tool handler has no call id; the `ToolManagerInterface`
1272
- * (`@orkestrel/agent`) supplies the canonical envelope's identity.)
1273
- *
1274
- * @param result - The terminal `WorkflowResult` (`@orkestrel/workflow`) the run produced
1275
- * @returns The plain success summary — `{ status, count }`
1276
- */
1277
- function workflowToolSummary(result) {
1278
- return {
1279
- status: result.status,
1280
- count: result.results.length
1281
- };
1282
- }
1283
- /**
1284
- * Complete a {@link WorkflowDraft} into a strict {@link WorkflowDefinition} — synthesize any
1285
- * MISSING `id` deterministically + positionally, and default any MISSING `name` to its
1286
- * (now-resolved) `id`.
1287
- *
1288
- * @remarks
1289
- * The positional id scheme is stable and human-legible: the workflow is `wf`, phase `i` is
1290
- * `phase-<i>`, and task `j` of that phase is `<phaseId>-task-<j>` (so a provided phase id flows
1291
- * into its tasks' synthesized ids). A PROVIDED `id` / `name` at any level is kept VERBATIM —
1292
- * synthesis touches only the omitted ones. A missing `name` defaults to the resolved `id` (never
1293
- * the other way round), so the result always has both. `run`, `description`, the per-phase
1294
- * `concurrency` / `bail`, the per-task `retries` / `timeout`, and the workflow `bail` carry over
1295
- * unchanged. The result is a complete {@link WorkflowDefinition}; the caller still validates it
1296
- * against the STRICT contract.
1297
- *
1298
- * @param draft - The draft workflow (id/name optional at all three levels)
1299
- * @returns A complete {@link WorkflowDefinition} with every id/name filled
1300
- */
1301
- function completeDraft(draft) {
1302
- const id = draft.id ?? "wf";
1303
- return {
1304
- id,
1305
- name: draft.name ?? id,
1306
- ...draft.description === void 0 ? {} : { description: draft.description },
1307
- phases: draft.phases.map((phase, index) => completePhaseDraft(phase, index)),
1308
- ...draft.bail === void 0 ? {} : { bail: draft.bail }
1309
- };
1310
- }
1311
- /**
1312
- * Complete one {@link PhaseDraft} into a strict phase definition — the per-phase step of
1313
- * {@link completeDraft} (phase `index` → `phase-<index>` when its id is omitted).
1314
- *
1315
- * @param phase - The draft phase
1316
- * @param index - The phase's positional index in the workflow
1317
- * @returns A complete phase definition
1318
- */
1319
- function completePhaseDraft(phase, index) {
1320
- const id = phase.id ?? `phase-${index}`;
1321
- return {
1322
- id,
1323
- name: phase.name ?? id,
1324
- ...phase.description === void 0 ? {} : { description: phase.description },
1325
- tasks: phase.tasks.map((task, taskIndex) => completeTaskDraft(task, id, taskIndex)),
1326
- ...phase.concurrency === void 0 ? {} : { concurrency: phase.concurrency },
1327
- ...phase.bail === void 0 ? {} : { bail: phase.bail }
1328
- };
1329
- }
1330
- /**
1331
- * Complete one {@link TaskDraft} into a strict task definition — the per-task leaf step of
1332
- * {@link completeDraft} (task `index` of phase `<phaseId>` → `<phaseId>-task-<index>` when its id
1333
- * is omitted).
1334
- *
1335
- * @param task - The draft task
1336
- * @param phaseId - The (resolved) parent phase id, so the synthesized task id nests under it
1337
- * @param index - The task's positional index within its phase
1338
- * @returns A complete task definition
1339
- */
1340
- function completeTaskDraft(task, phaseId, index) {
1341
- const id = task.id ?? `${phaseId}-task-${index}`;
1342
- return {
1343
- id,
1344
- name: task.name ?? id,
1345
- ...task.description === void 0 ? {} : { description: task.description },
1346
- ...task.run === void 0 ? {} : { run: task.run },
1347
- ...task.retries === void 0 ? {} : { retries: task.retries },
1348
- ...task.timeout === void 0 ? {} : { timeout: task.timeout }
1349
- };
1350
- }
1351
- /**
1352
- * Expand a flat {@link WorkflowSteps} blob into a strict {@link WorkflowDefinition} — each step
1353
- * becomes a one-task phase, IN ORDER.
1354
- *
1355
- * @remarks
1356
- * The expansion of the tool's ADVERTISED surface: the deliberately-reduced flat form. Each
1357
- * {@link import('./types.js').WorkflowStep} maps to a phase holding exactly one task: the step's
1358
- * `name` becomes the task's `run` (the behavior-registry key). Ids/names are auto-filled
1359
- * positionally — it builds an ids-omitted {@link WorkflowDraft} and delegates to
1360
- * {@link completeDraft}, so the two lenient surfaces share ONE synthesis path (step `i` → phase
1361
- * `phase-<i>`, its task `phase-<i>-task-0`). The optional `name` becomes the workflow's `name`.
1362
- * The result is a complete definition the caller validates against the STRICT contract before
1363
- * running.
1364
- *
1365
- * @param flat - The flat steps blob (`{ name?, steps: [{ name }] }`)
1366
- * @returns A complete {@link WorkflowDefinition} (one one-task phase per step)
1367
- */
1368
- function expandSteps(flat) {
1369
- return completeDraft({
1370
- ...flat.name === void 0 ? {} : { name: flat.name },
1371
- phases: flat.steps.map((step) => ({ tasks: [{ run: step.name }] }))
1372
- });
1373
- }
1374
- /**
1375
- * Normalize an LLM-supplied answer `value` to the type {@link PromptType} `form` expects, so a
1376
- * caller that only ever emits strings can still answer a typed prompt.
1377
- *
1378
- * @remarks
1379
- * `'confirm'` coerces to a `boolean` — a `boolean` passes through, and the strings `'true'` /
1380
- * `'false'` (case-insensitively) map to it; any other string is truthy-coerced via
1381
- * `Boolean(value)`. `'checkbox'` coerces to `readonly string[]` — an array passes through
1382
- * (stringifying each entry), a comma-separated string splits + trims into one, and any other
1383
- * single (non-comma) string becomes a one-item array. Every other form (`'input'` / `'password'`
1384
- * / `'select'` / `'editor'`) coerces to a plain `string` — a string passes through verbatim; a
1385
- * non-string, non-object scalar (`number` / `boolean`) stringifies via `String(value)`; an
1386
- * object or array (no lossless string form) falls back to `''` rather than serializing garbage.
1387
- * Pure and total — never throws.
1388
- *
1389
- * @param form - The {@link PromptType} the answer is being coerced FOR
1390
- * @param value - The raw, LLM-supplied answer value
1391
- * @returns The coerced answer — `boolean` for `'confirm'`, `readonly string[]` for `'checkbox'`,
1392
- * `string` otherwise
1393
- */
1394
- function coerceAnswer(form, value) {
1395
- if (form === "confirm") {
1396
- if (typeof value === "boolean") return value;
1397
- if (typeof value === "string") {
1398
- const lower = value.trim().toLowerCase();
1399
- if (lower === "true") return true;
1400
- if (lower === "false") return false;
1401
- }
1402
- return Boolean(value);
1403
- }
1404
- if (form === "checkbox") {
1405
- if (Array.isArray(value)) return value.map((entry) => String(entry));
1406
- if (typeof value === "string") {
1407
- if (value.includes(",")) return value.split(",").map((entry) => entry.trim());
1408
- return [value];
1409
- }
1410
- return [String(value)];
1411
- }
1412
- if (typeof value === "string") return value;
1413
- if (typeof value === "object" && value !== null) return "";
1414
- return String(value);
1415
- }
1416
- /**
1417
- * Map a caught error to the {@link AgentToolErrorCode} the terminal-tool factory should throw
1418
- * with — the pure classification step of that factory's error handling.
1419
- *
1420
- * @remarks
1421
- * Narrows `error` with {@link isTerminalError} (`@orkestrel/terminal`) first: a non-`TerminalError`
1422
- * value returns `undefined`, telling the caller this mapper does not apply (rethrow / handle
1423
- * otherwise). For a genuine `TerminalError`, `'DEADLOCK'` maps to `'DEADLOCK'`, `'EXPIRE'` maps
1424
- * to `'EXPIRE'`, and every other {@link import('@orkestrel/terminal').TerminalErrorCode}
1425
- * (`'TARGET'`, `'CANCEL'`, `'DRIVER'`) maps to the generic `'TOOL'` code. The mapper only
1426
- * classifies — the factory performs the actual throw.
1427
- *
1428
- * @param error - The value caught from a terminal-manager operation (`ask` / `answer` / …)
1429
- * @returns The mapped {@link AgentToolErrorCode}, or `undefined` if `error` is not a `TerminalError`
1430
- */
1431
- function terminalToolCode(error) {
1432
- if (!(0, _orkestrel_terminal.isTerminalError)(error)) return void 0;
1433
- if (error.code === "DEADLOCK") return "DEADLOCK";
1434
- if (error.code === "EXPIRE") return "EXPIRE";
1435
- return "TOOL";
1436
- }
1437
- /** Narrow an unknown value to a {@link ColumnSpec} — a valid {@link import('./types.js').ColumnKind} shorthand, or `{ type, optional }` with a valid `type`. */
1438
- function isColumnSpec(value) {
1439
- if (isColumnKind(value)) return true;
1440
- if (!(0, _orkestrel_contract.isRecord)(value)) return false;
1441
- return isColumnKind(value.type) && (value.optional === void 0 || typeof value.optional === "boolean");
1442
- }
1443
- /** Narrow an unknown value to a {@link import('./types.js').ColumnKind}. */
1444
- function isColumnKind(value) {
1445
- return value === "string" || value === "integer" || value === "number" || value === "boolean";
1446
- }
1447
- /**
1448
- * Compile a {@link TableSpec} into the `@orkestrel/database` {@link TablesShape} it configures —
1449
- * each {@link ColumnSpec} maps to the matching primitive shaper (`'string'` → `stringShape()`,
1450
- * `'integer'` → `integerShape()`, `'number'` → `numberShape()`, `'boolean'` → `booleanShape()`),
1451
- * wrapped in `optionalShape` when the column declares `optional: true`. Total, pure.
1452
- *
1453
- * @param spec - The small-model-facing table layout
1454
- * @returns The compiled `TablesShape` a `@orkestrel/database` `createDatabase` call accepts
1455
- */
1456
- function expandTables(spec) {
1457
- const tables = {};
1458
- for (const [table, definition] of Object.entries(spec)) {
1459
- const columns = {};
1460
- for (const [column, kind] of Object.entries(definition.columns)) columns[column] = columnShape(kind);
1461
- tables[table] = columns;
1462
- }
1463
- return tables;
1464
- }
1465
- /** Compile one {@link ColumnSpec} into its `@orkestrel/database` column shape — the per-column leaf {@link expandTables} maps over. */
1466
- function columnShape(spec) {
1467
- const kind = (0, _orkestrel_contract.isString)(spec) ? spec : spec.type;
1468
- const optional = !(0, _orkestrel_contract.isString)(spec) && spec.optional === true;
1469
- const shape = kindShape(kind);
1470
- return optional ? (0, _orkestrel_contract.optionalShape)(shape) : shape;
1471
- }
1472
- /** Map one {@link import('./types.js').ColumnKind} to its primitive `@orkestrel/database` shape — the leaf {@link columnShape} wraps. */
1473
- function kindShape(kind) {
1474
- if (kind === "string") return (0, _orkestrel_contract.stringShape)();
1475
- if (kind === "integer") return (0, _orkestrel_contract.integerShape)();
1476
- if (kind === "number") return (0, _orkestrel_contract.numberShape)();
1477
- return (0, _orkestrel_contract.booleanShape)();
1478
- }
1479
- /**
1480
- * Narrow an unknown value to a {@link DatabaseDefinition} — a non-empty `id` + `driver`, a
1481
- * `tables` record whose every value is `{ columns: record of valid ColumnSpec }`, and an optional
1482
- * `keys` record of strings. The boundary guard a {@link import('./types.js').DefinitionStoreInterface}
1483
- * applies to an untrusted persisted blob before trusting it as a definition (never an `as`).
1484
- */
1485
- function isDatabaseDefinition(value) {
1486
- if (!(0, _orkestrel_contract.isRecord)(value)) return false;
1487
- if (!(0, _orkestrel_contract.isNonEmptyString)(value.id) || !(0, _orkestrel_contract.isNonEmptyString)(value.driver)) return false;
1488
- if (!(0, _orkestrel_contract.isRecord)(value.tables)) return false;
1489
- for (const table of Object.values(value.tables)) {
1490
- if (!(0, _orkestrel_contract.isRecord)(table) || !(0, _orkestrel_contract.isRecord)(table.columns)) return false;
1491
- for (const column of Object.values(table.columns)) if (!isColumnSpec(column)) return false;
1492
- }
1493
- if (value.keys !== void 0) {
1494
- if (!(0, _orkestrel_contract.isRecord)(value.keys)) return false;
1495
- for (const key of Object.values(value.keys)) if (!(0, _orkestrel_contract.isString)(key)) return false;
1496
- }
1497
- return true;
1498
- }
1499
- /**
1500
- * Map a caught error to the {@link AgentToolErrorCode} the upcoming database tool should throw
1501
- * with — the pure classification step of that factory's error handling, mirroring
1502
- * {@link terminalToolCode}'s idiom for `@orkestrel/database`.
1503
- *
1504
- * @param error - The value caught from a `@orkestrel/database` table operation
1505
- * @returns The granular {@link DatabaseErrorCode}, or `undefined` if `error` is not a `DatabaseError`
1506
- */
1507
- function databaseToolCode(error) {
1508
- return (0, _orkestrel_database.isDatabaseError)(error) ? error.code : void 0;
1509
- }
1510
- /**
1511
- * Map a caught error to the {@link AgentToolErrorCode} the upcoming relation tool should throw
1512
- * with — the pure classification step of that factory's error handling, mirroring
1513
- * {@link terminalToolCode}'s idiom for `@orkestrel/relation`.
1514
- *
1515
- * @param error - The value caught from a `@orkestrel/relation` operation
1516
- * @returns The granular {@link RelationErrorCode}, or `undefined` if `error` is not a `RelationError`
1517
- */
1518
- function relationToolCode(error) {
1519
- return (0, _orkestrel_relation.isRelationError)(error) ? error.code : void 0;
1520
- }
1521
- /**
1522
- * Expand the relation tool's FLAT dot-path `include` list into a live `@orkestrel/relation`
1523
- * {@link Include} tree — the pure leaf {@link import('./factories.js').createRelationTool} calls
1524
- * before a `'load'` / `'find'` call.
5
+ * Determine whether an unknown value is structurally a {@link ToolCall}.
1525
6
  *
1526
7
  * @remarks
1527
- * Each path splits on `'.'` into a chain of relation names, deep-merged into one nested
1528
- * `Include` object with a leaf `true`. A longer path SUBSUMES a shorter sibling's bare `true` —
1529
- * `'contacts'` followed by `'contacts.account'` yields `{ contacts: { account: true } }`, never
1530
- * overwriting the deeper chain. An EMPTY segment (`''`, from a leading/trailing/doubled `.`) or a
1531
- * path whose segment count exceeds `depth` throws a typed `TOOL` {@link AgentToolError}.
8
+ * This total guard accepts a plain record with string `id` and `name` fields and a
9
+ * plain-record `arguments` field. Adversarial values return `false`.
1532
10
  *
1533
- * @param paths - The flat dot-path `include` list (or `undefined` — yields `{}`)
1534
- * @param depth - The max segment count a single path may reach
1535
- * @returns The equivalent nested {@link Include}
11
+ * @param value - The value to test
12
+ * @returns `true` when the value has the complete tool-call shape
1536
13
  *
1537
14
  * @example
1538
15
  * ```ts
1539
- * import { expandInclude } from '@src/core'
16
+ * import { isToolCall } from '@orkestrel/tool'
1540
17
  *
1541
- * expandInclude(['contacts', 'contacts.account'], 3)
1542
- * // { contacts: { account: true } }
18
+ * isToolCall({ id: '1', name: 'search', arguments: { query: 'birds' } }) // true
19
+ * isToolCall({ id: '1', name: 'search', arguments: [] }) // false
1543
20
  * ```
1544
21
  */
1545
- function expandInclude(paths, depth) {
1546
- let include = {};
1547
- for (const path of paths ?? []) {
1548
- const segments = path.split(".");
1549
- if (segments.length > depth || segments.some((segment) => segment.length === 0)) throw new AgentToolError("TOOL", `malformed include path '${path}'`, {
1550
- path,
1551
- depth
1552
- });
1553
- const ancestors = [];
1554
- let branch = include;
1555
- const last = segments.length - 1;
1556
- for (let index = 0; index < last; index++) {
1557
- const segment = segments[index];
1558
- if (segment === void 0) throw new AgentToolError("TOOL", `malformed include path '${path}'`, {
1559
- path,
1560
- depth
1561
- });
1562
- ancestors.push(branch);
1563
- const existing = branch[segment];
1564
- branch = typeof existing === "object" ? existing : {};
1565
- }
1566
- const leaf = segments[last];
1567
- if (leaf === void 0) throw new AgentToolError("TOOL", `malformed include path '${path}'`, {
1568
- path,
1569
- depth
1570
- });
1571
- const existing = branch[leaf];
1572
- let merged = {
1573
- ...branch,
1574
- [leaf]: existing === void 0 ? true : existing
1575
- };
1576
- for (let index = last - 1; index >= 0; index--) {
1577
- const ancestor = ancestors[index];
1578
- const segment = segments[index];
1579
- if (ancestor === void 0 || segment === void 0) throw new AgentToolError("TOOL", `malformed include path '${path}'`, {
1580
- path,
1581
- depth
1582
- });
1583
- merged = {
1584
- ...ancestor,
1585
- [segment]: merged
1586
- };
1587
- }
1588
- include = merged;
1589
- }
1590
- return include;
1591
- }
1592
- /**
1593
- * Resolve which registered {@link RelationManagerInterface} a relation-tool call addresses — the
1594
- * pure manager-resolution leaf {@link import('./factories.js').createRelationTool} calls on
1595
- * every operation.
1596
- *
1597
- * @remarks
1598
- * An explicit `name` must match a key of `managers` (a miss throws a typed `TOOL`
1599
- * {@link AgentToolError} naming the registered managers). An OMITTED `name` resolves to the sole
1600
- * registered manager when exactly one is registered, else throws the same typed error.
1601
- *
1602
- * @param managers - The tool's registered `RelationManagerInterface` map
1603
- * @param name - The call's optional `manager` field
1604
- * @returns The resolved {@link RelationManagerInterface}
1605
- */
1606
- function relationManagerOf(managers, name) {
1607
- if (name !== void 0) {
1608
- const manager = managers[name];
1609
- if (manager === void 0) throw new AgentToolError("TOOL", `unknown relation manager '${name}'`, {
1610
- manager: name,
1611
- managers: Object.keys(managers)
1612
- });
1613
- return manager;
1614
- }
1615
- const names = Object.keys(managers);
1616
- const [single] = names;
1617
- if (names.length === 1 && single !== void 0) {
1618
- const manager = managers[single];
1619
- if (manager !== void 0) return manager;
1620
- }
1621
- throw new AgentToolError("TOOL", "no relation manager resolved for the call", { managers: names });
1622
- }
1623
- /**
1624
- * Resolve a `model` name against a live {@link RelationManagerInterface} — the pure model-lookup
1625
- * leaf {@link import('./factories.js').createRelationTool} calls on every operation, mirroring
1626
- * {@link relationManagerOf}'s guard shape.
1627
- *
1628
- * @param manager - The resolved {@link RelationManagerInterface}
1629
- * @param name - The call's `model` field
1630
- * @returns The model's {@link ModelInterface}
1631
- */
1632
- function relationModelOf(manager, name) {
1633
- if (!manager.has(name)) throw new AgentToolError("TOOL", `unknown model '${name}'`, {
1634
- model: name,
1635
- models: manager.models()
1636
- });
1637
- return manager.model(name);
1638
- }
1639
- /**
1640
- * Normalize the database tool's parsed SERIALIZED criteria into a live `@orkestrel/database`
1641
- * {@link Criteria} — default each condition's OMITTED `connector` to `'and'`.
1642
- *
1643
- * @remarks
1644
- * The wire form ({@link import('./shapers.js').databaseToolShape}) lets a caller drop `connector`
1645
- * on the last condition (it has nothing to join FORWARD to); the compiled `Condition` a live
1646
- * `@orkestrel/database` table call accepts always carries one, so this fills the gap. `order` /
1647
- * `limit` / `offset` pass through unchanged. Pure and total.
1648
- *
1649
- * @param criteria - The parsed criteria (or `undefined`)
1650
- * @returns The equivalent live `Criteria`, or `undefined` when `criteria` is `undefined`
1651
- */
1652
- function criteriaOf(criteria) {
1653
- if (criteria === void 0) return void 0;
1654
- const conditions = criteria.conditions?.map((condition) => ({
1655
- ...condition,
1656
- connector: condition.connector ?? "and"
1657
- }));
1658
- return {
1659
- ...conditions === void 0 ? {} : { conditions },
1660
- ...criteria.order === void 0 ? {} : { order: criteria.order },
1661
- ...criteria.limit === void 0 ? {} : { limit: criteria.limit },
1662
- ...criteria.offset === void 0 ? {} : { offset: criteria.offset }
1663
- };
1664
- }
1665
- /**
1666
- * Clamp a `'records'` call's criteria to a row cap, and build the PROBE criteria the caller reads
1667
- * with — the pure leaf {@link import('./factories.js').createDatabaseTool}'s `'records'` operation
1668
- * uses to detect truncation without a separate `count` round trip.
1669
- *
1670
- * @remarks
1671
- * The effective limit is `min(criteria?.limit ?? cap, cap)`, floored at `0` (so a caller can never
1672
- * exceed the configured cap by supplying a larger `criteria.limit`). The returned probe criteria
1673
- * requests ONE MORE row than the effective limit (`limit: effective + 1`) — if storage returns
1674
- * that many, the caller knows the true result was truncated (`rows.length > effective`) and slices
1675
- * back down to `effective` before returning.
1676
- *
1677
- * @example
1678
- * ```ts
1679
- * import { clampCriteria } from '@src/core'
1680
- *
1681
- * const { criteria, limit } = clampCriteria(undefined, 100)
1682
- * // limit === 100, criteria.limit === 101 — a probe fetching one extra row
1683
- * const rows = await table.records(criteria)
1684
- * const truncated = rows.length > limit // true when storage had more than `limit` rows
1685
- * ```
1686
- *
1687
- * @param criteria - The live criteria to clamp (or `undefined`)
1688
- * @param cap - The row-count ceiling
1689
- * @returns The PROBE criteria (`limit` bumped by one) and the effective `limit`
1690
- */
1691
- function clampCriteria(criteria, cap) {
1692
- const limit = Math.max(0, Math.min(criteria?.limit ?? cap, cap));
1693
- return {
1694
- criteria: {
1695
- ...criteria,
1696
- limit: limit + 1
1697
- },
1698
- limit
1699
- };
1700
- }
1701
- /** Map a column NAME + its live `@orkestrel/database` `ContractShape` to a {@link ColumnSchema} — the leaf {@link tableSchema} maps over. */
1702
- function columnSchema(name, shape) {
1703
- return {
1704
- name,
1705
- type: (0, _orkestrel_database.shapeToColumnType)(shape),
1706
- nullable: shape.type === "optional" || shape.type === "nullable"
1707
- };
1708
- }
1709
- /**
1710
- * Build one {@link TableSchema} from a table NAME and its `@orkestrel/database` `TableExport` —
1711
- * the "deployed" schema shape `DatabaseInterface.migrate` diffs against, derived from a LIVE
1712
- * handle's `export()` rather than a re-declared {@link TableSpec}, so it works for ANY handle
1713
- * (config-tracked or caller-supplied).
1714
- *
1715
- * @param name - The table name
1716
- * @param table - The table's `TableExport` (`{ key, columns }`, `@orkestrel/database`)
1717
- * @returns The equivalent {@link TableSchema} (`indexes` empty — this package declares none)
1718
- */
1719
- function tableSchema(name, table) {
1720
- return {
1721
- name,
1722
- primary: table.key,
1723
- columns: Object.entries(table.columns).map(([column, shape]) => columnSchema(column, shape)),
1724
- indexes: []
1725
- };
22
+ function isToolCall(value) {
23
+ return (0, _orkestrel_contract.holds)(() => (0, _orkestrel_contract.isRecord)(value) && (0, _orkestrel_contract.isString)(value.id) && (0, _orkestrel_contract.isString)(value.name) && (0, _orkestrel_contract.isRecord)(value.arguments));
1726
24
  }
1727
25
  //#endregion
1728
- //#region src/core/stores/MemoryDefinitionStore.ts
26
+ //#region src/core/tools/Tool.ts
1729
27
  /**
1730
- * The in-memory {@link DefinitionStoreInterface} a process-lifetime `Map` of
1731
- * {@link DatabaseDefinition}s keyed by database id, the DEFAULT store
1732
- * {@link import('../factories.js').createMemoryDefinitionStore} builds. The EXACT twin of
1733
- * {@link import('./DatabaseDefinitionStore.js').DatabaseDefinitionStore}.
28
+ * An executable tool definition bound to a handler.
1734
29
  *
1735
30
  * @remarks
1736
- * A plain `Map<string, DatabaseDefinition>` (AGENTS §21 the definition is already pure,
1737
- * self-contained CONFIG-only JSON, so no encoding is needed for the memory tier). There is NO
1738
- * idle-TTL and NO eviction: a persisted definition lives until an explicit `delete`. A durable
1739
- * backend (JSON / SQLite / IndexedDB) swaps in through the SAME interface without touching a
1740
- * consumer — its driver-pluggable twin is
1741
- * {@link import('./DatabaseDefinitionStore.js').DatabaseDefinitionStore} (the definition as one
1742
- * opaque JSON column).
1743
- *
1744
- * - **`get` resolves the persisted definition for an id**, or `undefined` if none is stored.
1745
- * - **`set` inserts / replaces under the definition's OWN `id`** (no separate id param).
1746
- * - **`delete` drops a definition by id**; an absent id is a no-op (no throw).
1747
- *
1748
- * The public surface is EXACTLY `get` / `set` / `delete` — no extra members (the §22 method
1749
- * bijection with {@link DefinitionStoreInterface}).
31
+ * Schema fields and arguments are forwarded by reference. Handler failures are not
32
+ * caught here; {@link ToolManager} owns per-call error isolation.
1750
33
  *
1751
34
  * @example
1752
35
  * ```ts
1753
- * import { createMemoryDefinitionStore } from '@src/core'
36
+ * import { Tool } from '@orkestrel/tool'
1754
37
  *
1755
- * const store = createMemoryDefinitionStore()
1756
- * await store.set({ id: 'shop', driver: 'memory', tables: {} })
1757
- * const definition = await store.get('shop')
1758
- * await store.delete('shop')
38
+ * const tool = new Tool({
39
+ * name: 'add',
40
+ * description: 'Add two numbers',
41
+ * parameters: {
42
+ * type: 'object',
43
+ * properties: { a: { type: 'number' }, b: { type: 'number' } },
44
+ * },
45
+ * execute: (args) => Number(args.a) + Number(args.b),
46
+ * })
1759
47
  * ```
1760
48
  */
1761
- var MemoryDefinitionStore = class {
1762
- #definitions = /* @__PURE__ */ new Map();
1763
- get(id) {
1764
- return Promise.resolve(this.#definitions.get(id));
1765
- }
1766
- set(definition) {
1767
- this.#definitions.set(definition.id, definition);
1768
- return Promise.resolve();
49
+ var Tool = class {
50
+ name;
51
+ description;
52
+ summary;
53
+ parameters;
54
+ #execute;
55
+ constructor(options) {
56
+ this.name = options.name;
57
+ if (options.description !== void 0) this.description = options.description;
58
+ if (options.summary !== void 0) this.summary = options.summary;
59
+ if (options.parameters !== void 0) this.parameters = options.parameters;
60
+ this.#execute = options.execute;
1769
61
  }
1770
- delete(id) {
1771
- this.#definitions.delete(id);
1772
- return Promise.resolve();
62
+ execute(args) {
63
+ return this.#execute(args);
1773
64
  }
1774
65
  };
1775
66
  //#endregion
1776
- //#region src/core/stores/DatabaseDefinitionStore.ts
67
+ //#region src/core/tools/ToolManager.ts
1777
68
  /**
1778
- * A {@link DefinitionStoreInterface} backed by one table of the `@orkestrel/database` layer — a
1779
- * database's durable CONFIG state IS a row, so persistence reduces to keyed point-access
1780
- * (`get` / `set` / `delete`) over a {@link TableInterface}, the driver-pluggable twin of the
1781
- * plain-`Map` {@link import('./MemoryDefinitionStore.js').MemoryDefinitionStore}.
69
+ * An insertion-ordered tool registry with per-call error isolation.
1782
70
  *
1783
71
  * @remarks
1784
- * The store is driver-agnostic: it holds a single {@link TableInterface} whose backend (memory,
1785
- * JSON, SQLite, IndexedDB) is chosen by whoever builds it (the factories), so a JSON / SQLite /
1786
- * IndexedDB backend swaps in WITHOUT touching a consumer the same seam as
1787
- * {@link import('./MemoryDefinitionStore.js').MemoryDefinitionStore}. The driver defaults to
1788
- * memory ({@link import('../factories.js').createDatabaseDefinitionStore} passes
1789
- * `createMemoryDriver()`), so it ALSO works in memory out of the box; you opt into the durable
1790
- * plumbing by passing a JSON / SQLite / IndexedDB driver.
1791
- *
1792
- * The {@link DatabaseDefinition} is stored as ONE OPAQUE JSON COLUMN — the table is a row of
1793
- * `{ id; definition }` ({@link DatabaseDefinitionRow}). The definition is already a COMPLETE,
1794
- * self-contained, pure-JSON CONFIG payload (never a live handle), so storing it whole is lossless
1795
- * AND keeps the row type flat (`definition` reads back as `unknown`).
1796
- *
1797
- * - **`set(definition)` upserts under the definition's OWN `id`** (no separate id param) — it
1798
- * writes the row `{ id: definition.id, definition }`.
1799
- * - **`get(id)` resolves the stored definition for an id**, narrowing the opaque JSON column back
1800
- * to a {@link DatabaseDefinition} ({@link import('../helpers.js').isDatabaseDefinition} — the
1801
- * AGENTS §14 boundary narrow for an untrusted storage read), or `undefined` if none is stored
1802
- * or the stored blob is malformed.
1803
- * - **`delete(id)` drops a definition by id**; an absent id is a no-op (no throw).
1804
- *
1805
- * The public surface is EXACTLY `get` / `set` / `delete` — no extra members (the §22 method
1806
- * bijection with {@link DefinitionStoreInterface}).
72
+ * A repeated name overwrites the registered tool without changing its insertion
73
+ * position. Definitions advertise `summary` in place of `description` when present.
74
+ * Unknown names and handler throws resolve to error results; batch execution preserves
75
+ * input order and never fails as a whole because of an individual call.
1807
76
  *
1808
77
  * @example
1809
78
  * ```ts
1810
- * import { createDatabaseDefinitionStore, createMemoryDriver } from '@src/core'
79
+ * import { Tool, ToolManager } from '@orkestrel/tool'
1811
80
  *
1812
- * const store = createDatabaseDefinitionStore(createMemoryDriver()) // a durable driver swaps in here
1813
- * await store.set({ id: 'shop', driver: 'memory', tables: {} }) // persist the config (one JSON column)
1814
- * const definition = await store.get('shop')
1815
- * await store.delete('shop')
81
+ * const tools = new ToolManager()
82
+ * tools.add(new Tool({ name: 'add', execute: (args) => Number(args.x) + Number(args.y) }))
83
+ * const result = await tools.execute({
84
+ * id: '1',
85
+ * name: 'add',
86
+ * arguments: { x: 1, y: 2 },
87
+ * })
1816
88
  * ```
1817
89
  */
1818
- var DatabaseDefinitionStore = class {
1819
- #table;
1820
- /**
1821
- * Wrap a table as a definition store.
1822
- *
1823
- * @param table - The {@link TableInterface} holding the definitions — its row is the
1824
- * {@link DatabaseDefinitionRow} `{ id; definition }` shape (the definition one opaque JSON column)
1825
- */
1826
- constructor(table) {
1827
- this.#table = table;
90
+ var ToolManager = class {
91
+ #tools = /* @__PURE__ */ new Map();
92
+ get count() {
93
+ return this.#tools.size;
1828
94
  }
1829
- /** Resolve the persisted definition for `id`, narrowing the opaque JSON column back to a `DatabaseDefinition`. */
1830
- async get(id) {
1831
- const row = await this.#table.get(id);
1832
- if (row === void 0) return void 0;
1833
- return isDatabaseDefinition(row.definition) ? row.definition : void 0;
1834
- }
1835
- /** Insert or replace under the definition's OWN `id` (no separate id param) — the row is `{ id, definition }`. */
1836
- async set(definition) {
1837
- await this.#table.set({
1838
- id: definition.id,
1839
- definition
1840
- });
95
+ add(tools) {
96
+ if ((0, _orkestrel_contract.isArray)(tools)) {
97
+ for (const tool of tools) this.#tools.set(tool.name, tool);
98
+ return;
99
+ }
100
+ this.#tools.set(tools.name, tools);
1841
101
  }
1842
- /** Drop a definition by id; an absent id is a no-op (no throw). */
1843
- async delete(id) {
1844
- await this.#table.remove(id);
102
+ tool(name) {
103
+ return this.#tools.get(name);
1845
104
  }
1846
- };
1847
- //#endregion
1848
- //#region src/core/databases/DatabaseResolver.ts
1849
- /**
1850
- * Resolve database definitions into cached live handles for database tools.
1851
- *
1852
- * @example
1853
- * ```ts
1854
- * import { DatabaseResolver } from '@orkestrel/tool'
1855
- *
1856
- * const resolver = new DatabaseResolver(handles, drivers, key, store)
1857
- * const database = await resolver.resolve('shop')
1858
- * ```
1859
- */
1860
- var DatabaseResolver = class {
1861
- #handles;
1862
- #drivers;
1863
- #key;
1864
- #store;
1865
- /**
1866
- * Create a database resolver over the tool's live state and optional definition store.
1867
- *
1868
- * @param handles - Initial live database handles cached by id
1869
- * @param drivers - Driver factories keyed by definition driver name
1870
- * @param key - Key generator supplied to newly created databases
1871
- * @param store - Optional persistent definition store
1872
- */
1873
- constructor(handles, drivers, key, store) {
1874
- this.#handles = new Map(handles);
1875
- this.#drivers = drivers;
1876
- this.#key = key;
1877
- this.#store = store;
105
+ tools() {
106
+ return [...this.#tools.values()];
1878
107
  }
1879
- /**
1880
- * Determine whether a live database is cached by id.
1881
- *
1882
- * @param id - Database id
1883
- * @returns Whether a live handle is cached
1884
- */
1885
- has(id) {
1886
- return this.#handles.has(id);
108
+ definitions() {
109
+ return [...this.#tools.values()].map((tool) => this.#definition(tool));
1887
110
  }
1888
- /**
1889
- * Read a cached database without consulting the definition store.
1890
- *
1891
- * @param id - Database id
1892
- * @returns The cached live database, or `undefined`
1893
- */
1894
- get(id) {
1895
- return this.#handles.get(id);
111
+ execute(call) {
112
+ if ((0, _orkestrel_contract.isArray)(call)) return Promise.all(call.map((one) => this.#run(one)));
113
+ return this.#run(call);
1896
114
  }
1897
- /**
1898
- * Cache a live database by id.
1899
- *
1900
- * @param id - Database id
1901
- * @param database - Live database handle
1902
- * @returns Nothing
1903
- */
1904
- set(id, database) {
1905
- this.#handles.set(id, database);
115
+ remove(names) {
116
+ if ((0, _orkestrel_contract.isArray)(names)) {
117
+ let removed = false;
118
+ for (const name of names) if (this.#tools.delete(name)) removed = true;
119
+ return removed;
120
+ }
121
+ return this.#tools.delete(names);
1906
122
  }
1907
- /**
1908
- * Remove a cached live database by id.
1909
- *
1910
- * @param id - Database id
1911
- * @returns Nothing
1912
- */
1913
- delete(id) {
1914
- this.#handles.delete(id);
123
+ clear() {
124
+ this.#tools.clear();
1915
125
  }
1916
- /**
1917
- * Resolve a cached or stored database by id.
1918
- *
1919
- * @param id - Database definition id
1920
- * @returns The cached or newly constructed live database
1921
- */
1922
- async resolve(id) {
1923
- const cached = this.#handles.get(id);
1924
- if (cached !== void 0) return cached;
1925
- if (this.#store !== void 0) {
1926
- const definition = await this.#store.get(id);
1927
- if (definition !== void 0) {
1928
- const factory = this.#drivers[definition.driver];
1929
- if (factory === void 0) throw new AgentToolError("TOOL", `unknown driver '${definition.driver}'`, {
1930
- id,
1931
- driver: definition.driver
1932
- });
1933
- const handle = (0, _orkestrel_database.createDatabase)({
1934
- driver: factory(),
1935
- tables: expandTables(definition.tables),
1936
- ...definition.keys === void 0 ? {} : { keys: definition.keys },
1937
- key: this.#key
1938
- });
1939
- this.set(id, handle);
1940
- return handle;
1941
- }
126
+ async #run(call) {
127
+ const tool = this.#tools.get(call.name);
128
+ if (tool === void 0) return {
129
+ id: call.id,
130
+ name: call.name,
131
+ error: `tool not found: ${call.name}`
132
+ };
133
+ try {
134
+ const value = await tool.execute(call.arguments);
135
+ return {
136
+ id: call.id,
137
+ name: call.name,
138
+ value
139
+ };
140
+ } catch (error) {
141
+ const message = (0, _orkestrel_contract.attempt)(() => error instanceof Error ? String(error.message) : String(error));
142
+ return {
143
+ id: call.id,
144
+ name: call.name,
145
+ error: message.success ? message.value : "Unknown thrown value"
146
+ };
1942
147
  }
1943
- throw new AgentToolError("TOOL", `unknown database '${id}'`, { id });
148
+ }
149
+ #definition(tool) {
150
+ const definition = { name: tool.name };
151
+ const description = tool.summary ?? tool.description;
152
+ if (description !== void 0) definition.description = description;
153
+ if (tool.parameters !== void 0) definition.parameters = tool.parameters;
154
+ return definition;
1944
155
  }
1945
156
  };
1946
157
  //#endregion
1947
158
  //#region src/core/factories.ts
1948
159
  /**
1949
- * Wrap a registered tool as a {@link WorkflowFunction} (`@orkestrel/workflow`) — the OPT-IN
1950
- * adapter that lets a `function`-form task run a `@orkestrel/agent` tool BY NAME.
1951
- *
1952
- * @remarks
1953
- * OWNED here now (ported from `@orkestrel/workflow`). Composes into a caller's
1954
- * `WorkflowOptions.functions` registry like any other behavior
1955
- * (`{ publish: createToolFunction(tools, 'publish') }`); the pure workflow runner has no
1956
- * knowledge of tools itself. The returned function executes `name` against `tools` with the
1957
- * task's `controller.input` as the call arguments, id-correlated to the task's own id. A
1958
- * `ToolManagerInterface.execute` (`@orkestrel/agent`) NEVER throws (a handler throw is isolated
1959
- * into `result.error`), so a failing tool is surfaced here as a THROWN `Error` carrying the
1960
- * original message as `cause` — the leaf `fail`s, honouring `bail`. An UNREGISTERED tool name is
1961
- * a programmer error (an explicit binding to a name that doesn't exist) — unlike the engine's
1962
- * own silent auto-complete of an unresolved task handler, this THROWS a typed `TOOL`
1963
- * `WorkflowError` (`@orkestrel/workflow`).
1964
- *
1965
- * @param tools - The `ToolManagerInterface` (`@orkestrel/agent`) the named tool is registered on
1966
- * @param name - The registered tool's name
1967
- * @returns A {@link WorkflowFunction} that runs the named tool
1968
- *
1969
- * @example
1970
- * ```ts
1971
- * import { createToolFunction } from '@src/core'
1972
- * import { createToolManager } from '@orkestrel/agent'
1973
- * import { createWorkflowRunner } from '@orkestrel/workflow'
1974
- *
1975
- * const tools = createToolManager()
1976
- * tools.add(myPublishTool)
1977
- * const runner = createWorkflowRunner()
1978
- * await runner.execute(definition, { functions: { publish: createToolFunction(tools, 'publish') } })
1979
- * ```
1980
- */
1981
- function createToolFunction(tools, name) {
1982
- return async (controller) => {
1983
- if (tools.tool(name) === void 0) throw new _orkestrel_workflow.WorkflowError("TOOL", `tool '${name}' is not registered`, { tool: name });
1984
- const result = await tools.execute({
1985
- id: controller.task.id,
1986
- name,
1987
- arguments: controller.input
1988
- });
1989
- if (result.error !== void 0) throw new Error(result.error, { cause: result.error });
1990
- return result.value;
1991
- };
1992
- }
1993
- /**
1994
- * Wrap a live `AgentInterface` (`@orkestrel/agent`) as a {@link WorkflowFunction}
1995
- * (`@orkestrel/workflow`) — the OPT-IN adapter that runs the agent to a settled result, folding
1996
- * a nested workflow-authoring depth / cycle guard into its own closure.
1997
- *
1998
- * @remarks
1999
- * OWNED here now (ported from `@orkestrel/workflow`). Composes into a caller's
2000
- * `WorkflowOptions.functions` registry like any other behavior; the pure workflow runner has no
2001
- * knowledge of agents itself. Before running the agent, the depth/cycle guard REJECTS the call
2002
- * (a THROWN typed `DEPTH` `WorkflowError`, which the leaf `fail`s) when running it would push a
2003
- * nested chain past {@link import('./constants.js').MAX_WORKFLOW_DEPTH}, OR when this agent is
2004
- * already an ancestor (a cycle). When {@link import('./types.js').AgentFunctionOptions.runner}
2005
- * is supplied, the adapter BINDS a depth/cycle-aware {@link createWorkflowTool} onto the agent's
2006
- * `context.tools` (the propagation seam) — closed over `depth` and the extended ancestry (the
2007
- * tool itself computes `depth + 1` internally) — so the agent can author + run a NESTED workflow
2008
- * through it; the wrapped default is the CURRENT task's own workflow id (used only on a no-args
2009
- * tool call). The task's cancellation folds into the agent run: an already-aborted
2010
- * `controller.signal` cancels the agent up front; otherwise a one-shot listener fires
2011
- * `agent.abort(reason)` when the task cancels, removed in `finally`. `agent.generate()` resolves
2012
- * a partial `AgentResult` on a cancel (never rejects), returned as the task's completed value.
2013
- *
2014
- * A bound agent is effectively SINGLE-RUN: `context.tools.add` binds one `ToolInterface` under
2015
- * the fixed {@link import('./constants.js').WORKFLOW_TOOL_NAME}, and `agent.generate()` /
2016
- * `agent.abort()` are per-agent state. Two CONCURRENT tasks sharing the SAME `agent` instance
2017
- * race on that one tool binding (last-write-wins) and on generate/abort — give each concurrent
2018
- * task its OWN agent instance.
2019
- *
2020
- * @param agent - The live `AgentInterface` to run
2021
- * @param options - The nested-workflow binding + depth/cycle bookkeeping (see {@link import('./types.js').AgentFunctionOptions})
2022
- * @returns A {@link WorkflowFunction} that runs `agent` to its settled result
2023
- *
2024
- * @example
2025
- * ```ts
2026
- * import { createAgentFunction } from '@src/core'
2027
- * import { createWorkflowRunner } from '@orkestrel/workflow'
2028
- *
2029
- * const runner = createWorkflowRunner()
2030
- * const review = createAgentFunction(myAgent, { runner })
2031
- * await runner.execute(definition, { functions: { review } })
2032
- * ```
2033
- */
2034
- function createAgentFunction(agent, options) {
2035
- return async (controller) => {
2036
- const depth = options?.depth ?? 0;
2037
- const ancestry = options?.ancestry ?? [];
2038
- if (depth + 1 > 8) throw new _orkestrel_workflow.WorkflowError("DEPTH", `agent '${agent.id}' exceeds max workflow depth`, {
2039
- agent: agent.id,
2040
- depth,
2041
- max: 8
2042
- });
2043
- const tag = agentTag(agent.id);
2044
- if (ancestry.includes(tag)) throw new _orkestrel_workflow.WorkflowError("DEPTH", `agent '${agent.id}' is already an ancestor (cycle)`, {
2045
- agent: agent.id,
2046
- ancestry: [...ancestry]
2047
- });
2048
- const runner = options?.runner;
2049
- if (runner !== void 0) {
2050
- const workflowId = controller.task.phase.workflow.id;
2051
- const wrapped = {
2052
- id: workflowId,
2053
- name: workflowId,
2054
- phases: []
2055
- };
2056
- agent.context.tools.add(createWorkflowTool(wrapped, runner, {
2057
- depth,
2058
- ancestry: [...ancestry, tag]
2059
- }));
2060
- }
2061
- const signal = controller.signal;
2062
- const onAbort = { handleEvent() {
2063
- agent.abort(signal.reason);
2064
- } };
2065
- if (signal.aborted) agent.abort(signal.reason);
2066
- else signal.addEventListener("abort", onAbort, { once: true });
2067
- try {
2068
- return await agent.generate();
2069
- } finally {
2070
- signal.removeEventListener("abort", onAbort);
2071
- }
2072
- };
2073
- }
2074
- /**
2075
- * Compile the LENIENT workflow DRAFT contract — identical to `createWorkflowContract`
2076
- * (`@orkestrel/workflow`) EXCEPT `id` and `name` are OPTIONAL at all three levels (workflow /
2077
- * phase / task), so a small model can omit the six identity strings.
2078
- *
2079
- * @remarks
2080
- * The widened authoring surface {@link createWorkflowTool} parses an authored blob through
2081
- * before {@link import('./helpers.js').completeDraft} fills the missing ids/names. It does NOT
2082
- * relax the canonical contract — `createWorkflowContract` (`@orkestrel/workflow`) stays
2083
- * byte-for-byte unchanged and STRICT, and the completed draft is re-validated against THAT
2084
- * strict gate before running (soundness preserved). A PROVIDED `id` / `name` still carries
2085
- * `minLength: 1`, so an explicitly-empty `id: ''` is REJECTED (parses to `undefined`), never
2086
- * auto-filled — keeping "garbage" distinct from "omitted". `run` stays optional (a plain name
2087
- * string).
2088
- *
2089
- * @returns The compiled {@link import('./types.js').WorkflowDraft} contract
2090
- *
2091
- * @example
2092
- * ```ts
2093
- * import { createWorkflowDraftContract, completeDraft } from '@src/core'
2094
- *
2095
- * const draft = createWorkflowDraftContract()
2096
- * const parsed = draft.parse({ phases: [{ tasks: [{ run: 'compile' }] }] })
2097
- * const definition = parsed && completeDraft(parsed) // ids/names filled positionally
2098
- * draft.parse({ id: '', phases: [] }) // undefined — an explicit empty id is rejected
2099
- * ```
2100
- */
2101
- function createWorkflowDraftContract() {
2102
- return (0, _orkestrel_contract.createContract)(workflowDraftShape);
2103
- }
2104
- /**
2105
- * Wrap a {@link WorkflowDefinition} as an LLM-callable tool — it ADVERTISES the SIMPLE flat
2106
- * authoring shape (`{ name?, steps: [{ name }] }`) as its `parameters` so even a small model can
2107
- * author a complete tree, and its handler EXPANDS / COMPLETES the authored blob, validates it
2108
- * against the STRICT contract, runs it through `runner`, and, when
2109
- * {@link import('./types.js').WorkflowToolOptions.store} is supplied, PERSISTS each executed
2110
- * workflow's final snapshot after the run settles.
2111
- *
2112
- * @remarks
2113
- * A plain `ToolManagerInterface`-compatible tool (`@orkestrel/agent`), reproducing
2114
- * `@orkestrel/workflow`'s former call contract exactly (flat / draft / full authoring forms, the
2115
- * strict soundness gate, the depth/cycle guard). It is ALSO the propagation carrier
2116
- * {@link createAgentFunction} binds onto a wrapped agent's `context.tools`: because a tool
2117
- * handler receives ONLY the model-supplied `args` (no ambient context, no signal), the run's
2118
- * depth + ancestry are CLOSED OVER at bind time via {@link import('./types.js').WorkflowToolOptions},
2119
- * and the handler enforces the SAME depth / cycle guard itself before running the nested
2120
- * workflow at `depth + 1` with the extended ancestry.
2121
- *
2122
- * **Widened authoring surface (additive — the canonical contract + runner stay STRICT and
2123
- * unchanged).** A 2B model reliably CALLS the tool but cannot reliably emit the full four-level
2124
- * nested {@link WorkflowDefinition} (six required `id`/`name` strings, an all-or-nothing tree).
2125
- * So the tool ACCEPTS three authoring forms and converges them on the SAME strict
2126
- * `createWorkflowContract` gate before running (soundness preserved):
2127
- * - the FLAT shape `{ name?, steps: [{ name }] }` — the ADVERTISED `parameters` (the simplest
2128
- * form, {@link import('./helpers.js').expandSteps}'d into one one-task phase per step);
2129
- * - a nested DRAFT with any `id`/`name` OMITTED — {@link createWorkflowDraftContract}-parsed then
2130
- * {@link import('./helpers.js').completeDraft}'d (missing ids synthesized positionally);
2131
- * - the full nested {@link WorkflowDefinition} — the advanced escape-hatch, accepted as the draft
2132
- * super-set.
2133
- *
2134
- * The universal tool-handler contract (AGENTS §14): returns the plain run summary
2135
- * (`{ status, count }`) on success, THROWS a typed `WorkflowError` (`@orkestrel/workflow`) on
2136
- * every failure path — malformed authored args (`TOOL`), or an over-deep / cyclic nested run
2137
- * (`DEPTH`). The `ToolManagerInterface` isolates every throw into the canonical tool result's
2138
- * top-level `error`, so nothing escapes the run. `options.depth` / `options.ancestry` are the
2139
- * propagation carrier across a workflow → agent → workflow chain; `options.store` is this
2140
- * package's ADDITION — the persisted snapshot is retrievable via the store afterwards (a caller
2141
- * restores it through `@orkestrel/workflow`'s own `Workflow.restore` / store-backed factories).
2142
- *
2143
- * @param definition - The workflow the tool runs when called with no authored args
2144
- * @param runner - The `WorkflowRunnerInterface` (`@orkestrel/workflow`) that executes the (nested) workflow
2145
- * @param options - Depth/ancestry bookkeeping plus the optional durable store (see {@link import('./types.js').WorkflowToolOptions})
2146
- * @returns A `ToolInterface` (named {@link import('./constants.js').WORKFLOW_TOOL_NAME}) whose
2147
- * `parameters` advertise the flat authoring schema
2148
- *
2149
- * @example
2150
- * ```ts
2151
- * import { createWorkflowTool } from '@src/core'
2152
- * import { createWorkflowRunner, createMemoryWorkflowStore } from '@orkestrel/workflow'
2153
- * import { createToolManager } from '@orkestrel/agent'
2154
- *
2155
- * const runner = createWorkflowRunner()
2156
- * const store = createMemoryWorkflowStore()
2157
- * const tool = createWorkflowTool(definition, runner, { store })
2158
- * const tools = createToolManager()
2159
- * tools.add(tool) // authored runs are now persisted to `store` on settle
2160
- * ```
2161
- */
2162
- function createWorkflowTool(definition, runner, options) {
2163
- const strict = (0, _orkestrel_workflow.createWorkflowContract)();
2164
- const draft = createWorkflowDraftContract();
2165
- const steps = (0, _orkestrel_contract.createContract)(workflowStepsShape);
2166
- const depth = options?.depth ?? 0;
2167
- const ancestry = options?.ancestry ?? [];
2168
- const store = options?.store;
2169
- const parameters = (0, _orkestrel_contract.schemaToParameters)(steps.schema);
2170
- return (0, _orkestrel_agent.createTool)({
2171
- name: WORKFLOW_TOOL_NAME,
2172
- description: WORKFLOW_TOOL_DESCRIPTION,
2173
- summary: WORKFLOW_TOOL_SUMMARY,
2174
- ...parameters === void 0 ? {} : { parameters },
2175
- async execute(args) {
2176
- let target;
2177
- if (Object.keys(args).length === 0) target = definition;
2178
- else if (Array.isArray(args.steps)) {
2179
- const flat = steps.parse(args);
2180
- target = flat === void 0 ? void 0 : expandSteps(flat);
2181
- } else {
2182
- const parsed = draft.parse(args);
2183
- target = parsed === void 0 ? void 0 : completeDraft(parsed);
2184
- }
2185
- if (target === void 0 || !strict.is(target)) throw new _orkestrel_workflow.WorkflowError("TOOL", "malformed workflow definition", { workflow: definition.id });
2186
- if (depth + 1 > 8) throw new _orkestrel_workflow.WorkflowError("DEPTH", `nested workflow exceeds max depth 8`, {
2187
- workflow: target.id,
2188
- depth,
2189
- max: 8
2190
- });
2191
- const tag = workflowTag(target.id);
2192
- if (ancestry.includes(tag)) throw new _orkestrel_workflow.WorkflowError("DEPTH", `workflow '${target.id}' is already an ancestor (cycle)`, {
2193
- workflow: target.id,
2194
- ancestry: [...ancestry]
2195
- });
2196
- const result = await runner.execute(target);
2197
- if (store !== void 0) await store.set(result.workflow.snapshot());
2198
- return workflowToolSummary(result);
2199
- }
2200
- });
2201
- }
2202
- /**
2203
- * Build an LLM-callable workspace-editing tool — it ADVERTISES the `operation`-discriminated
2204
- * 13-op union ({@link import('./shapers.js').workspaceToolShape}) as its `parameters`, and its
2205
- * handler PARSES the model-supplied args against that contract and DISPATCHES the matched
2206
- * operation against the manager's ACTIVE workspace (the registry ops drive the manager itself),
2207
- * returning the plain result (throwing a typed `WorkspaceError`, `@orkestrel/agent`, on
2208
- * failure). EITHER drives a caller-supplied {@link WorkspaceToolOptions.manager} directly, OR
2209
- * constructs a fresh `WorkspaceManagerInterface` (`@orkestrel/agent`) over
2210
- * {@link import('./types.js').WorkspaceToolOptions.store} (via `@orkestrel/agent`'s
2211
- * `createWorkspaceManager`); neither given constructs a manager backed by `@orkestrel/agent`'s
2212
- * in-memory store default.
2213
- *
2214
- * @remarks
2215
- * MANAGER-DRIVEN: every edit / read op (read / list / has / search / replace / write / splice /
2216
- * prepend / append / move / remove) targets `manager.active`, so the model edits whichever
2217
- * workspace is active and a host can re-point it (`WorkspaceManagerInterface.switch`) between
2218
- * turns. Two REGISTRY ops make the model self-sufficient: `workspaces` LISTS the registered
2219
- * workspaces (each `{ id, files, active }`) so it can discover an id, and `switch` re-points the
2220
- * active workspace by id (lenient — an unknown id is a no-op reporting `switched: false`, never a
2221
- * throw).
2222
- *
2223
- * NO-ACTIVE RULE (the ergonomic seam): a WRITING op (write / splice / prepend / append / move /
2224
- * remove / replace) run when `manager.active` is `undefined` AUTO-CREATES + activates a default
2225
- * workspace (`manager.add()`) so the model can just start writing; a pure-READ op (read / list /
2226
- * has / search) against no active workspace returns the EMPTY result (`undefined` / `[]` /
2227
- * `false`), never creating one and never throwing.
2228
- *
2229
- * The handler conforms to the universal tool-handler contract (AGENTS §14): it `contract.parse`s
2230
- * the args, THROWS a `TOOL` `WorkspaceError` when no operation arm matched (a malformed / unknown
2231
- * operation), else `switch`es on `op.operation` and RETURNS the plain result — letting a
2232
- * `WorkspaceError` raised by the live workspace (`MODALITY` / `PATTERN` / `RANGE`) PROPAGATE
2233
- * uncaught. The range edit is the FLAT `'splice'` op: its four flat caret integers are
2234
- * reassembled into a `Range` (`@orkestrel/agent`) by `rangeOf` and fed to the workspace's ranged
2235
- * `write`.
2236
- *
2237
- * @param options - `manager` (drive directly) OR `store` (build a manager over it); neither ⇒
2238
- * an in-memory-backed manager (see {@link import('./types.js').WorkspaceToolOptions})
2239
- * @returns A `ToolInterface` (named {@link import('./constants.js').WORKSPACE_TOOL_NAME} by default)
2240
- *
2241
- * @example
2242
- * ```ts
2243
- * import { createWorkspaceTool } from '@src/core'
2244
- * import { createToolManager } from '@orkestrel/agent'
2245
- *
2246
- * const tool = createWorkspaceTool() // in-memory workspace, no persistence
2247
- * const tools = createToolManager()
2248
- * tools.add(tool)
2249
- * ```
2250
- */
2251
- function createWorkspaceTool(options) {
2252
- const manager = options?.manager ?? (0, _orkestrel_agent.createWorkspaceManager)(options?.store === void 0 ? void 0 : { store: options.store });
2253
- const contract = (0, _orkestrel_contract.createContract)(workspaceToolShape);
2254
- const parameters = (0, _orkestrel_contract.schemaToParameters)(contract.schema);
2255
- return (0, _orkestrel_agent.createTool)({
2256
- name: options?.name ?? "workspace",
2257
- description: options?.description ?? WORKSPACE_TOOL_DESCRIPTION,
2258
- summary: WORKSPACE_TOOL_SUMMARY,
2259
- ...parameters === void 0 ? {} : { parameters },
2260
- execute(args) {
2261
- const op = contract.parse(args);
2262
- if (op === void 0) throw new _orkestrel_agent.WorkspaceError("TOOL", `unknown or malformed operation`, { args });
2263
- if (op.operation === "workspaces") {
2264
- const activeId = manager.active?.id;
2265
- return manager.workspaces().map((workspace) => ({
2266
- id: workspace.id,
2267
- files: workspace.count,
2268
- active: workspace.id === activeId
2269
- }));
2270
- }
2271
- if (op.operation === "switch") {
2272
- const switched = manager.switch(op.id);
2273
- return switched === void 0 ? {
2274
- id: op.id,
2275
- switched: false
2276
- } : {
2277
- id: switched.id,
2278
- switched: true,
2279
- files: switched.count
2280
- };
2281
- }
2282
- const active = manager.active;
2283
- switch (op.operation) {
2284
- case "read": return active?.read(op.path);
2285
- case "list": return (active?.files() ?? []).map((file) => ({
2286
- path: file.path,
2287
- state: file.state,
2288
- size: file.size,
2289
- lines: file.lines,
2290
- kind: (0, _orkestrel_agent.isText)(file.content) ? "text" : "binary"
2291
- }));
2292
- case "has": return active?.has(op.path) ?? false;
2293
- case "search": return active?.search(op.query, {
2294
- ...op.regex === void 0 ? {} : { regex: op.regex },
2295
- ...op.exact === void 0 ? {} : { exact: op.exact },
2296
- ...op.limit === void 0 ? {} : { limit: op.limit }
2297
- }) ?? [];
2298
- case "replace": return (active ?? manager.add()).replace(op.query, op.replacement, {
2299
- ...op.regex === void 0 ? {} : { regex: op.regex },
2300
- ...op.exact === void 0 ? {} : { exact: op.exact },
2301
- ...op.limit === void 0 ? {} : { limit: op.limit }
2302
- });
2303
- case "write": {
2304
- const workspace = active ?? manager.add();
2305
- workspace.write(op.path, op.content);
2306
- return {
2307
- path: op.path,
2308
- state: workspace.file(op.path)?.state
2309
- };
2310
- }
2311
- case "splice": {
2312
- const workspace = active ?? manager.add();
2313
- workspace.write(op.path, op.content, (0, _orkestrel_agent.rangeOf)(op.fromLine, op.fromColumn, op.toLine, op.toColumn));
2314
- return {
2315
- path: op.path,
2316
- state: workspace.file(op.path)?.state
2317
- };
2318
- }
2319
- case "prepend": {
2320
- const workspace = active ?? manager.add();
2321
- workspace.prepend(op.path, op.content);
2322
- return {
2323
- path: op.path,
2324
- state: workspace.file(op.path)?.state
2325
- };
2326
- }
2327
- case "append": {
2328
- const workspace = active ?? manager.add();
2329
- workspace.append(op.path, op.content);
2330
- return {
2331
- path: op.path,
2332
- state: workspace.file(op.path)?.state
2333
- };
2334
- }
2335
- case "move": {
2336
- const workspace = active ?? manager.add();
2337
- return {
2338
- from: op.from,
2339
- to: op.to,
2340
- moved: workspace.move(op.from, op.to)
2341
- };
2342
- }
2343
- case "remove": {
2344
- const workspace = active ?? manager.add();
2345
- return {
2346
- path: op.path,
2347
- removed: workspace.remove(op.path)
2348
- };
2349
- }
2350
- }
2351
- }
2352
- });
2353
- }
2354
- /**
2355
- * Build an LLM-callable sub-agent delegation tool — resolves a live, seeded `AgentInterface`
2356
- * from `registry` and runs it to completion for ONE delegated `task`.
2357
- *
2358
- * @remarks
2359
- * The universal tool-handler contract (AGENTS §14): validates the call args against
2360
- * {@link import('./shapers.js').agentToolShape}, assembles an `AgentJobInput` (`task` seeds the
2361
- * sub-agent's conversation as a single `user` message; `provider` / `tools` / `system` fall
2362
- * back to the tool's own {@link import('./types.js').AgentToolOptions} defaults), rehydrates the sub-agent via
2363
- * `registry.build`, runs it with `agent.generate()`, and returns the settled
2364
- * `AgentResult.content` string (the sub-agent's final text). A missing / unresolvable `provider`, or a malformed call, THROWS a typed `TOOL`
2365
- * {@link import('./errors.js').AgentToolError}; a delegation that would exceed
2366
- * {@link import('./constants.js').AGENT_TOOL_DEPTH}, or re-enter an already-delegated agent (a
2367
- * cycle), THROWS a typed `DEPTH` {@link import('./errors.js').AgentToolError} — both isolated
2368
- * by the `ToolManagerInterface` into the canonical tool result's top-level `error`.
2369
- *
2370
- * `AgentInterface` (`@orkestrel/agent`) exposes no teardown method — a bound sub-agent's
2371
- * lifetime is the single `generate()` call this handler awaits; there is nothing to release
2372
- * afterwards (unlike a store-backed resource, its state lives entirely in the resolved
2373
- * `AgentContextInterface`, owned by the caller's registry).
2374
- *
2375
- * @param registry - The `AgentRegistryInterface` a delegated job resolves against (providers,
2376
- * tools, authorities, schedulers, and the `build` rehydration seam)
2377
- * @param options - Delegation defaults, depth/ancestry bookkeeping, and advertised overrides
2378
- * (see {@link import('./types.js').AgentToolOptions})
2379
- * @returns A `ToolInterface` (named {@link import('./constants.js').AGENT_TOOL_NAME} by default)
2380
- *
2381
- * @example
2382
- * ```ts
2383
- * import { createAgentTool } from '@src/core'
2384
- * import { createAgentRegistry, createToolManager } from '@orkestrel/agent'
2385
- *
2386
- * const registry = createAgentRegistry({ providers: { openai: myProvider } })
2387
- * const tool = createAgentTool(registry, { provider: 'openai' })
2388
- * const tools = createToolManager()
2389
- * tools.add(tool) // a model can now delegate a task to a sub-agent
2390
- * ```
2391
- */
2392
- function createAgentTool(registry, options) {
2393
- const contract = (0, _orkestrel_contract.createContract)(agentToolShape);
2394
- const parameters = (0, _orkestrel_contract.schemaToParameters)(contract.schema);
2395
- const depth = options?.depth ?? 0;
2396
- const ancestry = options?.ancestry ?? [];
2397
- return (0, _orkestrel_agent.createTool)({
2398
- name: options?.name ?? "agent",
2399
- description: options?.description ?? AGENT_TOOL_DESCRIPTION,
2400
- summary: AGENT_TOOL_SUMMARY,
2401
- ...parameters === void 0 ? {} : { parameters },
2402
- async execute(args) {
2403
- const call = contract.parse(args);
2404
- if (call === void 0) throw new AgentToolError("TOOL", "malformed agent-delegation call", { args });
2405
- const provider = call.provider ?? options?.provider;
2406
- if (provider === void 0) throw new AgentToolError("TOOL", "no provider resolved for the delegated agent", { task: call.task });
2407
- if (depth + 1 > 8) throw new AgentToolError("DEPTH", `delegation exceeds max agent depth 8`, {
2408
- provider,
2409
- depth,
2410
- max: 8
2411
- });
2412
- const tag = agentTag(provider);
2413
- if (ancestry.includes(tag)) throw new AgentToolError("DEPTH", `agent '${provider}' is already an ancestor (cycle)`, {
2414
- provider,
2415
- ancestry: [...ancestry]
2416
- });
2417
- const tools = call.tools ?? options?.tools;
2418
- const system = call.system ?? options?.system;
2419
- const agent = registry.build({
2420
- provider,
2421
- messages: [{
2422
- role: "user",
2423
- content: call.task
2424
- }],
2425
- ...system === void 0 ? {} : { system },
2426
- ...tools === void 0 ? {} : { tools }
2427
- });
2428
- const result = await agent.generate();
2429
- if (options?.store !== void 0) {
2430
- const active = agent.context.conversations.active;
2431
- if (active !== void 0) await options.store.set(active.snapshot());
2432
- }
2433
- return result.content;
2434
- }
2435
- });
2436
- }
2437
- /**
2438
- * Build an LLM-callable tool that returns the FULL `description` of another registered tool by
2439
- * name — the counterpart to the lean `summary` the other tools in this package advertise
2440
- * (`AGENT_TOOL_SUMMARY` / `WORKFLOW_TOOL_SUMMARY` / `WORKSPACE_TOOL_SUMMARY`).
2441
- *
2442
- * @remarks
2443
- * `ToolManagerInterface.definitions()` (`@orkestrel/agent`) advertises `tool.summary ??
2444
- * tool.description` — a lean one-sentence summary stands in for a tool's full teaching
2445
- * description when `summary` is set, keeping the advertised tool list compact for a small model.
2446
- * This tool is the on-demand expansion seam: given a registered tool's `name`, it looks the tool
2447
- * up via `tools.tool(name)` and returns its full `description` (falling back to `summary` when a
2448
- * tool has no `description` of its own, then a placeholder when it has neither).
2449
- *
2450
- * The universal tool-handler contract (AGENTS §14): validates the call args against
2451
- * {@link import('./shapers.js').describeToolShape}, RETURNS the plain description string on
2452
- * success, THROWS a typed `TOOL` {@link import('./errors.js').AgentToolError} on a malformed call
2453
- * or an unknown tool name.
2454
- *
2455
- * @param tools - The `ToolManagerInterface` (`@orkestrel/agent`) whose registered tools this
2456
- * tool can describe
2457
- * @returns A `ToolInterface` (named {@link import('./constants.js').DESCRIBE_TOOL_NAME})
2458
- *
2459
- * @example
2460
- * ```ts
2461
- * import { createDescribeTool, createWorkflowTool } from '@src/core'
2462
- * import { createToolManager } from '@orkestrel/agent'
2463
- *
2464
- * const tools = createToolManager()
2465
- * tools.add(createWorkflowTool(definition, runner))
2466
- * tools.add(createDescribeTool(tools))
2467
- * const full = await tools.execute({ id: '1', name: 'describe', arguments: { name: 'workflow' } })
2468
- * full.value // the workflow tool's full teaching description
2469
- * ```
2470
- */
2471
- function createDescribeTool(tools) {
2472
- const contract = (0, _orkestrel_contract.createContract)(describeToolShape);
2473
- const parameters = (0, _orkestrel_contract.schemaToParameters)(contract.schema);
2474
- return (0, _orkestrel_agent.createTool)({
2475
- name: DESCRIBE_TOOL_NAME,
2476
- description: DESCRIBE_TOOL_DESCRIPTION,
2477
- summary: DESCRIBE_TOOL_SUMMARY,
2478
- ...parameters === void 0 ? {} : { parameters },
2479
- async execute(args) {
2480
- const call = contract.parse(args);
2481
- if (call === void 0) throw new AgentToolError("TOOL", "malformed describe call", { args });
2482
- const tool = tools.tool(call.name);
2483
- if (tool === void 0) throw new AgentToolError("TOOL", `unknown tool '${call.name}'`, { name: call.name });
2484
- return tool.description ?? tool.summary ?? "<no description>";
2485
- }
2486
- });
2487
- }
2488
- /**
2489
- * Build an LLM-callable prompt tool — the ASK side of the terminal seam. Asks
2490
- * {@link import('./types.js').PromptToolOptions.to} a question and BLOCKS until it answers,
2491
- * returning the resolved answer value.
2492
- *
2493
- * @remarks
2494
- * The universal tool-handler contract (AGENTS §14): validates the call args against
2495
- * {@link import('./shapers.js').promptToolShape}, dispatches to the matching
2496
- * `TerminalManagerInterface.ask` overload (`@orkestrel/terminal`) for the call's `form`, and
2497
- * RETURNS the resolved answer on success. `from` is FIXED at construction
2498
- * ({@link import('./types.js').PromptToolOptions.from}) — never read from the model-supplied
2499
- * args — so a model cannot spoof which terminal is asking. A prompt CYCLE rejects with
2500
- * `TerminalError('DEADLOCK')`, re-surfaced as a typed `DEADLOCK`
2501
- * {@link import('./errors.js').AgentToolError}; an expired prompt re-surfaces as `EXPIRE`; an
2502
- * unknown `to` (or any other `TerminalError`) re-surfaces as `TOOL`, naming the unknown terminal
2503
- * plus the known ones (`manager.terminals()`).
2504
- *
2505
- * @param options - The live manager, the fixed `from` identity, and advertised overrides (see
2506
- * {@link import('./types.js').PromptToolOptions})
2507
- * @returns A `ToolInterface` (named {@link import('./constants.js').PROMPT_TOOL_NAME} by default)
2508
- *
2509
- * @example
2510
- * ```ts
2511
- * import { createPromptTool } from '@src/core'
2512
- * import { createTerminalManager, createToolManager } from '@orkestrel/terminal'
2513
- *
2514
- * const manager = createTerminalManager()
2515
- * manager.add('agent')
2516
- * manager.add('reviewer')
2517
- * const tool = createPromptTool({ manager, from: 'agent' })
2518
- * const tools = createToolManager()
2519
- * tools.add(tool) // the agent can now ask 'reviewer' and block for the answer
2520
- * ```
2521
- */
2522
- function createPromptTool(options) {
2523
- const contract = (0, _orkestrel_contract.createContract)(promptToolShape);
2524
- const parameters = (0, _orkestrel_contract.schemaToParameters)(contract.schema);
2525
- return (0, _orkestrel_agent.createTool)({
2526
- name: options.name ?? "ask",
2527
- description: options.description ?? PROMPT_TOOL_DESCRIPTION,
2528
- summary: PROMPT_TOOL_SUMMARY,
2529
- ...parameters === void 0 ? {} : { parameters },
2530
- async execute(args) {
2531
- const call = contract.parse(args);
2532
- if (call === void 0) throw new AgentToolError("TOOL", "malformed ask call", { args });
2533
- if ((call.form === "select" || call.form === "checkbox") && (call.choices ?? []).length === 0) throw new AgentToolError("TOOL", "select/checkbox requires at least one choice", {
2534
- to: call.to,
2535
- form: call.form
2536
- });
2537
- try {
2538
- switch (call.form) {
2539
- case "input": return await options.manager.ask(options.from, call.to, call.form, {
2540
- message: call.message,
2541
- ...call.default === void 0 ? {} : { default: call.default },
2542
- ...call.validate === void 0 ? {} : { validate: call.validate }
2543
- });
2544
- case "editor": return await options.manager.ask(options.from, call.to, call.form, {
2545
- message: call.message,
2546
- ...call.default === void 0 ? {} : { default: call.default },
2547
- ...call.validate === void 0 ? {} : { validate: call.validate }
2548
- });
2549
- case "password": return await options.manager.ask(options.from, call.to, call.form, {
2550
- message: call.message,
2551
- ...call.mask === void 0 ? {} : { mask: call.mask },
2552
- ...call.validate === void 0 ? {} : { validate: call.validate }
2553
- });
2554
- case "confirm": return await options.manager.ask(options.from, call.to, call.form, {
2555
- message: call.message,
2556
- ...call.default === void 0 ? {} : { default: call.default === "true" }
2557
- });
2558
- case "select": return await options.manager.ask(options.from, call.to, call.form, {
2559
- message: call.message,
2560
- choices: call.choices ?? [],
2561
- ...call.default === void 0 ? {} : { default: call.default }
2562
- });
2563
- case "checkbox": return await options.manager.ask(options.from, call.to, call.form, {
2564
- message: call.message,
2565
- choices: call.choices ?? [],
2566
- ...call.min === void 0 ? {} : { min: call.min },
2567
- ...call.max === void 0 ? {} : { max: call.max }
2568
- });
2569
- }
2570
- } catch (error) {
2571
- const code = terminalToolCode(error);
2572
- if (code === void 0) throw error;
2573
- if (code === "DEADLOCK") throw new AgentToolError("DEADLOCK", `asking '${call.to}' would form a prompt cycle`, (0, _orkestrel_terminal.isTerminalError)(error) ? error.context : {
2574
- from: options.from,
2575
- to: call.to
2576
- });
2577
- if (code === "EXPIRE") throw new AgentToolError("EXPIRE", `prompt to '${call.to}' expired before it was answered`, { to: call.to });
2578
- if ((0, _orkestrel_terminal.isTerminalError)(error) && error.code === "TARGET") throw new AgentToolError("TOOL", `unknown terminal '${call.to}'`, {
2579
- to: call.to,
2580
- known: options.manager.terminals()
2581
- });
2582
- throw new AgentToolError("TOOL", `asking '${call.to}' failed`, { to: call.to });
2583
- }
2584
- }
2585
- });
2586
- }
2587
- /**
2588
- * Build an LLM-callable answer tool — the ANSWER side of the terminal seam. Lists the prompts
2589
- * currently addressed to {@link import('./types.js').AnswerToolOptions.to}, or answers one of
2590
- * them by id.
2591
- *
2592
- * @remarks
2593
- * The universal tool-handler contract (AGENTS §14): validates the call args against
2594
- * {@link import('./shapers.js').answerToolShape} (discriminated by `operation`). `'pending'`
2595
- * returns a compact list (`{ id, from, form, message }`) of every prompt currently addressed to
2596
- * `to` (`TerminalManagerInterface.pending`, `@orkestrel/terminal`). `'answer'` looks the prompt
2597
- * up by `id` (an unknown id throws a typed `ANSWER` {@link import('./errors.js').AgentToolError}),
2598
- * normalizes the model-supplied `value` to the prompt's own form
2599
- * ({@link import('./helpers.js').coerceAnswer}), and applies it via
2600
- * `TerminalManagerInterface.answer` — a rejected / unknown / unresolvable outcome
2601
- * (`TerminalAnswerResult.error`) re-surfaces as a typed `ANSWER` `AgentToolError`; success returns
2602
- * `{ answered: id }`. `to` is FIXED at construction
2603
- * ({@link import('./types.js').AnswerToolOptions.to}) — never read from the model-supplied args —
2604
- * so a model cannot spoof which terminal it is answering for. Concurrent answerers racing on one
2605
- * endpoint are FIRST-WRITE-WINS — a late answer to an already-settled prompt returns a typed
2606
- * `ANSWER` `AgentToolError` (surfaced as a 422 over HTTP).
2607
- *
2608
- * @param options - The live manager, the fixed `to` identity, and advertised overrides (see
2609
- * {@link import('./types.js').AnswerToolOptions})
2610
- * @returns A `ToolInterface` (named {@link import('./constants.js').ANSWER_TOOL_NAME} by default)
2611
- *
2612
- * @example
2613
- * ```ts
2614
- * import { createAnswerTool } from '@src/core'
2615
- * import { createTerminalManager, createToolManager } from '@orkestrel/terminal'
2616
- *
2617
- * const manager = createTerminalManager()
2618
- * manager.add('reviewer')
2619
- * const tool = createAnswerTool({ manager, to: 'reviewer' })
2620
- * const tools = createToolManager()
2621
- * tools.add(tool) // the reviewer terminal can now list/answer prompts addressed to it
2622
- * ```
2623
- */
2624
- function createAnswerTool(options) {
2625
- const contract = (0, _orkestrel_contract.createContract)(answerToolShape);
2626
- const parameters = (0, _orkestrel_contract.schemaToParameters)(contract.schema);
2627
- return (0, _orkestrel_agent.createTool)({
2628
- name: options.name ?? "answer",
2629
- description: options.description ?? ANSWER_TOOL_DESCRIPTION,
2630
- summary: ANSWER_TOOL_SUMMARY,
2631
- ...parameters === void 0 ? {} : { parameters },
2632
- async execute(args) {
2633
- const call = contract.parse(args);
2634
- if (call === void 0) throw new AgentToolError("TOOL", "malformed answer call", { args });
2635
- if (call.operation === "pending") return options.manager.pending(options.to).map((prompt) => ({
2636
- id: prompt.id,
2637
- from: prompt.from,
2638
- form: prompt.form,
2639
- message: prompt.message
2640
- }));
2641
- const prompt = options.manager.pending(options.to).find((entry) => entry.id === call.id);
2642
- if (prompt === void 0) throw new AgentToolError("ANSWER", `unknown prompt '${call.id}'`, {
2643
- id: call.id,
2644
- reason: "unknown"
2645
- });
2646
- const coerced = coerceAnswer(prompt.form, call.value);
2647
- const result = options.manager.answer(options.to, call.id, coerced);
2648
- if (!result.success) throw new AgentToolError("ANSWER", `failed to answer prompt '${call.id}': ${result.error}`, {
2649
- id: call.id,
2650
- reason: result.error
2651
- });
2652
- return { answered: call.id };
2653
- }
2654
- });
2655
- }
2656
- /**
2657
- * Create the in-memory {@link DefinitionStoreInterface} — a process-lifetime `Map` of database
2658
- * definitions, the DEFAULT store the upcoming database / relation tools will persist their
2659
- * `DatabaseDefinition` configs through.
2660
- *
2661
- * @returns A {@link DefinitionStoreInterface}
2662
- *
2663
- * @example
2664
- * ```ts
2665
- * import { createMemoryDefinitionStore } from '@src/core'
2666
- *
2667
- * const store = createMemoryDefinitionStore()
2668
- * ```
2669
- */
2670
- function createMemoryDefinitionStore() {
2671
- return new MemoryDefinitionStore();
2672
- }
2673
- /**
2674
- * Create a {@link DefinitionStoreInterface} backed by one table of the `@orkestrel/database`
2675
- * layer — the driver-pluggable twin of {@link createMemoryDefinitionStore}, storing each
2676
- * database's definition as one opaque JSON column.
2677
- *
2678
- * @param driver - The {@link DriverInterface} backing the table (default an in-memory driver)
2679
- * @returns A {@link DefinitionStoreInterface}
2680
- *
2681
- * @example
2682
- * ```ts
2683
- * import { createDatabaseDefinitionStore } from '@src/core'
2684
- *
2685
- * const store = createDatabaseDefinitionStore() // in-memory by default
2686
- * ```
2687
- */
2688
- function createDatabaseDefinitionStore(driver = (0, _orkestrel_database.createMemoryDriver)()) {
2689
- return new DatabaseDefinitionStore((0, _orkestrel_database.createDatabase)({
2690
- driver,
2691
- tables: { definitions: {
2692
- id: (0, _orkestrel_contract.stringShape)(),
2693
- definition: (0, _orkestrel_contract.rawShape)({})
2694
- } }
2695
- }).table("definitions"));
2696
- }
2697
- /**
2698
- * Build an LLM-callable database tool — create, query, and mutate `@orkestrel/database`
2699
- * databases through one `operation`-discriminated call (AGENTS §14, matching
2700
- * {@link createWorkspaceTool}'s single-tool-many-operations shape).
2701
- *
2702
- * @remarks
2703
- * The universal tool-handler contract (AGENTS §14): validates the call args against
2704
- * {@link import('./shapers.js').databaseToolShape}, dispatches to the matching operation, and
2705
- * RETURNS a plain result on success. A database is resolved lazily and cached for the tool's
2706
- * lifetime — `'create'` mints one from `tables` ({@link import('./helpers.js').expandTables}) and
2707
- * a registered `driver` key ({@link import('./types.js').DatabaseToolOptions.drivers}, default
2708
- * `{ memory: () => createMemoryDriver() }`); any other operation addressing an uncached id falls
2709
- * back to {@link import('./types.js').DatabaseToolOptions.store} (an unknown id throws a typed
2710
- * `TOOL` {@link import('./errors.js').AgentToolError}). When a `store` is configured, `'create'`
2711
- * persists the new {@link import('./types.js').DatabaseDefinition} and `'destroy'` deletes it.
2712
- *
2713
- * `'migrate'` re-declares a LIVE handle's tables via `DatabaseInterface.import` (the SAME driver
2714
- * and storage, a NEW typed view) and calls its `migrate` against the OLD deployed schema —
2715
- * derived from the handle's OWN `export()` (via {@link import('./helpers.js').tableSchema}), so it
2716
- * works for any handle, config-tracked or caller-supplied via
2717
- * {@link import('./types.js').DatabaseToolOptions.databases}. `'records'` clamps its `criteria` to
2718
- * {@link import('./types.js').DatabaseToolOptions.limit} (default
2719
- * {@link import('./constants.js').DATABASE_TOOL_LIMIT}) via
2720
- * {@link import('./helpers.js').clampCriteria}, reporting `truncated` when storage held more rows
2721
- * than the cap. Every operation's `criteria` is normalized via
2722
- * {@link import('./helpers.js').criteriaOf} (defaults an omitted condition `connector` to `'and'`).
2723
- * When {@link import('./types.js').DatabaseToolOptions.readonly} is `true`, every mutating
2724
- * operation throws a typed `TOOL` `AgentToolError` before doing anything. When
2725
- * {@link import('./types.js').DatabaseToolOptions.timeout} is set, every `@orkestrel/database` call
2726
- * this tool makes is given a fresh `AbortSignal.timeout(timeout)`. A typed `@orkestrel/database`
2727
- * failure (`DatabaseError`) re-surfaces as a typed `DATABASE` `AgentToolError` carrying the
2728
- * original {@link import('@orkestrel/database').DatabaseErrorCode} in `context.code`
2729
- * ({@link import('./helpers.js').databaseToolCode}); an `AgentToolError` thrown by this tool's own
2730
- * guards passes through unwrapped.
2731
- *
2732
- * A lazily re-minted database over the DEFAULT in-memory driver yields an EMPTY database — only
2733
- * the {@link import('./types.js').DatabaseDefinition} schema persists in `store`, never rows;
2734
- * durable rows need a persistent driver factory registered in
2735
- * {@link import('./types.js').DatabaseToolOptions.drivers}. `'destroy'` closes whatever handle is
2736
- * cached for the id, including an embedder-supplied
2737
- * {@link import('./types.js').DatabaseToolOptions.databases} handle — the embedder relinquishes
2738
- * that handle's lifecycle to this tool for any id it wires in. This tool assumes the
2739
- * single-writer, non-reentrant model `@orkestrel/database` itself assumes — concurrent calls
2740
- * against one id are NOT serialized by this tool. `'get'` is uncapped by
2741
- * {@link import('./types.js').DatabaseToolOptions.limit} (bounded only by the caller's `key` array
2742
- * size), unlike `'records'` / `'find'` / `'links'`.
2743
- *
2744
- * @param options - The tool's configuration (see {@link import('./types.js').DatabaseToolOptions})
2745
- * @returns A `ToolInterface` (named {@link import('./constants.js').DATABASE_TOOL_NAME} by default)
2746
- *
2747
- * @example
2748
- * ```ts
2749
- * import { createDatabaseTool } from '@src/core'
2750
- *
2751
- * const tool = createDatabaseTool()
2752
- * await tool.execute({
2753
- * operation: 'create',
2754
- * id: 'shop',
2755
- * tables: { products: { columns: { name: 'string', price: 'number' } } },
2756
- * })
2757
- * ```
2758
- */
2759
- function createDatabaseTool(options = {}) {
2760
- const contract = (0, _orkestrel_contract.createContract)(databaseToolShape);
2761
- const parameters = (0, _orkestrel_contract.schemaToParameters)(contract.schema);
2762
- const handles = new Map(Object.entries(options.databases ?? {}));
2763
- const definitions = /* @__PURE__ */ new Map();
2764
- const drivers = options.drivers ?? { memory: _orkestrel_database.createMemoryDriver };
2765
- const key = options.key ?? _orkestrel_database.generateUUID;
2766
- const cap = options.limit ?? 1e3;
2767
- const store = options.store;
2768
- const resolver = store === void 0 ? new DatabaseResolver(handles, drivers, key) : new DatabaseResolver(handles, drivers, key, store);
2769
- return (0, _orkestrel_agent.createTool)({
2770
- name: options.name ?? "database",
2771
- description: options.description ?? DATABASE_TOOL_DESCRIPTION,
2772
- summary: DATABASE_TOOL_SUMMARY,
2773
- ...parameters === void 0 ? {} : { parameters },
2774
- async execute(args) {
2775
- const call = contract.parse(args);
2776
- if (call === void 0) throw new AgentToolError("TOOL", "malformed database call", { args });
2777
- if (options.readonly === true && DATABASE_TOOL_MUTATIONS.has(call.operation)) throw new AgentToolError("TOOL", `operation '${call.operation}' is disabled in readonly mode`, { operation: call.operation });
2778
- const read = options.timeout === void 0 ? void 0 : { signal: AbortSignal.timeout(options.timeout) };
2779
- try {
2780
- switch (call.operation) {
2781
- case "create": {
2782
- if (resolver.has(call.id) || store !== void 0 && await store.get(call.id) !== void 0) throw new AgentToolError("TOOL", `database '${call.id}' already exists`, { id: call.id });
2783
- const name = call.driver ?? "memory";
2784
- const factory = drivers[name];
2785
- if (factory === void 0) throw new AgentToolError("TOOL", `unknown driver '${name}'`, {
2786
- id: call.id,
2787
- driver: name
2788
- });
2789
- const tables = call.tables;
2790
- const keys = call.keys;
2791
- const handle = (0, _orkestrel_database.createDatabase)({
2792
- driver: factory(),
2793
- tables: expandTables(tables),
2794
- ...keys === void 0 ? {} : { keys },
2795
- key
2796
- });
2797
- resolver.set(call.id, handle);
2798
- const definition = {
2799
- id: call.id,
2800
- driver: name,
2801
- tables,
2802
- ...keys === void 0 ? {} : { keys }
2803
- };
2804
- definitions.set(call.id, definition);
2805
- if (store !== void 0) await store.set(definition);
2806
- return {
2807
- id: call.id,
2808
- tables: Object.keys(tables)
2809
- };
2810
- }
2811
- case "tables": {
2812
- const handle = await resolver.resolve(call.id);
2813
- return { tables: Object.keys(handle.export()).map((name) => {
2814
- const table = handle.table(name);
2815
- return {
2816
- name,
2817
- primary: table.primary,
2818
- columns: table.contract.schema
2819
- };
2820
- }) };
2821
- }
2822
- case "get": {
2823
- const table = (await resolver.resolve(call.id)).table(call.table);
2824
- const many = Array.isArray(call.key);
2825
- const keys = Array.isArray(call.key) ? call.key : [call.key];
2826
- const rows = await table.get(keys);
2827
- return many ? { rows } : { row: rows[0] };
2828
- }
2829
- case "records": {
2830
- const table = (await resolver.resolve(call.id)).table(call.table);
2831
- const { criteria: probe, limit } = clampCriteria(criteriaOf(call.criteria), cap);
2832
- const rows = await table.records(probe, read);
2833
- const truncated = rows.length > limit;
2834
- const sliced = rows.slice(0, limit);
2835
- return {
2836
- rows: sliced,
2837
- count: sliced.length,
2838
- truncated,
2839
- limit
2840
- };
2841
- }
2842
- case "count": return { count: await (await resolver.resolve(call.id)).table(call.table).count(criteriaOf(call.criteria), read) };
2843
- case "aggregate": return { value: await (await resolver.resolve(call.id)).table(call.table).aggregate(call.function, call.column, criteriaOf(call.criteria), read) };
2844
- case "add": {
2845
- const table = (await resolver.resolve(call.id)).table(call.table);
2846
- const many = Array.isArray(call.row);
2847
- const rows = Array.isArray(call.row) ? call.row : [call.row];
2848
- const keys = await table.add(rows, read);
2849
- return many ? { keys } : { key: keys[0] };
2850
- }
2851
- case "set": {
2852
- const table = (await resolver.resolve(call.id)).table(call.table);
2853
- const many = Array.isArray(call.row);
2854
- const rows = Array.isArray(call.row) ? call.row : [call.row];
2855
- const keys = await table.set(rows, read);
2856
- return many ? { keys } : { key: keys[0] };
2857
- }
2858
- case "update": {
2859
- const table = (await resolver.resolve(call.id)).table(call.table);
2860
- const changes = call.changes;
2861
- const many = Array.isArray(call.key);
2862
- const keys = Array.isArray(call.key) ? call.key : [call.key];
2863
- const updated = await table.update(keys, changes, read);
2864
- return many ? { updated } : { updated: updated[0] };
2865
- }
2866
- case "remove": {
2867
- const table = (await resolver.resolve(call.id)).table(call.table);
2868
- const many = Array.isArray(call.key);
2869
- const keys = Array.isArray(call.key) ? call.key : [call.key];
2870
- const removed = await table.remove(keys, read);
2871
- return many ? { removed } : { removed: removed[0] };
2872
- }
2873
- case "migrate": {
2874
- const handle = await resolver.resolve(call.id);
2875
- const previous = handle.export();
2876
- const deployed = Object.entries(previous).map(([name, table]) => tableSchema(name, table));
2877
- const tables = call.tables;
2878
- const keys = {};
2879
- for (const name of Object.keys(tables)) {
2880
- const existing = previous[name];
2881
- if (existing !== void 0) keys[name] = existing.key;
2882
- }
2883
- const declared = expandTables(tables);
2884
- const migrated = handle.import(declared, Object.keys(keys).length > 0 ? keys : void 0);
2885
- const migration = await migrated.migrate(deployed, read);
2886
- resolver.set(call.id, migrated);
2887
- const tracked = definitions.get(call.id) ?? (store === void 0 ? void 0 : await store.get(call.id));
2888
- if (tracked !== void 0) {
2889
- const updated = {
2890
- id: call.id,
2891
- driver: tracked.driver,
2892
- tables,
2893
- ...Object.keys(keys).length > 0 ? { keys } : {}
2894
- };
2895
- definitions.set(call.id, updated);
2896
- if (store !== void 0) await store.set(updated);
2897
- }
2898
- return { migration };
2899
- }
2900
- case "destroy": {
2901
- const cached = resolver.get(call.id);
2902
- const persisted = store !== void 0 && cached === void 0 ? await store.get(call.id) !== void 0 : false;
2903
- if (cached !== void 0) {
2904
- await cached.close();
2905
- resolver.delete(call.id);
2906
- }
2907
- definitions.delete(call.id);
2908
- if (store !== void 0) await store.delete(call.id);
2909
- return {
2910
- id: call.id,
2911
- destroyed: cached !== void 0 || persisted
2912
- };
2913
- }
2914
- }
2915
- } catch (error) {
2916
- if (isAgentToolError(error)) throw error;
2917
- const code = databaseToolCode(error);
2918
- if (code === void 0) throw error;
2919
- throw new AgentToolError("DATABASE", error instanceof Error ? error.message : String(error), {
2920
- code,
2921
- operation: call.operation,
2922
- id: call.id,
2923
- ..."table" in call ? { table: call.table } : {}
2924
- });
2925
- }
2926
- }
2927
- });
2928
- }
2929
- /**
2930
- * Build an LLM-callable relation tool — traverse and edit `@orkestrel/relation` relationships
2931
- * through one `operation`-discriminated call (AGENTS §14, matching {@link createDatabaseTool}'s
2932
- * single-tool-many-operations shape).
2933
- *
2934
- * @remarks
2935
- * The universal tool-handler contract (AGENTS §14): validates the call args against
2936
- * {@link import('./shapers.js').relationToolShape}, resolves the addressed
2937
- * {@link import('@orkestrel/relation').RelationManagerInterface} — an explicit `manager` field
2938
- * must match a key of {@link import('./types.js').RelationToolOptions.managers}, an OMITTED one
2939
- * resolves to the SOLE registered manager, either miss throwing a typed `TOOL`
2940
- * {@link import('./errors.js').AgentToolError}
2941
- * ({@link import('./helpers.js').relationManagerOf}) — then resolves `model` against it
2942
- * ({@link import('./helpers.js').relationModelOf}, same typed-`TOOL`-on-miss shape), and
2943
- * dispatches to the matched operation, RETURNING a plain result on success.
2944
- *
2945
- * `'load'` / `'find'` expand the call's FLAT dot-path `include` list into a live
2946
- * `@orkestrel/relation` `Include` tree via {@link import('./helpers.js').expandInclude}, capped
2947
- * at {@link import('./types.js').RelationToolOptions.depth} (default
2948
- * {@link import('./constants.js').RELATION_TOOL_DEPTH}) — a path exceeding the cap, or carrying an
2949
- * empty segment, throws a typed `TOOL` error. `'load'` dispatches on whether `key` is an array
2950
- * (positional many-key form, AGENTS §9.2) or a single key. `'find'` and `'links'` clamp their
2951
- * result to {@link import('./types.js').RelationToolOptions.limit} (default
2952
- * {@link import('./constants.js').RELATION_TOOL_LIMIT}) — `'find'` probes one row past the
2953
- * effective limit (mirroring {@link import('./helpers.js').clampCriteria}'s idiom) to report
2954
- * `truncated`; `'links'` (which has no upstream pagination) fetches the FULL linked-key list and
2955
- * slices/truncates it the same way. `'link'` / `'unlink'` write / remove one `through` junction
2956
- * row.
2957
- *
2958
- * A typed `@orkestrel/relation` failure (`RelationError`) re-surfaces as a typed `RELATION`
2959
- * `AgentToolError` carrying the original {@link import('@orkestrel/relation').RelationErrorCode}
2960
- * in `context.code`; a typed `@orkestrel/database` failure underneath it (`DatabaseError`)
2961
- * re-surfaces as a typed `DATABASE` `AgentToolError`, mirroring {@link createDatabaseTool}'s error
2962
- * mapping; an `AgentToolError` thrown by this tool's own guards (malformed args, an unknown
2963
- * manager/model) passes through unwrapped.
2964
- *
2965
- * @param options - The tool's configuration (see {@link import('./types.js').RelationToolOptions})
2966
- * @returns A `ToolInterface` (named {@link import('./constants.js').RELATION_TOOL_NAME} by default)
2967
- *
2968
- * @example
2969
- * ```ts
2970
- * import { createRelationTool } from '@src/core'
2971
- *
2972
- * const tool = createRelationTool({ managers: { shop: manager } })
2973
- * await tool.execute({ operation: 'load', model: 'accounts', key: 'acc1', include: ['contacts'] })
2974
- * ```
2975
- */
2976
- function createRelationTool(options) {
2977
- const contract = (0, _orkestrel_contract.createContract)(relationToolShape);
2978
- const parameters = (0, _orkestrel_contract.schemaToParameters)(contract.schema);
2979
- const depth = options.depth ?? 3;
2980
- const cap = options.limit ?? 1e3;
2981
- return (0, _orkestrel_agent.createTool)({
2982
- name: options.name ?? "relation",
2983
- description: options.description ?? RELATION_TOOL_DESCRIPTION,
2984
- summary: RELATION_TOOL_SUMMARY,
2985
- ...parameters === void 0 ? {} : { parameters },
2986
- async execute(args) {
2987
- const call = contract.parse(args);
2988
- if (call === void 0) throw new AgentToolError("TOOL", "malformed relation call", { args });
2989
- try {
2990
- const model = relationModelOf(relationManagerOf(options.managers, call.manager), call.model);
2991
- switch (call.operation) {
2992
- case "load": {
2993
- const include = expandInclude(call.include, depth);
2994
- if (typeof call.key === "string" || typeof call.key === "number") return { row: await model.load(call.key, include) };
2995
- return { rows: await model.load(call.key, include) };
2996
- }
2997
- case "find": {
2998
- const include = expandInclude(call.include, depth);
2999
- const effective = Math.min(call.limit ?? cap, cap);
3000
- const rows = await model.find(include, {
3001
- limit: effective + 1,
3002
- ...call.offset === void 0 ? {} : { offset: call.offset },
3003
- ...call.sort === void 0 ? {} : { sort: call.sort },
3004
- ...call.direction === void 0 ? {} : { direction: call.direction }
3005
- });
3006
- const truncated = rows.length > effective;
3007
- const sliced = rows.slice(0, effective);
3008
- return {
3009
- rows: sliced,
3010
- count: sliced.length,
3011
- truncated,
3012
- limit: effective
3013
- };
3014
- }
3015
- case "link":
3016
- await model.link(call.key, call.relation, call.target);
3017
- return { linked: true };
3018
- case "unlink":
3019
- await model.unlink(call.key, call.relation, call.target);
3020
- return { unlinked: true };
3021
- case "links": {
3022
- const keys = await model.links(call.key, call.relation);
3023
- const truncated = keys.length > cap;
3024
- const sliced = keys.slice(0, cap);
3025
- return {
3026
- keys: sliced,
3027
- count: sliced.length,
3028
- truncated,
3029
- limit: cap
3030
- };
3031
- }
3032
- }
3033
- } catch (error) {
3034
- if (isAgentToolError(error)) throw error;
3035
- const relation = relationToolCode(error);
3036
- if (relation !== void 0) throw new AgentToolError("RELATION", error instanceof Error ? error.message : String(error), {
3037
- code: relation,
3038
- operation: call.operation,
3039
- model: call.model,
3040
- ..."relation" in call ? { relation: call.relation } : {}
3041
- });
3042
- const database = databaseToolCode(error);
3043
- if (database === void 0) throw error;
3044
- throw new AgentToolError("DATABASE", error instanceof Error ? error.message : String(error), {
3045
- code: database,
3046
- operation: call.operation
3047
- });
3048
- }
3049
- }
3050
- });
3051
- }
3052
- /**
3053
- * Build a standalone LLM-callable tool that infers a JSON Schema from example values — the
3054
- * utility half of the "existing API/DB → MCP tool" bridge (the other half,
3055
- * {@link createEndpointTool}, wraps one CONCRETE endpoint).
3056
- *
3057
- * @remarks
3058
- * The universal tool-handler contract (AGENTS §14): validates the call args against
3059
- * {@link import('./shapers.js').inferToolShape} (`samples` non-empty, `format` / `enum` optional
3060
- * booleans, `candidates` an optional array), infers a schema via `@orkestrel/contract`'s
3061
- * `samplesToSchema`, wraps a non-object root as `{ value: <schema> }` via `schemaToObject` (mirrors
3062
- * the tool-parameters convention every other `create*Tool` factory advertises), and RETURNS the
3063
- * resulting parameters record. An empty `samples` array fails `inferToolShape`'s `min: 1` bound —
3064
- * `contract.parse` returns `undefined` and the handler throws a typed `TOOL`
3065
- * {@link import('./errors.js').AgentToolError}.
3066
- *
3067
- * When `candidates` is ABSENT, the return is the bare parameters record — unchanged from before
3068
- * this array existed. When `candidates` is PRESENT (any array, including empty), the handler
3069
- * compiles a SEPARATE per-call contract from the RAW inferred schema (via `@orkestrel/contract`'s
3070
- * `schemaToShape`, NOT the `schemaToObject`-wrapped parameters — a bare-value sample checks a
3071
- * bare-value candidate) and returns `{ parameters, checks }`, one check per candidate at the same
3072
- * index. Every entry has a UNIFORM shape — `{ index, valid, coercible }`, with `faults` added ONLY
3073
- * when `valid` is `false`: `valid` is the STRICT guard verdict (`checker.is(candidate)`), the
3074
- * OPPOSITE of {@link createEndpointTool}'s enforcement, which coerces (`7` becomes `'7'` for a
3075
- * string slot) — here a conformance report answers "does this value conform AS-IS": `7` against a
3076
- * string slot is `valid: false`, full stop. `coercible` answers a SEPARATE question — "would the
3077
- * NORMALIZING parse accept this value", i.e. would {@link createEndpointTool}'s default enforcement
3078
- * admit it (`checker.parse(candidate) !== undefined`) — computed for every candidate regardless of
3079
- * `valid`; by the house parse/guard round-trip guarantee (AGENTS §14), a `valid: true` entry is
3080
- * ALWAYS also `coercible: true`. `@orkestrel/contract` 0.0.7's `explain` mirrors the normalizing
3081
- * `parse`'s leniency, not `is`'s strictness — so a strictly-invalid but coercible candidate (`7`
3082
- * against a string slot) yields `{ valid: false, coercible: true, faults: [] }`: EMPTY faults, since
3083
- * the mismatch the normalizing parse would silently fix is not one `explain` reports. `faults`
3084
- * therefore only ever populates for a NON-coercible mismatch — a wrong type the parse can't coerce
3085
- * (a boolean in a string slot), a missing required key, or an out-of-enum value — where
3086
- * `coercible: false`. `checker.is` / `.parse` / `.explain` are all total over JSON-safe input — a
3087
- * JSON-safe hostile candidate (a `__proto__`-carrying object, deeply nested data) reaches all three
3088
- * and yields a bounded, non-throwing per-candidate verdict; a NON-JSON-safe candidate (e.g. a
3089
- * throwing-getter `Proxy`) never reaches the checker at all — it fails the OUTER `args` parse
3090
- * against {@link import('./shapers.js').inferToolShape} and rejects the WHOLE call with the same
3091
- * `TOOL` {@link import('./errors.js').AgentToolError} a malformed `samples`/`format`/`enum` throws,
3092
- * with no per-candidate verdict produced.
160
+ * Create an executable tool.
3093
161
  *
3094
- * @param options - Advertised `name` / `description` overrides (see
3095
- * {@link import('./types.js').InferToolOptions})
3096
- * @returns A `ToolInterface` (named {@link import('./constants.js').INFER_TOOL_NAME} by default)
162
+ * @param options - The advertised definition and execution handler
163
+ * @returns A tool bound to the supplied handler
3097
164
  *
3098
165
  * @example
3099
166
  * ```ts
3100
- * import { createInferTool } from '@src/core'
3101
- * import { createToolManager } from '@orkestrel/agent'
3102
- *
3103
- * const tool = createInferTool()
3104
- * const tools = createToolManager()
3105
- * tools.add(tool)
167
+ * import { createTool } from '@orkestrel/tool'
3106
168
  *
3107
- * const result = await tools.execute({
3108
- * id: 'call-1',
3109
- * name: 'infer',
3110
- * arguments: { samples: [{ id: 1, name: 'Ada' }, { id: 2, name: 'Bob' }] },
3111
- * })
3112
- * // result.value -> { type: 'object', properties: { id: {...}, name: {...} }, ... }
3113
- *
3114
- * // with candidates, the result is wrapped with per-candidate verdicts
3115
- * const checked = await tools.execute({
3116
- * id: 'call-2',
3117
- * name: 'infer',
3118
- * arguments: {
3119
- * samples: [{ id: 1, name: 'Ada' }],
3120
- * candidates: [{ id: 2, name: 'Bob' }, { id: 'x', name: 'Cy' }],
3121
- * },
169
+ * const add = createTool({
170
+ * name: 'add',
171
+ * description: 'Add two numbers',
172
+ * execute: (args) => Number(args.a) + Number(args.b),
3122
173
  * })
3123
- * // checked.value -> { parameters: {...}, checks: [
3124
- * // { index: 0, valid: true, coercible: true },
3125
- * // { index: 1, valid: false, coercible: false, faults: [...] },
3126
- * // ] }
3127
174
  * ```
3128
175
  */
3129
- function createInferTool(options) {
3130
- const contract = (0, _orkestrel_contract.createContract)(inferToolShape);
3131
- const parameters = (0, _orkestrel_contract.schemaToParameters)(contract.schema);
3132
- return (0, _orkestrel_agent.createTool)({
3133
- name: options?.name ?? "infer",
3134
- description: options?.description ?? INFER_TOOL_DESCRIPTION,
3135
- summary: INFER_TOOL_SUMMARY,
3136
- ...parameters === void 0 ? {} : { parameters },
3137
- async execute(args) {
3138
- const parsed = contract.parse(args);
3139
- if (parsed === void 0) throw new AgentToolError("TOOL", "malformed infer arguments", { args });
3140
- const schema = (0, _orkestrel_contract.samplesToSchema)(parsed.samples, {
3141
- format: parsed.format ?? false,
3142
- enum: parsed.enum ?? false
3143
- });
3144
- const result = (0, _orkestrel_contract.schemaToParameters)((0, _orkestrel_contract.schemaToObject)(schema));
3145
- if (result === void 0) throw new AgentToolError("TOOL", "could not infer a schema", { args });
3146
- if (parsed.candidates === void 0) return result;
3147
- const checker = (0, _orkestrel_contract.createContract)((0, _orkestrel_contract.schemaToShape)(schema));
3148
- return {
3149
- parameters: result,
3150
- checks: parsed.candidates.map((candidate, index) => {
3151
- const valid = checker.is(candidate);
3152
- const coercible = checker.parse(candidate) !== void 0;
3153
- return valid ? {
3154
- index,
3155
- valid,
3156
- coercible
3157
- } : {
3158
- index,
3159
- valid,
3160
- coercible,
3161
- faults: checker.explain(candidate)
3162
- };
3163
- })
3164
- };
3165
- }
3166
- });
176
+ function createTool(options) {
177
+ return new Tool(options);
3167
178
  }
3168
179
  /**
3169
- * Wrap one CONCRETE endpoint ({@link import('./types.js').EndpointDefinition}) as an LLM-callable
3170
- * `ToolInterface` — the endpoint half of the "existing API/DB → MCP tool" bridge (the other half,
3171
- * {@link createInferTool}, is a standalone inference utility).
180
+ * Create an empty tool registry.
3172
181
  *
3173
- * @remarks
3174
- * `parameters` is inferred ONCE at construction from `definition.samples` via
3175
- * `@orkestrel/contract`'s `samplesToSchema` (tuned by {@link import('./types.js').EndpointToolOptions}'s
3176
- * `format` / `enum`), wrapping a non-object root as `{ value: <schema> }` via `schemaToObject` —
3177
- * the SAME object-rooted schema is both the ADVERTISED `parameters` and, by default
3178
- * ({@link import('./types.js').EndpointToolOptions.validate} `true`), the ENFORCED contract:
3179
- * `@orkestrel/contract` 0.0.7's `schemaToShape` compiles it ONCE (via `createContract`) into a
3180
- * `ContractInterface` whose `.parse` runs on every call's `args` before `definition.invoke` — a
3181
- * NORMALIZING parse, not a strict type check: a scalar is COERCED to its inferred type where the
3182
- * house parsers coerce (a number to/from a numeric string, a boolean from `'1'`/`'0'`/`'true'`/
3183
- * `'false'`/`1`/`0`), so `definition.invoke` receives the COERCED value (e.g. `7` sent for a
3184
- * string slot arrives as `'7'`), not the raw call value. A call whose `args` fails to parse into
3185
- * a record — a required key missing, or a value not coercible to its slot's type — THROWS a
3186
- * typed `TOOL` {@link import('./errors.js').AgentToolError} carrying the compiled contract's
3187
- * structured `explain` faults, and `definition.invoke` is never called. `format` annotations are
3188
- * NEVER asserted, and a key outside the closed inferred schema is SILENTLY DROPPED rather than
3189
- * rejected (see {@link import('./types.js').EndpointToolOptions.validate}). With
3190
- * `validate: false`, `execute` PASSES THROUGH the model-supplied `args` to `definition.invoke`
3191
- * WITHOUT re-validation — the pre-0.0.7 behavior, preserved as an explicit opt-out. Either way,
3192
- * `invoke`'s return flows back as the tool call's plain result; a throw PROPAGATES uncaught,
3193
- * isolated by the `ToolManagerInterface` (`@orkestrel/agent`) into the canonical error envelope
3194
- * (AGENTS §14) — never caught or re-wrapped here.
3195
- *
3196
- * @param definition - The endpoint's identity, non-empty samples, and local handler (see
3197
- * {@link import('./types.js').EndpointDefinition})
3198
- * @param options - Construction-time inference tuning + the validate opt-out (see
3199
- * {@link import('./types.js').EndpointToolOptions})
3200
- * @returns A `ToolInterface` named `definition.name`
182
+ * @returns A registry that advertises definitions and executes calls with per-call
183
+ * error isolation
3201
184
  *
3202
185
  * @example
3203
186
  * ```ts
3204
- * import { createEndpointTool } from '@src/core'
3205
- * import { createToolManager } from '@orkestrel/agent'
187
+ * import { createTool, createToolManager } from '@orkestrel/tool'
3206
188
  *
3207
- * const tool = createEndpointTool({
3208
- * name: 'lookupUser',
3209
- * description: 'Look up a user by id.',
3210
- * samples: [{ id: '1', name: 'Ada' }, { id: '2', name: 'Bob' }],
3211
- * invoke: (args) => ({ id: args.id, name: 'Ada' }),
3212
- * })
3213
189
  * const tools = createToolManager()
3214
- * tools.add(tool)
3215
- *
3216
- * // conforming args (all required keys present) parse and reach `invoke`
190
+ * tools.add(createTool({ name: 'echo', execute: (args) => args.value }))
3217
191
  * const result = await tools.execute({
3218
- * id: 'call-1',
3219
- * name: 'lookupUser',
3220
- * arguments: { id: '1', name: 'Ada' },
192
+ * id: '1',
193
+ * name: 'echo',
194
+ * arguments: { value: 'hello' },
3221
195
  * })
3222
- * // result.value -> { id: '1', name: 'Ada' }
3223
- *
3224
- * // a nonconforming call (id is not coercible to the required string) is rejected before
3225
- * // `invoke` runs
3226
- * const rejected = await tools.execute({
3227
- * id: 'call-2',
3228
- * name: 'lookupUser',
3229
- * arguments: { id: true, name: 'Ada' },
3230
- * })
3231
- * // rejected.error -> the TOOL AgentToolError message
3232
196
  * ```
3233
197
  */
3234
- function createEndpointTool(definition, options) {
3235
- if (definition.samples.length === 0) throw new AgentToolError("TOOL", "endpoint requires at least one sample", { name: definition.name });
3236
- const objectSchema = (0, _orkestrel_contract.schemaToObject)((0, _orkestrel_contract.samplesToSchema)(definition.samples, {
3237
- format: options?.format ?? false,
3238
- enum: options?.enum ?? false
3239
- }));
3240
- const parameters = (0, _orkestrel_contract.schemaToParameters)(objectSchema);
3241
- if (!(options?.validate ?? true)) return (0, _orkestrel_agent.createTool)({
3242
- name: definition.name,
3243
- description: definition.description,
3244
- ...parameters === void 0 ? {} : { parameters },
3245
- execute(args) {
3246
- return definition.invoke(args);
3247
- }
3248
- });
3249
- const contract = (0, _orkestrel_contract.createContract)((0, _orkestrel_contract.schemaToShape)(objectSchema));
3250
- return (0, _orkestrel_agent.createTool)({
3251
- name: definition.name,
3252
- description: definition.description,
3253
- ...parameters === void 0 ? {} : { parameters },
3254
- execute(args) {
3255
- const parsed = contract.parse(args);
3256
- if (parsed === void 0 || !(0, _orkestrel_contract.isRecord)(parsed)) throw new AgentToolError("TOOL", "malformed endpoint call arguments", {
3257
- name: definition.name,
3258
- faults: contract.explain(args)
3259
- });
3260
- return definition.invoke(parsed);
3261
- }
3262
- });
198
+ function createToolManager() {
199
+ return new ToolManager();
3263
200
  }
3264
201
  //#endregion
3265
- exports.AGENT_TOOL_DEPTH = AGENT_TOOL_DEPTH;
3266
- exports.AGENT_TOOL_DESCRIPTION = AGENT_TOOL_DESCRIPTION;
3267
- exports.AGENT_TOOL_NAME = AGENT_TOOL_NAME;
3268
- exports.AGENT_TOOL_SUMMARY = AGENT_TOOL_SUMMARY;
3269
- exports.ANSWER_TOOL_DESCRIPTION = ANSWER_TOOL_DESCRIPTION;
3270
- exports.ANSWER_TOOL_NAME = ANSWER_TOOL_NAME;
3271
- exports.ANSWER_TOOL_SUMMARY = ANSWER_TOOL_SUMMARY;
3272
- exports.AgentToolError = AgentToolError;
3273
- exports.DATABASE_TOOL_DESCRIPTION = DATABASE_TOOL_DESCRIPTION;
3274
- exports.DATABASE_TOOL_LIMIT = DATABASE_TOOL_LIMIT;
3275
- exports.DATABASE_TOOL_MUTATIONS = DATABASE_TOOL_MUTATIONS;
3276
- exports.DATABASE_TOOL_NAME = DATABASE_TOOL_NAME;
3277
- exports.DATABASE_TOOL_SUMMARY = DATABASE_TOOL_SUMMARY;
3278
- exports.DESCRIBE_TOOL_DESCRIPTION = DESCRIBE_TOOL_DESCRIPTION;
3279
- exports.DESCRIBE_TOOL_NAME = DESCRIBE_TOOL_NAME;
3280
- exports.DESCRIBE_TOOL_SUMMARY = DESCRIBE_TOOL_SUMMARY;
3281
- exports.DatabaseDefinitionStore = DatabaseDefinitionStore;
3282
- exports.DatabaseResolver = DatabaseResolver;
3283
- exports.INFER_TOOL_DESCRIPTION = INFER_TOOL_DESCRIPTION;
3284
- exports.INFER_TOOL_NAME = INFER_TOOL_NAME;
3285
- exports.INFER_TOOL_SUMMARY = INFER_TOOL_SUMMARY;
3286
- exports.MAX_WORKFLOW_DEPTH = MAX_WORKFLOW_DEPTH;
3287
- exports.MemoryDefinitionStore = MemoryDefinitionStore;
3288
- exports.PROMPT_TOOL_DESCRIPTION = PROMPT_TOOL_DESCRIPTION;
3289
- exports.PROMPT_TOOL_NAME = PROMPT_TOOL_NAME;
3290
- exports.PROMPT_TOOL_SUMMARY = PROMPT_TOOL_SUMMARY;
3291
- exports.RELATION_TOOL_DEPTH = RELATION_TOOL_DEPTH;
3292
- exports.RELATION_TOOL_DESCRIPTION = RELATION_TOOL_DESCRIPTION;
3293
- exports.RELATION_TOOL_LIMIT = RELATION_TOOL_LIMIT;
3294
- exports.RELATION_TOOL_NAME = RELATION_TOOL_NAME;
3295
- exports.RELATION_TOOL_SUMMARY = RELATION_TOOL_SUMMARY;
3296
- exports.WORKFLOW_TOOL_DESCRIPTION = WORKFLOW_TOOL_DESCRIPTION;
3297
- exports.WORKFLOW_TOOL_FLAT_EXAMPLE = WORKFLOW_TOOL_FLAT_EXAMPLE;
3298
- exports.WORKFLOW_TOOL_NAME = WORKFLOW_TOOL_NAME;
3299
- exports.WORKFLOW_TOOL_NESTED_EXAMPLE = WORKFLOW_TOOL_NESTED_EXAMPLE;
3300
- exports.WORKFLOW_TOOL_SUMMARY = WORKFLOW_TOOL_SUMMARY;
3301
- exports.WORKSPACE_TOOL_DESCRIPTION = WORKSPACE_TOOL_DESCRIPTION;
3302
- exports.WORKSPACE_TOOL_EXAMPLE = WORKSPACE_TOOL_EXAMPLE;
3303
- exports.WORKSPACE_TOOL_NAME = WORKSPACE_TOOL_NAME;
3304
- exports.WORKSPACE_TOOL_SUMMARY = WORKSPACE_TOOL_SUMMARY;
3305
- exports.agentTag = agentTag;
3306
- exports.agentToolShape = agentToolShape;
3307
- exports.answerToolShape = answerToolShape;
3308
- exports.clampCriteria = clampCriteria;
3309
- exports.coerceAnswer = coerceAnswer;
3310
- exports.columnKindShape = columnKindShape;
3311
- exports.columnSchema = columnSchema;
3312
- exports.columnShape = columnShape;
3313
- exports.columnSpecShape = columnSpecShape;
3314
- exports.completeDraft = completeDraft;
3315
- exports.completePhaseDraft = completePhaseDraft;
3316
- exports.completeTaskDraft = completeTaskDraft;
3317
- exports.conditionShape = conditionShape;
3318
- exports.createAgentFunction = createAgentFunction;
3319
- exports.createAgentTool = createAgentTool;
3320
- exports.createAnswerTool = createAnswerTool;
3321
- exports.createDatabaseDefinitionStore = createDatabaseDefinitionStore;
3322
- exports.createDatabaseTool = createDatabaseTool;
3323
- exports.createDescribeTool = createDescribeTool;
3324
- exports.createEndpointTool = createEndpointTool;
3325
- exports.createInferTool = createInferTool;
3326
- exports.createMemoryDefinitionStore = createMemoryDefinitionStore;
3327
- exports.createPromptTool = createPromptTool;
3328
- exports.createRelationTool = createRelationTool;
3329
- exports.createToolFunction = createToolFunction;
3330
- exports.createWorkflowDraftContract = createWorkflowDraftContract;
3331
- exports.createWorkflowTool = createWorkflowTool;
3332
- exports.createWorkspaceTool = createWorkspaceTool;
3333
- exports.criteriaOf = criteriaOf;
3334
- exports.criteriaShape = criteriaShape;
3335
- exports.databaseToolCode = databaseToolCode;
3336
- exports.databaseToolShape = databaseToolShape;
3337
- exports.describeToolShape = describeToolShape;
3338
- exports.expandInclude = expandInclude;
3339
- exports.expandSteps = expandSteps;
3340
- exports.expandTables = expandTables;
3341
- exports.includeShape = includeShape;
3342
- exports.inferToolShape = inferToolShape;
3343
- exports.isAgentToolError = isAgentToolError;
3344
- exports.isColumnKind = isColumnKind;
3345
- exports.isColumnSpec = isColumnSpec;
3346
- exports.isDatabaseDefinition = isDatabaseDefinition;
3347
- exports.keyShape = keyShape;
3348
- exports.kindShape = kindShape;
3349
- exports.managerShape = managerShape;
3350
- exports.orderShape = orderShape;
3351
- exports.phaseDraftShape = phaseDraftShape;
3352
- exports.promptToolShape = promptToolShape;
3353
- exports.relationKeyShape = relationKeyShape;
3354
- exports.relationManagerOf = relationManagerOf;
3355
- exports.relationModelOf = relationModelOf;
3356
- exports.relationToolCode = relationToolCode;
3357
- exports.relationToolShape = relationToolShape;
3358
- exports.rowShape = rowShape;
3359
- exports.rowsShape = rowsShape;
3360
- exports.singleKeyShape = singleKeyShape;
3361
- exports.stepShape = stepShape;
3362
- exports.tableSchema = tableSchema;
3363
- exports.tableSpecShape = tableSpecShape;
3364
- exports.taskDraftShape = taskDraftShape;
3365
- exports.terminalToolCode = terminalToolCode;
3366
- exports.workflowDraftShape = workflowDraftShape;
3367
- exports.workflowStepsShape = workflowStepsShape;
3368
- exports.workflowTag = workflowTag;
3369
- exports.workflowToolSummary = workflowToolSummary;
3370
- exports.workspaceToolShape = workspaceToolShape;
202
+ exports.Tool = Tool;
203
+ exports.ToolManager = ToolManager;
204
+ exports.createTool = createTool;
205
+ exports.createToolManager = createToolManager;
206
+ exports.isToolCall = isToolCall;
3371
207
 
3372
208
  //# sourceMappingURL=index.cjs.map