@orkestrel/toolbox 0.0.11 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -7
- package/dist/src/core/index.cjs +770 -580
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +1237 -1020
- package/dist/src/core/index.d.ts +1237 -1020
- package/dist/src/core/index.js +755 -565
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +26 -28
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +30 -52
- package/dist/src/server/index.d.ts +30 -52
- package/dist/src/server/index.js +28 -29
- package/dist/src/server/index.js.map +1 -1
- package/package.json +22 -24
package/dist/src/core/index.js
CHANGED
|
@@ -9,40 +9,44 @@ import { createTool } from "@orkestrel/tool";
|
|
|
9
9
|
import { createForm, isFormError, isFormValues, parseForm } from "@orkestrel/form";
|
|
10
10
|
//#region src/core/constants.ts
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
* model calls and the `ToolManagerInterface` (`@orkestrel/tool`) registers under.
|
|
12
|
+
* Holds the name {@link import('./factories.js').createAgentTool} advertises by default, `'agent'`
|
|
13
|
+
* — the key a model calls and the `ToolManagerInterface` (`@orkestrel/tool`) registers under.
|
|
14
14
|
*/
|
|
15
15
|
var AGENT_TOOL_NAME = "agent";
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
18
|
-
* reach — the bound {@link import('./factories.js').createAgentTool}'s depth/cycle guard
|
|
17
|
+
* Holds the maximum nesting depth a delegation chain (agent tool → sub-agent → agent tool → …) may
|
|
18
|
+
* reach, `8` — the bound {@link import('./factories.js').createAgentTool}'s depth/cycle guard
|
|
19
19
|
* enforces.
|
|
20
20
|
*
|
|
21
21
|
* @remarks
|
|
22
|
-
* Deliberately a
|
|
23
|
-
* sharing one reference):
|
|
22
|
+
* Deliberately a separate constant from {@link MAX_WORKFLOW_CHAIN} (rather than the guards
|
|
23
|
+
* sharing one reference): they bound different chains (workflow nesting against agent
|
|
24
24
|
* delegation) that happen to share a value today, and keeping this bound decoupled means a
|
|
25
25
|
* future change to one never silently shifts the other. Same numeric value by convention, not
|
|
26
26
|
* by shared reference.
|
|
27
27
|
*/
|
|
28
28
|
var AGENT_TOOL_DEPTH = 8;
|
|
29
29
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* for the
|
|
34
|
-
*
|
|
30
|
+
* Holds the lean {@link import('@orkestrel/tool').ToolInterface.summary}
|
|
31
|
+
* {@link import('./factories.js').createAgentTool} advertises in place of
|
|
32
|
+
* {@link AGENT_TOOL_DESCRIPTION} — one sentence offering sub-agent delegation and pointing at
|
|
33
|
+
* `describe` for the optional overrides.
|
|
34
|
+
*
|
|
35
|
+
* @remarks
|
|
36
|
+
* A `ToolManagerInterface.definitions()` call (`@orkestrel/tool`) advertises
|
|
37
|
+
* `summary ?? description`, so this one-sentence text stands in for the full teaching description;
|
|
38
|
+
* the full text stays retrievable through {@link import('./factories.js').createDescribeTool}.
|
|
35
39
|
*/
|
|
36
40
|
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.";
|
|
37
41
|
/**
|
|
38
|
-
*
|
|
42
|
+
* Holds the description {@link import('./factories.js').createAgentTool} advertises — a short guide
|
|
39
43
|
* covering the required task and optional provider, tools, and system overrides.
|
|
40
44
|
*/
|
|
41
45
|
var AGENT_TOOL_DESCRIPTION = [
|
|
42
46
|
"Delegate a task to a sub-agent and return its result. Every call runs ONE sub-agent turn to completion.",
|
|
43
47
|
"",
|
|
44
48
|
"Required:",
|
|
45
|
-
" task - the instructions the sub-agent
|
|
49
|
+
" task - the instructions the sub-agent carries out.",
|
|
46
50
|
"Optional overrides (default to the values this tool was configured with):",
|
|
47
51
|
" provider - the registry key of the model/provider the sub-agent runs against.",
|
|
48
52
|
" tools - registry keys of the tools loaded into the sub-agent (replaces the default list, not merged).",
|
|
@@ -51,7 +55,7 @@ var AGENT_TOOL_DESCRIPTION = [
|
|
|
51
55
|
JSON.stringify({ task: "Summarize the attached notes in three bullet points." })
|
|
52
56
|
].join("\n");
|
|
53
57
|
/**
|
|
54
|
-
*
|
|
58
|
+
* Holds the maximum nesting depth a workflow → agent → workflow chain may reach, `8` — the bound
|
|
55
59
|
* {@link import('./factories.js').createAgentFunction} and
|
|
56
60
|
* {@link import('./factories.js').createWorkflowTool}'s depth/cycle guards enforce.
|
|
57
61
|
*
|
|
@@ -60,42 +64,42 @@ var AGENT_TOOL_DESCRIPTION = [
|
|
|
60
64
|
* workflows is allowed, while a ninth nested workflow is rejected with this package's typed
|
|
61
65
|
* `DEPTH` `ToolboxError`.
|
|
62
66
|
*/
|
|
63
|
-
var
|
|
67
|
+
var MAX_WORKFLOW_CHAIN = 8;
|
|
64
68
|
/**
|
|
65
|
-
*
|
|
66
|
-
* model calls and the `ToolManagerInterface` (`@orkestrel/tool`) registers
|
|
67
|
-
* {@link import('./factories.js').createAgentFunction} binds the
|
|
68
|
-
* under onto a wrapped agent's `context.tools`.
|
|
69
|
+
* Holds the name {@link import('./factories.js').createWorkflowTool} advertises by default,
|
|
70
|
+
* `'workflow'` — the key a model calls and the `ToolManagerInterface` (`@orkestrel/tool`) registers
|
|
71
|
+
* under, and the name {@link import('./factories.js').createAgentFunction} binds the
|
|
72
|
+
* depth/cycle-aware workflow tool under onto a wrapped agent's `context.tools`.
|
|
69
73
|
*
|
|
70
74
|
* @remarks
|
|
71
75
|
* The propagation seam's well-known key: when
|
|
72
76
|
* `createAgentFunction`'s `runner` option is supplied, it adds a `createWorkflowTool`-built tool
|
|
73
|
-
* under this name to the agent's `context.tools`, so it can author
|
|
74
|
-
* (bounded by {@link
|
|
77
|
+
* under this name to the agent's `context.tools`, so it can author and run a nested workflow
|
|
78
|
+
* (bounded by {@link MAX_WORKFLOW_CHAIN}).
|
|
75
79
|
*/
|
|
76
80
|
var WORKFLOW_TOOL_NAME = "workflow";
|
|
77
81
|
/**
|
|
78
|
-
*
|
|
79
|
-
* {@link import('./factories.js').createWorkflowTool}: `{ name, steps: [{ name }] }`.
|
|
82
|
+
* Holds a complete flat authoring example — the primary way a small model authors a workflow
|
|
83
|
+
* through {@link import('./factories.js').createWorkflowTool}: `{ name, steps: [{ name }] }`.
|
|
80
84
|
*
|
|
81
85
|
* @remarks
|
|
82
86
|
* Each step becomes a one-task phase, in
|
|
83
|
-
* order; a step's `name` is a
|
|
84
|
-
* task's `
|
|
87
|
+
* order; a step's `name` is a registered behavior name (not a label) — the registry key its
|
|
88
|
+
* task's `behavior` resolves against. The tool expands this
|
|
85
89
|
* ({@link import('./helpers.js').expandSteps}) into a valid `WorkflowDefinition`
|
|
86
|
-
* (`@orkestrel/workflow`). It is embedded
|
|
90
|
+
* (`@orkestrel/workflow`). It is embedded verbatim in {@link WORKFLOW_TOOL_DESCRIPTION}.
|
|
87
91
|
*/
|
|
88
92
|
var WORKFLOW_TOOL_FLAT_EXAMPLE = Object.freeze({
|
|
89
93
|
name: "release",
|
|
90
94
|
steps: Object.freeze([Object.freeze({ name: "compile" }), Object.freeze({ name: "publish" })])
|
|
91
95
|
});
|
|
92
96
|
/**
|
|
93
|
-
*
|
|
94
|
-
* the flat shape: a full `WorkflowDefinition` (`@orkestrel/workflow`).
|
|
97
|
+
* Holds a minimal nested authoring example — the advanced escape-hatch form a model can use instead
|
|
98
|
+
* of the flat shape: a full `WorkflowDefinition` (`@orkestrel/workflow`).
|
|
95
99
|
*
|
|
96
100
|
* @remarks
|
|
97
|
-
* The full
|
|
98
|
-
* {@link WORKFLOW_TOOL_DESCRIPTION} as the advanced alternative. It is embedded
|
|
101
|
+
* The full nested form, documented in
|
|
102
|
+
* {@link WORKFLOW_TOOL_DESCRIPTION} as the advanced alternative. It is embedded verbatim.
|
|
99
103
|
*/
|
|
100
104
|
var WORKFLOW_TOOL_NESTED_EXAMPLE = Object.freeze({
|
|
101
105
|
id: "release",
|
|
@@ -106,20 +110,24 @@ var WORKFLOW_TOOL_NESTED_EXAMPLE = Object.freeze({
|
|
|
106
110
|
tasks: Object.freeze([Object.freeze({
|
|
107
111
|
id: "compile",
|
|
108
112
|
name: "Compile",
|
|
109
|
-
|
|
113
|
+
behavior: "compile"
|
|
110
114
|
})])
|
|
111
115
|
})])
|
|
112
116
|
});
|
|
113
117
|
/**
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
* for the
|
|
118
|
-
*
|
|
118
|
+
* Holds the lean {@link import('@orkestrel/tool').ToolInterface.summary}
|
|
119
|
+
* {@link import('./factories.js').createWorkflowTool} advertises in place of
|
|
120
|
+
* {@link WORKFLOW_TOOL_DESCRIPTION} — one sentence offering multi-phase authoring and pointing at
|
|
121
|
+
* `describe` for the authoring schema and its examples.
|
|
122
|
+
*
|
|
123
|
+
* @remarks
|
|
124
|
+
* A `ToolManagerInterface.definitions()` call (`@orkestrel/tool`) advertises
|
|
125
|
+
* `summary ?? description`, so this one-sentence text stands in for the full teaching description;
|
|
126
|
+
* the full text stays retrievable through {@link import('./factories.js').createDescribeTool}.
|
|
119
127
|
*/
|
|
120
128
|
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.";
|
|
121
129
|
/**
|
|
122
|
-
*
|
|
130
|
+
* Holds the description {@link import('./factories.js').createWorkflowTool} advertises — the flat
|
|
123
131
|
* authoring form, its worked example, and the advanced nested definition form.
|
|
124
132
|
*/
|
|
125
133
|
var WORKFLOW_TOOL_DESCRIPTION = [
|
|
@@ -133,18 +141,19 @@ var WORKFLOW_TOOL_DESCRIPTION = [
|
|
|
133
141
|
"Example:",
|
|
134
142
|
JSON.stringify(WORKFLOW_TOOL_FLAT_EXAMPLE),
|
|
135
143
|
"",
|
|
136
|
-
"ADVANCED — the full nested form, for multi-task phases or explicit ids. A workflow has phases; a phase has tasks; a task may have a \"
|
|
144
|
+
"ADVANCED — the full nested form, for multi-task phases or explicit ids. A workflow has phases; a phase has tasks; a task may have a \"behavior\" (a registered behavior name); omitting it creates a JSON-null no-op:",
|
|
137
145
|
JSON.stringify(WORKFLOW_TOOL_NESTED_EXAMPLE),
|
|
138
146
|
"In the nested form you may omit any \"id\"/\"name\" and they are filled in positionally; a provided one is kept."
|
|
139
147
|
].join("\n");
|
|
140
148
|
/**
|
|
141
|
-
*
|
|
142
|
-
* model calls and the `ToolManagerInterface` (`@orkestrel/tool`)
|
|
149
|
+
* Holds the name {@link import('./factories.js').createWorkspaceTool} advertises by default,
|
|
150
|
+
* `'workspace'` — the key a model calls and the `ToolManagerInterface` (`@orkestrel/tool`)
|
|
151
|
+
* registers under.
|
|
143
152
|
*/
|
|
144
153
|
var WORKSPACE_TOOL_NAME = "workspace";
|
|
145
154
|
/**
|
|
146
|
-
*
|
|
147
|
-
*
|
|
155
|
+
* Holds a valid {@link import('./types.js').WorkspaceOperation} object — the canonical example
|
|
156
|
+
* embedded verbatim in {@link WORKSPACE_TOOL_DESCRIPTION}.
|
|
148
157
|
*
|
|
149
158
|
* @remarks
|
|
150
159
|
* A `'write'` op (the most common authoring
|
|
@@ -157,15 +166,19 @@ var WORKSPACE_TOOL_EXAMPLE = Object.freeze({
|
|
|
157
166
|
content: "hello"
|
|
158
167
|
});
|
|
159
168
|
/**
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
169
|
+
* Holds the lean {@link import('@orkestrel/tool').ToolInterface.summary}
|
|
170
|
+
* {@link import('./factories.js').createWorkspaceTool} advertises in place of
|
|
171
|
+
* {@link WORKSPACE_TOOL_DESCRIPTION} — one sentence offering the `operation`-keyed file editing and
|
|
172
|
+
* pointing at `describe` for the operation list and its fields.
|
|
173
|
+
*
|
|
174
|
+
* @remarks
|
|
175
|
+
* A `ToolManagerInterface.definitions()` call (`@orkestrel/tool`) advertises
|
|
176
|
+
* `summary ?? description`, so this one-sentence text stands in for the full teaching description;
|
|
177
|
+
* the full text stays retrievable through {@link import('./factories.js').createDescribeTool}.
|
|
165
178
|
*/
|
|
166
179
|
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.";
|
|
167
180
|
/**
|
|
168
|
-
*
|
|
181
|
+
* Holds the description {@link import('./factories.js').createWorkspaceTool} advertises — the
|
|
169
182
|
* operation-keyed workspace protocol, all supported operations, and worked examples.
|
|
170
183
|
*
|
|
171
184
|
* @remarks
|
|
@@ -197,24 +210,26 @@ var WORKSPACE_TOOL_DESCRIPTION = [
|
|
|
197
210
|
JSON.stringify(WORKSPACE_TOOL_EXAMPLE)
|
|
198
211
|
].join("\n");
|
|
199
212
|
/**
|
|
200
|
-
*
|
|
201
|
-
* model calls and the `ToolManagerInterface` (`@orkestrel/tool`) registers
|
|
213
|
+
* Holds the name {@link import('./factories.js').createDescribeTool} advertises by default,
|
|
214
|
+
* `'describe'` — the key a model calls and the `ToolManagerInterface` (`@orkestrel/tool`) registers
|
|
215
|
+
* under.
|
|
202
216
|
*
|
|
203
217
|
* @remarks
|
|
204
|
-
*
|
|
205
|
-
* {@link
|
|
218
|
+
* Pairs with the lean {@link AGENT_TOOL_SUMMARY} / {@link WORKFLOW_TOOL_SUMMARY} /
|
|
219
|
+
* {@link WORKSPACE_TOOL_SUMMARY} — a model that reads only the
|
|
206
220
|
* advertised summary can call `describe` with that tool's registered name to get its full
|
|
207
221
|
* teaching description back.
|
|
208
222
|
*/
|
|
209
223
|
var DESCRIBE_TOOL_NAME = "describe";
|
|
210
224
|
/**
|
|
211
|
-
*
|
|
225
|
+
* Holds the lean {@link import('@orkestrel/tool').ToolInterface.summary} {@link import('./factories.js').createDescribeTool}
|
|
212
226
|
* advertises — this tool needs no teaching of its own, so its summary and description are both
|
|
213
227
|
* short.
|
|
214
228
|
*/
|
|
215
229
|
var DESCRIBE_TOOL_SUMMARY = "Return the full description of a named registered tool.";
|
|
216
230
|
/**
|
|
217
|
-
*
|
|
231
|
+
* Holds the description {@link import('./factories.js').createDescribeTool} advertises — the
|
|
232
|
+
* registered tool `name` it requires, and the full description of that tool it returns.
|
|
218
233
|
*
|
|
219
234
|
* @remarks
|
|
220
235
|
* Deliberately short — unlike the workflow / workspace / agent tools, this one has no authoring
|
|
@@ -222,19 +237,27 @@ var DESCRIBE_TOOL_SUMMARY = "Return the full description of a named registered t
|
|
|
222
237
|
*/
|
|
223
238
|
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).";
|
|
224
239
|
/**
|
|
225
|
-
*
|
|
226
|
-
* model calls and the `ToolManagerInterface` (`@orkestrel/tool`) registers under.
|
|
240
|
+
* Holds the name {@link import('./factories.js').createPromptTool} advertises by default, `'ask'` —
|
|
241
|
+
* the key a model calls and the `ToolManagerInterface` (`@orkestrel/tool`) registers under.
|
|
227
242
|
*/
|
|
228
243
|
var PROMPT_TOOL_NAME = "ask";
|
|
229
244
|
/**
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
245
|
+
* Holds the lean {@link import('@orkestrel/tool').ToolInterface.summary}
|
|
246
|
+
* {@link import('./factories.js').createPromptTool} advertises in place of
|
|
247
|
+
* {@link PROMPT_TOOL_DESCRIPTION} — one sentence offering the blocking multi-field ask and pointing
|
|
248
|
+
* at `describe` for the schema.
|
|
249
|
+
*
|
|
250
|
+
* @remarks
|
|
251
|
+
* A `ToolManagerInterface.definitions()` call (`@orkestrel/tool`) advertises
|
|
252
|
+
* `summary ?? description`, so this one-sentence text stands in for the full teaching description;
|
|
253
|
+
* the full text stays retrievable through {@link import('./factories.js').createDescribeTool}.
|
|
235
254
|
*/
|
|
236
255
|
var PROMPT_TOOL_SUMMARY = "Ask another terminal a multi-field form and BLOCK until it answers; the call resolves with the values record. Call describe('ask') for the schema.";
|
|
237
|
-
/**
|
|
256
|
+
/**
|
|
257
|
+
* Holds the form protocol {@link import('./factories.js').createPromptTool} advertises — the
|
|
258
|
+
* required `to` and `schema`, how a field declares its control and its rules, and a worked
|
|
259
|
+
* example.
|
|
260
|
+
*/
|
|
238
261
|
var PROMPT_TOOL_DESCRIPTION = [
|
|
239
262
|
"Ask another terminal a multi-field form and block until it answers. This call does not return until the addressed terminal answers, or the form expires.",
|
|
240
263
|
"",
|
|
@@ -260,24 +283,32 @@ var PROMPT_TOOL_DESCRIPTION = [
|
|
|
260
283
|
})
|
|
261
284
|
].join("\n");
|
|
262
285
|
/**
|
|
263
|
-
*
|
|
264
|
-
* model calls and the `ToolManagerInterface` (`@orkestrel/tool`) registers
|
|
286
|
+
* Holds the name {@link import('./factories.js').createAnswerTool} advertises by default,
|
|
287
|
+
* `'answer'` — the key a model calls and the `ToolManagerInterface` (`@orkestrel/tool`) registers
|
|
288
|
+
* under.
|
|
265
289
|
*/
|
|
266
290
|
var ANSWER_TOOL_NAME = "answer";
|
|
267
291
|
/**
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
292
|
+
* Holds the lean {@link import('@orkestrel/tool').ToolInterface.summary}
|
|
293
|
+
* {@link import('./factories.js').createAnswerTool} advertises in place of
|
|
294
|
+
* {@link ANSWER_TOOL_DESCRIPTION} — one sentence offering the pending listing and the answer call,
|
|
295
|
+
* and pointing at `describe` for the fields each takes.
|
|
296
|
+
*
|
|
297
|
+
* @remarks
|
|
298
|
+
* A `ToolManagerInterface.definitions()` call (`@orkestrel/tool`) advertises
|
|
299
|
+
* `summary ?? description`, so this one-sentence text stands in for the full teaching description;
|
|
300
|
+
* the full text stays retrievable through {@link import('./factories.js').createDescribeTool}.
|
|
273
301
|
*/
|
|
274
302
|
var ANSWER_TOOL_SUMMARY = "List forms addressed to this terminal, or answer one by id with a values record. Call describe('answer') for the required fields.";
|
|
275
|
-
/**
|
|
303
|
+
/**
|
|
304
|
+
* Holds the protocol {@link import('./factories.js').createAnswerTool} advertises — the `pending`
|
|
305
|
+
* and `answer` operations and the `values` record an answer supplies, each with a worked example.
|
|
306
|
+
*/
|
|
276
307
|
var ANSWER_TOOL_DESCRIPTION = [
|
|
277
|
-
"List the forms
|
|
308
|
+
"List the forms addressed to this terminal, or answer one of them by id. Every call is ONE operation, chosen by the \"operation\" field.",
|
|
278
309
|
"",
|
|
279
310
|
"Operations:",
|
|
280
|
-
"- pending { \"operation\": \"pending\" } — list every form
|
|
311
|
+
"- pending { \"operation\": \"pending\" } — list every form addressed to this terminal (id, from, schema).",
|
|
281
312
|
"- answer { \"operation\": \"answer\", \"id\": \"<form id>\", \"values\": { \"<field name>\": <field value> } } — answer every required field with the value its control accepts.",
|
|
282
313
|
"Example — list pending forms:",
|
|
283
314
|
JSON.stringify({ operation: "pending" }),
|
|
@@ -292,28 +323,31 @@ var ANSWER_TOOL_DESCRIPTION = [
|
|
|
292
323
|
})
|
|
293
324
|
].join("\n");
|
|
294
325
|
/**
|
|
295
|
-
*
|
|
296
|
-
* calls and the `ToolManagerInterface` (`@orkestrel/tool`) registers
|
|
297
|
-
*
|
|
298
|
-
* @remarks
|
|
299
|
-
* SRC-1 of a 3-unit spine: this unit lands the persistence + schema foundation
|
|
300
|
-
* ({@link import('./types.js').DatabaseDefinition}, {@link import('./types.js').DefinitionStoreInterface},
|
|
301
|
-
* {@link import('./helpers.js').expandTables}); `createDatabaseTool` itself is built in a later unit.
|
|
326
|
+
* Holds the name {@link import('./factories.js').createDatabaseTool} advertises by default,
|
|
327
|
+
* `'database'` — the key a model calls and the `ToolManagerInterface` (`@orkestrel/tool`) registers
|
|
328
|
+
* under.
|
|
302
329
|
*/
|
|
303
330
|
var DATABASE_TOOL_NAME = "database";
|
|
304
331
|
/**
|
|
305
|
-
*
|
|
306
|
-
*
|
|
332
|
+
* Holds the lean {@link import('@orkestrel/tool').ToolInterface.summary}
|
|
333
|
+
* {@link import('./factories.js').createDatabaseTool} advertises in place of
|
|
334
|
+
* {@link DATABASE_TOOL_DESCRIPTION} — one sentence offering the `operation`-keyed database call and
|
|
335
|
+
* pointing at `describe` for the operation list, the query form, and the column DSL.
|
|
336
|
+
*
|
|
337
|
+
* @remarks
|
|
338
|
+
* A `ToolManagerInterface.definitions()` call (`@orkestrel/tool`) advertises
|
|
339
|
+
* `summary ?? description`, so this one-sentence text stands in for the full teaching description;
|
|
340
|
+
* the full text stays retrievable through {@link import('./factories.js').createDescribeTool}.
|
|
307
341
|
*/
|
|
308
342
|
var DATABASE_TOOL_SUMMARY = "Create and query a database — one operation per call (create, tables, get, records, count, aggregate, add, set, update, remove, destroy), chosen by the 'operation' field. Call describe('database') for the full operation list, the query form, and the column DSL.";
|
|
309
343
|
/**
|
|
310
|
-
*
|
|
311
|
-
* small model the operation list, the
|
|
312
|
-
* column DSL.
|
|
344
|
+
* Holds the description {@link import('./factories.js').createDatabaseTool} advertises — a
|
|
345
|
+
* multi-line guide that teaches a small model the operation list, the serialized query form, and
|
|
346
|
+
* the {@link import('./types.js').TableSpec} column DSL.
|
|
313
347
|
*
|
|
314
348
|
* @remarks
|
|
315
|
-
* The query form is deliberately
|
|
316
|
-
* `{ column, operator, values, connector? }` where `values` is
|
|
349
|
+
* The query form is deliberately serialized (never fluent) — every condition is a flat object
|
|
350
|
+
* `{ column, operator, values, connector? }` where `values` is always an array, even for a
|
|
317
351
|
* single-value operator (`{ column: 'age', operator: 'from', values: [18] }`), so a small model
|
|
318
352
|
* never has to chain method calls or guess whether a value is scalar or a list.
|
|
319
353
|
*/
|
|
@@ -321,7 +355,7 @@ var DATABASE_TOOL_DESCRIPTION = [
|
|
|
321
355
|
"Create and query a database. Every call is ONE operation, chosen by the \"operation\" field.",
|
|
322
356
|
"",
|
|
323
357
|
"Operations (each takes the fields listed):",
|
|
324
|
-
"- create { \"operation\": \"create\", \"id\": \"<database id>\", \"tables\": { \"<table>\": { \"columns\": { \"<column>\": \"string\" | \"integer\" | \"number\" | \"boolean\" | { \"
|
|
358
|
+
"- create { \"operation\": \"create\", \"id\": \"<database id>\", \"tables\": { \"<table>\": { \"columns\": { \"<column>\": \"string\" | \"integer\" | \"number\" | \"boolean\" | { \"primitive\": \"string\", \"optional\": true } } } }, \"primary\"?: { \"<table>\": \"<column>\" }, \"indexes\"?: { \"<table>\": [[\"<column>\"]] }, \"version\"?: <number> } — define a new database.",
|
|
325
359
|
"- tables { \"operation\": \"tables\", \"id\": \"<database id>\" } — list a database's table names.",
|
|
326
360
|
"- get { \"operation\": \"get\", \"id\": \"<database id>\", \"table\": \"<table>\", \"key\": \"<row key>\" } — fetch one row by its primary key.",
|
|
327
361
|
"- records { \"operation\": \"records\", \"id\": \"<database id>\", \"table\": \"<table>\", \"query\"?: <Query> } — list rows matching a query.",
|
|
@@ -338,7 +372,7 @@ var DATABASE_TOOL_DESCRIPTION = [
|
|
|
338
372
|
" operators: equals, not, above, below, from, to, between, like, glob, starts, ends, any, none, absent, present.",
|
|
339
373
|
" \"connector\" joins this condition to the next (\"and\" | \"or\"); omit on the last condition.",
|
|
340
374
|
"",
|
|
341
|
-
"Column DSL (used by \"create\" \"tables\"): a column is either a bare
|
|
375
|
+
"Column DSL (used by \"create\" \"tables\"): a column is either a bare primitive string (\"string\" | \"integer\" | \"number\" | \"boolean\"), or { \"primitive\": \"<primitive>\", \"optional\": true } when the column may be absent from a row.",
|
|
342
376
|
"Schema configuration is creation-time only. Evolve a schema by opening a newly configured target-version database over a versioned persistent driver; reconciliation requires paired metadata/stamp capabilities.",
|
|
343
377
|
"Example — create a database:",
|
|
344
378
|
JSON.stringify({
|
|
@@ -348,7 +382,7 @@ var DATABASE_TOOL_DESCRIPTION = [
|
|
|
348
382
|
name: "string",
|
|
349
383
|
price: "number",
|
|
350
384
|
notes: {
|
|
351
|
-
|
|
385
|
+
primitive: "string",
|
|
352
386
|
optional: true
|
|
353
387
|
}
|
|
354
388
|
} } }
|
|
@@ -365,9 +399,12 @@ var DATABASE_TOOL_DESCRIPTION = [
|
|
|
365
399
|
}] }
|
|
366
400
|
})
|
|
367
401
|
].join("\n");
|
|
368
|
-
/**
|
|
402
|
+
/**
|
|
403
|
+
* Holds the default cap on rows a `records` call returns when the caller omits `query.limit`,
|
|
404
|
+
* `1000` — the database tool's default row ceiling.
|
|
405
|
+
*/
|
|
369
406
|
var DATABASE_TOOL_LIMIT = 1e3;
|
|
370
|
-
/**
|
|
407
|
+
/** Lists the runtime-frozen database-tool mutation names disabled by `DatabaseToolOptions.readonly`. */
|
|
371
408
|
var DATABASE_TOOL_MUTATIONS = Object.freeze([
|
|
372
409
|
"create",
|
|
373
410
|
"add",
|
|
@@ -377,21 +414,30 @@ var DATABASE_TOOL_MUTATIONS = Object.freeze([
|
|
|
377
414
|
"destroy"
|
|
378
415
|
]);
|
|
379
416
|
/**
|
|
380
|
-
*
|
|
381
|
-
* `ToolManagerInterface` (`@orkestrel/tool`) registers
|
|
417
|
+
* Holds the name {@link import('./factories.js').createRelationTool} advertises by default,
|
|
418
|
+
* `'relation'` — the key a model calls and the `ToolManagerInterface` (`@orkestrel/tool`) registers
|
|
419
|
+
* under.
|
|
382
420
|
*/
|
|
383
421
|
var RELATION_TOOL_NAME = "relation";
|
|
384
422
|
/**
|
|
385
|
-
*
|
|
386
|
-
* in place of
|
|
423
|
+
* Holds the lean {@link import('@orkestrel/tool').ToolInterface.summary}
|
|
424
|
+
* {@link import('./factories.js').createRelationTool} advertises in place of
|
|
425
|
+
* {@link RELATION_TOOL_DESCRIPTION} — one sentence offering the `operation`-keyed relationship
|
|
426
|
+
* traversal and pointing at `describe` for the include-path syntax.
|
|
427
|
+
*
|
|
428
|
+
* @remarks
|
|
429
|
+
* A `ToolManagerInterface.definitions()` call (`@orkestrel/tool`) advertises
|
|
430
|
+
* `summary ?? description`, so this one-sentence text stands in for the full teaching description;
|
|
431
|
+
* the full text stays retrievable through {@link import('./factories.js').createDescribeTool}.
|
|
387
432
|
*/
|
|
388
433
|
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.";
|
|
389
434
|
/**
|
|
390
|
-
*
|
|
391
|
-
* the operation list and the flat dot-path `include`
|
|
435
|
+
* Holds the description {@link import('./factories.js').createRelationTool} advertises — a
|
|
436
|
+
* multi-line guide that teaches a small model the operation list and the flat dot-path `include`
|
|
437
|
+
* syntax.
|
|
392
438
|
*
|
|
393
439
|
* @remarks
|
|
394
|
-
* An include path is a
|
|
440
|
+
* An include path is a flat dot-separated string (`'contacts.account'`), never a nested object —
|
|
395
441
|
* the same small-model ergonomic lever the other tools in this package use for flat args.
|
|
396
442
|
*/
|
|
397
443
|
var RELATION_TOOL_DESCRIPTION = [
|
|
@@ -413,24 +459,38 @@ var RELATION_TOOL_DESCRIPTION = [
|
|
|
413
459
|
include: ["contacts.account"]
|
|
414
460
|
})
|
|
415
461
|
].join("\n");
|
|
416
|
-
/**
|
|
462
|
+
/**
|
|
463
|
+
* Holds the default cap on rows a `find` or `links` call returns when the caller omits `limit`,
|
|
464
|
+
* `1000` — the relation tool's default row ceiling.
|
|
465
|
+
*/
|
|
417
466
|
var RELATION_TOOL_LIMIT = 1e3;
|
|
418
|
-
/**
|
|
467
|
+
/**
|
|
468
|
+
* Holds the default cap on how many `include` path segments deep a `load` or `find` call may
|
|
469
|
+
* traverse, `3` — the relation tool's default include-depth ceiling.
|
|
470
|
+
*/
|
|
419
471
|
var RELATION_TOOL_DEPTH = 3;
|
|
420
472
|
/**
|
|
421
|
-
*
|
|
422
|
-
* model calls and the `ToolManagerInterface` (`@orkestrel/tool`) registers under.
|
|
473
|
+
* Holds the name {@link import('./factories.js').createInferTool} advertises by default, `'infer'`
|
|
474
|
+
* — the key a model calls and the `ToolManagerInterface` (`@orkestrel/tool`) registers under.
|
|
423
475
|
*/
|
|
424
476
|
var INFER_TOOL_NAME = "infer";
|
|
425
477
|
/**
|
|
426
|
-
*
|
|
427
|
-
*
|
|
428
|
-
*
|
|
429
|
-
*
|
|
430
|
-
*
|
|
478
|
+
* Holds the lean {@link import('@orkestrel/tool').ToolInterface.summary}
|
|
479
|
+
* {@link import('./factories.js').createInferTool} advertises in place of
|
|
480
|
+
* {@link INFER_TOOL_DESCRIPTION} — one sentence offering schema inference from example values and
|
|
481
|
+
* pointing at `describe` for the fields it takes.
|
|
482
|
+
*
|
|
483
|
+
* @remarks
|
|
484
|
+
* A `ToolManagerInterface.definitions()` call (`@orkestrel/tool`) advertises
|
|
485
|
+
* `summary ?? description`, so this one-sentence text stands in for the full teaching description;
|
|
486
|
+
* the full text stays retrievable through {@link import('./factories.js').createDescribeTool}.
|
|
431
487
|
*/
|
|
432
488
|
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.";
|
|
433
|
-
/**
|
|
489
|
+
/**
|
|
490
|
+
* Holds the schema-inference protocol {@link import('./factories.js').createInferTool} advertises —
|
|
491
|
+
* the required `samples`, the optional `format`, `enum`, and `candidates` arguments, and a worked
|
|
492
|
+
* example of the bare return and of the `candidates`-wrapped return.
|
|
493
|
+
*/
|
|
434
494
|
var INFER_TOOL_DESCRIPTION = [
|
|
435
495
|
"Infer a JSON Schema from example values, returned in the same shape a tool advertises its parameters.",
|
|
436
496
|
"",
|
|
@@ -443,7 +503,7 @@ var INFER_TOOL_DESCRIPTION = [
|
|
|
443
503
|
" is wrapped as { parameters, checks } instead of the bare parameters record, one",
|
|
444
504
|
" check per candidate (same index). Every check has the uniform shape",
|
|
445
505
|
" { index, valid, coercible, faults? }. `valid` is a STRICT verdict (no coercion)",
|
|
446
|
-
" —
|
|
506
|
+
" — for example the number 7 is NOT valid against a string slot. `coercible` answers a",
|
|
447
507
|
" separate question: would the SAME value be accepted by an endpoint tool call,",
|
|
448
508
|
" whose enforcement NORMALIZES args (7 coerces to '7')? So 7 against a string slot",
|
|
449
509
|
" yields { valid: false, coercible: true, faults: [] } — a strict mismatch that",
|
|
@@ -523,7 +583,7 @@ var INFER_TOOL_DESCRIPTION = [
|
|
|
523
583
|
//#endregion
|
|
524
584
|
//#region src/core/errors.ts
|
|
525
585
|
/**
|
|
526
|
-
*
|
|
586
|
+
* Represents a package-owned tool-call failure: malformed input or unresolved configuration (`TOOL`),
|
|
527
587
|
* delegation depth/cycle rejection (`DEPTH`), prompt failure (`DEADLOCK` / `EXPIRE` / `ANSWER`),
|
|
528
588
|
* or a translated upstream database/relation failure (`DATABASE` / `RELATION`).
|
|
529
589
|
*
|
|
@@ -555,10 +615,10 @@ var ToolboxError = class extends Error {
|
|
|
555
615
|
}
|
|
556
616
|
};
|
|
557
617
|
/**
|
|
558
|
-
*
|
|
618
|
+
* Narrows an unknown caught value to a {@link ToolboxError}.
|
|
559
619
|
*
|
|
560
620
|
* @param value - The value to test (typically a `catch` binding)
|
|
561
|
-
* @returns
|
|
621
|
+
* @returns True if `value` is an {@link ToolboxError}; false otherwise
|
|
562
622
|
*
|
|
563
623
|
* @example
|
|
564
624
|
* ```ts
|
|
@@ -575,9 +635,102 @@ function isToolboxError(value) {
|
|
|
575
635
|
return value instanceof ToolboxError;
|
|
576
636
|
}
|
|
577
637
|
//#endregion
|
|
638
|
+
//#region src/core/validators.ts
|
|
639
|
+
/**
|
|
640
|
+
* Narrows an unknown value to a valid alternating workflow lineage.
|
|
641
|
+
*
|
|
642
|
+
* @param value - The value to inspect
|
|
643
|
+
* @returns True if `value` is a unique, nonempty-tagged workflow/agent chain; false otherwise
|
|
644
|
+
*/
|
|
645
|
+
function isWorkflowLineage(value) {
|
|
646
|
+
const inspected = attempt(() => {
|
|
647
|
+
if (!Array.isArray(value)) return false;
|
|
648
|
+
const seen = /* @__PURE__ */ new Set();
|
|
649
|
+
for (const [index, tag] of value.entries()) {
|
|
650
|
+
if (!isString(tag)) return false;
|
|
651
|
+
const prefix = index % 2 === 0 ? "workflow:" : "agent:";
|
|
652
|
+
if (!tag.startsWith(prefix) || tag.length === prefix.length || seen.has(tag)) return false;
|
|
653
|
+
seen.add(tag);
|
|
654
|
+
}
|
|
655
|
+
return true;
|
|
656
|
+
});
|
|
657
|
+
return inspected.success && inspected.value;
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Narrows an unknown callable to Toolbox's frozen contextual agent adapter metadata.
|
|
661
|
+
*
|
|
662
|
+
* @param value - The value to inspect
|
|
663
|
+
* @returns True if it is a frozen {@link AgentFunction} with a frozen valid lineage; false otherwise
|
|
664
|
+
*/
|
|
665
|
+
function isAgentFunction(value) {
|
|
666
|
+
const inspected = attempt(() => {
|
|
667
|
+
if (typeof value !== "function" || !Object.isFrozen(value)) return false;
|
|
668
|
+
const category = Reflect.getOwnPropertyDescriptor(value, "category");
|
|
669
|
+
const lineage = Reflect.getOwnPropertyDescriptor(value, "lineage");
|
|
670
|
+
return category?.value === "agent" && lineage !== void 0 && "value" in lineage && Object.isFrozen(lineage.value) && isWorkflowLineage(lineage.value);
|
|
671
|
+
});
|
|
672
|
+
return inspected.success && inspected.value;
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Narrows an unknown value to a {@link ColumnSpec}.
|
|
676
|
+
*
|
|
677
|
+
* @param value - The value to inspect
|
|
678
|
+
* @returns True if `value` is a valid {@link ColumnPrimitive} shorthand or a `{ primitive, optional }` record with a valid `primitive`; false otherwise
|
|
679
|
+
*/
|
|
680
|
+
function isColumnSpec(value) {
|
|
681
|
+
if (isColumnPrimitive(value)) return true;
|
|
682
|
+
if (!isRecord(value)) return false;
|
|
683
|
+
return isColumnPrimitive(value.primitive) && (value.optional === void 0 || typeof value.optional === "boolean");
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Narrows an unknown value to a {@link ColumnPrimitive}.
|
|
687
|
+
*
|
|
688
|
+
* @param value - The value to inspect
|
|
689
|
+
* @returns True if `value` is `'string'`, `'integer'`, `'number'`, or `'boolean'`; false otherwise
|
|
690
|
+
*/
|
|
691
|
+
function isColumnPrimitive(value) {
|
|
692
|
+
return value === "string" || value === "integer" || value === "number" || value === "boolean";
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Narrows an unknown value to a {@link DatabaseDefinition} — a non-empty `id` and `driver`, a
|
|
696
|
+
* `tables` record whose every value is `{ columns: record of valid ColumnSpec }`, plus optional
|
|
697
|
+
* `primary`, `indexes`, and finite `version` schema configuration. The boundary guard a
|
|
698
|
+
* {@link import('./types.js').DefinitionStoreInterface} applies to an untrusted persisted blob
|
|
699
|
+
* before trusting it as a definition (never an `as`).
|
|
700
|
+
*
|
|
701
|
+
* @param value - The value to inspect
|
|
702
|
+
* @returns True if `value` is a complete {@link DatabaseDefinition}; false otherwise
|
|
703
|
+
*/
|
|
704
|
+
function isDatabaseDefinition(value) {
|
|
705
|
+
if (!isRecord(value)) return false;
|
|
706
|
+
if ("keys" in value) return false;
|
|
707
|
+
if (!isNonEmptyString(value.id) || !isNonEmptyString(value.driver)) return false;
|
|
708
|
+
if (!isRecord(value.tables)) return false;
|
|
709
|
+
for (const table of Object.values(value.tables)) {
|
|
710
|
+
if (!isRecord(table) || !isRecord(table.columns)) return false;
|
|
711
|
+
for (const column of Object.values(table.columns)) if (!isColumnSpec(column)) return false;
|
|
712
|
+
}
|
|
713
|
+
if (value.primary !== void 0) {
|
|
714
|
+
if (!isRecord(value.primary)) return false;
|
|
715
|
+
for (const key of Object.values(value.primary)) if (!isNonEmptyString(key)) return false;
|
|
716
|
+
}
|
|
717
|
+
if (value.indexes !== void 0) {
|
|
718
|
+
if (!isRecord(value.indexes)) return false;
|
|
719
|
+
for (const groups of Object.values(value.indexes)) {
|
|
720
|
+
if (!Array.isArray(groups)) return false;
|
|
721
|
+
for (const group of groups) {
|
|
722
|
+
if (!Array.isArray(group) || group.length === 0) return false;
|
|
723
|
+
for (const column of group) if (!isNonEmptyString(column)) return false;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
if (value.version !== void 0 && !isFiniteNumber(value.version)) return false;
|
|
728
|
+
return true;
|
|
729
|
+
}
|
|
730
|
+
//#endregion
|
|
578
731
|
//#region src/core/shapers.ts
|
|
579
732
|
/**
|
|
580
|
-
*
|
|
733
|
+
* Describes the shape of {@link import('./factories.js').createPromptTool}'s call arguments — `to` names the
|
|
581
734
|
* terminal identity and `schema` carries the complete multi-field form document.
|
|
582
735
|
*
|
|
583
736
|
* @remarks
|
|
@@ -592,7 +745,7 @@ var promptToolShape = objectShape({
|
|
|
592
745
|
schema: jsonShape({ description: "The @orkestrel/form schema to ask, with form metadata and one or more typed fields." })
|
|
593
746
|
});
|
|
594
747
|
/**
|
|
595
|
-
*
|
|
748
|
+
* Describes the shape of {@link import('./factories.js').createAnswerTool}'s call arguments — discriminated
|
|
596
749
|
* by `operation`: `'pending'` lists the forms addressed to this tool's terminal, while `'answer'`
|
|
597
750
|
* resolves one by `id` with a complete `values` record.
|
|
598
751
|
*
|
|
@@ -600,7 +753,7 @@ var promptToolShape = objectShape({
|
|
|
600
753
|
* `values` is first bounded to exact JSON. The handler then narrows it with the originating Form
|
|
601
754
|
* package's `isFormValues` guard before the terminal manager applies it to the parked live form.
|
|
602
755
|
*/
|
|
603
|
-
var answerToolShape = unionShape(objectShape({ operation: literalShape(["pending"], { description: "List the forms
|
|
756
|
+
var answerToolShape = unionShape(objectShape({ operation: literalShape(["pending"], { description: "List the forms addressed to this terminal." }) }), objectShape({
|
|
604
757
|
operation: literalShape(["answer"], { description: "Answer one pending form by id." }),
|
|
605
758
|
id: stringShape({
|
|
606
759
|
min: 1,
|
|
@@ -609,7 +762,7 @@ var answerToolShape = unionShape(objectShape({ operation: literalShape(["pending
|
|
|
609
762
|
values: jsonShape({ description: "The form answers keyed by field name." })
|
|
610
763
|
}));
|
|
611
764
|
/**
|
|
612
|
-
*
|
|
765
|
+
* Describes the shape of {@link import('./types.js').AgentToolArguments} —
|
|
613
766
|
* {@link import('./factories.js').createAgentTool}'s advertised `parameters`.
|
|
614
767
|
*
|
|
615
768
|
* @remarks
|
|
@@ -619,7 +772,7 @@ var answerToolShape = unionShape(objectShape({ operation: literalShape(["pending
|
|
|
619
772
|
var agentToolShape = objectShape({
|
|
620
773
|
task: stringShape({
|
|
621
774
|
min: 1,
|
|
622
|
-
description: "The instructions the sub-agent
|
|
775
|
+
description: "The instructions the sub-agent carries out."
|
|
623
776
|
}),
|
|
624
777
|
provider: optionalShape(stringShape({
|
|
625
778
|
min: 1,
|
|
@@ -629,7 +782,7 @@ var agentToolShape = objectShape({
|
|
|
629
782
|
system: optionalShape(stringShape({ description: "A system prompt seeding the sub-agent's context (overrides the default)." }))
|
|
630
783
|
});
|
|
631
784
|
/**
|
|
632
|
-
*
|
|
785
|
+
* Describes the shape of {@link import('./types.js').DescribeToolArguments} —
|
|
633
786
|
* {@link import('./factories.js').createDescribeTool}'s advertised `parameters`.
|
|
634
787
|
*
|
|
635
788
|
* @remarks
|
|
@@ -640,8 +793,8 @@ var describeToolShape = objectShape({ name: stringShape({
|
|
|
640
793
|
description: "The registered name of the tool whose full description to return."
|
|
641
794
|
}) });
|
|
642
795
|
/**
|
|
643
|
-
*
|
|
644
|
-
*
|
|
796
|
+
* Describes the shape of a {@link import('./types.js').TaskDraft} — identical to a strict task
|
|
797
|
+
* shape except that `id` and `name` are optional.
|
|
645
798
|
*/
|
|
646
799
|
var taskDraftShape = objectShape({
|
|
647
800
|
id: optionalShape(stringShape({
|
|
@@ -653,7 +806,7 @@ var taskDraftShape = objectShape({
|
|
|
653
806
|
description: "Task name; defaults to the id when omitted."
|
|
654
807
|
})),
|
|
655
808
|
description: optionalShape(stringShape({ description: "Optional task description." })),
|
|
656
|
-
|
|
809
|
+
behavior: optionalShape(stringShape({
|
|
657
810
|
min: 1,
|
|
658
811
|
description: "The registered behavior name to invoke (a registry key, not a label); omitted completes with JSON null."
|
|
659
812
|
})),
|
|
@@ -668,8 +821,8 @@ var taskDraftShape = objectShape({
|
|
|
668
821
|
}))
|
|
669
822
|
});
|
|
670
823
|
/**
|
|
671
|
-
*
|
|
672
|
-
* `name` are
|
|
824
|
+
* Describes the shape of a phase in a draft workflow — identical to a strict phase shape except
|
|
825
|
+
* that `id` and `name` are optional and each task takes {@link taskDraftShape}.
|
|
673
826
|
*/
|
|
674
827
|
var phaseDraftShape = objectShape({
|
|
675
828
|
id: optionalShape(stringShape({
|
|
@@ -689,17 +842,17 @@ var phaseDraftShape = objectShape({
|
|
|
689
842
|
bail: optionalShape(literalShape([true, false], { description: "Per-phase failure-policy override; omitted inherits the workflow bail." }))
|
|
690
843
|
});
|
|
691
844
|
/**
|
|
692
|
-
*
|
|
693
|
-
* are
|
|
694
|
-
* identity
|
|
845
|
+
* Describes the shape of a draft workflow — identical to a strict workflow shape except that `id`
|
|
846
|
+
* and `name` are optional at the workflow, phase, and task levels, so a small model can omit every
|
|
847
|
+
* identity string and let the tool synthesize them positionally.
|
|
695
848
|
*
|
|
696
849
|
* @remarks
|
|
697
850
|
* The lenient counterpart {@link import('./factories.js').createWorkflowDraftContract} compiles.
|
|
698
|
-
* `
|
|
851
|
+
* `behavior` stays optional like the strict form; omission is the deliberate JSON `null` no-op. A
|
|
699
852
|
* provided `id` / `name` still has `minLength: 1` (so
|
|
700
|
-
* an explicitly-empty `id: ''` is
|
|
853
|
+
* an explicitly-empty `id: ''` is rejected, not auto-filled). After
|
|
701
854
|
* {@link import('./helpers.js').completeDraft} fills the missing ids/names, the result is
|
|
702
|
-
* validated against the
|
|
855
|
+
* validated against the strict `createWorkflowContract` (`@orkestrel/workflow`) gate before
|
|
703
856
|
* running.
|
|
704
857
|
*/
|
|
705
858
|
var workflowDraftShape = objectShape({
|
|
@@ -716,27 +869,29 @@ var workflowDraftShape = objectShape({
|
|
|
716
869
|
bail: optionalShape(literalShape([true, false], { description: "Failure policy: false (default) continues gracefully, true halts on the first failure." }))
|
|
717
870
|
});
|
|
718
871
|
/**
|
|
719
|
-
*
|
|
872
|
+
* Describes the shape of one flat step — `{ name }` — the building block of
|
|
873
|
+
* {@link workflowStepsShape}.
|
|
720
874
|
*
|
|
721
875
|
* @remarks
|
|
722
|
-
* `name` is the
|
|
876
|
+
* `name` is the registered behavior name the step runs (it becomes the task's `behavior`). The tool
|
|
723
877
|
* expands each step into a one-task phase, in order ({@link import('./helpers.js').expandSteps}).
|
|
724
878
|
*/
|
|
725
879
|
var stepShape = objectShape({ name: stringShape({
|
|
726
880
|
min: 1,
|
|
727
|
-
description: "The registered behavior name this step runs (becomes the task
|
|
881
|
+
description: "The registered behavior name this step runs (becomes the task behavior)."
|
|
728
882
|
}) });
|
|
729
883
|
/**
|
|
730
|
-
*
|
|
731
|
-
* `parameters` — the simplest surface a small model can fill:
|
|
884
|
+
* Describes the flat authoring shape {@link import('./factories.js').createWorkflowTool} advertises
|
|
885
|
+
* as its `parameters` — the simplest surface a small model can fill:
|
|
886
|
+
* `{ name?, steps: [{ name }] }`.
|
|
732
887
|
*
|
|
733
888
|
* @remarks
|
|
734
889
|
* A deliberately-reduced surface: a flat ordered list of steps, each a `{ name }`. The tool
|
|
735
|
-
*
|
|
890
|
+
* expands it ({@link import('./helpers.js').expandSteps}) into a full
|
|
736
891
|
* {@link import('./types.js').WorkflowDefinition} — one one-task phase per step, in order —
|
|
737
|
-
* then validates against the
|
|
738
|
-
* full nested form is
|
|
739
|
-
* documented as the advanced escape-hatch in the tool's description — but
|
|
892
|
+
* then validates against the strict `createWorkflowContract` (`@orkestrel/workflow`) gate. The
|
|
893
|
+
* full nested form is still accepted by the tool (it branches on the args' shape) and is
|
|
894
|
+
* documented as the advanced escape-hatch in the tool's description — but this is what
|
|
740
895
|
* `parameters` advertises.
|
|
741
896
|
*/
|
|
742
897
|
var workflowStepsShape = objectShape({
|
|
@@ -747,20 +902,20 @@ var workflowStepsShape = objectShape({
|
|
|
747
902
|
steps: arrayShape(stepShape, { description: "The ordered steps to run, one after another (each becomes a one-task phase)." })
|
|
748
903
|
});
|
|
749
904
|
/**
|
|
750
|
-
*
|
|
751
|
-
* over the
|
|
752
|
-
* literal (never a bare `kind
|
|
753
|
-
* discriminant then its
|
|
905
|
+
* Describes the shape of a {@link import('./types.js').WorkspaceOperation} — a descriptive tagged union
|
|
906
|
+
* over the workspace edit, read, and navigation operations, discriminated by the `operation`
|
|
907
|
+
* literal (never a bare `kind`). Each variant leads with its `operation`
|
|
908
|
+
* discriminant then its flat fields, every field through `stringShape` / `optionalShape` /
|
|
754
909
|
* `integerShape({ min: 1 })` / `booleanShape`, each carrying a strong field-level `description`.
|
|
755
910
|
*
|
|
756
911
|
* @remarks
|
|
757
|
-
* The union compiles to an `anyOf` JSON Schema
|
|
912
|
+
* The union compiles to an `anyOf` JSON Schema, a `unionOf` guard, and a first-match parser
|
|
758
913
|
* automatically ({@link import('./factories.js').createWorkspaceTool} types the result to the
|
|
759
|
-
* hand-written {@link import('./types.js').WorkspaceOperation}). `limit` and the
|
|
760
|
-
* caret components are
|
|
761
|
-
* are `optionalShape(booleanShape(...))`. The
|
|
914
|
+
* hand-written {@link import('./types.js').WorkspaceOperation}). `limit` and the `'splice'`
|
|
915
|
+
* caret components are positive integers (`integerShape({ min: 1 })`); `regex` / `sensitive`
|
|
916
|
+
* are `optionalShape(booleanShape(...))`. The registry arms — `workspaces` (list the
|
|
762
917
|
* workspaces the model can move between) and `switch` (re-point the active one by `id`) — let a
|
|
763
|
-
* model
|
|
918
|
+
* model discover then choose which workspace the edit / read arms target.
|
|
764
919
|
*/
|
|
765
920
|
var workspaceToolShape = unionShape(objectShape({
|
|
766
921
|
operation: literalShape(["read"], { description: "Read a whole text file's text by path." }),
|
|
@@ -830,27 +985,37 @@ var workspaceToolShape = unionShape(objectShape({
|
|
|
830
985
|
operation: 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." }),
|
|
831
986
|
id: stringShape({ description: "The id of the workspace to make active (from the \"workspaces\" listing)." })
|
|
832
987
|
}));
|
|
833
|
-
/**
|
|
834
|
-
var
|
|
988
|
+
/** Describes a {@link import('./types.js').ColumnPrimitive} literal — the leaf {@link columnSpecShape} wraps. */
|
|
989
|
+
var columnPrimitiveShape = literalShape([
|
|
835
990
|
"string",
|
|
836
991
|
"integer",
|
|
837
992
|
"number",
|
|
838
993
|
"boolean"
|
|
839
|
-
], { description: "A column
|
|
840
|
-
/**
|
|
841
|
-
var columnSpecShape = unionShape(
|
|
842
|
-
|
|
994
|
+
], { description: "A column primitive: \"string\" | \"integer\" | \"number\" | \"boolean\"." });
|
|
995
|
+
/** Describes a {@link import('./types.js').ColumnSpec} — a bare {@link columnPrimitiveShape}, or `{ primitive, optional }`. */
|
|
996
|
+
var columnSpecShape = unionShape(columnPrimitiveShape, objectShape({
|
|
997
|
+
primitive: columnPrimitiveShape,
|
|
843
998
|
optional: optionalShape(booleanShape({ description: "Whether the column may be absent from a row." }))
|
|
844
999
|
}));
|
|
845
|
-
/**
|
|
846
|
-
var tableSpecShape = recordShape(objectShape({ columns: recordShape(columnSpecShape, { description: "Column name to its
|
|
847
|
-
/**
|
|
1000
|
+
/** Describes a {@link import('./types.js').TableSpec} — table name to `{ columns }`, each column a {@link columnSpecShape}. */
|
|
1001
|
+
var tableSpecShape = recordShape(objectShape({ columns: recordShape(columnSpecShape, { description: "Column name to its primitive or its { primitive, optional } spec." }) }), { description: "Table name to its column layout." });
|
|
1002
|
+
/**
|
|
1003
|
+
* Describes one key value for the database tool and the relation tool — a string or number; the
|
|
1004
|
+
* array form (multiple keys, positional) resolves first, so an array argument is read as many keys
|
|
1005
|
+
* rather than one.
|
|
1006
|
+
*/
|
|
848
1007
|
var keyShape = unionShape(arrayShape(unionShape(stringShape(), numberShape()), { description: "Multiple row keys, positional — a miss at an index is undefined there." }), stringShape({ description: "One row key." }), numberShape({ description: "One row key." }));
|
|
849
|
-
/**
|
|
1008
|
+
/** Describes a loose row — a flat object of column name to JSON value. */
|
|
850
1009
|
var rowShape = recordShape(jsonShape(), { description: "A row as a flat object of column name to value." });
|
|
851
|
-
/**
|
|
1010
|
+
/**
|
|
1011
|
+
* Describes one or many loose rows — the array form resolves first, so an array argument is read as
|
|
1012
|
+
* many rows rather than one.
|
|
1013
|
+
*/
|
|
852
1014
|
var rowsShape = unionShape(arrayShape(rowShape, { description: "Multiple rows." }), rowShape);
|
|
853
|
-
/**
|
|
1015
|
+
/**
|
|
1016
|
+
* Describes one serialized where condition — `values` is always an array, even for a single-value
|
|
1017
|
+
* operator.
|
|
1018
|
+
*/
|
|
854
1019
|
var conditionShape = objectShape({
|
|
855
1020
|
column: stringShape({ description: "The column this condition applies to." }),
|
|
856
1021
|
operator: literalShape([
|
|
@@ -873,12 +1038,12 @@ var conditionShape = objectShape({
|
|
|
873
1038
|
values: arrayShape(jsonShape(), { description: "The operand values the operator needs (always an array, even for one value)." }),
|
|
874
1039
|
connector: optionalShape(literalShape(["and", "or"], { description: "Joins this condition to the next; omit on the last condition." }))
|
|
875
1040
|
});
|
|
876
|
-
/**
|
|
1041
|
+
/** Describes one sort term — a `column` to sort by and the `direction` to sort it in. */
|
|
877
1042
|
var orderShape = objectShape({
|
|
878
1043
|
column: stringShape({ description: "The column to sort by." }),
|
|
879
1044
|
direction: literalShape(["ascending", "descending"], { description: "The sort direction." })
|
|
880
1045
|
});
|
|
881
|
-
/**
|
|
1046
|
+
/** Describes the serialized query form — conditions, order, and pagination. */
|
|
882
1047
|
var queryShape = objectShape({
|
|
883
1048
|
conditions: optionalShape(arrayShape(conditionShape, { description: "The WHERE conditions, folded left to right." })),
|
|
884
1049
|
order: optionalShape(arrayShape(orderShape, { description: "The sort terms, applied in order." })),
|
|
@@ -892,18 +1057,17 @@ var queryShape = objectShape({
|
|
|
892
1057
|
}))
|
|
893
1058
|
});
|
|
894
1059
|
/**
|
|
895
|
-
*
|
|
896
|
-
* discriminated by `operation` into the
|
|
897
|
-
* `'
|
|
898
|
-
* `'remove'` / `'destroy'`).
|
|
1060
|
+
* Describes the shape of {@link import('./factories.js').createDatabaseTool}'s call arguments —
|
|
1061
|
+
* discriminated by `operation` into the database operations `'create'`, `'tables'`, `'get'`,
|
|
1062
|
+
* `'records'`, `'count'`, `'aggregate'`, `'add'`, `'set'`, `'update'`, `'remove'`, and `'destroy'`.
|
|
899
1063
|
*
|
|
900
1064
|
* @remarks
|
|
901
1065
|
* Every arm carries `id` (the database id). `'create'` carries `tables` (the
|
|
902
|
-
* {@link import('./types.js').TableSpec} column DSL, compiled
|
|
903
|
-
* {@link import('./
|
|
1066
|
+
* {@link import('./types.js').TableSpec} column DSL, compiled through
|
|
1067
|
+
* {@link import('./compilers.js').expandTables}); `'get'` / `'update'` / `'remove'` carry `key`
|
|
904
1068
|
* (one key or an array of keys, positional); `'add'` / `'set'` carry `row` (one row or an array of
|
|
905
1069
|
* rows); `'update'` also carries `changes` (a loose partial row); `'records'` / `'count'` /
|
|
906
|
-
* `'aggregate'` carry an optional `query` (the
|
|
1070
|
+
* `'aggregate'` carry an optional `query` (the serialized form — `values` is always an array,
|
|
907
1071
|
* even for a single-value operator, so a caller never chains method calls or guesses arity).
|
|
908
1072
|
*/
|
|
909
1073
|
var databaseToolShape = unionShape(objectShape({
|
|
@@ -1036,21 +1200,19 @@ var databaseToolShape = unionShape(objectShape({
|
|
|
1036
1200
|
description: "The database id."
|
|
1037
1201
|
})
|
|
1038
1202
|
}));
|
|
1039
|
-
/**
|
|
1040
|
-
var relationKeyShape = unionShape(arrayShape(unionShape(stringShape(), numberShape()), { description: "Multiple row keys, positional — a miss at an index is undefined there." }), stringShape({ description: "One row key." }), numberShape({ description: "One row key." }));
|
|
1041
|
-
/** A single row key (not an array) — used by `'link'` / `'unlink'` / `'links'`, which address exactly one owning row. */
|
|
1203
|
+
/** Describes a single row key (not an array) — used by `'link'` / `'unlink'` / `'links'`, which address exactly one owning row. */
|
|
1042
1204
|
var singleKeyShape = unionShape(stringShape({ description: "The owning row key." }), numberShape({ description: "The owning row key." }));
|
|
1043
|
-
/**
|
|
1044
|
-
var includeShape = optionalShape(arrayShape(stringShape({ description: "A dot-separated chain of relation names,
|
|
1045
|
-
/**
|
|
1205
|
+
/** Describes the flat dot-path relation include list, expanded through {@link import('./helpers.js').expandInclude}. */
|
|
1206
|
+
var includeShape = optionalShape(arrayShape(stringShape({ description: "A dot-separated chain of relation names, for example \"contacts.account\"." }), { description: "Which relations to attach, as flat dot-paths." }));
|
|
1207
|
+
/** Describes which registered relation manager to address — omitted resolves to the sole registered manager. */
|
|
1046
1208
|
var managerShape = optionalShape(stringShape({
|
|
1047
1209
|
min: 1,
|
|
1048
1210
|
description: "Which registered relation manager to address."
|
|
1049
1211
|
}));
|
|
1050
1212
|
/**
|
|
1051
|
-
*
|
|
1052
|
-
* discriminated by `operation` into the
|
|
1053
|
-
* `'unlink'
|
|
1213
|
+
* Describes the shape of {@link import('./factories.js').createRelationTool}'s call arguments —
|
|
1214
|
+
* discriminated by `operation` into the relation operations `'load'`, `'find'`, `'link'`,
|
|
1215
|
+
* `'unlink'`, and `'links'`.
|
|
1054
1216
|
*
|
|
1055
1217
|
* @remarks
|
|
1056
1218
|
* `'load'` fetches one or more rows (positional key/array) with `include` attached. `'find'`
|
|
@@ -1064,7 +1226,7 @@ var relationToolShape = unionShape(objectShape({
|
|
|
1064
1226
|
min: 1,
|
|
1065
1227
|
description: "The model (table) name."
|
|
1066
1228
|
}),
|
|
1067
|
-
key:
|
|
1229
|
+
key: keyShape,
|
|
1068
1230
|
include: includeShape
|
|
1069
1231
|
}), objectShape({
|
|
1070
1232
|
operation: literalShape(["find"], { description: "List rows, with related rows attached." }),
|
|
@@ -1127,7 +1289,7 @@ var relationToolShape = unionShape(objectShape({
|
|
|
1127
1289
|
})
|
|
1128
1290
|
}));
|
|
1129
1291
|
/**
|
|
1130
|
-
*
|
|
1292
|
+
* Describes the shape of {@link import('./factories.js').createInferTool}'s call arguments — one or more
|
|
1131
1293
|
* example `samples` to infer a JSON Schema from, plus per-call `format` / `enum` toggles and an
|
|
1132
1294
|
* optional `candidates` array to check against the inferred schema.
|
|
1133
1295
|
*
|
|
@@ -1135,8 +1297,8 @@ var relationToolShape = unionShape(objectShape({
|
|
|
1135
1297
|
* `samples` requires at least one element (`min: 1`) — an empty array parses to `undefined`,
|
|
1136
1298
|
* surfaced by the handler as a typed `TOOL` {@link import('./errors.js').ToolboxError}. When
|
|
1137
1299
|
* `candidates` is present (any array, including empty), the handler compiles a contract from the
|
|
1138
|
-
* freshly inferred schema and checks each candidate against it with a
|
|
1139
|
-
* coercion) — the opposite of {@link import('./factories.js').createEndpointTool}'s
|
|
1300
|
+
* freshly inferred schema and checks each candidate against it with a strict guard (`.is`, no
|
|
1301
|
+
* coercion) — the opposite of {@link import('./factories.js').createEndpointTool}'s normalizing
|
|
1140
1302
|
* `.parse` enforcement.
|
|
1141
1303
|
*/
|
|
1142
1304
|
var inferToolShape = objectShape({
|
|
@@ -1151,24 +1313,24 @@ var inferToolShape = objectShape({
|
|
|
1151
1313
|
//#endregion
|
|
1152
1314
|
//#region src/core/helpers.ts
|
|
1153
1315
|
/**
|
|
1154
|
-
*
|
|
1316
|
+
* Returns the ancestry identifier of a workflow in a run chain — `workflow:<id>`.
|
|
1155
1317
|
*
|
|
1156
1318
|
* @remarks
|
|
1157
|
-
* Namespacing keeps a workflow id and
|
|
1158
|
-
* collision, so re-entering a workflow
|
|
1319
|
+
* Namespacing keeps a workflow id and a {@link tagAgent} agent name in one set without
|
|
1320
|
+
* collision, so re-entering a workflow or an agent already in the chain is a single `includes`
|
|
1159
1321
|
* check.
|
|
1160
1322
|
*
|
|
1161
1323
|
* @param id - The workflow definition's `id`
|
|
1162
1324
|
* @returns The namespaced ancestry tag (`workflow:<id>`)
|
|
1163
1325
|
*/
|
|
1164
|
-
function
|
|
1326
|
+
function tagWorkflow(id) {
|
|
1165
1327
|
return `workflow:${id}`;
|
|
1166
1328
|
}
|
|
1167
1329
|
/**
|
|
1168
|
-
*
|
|
1330
|
+
* Returns the ancestry identifier of an agent in a run chain — `agent:<name>`.
|
|
1169
1331
|
*
|
|
1170
1332
|
* @remarks
|
|
1171
|
-
* The agent counterpart of {@link
|
|
1333
|
+
* The agent counterpart of {@link tagWorkflow}: {@link import('./factories.js').createAgentFunction}
|
|
1172
1334
|
* / {@link import('./factories.js').createWorkflowTool} guard against re-entering an agent or
|
|
1173
1335
|
* workflow already in the chain (this package's typed `DEPTH` `ToolboxError`). The
|
|
1174
1336
|
* `agent:` namespace keeps it distinct from a same-string workflow id.
|
|
@@ -1176,88 +1338,53 @@ function workflowTag(id) {
|
|
|
1176
1338
|
* @param name - The agent's identifier / registry name
|
|
1177
1339
|
* @returns The namespaced ancestry tag (`agent:<name>`)
|
|
1178
1340
|
*/
|
|
1179
|
-
function
|
|
1341
|
+
function tagAgent(name) {
|
|
1180
1342
|
return `agent:${name}`;
|
|
1181
1343
|
}
|
|
1182
1344
|
/**
|
|
1183
|
-
*
|
|
1184
|
-
*
|
|
1185
|
-
* @param value - The value to inspect
|
|
1186
|
-
* @returns Whether `value` is a unique, nonempty-tagged workflow/agent chain
|
|
1187
|
-
*/
|
|
1188
|
-
function isWorkflowLineage(value) {
|
|
1189
|
-
const inspected = attempt(() => {
|
|
1190
|
-
if (!Array.isArray(value)) return false;
|
|
1191
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1192
|
-
for (const [index, tag] of value.entries()) {
|
|
1193
|
-
if (!isString(tag)) return false;
|
|
1194
|
-
const prefix = index % 2 === 0 ? "workflow:" : "agent:";
|
|
1195
|
-
if (!tag.startsWith(prefix) || tag.length === prefix.length || seen.has(tag)) return false;
|
|
1196
|
-
seen.add(tag);
|
|
1197
|
-
}
|
|
1198
|
-
return true;
|
|
1199
|
-
});
|
|
1200
|
-
return inspected.success && inspected.value;
|
|
1201
|
-
}
|
|
1202
|
-
/**
|
|
1203
|
-
* Build a validated, copied, and frozen workflow lineage value.
|
|
1345
|
+
* Returns the canonical workflow lineage — validated, copied, and frozen.
|
|
1204
1346
|
*
|
|
1205
1347
|
* @param lineage - The configured chain; omitted means a direct root
|
|
1206
1348
|
* @returns An immutable caller-isolated lineage
|
|
1207
1349
|
*/
|
|
1208
|
-
function
|
|
1350
|
+
function normalizeLineage(lineage = []) {
|
|
1209
1351
|
if (!isWorkflowLineage(lineage)) throw new ToolboxError("TOOL", "workflow lineage must alternate unique workflow and agent tags");
|
|
1210
1352
|
return Object.freeze([...lineage]);
|
|
1211
1353
|
}
|
|
1212
1354
|
/**
|
|
1213
|
-
*
|
|
1355
|
+
* Appends one tag to a workflow lineage and returns a frozen copy.
|
|
1214
1356
|
*
|
|
1215
1357
|
* @param lineage - The valid chain to extend
|
|
1216
1358
|
* @param tag - The workflow or agent tag to append
|
|
1217
1359
|
* @returns The validated immutable extension
|
|
1218
1360
|
*/
|
|
1219
1361
|
function extendLineage(lineage, tag) {
|
|
1220
|
-
return
|
|
1362
|
+
return normalizeLineage([...lineage, tag]);
|
|
1221
1363
|
}
|
|
1222
1364
|
/**
|
|
1223
|
-
*
|
|
1365
|
+
* Derives the zero-based workflow nesting depth from a valid lineage.
|
|
1224
1366
|
*
|
|
1225
1367
|
* @param lineage - The workflow/agent chain
|
|
1226
1368
|
* @returns Zero for an empty/root lineage, then one per nested workflow
|
|
1227
1369
|
*/
|
|
1228
1370
|
function deriveWorkflowDepth(lineage) {
|
|
1229
|
-
const workflows =
|
|
1371
|
+
const workflows = normalizeLineage(lineage).filter((tag) => tag.startsWith("workflow:")).length;
|
|
1230
1372
|
return Math.max(0, workflows - 1);
|
|
1231
1373
|
}
|
|
1232
1374
|
/**
|
|
1233
|
-
*
|
|
1234
|
-
*
|
|
1235
|
-
*
|
|
1236
|
-
* @returns Whether it is a frozen {@link AgentFunction} with a frozen valid lineage
|
|
1237
|
-
*/
|
|
1238
|
-
function isAgentFunction(value) {
|
|
1239
|
-
const inspected = attempt(() => {
|
|
1240
|
-
if (typeof value !== "function" || !Object.isFrozen(value)) return false;
|
|
1241
|
-
const category = Reflect.getOwnPropertyDescriptor(value, "category");
|
|
1242
|
-
const lineage = Reflect.getOwnPropertyDescriptor(value, "lineage");
|
|
1243
|
-
return category?.value === "agent" && lineage !== void 0 && "value" in lineage && Object.isFrozen(lineage.value) && isWorkflowLineage(lineage.value);
|
|
1244
|
-
});
|
|
1245
|
-
return inspected.success && inspected.value;
|
|
1246
|
-
}
|
|
1247
|
-
/**
|
|
1248
|
-
* Build the plain success summary {@link import('./factories.js').createWorkflowTool} returns on
|
|
1249
|
-
* a completed run — the universal tool-handler contract (AGENTS §14): return a plain value on
|
|
1250
|
-
* success, appearing identically over BOTH the agent loop and MCP.
|
|
1375
|
+
* Builds the plain success summary {@link import('./factories.js').createWorkflowTool} returns on a
|
|
1376
|
+
* completed run — the universal tool-handler contract: return a plain value on success, appearing
|
|
1377
|
+
* identically over both the agent loop and MCP.
|
|
1251
1378
|
*
|
|
1252
1379
|
* @remarks
|
|
1253
|
-
* The summary is
|
|
1380
|
+
* The summary is lean: the workflow's terminal `status`, settled-task count, and exact optional
|
|
1254
1381
|
* native persistence outcome. It carries no synthetic `id` / `name`: a tool handler has no call
|
|
1255
1382
|
* id; the `ToolManagerInterface` (`@orkestrel/tool`) supplies the canonical envelope's identity.
|
|
1256
1383
|
*
|
|
1257
1384
|
* @param result - The terminal `WorkflowResult` (`@orkestrel/workflow`) the run produced
|
|
1258
1385
|
* @returns The plain success summary — `{ status, count, durable?, fault? }`
|
|
1259
1386
|
*/
|
|
1260
|
-
function
|
|
1387
|
+
function summarizeWorkflow(result) {
|
|
1261
1388
|
return {
|
|
1262
1389
|
status: result.status,
|
|
1263
1390
|
count: result.results.length,
|
|
@@ -1266,21 +1393,21 @@ function workflowToolSummary(result) {
|
|
|
1266
1393
|
};
|
|
1267
1394
|
}
|
|
1268
1395
|
/**
|
|
1269
|
-
*
|
|
1270
|
-
*
|
|
1271
|
-
*
|
|
1396
|
+
* Completes a {@link WorkflowDraft} into a strict {@link WorkflowDefinition} — synthesizes a
|
|
1397
|
+
* missing `id` deterministically and positionally, and defaults a missing `name` to its resolved
|
|
1398
|
+
* `id`.
|
|
1272
1399
|
*
|
|
1273
1400
|
* @remarks
|
|
1274
1401
|
* The positional id scheme is stable and human-legible: the workflow is `wf`, phase `i` is
|
|
1275
1402
|
* `phase-<i>`, and task `j` of that phase is `<phaseId>-task-<j>` (so a provided phase id flows
|
|
1276
|
-
* into its tasks' synthesized ids). A
|
|
1403
|
+
* into its tasks' synthesized ids). A provided `id` / `name` at any level is kept verbatim —
|
|
1277
1404
|
* synthesis touches only the omitted ones. A missing `name` defaults to the resolved `id` (never
|
|
1278
|
-
* the other way round), so the result always
|
|
1405
|
+
* the other way round), so the result always carries an `id` and a `name`. `behavior`, `description`, the per-phase
|
|
1279
1406
|
* `concurrency` / `bail`, the per-task `retries` / `timeout`, and the workflow `bail` carry over
|
|
1280
1407
|
* unchanged. The result is a complete {@link WorkflowDefinition}; the caller still validates it
|
|
1281
|
-
* against the
|
|
1408
|
+
* against the strict contract.
|
|
1282
1409
|
*
|
|
1283
|
-
* @param draft - The draft workflow (id
|
|
1410
|
+
* @param draft - The draft workflow (`id` and `name` optional at the workflow, phase, and task levels)
|
|
1284
1411
|
* @returns A complete {@link WorkflowDefinition} with every id/name filled
|
|
1285
1412
|
*/
|
|
1286
1413
|
function completeDraft(draft) {
|
|
@@ -1294,7 +1421,7 @@ function completeDraft(draft) {
|
|
|
1294
1421
|
};
|
|
1295
1422
|
}
|
|
1296
1423
|
/**
|
|
1297
|
-
*
|
|
1424
|
+
* Completes one {@link PhaseDraft} into a strict phase definition — the per-phase step of
|
|
1298
1425
|
* {@link completeDraft} (phase `index` → `phase-<index>` when its id is omitted).
|
|
1299
1426
|
*
|
|
1300
1427
|
* @param phase - The draft phase
|
|
@@ -1313,7 +1440,7 @@ function completePhaseDraft(phase, index) {
|
|
|
1313
1440
|
};
|
|
1314
1441
|
}
|
|
1315
1442
|
/**
|
|
1316
|
-
*
|
|
1443
|
+
* Completes one {@link TaskDraft} into a strict task definition — the per-task leaf step of
|
|
1317
1444
|
* {@link completeDraft} (task `index` of phase `<phaseId>` → `<phaseId>-task-<index>` when its id
|
|
1318
1445
|
* is omitted).
|
|
1319
1446
|
*
|
|
@@ -1328,25 +1455,25 @@ function completeTaskDraft(task, phaseId, index) {
|
|
|
1328
1455
|
id,
|
|
1329
1456
|
name: task.name ?? id,
|
|
1330
1457
|
...task.description === void 0 ? {} : { description: task.description },
|
|
1331
|
-
...task.
|
|
1458
|
+
...task.behavior === void 0 ? {} : { behavior: task.behavior },
|
|
1332
1459
|
...task.retries === void 0 ? {} : { retries: task.retries },
|
|
1333
1460
|
...task.timeout === void 0 ? {} : { timeout: task.timeout }
|
|
1334
1461
|
};
|
|
1335
1462
|
}
|
|
1336
1463
|
/**
|
|
1337
|
-
*
|
|
1338
|
-
* becomes a one-task phase,
|
|
1464
|
+
* Expands a flat {@link WorkflowSteps} blob into a strict {@link WorkflowDefinition} — each step
|
|
1465
|
+
* becomes a one-task phase, in order.
|
|
1339
1466
|
*
|
|
1340
1467
|
* @remarks
|
|
1341
|
-
* The expansion of the tool's
|
|
1468
|
+
* The expansion of the tool's advertised surface: the deliberately-reduced flat form. Each
|
|
1342
1469
|
* {@link import('./types.js').WorkflowStep} maps to a phase holding exactly one task: the step's
|
|
1343
|
-
* `name` becomes the task's `
|
|
1470
|
+
* `name` becomes the task's `behavior` (the behavior-registry key). Ids/names are auto-filled
|
|
1344
1471
|
* positionally — it builds an ids-omitted {@link WorkflowDraft} and delegates to
|
|
1345
|
-
* {@link completeDraft}, so the
|
|
1472
|
+
* {@link completeDraft}, so the lenient surfaces share one synthesis path (step `i` → phase
|
|
1346
1473
|
* `phase-<i>`, its task `phase-<i>-task-0`). The optional `name` becomes both the workflow's
|
|
1347
1474
|
* deterministic id and its name, so named flat workflows retain distinct persistence keys;
|
|
1348
1475
|
* omission keeps the shared draft fallback `wf`. The result is a complete definition the caller
|
|
1349
|
-
* validates against the
|
|
1476
|
+
* validates against the strict contract before running.
|
|
1350
1477
|
*
|
|
1351
1478
|
* @param flat - The flat steps blob (`{ name?, steps: [{ name }] }`)
|
|
1352
1479
|
* @returns A complete {@link WorkflowDefinition} (one one-task phase per step)
|
|
@@ -1357,12 +1484,12 @@ function expandSteps(flat) {
|
|
|
1357
1484
|
id: flat.name,
|
|
1358
1485
|
name: flat.name
|
|
1359
1486
|
},
|
|
1360
|
-
phases: flat.steps.map((step) => ({ tasks: [{
|
|
1487
|
+
phases: flat.steps.map((step) => ({ tasks: [{ behavior: step.name }] }))
|
|
1361
1488
|
});
|
|
1362
1489
|
}
|
|
1363
1490
|
/**
|
|
1364
|
-
*
|
|
1365
|
-
*
|
|
1491
|
+
* Maps a caught error to the {@link ToolboxErrorCode} the terminal-tool factory throws with — the
|
|
1492
|
+
* pure classification step of that factory's error handling.
|
|
1366
1493
|
*
|
|
1367
1494
|
* @remarks
|
|
1368
1495
|
* Narrows `error` with {@link isTerminalError} (`@orkestrel/terminal`) first: a non-`TerminalError`
|
|
@@ -1375,119 +1502,46 @@ function expandSteps(flat) {
|
|
|
1375
1502
|
* @param error - The value caught from a terminal-manager operation (`ask` / `answer` / …)
|
|
1376
1503
|
* @returns The mapped {@link ToolboxErrorCode}, or `undefined` if `error` is not a `TerminalError`
|
|
1377
1504
|
*/
|
|
1378
|
-
function
|
|
1505
|
+
function inferTerminalCode(error) {
|
|
1379
1506
|
if (!isTerminalError(error)) return void 0;
|
|
1380
1507
|
if (error.code === "DEADLOCK") return "DEADLOCK";
|
|
1381
1508
|
if (error.code === "EXPIRE") return "EXPIRE";
|
|
1382
1509
|
return "TOOL";
|
|
1383
1510
|
}
|
|
1384
|
-
/** Narrow an unknown value to a {@link ColumnSpec} — a valid {@link import('./types.js').ColumnKind} shorthand, or `{ type, optional }` with a valid `type`. */
|
|
1385
|
-
function isColumnSpec(value) {
|
|
1386
|
-
if (isColumnKind(value)) return true;
|
|
1387
|
-
if (!isRecord(value)) return false;
|
|
1388
|
-
return isColumnKind(value.type) && (value.optional === void 0 || typeof value.optional === "boolean");
|
|
1389
|
-
}
|
|
1390
|
-
/** Narrow an unknown value to a {@link import('./types.js').ColumnKind}. */
|
|
1391
|
-
function isColumnKind(value) {
|
|
1392
|
-
return value === "string" || value === "integer" || value === "number" || value === "boolean";
|
|
1393
|
-
}
|
|
1394
|
-
/**
|
|
1395
|
-
* Compile a {@link TableSpec} into the `@orkestrel/database` {@link TableMap} it configures —
|
|
1396
|
-
* each {@link ColumnSpec} maps to the matching primitive shaper (`'string'` → `stringShape()`,
|
|
1397
|
-
* `'integer'` → `integerShape()`, `'number'` → `numberShape()`, `'boolean'` → `booleanShape()`),
|
|
1398
|
-
* wrapped in `optionalShape` when the column declares `optional: true`. Total, pure.
|
|
1399
|
-
*
|
|
1400
|
-
* @param spec - The small-model-facing table layout
|
|
1401
|
-
* @returns The compiled `TableMap` a `@orkestrel/database` `createDatabase` call accepts
|
|
1402
|
-
*/
|
|
1403
|
-
function expandTables(spec) {
|
|
1404
|
-
const tables = {};
|
|
1405
|
-
for (const [table, definition] of Object.entries(spec)) {
|
|
1406
|
-
const columns = {};
|
|
1407
|
-
for (const [column, kind] of Object.entries(definition.columns)) columns[column] = columnShape(kind);
|
|
1408
|
-
tables[table] = columns;
|
|
1409
|
-
}
|
|
1410
|
-
return tables;
|
|
1411
|
-
}
|
|
1412
|
-
/** Compile one {@link ColumnSpec} into its `@orkestrel/database` column shape — the per-column leaf {@link expandTables} maps over. */
|
|
1413
|
-
function columnShape(spec) {
|
|
1414
|
-
const kind = isString(spec) ? spec : spec.type;
|
|
1415
|
-
const optional = !isString(spec) && spec.optional === true;
|
|
1416
|
-
const shape = kindShape(kind);
|
|
1417
|
-
return optional ? optionalShape(shape) : shape;
|
|
1418
|
-
}
|
|
1419
|
-
/** Map one {@link import('./types.js').ColumnKind} to its primitive `@orkestrel/database` shape — the leaf {@link columnShape} wraps. */
|
|
1420
|
-
function kindShape(kind) {
|
|
1421
|
-
if (kind === "string") return stringShape();
|
|
1422
|
-
if (kind === "integer") return integerShape();
|
|
1423
|
-
if (kind === "number") return numberShape();
|
|
1424
|
-
return booleanShape();
|
|
1425
|
-
}
|
|
1426
|
-
/**
|
|
1427
|
-
* Narrow an unknown value to a {@link DatabaseDefinition} — a non-empty `id` + `driver`, a
|
|
1428
|
-
* `tables` record whose every value is `{ columns: record of valid ColumnSpec }`, plus optional
|
|
1429
|
-
* `primary`, `indexes`, and finite `version` schema configuration. The boundary guard a
|
|
1430
|
-
* {@link import('./types.js').DefinitionStoreInterface} applies to an untrusted persisted blob
|
|
1431
|
-
* before trusting it as a definition (never an `as`).
|
|
1432
|
-
*/
|
|
1433
|
-
function isDatabaseDefinition(value) {
|
|
1434
|
-
if (!isRecord(value)) return false;
|
|
1435
|
-
if ("keys" in value) return false;
|
|
1436
|
-
if (!isNonEmptyString(value.id) || !isNonEmptyString(value.driver)) return false;
|
|
1437
|
-
if (!isRecord(value.tables)) return false;
|
|
1438
|
-
for (const table of Object.values(value.tables)) {
|
|
1439
|
-
if (!isRecord(table) || !isRecord(table.columns)) return false;
|
|
1440
|
-
for (const column of Object.values(table.columns)) if (!isColumnSpec(column)) return false;
|
|
1441
|
-
}
|
|
1442
|
-
if (value.primary !== void 0) {
|
|
1443
|
-
if (!isRecord(value.primary)) return false;
|
|
1444
|
-
for (const key of Object.values(value.primary)) if (!isNonEmptyString(key)) return false;
|
|
1445
|
-
}
|
|
1446
|
-
if (value.indexes !== void 0) {
|
|
1447
|
-
if (!isRecord(value.indexes)) return false;
|
|
1448
|
-
for (const groups of Object.values(value.indexes)) {
|
|
1449
|
-
if (!Array.isArray(groups)) return false;
|
|
1450
|
-
for (const group of groups) {
|
|
1451
|
-
if (!Array.isArray(group) || group.length === 0) return false;
|
|
1452
|
-
for (const column of group) if (!isNonEmptyString(column)) return false;
|
|
1453
|
-
}
|
|
1454
|
-
}
|
|
1455
|
-
}
|
|
1456
|
-
if (value.version !== void 0 && !isFiniteNumber(value.version)) return false;
|
|
1457
|
-
return true;
|
|
1458
|
-
}
|
|
1459
1511
|
/**
|
|
1460
|
-
*
|
|
1461
|
-
* with — the pure classification step
|
|
1462
|
-
* {@link
|
|
1512
|
+
* Maps a caught error to the granular {@link DatabaseErrorCode} (`@orkestrel/database`) the code
|
|
1513
|
+
* {@link import('./factories.js').createDatabaseTool} throws with — the pure classification step
|
|
1514
|
+
* of that factory's error handling, mirroring {@link inferTerminalCode}'s idiom for
|
|
1515
|
+
* `@orkestrel/database`.
|
|
1463
1516
|
*
|
|
1464
1517
|
* @param error - The value caught from a `@orkestrel/database` table operation
|
|
1465
1518
|
* @returns The granular {@link DatabaseErrorCode}, or `undefined` if `error` is not a `DatabaseError`
|
|
1466
1519
|
*/
|
|
1467
|
-
function
|
|
1520
|
+
function inferDatabaseCode(error) {
|
|
1468
1521
|
return isDatabaseError(error) ? error.code : void 0;
|
|
1469
1522
|
}
|
|
1470
1523
|
/**
|
|
1471
|
-
*
|
|
1472
|
-
* with — the pure classification step
|
|
1473
|
-
* {@link
|
|
1524
|
+
* Maps a caught error to the granular {@link RelationErrorCode} (`@orkestrel/relation`) the code
|
|
1525
|
+
* {@link import('./factories.js').createRelationTool} throws with — the pure classification step
|
|
1526
|
+
* of that factory's error handling, mirroring {@link inferTerminalCode}'s idiom for
|
|
1527
|
+
* `@orkestrel/relation`.
|
|
1474
1528
|
*
|
|
1475
1529
|
* @param error - The value caught from a `@orkestrel/relation` operation
|
|
1476
1530
|
* @returns The granular {@link RelationErrorCode}, or `undefined` if `error` is not a `RelationError`
|
|
1477
1531
|
*/
|
|
1478
|
-
function
|
|
1532
|
+
function inferRelationCode(error) {
|
|
1479
1533
|
return isRelationError(error) ? error.code : void 0;
|
|
1480
1534
|
}
|
|
1481
1535
|
/**
|
|
1482
|
-
*
|
|
1536
|
+
* Expands the relation tool's flat dot-path `include` list into a live `@orkestrel/relation`
|
|
1483
1537
|
* {@link Include} tree — the pure leaf {@link import('./factories.js').createRelationTool} calls
|
|
1484
1538
|
* before a `'load'` / `'find'` call.
|
|
1485
1539
|
*
|
|
1486
1540
|
* @remarks
|
|
1487
1541
|
* Each path splits on `'.'` into a chain of relation names, deep-merged into one nested
|
|
1488
|
-
* `Include` object with a leaf `true`. A longer path
|
|
1542
|
+
* `Include` object with a leaf `true`. A longer path subsumes a shorter sibling's bare `true` —
|
|
1489
1543
|
* `'contacts'` followed by `'contacts.account'` yields `{ contacts: { account: true } }`, never
|
|
1490
|
-
* overwriting the deeper chain. An
|
|
1544
|
+
* overwriting the deeper chain. An empty segment (`''`, from a leading/trailing/doubled `.`) or a
|
|
1491
1545
|
* path whose segment count exceeds `depth` throws a typed `TOOL` {@link ToolboxError}.
|
|
1492
1546
|
*
|
|
1493
1547
|
* @param paths - The flat dot-path `include` list (or `undefined` — yields `{}`)
|
|
@@ -1550,20 +1604,20 @@ function expandInclude(paths, depth) {
|
|
|
1550
1604
|
return include;
|
|
1551
1605
|
}
|
|
1552
1606
|
/**
|
|
1553
|
-
*
|
|
1607
|
+
* Resolves which registered {@link RelationManagerInterface} a relation-tool call addresses — the
|
|
1554
1608
|
* pure manager-resolution leaf {@link import('./factories.js').createRelationTool} calls on
|
|
1555
1609
|
* every operation.
|
|
1556
1610
|
*
|
|
1557
1611
|
* @remarks
|
|
1558
1612
|
* An explicit `name` must match a key of `managers` (a miss throws a typed `TOOL`
|
|
1559
|
-
* {@link ToolboxError} naming the registered managers). An
|
|
1613
|
+
* {@link ToolboxError} naming the registered managers). An omitted `name` resolves to the sole
|
|
1560
1614
|
* registered manager when exactly one is registered, else throws the same typed error.
|
|
1561
1615
|
*
|
|
1562
1616
|
* @param managers - The tool's registered `RelationManagerInterface` map
|
|
1563
1617
|
* @param name - The call's optional `manager` field
|
|
1564
1618
|
* @returns The resolved {@link RelationManagerInterface}
|
|
1565
1619
|
*/
|
|
1566
|
-
function
|
|
1620
|
+
function resolveRelationManager(managers, name) {
|
|
1567
1621
|
if (name !== void 0) {
|
|
1568
1622
|
const manager = managers[name];
|
|
1569
1623
|
if (manager === void 0) throw new ToolboxError("TOOL", `unknown relation manager '${name}'`, {
|
|
@@ -1581,35 +1635,35 @@ function relationManagerOf(managers, name) {
|
|
|
1581
1635
|
throw new ToolboxError("TOOL", "no relation manager resolved for the call", { managers: names });
|
|
1582
1636
|
}
|
|
1583
1637
|
/**
|
|
1584
|
-
*
|
|
1638
|
+
* Resolves a `model` name against a live {@link RelationManagerInterface} — the pure model-lookup
|
|
1585
1639
|
* leaf {@link import('./factories.js').createRelationTool} calls on every operation, mirroring
|
|
1586
|
-
* {@link
|
|
1640
|
+
* {@link resolveRelationManager}'s guard shape.
|
|
1587
1641
|
*
|
|
1588
1642
|
* @param manager - The resolved {@link RelationManagerInterface}
|
|
1589
1643
|
* @param name - The call's `model` field
|
|
1590
1644
|
* @returns The model's {@link ModelInterface}
|
|
1591
1645
|
*/
|
|
1592
|
-
function
|
|
1646
|
+
function resolveRelationModel(manager, name) {
|
|
1593
1647
|
if (!manager.has(name)) throw new ToolboxError("TOOL", `unknown model '${name}'`, {
|
|
1594
1648
|
model: name,
|
|
1595
|
-
models: manager.
|
|
1649
|
+
models: manager.names()
|
|
1596
1650
|
});
|
|
1597
1651
|
return manager.model(name);
|
|
1598
1652
|
}
|
|
1599
1653
|
/**
|
|
1600
|
-
*
|
|
1601
|
-
*
|
|
1654
|
+
* Returns the canonical live `@orkestrel/database` {@link QueryInput} for the database tool's
|
|
1655
|
+
* parsed serialized query — each condition's omitted `connector` defaults to `'and'`.
|
|
1602
1656
|
*
|
|
1603
1657
|
* @remarks
|
|
1604
1658
|
* The wire form ({@link import('./shapers.js').databaseToolShape}) lets a caller drop `connector`
|
|
1605
|
-
* on the last condition (it has nothing to join
|
|
1659
|
+
* on the last condition (it has nothing to join forward to); the compiled `Condition` a live
|
|
1606
1660
|
* `@orkestrel/database` table call accepts always carries one, so this fills the gap. `order` /
|
|
1607
1661
|
* `limit` / `offset` pass through unchanged. Pure and total.
|
|
1608
1662
|
*
|
|
1609
1663
|
* @param query - The parsed query (or `undefined`)
|
|
1610
1664
|
* @returns The equivalent live `QueryInput`, or `undefined` when `query` is `undefined`
|
|
1611
1665
|
*/
|
|
1612
|
-
function
|
|
1666
|
+
function normalizeQuery(query) {
|
|
1613
1667
|
if (query === void 0) return void 0;
|
|
1614
1668
|
const conditions = query.conditions?.map((condition) => ({
|
|
1615
1669
|
...condition,
|
|
@@ -1623,14 +1677,39 @@ function queryOf(query) {
|
|
|
1623
1677
|
};
|
|
1624
1678
|
}
|
|
1625
1679
|
/**
|
|
1626
|
-
*
|
|
1627
|
-
*
|
|
1628
|
-
* uses to detect truncation without a separate `count` round trip.
|
|
1680
|
+
* Picks the effective row limit a tool reads with — the requested count when it sits inside the
|
|
1681
|
+
* cap, the cap when it exceeds it, and `0` when either falls below zero.
|
|
1629
1682
|
*
|
|
1630
1683
|
* @remarks
|
|
1631
|
-
* The
|
|
1632
|
-
*
|
|
1633
|
-
*
|
|
1684
|
+
* The one place the database tool's `'records'` clamp ({@link clampQuery}) and the relation tool's
|
|
1685
|
+
* `'find'` / `'links'` truncation both take their ceiling from, so a negative construction-time
|
|
1686
|
+
* option can never reach a slice. Pure and total.
|
|
1687
|
+
*
|
|
1688
|
+
* @example
|
|
1689
|
+
* ```ts
|
|
1690
|
+
* import { resolveLimit } from '@src/core'
|
|
1691
|
+
*
|
|
1692
|
+
* resolveLimit(undefined, 100) // 100 — an omitted request takes the cap
|
|
1693
|
+
* resolveLimit(500, 100) // 100 — a request over the cap is clamped down
|
|
1694
|
+
* resolveLimit(-1, 100) // 0 — a negative request floors at zero
|
|
1695
|
+
* ```
|
|
1696
|
+
*
|
|
1697
|
+
* @param requested - The caller's requested row count (or `undefined`)
|
|
1698
|
+
* @param cap - The row-count ceiling
|
|
1699
|
+
* @returns The effective row limit, never below `0` and never above `cap`
|
|
1700
|
+
*/
|
|
1701
|
+
function resolveLimit(requested, cap) {
|
|
1702
|
+
return Math.max(0, Math.min(requested ?? cap, cap));
|
|
1703
|
+
}
|
|
1704
|
+
/**
|
|
1705
|
+
* Clamps a `'records'` call's query to a row cap, and builds the probe query the caller reads with
|
|
1706
|
+
* — the pure leaf {@link import('./factories.js').createDatabaseTool}'s `'records'` operation uses
|
|
1707
|
+
* to detect truncation without a separate `count` round trip.
|
|
1708
|
+
*
|
|
1709
|
+
* @remarks
|
|
1710
|
+
* {@link resolveLimit} picks the effective limit, so a caller can never exceed the configured cap
|
|
1711
|
+
* by supplying a larger `query.limit`, and can never drive it below `0`. The returned probe query
|
|
1712
|
+
* requests one more row than the effective limit (`limit: effective + 1`) — if storage returns
|
|
1634
1713
|
* that many, the caller knows the true result was truncated (`rows.length > effective`) and slices
|
|
1635
1714
|
* back down to `effective` before returning.
|
|
1636
1715
|
*
|
|
@@ -1646,10 +1725,10 @@ function queryOf(query) {
|
|
|
1646
1725
|
*
|
|
1647
1726
|
* @param query - The live query to clamp (or `undefined`)
|
|
1648
1727
|
* @param cap - The row-count ceiling
|
|
1649
|
-
* @returns The
|
|
1728
|
+
* @returns The probe query (`limit` bumped by one) and the effective `limit`
|
|
1650
1729
|
*/
|
|
1651
1730
|
function clampQuery(query, cap) {
|
|
1652
|
-
const limit =
|
|
1731
|
+
const limit = resolveLimit(query?.limit, cap);
|
|
1653
1732
|
return {
|
|
1654
1733
|
query: {
|
|
1655
1734
|
...query,
|
|
@@ -1659,28 +1738,77 @@ function clampQuery(query, cap) {
|
|
|
1659
1738
|
};
|
|
1660
1739
|
}
|
|
1661
1740
|
//#endregion
|
|
1741
|
+
//#region src/core/compilers.ts
|
|
1742
|
+
/**
|
|
1743
|
+
* Compiles a {@link TableSpec} into the `@orkestrel/database` {@link TableMap} it configures —
|
|
1744
|
+
* each {@link ColumnSpec} maps to the matching primitive shaper (`'string'` → `stringShape()`,
|
|
1745
|
+
* `'integer'` → `integerShape()`, `'number'` → `numberShape()`, `'boolean'` → `booleanShape()`),
|
|
1746
|
+
* wrapped in `optionalShape` when the column declares `optional: true`. Total, pure.
|
|
1747
|
+
*
|
|
1748
|
+
* @param spec - The small-model-facing table layout
|
|
1749
|
+
* @returns The compiled `TableMap` a `@orkestrel/database` `createDatabase` call accepts
|
|
1750
|
+
*/
|
|
1751
|
+
function expandTables(spec) {
|
|
1752
|
+
const tables = {};
|
|
1753
|
+
for (const [table, definition] of Object.entries(spec)) {
|
|
1754
|
+
const columns = {};
|
|
1755
|
+
for (const [column, columnSpec] of Object.entries(definition.columns)) columns[column] = compileColumn(columnSpec);
|
|
1756
|
+
tables[table] = columns;
|
|
1757
|
+
}
|
|
1758
|
+
return tables;
|
|
1759
|
+
}
|
|
1760
|
+
/**
|
|
1761
|
+
* Compiles one {@link ColumnSpec} into its `@orkestrel/database` column shape — the per-column leaf
|
|
1762
|
+
* {@link expandTables} maps over.
|
|
1763
|
+
*
|
|
1764
|
+
* @param spec - The column spec to compile
|
|
1765
|
+
* @returns The `@orkestrel/database` column shape, wrapped in `optionalShape` when the spec declares `optional: true`
|
|
1766
|
+
*/
|
|
1767
|
+
function compileColumn(spec) {
|
|
1768
|
+
const primitive = isString(spec) ? spec : spec.primitive;
|
|
1769
|
+
const optional = !isString(spec) && spec.optional === true;
|
|
1770
|
+
const shape = compileColumnPrimitive(primitive);
|
|
1771
|
+
return optional ? optionalShape(shape) : shape;
|
|
1772
|
+
}
|
|
1773
|
+
/**
|
|
1774
|
+
* Compiles one {@link import('./types.js').ColumnPrimitive} into its primitive
|
|
1775
|
+
* `@orkestrel/database` shape — the leaf {@link compileColumn} wraps.
|
|
1776
|
+
*
|
|
1777
|
+
* @param primitive - The declared column primitive
|
|
1778
|
+
* @returns The matching primitive `@orkestrel/database` shape
|
|
1779
|
+
*/
|
|
1780
|
+
function compileColumnPrimitive(primitive) {
|
|
1781
|
+
if (primitive === "string") return stringShape();
|
|
1782
|
+
if (primitive === "integer") return integerShape();
|
|
1783
|
+
if (primitive === "number") return numberShape();
|
|
1784
|
+
return booleanShape();
|
|
1785
|
+
}
|
|
1786
|
+
//#endregion
|
|
1662
1787
|
//#region src/core/stores/MemoryDefinitionStore.ts
|
|
1663
1788
|
/**
|
|
1664
|
-
*
|
|
1665
|
-
* {@link DatabaseDefinition}s keyed by database id, the
|
|
1666
|
-
* {@link import('../factories.js').createMemoryDefinitionStore} builds.
|
|
1667
|
-
* {@link
|
|
1789
|
+
* Represents the in-memory {@link DefinitionStoreInterface} — a process-lifetime `Map` of
|
|
1790
|
+
* {@link DatabaseDefinition}s keyed by database id, the default store
|
|
1791
|
+
* {@link import('../factories.js').createMemoryDefinitionStore} builds. It implements the same
|
|
1792
|
+
* {@link DefinitionStoreInterface} contract as
|
|
1793
|
+
* {@link import('./DatabaseDefinitionStore.js').DatabaseDefinitionStore}: this store copies on
|
|
1794
|
+
* write and on read; the table-backed store narrows an untrusted stored blob and reports
|
|
1795
|
+
* `undefined` for a malformed one.
|
|
1668
1796
|
*
|
|
1669
1797
|
* @remarks
|
|
1670
|
-
* A plain `Map<string, DatabaseDefinition>` (
|
|
1671
|
-
* self-contained
|
|
1672
|
-
* structured-cloned on both write and read, keeping caller mutation outside the store. There is
|
|
1673
|
-
* idle-TTL and
|
|
1674
|
-
* backend (JSON / SQLite / IndexedDB) swaps in through the
|
|
1798
|
+
* A plain `Map<string, DatabaseDefinition>` (the definition is already pure,
|
|
1799
|
+
* self-contained config-only JSON, so no encoding is needed for the memory tier). Values are
|
|
1800
|
+
* structured-cloned on both write and read, keeping caller mutation outside the store. There is no
|
|
1801
|
+
* idle-TTL and no eviction: a persisted definition lives until an explicit `delete`. A durable
|
|
1802
|
+
* backend (JSON / SQLite / IndexedDB) swaps in through the same interface without touching a
|
|
1675
1803
|
* consumer — its driver-pluggable twin is
|
|
1676
1804
|
* {@link import('./DatabaseDefinitionStore.js').DatabaseDefinitionStore} (the definition as one
|
|
1677
1805
|
* opaque JSON column).
|
|
1678
1806
|
*
|
|
1679
1807
|
* - **`get` resolves the persisted definition for an id**, or `undefined` if none is stored.
|
|
1680
|
-
* - **`set` inserts / replaces under the definition's
|
|
1808
|
+
* - **`set` inserts / replaces under the definition's own `id`** (no separate id param).
|
|
1681
1809
|
* - **`delete` drops a definition by id**; an absent id is a no-op (no throw).
|
|
1682
1810
|
*
|
|
1683
|
-
* The public surface is
|
|
1811
|
+
* The public surface is exactly `get` / `set` / `delete` — no extra members (the method
|
|
1684
1812
|
* bijection with {@link DefinitionStoreInterface}).
|
|
1685
1813
|
*
|
|
1686
1814
|
* @example
|
|
@@ -1695,14 +1823,32 @@ function clampQuery(query, cap) {
|
|
|
1695
1823
|
*/
|
|
1696
1824
|
var MemoryDefinitionStore = class {
|
|
1697
1825
|
#definitions = /* @__PURE__ */ new Map();
|
|
1826
|
+
/**
|
|
1827
|
+
* Resolves the persisted definition for `id`, copied out of the backing `Map`.
|
|
1828
|
+
*
|
|
1829
|
+
* @param id - The database id to read
|
|
1830
|
+
* @returns The stored {@link DatabaseDefinition}, or `undefined` when none is stored
|
|
1831
|
+
*/
|
|
1698
1832
|
get(id) {
|
|
1699
1833
|
const definition = this.#definitions.get(id);
|
|
1700
1834
|
return Promise.resolve(definition === void 0 ? void 0 : structuredClone(definition));
|
|
1701
1835
|
}
|
|
1836
|
+
/**
|
|
1837
|
+
* Inserts or replaces a definition under its own `id`, copied into the backing `Map`.
|
|
1838
|
+
*
|
|
1839
|
+
* @param definition - The config to persist; its own `id` is the key (no separate id param)
|
|
1840
|
+
* @returns A promise settling after the definition is stored
|
|
1841
|
+
*/
|
|
1702
1842
|
set(definition) {
|
|
1703
1843
|
this.#definitions.set(definition.id, structuredClone(definition));
|
|
1704
1844
|
return Promise.resolve();
|
|
1705
1845
|
}
|
|
1846
|
+
/**
|
|
1847
|
+
* Drops the definition stored under `id`.
|
|
1848
|
+
*
|
|
1849
|
+
* @param id - The database id to drop; an absent id is a no-op, never a throw
|
|
1850
|
+
* @returns A promise settling after no definition is stored under `id`
|
|
1851
|
+
*/
|
|
1706
1852
|
delete(id) {
|
|
1707
1853
|
this.#definitions.delete(id);
|
|
1708
1854
|
return Promise.resolve();
|
|
@@ -1711,34 +1857,34 @@ var MemoryDefinitionStore = class {
|
|
|
1711
1857
|
//#endregion
|
|
1712
1858
|
//#region src/core/stores/DatabaseDefinitionStore.ts
|
|
1713
1859
|
/**
|
|
1714
|
-
*
|
|
1715
|
-
* database's durable
|
|
1860
|
+
* Represents a {@link DefinitionStoreInterface} backed by one table of the `@orkestrel/database` layer — a
|
|
1861
|
+
* database's durable config state is a row, so persistence reduces to keyed point-access
|
|
1716
1862
|
* (`get` / `set` / `delete`) over a {@link TableInterface}, the driver-pluggable twin of the
|
|
1717
1863
|
* plain-`Map` {@link import('./MemoryDefinitionStore.js').MemoryDefinitionStore}.
|
|
1718
1864
|
*
|
|
1719
1865
|
* @remarks
|
|
1720
1866
|
* The store is driver-agnostic: it holds a single {@link TableInterface} whose backend (memory,
|
|
1721
1867
|
* JSON, SQLite, IndexedDB) is chosen by whoever builds it (the factories), so a JSON / SQLite /
|
|
1722
|
-
* IndexedDB backend swaps in
|
|
1868
|
+
* IndexedDB backend swaps in without touching a consumer — the same seam as
|
|
1723
1869
|
* {@link import('./MemoryDefinitionStore.js').MemoryDefinitionStore}. The driver defaults to
|
|
1724
1870
|
* memory ({@link import('../factories.js').createDatabaseDefinitionStore} passes
|
|
1725
|
-
* `createMemoryDriver()`), so it
|
|
1871
|
+
* `createMemoryDriver()`), so it also works in memory out of the box; you opt into the durable
|
|
1726
1872
|
* plumbing by passing a JSON / SQLite / IndexedDB driver.
|
|
1727
1873
|
*
|
|
1728
|
-
* The {@link DatabaseDefinition} is stored as
|
|
1729
|
-
* `{ id; definition }` ({@link DatabaseDefinitionRow}). The definition is already a
|
|
1730
|
-
* self-contained, pure-JSON
|
|
1731
|
-
*
|
|
1874
|
+
* The {@link DatabaseDefinition} is stored as one opaque JSON column — the table is a row of
|
|
1875
|
+
* `{ id; definition }` ({@link DatabaseDefinitionRow}). The definition is already a complete,
|
|
1876
|
+
* self-contained, pure-JSON config payload (never a live handle), so storing it whole is lossless
|
|
1877
|
+
* and keeps the row type flat (`definition` reads back as `unknown`).
|
|
1732
1878
|
*
|
|
1733
|
-
* - **`set(definition)` upserts under the definition's
|
|
1879
|
+
* - **`set(definition)` upserts under the definition's own `id`** (no separate id param) — it
|
|
1734
1880
|
* writes the row `{ id: definition.id, definition }`.
|
|
1735
1881
|
* - **`get(id)` resolves the stored definition for an id**, narrowing the opaque JSON column back
|
|
1736
|
-
* to a {@link DatabaseDefinition} ({@link import('../
|
|
1737
|
-
*
|
|
1882
|
+
* to a {@link DatabaseDefinition} ({@link import('../validators.js').isDatabaseDefinition} — the
|
|
1883
|
+
* boundary narrow for an untrusted storage read), or `undefined` if none is stored
|
|
1738
1884
|
* or the stored blob is malformed.
|
|
1739
1885
|
* - **`delete(id)` drops a definition by id**; an absent id is a no-op (no throw).
|
|
1740
1886
|
*
|
|
1741
|
-
* The public surface is
|
|
1887
|
+
* The public surface is exactly `get` / `set` / `delete` — no extra members (the method
|
|
1742
1888
|
* bijection with {@link DefinitionStoreInterface}).
|
|
1743
1889
|
*
|
|
1744
1890
|
* @example
|
|
@@ -1754,7 +1900,7 @@ var MemoryDefinitionStore = class {
|
|
|
1754
1900
|
var DatabaseDefinitionStore = class {
|
|
1755
1901
|
#table;
|
|
1756
1902
|
/**
|
|
1757
|
-
*
|
|
1903
|
+
* Wraps a table as a definition store.
|
|
1758
1904
|
*
|
|
1759
1905
|
* @param table - The {@link TableInterface} holding the definitions — its row is the
|
|
1760
1906
|
* {@link DatabaseDefinitionRow} `{ id; definition }` shape (the definition one opaque JSON column)
|
|
@@ -1762,20 +1908,37 @@ var DatabaseDefinitionStore = class {
|
|
|
1762
1908
|
constructor(table) {
|
|
1763
1909
|
this.#table = table;
|
|
1764
1910
|
}
|
|
1765
|
-
/**
|
|
1911
|
+
/**
|
|
1912
|
+
* Resolves the persisted definition for `id`, narrowing the opaque JSON column back to a
|
|
1913
|
+
* {@link DatabaseDefinition}.
|
|
1914
|
+
*
|
|
1915
|
+
* @param id - The database id to read
|
|
1916
|
+
* @returns The stored {@link DatabaseDefinition}, or `undefined` when none is stored or the
|
|
1917
|
+
* stored blob is malformed
|
|
1918
|
+
*/
|
|
1766
1919
|
async get(id) {
|
|
1767
1920
|
const row = await this.#table.get(id);
|
|
1768
1921
|
if (row === void 0) return void 0;
|
|
1769
1922
|
return isDatabaseDefinition(row.definition) ? row.definition : void 0;
|
|
1770
1923
|
}
|
|
1771
|
-
/**
|
|
1924
|
+
/**
|
|
1925
|
+
* Inserts or replaces a definition under its own `id` — the written row is `{ id, definition }`.
|
|
1926
|
+
*
|
|
1927
|
+
* @param definition - The config to persist; its own `id` is the row key (no separate id param)
|
|
1928
|
+
* @returns A promise settling after the row is written
|
|
1929
|
+
*/
|
|
1772
1930
|
async set(definition) {
|
|
1773
1931
|
await this.#table.set({
|
|
1774
1932
|
id: definition.id,
|
|
1775
1933
|
definition
|
|
1776
1934
|
});
|
|
1777
1935
|
}
|
|
1778
|
-
/**
|
|
1936
|
+
/**
|
|
1937
|
+
* Drops the definition stored under `id`.
|
|
1938
|
+
*
|
|
1939
|
+
* @param id - The database id to drop; an absent id is a no-op, never a throw
|
|
1940
|
+
* @returns A promise settling after no row is stored under `id`
|
|
1941
|
+
*/
|
|
1779
1942
|
async delete(id) {
|
|
1780
1943
|
await this.#table.remove(id);
|
|
1781
1944
|
}
|
|
@@ -1783,7 +1946,8 @@ var DatabaseDefinitionStore = class {
|
|
|
1783
1946
|
//#endregion
|
|
1784
1947
|
//#region src/core/databases/DatabaseResolver.ts
|
|
1785
1948
|
/**
|
|
1786
|
-
*
|
|
1949
|
+
* Resolves a database definition into a cached live handle for a database tool, over the tool's
|
|
1950
|
+
* live handles, its stored definitions, its driver registry, and an optional key generator.
|
|
1787
1951
|
*
|
|
1788
1952
|
* @example
|
|
1789
1953
|
* ```ts
|
|
@@ -1799,7 +1963,7 @@ var DatabaseResolver = class {
|
|
|
1799
1963
|
#generator;
|
|
1800
1964
|
#store;
|
|
1801
1965
|
/**
|
|
1802
|
-
*
|
|
1966
|
+
* Creates a database resolver over the tool's live state and optional definition store.
|
|
1803
1967
|
*
|
|
1804
1968
|
* @param handles - Initial live database handles cached by id
|
|
1805
1969
|
* @param drivers - Driver factories keyed by definition driver name
|
|
@@ -1813,16 +1977,16 @@ var DatabaseResolver = class {
|
|
|
1813
1977
|
this.#store = store;
|
|
1814
1978
|
}
|
|
1815
1979
|
/**
|
|
1816
|
-
*
|
|
1980
|
+
* Determines whether a live database is cached by id.
|
|
1817
1981
|
*
|
|
1818
1982
|
* @param id - Database id
|
|
1819
|
-
* @returns
|
|
1983
|
+
* @returns True if a live handle is cached; false otherwise
|
|
1820
1984
|
*/
|
|
1821
1985
|
has(id) {
|
|
1822
1986
|
return this.#handles.has(id);
|
|
1823
1987
|
}
|
|
1824
1988
|
/**
|
|
1825
|
-
*
|
|
1989
|
+
* Reads a cached database without consulting the definition store.
|
|
1826
1990
|
*
|
|
1827
1991
|
* @param id - Database id
|
|
1828
1992
|
* @returns The cached live database, or `undefined`
|
|
@@ -1831,7 +1995,7 @@ var DatabaseResolver = class {
|
|
|
1831
1995
|
return this.#handles.get(id);
|
|
1832
1996
|
}
|
|
1833
1997
|
/**
|
|
1834
|
-
*
|
|
1998
|
+
* Caches a live database by id.
|
|
1835
1999
|
*
|
|
1836
2000
|
* @param id - Database id
|
|
1837
2001
|
* @param database - Live database handle
|
|
@@ -1841,7 +2005,7 @@ var DatabaseResolver = class {
|
|
|
1841
2005
|
this.#handles.set(id, database);
|
|
1842
2006
|
}
|
|
1843
2007
|
/**
|
|
1844
|
-
*
|
|
2008
|
+
* Removes a cached live database by id.
|
|
1845
2009
|
*
|
|
1846
2010
|
* @param id - Database id
|
|
1847
2011
|
* @returns Nothing
|
|
@@ -1850,7 +2014,8 @@ var DatabaseResolver = class {
|
|
|
1850
2014
|
this.#handles.delete(id);
|
|
1851
2015
|
}
|
|
1852
2016
|
/**
|
|
1853
|
-
*
|
|
2017
|
+
* Resolves a live database by id — the cached handle when one exists, otherwise a database
|
|
2018
|
+
* constructed from its stored definition.
|
|
1854
2019
|
*
|
|
1855
2020
|
* @param id - Database definition id
|
|
1856
2021
|
* @returns The cached or newly constructed live database
|
|
@@ -1885,8 +2050,8 @@ var DatabaseResolver = class {
|
|
|
1885
2050
|
//#endregion
|
|
1886
2051
|
//#region src/core/factories.ts
|
|
1887
2052
|
/**
|
|
1888
|
-
*
|
|
1889
|
-
* adapter that lets a `function`-form task run a `@orkestrel/tool` tool
|
|
2053
|
+
* Wraps a registered tool as a {@link WorkflowFunction} (`@orkestrel/workflow`) — the opt-in
|
|
2054
|
+
* adapter that lets a `function`-form task run a `@orkestrel/tool` tool by name.
|
|
1890
2055
|
*
|
|
1891
2056
|
* @remarks
|
|
1892
2057
|
* Composes into a caller's `WorkflowOptions.functions` registry like any other behavior
|
|
@@ -1924,7 +2089,7 @@ function createToolFunction(tools, name) {
|
|
|
1924
2089
|
};
|
|
1925
2090
|
}
|
|
1926
2091
|
/**
|
|
1927
|
-
*
|
|
2092
|
+
* Wraps a live `AgentInterface` (`@orkestrel/agent`) as a {@link WorkflowFunction}
|
|
1928
2093
|
* (`@orkestrel/workflow`) — the opt-in adapter that runs the agent to a settled result and carries
|
|
1929
2094
|
* immutable lineage metadata for contextual Toolbox composition.
|
|
1930
2095
|
*
|
|
@@ -1932,7 +2097,7 @@ function createToolFunction(tools, name) {
|
|
|
1932
2097
|
* Composes into a caller's registry like any other behavior; the pure workflow runner has no
|
|
1933
2098
|
* knowledge of agents itself. A root adapter derives its workflow lineage from the controller,
|
|
1934
2099
|
* while a contextual adapter requires an exact match. Repeated agents, repeated workflows, and
|
|
1935
|
-
* targets deeper than {@link import('./constants.js').
|
|
2100
|
+
* targets deeper than {@link import('./constants.js').MAX_WORKFLOW_CHAIN} are rejected before
|
|
1936
2101
|
* agent or runner activity. When {@link import('./types.js').AgentFunctionOptions.runner} is
|
|
1937
2102
|
* supplied, the adapter binds a recursion-safe {@link createWorkflowTool} onto the agent's
|
|
1938
2103
|
* `context.tools`, propagating opaque leaves, raw agents, lineage, and the native store. The
|
|
@@ -1960,12 +2125,12 @@ function createToolFunction(tools, name) {
|
|
|
1960
2125
|
* ```
|
|
1961
2126
|
*/
|
|
1962
2127
|
function createAgentFunction(agent, options) {
|
|
1963
|
-
const lineage =
|
|
2128
|
+
const lineage = normalizeLineage(options?.lineage);
|
|
1964
2129
|
if (lineage.length > 0 && !lineage.at(-1)?.startsWith("workflow:")) throw new ToolboxError("TOOL", "agent function lineage must end with a workflow tag");
|
|
1965
2130
|
const functions = options?.functions === void 0 ? void 0 : Object.freeze({ ...options.functions });
|
|
1966
2131
|
const agents = options?.agents === void 0 ? void 0 : Object.freeze({ ...options.agents });
|
|
1967
2132
|
return Object.freeze(Object.assign(async (controller) => {
|
|
1968
|
-
const workflow =
|
|
2133
|
+
const workflow = tagWorkflow(controller.task.phase.workflow.id);
|
|
1969
2134
|
const current = lineage.length === 0 ? extendLineage(lineage, workflow) : lineage;
|
|
1970
2135
|
if (current.at(-1) !== workflow) throw new ToolboxError("DEPTH", "agent function workflow does not match its lineage", {
|
|
1971
2136
|
workflow,
|
|
@@ -1977,7 +2142,7 @@ function createAgentFunction(agent, options) {
|
|
|
1977
2142
|
depth,
|
|
1978
2143
|
max: 8
|
|
1979
2144
|
});
|
|
1980
|
-
const tag =
|
|
2145
|
+
const tag = tagAgent(agent.id);
|
|
1981
2146
|
if (current.includes(tag)) throw new ToolboxError("DEPTH", `agent '${agent.id}' is already an ancestor (cycle)`, {
|
|
1982
2147
|
agent: agent.id,
|
|
1983
2148
|
lineage: [...current]
|
|
@@ -2009,7 +2174,7 @@ function createAgentFunction(agent, options) {
|
|
|
2009
2174
|
}));
|
|
2010
2175
|
}
|
|
2011
2176
|
/**
|
|
2012
|
-
*
|
|
2177
|
+
* Composes opaque host functions and raw agents into one immutable workflow registry.
|
|
2013
2178
|
*
|
|
2014
2179
|
* @remarks
|
|
2015
2180
|
* This is the target-aware composition boundary used by direct runner consumers and
|
|
@@ -2036,7 +2201,7 @@ function createAgentFunction(agent, options) {
|
|
|
2036
2201
|
* ```
|
|
2037
2202
|
*/
|
|
2038
2203
|
function createWorkflowFunctions(runner, options) {
|
|
2039
|
-
const lineage =
|
|
2204
|
+
const lineage = normalizeLineage(options?.lineage);
|
|
2040
2205
|
if (lineage.length > 0 && !lineage.at(-1)?.startsWith("workflow:")) throw new ToolboxError("TOOL", "workflow functions lineage must end with a workflow tag");
|
|
2041
2206
|
const functions = options?.functions === void 0 ? Object.freeze({}) : Object.freeze({ ...options.functions });
|
|
2042
2207
|
const agents = options?.agents === void 0 ? Object.freeze({}) : Object.freeze({ ...options.agents });
|
|
@@ -2060,7 +2225,9 @@ function createWorkflowFunctions(runner, options) {
|
|
|
2060
2225
|
return Object.freeze(composed);
|
|
2061
2226
|
}
|
|
2062
2227
|
/**
|
|
2063
|
-
*
|
|
2228
|
+
* Compiles the lenient workflow draft contract {@link createWorkflowTool} parses an authored tree
|
|
2229
|
+
* with — a `ContractInterface` over a {@link WorkflowDraft}, whose `id` and `name` are optional at
|
|
2230
|
+
* the workflow, phase, and task levels.
|
|
2064
2231
|
*
|
|
2065
2232
|
* @returns The compiled {@link import('./types.js').WorkflowDraft} contract
|
|
2066
2233
|
*/
|
|
@@ -2068,34 +2235,33 @@ function createWorkflowDraftContract() {
|
|
|
2068
2235
|
return createContract(workflowDraftShape);
|
|
2069
2236
|
}
|
|
2070
2237
|
/**
|
|
2071
|
-
*
|
|
2072
|
-
*
|
|
2073
|
-
*
|
|
2074
|
-
*
|
|
2075
|
-
* named functions and native checkpoint store.
|
|
2238
|
+
* Wraps a {@link WorkflowDefinition} as an LLM-callable tool — it advertises the flat authoring
|
|
2239
|
+
* shape (`{ name?, steps: [{ name }] }`) as its `parameters`, and its handler completes the
|
|
2240
|
+
* authored blob, validates it against the strict contract, and runs it through `runner`, forwarding
|
|
2241
|
+
* the caller's optional named functions and native checkpoint store.
|
|
2076
2242
|
*
|
|
2077
2243
|
* @remarks
|
|
2078
2244
|
* A plain `ToolManagerInterface`-compatible tool (`@orkestrel/tool`), reproducing
|
|
2079
2245
|
* Toolbox's flat / draft / full authoring contract, strict soundness gate, and lineage guard.
|
|
2080
|
-
* It is
|
|
2246
|
+
* It is also the propagation carrier
|
|
2081
2247
|
* {@link createAgentFunction} binds onto a wrapped agent's `context.tools`: because a tool
|
|
2082
2248
|
* handler receives only the model-supplied `args`, the immutable lineage is closed over at bind
|
|
2083
2249
|
* time. The authored target is appended and its zero-based depth derived from workflow tags.
|
|
2084
2250
|
*
|
|
2085
|
-
* **Widened authoring surface (additive — the canonical contract
|
|
2086
|
-
* unchanged).** A 2B model reliably
|
|
2087
|
-
*
|
|
2088
|
-
* So the tool
|
|
2251
|
+
* **Widened authoring surface (additive — the canonical contract and runner stay strict and
|
|
2252
|
+
* unchanged).** A 2B model reliably calls the tool but cannot reliably emit the full nested
|
|
2253
|
+
* {@link WorkflowDefinition}, whose every `id` and `name` is required across an all-or-nothing
|
|
2254
|
+
* tree. So the tool accepts the authoring forms that follow and converges them on the same strict
|
|
2089
2255
|
* `createWorkflowContract` gate before running (soundness preserved):
|
|
2090
|
-
* - the
|
|
2256
|
+
* - the flat shape `{ name?, steps: [{ name }] }` — the advertised `parameters` (the simplest
|
|
2091
2257
|
* form, {@link import('./helpers.js').expandSteps}'d into one one-task phase per step);
|
|
2092
|
-
* - a nested
|
|
2258
|
+
* - a nested draft with any `id`/`name` omitted — {@link createWorkflowDraftContract}-parsed then
|
|
2093
2259
|
* {@link import('./helpers.js').completeDraft}'d (missing ids synthesized positionally);
|
|
2094
2260
|
* - the full nested {@link WorkflowDefinition} — the advanced escape-hatch, accepted as the draft
|
|
2095
2261
|
* super-set.
|
|
2096
2262
|
*
|
|
2097
|
-
* The universal tool-handler contract
|
|
2098
|
-
* (`{ status, count, durable?, fault? }`) on success,
|
|
2263
|
+
* The universal tool-handler contract: returns the plain run summary
|
|
2264
|
+
* (`{ status, count, durable?, fault? }`) on success, throws a typed `ToolboxError` on Toolbox
|
|
2099
2265
|
* boundary failures — malformed authored args (`TOOL`), or an over-deep / cyclic nested run
|
|
2100
2266
|
* (`DEPTH`) — and preserves genuine runner `WorkflowError`s. The `ToolManagerInterface` isolates every throw into the canonical tool result's
|
|
2101
2267
|
* top-level `error`, so nothing escapes the run. `options.lineage` is the propagation carrier
|
|
@@ -2109,6 +2275,34 @@ function createWorkflowDraftContract() {
|
|
|
2109
2275
|
* @returns A `ToolInterface` (named {@link import('./constants.js').WORKFLOW_TOOL_NAME}) whose
|
|
2110
2276
|
* `parameters` advertise the flat authoring schema
|
|
2111
2277
|
*
|
|
2278
|
+
* @example Authoring and running a workflow through the tool with a real ToolManager
|
|
2279
|
+
* ```ts
|
|
2280
|
+
* import { createWorkflowTool } from '@orkestrel/toolbox'
|
|
2281
|
+
* import { createToolManager } from '@orkestrel/tool'
|
|
2282
|
+
* import { createWorkflowRunner } from '@orkestrel/workflow'
|
|
2283
|
+
* import type { WorkflowDefinition } from '@orkestrel/workflow'
|
|
2284
|
+
*
|
|
2285
|
+
* const definition: WorkflowDefinition = { id: 'release', name: 'Release', phases: [] }
|
|
2286
|
+
* const runner = createWorkflowRunner()
|
|
2287
|
+
* const functions = {
|
|
2288
|
+
* compile: () => 'compiled',
|
|
2289
|
+
* publish: () => 'published',
|
|
2290
|
+
* }
|
|
2291
|
+
* const tool = createWorkflowTool(definition, runner, { functions })
|
|
2292
|
+
*
|
|
2293
|
+
* const tools = createToolManager()
|
|
2294
|
+
* tools.add(tool)
|
|
2295
|
+
*
|
|
2296
|
+
* // A small model authors the simple flat shape — no ids/names required.
|
|
2297
|
+
* const result = await tools.execute({
|
|
2298
|
+
* id: 'call-1',
|
|
2299
|
+
* name: 'workflow',
|
|
2300
|
+
* arguments: { name: 'release', steps: [{ name: 'compile' }, { name: 'publish' }] },
|
|
2301
|
+
* })
|
|
2302
|
+
* if (!result.success) throw new Error(result.error)
|
|
2303
|
+
* result.value // { status: 'completed', count: 2 } — the single-level envelope; no nested { id, name, value }
|
|
2304
|
+
* ```
|
|
2305
|
+
*
|
|
2112
2306
|
* @example
|
|
2113
2307
|
* ```ts
|
|
2114
2308
|
* import { createWorkflowTool } from '@src/core'
|
|
@@ -2126,7 +2320,7 @@ function createWorkflowTool(definition, runner, options) {
|
|
|
2126
2320
|
const strict = createWorkflowContract();
|
|
2127
2321
|
const draft = createWorkflowDraftContract();
|
|
2128
2322
|
const steps = createContract(workflowStepsShape);
|
|
2129
|
-
const lineage =
|
|
2323
|
+
const lineage = normalizeLineage(options?.lineage);
|
|
2130
2324
|
if (lineage.length > 0 && !lineage.at(-1)?.startsWith("agent:")) throw new ToolboxError("TOOL", "workflow tool lineage must end with an agent tag");
|
|
2131
2325
|
const functions = options?.functions === void 0 ? void 0 : Object.freeze({ ...options.functions });
|
|
2132
2326
|
const agents = options?.agents === void 0 ? void 0 : Object.freeze({ ...options.agents });
|
|
@@ -2151,7 +2345,7 @@ function createWorkflowTool(definition, runner, options) {
|
|
|
2151
2345
|
target = parsed === void 0 ? void 0 : completeDraft(parsed);
|
|
2152
2346
|
}
|
|
2153
2347
|
if (target === void 0 || !strict.is(target)) throw new ToolboxError("TOOL", "malformed workflow definition", { workflow: definition.id });
|
|
2154
|
-
const tag =
|
|
2348
|
+
const tag = tagWorkflow(target.id);
|
|
2155
2349
|
if (lineage.includes(tag)) throw new ToolboxError("DEPTH", `workflow '${target.id}' is already an ancestor (cycle)`, {
|
|
2156
2350
|
workflow: target.id,
|
|
2157
2351
|
lineage: [...lineage]
|
|
@@ -2169,7 +2363,7 @@ function createWorkflowTool(definition, runner, options) {
|
|
|
2169
2363
|
...agents === void 0 ? {} : { agents },
|
|
2170
2364
|
...store === void 0 ? {} : { store }
|
|
2171
2365
|
});
|
|
2172
|
-
return
|
|
2366
|
+
return summarizeWorkflow(await runner.execute(target, {
|
|
2173
2367
|
...registry === void 0 ? {} : { functions: registry },
|
|
2174
2368
|
...store === void 0 ? {} : { store }
|
|
2175
2369
|
}));
|
|
@@ -2177,42 +2371,35 @@ function createWorkflowTool(definition, runner, options) {
|
|
|
2177
2371
|
});
|
|
2178
2372
|
}
|
|
2179
2373
|
/**
|
|
2180
|
-
*
|
|
2181
|
-
*
|
|
2182
|
-
*
|
|
2183
|
-
*
|
|
2184
|
-
* returning the plain result. A malformed operation throws this package's `ToolboxError`; a
|
|
2185
|
-
* genuine workspace-domain failure propagates `@orkestrel/workspace`'s typed `WorkspaceError`.
|
|
2186
|
-
* EITHER drives a caller-supplied {@link WorkspaceToolOptions.manager} directly, OR
|
|
2187
|
-
* constructs a fresh `WorkspaceManagerInterface` (`@orkestrel/workspace`) over
|
|
2188
|
-
* {@link import('./types.js').WorkspaceToolOptions.store} (via `@orkestrel/workspace`'s
|
|
2189
|
-
* `createWorkspaceManager`); neither given constructs a manager backed by
|
|
2190
|
-
* `@orkestrel/workspace`'s in-memory store default.
|
|
2374
|
+
* Builds an LLM-callable workspace-editing tool — it advertises the `operation`-discriminated union
|
|
2375
|
+
* ({@link import('./shapers.js').workspaceToolShape}) as its `parameters`, and its handler parses
|
|
2376
|
+
* the model-supplied args against that contract and dispatches the matched operation against the
|
|
2377
|
+
* manager's active workspace, returning the plain result.
|
|
2191
2378
|
*
|
|
2192
2379
|
* @remarks
|
|
2193
|
-
*
|
|
2380
|
+
* Manager-driven: every edit / read op (read / list / has / search / replace / write / splice /
|
|
2194
2381
|
* prepend / append / move / remove) targets `manager.active`, so the model edits whichever
|
|
2195
2382
|
* workspace is active and a host can re-point it (`WorkspaceManagerInterface.switch`) between
|
|
2196
|
-
* turns.
|
|
2383
|
+
* turns. The registry ops make the model self-sufficient: `workspaces` lists the registered
|
|
2197
2384
|
* workspaces (each `{ id, files, active }`) so it can discover an id, and `switch` re-points the
|
|
2198
2385
|
* active workspace by id (lenient — an unknown id is a no-op reporting `switched: false`, never a
|
|
2199
2386
|
* throw).
|
|
2200
2387
|
*
|
|
2201
|
-
*
|
|
2202
|
-
* remove / replace) run when `manager.active` is `undefined`
|
|
2203
|
-
* workspace (`manager.add()`) so the model can
|
|
2204
|
-
* has / search) against no active workspace returns the
|
|
2388
|
+
* No-active rule (the ergonomic seam): a writing op (write / splice / prepend / append / move /
|
|
2389
|
+
* remove / replace) run when `manager.active` is `undefined` auto-creates and activates a default
|
|
2390
|
+
* workspace (`manager.add()`) so the model can start writing; a pure-read op (read / list /
|
|
2391
|
+
* has / search) against no active workspace returns the empty result (`undefined` / `[]` /
|
|
2205
2392
|
* `false`), never creating one and never throwing.
|
|
2206
2393
|
*
|
|
2207
|
-
* The handler conforms to the universal tool-handler contract
|
|
2208
|
-
* the args,
|
|
2209
|
-
* operation), else `switch`es on `op.operation` and
|
|
2210
|
-
* `WorkspaceError` raised by the live workspace (`MODALITY` / `PATTERN` / `RANGE`)
|
|
2211
|
-
* uncaught. The range edit is the
|
|
2394
|
+
* The handler conforms to the universal tool-handler contract: it `contract.parse`s
|
|
2395
|
+
* the args, throws a `TOOL` `ToolboxError` when no operation arm matched (a malformed / unknown
|
|
2396
|
+
* operation), else `switch`es on `op.operation` and returns the plain result — letting a
|
|
2397
|
+
* `WorkspaceError` raised by the live workspace (`MISSING` / `MODALITY` / `PATTERN` / `RANGE`)
|
|
2398
|
+
* propagate uncaught. The range edit is the flat `'splice'` op: its flat caret integers are
|
|
2212
2399
|
* reassembled into a `Range` (`@orkestrel/workspace`) by `rangeOf` and fed to the workspace's
|
|
2213
2400
|
* ranged `write`.
|
|
2214
2401
|
*
|
|
2215
|
-
* @param options - `manager` (drive directly)
|
|
2402
|
+
* @param options - `manager` (drive directly) or `store` (build a manager over it); neither ⇒
|
|
2216
2403
|
* an in-memory-backed manager (see {@link import('./types.js').WorkspaceToolOptions})
|
|
2217
2404
|
* @returns A `ToolInterface` (named {@link import('./constants.js').WORKSPACE_TOOL_NAME} by default)
|
|
2218
2405
|
*
|
|
@@ -2330,19 +2517,19 @@ function createWorkspaceTool(options) {
|
|
|
2330
2517
|
});
|
|
2331
2518
|
}
|
|
2332
2519
|
/**
|
|
2333
|
-
*
|
|
2334
|
-
* from `registry` and runs it to completion for
|
|
2520
|
+
* Builds an LLM-callable sub-agent delegation tool — it resolves a live, seeded `AgentInterface`
|
|
2521
|
+
* from `registry` and runs it to completion for one delegated `task`.
|
|
2335
2522
|
*
|
|
2336
2523
|
* @remarks
|
|
2337
|
-
* The universal tool-handler contract
|
|
2524
|
+
* The universal tool-handler contract: validates the call args against
|
|
2338
2525
|
* {@link import('./shapers.js').agentToolShape}, assembles an `AgentJobInput` (`task` seeds the
|
|
2339
2526
|
* sub-agent's conversation as a single `user` message; `provider` / `tools` / `system` fall
|
|
2340
|
-
* back to the tool's own {@link import('./types.js').AgentToolOptions} defaults), rehydrates the sub-agent
|
|
2527
|
+
* back to the tool's own {@link import('./types.js').AgentToolOptions} defaults), rehydrates the sub-agent through
|
|
2341
2528
|
* `registry.build`, runs it with `agent.generate()`, and returns the settled
|
|
2342
|
-
* `AgentResult.content` string (the sub-agent's final text). A missing / unresolvable `provider`, or a malformed call,
|
|
2529
|
+
* `AgentResult.content` string (the sub-agent's final text). A missing / unresolvable `provider`, or a malformed call, throws a typed `TOOL`
|
|
2343
2530
|
* {@link import('./errors.js').ToolboxError}; a delegation that would exceed
|
|
2344
2531
|
* {@link import('./constants.js').AGENT_TOOL_DEPTH}, or re-enter an already-delegated agent (a
|
|
2345
|
-
* cycle),
|
|
2532
|
+
* cycle), throws a typed `DEPTH` {@link import('./errors.js').ToolboxError}, each isolated
|
|
2346
2533
|
* by the `ToolManagerInterface` into the canonical tool result's top-level `error`.
|
|
2347
2534
|
*
|
|
2348
2535
|
* `AgentInterface` (`@orkestrel/agent`) exposes no teardown method — a bound sub-agent's
|
|
@@ -2388,7 +2575,7 @@ function createAgentTool(registry, options) {
|
|
|
2388
2575
|
depth,
|
|
2389
2576
|
max: 8
|
|
2390
2577
|
});
|
|
2391
|
-
const tag =
|
|
2578
|
+
const tag = tagAgent(provider);
|
|
2392
2579
|
if (ancestry.includes(tag)) throw new ToolboxError("DEPTH", `agent '${provider}' is already an ancestor (cycle)`, {
|
|
2393
2580
|
provider,
|
|
2394
2581
|
ancestry: [...ancestry]
|
|
@@ -2414,21 +2601,20 @@ function createAgentTool(registry, options) {
|
|
|
2414
2601
|
});
|
|
2415
2602
|
}
|
|
2416
2603
|
/**
|
|
2417
|
-
*
|
|
2418
|
-
* name — the counterpart to the lean `summary` the other tools in this package advertise
|
|
2419
|
-
* (`AGENT_TOOL_SUMMARY` / `WORKFLOW_TOOL_SUMMARY` / `WORKSPACE_TOOL_SUMMARY`).
|
|
2604
|
+
* Builds an LLM-callable tool that returns the full `description` of another registered tool by
|
|
2605
|
+
* name — the counterpart to the lean `summary` the other tools in this package advertise.
|
|
2420
2606
|
*
|
|
2421
2607
|
* @remarks
|
|
2422
2608
|
* `ToolManagerInterface.definitions()` (`@orkestrel/tool`) advertises `tool.summary ??
|
|
2423
2609
|
* tool.description` — a lean one-sentence summary stands in for a tool's full teaching
|
|
2424
2610
|
* description when `summary` is set, keeping the advertised tool list compact for a small model.
|
|
2425
2611
|
* This tool is the on-demand expansion seam: given a registered tool's `name`, it looks the tool
|
|
2426
|
-
* up
|
|
2612
|
+
* up through `tools.tool(name)` and returns its full `description` (falling back to `summary` when a
|
|
2427
2613
|
* tool has no `description` of its own, then a placeholder when it has neither).
|
|
2428
2614
|
*
|
|
2429
|
-
* The universal tool-handler contract
|
|
2430
|
-
* {@link import('./shapers.js').describeToolShape},
|
|
2431
|
-
* success,
|
|
2615
|
+
* The universal tool-handler contract: validates the call args against
|
|
2616
|
+
* {@link import('./shapers.js').describeToolShape}, returns the plain description string on
|
|
2617
|
+
* success, throws a typed `TOOL` {@link import('./errors.js').ToolboxError} on a malformed call
|
|
2432
2618
|
* or an unknown tool name.
|
|
2433
2619
|
*
|
|
2434
2620
|
* @param tools - The `ToolManagerInterface` (`@orkestrel/tool`) whose registered tools this
|
|
@@ -2465,20 +2651,23 @@ function createDescribeTool(tools) {
|
|
|
2465
2651
|
});
|
|
2466
2652
|
}
|
|
2467
2653
|
/**
|
|
2468
|
-
*
|
|
2469
|
-
*
|
|
2654
|
+
* Builds an LLM-callable form tool — the ask side of the terminal seam. It asks a multi-field form
|
|
2655
|
+
* and blocks until the addressed terminal answers, returning the resolved values record.
|
|
2470
2656
|
*
|
|
2471
2657
|
* @remarks
|
|
2472
|
-
* The universal tool-handler contract
|
|
2658
|
+
* The universal tool-handler contract: validates the call args against
|
|
2473
2659
|
* {@link import('./shapers.js').promptToolShape}, parses the call's schema through
|
|
2474
2660
|
* `@orkestrel/form`, constructs the live form, and passes it to `TerminalManagerInterface.ask`.
|
|
2475
|
-
* `from` is
|
|
2661
|
+
* `from` is fixed at construction
|
|
2476
2662
|
* ({@link import('./types.js').PromptToolOptions.from}) — never read from the model-supplied
|
|
2477
|
-
* args — so a model cannot spoof which terminal is asking. A form
|
|
2663
|
+
* args — so a model cannot spoof which terminal is asking. A form cycle rejects with
|
|
2478
2664
|
* `TerminalError('DEADLOCK')`, re-surfaced as a typed `DEADLOCK`
|
|
2479
2665
|
* {@link import('./errors.js').ToolboxError}; an expired form re-surfaces as `EXPIRE`; an
|
|
2480
2666
|
* unknown `to` (or any other `TerminalError`) re-surfaces as `TOOL`, naming the unknown terminal
|
|
2481
|
-
*
|
|
2667
|
+
* and how many brokers the manager holds (`manager.count`). A schema `parseForm` refuses — an
|
|
2668
|
+
* unknown control, a duplicate field name, a `'select'` or `'checkbox'` field without usable
|
|
2669
|
+
* `choices` — throws a typed `TOOL` error up front, so a malformed schema never parks as a form
|
|
2670
|
+
* nobody can answer.
|
|
2482
2671
|
*
|
|
2483
2672
|
* @param options - The live manager, the fixed `from` identity, and advertised overrides (see
|
|
2484
2673
|
* {@link import('./types.js').PromptToolOptions})
|
|
@@ -2519,7 +2708,7 @@ function createPromptTool(options) {
|
|
|
2519
2708
|
return await options.manager.ask(options.from, call.to, createForm(schema));
|
|
2520
2709
|
} catch (error) {
|
|
2521
2710
|
if (isFormError(error) && error.code === "ABANDONED") throw new ToolboxError("EXPIRE", `form to '${call.to}' expired before it was answered`, { to: call.to });
|
|
2522
|
-
const code =
|
|
2711
|
+
const code = inferTerminalCode(error);
|
|
2523
2712
|
if (code === void 0) throw error;
|
|
2524
2713
|
if (code === "DEADLOCK") throw new ToolboxError("DEADLOCK", `asking '${call.to}' would create a form cycle`, isTerminalError(error) ? error.context : {
|
|
2525
2714
|
from: options.from,
|
|
@@ -2528,7 +2717,7 @@ function createPromptTool(options) {
|
|
|
2528
2717
|
if (code === "EXPIRE") throw new ToolboxError("EXPIRE", `form to '${call.to}' expired before it was answered`, { to: call.to });
|
|
2529
2718
|
if (isTerminalError(error) && error.code === "TARGET") throw new ToolboxError("TOOL", `unknown terminal '${call.to}'`, {
|
|
2530
2719
|
to: call.to,
|
|
2531
|
-
|
|
2720
|
+
count: options.manager.count
|
|
2532
2721
|
});
|
|
2533
2722
|
throw new ToolboxError("TOOL", `asking '${call.to}' failed`, { to: call.to });
|
|
2534
2723
|
}
|
|
@@ -2536,22 +2725,21 @@ function createPromptTool(options) {
|
|
|
2536
2725
|
});
|
|
2537
2726
|
}
|
|
2538
2727
|
/**
|
|
2539
|
-
*
|
|
2540
|
-
*
|
|
2541
|
-
* them by id.
|
|
2728
|
+
* Builds an LLM-callable answer tool — the answer side of the terminal seam. It lists the forms
|
|
2729
|
+
* addressed to {@link import('./types.js').AnswerToolOptions.to}, or answers one of them by id.
|
|
2542
2730
|
*
|
|
2543
2731
|
* @remarks
|
|
2544
|
-
* The universal tool-handler contract
|
|
2732
|
+
* The universal tool-handler contract: validates the call args against
|
|
2545
2733
|
* {@link import('./shapers.js').answerToolShape} (discriminated by `operation`). `'pending'`
|
|
2546
|
-
* returns a compact list (`{ id, from, schema }`) of every form
|
|
2734
|
+
* returns a compact list (`{ id, from, schema }`) of every form addressed to `to`.
|
|
2547
2735
|
* `'answer'` looks the form up by `id`, narrows the supplied `values` through `@orkestrel/form`,
|
|
2548
|
-
* and applies it
|
|
2736
|
+
* and applies it through
|
|
2549
2737
|
* `TerminalManagerInterface.answer` — a rejected / unknown / unresolvable outcome
|
|
2550
2738
|
* (`TerminalAnswerResult.error`) re-surfaces as a typed `ANSWER` `ToolboxError`; success returns
|
|
2551
|
-
* `{ answered: id }`. `to` is
|
|
2739
|
+
* `{ answered: id }`. `to` is fixed at construction
|
|
2552
2740
|
* ({@link import('./types.js').AnswerToolOptions.to}) — never read from the model-supplied args —
|
|
2553
2741
|
* so a model cannot spoof which terminal it is answering for. Concurrent answerers racing on one
|
|
2554
|
-
* endpoint are
|
|
2742
|
+
* endpoint are first-write-wins — a late answer to an already-settled form returns a typed
|
|
2555
2743
|
* `ANSWER` `ToolboxError` (surfaced as a 422 over HTTP).
|
|
2556
2744
|
*
|
|
2557
2745
|
* @param options - The live manager, the fixed `to` identity, and advertised overrides (see
|
|
@@ -2603,9 +2791,9 @@ function createAnswerTool(options) {
|
|
|
2603
2791
|
});
|
|
2604
2792
|
}
|
|
2605
2793
|
/**
|
|
2606
|
-
*
|
|
2607
|
-
* definitions, the
|
|
2608
|
-
*
|
|
2794
|
+
* Creates the in-memory {@link DefinitionStoreInterface} — a process-lifetime `Map` of database
|
|
2795
|
+
* definitions, the default store the database and relation tools persist their `DatabaseDefinition`
|
|
2796
|
+
* configs through.
|
|
2609
2797
|
*
|
|
2610
2798
|
* @returns A {@link DefinitionStoreInterface}
|
|
2611
2799
|
*
|
|
@@ -2620,7 +2808,7 @@ function createMemoryDefinitionStore() {
|
|
|
2620
2808
|
return new MemoryDefinitionStore();
|
|
2621
2809
|
}
|
|
2622
2810
|
/**
|
|
2623
|
-
*
|
|
2811
|
+
* Creates a {@link DefinitionStoreInterface} backed by one table of the `@orkestrel/database`
|
|
2624
2812
|
* layer — the driver-pluggable twin of {@link createMemoryDefinitionStore}, storing each
|
|
2625
2813
|
* database's definition as one opaque JSON column.
|
|
2626
2814
|
*
|
|
@@ -2645,15 +2833,15 @@ function createDatabaseDefinitionStore(driver = createMemoryDriver()) {
|
|
|
2645
2833
|
}).table("definitions"));
|
|
2646
2834
|
}
|
|
2647
2835
|
/**
|
|
2648
|
-
*
|
|
2649
|
-
* databases through one `operation`-discriminated call (
|
|
2836
|
+
* Builds an LLM-callable database tool — it creates, queries, and mutates `@orkestrel/database`
|
|
2837
|
+
* databases through one `operation`-discriminated call (matching
|
|
2650
2838
|
* {@link createWorkspaceTool}'s single-tool-many-operations shape).
|
|
2651
2839
|
*
|
|
2652
2840
|
* @remarks
|
|
2653
|
-
* The universal tool-handler contract
|
|
2841
|
+
* The universal tool-handler contract: validates the call args against
|
|
2654
2842
|
* {@link import('./shapers.js').databaseToolShape}, dispatches to the matching operation, and
|
|
2655
|
-
*
|
|
2656
|
-
* lifetime — `'create'` mints one from `tables` ({@link import('./
|
|
2843
|
+
* returns a plain result on success. A database is resolved lazily and cached for the tool's
|
|
2844
|
+
* lifetime — `'create'` mints one from `tables` ({@link import('./compilers.js').expandTables}) and
|
|
2657
2845
|
* a registered `driver` key ({@link import('./types.js').DatabaseToolOptions.drivers}, default
|
|
2658
2846
|
* `{ memory: () => createMemoryDriver() }`); any other operation addressing an uncached id falls
|
|
2659
2847
|
* back to {@link import('./types.js').DatabaseToolOptions.store} (an unknown id throws a typed
|
|
@@ -2662,10 +2850,10 @@ function createDatabaseDefinitionStore(driver = createMemoryDriver()) {
|
|
|
2662
2850
|
*
|
|
2663
2851
|
* `'records'` clamps its `query` to
|
|
2664
2852
|
* {@link import('./types.js').DatabaseToolOptions.limit} (default
|
|
2665
|
-
* {@link import('./constants.js').DATABASE_TOOL_LIMIT})
|
|
2853
|
+
* {@link import('./constants.js').DATABASE_TOOL_LIMIT}) through
|
|
2666
2854
|
* {@link import('./helpers.js').clampQuery}, reporting `truncated` when storage held more rows
|
|
2667
|
-
* than the cap. Every operation's `query` is normalized
|
|
2668
|
-
* {@link import('./helpers.js').
|
|
2855
|
+
* than the cap. Every operation's `query` is normalized through
|
|
2856
|
+
* {@link import('./helpers.js').normalizeQuery} (defaults an omitted condition `connector` to `'and'`).
|
|
2669
2857
|
* When {@link import('./types.js').DatabaseToolOptions.readonly} is `true`, every mutating
|
|
2670
2858
|
* operation throws a typed `TOOL` `ToolboxError` before doing anything. A configured
|
|
2671
2859
|
* {@link import('./types.js').DatabaseToolOptions.timeout} is passed as a fresh
|
|
@@ -2674,10 +2862,10 @@ function createDatabaseDefinitionStore(driver = createMemoryDriver()) {
|
|
|
2674
2862
|
* not an outer deadline for resolution, construction, schema inspection, `get`, or `close`. A typed `@orkestrel/database`
|
|
2675
2863
|
* failure (`DatabaseError`) re-surfaces as a typed `DATABASE` `ToolboxError` carrying the
|
|
2676
2864
|
* original {@link import('@orkestrel/database').DatabaseErrorCode} in `context.code`
|
|
2677
|
-
* ({@link import('./helpers.js').
|
|
2865
|
+
* ({@link import('./helpers.js').inferDatabaseCode}); an `ToolboxError` thrown by this tool's own
|
|
2678
2866
|
* guards passes through unwrapped.
|
|
2679
2867
|
*
|
|
2680
|
-
* A lazily re-minted database over the
|
|
2868
|
+
* A lazily re-minted database over the default in-memory driver yields an empty database — only
|
|
2681
2869
|
* the {@link import('./types.js').DatabaseDefinition} schema persists in `store`, never rows;
|
|
2682
2870
|
* durable rows need a persistent driver factory registered in
|
|
2683
2871
|
* {@link import('./types.js').DatabaseToolOptions.drivers}. `'destroy'` closes whatever handle is
|
|
@@ -2685,7 +2873,7 @@ function createDatabaseDefinitionStore(driver = createMemoryDriver()) {
|
|
|
2685
2873
|
* {@link import('./types.js').DatabaseToolOptions.databases} handle — the embedder relinquishes
|
|
2686
2874
|
* that handle's lifecycle to this tool for any id it wires in. This tool assumes the
|
|
2687
2875
|
* single-writer, non-reentrant model `@orkestrel/database` itself assumes — concurrent calls
|
|
2688
|
-
* against one id are
|
|
2876
|
+
* against one id are not serialized by this tool. `'get'` is uncapped by
|
|
2689
2877
|
* {@link import('./types.js').DatabaseToolOptions.limit} (bounded only by the caller's `key` array
|
|
2690
2878
|
* size), unlike `'records'` / `'find'` / `'links'`.
|
|
2691
2879
|
*
|
|
@@ -2782,7 +2970,7 @@ function createDatabaseTool(options = {}) {
|
|
|
2782
2970
|
}
|
|
2783
2971
|
case "records": {
|
|
2784
2972
|
const table = (await resolver.resolve(call.id)).table(call.table);
|
|
2785
|
-
const { query: probe, limit } = clampQuery(
|
|
2973
|
+
const { query: probe, limit } = clampQuery(normalizeQuery(call.query), cap);
|
|
2786
2974
|
const rows = await table.records(probe, read);
|
|
2787
2975
|
const truncated = rows.length > limit;
|
|
2788
2976
|
const sliced = rows.slice(0, limit);
|
|
@@ -2793,8 +2981,8 @@ function createDatabaseTool(options = {}) {
|
|
|
2793
2981
|
limit
|
|
2794
2982
|
};
|
|
2795
2983
|
}
|
|
2796
|
-
case "count": return { count: await (await resolver.resolve(call.id)).table(call.table).count(
|
|
2797
|
-
case "aggregate": return { value: await (await resolver.resolve(call.id)).table(call.table).aggregate(call.function, call.column,
|
|
2984
|
+
case "count": return { count: await (await resolver.resolve(call.id)).table(call.table).count(normalizeQuery(call.query), read) };
|
|
2985
|
+
case "aggregate": return { value: await (await resolver.resolve(call.id)).table(call.table).aggregate(call.function, call.column, normalizeQuery(call.query), read) };
|
|
2798
2986
|
case "add": {
|
|
2799
2987
|
const table = (await resolver.resolve(call.id)).table(call.table);
|
|
2800
2988
|
const many = Array.isArray(call.row);
|
|
@@ -2840,7 +3028,7 @@ function createDatabaseTool(options = {}) {
|
|
|
2840
3028
|
}
|
|
2841
3029
|
} catch (error) {
|
|
2842
3030
|
if (isToolboxError(error)) throw error;
|
|
2843
|
-
const code =
|
|
3031
|
+
const code = inferDatabaseCode(error);
|
|
2844
3032
|
if (code === void 0) throw error;
|
|
2845
3033
|
throw new ToolboxError("DATABASE", error instanceof Error ? error.message : String(error), {
|
|
2846
3034
|
code,
|
|
@@ -2853,32 +3041,33 @@ function createDatabaseTool(options = {}) {
|
|
|
2853
3041
|
});
|
|
2854
3042
|
}
|
|
2855
3043
|
/**
|
|
2856
|
-
*
|
|
2857
|
-
* through one `operation`-discriminated call (
|
|
3044
|
+
* Builds an LLM-callable relation tool — it traverses and edits `@orkestrel/relation` relationships
|
|
3045
|
+
* through one `operation`-discriminated call (matching {@link createDatabaseTool}'s
|
|
2858
3046
|
* single-tool-many-operations shape).
|
|
2859
3047
|
*
|
|
2860
3048
|
* @remarks
|
|
2861
|
-
* The universal tool-handler contract
|
|
3049
|
+
* The universal tool-handler contract: validates the call args against
|
|
2862
3050
|
* {@link import('./shapers.js').relationToolShape}, resolves the addressed
|
|
2863
3051
|
* {@link import('@orkestrel/relation').RelationManagerInterface} — an explicit `manager` field
|
|
2864
|
-
* must match a key of {@link import('./types.js').RelationToolOptions.managers}, an
|
|
2865
|
-
* resolves to the
|
|
3052
|
+
* must match a key of {@link import('./types.js').RelationToolOptions.managers}, an omitted one
|
|
3053
|
+
* resolves to the sole registered manager, either miss throwing a typed `TOOL`
|
|
2866
3054
|
* {@link import('./errors.js').ToolboxError}
|
|
2867
|
-
* ({@link import('./helpers.js').
|
|
2868
|
-
* ({@link import('./helpers.js').
|
|
2869
|
-
* dispatches to the matched operation,
|
|
3055
|
+
* ({@link import('./helpers.js').resolveRelationManager}) — then resolves `model` against it
|
|
3056
|
+
* ({@link import('./helpers.js').resolveRelationModel}, same typed-`TOOL`-on-miss shape), and
|
|
3057
|
+
* dispatches to the matched operation, returning a plain result on success.
|
|
2870
3058
|
*
|
|
2871
|
-
* `'load'` / `'find'` expand the call's
|
|
2872
|
-
* `@orkestrel/relation` `Include` tree
|
|
3059
|
+
* `'load'` / `'find'` expand the call's flat dot-path `include` list into a live
|
|
3060
|
+
* `@orkestrel/relation` `Include` tree through {@link import('./helpers.js').expandInclude}, capped
|
|
2873
3061
|
* at {@link import('./types.js').RelationToolOptions.depth} (default
|
|
2874
3062
|
* {@link import('./constants.js').RELATION_TOOL_DEPTH}) — a path exceeding the cap, or carrying an
|
|
2875
3063
|
* empty segment, throws a typed `TOOL` error. `'load'` dispatches on whether `key` is an array
|
|
2876
|
-
* (positional many-key form
|
|
3064
|
+
* (positional many-key form — an array key is read as many keys rather than one) or a single key.
|
|
3065
|
+
* `'find'` and `'links'` clamp their
|
|
2877
3066
|
* result to {@link import('./types.js').RelationToolOptions.limit} (default
|
|
2878
|
-
* {@link import('./constants.js').RELATION_TOOL_LIMIT})
|
|
2879
|
-
*
|
|
2880
|
-
* `truncated`; `'links'` (which has no upstream pagination) fetches the
|
|
2881
|
-
* slices/truncates it the same way. `'link'` / `'unlink'` write / remove one `through` junction
|
|
3067
|
+
* {@link import('./constants.js').RELATION_TOOL_LIMIT}), taken through
|
|
3068
|
+
* {@link import('./helpers.js').resolveLimit} — `'find'` probes one row past the effective limit to
|
|
3069
|
+
* report `truncated`; `'links'` (which has no upstream pagination) fetches the full linked-key
|
|
3070
|
+
* list and slices/truncates it the same way. `'link'` / `'unlink'` write / remove one `through` junction
|
|
2882
3071
|
* row.
|
|
2883
3072
|
*
|
|
2884
3073
|
* A typed `@orkestrel/relation` failure (`RelationError`) re-surfaces as a typed `RELATION`
|
|
@@ -2913,7 +3102,7 @@ function createRelationTool(options) {
|
|
|
2913
3102
|
const call = contract.parse(args);
|
|
2914
3103
|
if (call === void 0) throw new ToolboxError("TOOL", "malformed relation call", { args });
|
|
2915
3104
|
try {
|
|
2916
|
-
const model =
|
|
3105
|
+
const model = resolveRelationModel(resolveRelationManager(options.managers, call.manager), call.model);
|
|
2917
3106
|
switch (call.operation) {
|
|
2918
3107
|
case "load": {
|
|
2919
3108
|
const include = expandInclude(call.include, depth);
|
|
@@ -2922,7 +3111,7 @@ function createRelationTool(options) {
|
|
|
2922
3111
|
}
|
|
2923
3112
|
case "find": {
|
|
2924
3113
|
const include = expandInclude(call.include, depth);
|
|
2925
|
-
const effective =
|
|
3114
|
+
const effective = resolveLimit(call.limit, cap);
|
|
2926
3115
|
const rows = await model.find(include, {
|
|
2927
3116
|
limit: effective + 1,
|
|
2928
3117
|
...call.offset === void 0 ? {} : { offset: call.offset },
|
|
@@ -2945,27 +3134,28 @@ function createRelationTool(options) {
|
|
|
2945
3134
|
await model.unlink(call.key, call.relation, call.target);
|
|
2946
3135
|
return { unlinked: true };
|
|
2947
3136
|
case "links": {
|
|
3137
|
+
const effective = resolveLimit(void 0, cap);
|
|
2948
3138
|
const keys = await model.links(call.key, call.relation);
|
|
2949
|
-
const truncated = keys.length >
|
|
2950
|
-
const sliced = keys.slice(0,
|
|
3139
|
+
const truncated = keys.length > effective;
|
|
3140
|
+
const sliced = keys.slice(0, effective);
|
|
2951
3141
|
return {
|
|
2952
3142
|
keys: sliced,
|
|
2953
3143
|
count: sliced.length,
|
|
2954
3144
|
truncated,
|
|
2955
|
-
limit:
|
|
3145
|
+
limit: effective
|
|
2956
3146
|
};
|
|
2957
3147
|
}
|
|
2958
3148
|
}
|
|
2959
3149
|
} catch (error) {
|
|
2960
3150
|
if (isToolboxError(error)) throw error;
|
|
2961
|
-
const relation =
|
|
3151
|
+
const relation = inferRelationCode(error);
|
|
2962
3152
|
if (relation !== void 0) throw new ToolboxError("RELATION", error instanceof Error ? error.message : String(error), {
|
|
2963
3153
|
code: relation,
|
|
2964
3154
|
operation: call.operation,
|
|
2965
3155
|
model: call.model,
|
|
2966
3156
|
..."relation" in call ? { relation: call.relation } : {}
|
|
2967
3157
|
});
|
|
2968
|
-
const database =
|
|
3158
|
+
const database = inferDatabaseCode(error);
|
|
2969
3159
|
if (database === void 0) throw error;
|
|
2970
3160
|
throw new ToolboxError("DATABASE", error instanceof Error ? error.message : String(error), {
|
|
2971
3161
|
code: database,
|
|
@@ -2976,44 +3166,44 @@ function createRelationTool(options) {
|
|
|
2976
3166
|
});
|
|
2977
3167
|
}
|
|
2978
3168
|
/**
|
|
2979
|
-
*
|
|
2980
|
-
*
|
|
2981
|
-
* {@link createEndpointTool}, wraps one
|
|
3169
|
+
* Builds a standalone LLM-callable tool that infers a JSON Schema from example values — the utility
|
|
3170
|
+
* half of the bridge from an existing API or database into an MCP tool (the other half,
|
|
3171
|
+
* {@link createEndpointTool}, wraps one concrete endpoint).
|
|
2982
3172
|
*
|
|
2983
3173
|
* @remarks
|
|
2984
|
-
* The universal tool-handler contract
|
|
3174
|
+
* The universal tool-handler contract: validates the call args against
|
|
2985
3175
|
* {@link import('./shapers.js').inferToolShape} (`samples` non-empty, `format` / `enum` optional
|
|
2986
|
-
* booleans, `candidates` an optional array), infers a schema
|
|
2987
|
-
* `samplesToSchema`, wraps a non-object root as `{ value: <schema> }`
|
|
2988
|
-
* the tool-parameters convention every other `create*Tool` factory advertises), and
|
|
3176
|
+
* booleans, `candidates` an optional array), infers a schema through `@orkestrel/contract`'s
|
|
3177
|
+
* `samplesToSchema`, wraps a non-object root as `{ value: <schema> }` through `schemaToObject` (mirrors
|
|
3178
|
+
* the tool-parameters convention every other `create*Tool` factory advertises), and returns the
|
|
2989
3179
|
* resulting parameters record. An empty `samples` array fails `inferToolShape`'s `min: 1` bound —
|
|
2990
3180
|
* `contract.parse` returns `undefined` and the handler throws a typed `TOOL`
|
|
2991
3181
|
* {@link import('./errors.js').ToolboxError}.
|
|
2992
3182
|
*
|
|
2993
|
-
* When `candidates` is
|
|
2994
|
-
* this array existed. When `candidates` is
|
|
2995
|
-
* compiles a
|
|
2996
|
-
* `schemaToShape`,
|
|
3183
|
+
* When `candidates` is absent, the return is the bare parameters record — unchanged from before
|
|
3184
|
+
* this array existed. When `candidates` is present (any array, including empty), the handler
|
|
3185
|
+
* compiles a separate per-call contract from the raw inferred schema (through `@orkestrel/contract`'s
|
|
3186
|
+
* `schemaToShape`, not the `schemaToObject`-wrapped parameters — a bare-value sample checks a
|
|
2997
3187
|
* bare-value candidate) and returns `{ parameters, checks }`, one check per candidate at the same
|
|
2998
|
-
* index. Every entry has a
|
|
2999
|
-
* when `valid` is `false`: `valid` is the
|
|
3000
|
-
*
|
|
3001
|
-
* string slot) — here a conformance report answers "does this value conform
|
|
3002
|
-
* string slot is `valid: false`, full stop. `coercible` answers a
|
|
3003
|
-
*
|
|
3188
|
+
* index. Every entry has a uniform shape — `{ index, valid, coercible }`, with `faults` added only
|
|
3189
|
+
* when `valid` is `false`: `valid` is the strict guard verdict (`checker.is(candidate)`), the
|
|
3190
|
+
* opposite of {@link createEndpointTool}'s enforcement, which coerces (`7` becomes `'7'` for a
|
|
3191
|
+
* string slot) — here a conformance report answers "does this value conform as-is": `7` against a
|
|
3192
|
+
* string slot is `valid: false`, full stop. `coercible` answers a separate question — "would the
|
|
3193
|
+
* normalizing parse accept this value", that is would {@link createEndpointTool}'s default enforcement
|
|
3004
3194
|
* admit it (`checker.parse(candidate) !== undefined`) — computed for every candidate regardless of
|
|
3005
|
-
* `valid`; by the house parse/guard round-trip
|
|
3006
|
-
*
|
|
3195
|
+
* `valid`; by the house parse/guard round-trip, a `valid: true` entry is
|
|
3196
|
+
* always also `coercible: true`. `@orkestrel/contract`'s `explain` mirrors the normalizing
|
|
3007
3197
|
* `parse`'s leniency, not `is`'s strictness — so a strictly-invalid but coercible candidate (`7`
|
|
3008
|
-
* against a string slot) yields `{ valid: false, coercible: true, faults: [] }`:
|
|
3198
|
+
* against a string slot) yields `{ valid: false, coercible: true, faults: [] }`: empty faults, because
|
|
3009
3199
|
* the mismatch the normalizing parse would silently fix is not one `explain` reports. `faults`
|
|
3010
|
-
* therefore only ever populates for a
|
|
3200
|
+
* therefore only ever populates for a non-coercible mismatch — a wrong type the parse can't coerce
|
|
3011
3201
|
* (a boolean in a string slot), a missing required key, or an out-of-enum value — where
|
|
3012
3202
|
* `coercible: false`. `checker.is` / `.parse` / `.explain` are all total over JSON-safe input — a
|
|
3013
|
-
* JSON-safe hostile candidate (a `__proto__`-carrying object, deeply nested data) reaches
|
|
3014
|
-
* and yields a bounded, non-throwing per-candidate verdict; a
|
|
3015
|
-
* throwing-getter `Proxy`) never reaches the checker at all — it fails the
|
|
3016
|
-
* against {@link import('./shapers.js').inferToolShape} and rejects the
|
|
3203
|
+
* JSON-safe hostile candidate (a `__proto__`-carrying object, deeply nested data) reaches each of them
|
|
3204
|
+
* and yields a bounded, non-throwing per-candidate verdict; a non-JSON-safe candidate (for example a
|
|
3205
|
+
* throwing-getter `Proxy`) never reaches the checker at all — it fails the outer `args` parse
|
|
3206
|
+
* against {@link import('./shapers.js').inferToolShape} and rejects the whole call with the same
|
|
3017
3207
|
* `TOOL` {@link import('./errors.js').ToolboxError} a malformed `samples`/`format`/`enum` throws,
|
|
3018
3208
|
* with no per-candidate verdict produced.
|
|
3019
3209
|
*
|
|
@@ -3092,36 +3282,36 @@ function createInferTool(options) {
|
|
|
3092
3282
|
});
|
|
3093
3283
|
}
|
|
3094
3284
|
/**
|
|
3095
|
-
*
|
|
3096
|
-
* `ToolInterface` — the endpoint half of the
|
|
3097
|
-
* {@link createInferTool}, is a standalone inference utility).
|
|
3285
|
+
* Wraps one concrete endpoint ({@link import('./types.js').EndpointDefinition}) as an LLM-callable
|
|
3286
|
+
* `ToolInterface` — the endpoint half of the bridge from an existing API or database into an MCP
|
|
3287
|
+
* tool (the other half, {@link createInferTool}, is a standalone inference utility).
|
|
3098
3288
|
*
|
|
3099
3289
|
* @remarks
|
|
3100
|
-
* `parameters` is inferred
|
|
3290
|
+
* `parameters` is inferred once at construction from `definition.samples` through
|
|
3101
3291
|
* `@orkestrel/contract`'s `samplesToSchema` (tuned by {@link import('./types.js').EndpointToolOptions}'s
|
|
3102
|
-
* `format` / `enum`), wrapping a non-object root as `{ value: <schema> }`
|
|
3103
|
-
* the
|
|
3104
|
-
* ({@link import('./types.js').EndpointToolOptions.validate} `true`), the
|
|
3105
|
-
* `@orkestrel/contract`
|
|
3106
|
-
* `ContractInterface` whose `.parse` runs on every call's `args` before `definition.
|
|
3107
|
-
*
|
|
3292
|
+
* `format` / `enum`), wrapping a non-object root as `{ value: <schema> }` through `schemaToObject` —
|
|
3293
|
+
* the same object-rooted schema is both the advertised `parameters` and, by default
|
|
3294
|
+
* ({@link import('./types.js').EndpointToolOptions.validate} `true`), the enforced contract:
|
|
3295
|
+
* `@orkestrel/contract`'s `schemaToShape` compiles it once (through `createContract`) into a
|
|
3296
|
+
* `ContractInterface` whose `.parse` runs on every call's `args` before `definition.execute` — a
|
|
3297
|
+
* normalizing parse, not a strict type check: a scalar is coerced to its inferred type where the
|
|
3108
3298
|
* house parsers coerce (a number to/from a numeric string, a boolean from `'1'`/`'0'`/`'true'`/
|
|
3109
|
-
* `'false'`/`1`/`0`), so `definition.
|
|
3299
|
+
* `'false'`/`1`/`0`), so `definition.execute` receives the coerced value (for example `7` sent for a
|
|
3110
3300
|
* string slot arrives as `'7'`), not the raw call value. A call whose `args` fails to parse into
|
|
3111
|
-
* a record — a required key missing, or a value not coercible to its slot's type —
|
|
3301
|
+
* a record — a required key missing, or a value not coercible to its slot's type — throws a
|
|
3112
3302
|
* typed `TOOL` {@link import('./errors.js').ToolboxError} carrying the compiled contract's
|
|
3113
|
-
* structured `explain` faults, and `definition.
|
|
3114
|
-
*
|
|
3303
|
+
* structured `explain` faults, and `definition.execute` is never called. `format` annotations are
|
|
3304
|
+
* never asserted, and a key outside the closed inferred schema is silently dropped rather than
|
|
3115
3305
|
* rejected (see {@link import('./types.js').EndpointToolOptions.validate}). With
|
|
3116
|
-
* `validate: false`, `execute`
|
|
3117
|
-
*
|
|
3118
|
-
*
|
|
3306
|
+
* `validate: false`, the tool's `execute` passes through the model-supplied `args` to
|
|
3307
|
+
* `definition.execute` without re-validation — the raw-passthrough opt-out. Either way, the
|
|
3308
|
+
* definition's return flows back as the tool call's plain result; a throw propagates uncaught,
|
|
3119
3309
|
* isolated by the `ToolManagerInterface` (`@orkestrel/tool`) into the canonical error envelope
|
|
3120
|
-
*
|
|
3310
|
+
* — never caught or re-wrapped here.
|
|
3121
3311
|
*
|
|
3122
3312
|
* @param definition - The endpoint's identity, non-empty samples, and local handler (see
|
|
3123
3313
|
* {@link import('./types.js').EndpointDefinition})
|
|
3124
|
-
* @param options - Construction-time inference tuning
|
|
3314
|
+
* @param options - Construction-time inference tuning and the validate opt-out (see
|
|
3125
3315
|
* {@link import('./types.js').EndpointToolOptions})
|
|
3126
3316
|
* @returns A `ToolInterface` named `definition.name`
|
|
3127
3317
|
*
|
|
@@ -3134,12 +3324,12 @@ function createInferTool(options) {
|
|
|
3134
3324
|
* name: 'lookupUser',
|
|
3135
3325
|
* description: 'Look up a user by id.',
|
|
3136
3326
|
* samples: [{ id: '1', name: 'Ada' }, { id: '2', name: 'Bob' }],
|
|
3137
|
-
*
|
|
3327
|
+
* execute: (args) => ({ id: args.id, name: 'Ada' }),
|
|
3138
3328
|
* })
|
|
3139
3329
|
* const tools = createToolManager()
|
|
3140
3330
|
* tools.add(tool)
|
|
3141
3331
|
*
|
|
3142
|
-
* // conforming args (all required keys present) parse and reach `
|
|
3332
|
+
* // conforming args (all required keys present) parse and reach the definition's `execute`
|
|
3143
3333
|
* const result = await tools.execute({
|
|
3144
3334
|
* id: 'call-1',
|
|
3145
3335
|
* name: 'lookupUser',
|
|
@@ -3148,7 +3338,7 @@ function createInferTool(options) {
|
|
|
3148
3338
|
* // result.value -> { id: '1', name: 'Ada' }
|
|
3149
3339
|
*
|
|
3150
3340
|
* // a nonconforming call (id is not coercible to the required string) is rejected before
|
|
3151
|
-
* // `
|
|
3341
|
+
* // the definition's `execute` runs
|
|
3152
3342
|
* const rejected = await tools.execute({
|
|
3153
3343
|
* id: 'call-2',
|
|
3154
3344
|
* name: 'lookupUser',
|
|
@@ -3169,7 +3359,7 @@ function createEndpointTool(definition, options) {
|
|
|
3169
3359
|
description: definition.description,
|
|
3170
3360
|
...parameters === void 0 ? {} : { parameters },
|
|
3171
3361
|
execute(args) {
|
|
3172
|
-
return definition.
|
|
3362
|
+
return definition.execute(args);
|
|
3173
3363
|
}
|
|
3174
3364
|
});
|
|
3175
3365
|
const contract = createContract(schemaToShape(objectSchema));
|
|
@@ -3183,11 +3373,11 @@ function createEndpointTool(definition, options) {
|
|
|
3183
3373
|
name: definition.name,
|
|
3184
3374
|
faults: contract.explain(args)
|
|
3185
3375
|
});
|
|
3186
|
-
return definition.
|
|
3376
|
+
return definition.execute(parsed);
|
|
3187
3377
|
}
|
|
3188
3378
|
});
|
|
3189
3379
|
}
|
|
3190
3380
|
//#endregion
|
|
3191
|
-
export { AGENT_TOOL_DEPTH, AGENT_TOOL_DESCRIPTION, AGENT_TOOL_NAME, AGENT_TOOL_SUMMARY, ANSWER_TOOL_DESCRIPTION, ANSWER_TOOL_NAME, ANSWER_TOOL_SUMMARY, DATABASE_TOOL_DESCRIPTION, DATABASE_TOOL_LIMIT, DATABASE_TOOL_MUTATIONS, DATABASE_TOOL_NAME, DATABASE_TOOL_SUMMARY, DESCRIBE_TOOL_DESCRIPTION, DESCRIBE_TOOL_NAME, DESCRIBE_TOOL_SUMMARY, DatabaseDefinitionStore, DatabaseResolver, INFER_TOOL_DESCRIPTION, INFER_TOOL_NAME, INFER_TOOL_SUMMARY,
|
|
3381
|
+
export { AGENT_TOOL_DEPTH, AGENT_TOOL_DESCRIPTION, AGENT_TOOL_NAME, AGENT_TOOL_SUMMARY, ANSWER_TOOL_DESCRIPTION, ANSWER_TOOL_NAME, ANSWER_TOOL_SUMMARY, DATABASE_TOOL_DESCRIPTION, DATABASE_TOOL_LIMIT, DATABASE_TOOL_MUTATIONS, DATABASE_TOOL_NAME, DATABASE_TOOL_SUMMARY, DESCRIBE_TOOL_DESCRIPTION, DESCRIBE_TOOL_NAME, DESCRIBE_TOOL_SUMMARY, DatabaseDefinitionStore, DatabaseResolver, INFER_TOOL_DESCRIPTION, INFER_TOOL_NAME, INFER_TOOL_SUMMARY, MAX_WORKFLOW_CHAIN, MemoryDefinitionStore, PROMPT_TOOL_DESCRIPTION, PROMPT_TOOL_NAME, PROMPT_TOOL_SUMMARY, RELATION_TOOL_DEPTH, RELATION_TOOL_DESCRIPTION, RELATION_TOOL_LIMIT, RELATION_TOOL_NAME, RELATION_TOOL_SUMMARY, ToolboxError, WORKFLOW_TOOL_DESCRIPTION, WORKFLOW_TOOL_FLAT_EXAMPLE, WORKFLOW_TOOL_NAME, WORKFLOW_TOOL_NESTED_EXAMPLE, WORKFLOW_TOOL_SUMMARY, WORKSPACE_TOOL_DESCRIPTION, WORKSPACE_TOOL_EXAMPLE, WORKSPACE_TOOL_NAME, WORKSPACE_TOOL_SUMMARY, agentToolShape, answerToolShape, clampQuery, columnPrimitiveShape, columnSpecShape, compileColumn, compileColumnPrimitive, completeDraft, completePhaseDraft, completeTaskDraft, conditionShape, createAgentFunction, createAgentTool, createAnswerTool, createDatabaseDefinitionStore, createDatabaseTool, createDescribeTool, createEndpointTool, createInferTool, createMemoryDefinitionStore, createPromptTool, createRelationTool, createToolFunction, createWorkflowDraftContract, createWorkflowFunctions, createWorkflowTool, createWorkspaceTool, databaseToolShape, deriveWorkflowDepth, describeToolShape, expandInclude, expandSteps, expandTables, extendLineage, includeShape, inferDatabaseCode, inferRelationCode, inferTerminalCode, inferToolShape, isAgentFunction, isColumnPrimitive, isColumnSpec, isDatabaseDefinition, isToolboxError, isWorkflowLineage, keyShape, managerShape, normalizeLineage, normalizeQuery, orderShape, phaseDraftShape, promptToolShape, queryShape, relationToolShape, resolveLimit, resolveRelationManager, resolveRelationModel, rowShape, rowsShape, singleKeyShape, stepShape, summarizeWorkflow, tableSpecShape, tagAgent, tagWorkflow, taskDraftShape, workflowDraftShape, workflowStepsShape, workspaceToolShape };
|
|
3192
3382
|
|
|
3193
3383
|
//# sourceMappingURL=index.js.map
|