@orkestrel/toolbox 0.0.24 → 0.0.26
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/dist/src/core/index.cjs +592 -245
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +429 -125
- package/dist/src/core/index.d.ts +429 -125
- package/dist/src/core/index.js +581 -249
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +71 -16
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +45 -12
- package/dist/src/server/index.d.ts +45 -12
- package/dist/src/server/index.js +72 -18
- package/dist/src/server/index.js.map +1 -1
- package/package.json +18 -18
package/dist/src/core/index.cjs
CHANGED
|
@@ -5,8 +5,8 @@ let _orkestrel_terminal = require("@orkestrel/terminal");
|
|
|
5
5
|
let _orkestrel_database = require("@orkestrel/database");
|
|
6
6
|
let _orkestrel_relation = require("@orkestrel/relation");
|
|
7
7
|
let _orkestrel_agent = require("@orkestrel/agent");
|
|
8
|
-
let _orkestrel_workspace = require("@orkestrel/workspace");
|
|
9
8
|
let _orkestrel_tool = require("@orkestrel/tool");
|
|
9
|
+
let _orkestrel_workspace = require("@orkestrel/workspace");
|
|
10
10
|
let _orkestrel_form = require("@orkestrel/form");
|
|
11
11
|
// src/core/constants.ts
|
|
12
12
|
/**
|
|
@@ -191,7 +191,7 @@ var WORKSPACE_TOOL_DESCRIPTION = [
|
|
|
191
191
|
"All file operations act on the ACTIVE workspace; use \"workspaces\" then \"switch\" to move between workspaces.",
|
|
192
192
|
"",
|
|
193
193
|
"Operations (each takes the fields listed):",
|
|
194
|
-
"- read { \"operation\": \"read\", \"path\": \"<file>\" } — return the file's text.",
|
|
194
|
+
"- read { \"operation\": \"read\", \"path\": \"<file>\" } — return the file's text, or null when the path holds no text.",
|
|
195
195
|
"- list { \"operation\": \"list\" } — list every file in the active workspace (path, state, size, lines, kind).",
|
|
196
196
|
"- has { \"operation\": \"has\", \"path\": \"<file>\" } — whether the file exists.",
|
|
197
197
|
"- search { \"operation\": \"search\", \"query\": \"<text>\", \"regex\"?: bool, \"sensitive\"?: bool, \"limit\"?: int } — find lines matching the query across all files.",
|
|
@@ -205,7 +205,7 @@ var WORKSPACE_TOOL_DESCRIPTION = [
|
|
|
205
205
|
"- workspaces { \"operation\": \"workspaces\" } — list the workspaces you can switch between (each id, file count, active).",
|
|
206
206
|
"- switch { \"operation\": \"switch\", \"id\": \"<id>\" } — make the workspace with that id active (ids come from \"workspaces\").",
|
|
207
207
|
"",
|
|
208
|
-
"Notes: lines and columns are 1-based (column 1 is the first character). \"regex\" defaults to false (a literal substring), \"sensitive\" defaults to true (case-sensitive). \"search\"/\"replace\"/\"splice\" act only on text files.
|
|
208
|
+
"Notes: lines and columns are 1-based (column 1 is the first character). \"regex\" defaults to false (a literal substring), \"sensitive\" defaults to true (case-sensitive). \"search\"/\"replace\"/\"splice\" act only on text files. With no active workspace, \"write\", \"prepend\", and \"append\" create one, and \"replace\", \"splice\", \"move\", and \"remove\" refuse.",
|
|
209
209
|
"",
|
|
210
210
|
"Example — write a file:",
|
|
211
211
|
JSON.stringify(WORKSPACE_TOOL_EXAMPLE)
|
|
@@ -681,59 +681,77 @@ function isAgentFunction(value) {
|
|
|
681
681
|
return inspected.success && inspected.value;
|
|
682
682
|
}
|
|
683
683
|
/**
|
|
684
|
-
* Narrows an unknown value to a {@link
|
|
684
|
+
* Narrows an unknown value to a {@link ColumnPrimitive}.
|
|
685
|
+
*
|
|
686
|
+
* @param value - The value to inspect
|
|
687
|
+
* @returns True if `value` is `'string'`, `'integer'`, `'number'`, or `'boolean'`; false otherwise
|
|
688
|
+
*/
|
|
689
|
+
var isColumnPrimitive = (0, _orkestrel_contract.literalOf)("string", "integer", "number", "boolean");
|
|
690
|
+
/**
|
|
691
|
+
* Narrows an unknown value to a {@link ColumnSpec} — a bare {@link ColumnPrimitive}, or exactly
|
|
692
|
+
* `{ primitive, optional? }`.
|
|
693
|
+
*
|
|
694
|
+
* @param value - The value to inspect
|
|
695
|
+
* @returns True if `value` is a valid shorthand or an exact record with a valid `primitive`; false otherwise
|
|
696
|
+
*/
|
|
697
|
+
var isColumnSpec = (0, _orkestrel_contract.unionOf)(isColumnPrimitive, (0, _orkestrel_contract.recordOf)({
|
|
698
|
+
primitive: isColumnPrimitive,
|
|
699
|
+
optional: _orkestrel_contract.isBoolean
|
|
700
|
+
}, ["optional"]));
|
|
701
|
+
/**
|
|
702
|
+
* Narrows an unknown value to a {@link TableSpec} — table name to exactly `{ columns }`, each column
|
|
703
|
+
* a valid {@link ColumnSpec}.
|
|
685
704
|
*
|
|
686
705
|
* @param value - The value to inspect
|
|
687
|
-
* @returns True if `value` is a
|
|
706
|
+
* @returns True if `value` is a table layout; false otherwise
|
|
688
707
|
*/
|
|
689
|
-
function
|
|
690
|
-
|
|
691
|
-
if (!(0, _orkestrel_contract.isRecord)(value)) return false;
|
|
692
|
-
return isColumnPrimitive(value.primitive) && (value.optional === void 0 || (0, _orkestrel_contract.isBoolean)(value.optional));
|
|
708
|
+
function isTableSpec(value) {
|
|
709
|
+
return (0, _orkestrel_contract.holds)(() => (0, _orkestrel_contract.isRecord)(value) && Object.values(value).every((table) => (0, _orkestrel_contract.isRecord)(table) && Object.keys(table).length === 1 && (0, _orkestrel_contract.isRecord)(table.columns) && Object.values(table.columns).every((column) => isColumnSpec(column))));
|
|
693
710
|
}
|
|
694
711
|
/**
|
|
695
|
-
* Narrows an unknown value to a
|
|
712
|
+
* Narrows an unknown value to a `PrimaryMap` (`@orkestrel/database`) — table name to a nonempty
|
|
713
|
+
* primary-key column name.
|
|
696
714
|
*
|
|
697
715
|
* @param value - The value to inspect
|
|
698
|
-
* @returns True if
|
|
716
|
+
* @returns True if every value is a nonempty string; false otherwise
|
|
699
717
|
*/
|
|
700
|
-
|
|
718
|
+
function isPrimaryMap(value) {
|
|
719
|
+
return (0, _orkestrel_contract.holds)(() => (0, _orkestrel_contract.isRecord)(value) && Object.values(value).every((key) => (0, _orkestrel_contract.isNonEmptyString)(key)));
|
|
720
|
+
}
|
|
701
721
|
/**
|
|
702
|
-
* Narrows an unknown value to
|
|
703
|
-
*
|
|
704
|
-
* `primary`, `indexes`, and finite `version` schema configuration. The boundary guard a
|
|
705
|
-
* {@link import('./types.js').DefinitionStoreInterface} applies to an untrusted persisted blob
|
|
706
|
-
* before trusting it as a definition (never an `as`).
|
|
722
|
+
* Narrows an unknown value to an `IndexMap` (`@orkestrel/database`) — table name to its index
|
|
723
|
+
* groups, each group a nonempty list of nonempty column names.
|
|
707
724
|
*
|
|
708
725
|
* @param value - The value to inspect
|
|
709
|
-
* @returns True if
|
|
726
|
+
* @returns True if every value is a list of such groups; false otherwise
|
|
710
727
|
*/
|
|
711
|
-
function
|
|
712
|
-
|
|
713
|
-
if ("keys" in value) return false;
|
|
714
|
-
if (!(0, _orkestrel_contract.isNonEmptyString)(value.id) || !(0, _orkestrel_contract.isNonEmptyString)(value.driver)) return false;
|
|
715
|
-
if (!(0, _orkestrel_contract.isRecord)(value.tables)) return false;
|
|
716
|
-
for (const table of Object.values(value.tables)) {
|
|
717
|
-
if (!(0, _orkestrel_contract.isRecord)(table) || !(0, _orkestrel_contract.isRecord)(table.columns)) return false;
|
|
718
|
-
for (const column of Object.values(table.columns)) if (!isColumnSpec(column)) return false;
|
|
719
|
-
}
|
|
720
|
-
if (value.primary !== void 0) {
|
|
721
|
-
if (!(0, _orkestrel_contract.isRecord)(value.primary)) return false;
|
|
722
|
-
for (const key of Object.values(value.primary)) if (!(0, _orkestrel_contract.isNonEmptyString)(key)) return false;
|
|
723
|
-
}
|
|
724
|
-
if (value.indexes !== void 0) {
|
|
725
|
-
if (!(0, _orkestrel_contract.isRecord)(value.indexes)) return false;
|
|
726
|
-
for (const groups of Object.values(value.indexes)) {
|
|
727
|
-
if (!(0, _orkestrel_contract.isArray)(groups)) return false;
|
|
728
|
-
for (const group of groups) {
|
|
729
|
-
if (!(0, _orkestrel_contract.isArray)(group) || group.length === 0) return false;
|
|
730
|
-
for (const column of group) if (!(0, _orkestrel_contract.isNonEmptyString)(column)) return false;
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
if (value.version !== void 0 && !(0, _orkestrel_contract.isFiniteNumber)(value.version)) return false;
|
|
735
|
-
return true;
|
|
728
|
+
function isIndexMap(value) {
|
|
729
|
+
return (0, _orkestrel_contract.holds)(() => (0, _orkestrel_contract.isRecord)(value) && Object.values(value).every((groups) => (0, _orkestrel_contract.isArray)(groups) && groups.every((group) => (0, _orkestrel_contract.isArray)(group) && group.length > 0 && group.every((column) => (0, _orkestrel_contract.isNonEmptyString)(column)))));
|
|
736
730
|
}
|
|
731
|
+
/**
|
|
732
|
+
* Narrows an unknown value to exactly a {@link DatabaseDefinition} — a non-empty `id` and `driver`,
|
|
733
|
+
* a {@link TableSpec}, plus optional `primary`, `indexes`, and finite `version` schema configuration,
|
|
734
|
+
* refusing every other key. The boundary guard a {@link import('./types.js').DefinitionStoreInterface}
|
|
735
|
+
* applies to an untrusted persisted blob before trusting it as a definition (never an `as`).
|
|
736
|
+
*
|
|
737
|
+
* @remarks
|
|
738
|
+
* A lockstep test holds this guard to {@link import('./shapers.js').definitionShape}.
|
|
739
|
+
*
|
|
740
|
+
* @param value - The value to inspect
|
|
741
|
+
* @returns True if `value` is a complete {@link DatabaseDefinition}; false otherwise
|
|
742
|
+
*/
|
|
743
|
+
var isDatabaseDefinition = (0, _orkestrel_contract.recordOf)({
|
|
744
|
+
id: _orkestrel_contract.isNonEmptyString,
|
|
745
|
+
driver: _orkestrel_contract.isNonEmptyString,
|
|
746
|
+
tables: isTableSpec,
|
|
747
|
+
primary: isPrimaryMap,
|
|
748
|
+
indexes: isIndexMap,
|
|
749
|
+
version: _orkestrel_contract.isFiniteNumber
|
|
750
|
+
}, [
|
|
751
|
+
"primary",
|
|
752
|
+
"indexes",
|
|
753
|
+
"version"
|
|
754
|
+
]);
|
|
737
755
|
// src/core/shapers.ts
|
|
738
756
|
/**
|
|
739
757
|
* Describes the shape of {@link import('./factories.js').createPromptTool}'s call arguments — `to` names the
|
|
@@ -821,9 +839,9 @@ var taskDraftShape = (0, _orkestrel_contract.objectShape)({
|
|
|
821
839
|
description: "Extra attempts after the first on failure; overrides the phase default. Omitted means none."
|
|
822
840
|
})),
|
|
823
841
|
timeout: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
|
|
824
|
-
min:
|
|
842
|
+
min: 1,
|
|
825
843
|
max: _orkestrel_workflow.MAX_TIMER_MS,
|
|
826
|
-
description: "
|
|
844
|
+
description: "Per-attempt deadline in milliseconds. Omit the key for no deadline."
|
|
827
845
|
}))
|
|
828
846
|
});
|
|
829
847
|
/**
|
|
@@ -843,6 +861,7 @@ var phaseDraftShape = (0, _orkestrel_contract.objectShape)({
|
|
|
843
861
|
tasks: (0, _orkestrel_contract.arrayShape)(taskDraftShape, { description: "The phase tasks; they run CONCURRENTLY." }),
|
|
844
862
|
concurrency: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
|
|
845
863
|
min: 1,
|
|
864
|
+
max: Number.MAX_SAFE_INTEGER,
|
|
846
865
|
description: "Max tasks in flight at once (a resource throttle); omitted means unbounded."
|
|
847
866
|
})),
|
|
848
867
|
bail: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.literalShape)([true, false], { description: "Per-phase failure-policy override; omitted inherits the workflow bail." }))
|
|
@@ -925,13 +944,22 @@ var workflowStepsShape = (0, _orkestrel_contract.objectShape)({
|
|
|
925
944
|
*/
|
|
926
945
|
var workspaceToolShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.objectShape)({
|
|
927
946
|
operation: (0, _orkestrel_contract.literalShape)(["read"], { description: "Read a whole text file's text by path." }),
|
|
928
|
-
path: (0, _orkestrel_contract.stringShape)({
|
|
947
|
+
path: (0, _orkestrel_contract.stringShape)({
|
|
948
|
+
min: 1,
|
|
949
|
+
description: "The path of the file to read."
|
|
950
|
+
})
|
|
929
951
|
}), (0, _orkestrel_contract.objectShape)({ operation: (0, _orkestrel_contract.literalShape)(["list"], { description: "List every file in the workspace." }) }), (0, _orkestrel_contract.objectShape)({
|
|
930
952
|
operation: (0, _orkestrel_contract.literalShape)(["has"], { description: "Check whether a file exists at the path." }),
|
|
931
|
-
path: (0, _orkestrel_contract.stringShape)({
|
|
953
|
+
path: (0, _orkestrel_contract.stringShape)({
|
|
954
|
+
min: 1,
|
|
955
|
+
description: "The path to check for."
|
|
956
|
+
})
|
|
932
957
|
}), (0, _orkestrel_contract.objectShape)({
|
|
933
958
|
operation: (0, _orkestrel_contract.literalShape)(["search"], { description: "Search every text file for a query, returning each hit." }),
|
|
934
|
-
query: (0, _orkestrel_contract.stringShape)({
|
|
959
|
+
query: (0, _orkestrel_contract.stringShape)({
|
|
960
|
+
min: 1,
|
|
961
|
+
description: "The text (or regular-expression source) to search for."
|
|
962
|
+
}),
|
|
935
963
|
regex: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Treat the query as a regular expression. Defaults to false (a literal substring)." })),
|
|
936
964
|
sensitive: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Match case-sensitively. Defaults to true (set false for case-insensitive)." })),
|
|
937
965
|
limit: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
|
|
@@ -940,7 +968,10 @@ var workspaceToolShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_cont
|
|
|
940
968
|
}))
|
|
941
969
|
}), (0, _orkestrel_contract.objectShape)({
|
|
942
970
|
operation: (0, _orkestrel_contract.literalShape)(["replace"], { description: "Replace a query with a replacement across every text file." }),
|
|
943
|
-
query: (0, _orkestrel_contract.stringShape)({
|
|
971
|
+
query: (0, _orkestrel_contract.stringShape)({
|
|
972
|
+
min: 1,
|
|
973
|
+
description: "The text (or regular-expression source) to replace."
|
|
974
|
+
}),
|
|
944
975
|
replacement: (0, _orkestrel_contract.stringShape)({ description: "The text to substitute for each match." }),
|
|
945
976
|
regex: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Treat the query as a regular expression. Defaults to false (a literal substring)." })),
|
|
946
977
|
sensitive: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Match case-sensitively. Defaults to true (set false for case-insensitive)." })),
|
|
@@ -950,11 +981,17 @@ var workspaceToolShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_cont
|
|
|
950
981
|
}))
|
|
951
982
|
}), (0, _orkestrel_contract.objectShape)({
|
|
952
983
|
operation: (0, _orkestrel_contract.literalShape)(["write"], { description: "Create or overwrite a whole file with content." }),
|
|
953
|
-
path: (0, _orkestrel_contract.stringShape)({
|
|
984
|
+
path: (0, _orkestrel_contract.stringShape)({
|
|
985
|
+
min: 1,
|
|
986
|
+
description: "The path of the file to write."
|
|
987
|
+
}),
|
|
954
988
|
content: (0, _orkestrel_contract.stringShape)({ description: "The full new contents of the file." })
|
|
955
989
|
}), (0, _orkestrel_contract.objectShape)({
|
|
956
990
|
operation: (0, _orkestrel_contract.literalShape)(["splice"], { description: "Replace a 1-based range of an existing text file (from inclusive, to exclusive) with content." }),
|
|
957
|
-
path: (0, _orkestrel_contract.stringShape)({
|
|
991
|
+
path: (0, _orkestrel_contract.stringShape)({
|
|
992
|
+
min: 1,
|
|
993
|
+
description: "The path of the text file to edit."
|
|
994
|
+
}),
|
|
958
995
|
content: (0, _orkestrel_contract.stringShape)({ description: "The text to splice in place of the range." }),
|
|
959
996
|
fromLine: (0, _orkestrel_contract.integerShape)({
|
|
960
997
|
min: 1,
|
|
@@ -974,19 +1011,34 @@ var workspaceToolShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_cont
|
|
|
974
1011
|
})
|
|
975
1012
|
}), (0, _orkestrel_contract.objectShape)({
|
|
976
1013
|
operation: (0, _orkestrel_contract.literalShape)(["prepend"], { description: "Add content to the start of a file (creating it when absent)." }),
|
|
977
|
-
path: (0, _orkestrel_contract.stringShape)({
|
|
1014
|
+
path: (0, _orkestrel_contract.stringShape)({
|
|
1015
|
+
min: 1,
|
|
1016
|
+
description: "The path of the file to prepend to."
|
|
1017
|
+
}),
|
|
978
1018
|
content: (0, _orkestrel_contract.stringShape)({ description: "The text to add at the start of the file." })
|
|
979
1019
|
}), (0, _orkestrel_contract.objectShape)({
|
|
980
1020
|
operation: (0, _orkestrel_contract.literalShape)(["append"], { description: "Add content to the end of a file (creating it when absent)." }),
|
|
981
|
-
path: (0, _orkestrel_contract.stringShape)({
|
|
1021
|
+
path: (0, _orkestrel_contract.stringShape)({
|
|
1022
|
+
min: 1,
|
|
1023
|
+
description: "The path of the file to append to."
|
|
1024
|
+
}),
|
|
982
1025
|
content: (0, _orkestrel_contract.stringShape)({ description: "The text to add at the end of the file." })
|
|
983
1026
|
}), (0, _orkestrel_contract.objectShape)({
|
|
984
1027
|
operation: (0, _orkestrel_contract.literalShape)(["move"], { description: "Rename or move a file (overwriting an occupied target)." }),
|
|
985
|
-
from: (0, _orkestrel_contract.stringShape)({
|
|
986
|
-
|
|
1028
|
+
from: (0, _orkestrel_contract.stringShape)({
|
|
1029
|
+
min: 1,
|
|
1030
|
+
description: "The current path of the file."
|
|
1031
|
+
}),
|
|
1032
|
+
to: (0, _orkestrel_contract.stringShape)({
|
|
1033
|
+
min: 1,
|
|
1034
|
+
description: "The new path for the file."
|
|
1035
|
+
})
|
|
987
1036
|
}), (0, _orkestrel_contract.objectShape)({
|
|
988
1037
|
operation: (0, _orkestrel_contract.literalShape)(["remove"], { description: "Delete a file from the workspace." }),
|
|
989
|
-
path: (0, _orkestrel_contract.stringShape)({
|
|
1038
|
+
path: (0, _orkestrel_contract.stringShape)({
|
|
1039
|
+
min: 1,
|
|
1040
|
+
description: "The path of the file to remove."
|
|
1041
|
+
})
|
|
990
1042
|
}), (0, _orkestrel_contract.objectShape)({ operation: (0, _orkestrel_contract.literalShape)(["workspaces"], { description: "List the workspaces you can move between (each id, file count, and whether it is active), so you can pick an id to switch to." }) }), (0, _orkestrel_contract.objectShape)({
|
|
991
1043
|
operation: (0, _orkestrel_contract.literalShape)(["switch"], { description: "Switch the active workspace to the one with this id (get ids from the \"workspaces\" operation). Edit and read operations then target it." }),
|
|
992
1044
|
id: (0, _orkestrel_contract.stringShape)({ description: "The id of the workspace to make active (from the \"workspaces\" listing)." })
|
|
@@ -1005,12 +1057,41 @@ var columnSpecShape = (0, _orkestrel_contract.unionShape)(columnPrimitiveShape,
|
|
|
1005
1057
|
}));
|
|
1006
1058
|
/** Describes a {@link import('./types.js').TableSpec} — table name to `{ columns }`, each column a {@link columnSpecShape}. */
|
|
1007
1059
|
var tableSpecShape = (0, _orkestrel_contract.recordShape)((0, _orkestrel_contract.objectShape)({ columns: (0, _orkestrel_contract.recordShape)(columnSpecShape, { description: "Column name to its primitive or its { primitive, optional } spec." }) }), { description: "Table name to its column layout." });
|
|
1060
|
+
/** Describes a {@link import('./types.js').DatabaseDefinition}'s `primary` map — table name to its primary-key column. */
|
|
1061
|
+
var primaryShape = (0, _orkestrel_contract.recordShape)((0, _orkestrel_contract.stringShape)({ min: 1 }), { description: "Table name to its primary-key column." });
|
|
1062
|
+
/** Describes a {@link import('./types.js').DatabaseDefinition}'s `indexes` map — table name to its index column groups. */
|
|
1063
|
+
var indexesShape = (0, _orkestrel_contract.recordShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.stringShape)({ min: 1 }), {
|
|
1064
|
+
min: 1,
|
|
1065
|
+
description: "One nonempty index group of column names."
|
|
1066
|
+
}), { description: "Zero or more index groups declared for the table." }), { description: "Table name to its index column groups." });
|
|
1067
|
+
/**
|
|
1068
|
+
* Describes a stored {@link import('./types.js').DatabaseDefinition} exactly: the owned record
|
|
1069
|
+
* {@link import('./stores/DatabaseDefinitionStore.js').DatabaseDefinitionStore} reads and writes.
|
|
1070
|
+
*
|
|
1071
|
+
* @remarks
|
|
1072
|
+
* {@link import('./validators.js').isDatabaseDefinition} is the guard the store runs, and a lockstep
|
|
1073
|
+
* test holds it to this shape. The compiled contract fills a refusal's faults.
|
|
1074
|
+
*/
|
|
1075
|
+
var definitionShape = (0, _orkestrel_contract.objectShape)({
|
|
1076
|
+
id: (0, _orkestrel_contract.stringShape)({
|
|
1077
|
+
min: 1,
|
|
1078
|
+
description: "The database id."
|
|
1079
|
+
}),
|
|
1080
|
+
driver: (0, _orkestrel_contract.stringShape)({
|
|
1081
|
+
min: 1,
|
|
1082
|
+
description: "The registered driver key."
|
|
1083
|
+
}),
|
|
1084
|
+
tables: tableSpecShape,
|
|
1085
|
+
primary: (0, _orkestrel_contract.optionalShape)(primaryShape),
|
|
1086
|
+
indexes: (0, _orkestrel_contract.optionalShape)(indexesShape),
|
|
1087
|
+
version: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.numberShape)({ description: "The schema version for open-time reconciliation." }))
|
|
1088
|
+
});
|
|
1008
1089
|
/**
|
|
1009
1090
|
* Describes one key value for the database tool and the relation tool — a string or number; the
|
|
1010
1091
|
* array form (multiple keys, positional) resolves first, so an array argument is read as many keys
|
|
1011
1092
|
* rather than one.
|
|
1012
1093
|
*/
|
|
1013
|
-
var keyShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.stringShape)(), (0, _orkestrel_contract.numberShape)()), { description: "Multiple row keys, positional — a miss at an index is
|
|
1094
|
+
var keyShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.stringShape)(), (0, _orkestrel_contract.numberShape)()), { description: "Multiple row keys, positional — a miss at an index is null there." }), (0, _orkestrel_contract.stringShape)({ description: "One row key." }), (0, _orkestrel_contract.numberShape)({ description: "One row key." }));
|
|
1014
1095
|
/** Describes a loose row — a flat object of column name to JSON value. */
|
|
1015
1096
|
var rowShape = (0, _orkestrel_contract.recordShape)((0, _orkestrel_contract.jsonShape)(), { description: "A row as a flat object of column name to value." });
|
|
1016
1097
|
/**
|
|
@@ -1087,11 +1168,8 @@ var databaseToolShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contr
|
|
|
1087
1168
|
min: 1,
|
|
1088
1169
|
description: "The registered driver key. Defaults to \"memory\"."
|
|
1089
1170
|
})),
|
|
1090
|
-
primary: (0, _orkestrel_contract.optionalShape)(
|
|
1091
|
-
indexes: (0, _orkestrel_contract.optionalShape)(
|
|
1092
|
-
min: 1,
|
|
1093
|
-
description: "One nonempty index group of column names."
|
|
1094
|
-
}), { description: "Zero or more index groups declared for the table." }), { description: "Table name to its index column groups." })),
|
|
1171
|
+
primary: (0, _orkestrel_contract.optionalShape)(primaryShape),
|
|
1172
|
+
indexes: (0, _orkestrel_contract.optionalShape)(indexesShape),
|
|
1095
1173
|
version: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.numberShape)({ description: "The schema version for open-time reconciliation." }))
|
|
1096
1174
|
}), (0, _orkestrel_contract.objectShape)({
|
|
1097
1175
|
operation: (0, _orkestrel_contract.literalShape)(["tables"], { description: "List a database's table names." }),
|
|
@@ -1300,8 +1378,8 @@ var relationToolShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contr
|
|
|
1300
1378
|
* optional `candidates` array to check against the inferred schema.
|
|
1301
1379
|
*
|
|
1302
1380
|
* @remarks
|
|
1303
|
-
* `samples` requires at least one element (`min: 1`) — an empty array
|
|
1304
|
-
*
|
|
1381
|
+
* `samples` requires at least one element (`min: 1`) — the Tool door refuses an empty array with
|
|
1382
|
+
* a `ToolError` carrying code `ARGUMENTS` (`@orkestrel/tool`). When
|
|
1305
1383
|
* `candidates` is present (any array, including empty), the handler compiles a contract from the
|
|
1306
1384
|
* freshly inferred schema and checks each candidate against it with a strict guard (`.is`, no
|
|
1307
1385
|
* coercion) — the opposite of {@link import('./factories.js').createEndpointTool}'s normalizing
|
|
@@ -1316,6 +1394,20 @@ var inferToolShape = (0, _orkestrel_contract.objectShape)({
|
|
|
1316
1394
|
enum: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Infer enum constraints from repeated literal values. Defaults to false." })),
|
|
1317
1395
|
candidates: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.jsonShape)(), { description: "Optional values to check against the freshly inferred schema. When present, the tool returns a per-candidate verdict (strict — no coercion) alongside the inferred parameters." }))
|
|
1318
1396
|
});
|
|
1397
|
+
// src/core/contracts.ts
|
|
1398
|
+
/**
|
|
1399
|
+
* Holds the compiled {@link import('./shapers.js').definitionShape}, whose `audit` fills the faults a
|
|
1400
|
+
* refused stored definition carries.
|
|
1401
|
+
*
|
|
1402
|
+
* @example
|
|
1403
|
+
* ```ts
|
|
1404
|
+
* import { definitionContract } from '@orkestrel/toolbox'
|
|
1405
|
+
*
|
|
1406
|
+
* definitionContract.audit({ id: 'shop', driver: 'memory', tables: {}, keys: {} })
|
|
1407
|
+
* // [{ reason: 'extra', path: ['keys'] }]
|
|
1408
|
+
* ```
|
|
1409
|
+
*/
|
|
1410
|
+
var definitionContract = (0, _orkestrel_contract.createContract)(definitionShape);
|
|
1319
1411
|
// src/core/helpers.ts
|
|
1320
1412
|
/**
|
|
1321
1413
|
* Returns the ancestry identifier of a workflow in a run chain — `workflow:<id>`.
|
|
@@ -1564,7 +1656,7 @@ function inferRelationCode(error) {
|
|
|
1564
1656
|
function expandInclude(paths, depth) {
|
|
1565
1657
|
return Iterator.from(paths ?? []).reduce((include, path) => {
|
|
1566
1658
|
const segments = path.split(".");
|
|
1567
|
-
if (segments.length > depth || segments.some((segment) => segment.length === 0)) throw new ToolboxError("TOOL", `malformed include path
|
|
1659
|
+
if (segments.length > depth || segments.some((segment) => segment.length === 0)) throw new ToolboxError("TOOL", `malformed include path ${(0, _orkestrel_contract.preview)(path)}; send dot-separated relation names with no empty segment and at most ${String(depth)} segments`, {
|
|
1568
1660
|
path,
|
|
1569
1661
|
depth
|
|
1570
1662
|
});
|
|
@@ -1620,7 +1712,7 @@ function expandInclude(paths, depth) {
|
|
|
1620
1712
|
function resolveRelationManager(managers, name) {
|
|
1621
1713
|
if (name !== void 0) {
|
|
1622
1714
|
const manager = managers[name];
|
|
1623
|
-
if (manager === void 0) throw new ToolboxError("TOOL", `unknown relation manager
|
|
1715
|
+
if (manager === void 0) throw new ToolboxError("TOOL", `unknown relation manager ${(0, _orkestrel_contract.preview)(name)}; send manager as one of ${renderCandidates(Object.keys(managers))}`, {
|
|
1624
1716
|
manager: name,
|
|
1625
1717
|
managers: Object.keys(managers)
|
|
1626
1718
|
});
|
|
@@ -1632,7 +1724,7 @@ function resolveRelationManager(managers, name) {
|
|
|
1632
1724
|
const manager = managers[single];
|
|
1633
1725
|
if (manager !== void 0) return manager;
|
|
1634
1726
|
}
|
|
1635
|
-
throw new ToolboxError("TOOL",
|
|
1727
|
+
throw new ToolboxError("TOOL", `no relation manager resolved for the call; send manager as one of ${renderCandidates(names)}`, { managers: names });
|
|
1636
1728
|
}
|
|
1637
1729
|
/**
|
|
1638
1730
|
* Resolves a `model` name against a live {@link RelationManagerInterface} — the pure model-lookup
|
|
@@ -1644,7 +1736,7 @@ function resolveRelationManager(managers, name) {
|
|
|
1644
1736
|
* @returns The model's {@link ModelInterface}
|
|
1645
1737
|
*/
|
|
1646
1738
|
function resolveRelationModel(manager, name) {
|
|
1647
|
-
if (!manager.has(name)) throw new ToolboxError("TOOL", `unknown model
|
|
1739
|
+
if (!manager.has(name)) throw new ToolboxError("TOOL", `unknown model ${(0, _orkestrel_contract.preview)(name)}; send model as one of ${renderCandidates(manager.names())}`, {
|
|
1648
1740
|
model: name,
|
|
1649
1741
|
models: manager.names()
|
|
1650
1742
|
});
|
|
@@ -1738,7 +1830,21 @@ function clampQuery(query, cap) {
|
|
|
1738
1830
|
};
|
|
1739
1831
|
}
|
|
1740
1832
|
/**
|
|
1741
|
-
* Resolves an owned workflow input through the supplied flat or draft contracts
|
|
1833
|
+
* Resolves an owned workflow input through the supplied flat or draft contracts, or refuses it with
|
|
1834
|
+
* what to send.
|
|
1835
|
+
*
|
|
1836
|
+
* @remarks
|
|
1837
|
+
* Empty input is the wrapped definition. A `phases` key with no `steps` key takes the draft form,
|
|
1838
|
+
* and everything else takes the advertised flat form, so a blob carrying both keys refuses `phases`
|
|
1839
|
+
* as undeclared rather than ignoring it, and a blob carrying neither is told to send `steps`.
|
|
1840
|
+
*
|
|
1841
|
+
* @param owned - The call's owned JSON arguments
|
|
1842
|
+
* @param definition - The wrapped definition empty input runs
|
|
1843
|
+
* @param steps - The compiled flat form
|
|
1844
|
+
* @param draft - The compiled draft form
|
|
1845
|
+
* @returns The definition the input resolves to
|
|
1846
|
+
* @throws {import('@orkestrel/tool').ToolError} Thrown with code `ARGUMENTS` when the chosen form refuses the input, carrying `context.faults`
|
|
1847
|
+
*
|
|
1742
1848
|
* @example
|
|
1743
1849
|
* ```ts
|
|
1744
1850
|
* import { createContract } from '@orkestrel/contract'
|
|
@@ -1751,12 +1857,196 @@ function clampQuery(query, cap) {
|
|
|
1751
1857
|
*/
|
|
1752
1858
|
function resolveWorkflowTarget(owned, definition, steps, draft) {
|
|
1753
1859
|
if (Object.keys(owned).length === 0) return definition;
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1860
|
+
return Object.hasOwn(owned, "phases") && !Object.hasOwn(owned, "steps") ? completeDraft(requireToolCall(draft, owned)) : expandSteps(requireToolCall(steps, owned));
|
|
1861
|
+
}
|
|
1862
|
+
/**
|
|
1863
|
+
* Renders the candidates a refusal offers, each encoded and length-capped and at most
|
|
1864
|
+
* `REFUSAL_LIST_LIMIT` of them named, so a long or multiline name cannot fake or balloon the message.
|
|
1865
|
+
*
|
|
1866
|
+
* @param names - The candidates to offer
|
|
1867
|
+
* @returns The encoded candidates joined with `, `, empty when there are none
|
|
1868
|
+
*
|
|
1869
|
+
* @example
|
|
1870
|
+
* ```ts
|
|
1871
|
+
* import { renderCandidates } from '@src/core'
|
|
1872
|
+
*
|
|
1873
|
+
* renderCandidates(['read', 'list']) // '"read", "list"'
|
|
1874
|
+
* ```
|
|
1875
|
+
*/
|
|
1876
|
+
function renderCandidates(names) {
|
|
1877
|
+
return (0, _orkestrel_contract.renderList)(names.map((name) => (0, _orkestrel_contract.preview)(name)));
|
|
1878
|
+
}
|
|
1879
|
+
/**
|
|
1880
|
+
* Caps the complete text of a refusal at `FAULT_TEXT_LIMIT` characters, saying where it stopped.
|
|
1881
|
+
*
|
|
1882
|
+
* @remarks
|
|
1883
|
+
* A refusal goes back to a model whose context window can be small. Use this where a message joins
|
|
1884
|
+
* parts this package cannot bound one by one: a head beside a report, or an upstream error's text.
|
|
1885
|
+
*
|
|
1886
|
+
* @param text - The refusal text
|
|
1887
|
+
* @returns The text unchanged when it fits, otherwise cut to the limit and well formed
|
|
1888
|
+
*
|
|
1889
|
+
* @example
|
|
1890
|
+
* ```ts
|
|
1891
|
+
* import { limitRefusal } from '@src/core'
|
|
1892
|
+
*
|
|
1893
|
+
* limitRefusal('send steps') // 'send steps'
|
|
1894
|
+
* limitRefusal('x'.repeat(5000)).length // 2048
|
|
1895
|
+
* ```
|
|
1896
|
+
*/
|
|
1897
|
+
function limitRefusal(text) {
|
|
1898
|
+
if (text.length <= _orkestrel_contract.FAULT_TEXT_LIMIT) return text;
|
|
1899
|
+
return `${text.slice(0, _orkestrel_contract.FAULT_TEXT_LIMIT - 62).toWellFormed()}… the message stops here; correct what it names and call again`;
|
|
1900
|
+
}
|
|
1901
|
+
/**
|
|
1902
|
+
* Reads a call's arguments through a compiled contract, or refuses them with what to send.
|
|
1903
|
+
*
|
|
1904
|
+
* @remarks
|
|
1905
|
+
* Uses the contract admission door and translates refusals to tool errors.
|
|
1906
|
+
* Tools with object and union roots enforce `createTool`'s `contract` and use this helper to type
|
|
1907
|
+
* the parsed arguments. The workflow tool uses this door under `strict: false` because it accepts
|
|
1908
|
+
* several authoring forms while advertising the flat form.
|
|
1909
|
+
*
|
|
1910
|
+
* @param contract - The compiled contract of the arguments
|
|
1911
|
+
* @param value - The arguments the call sent
|
|
1912
|
+
* @returns The admitted value, which may retain input identity under a union
|
|
1913
|
+
* @throws {import('@orkestrel/tool').ToolError} Thrown with code `ARGUMENTS`, carrying `context.faults` when the arguments could be read
|
|
1914
|
+
*
|
|
1915
|
+
* @example
|
|
1916
|
+
* ```ts
|
|
1917
|
+
* import { createContract, numberShape, objectShape } from '@orkestrel/contract'
|
|
1918
|
+
* import { requireToolCall } from '@src/core'
|
|
1919
|
+
*
|
|
1920
|
+
* const contract = createContract(objectShape({ amount: numberShape() }))
|
|
1921
|
+
* requireToolCall(contract, { amount: '3' }) // { amount: 3 }
|
|
1922
|
+
* requireToolCall(contract, { amount: 3, extra: 1 }) // throws ToolError ('ARGUMENTS')
|
|
1923
|
+
* ```
|
|
1924
|
+
*/
|
|
1925
|
+
function requireToolCall(contract, value) {
|
|
1926
|
+
const read = (0, _orkestrel_contract.admitValue)(contract, value);
|
|
1927
|
+
if (!read.success) {
|
|
1928
|
+
if (read.error.cause === void 0) throw buildArgumentRefusal(contract.schema, value, read.error.faults);
|
|
1929
|
+
throw new _orkestrel_tool.ToolError("ARGUMENTS", "Arguments could not be read; send one JSON object of plain values", void 0, { cause: read.error.cause });
|
|
1757
1930
|
}
|
|
1758
|
-
|
|
1759
|
-
|
|
1931
|
+
return read.value.value;
|
|
1932
|
+
}
|
|
1933
|
+
/**
|
|
1934
|
+
* Builds an argument refusal from the supplied faults, auditing the schema when no faults were supplied.
|
|
1935
|
+
*
|
|
1936
|
+
* @remarks
|
|
1937
|
+
* Uses the contract's union reporting: a selected arm names its own faults, and a value selecting
|
|
1938
|
+
* no arm receives a bounded summary of alternatives. Supplied faults remain unchanged.
|
|
1939
|
+
*
|
|
1940
|
+
* @param schema - The compiled schema the arguments are held to
|
|
1941
|
+
* @param value - The arguments the call sent
|
|
1942
|
+
* @param faults - The faults admission reported
|
|
1943
|
+
* @returns The `ToolError` to throw, with code `ARGUMENTS` and the faults in `context.faults`
|
|
1944
|
+
*
|
|
1945
|
+
* @example
|
|
1946
|
+
* ```ts
|
|
1947
|
+
* import { createContract, literalShape, objectShape, stringShape, unionShape } from '@orkestrel/contract'
|
|
1948
|
+
* import { buildArgumentRefusal } from '@src/core'
|
|
1949
|
+
*
|
|
1950
|
+
* const { schema } = createContract(
|
|
1951
|
+
* unionShape(
|
|
1952
|
+
* objectShape({ operation: literalShape(['read']), path: stringShape() }),
|
|
1953
|
+
* objectShape({ operation: literalShape(['list']) }),
|
|
1954
|
+
* ),
|
|
1955
|
+
* )
|
|
1956
|
+
* buildArgumentRefusal(schema, { operation: 'read' }, []).message
|
|
1957
|
+
* // 'path: missing; expected string'
|
|
1958
|
+
* ```
|
|
1959
|
+
*/
|
|
1960
|
+
function buildArgumentRefusal(schema, value, faults) {
|
|
1961
|
+
const listed = faults.length === 0 ? (0, _orkestrel_contract.createContract)((0, _orkestrel_contract.schemaToShape)(schema)).audit(value) : faults;
|
|
1962
|
+
return new _orkestrel_tool.ToolError("ARGUMENTS", (0, _orkestrel_contract.describeFaults)(listed, schema), { faults: listed });
|
|
1963
|
+
}
|
|
1964
|
+
/**
|
|
1965
|
+
* Picks the effective value of a positive numeric tool option: the configured value when it is a
|
|
1966
|
+
* positive safe integer, and the fallback when it is omitted.
|
|
1967
|
+
*
|
|
1968
|
+
* @param value - The configured option, or `undefined`
|
|
1969
|
+
* @param fallback - The default an omitted option takes
|
|
1970
|
+
* @param subject - The name the refusal gives the option
|
|
1971
|
+
* @returns The effective value
|
|
1972
|
+
* @throws {ToolboxError} Thrown with code `TOOL` when `value` is present and is anything else, because `NaN` as a bound passes every comparison
|
|
1973
|
+
*
|
|
1974
|
+
* @example
|
|
1975
|
+
* ```ts
|
|
1976
|
+
* import { resolveBound } from '@src/core'
|
|
1977
|
+
*
|
|
1978
|
+
* resolveBound(undefined, 100, 'database limit') // 100
|
|
1979
|
+
* resolveBound(25, 100, 'database limit') // 25
|
|
1980
|
+
* resolveBound(0, 100, 'database limit') // throws ToolboxError ('TOOL')
|
|
1981
|
+
* ```
|
|
1982
|
+
*/
|
|
1983
|
+
function resolveBound(value, fallback, subject) {
|
|
1984
|
+
if (value === void 0) return fallback;
|
|
1985
|
+
if ((0, _orkestrel_agent.isPositiveSafeInteger)(value)) return value;
|
|
1986
|
+
throw new ToolboxError("TOOL", `${subject} must be a positive safe integer; received ${(0, _orkestrel_contract.preview)(value)}; omit it for the default ${String(fallback)}`, { received: (0, _orkestrel_contract.preview)(value) });
|
|
1987
|
+
}
|
|
1988
|
+
/**
|
|
1989
|
+
* Reads the active workspace for an operation that edits a file that must already exist, or refuses.
|
|
1990
|
+
*
|
|
1991
|
+
* @param manager - The manager the workspace tool drives
|
|
1992
|
+
* @param operation - The operation that needs an active workspace
|
|
1993
|
+
* @returns The active workspace
|
|
1994
|
+
* @throws {ToolboxError} Thrown with code `TOOL` when no workspace is active, and creates none
|
|
1995
|
+
*
|
|
1996
|
+
* @example
|
|
1997
|
+
* ```ts
|
|
1998
|
+
* import { createWorkspaceManager } from '@orkestrel/workspace'
|
|
1999
|
+
* import { requireActiveWorkspace } from '@src/core'
|
|
2000
|
+
*
|
|
2001
|
+
* const manager = createWorkspaceManager()
|
|
2002
|
+
* const workspace = manager.add()
|
|
2003
|
+
* requireActiveWorkspace(manager, 'remove') === workspace // true
|
|
2004
|
+
* ```
|
|
2005
|
+
*/
|
|
2006
|
+
function requireActiveWorkspace(manager, operation) {
|
|
2007
|
+
const active = manager.active;
|
|
2008
|
+
if (active !== void 0) return active;
|
|
2009
|
+
throw new ToolboxError("TOOL", `operation ${(0, _orkestrel_contract.preview)(operation)} needs an active workspace and none is; call "write" to create one, or "workspaces" then "switch"`, { operation });
|
|
2010
|
+
}
|
|
2011
|
+
/**
|
|
2012
|
+
* Describes a stored definition that names another id, naming the id it holds and the id it must hold.
|
|
2013
|
+
*
|
|
2014
|
+
* @param id - The database id the row is stored under
|
|
2015
|
+
* @param held - The id the stored definition names
|
|
2016
|
+
* @returns The refusal message
|
|
2017
|
+
*
|
|
2018
|
+
* @example
|
|
2019
|
+
* ```ts
|
|
2020
|
+
* import { describeDefinitionMismatch } from '@src/core'
|
|
2021
|
+
*
|
|
2022
|
+
* describeDefinitionMismatch('shop', 'other')
|
|
2023
|
+
* // 'database "shop" holds a definition that names id "other"; its id must be "shop"; ...'
|
|
2024
|
+
* ```
|
|
2025
|
+
*/
|
|
2026
|
+
function describeDefinitionMismatch(id, held) {
|
|
2027
|
+
const required = (0, _orkestrel_contract.preview)(id);
|
|
2028
|
+
return `database ${required} holds a definition that names id ${(0, _orkestrel_contract.preview)(held)}; its id must be ${required}; read the row through your own table and set its id to ${required}, or delete it with store.delete(${required})`;
|
|
2029
|
+
}
|
|
2030
|
+
/**
|
|
2031
|
+
* Describes a stored definition this package refuses to read, naming its first fault and the remedy.
|
|
2032
|
+
*
|
|
2033
|
+
* @param id - The database id the row is stored under
|
|
2034
|
+
* @param faults - The refused definition's faults
|
|
2035
|
+
* @returns The refusal message
|
|
2036
|
+
*
|
|
2037
|
+
* @example
|
|
2038
|
+
* ```ts
|
|
2039
|
+
* import { describeDefinitionRefusal } from '@src/core'
|
|
2040
|
+
*
|
|
2041
|
+
* describeDefinitionRefusal('shop', [{ reason: 'extra', path: ['keys'] }])
|
|
2042
|
+
* // 'database "shop" holds a definition this version refuses (extra key "keys" at the root; ...'
|
|
2043
|
+
* ```
|
|
2044
|
+
*/
|
|
2045
|
+
function describeDefinitionRefusal(id, faults) {
|
|
2046
|
+
const [first] = faults;
|
|
2047
|
+
const path = first === void 0 ? [] : (0, _orkestrel_contract.extractPath)(first);
|
|
2048
|
+
const held = (0, _orkestrel_contract.preview)(id);
|
|
2049
|
+
return `database ${held} holds a definition this version refuses (${first?.reason === "extra" ? `extra key ${(0, _orkestrel_contract.preview)(path.at(-1))} at ${path.length === 1 ? "the root" : (0, _orkestrel_contract.preview)(path.slice(0, -1).join("."))}` : first === void 0 ? "a value its shape cannot name" : `${first.reason} at ${path.length === 0 ? "the root" : (0, _orkestrel_contract.preview)(path.join("."))}`}; a definition written before this change can carry it); read the row through your own table and ${first?.reason === "extra" ? "rewrite it without the key" : "rewrite it to the declared shape"}, or delete it with store.delete(${held})`;
|
|
1760
2050
|
}
|
|
1761
2051
|
// src/core/compilers.ts
|
|
1762
2052
|
/**
|
|
@@ -1810,8 +2100,8 @@ function compileColumnPrimitive(primitive) {
|
|
|
1810
2100
|
* {@link import('../factories.js').createMemoryDefinitionStore} builds. It implements the same
|
|
1811
2101
|
* {@link DefinitionStoreInterface} contract as
|
|
1812
2102
|
* {@link import('./DatabaseDefinitionStore.js').DatabaseDefinitionStore}: this store copies on
|
|
1813
|
-
* write and on read; the table-backed store narrows an untrusted stored blob and
|
|
1814
|
-
*
|
|
2103
|
+
* write and on read; the table-backed store narrows an untrusted stored blob and refuses a malformed
|
|
2104
|
+
* one.
|
|
1815
2105
|
*
|
|
1816
2106
|
* @remarks
|
|
1817
2107
|
* A plain `Map<string, DatabaseDefinition>` (the definition is already pure,
|
|
@@ -1895,12 +2185,15 @@ var MemoryDefinitionStore = class {
|
|
|
1895
2185
|
* and keeps the row type flat (`definition` reads back as `unknown`).
|
|
1896
2186
|
*
|
|
1897
2187
|
* - **`set(definition)` upserts under the definition's own `id`** (no separate id param) — it
|
|
1898
|
-
* writes the row `{ id: definition.id, definition }
|
|
2188
|
+
* writes the row `{ id: definition.id, definition }`, and first refuses what `get` would refuse,
|
|
2189
|
+
* so no row is written that `get` cannot read.
|
|
1899
2190
|
* - **`get(id)` resolves the stored definition for an id**, narrowing the opaque JSON column back
|
|
1900
2191
|
* to a {@link DatabaseDefinition} ({@link import('../validators.js').isDatabaseDefinition} — the
|
|
1901
|
-
* boundary narrow for an untrusted storage read), or `undefined` if
|
|
1902
|
-
*
|
|
1903
|
-
*
|
|
2192
|
+
* boundary narrow for an untrusted storage read), or `undefined` if no row is stored. A present
|
|
2193
|
+
* row whose blob is malformed or names another id throws, because reading it as absent would let
|
|
2194
|
+
* a later `set` overwrite it.
|
|
2195
|
+
* - **`delete(id)` drops a definition by id** without reading it, the remedy for a refused row; an
|
|
2196
|
+
* absent id is a no-op (no throw).
|
|
1904
2197
|
*
|
|
1905
2198
|
* The public surface is exactly `get` / `set` / `delete` — no extra members (the method
|
|
1906
2199
|
* bijection with {@link DefinitionStoreInterface}).
|
|
@@ -1931,21 +2224,47 @@ var DatabaseDefinitionStore = class {
|
|
|
1931
2224
|
* {@link DatabaseDefinition}.
|
|
1932
2225
|
*
|
|
1933
2226
|
* @param id - The database id to read
|
|
1934
|
-
* @returns The stored {@link DatabaseDefinition}, or `undefined` when
|
|
1935
|
-
*
|
|
2227
|
+
* @returns The stored {@link DatabaseDefinition}, or `undefined` when no row is stored
|
|
2228
|
+
* @throws {ToolboxError} Thrown with code `DATABASE` when the row's blob is malformed or names another id, with the faults under `context.faults`
|
|
1936
2229
|
*/
|
|
1937
2230
|
async get(id) {
|
|
1938
2231
|
const row = await this.#table.get(id);
|
|
1939
2232
|
if (row === void 0) return void 0;
|
|
1940
|
-
|
|
2233
|
+
const definition = row.definition;
|
|
2234
|
+
if (!isDatabaseDefinition(definition)) {
|
|
2235
|
+
const faults = definitionContract.audit(definition);
|
|
2236
|
+
throw new ToolboxError("DATABASE", describeDefinitionRefusal(id, faults), {
|
|
2237
|
+
id,
|
|
2238
|
+
faults
|
|
2239
|
+
});
|
|
2240
|
+
}
|
|
2241
|
+
if (definition.id !== id) {
|
|
2242
|
+
const faults = [{
|
|
2243
|
+
reason: "type",
|
|
2244
|
+
path: ["id"],
|
|
2245
|
+
expected: "literal",
|
|
2246
|
+
received: (0, _orkestrel_contract.preview)(definition.id)
|
|
2247
|
+
}];
|
|
2248
|
+
throw new ToolboxError("DATABASE", describeDefinitionMismatch(id, definition.id), {
|
|
2249
|
+
id,
|
|
2250
|
+
faults
|
|
2251
|
+
});
|
|
2252
|
+
}
|
|
2253
|
+
return definition;
|
|
1941
2254
|
}
|
|
1942
2255
|
/**
|
|
1943
2256
|
* Inserts or replaces a definition under its own `id` — the written row is `{ id, definition }`.
|
|
1944
2257
|
*
|
|
1945
2258
|
* @param definition - The config to persist; its own `id` is the row key (no separate id param)
|
|
1946
2259
|
* @returns A promise settling after the row is written
|
|
2260
|
+
* @throws {ToolboxError} Thrown with code `DATABASE` before any write when the definition fails the guard `get` reads, with the faults under `context.faults`
|
|
1947
2261
|
*/
|
|
1948
2262
|
async set(definition) {
|
|
2263
|
+
if (!isDatabaseDefinition(definition)) {
|
|
2264
|
+
const unchecked = definition;
|
|
2265
|
+
const faults = definitionContract.audit(unchecked);
|
|
2266
|
+
throw new ToolboxError("DATABASE", limitRefusal(`a definition store writes only a complete definition; correct each line and call set again:\n${(0, _orkestrel_contract.describeFaults)(faults, definitionContract.schema)}`), { faults });
|
|
2267
|
+
}
|
|
1949
2268
|
await this.#table.set({
|
|
1950
2269
|
id: definition.id,
|
|
1951
2270
|
definition
|
|
@@ -2044,7 +2363,7 @@ var DatabaseResolver = class {
|
|
|
2044
2363
|
const definition = await this.#store.get(id);
|
|
2045
2364
|
if (definition !== void 0) {
|
|
2046
2365
|
const factory = this.#drivers[definition.driver];
|
|
2047
|
-
if (factory === void 0) throw new ToolboxError("TOOL", `unknown driver
|
|
2366
|
+
if (factory === void 0) throw new ToolboxError("TOOL", `database ${(0, _orkestrel_contract.preview)(id)} names unknown driver ${(0, _orkestrel_contract.preview)(definition.driver)}; register it in the tool's drivers, one of ${renderCandidates(Object.keys(this.#drivers))}`, {
|
|
2048
2367
|
id,
|
|
2049
2368
|
driver: definition.driver
|
|
2050
2369
|
});
|
|
@@ -2061,7 +2380,7 @@ var DatabaseResolver = class {
|
|
|
2061
2380
|
return handle;
|
|
2062
2381
|
}
|
|
2063
2382
|
}
|
|
2064
|
-
throw new ToolboxError("TOOL", `unknown database
|
|
2383
|
+
throw new ToolboxError("TOOL", `unknown database ${(0, _orkestrel_contract.preview)(id)}; define it first with { "operation": "create", "id", "tables" }`, { id });
|
|
2065
2384
|
}
|
|
2066
2385
|
};
|
|
2067
2386
|
// src/core/factories.ts
|
|
@@ -2118,7 +2437,8 @@ function createToolFunction(tools, name) {
|
|
|
2118
2437
|
* targets deeper than {@link import('./constants.js').MAX_WORKFLOW_CHAIN} are rejected before
|
|
2119
2438
|
* agent or runner activity. When {@link import('./types.js').AgentFunctionOptions.runner} is
|
|
2120
2439
|
* supplied, the adapter binds a recursion-safe {@link createWorkflowTool} onto the agent's
|
|
2121
|
-
* `context.tools`, propagating opaque leaves, raw agents, lineage, and the native store. The
|
|
2440
|
+
* `context.tools`, propagating opaque leaves, raw agents, lineage, and the native store. The agent
|
|
2441
|
+
* needs an `authority` of its own to run that tool, because an agent with none denies every call. The
|
|
2122
2442
|
* task's cancellation propagates through the Agent's native per-run signal seam:
|
|
2123
2443
|
* `agent.generate({ signal: controller.signal })` resolves a partial `AgentResult` on a cancel
|
|
2124
2444
|
* (never rejects), projected onto its exact JSON fields.
|
|
@@ -2264,7 +2584,12 @@ function createWorkflowDraftContract() {
|
|
|
2264
2584
|
* It is also the propagation carrier
|
|
2265
2585
|
* {@link createAgentFunction} binds onto a wrapped agent's `context.tools`: because a tool
|
|
2266
2586
|
* handler receives only the model-supplied `args`, the immutable lineage is closed over at bind
|
|
2267
|
-
* time. The authored target is appended and its zero-based depth derived from workflow tags.
|
|
2587
|
+
* time. The authored target is appended and its zero-based depth derived from workflow tags. That
|
|
2588
|
+
* bound tool wraps the containing workflow's id over no phases, and its empty-args call always
|
|
2589
|
+
* refuses as a cycle before the runner, so the wrapper never runs.
|
|
2590
|
+
*
|
|
2591
|
+
* `parameters` advertises the flat form alone, so the tool is built `strict: false` and its handler
|
|
2592
|
+
* runs the door ({@link import('./helpers.js').requireToolCall}) on the form the arguments take.
|
|
2268
2593
|
*
|
|
2269
2594
|
* **Widened authoring surface (additive — the canonical contract and runner stay strict and
|
|
2270
2595
|
* unchanged).** A 2B model reliably calls the tool but cannot reliably emit the full nested
|
|
@@ -2279,9 +2604,11 @@ function createWorkflowDraftContract() {
|
|
|
2279
2604
|
* super-set.
|
|
2280
2605
|
*
|
|
2281
2606
|
* The universal tool-handler contract: returns the plain run summary
|
|
2282
|
-
* (`{ status, count, durable?, fault? }`) on success
|
|
2283
|
-
*
|
|
2284
|
-
*
|
|
2607
|
+
* (`{ status, count, durable?, fault? }`) on success. Malformed authored args throw a `ToolError`
|
|
2608
|
+
* with code `ARGUMENTS` (`@orkestrel/tool`) whose text lists what to correct, carrying
|
|
2609
|
+
* `context.faults`; a wrapped definition the strict contract refuses throws `TOOL`, and an over-deep
|
|
2610
|
+
* or cyclic nested run throws `DEPTH`. Genuine runner `WorkflowError`s are preserved, including
|
|
2611
|
+
* `SCHEDULE` for a target with no task. The `ToolManagerInterface` isolates every throw into the canonical tool result's
|
|
2285
2612
|
* top-level `error`, so nothing escapes the run. `options.lineage` is the propagation carrier
|
|
2286
2613
|
* across a workflow → agent → workflow chain. Opaque `functions` remain host leaves; raw `agents`
|
|
2287
2614
|
* are contextually adapted through {@link createWorkflowFunctions}; native checkpoint persistence
|
|
@@ -2349,20 +2676,26 @@ function createWorkflowTool(definition, runner, options) {
|
|
|
2349
2676
|
description: WORKFLOW_TOOL_DESCRIPTION,
|
|
2350
2677
|
summary: WORKFLOW_TOOL_SUMMARY,
|
|
2351
2678
|
...parameters === void 0 ? {} : { parameters },
|
|
2679
|
+
strict: false,
|
|
2352
2680
|
async execute(args) {
|
|
2353
2681
|
const cloned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(args));
|
|
2354
|
-
if (!cloned.success) throw new
|
|
2355
|
-
const
|
|
2356
|
-
|
|
2357
|
-
|
|
2682
|
+
if (!cloned.success) throw new _orkestrel_tool.ToolError("ARGUMENTS", `Arguments could not be read; send one JSON object such as ${JSON.stringify(WORKFLOW_TOOL_FLAT_EXAMPLE)}`, void 0, { cause: cloned.error });
|
|
2683
|
+
const target = resolveWorkflowTarget(cloned.value, definition, steps, draft);
|
|
2684
|
+
if (!strict.is(target)) {
|
|
2685
|
+
const faults = strict.audit(target);
|
|
2686
|
+
throw new ToolboxError("TOOL", limitRefusal(`workflow ${(0, _orkestrel_contract.preview)(definition.id)} cannot run; send steps to author another one, or correct it:\n${(0, _orkestrel_contract.describeFaults)(faults, strict.schema)}`), {
|
|
2687
|
+
workflow: definition.id,
|
|
2688
|
+
faults
|
|
2689
|
+
});
|
|
2690
|
+
}
|
|
2358
2691
|
const tag = tagWorkflow(target.id);
|
|
2359
|
-
if (lineage.includes(tag)) throw new ToolboxError("DEPTH", `workflow
|
|
2692
|
+
if (lineage.includes(tag)) throw new ToolboxError("DEPTH", `workflow ${(0, _orkestrel_contract.preview)(target.id)} is already an ancestor (cycle); send steps with a new name, or phases with a new id`, {
|
|
2360
2693
|
workflow: target.id,
|
|
2361
2694
|
lineage: [...lineage]
|
|
2362
2695
|
});
|
|
2363
2696
|
const targetLineage = extendLineage(lineage, tag);
|
|
2364
2697
|
const depth = deriveWorkflowDepth(targetLineage);
|
|
2365
|
-
if (depth > 8) throw new ToolboxError("DEPTH", `nested workflow exceeds max depth 8`, {
|
|
2698
|
+
if (depth > 8) throw new ToolboxError("DEPTH", `nested workflow exceeds max depth 8; finish this task without nesting another workflow`, {
|
|
2366
2699
|
workflow: target.id,
|
|
2367
2700
|
depth,
|
|
2368
2701
|
max: 8
|
|
@@ -2395,17 +2728,20 @@ function createWorkflowTool(definition, runner, options) {
|
|
|
2395
2728
|
* active workspace by id (lenient — an unknown id is a no-op reporting `switched: false`, never a
|
|
2396
2729
|
* throw).
|
|
2397
2730
|
*
|
|
2398
|
-
* No-active rule (the ergonomic seam):
|
|
2399
|
-
*
|
|
2400
|
-
*
|
|
2401
|
-
*
|
|
2402
|
-
*
|
|
2403
|
-
*
|
|
2404
|
-
*
|
|
2405
|
-
*
|
|
2406
|
-
*
|
|
2407
|
-
*
|
|
2408
|
-
*
|
|
2731
|
+
* No-active rule (the ergonomic seam): an op that can create the file it names (write / prepend /
|
|
2732
|
+
* append) run when `manager.active` is `undefined` auto-creates and activates a default workspace
|
|
2733
|
+
* (`manager.add()`) so the model can start writing; an op that edits a file that must already exist
|
|
2734
|
+
* (splice / move / remove / replace) refuses with a `TOOL` `ToolboxError` and creates none; a
|
|
2735
|
+
* pure-read op (read / list / has / search) against no active workspace returns the empty result
|
|
2736
|
+
* (`null` / `[]` / `false`), never creating one and never throwing.
|
|
2737
|
+
*
|
|
2738
|
+
* The Tool door enforces the operation union through `contract`, repairing lossless scalar leaves
|
|
2739
|
+
* and refusing malformed args with a `ToolError` carrying code `ARGUMENTS`. A selected operation
|
|
2740
|
+
* reports its own faults; an unknown operation reports a bounded summary of alternatives.
|
|
2741
|
+
* The handler then `switch`es on `op.operation` and returns the plain result
|
|
2742
|
+
* — letting a `WorkspaceError` raised by the live workspace (`MISSING` / `MODALITY` / `PATTERN` /
|
|
2743
|
+
* `RANGE`) propagate uncaught. `read` returns `null` for a path that holds no text, because JSON
|
|
2744
|
+
* tells that from an empty file. The range edit is the flat `'splice'` op: its flat caret integers are
|
|
2409
2745
|
* reassembled into a `Range` (`@orkestrel/workspace`) by `rangeOf` and fed to the workspace's
|
|
2410
2746
|
* ranged `write`.
|
|
2411
2747
|
*
|
|
@@ -2426,15 +2762,13 @@ function createWorkflowTool(definition, runner, options) {
|
|
|
2426
2762
|
function createWorkspaceTool(options) {
|
|
2427
2763
|
const manager = options?.manager ?? (0, _orkestrel_workspace.createWorkspaceManager)(options?.store === void 0 ? void 0 : { store: options.store });
|
|
2428
2764
|
const contract = (0, _orkestrel_contract.createContract)(workspaceToolShape);
|
|
2429
|
-
const parameters = (0, _orkestrel_contract.schemaToParameters)(contract.schema);
|
|
2430
2765
|
return (0, _orkestrel_tool.createTool)({
|
|
2431
2766
|
name: options?.name ?? "workspace",
|
|
2432
2767
|
description: options?.description ?? WORKSPACE_TOOL_DESCRIPTION,
|
|
2433
2768
|
summary: WORKSPACE_TOOL_SUMMARY,
|
|
2434
|
-
|
|
2769
|
+
contract: workspaceToolShape,
|
|
2435
2770
|
execute(args) {
|
|
2436
|
-
const op = contract
|
|
2437
|
-
if (op === void 0) throw new ToolboxError("TOOL", "unknown or malformed operation", { args });
|
|
2771
|
+
const op = requireToolCall(contract, args);
|
|
2438
2772
|
if (op.operation === "workspaces") {
|
|
2439
2773
|
const activeId = manager.active?.id;
|
|
2440
2774
|
return manager.workspaces().map((workspace) => ({
|
|
@@ -2456,7 +2790,7 @@ function createWorkspaceTool(options) {
|
|
|
2456
2790
|
}
|
|
2457
2791
|
const active = manager.active;
|
|
2458
2792
|
switch (op.operation) {
|
|
2459
|
-
case "read": return active?.read(op.path);
|
|
2793
|
+
case "read": return active?.read(op.path) ?? null;
|
|
2460
2794
|
case "list": return (active?.files() ?? []).map((file) => ({
|
|
2461
2795
|
path: file.path,
|
|
2462
2796
|
state: file.state,
|
|
@@ -2470,7 +2804,7 @@ function createWorkspaceTool(options) {
|
|
|
2470
2804
|
...op.sensitive === void 0 ? {} : { sensitive: op.sensitive },
|
|
2471
2805
|
...op.limit === void 0 ? {} : { limit: op.limit }
|
|
2472
2806
|
}) ?? [];
|
|
2473
|
-
case "replace": return (
|
|
2807
|
+
case "replace": return requireActiveWorkspace(manager, op.operation).replace(op.query, op.replacement, {
|
|
2474
2808
|
...op.regex === void 0 ? {} : { regex: op.regex },
|
|
2475
2809
|
...op.sensitive === void 0 ? {} : { sensitive: op.sensitive },
|
|
2476
2810
|
...op.limit === void 0 ? {} : { limit: op.limit }
|
|
@@ -2484,7 +2818,7 @@ function createWorkspaceTool(options) {
|
|
|
2484
2818
|
};
|
|
2485
2819
|
}
|
|
2486
2820
|
case "splice": {
|
|
2487
|
-
const workspace =
|
|
2821
|
+
const workspace = requireActiveWorkspace(manager, op.operation);
|
|
2488
2822
|
workspace.write(op.path, op.content, (0, _orkestrel_workspace.rangeOf)(op.fromLine, op.fromColumn, op.toLine, op.toColumn));
|
|
2489
2823
|
return {
|
|
2490
2824
|
path: op.path,
|
|
@@ -2508,7 +2842,7 @@ function createWorkspaceTool(options) {
|
|
|
2508
2842
|
};
|
|
2509
2843
|
}
|
|
2510
2844
|
case "move": {
|
|
2511
|
-
const workspace =
|
|
2845
|
+
const workspace = requireActiveWorkspace(manager, op.operation);
|
|
2512
2846
|
return {
|
|
2513
2847
|
from: op.from,
|
|
2514
2848
|
to: op.to,
|
|
@@ -2516,7 +2850,7 @@ function createWorkspaceTool(options) {
|
|
|
2516
2850
|
};
|
|
2517
2851
|
}
|
|
2518
2852
|
case "remove": {
|
|
2519
|
-
const workspace =
|
|
2853
|
+
const workspace = requireActiveWorkspace(manager, op.operation);
|
|
2520
2854
|
return {
|
|
2521
2855
|
path: op.path,
|
|
2522
2856
|
removed: workspace.remove(op.path)
|
|
@@ -2531,16 +2865,20 @@ function createWorkspaceTool(options) {
|
|
|
2531
2865
|
* from `registry` and runs it to completion for one delegated `task`.
|
|
2532
2866
|
*
|
|
2533
2867
|
* @remarks
|
|
2534
|
-
* The universal tool-handler contract:
|
|
2535
|
-
* {@link import('./shapers.js').agentToolShape},
|
|
2868
|
+
* The universal tool-handler contract: the Tool door holds the call args to
|
|
2869
|
+
* {@link import('./shapers.js').agentToolShape}, refusing a malformed call with a `ToolError`
|
|
2870
|
+
* carrying code `ARGUMENTS`. The handler assembles an `AgentJobInput` (`task` seeds the
|
|
2536
2871
|
* sub-agent's conversation as a single `user` message; `provider` / `tools` / `system` fall
|
|
2537
|
-
* back to the tool's own {@link import('./types.js').AgentToolOptions} defaults
|
|
2872
|
+
* back to the tool's own {@link import('./types.js').AgentToolOptions} defaults, and `authority` is
|
|
2873
|
+
* the tool's own, never the call's), rehydrates the sub-agent through
|
|
2538
2874
|
* `registry.build`, runs it with `agent.generate()`, and returns the settled
|
|
2539
|
-
* `AgentResult.content` string (the sub-agent's final text). A missing / unresolvable `provider`, or
|
|
2875
|
+
* `AgentResult.content` string (the sub-agent's final text). A missing / unresolvable `provider`, or
|
|
2876
|
+
* tools with no configured `authority`, throws a typed `TOOL`
|
|
2540
2877
|
* {@link import('./errors.js').ToolboxError}; a delegation that would exceed
|
|
2541
2878
|
* {@link import('./constants.js').AGENT_TOOL_DEPTH}, or re-enter an already-delegated agent (a
|
|
2542
2879
|
* cycle), throws a typed `DEPTH` {@link import('./errors.js').ToolboxError}, each isolated
|
|
2543
|
-
* by the `ToolManagerInterface` into the canonical tool result's top-level `error`.
|
|
2880
|
+
* by the `ToolManagerInterface` into the canonical tool result's top-level `error`. A `depth` that
|
|
2881
|
+
* is not a nonnegative safe integer throws `TOOL` at construction.
|
|
2544
2882
|
*
|
|
2545
2883
|
* `AgentInterface` (`@orkestrel/agent`) exposes no teardown method — a bound sub-agent's
|
|
2546
2884
|
* lifetime is the single `generate()` call this handler awaits; there is nothing to release
|
|
@@ -2567,30 +2905,33 @@ function createWorkspaceTool(options) {
|
|
|
2567
2905
|
*/
|
|
2568
2906
|
function createAgentTool(registry, options) {
|
|
2569
2907
|
const contract = (0, _orkestrel_contract.createContract)(agentToolShape);
|
|
2570
|
-
const parameters = (0, _orkestrel_contract.schemaToParameters)(contract.schema);
|
|
2571
2908
|
const depth = options?.depth ?? 0;
|
|
2909
|
+
if (!(0, _orkestrel_contract.isNonNegativeInteger)(depth) || !Number.isSafeInteger(depth)) throw new ToolboxError("TOOL", `agent tool depth must be a nonnegative safe integer; received ${(0, _orkestrel_contract.preview)(depth)}; omit it for a direct root`, { received: (0, _orkestrel_contract.preview)(depth) });
|
|
2572
2910
|
const ancestry = options?.ancestry ?? [];
|
|
2573
2911
|
return (0, _orkestrel_tool.createTool)({
|
|
2574
2912
|
name: options?.name ?? "agent",
|
|
2575
2913
|
description: options?.description ?? AGENT_TOOL_DESCRIPTION,
|
|
2576
2914
|
summary: AGENT_TOOL_SUMMARY,
|
|
2577
|
-
|
|
2915
|
+
contract: agentToolShape,
|
|
2578
2916
|
async execute(args) {
|
|
2579
|
-
const call = contract
|
|
2580
|
-
if (call === void 0) throw new ToolboxError("TOOL", "malformed agent-delegation call", { args });
|
|
2917
|
+
const call = requireToolCall(contract, args);
|
|
2581
2918
|
const provider = call.provider ?? options?.provider;
|
|
2582
|
-
if (provider === void 0) throw new ToolboxError("TOOL", "no provider resolved for the delegated agent", { task: call.task });
|
|
2583
|
-
if (depth + 1 > 8) throw new ToolboxError("DEPTH", `delegation exceeds max agent depth 8`, {
|
|
2919
|
+
if (provider === void 0) throw new ToolboxError("TOOL", "no provider resolved for the delegated agent; send provider as a registered provider key", { task: call.task });
|
|
2920
|
+
if (depth + 1 > 8) throw new ToolboxError("DEPTH", `delegation exceeds max agent depth 8; carry out this task without delegating`, {
|
|
2584
2921
|
provider,
|
|
2585
2922
|
depth,
|
|
2586
2923
|
max: 8
|
|
2587
2924
|
});
|
|
2588
2925
|
const tag = tagAgent(provider);
|
|
2589
|
-
if (ancestry.includes(tag)) throw new ToolboxError("DEPTH", `agent
|
|
2926
|
+
if (ancestry.includes(tag)) throw new ToolboxError("DEPTH", `agent ${(0, _orkestrel_contract.preview)(provider)} is already an ancestor (cycle); send another provider, or carry out this task without delegating`, {
|
|
2590
2927
|
provider,
|
|
2591
2928
|
ancestry: [...ancestry]
|
|
2592
2929
|
});
|
|
2593
2930
|
const tools = call.tools ?? options?.tools;
|
|
2931
|
+
if (tools !== void 0 && tools.length > 0 && options?.authority === void 0) throw new ToolboxError("TOOL", "this agent tool has no authority to decide tool calls, so its sub-agent can load no tools; send tools: [] to delegate with none, or have the host set authority on this tool", {
|
|
2932
|
+
provider,
|
|
2933
|
+
tools: [...tools]
|
|
2934
|
+
});
|
|
2594
2935
|
const system = call.system ?? options?.system;
|
|
2595
2936
|
const agent = registry.build({
|
|
2596
2937
|
provider,
|
|
@@ -2599,7 +2940,8 @@ function createAgentTool(registry, options) {
|
|
|
2599
2940
|
content: call.task
|
|
2600
2941
|
}],
|
|
2601
2942
|
...system === void 0 ? {} : { system },
|
|
2602
|
-
...tools === void 0 ? {} : { tools }
|
|
2943
|
+
...tools === void 0 ? {} : { tools },
|
|
2944
|
+
...options?.authority === void 0 ? {} : { authority: options.authority }
|
|
2603
2945
|
});
|
|
2604
2946
|
const result = await agent.generate();
|
|
2605
2947
|
if (options?.store !== void 0) {
|
|
@@ -2620,12 +2962,13 @@ function createAgentTool(registry, options) {
|
|
|
2620
2962
|
* description when `summary` is set, keeping the advertised tool list compact for a small model.
|
|
2621
2963
|
* This tool is the on-demand expansion seam: given a registered tool's `name`, it looks the tool
|
|
2622
2964
|
* up through `tools.tool(name)` and returns its full `description` (falling back to `summary` when a
|
|
2623
|
-
* tool has no `description` of its own, then
|
|
2965
|
+
* tool has no `description` of its own, then an explicit `null` when it has neither).
|
|
2624
2966
|
*
|
|
2625
|
-
* The universal tool-handler contract:
|
|
2626
|
-
* {@link import('./shapers.js').describeToolShape},
|
|
2627
|
-
*
|
|
2628
|
-
*
|
|
2967
|
+
* The universal tool-handler contract: the Tool door holds the call args to
|
|
2968
|
+
* {@link import('./shapers.js').describeToolShape}, refusing a malformed call with a `ToolError`
|
|
2969
|
+
* carrying code `ARGUMENTS`. The handler returns the plain description on success, and throws a typed
|
|
2970
|
+
* `TOOL` {@link import('./errors.js').ToolboxError} that lists the registered names on an unknown
|
|
2971
|
+
* tool name.
|
|
2629
2972
|
*
|
|
2630
2973
|
* @param tools - The `ToolManagerInterface` (`@orkestrel/tool`) whose registered tools this
|
|
2631
2974
|
* tool can describe
|
|
@@ -2645,18 +2988,22 @@ function createAgentTool(registry, options) {
|
|
|
2645
2988
|
*/
|
|
2646
2989
|
function createDescribeTool(tools) {
|
|
2647
2990
|
const contract = (0, _orkestrel_contract.createContract)(describeToolShape);
|
|
2648
|
-
const parameters = (0, _orkestrel_contract.schemaToParameters)(contract.schema);
|
|
2649
2991
|
return (0, _orkestrel_tool.createTool)({
|
|
2650
2992
|
name: DESCRIBE_TOOL_NAME,
|
|
2651
2993
|
description: DESCRIBE_TOOL_DESCRIPTION,
|
|
2652
2994
|
summary: DESCRIBE_TOOL_SUMMARY,
|
|
2653
|
-
|
|
2995
|
+
contract: describeToolShape,
|
|
2654
2996
|
async execute(args) {
|
|
2655
|
-
const call = contract
|
|
2656
|
-
if (call === void 0) throw new ToolboxError("TOOL", "malformed describe call", { args });
|
|
2997
|
+
const call = requireToolCall(contract, args);
|
|
2657
2998
|
const tool = tools.tool(call.name);
|
|
2658
|
-
if (tool === void 0)
|
|
2659
|
-
|
|
2999
|
+
if (tool === void 0) {
|
|
3000
|
+
const names = tools.tools().map((registered) => registered.name);
|
|
3001
|
+
throw new ToolboxError("TOOL", `unknown tool ${(0, _orkestrel_contract.preview)(call.name)}; send name as one of ${renderCandidates(names)}`, {
|
|
3002
|
+
name: call.name,
|
|
3003
|
+
names
|
|
3004
|
+
});
|
|
3005
|
+
}
|
|
3006
|
+
return tool.description ?? tool.summary ?? null;
|
|
2660
3007
|
}
|
|
2661
3008
|
});
|
|
2662
3009
|
}
|
|
@@ -2665,8 +3012,9 @@ function createDescribeTool(tools) {
|
|
|
2665
3012
|
* and blocks until the addressed terminal answers, returning the resolved values record.
|
|
2666
3013
|
*
|
|
2667
3014
|
* @remarks
|
|
2668
|
-
* The universal tool-handler contract:
|
|
2669
|
-
* {@link import('./shapers.js').promptToolShape},
|
|
3015
|
+
* The universal tool-handler contract: the Tool door holds the call args to
|
|
3016
|
+
* {@link import('./shapers.js').promptToolShape}, refusing a malformed call with a `ToolError`
|
|
3017
|
+
* carrying code `ARGUMENTS`. The handler parses the call's schema through
|
|
2670
3018
|
* `@orkestrel/form`, constructs the live form, and passes it to `TerminalManagerInterface.ask`.
|
|
2671
3019
|
* `from` is fixed at construction
|
|
2672
3020
|
* ({@link import('./types.js').PromptToolOptions.from}) — never read from the model-supplied
|
|
@@ -2674,7 +3022,7 @@ function createDescribeTool(tools) {
|
|
|
2674
3022
|
* `TerminalError('DEADLOCK')`, re-surfaced as a typed `DEADLOCK`
|
|
2675
3023
|
* {@link import('./errors.js').ToolboxError}; an expired form re-surfaces as `EXPIRE`; an
|
|
2676
3024
|
* unknown `to` (or any other `TerminalError`) re-surfaces as `TOOL`, naming the unknown terminal
|
|
2677
|
-
* and
|
|
3025
|
+
* and the mounted terminals the manager's refusal lists, encoded and bounded. A schema `parseForm` refuses — an
|
|
2678
3026
|
* unknown control, a duplicate field name, a `'select'` or `'checkbox'` field without usable
|
|
2679
3027
|
* `choices` — throws a typed `TOOL` error up front, so a malformed schema never parks as a form
|
|
2680
3028
|
* nobody can answer.
|
|
@@ -2699,37 +3047,40 @@ function createDescribeTool(tools) {
|
|
|
2699
3047
|
*/
|
|
2700
3048
|
function createPromptTool(options) {
|
|
2701
3049
|
const contract = (0, _orkestrel_contract.createContract)(promptToolShape);
|
|
2702
|
-
const parameters = (0, _orkestrel_contract.schemaToParameters)(contract.schema);
|
|
2703
3050
|
return (0, _orkestrel_tool.createTool)({
|
|
2704
3051
|
name: options.name ?? "ask",
|
|
2705
3052
|
description: options.description ?? PROMPT_TOOL_DESCRIPTION,
|
|
2706
3053
|
summary: PROMPT_TOOL_SUMMARY,
|
|
2707
|
-
|
|
3054
|
+
contract: promptToolShape,
|
|
2708
3055
|
async execute(args) {
|
|
2709
|
-
const call = contract
|
|
2710
|
-
if (call === void 0) throw new ToolboxError("TOOL", "malformed ask call", { args });
|
|
3056
|
+
const call = requireToolCall(contract, args);
|
|
2711
3057
|
const schema = (0, _orkestrel_form.parseForm)(call.schema);
|
|
2712
|
-
if (schema === void 0) throw new ToolboxError("TOOL",
|
|
2713
|
-
for (const field of schema.fields) if ((field.control === "select" || field.control === "checkbox") && !field.choices.some((choice) => choice.disabled !== true)) throw new ToolboxError("TOOL", `${field.control} field
|
|
3058
|
+
if (schema === void 0) throw new ToolboxError("TOOL", `malformed form schema; send { "fields": [{ "control", "name" }] } with unique names, where control is one of ${_orkestrel_form.FIELD_CONTROLS.join(", ")}, and select or checkbox carries choices of { "label", "value" }`, { schema: call.schema });
|
|
3059
|
+
for (const field of schema.fields) if ((field.control === "select" || field.control === "checkbox") && !field.choices.some((choice) => choice.disabled !== true)) throw new ToolboxError("TOOL", `${field.control} field ${(0, _orkestrel_contract.preview)(field.name)} has no enabled choices; add a choice that is not disabled`, {
|
|
2714
3060
|
control: field.control,
|
|
2715
3061
|
field: field.name
|
|
2716
3062
|
});
|
|
2717
3063
|
try {
|
|
2718
3064
|
return await options.manager.ask(options.from, call.to, (0, _orkestrel_form.createForm)(schema));
|
|
2719
3065
|
} catch (error) {
|
|
2720
|
-
if ((0, _orkestrel_form.isFormError)(error) && error.code === "ABANDONED") throw new ToolboxError("EXPIRE", `form to
|
|
3066
|
+
if ((0, _orkestrel_form.isFormError)(error) && error.code === "ABANDONED") throw new ToolboxError("EXPIRE", `form to ${(0, _orkestrel_contract.preview)(call.to)} expired before it was answered; ask again to give it more time`, { to: call.to });
|
|
2721
3067
|
const code = inferTerminalCode(error);
|
|
2722
3068
|
if (code === void 0) throw error;
|
|
2723
|
-
if (code === "DEADLOCK") throw new ToolboxError("DEADLOCK", `asking
|
|
3069
|
+
if (code === "DEADLOCK") throw new ToolboxError("DEADLOCK", `asking ${(0, _orkestrel_contract.preview)(call.to)} would create a form cycle; answer the form addressed to you first`, (0, _orkestrel_terminal.isTerminalError)(error) ? error.context : {
|
|
2724
3070
|
from: options.from,
|
|
2725
3071
|
to: call.to
|
|
2726
3072
|
});
|
|
2727
|
-
if (code === "EXPIRE") throw new ToolboxError("EXPIRE", `form to
|
|
2728
|
-
if ((0, _orkestrel_terminal.isTerminalError)(error) && error.code === "TARGET")
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
3073
|
+
if (code === "EXPIRE") throw new ToolboxError("EXPIRE", `form to ${(0, _orkestrel_contract.preview)(call.to)} expired before it was answered; ask again to give it more time`, { to: call.to });
|
|
3074
|
+
if ((0, _orkestrel_terminal.isTerminalError)(error) && error.code === "TARGET") {
|
|
3075
|
+
const listed = error.context?.known;
|
|
3076
|
+
const known = (0, _orkestrel_contract.arrayOf)(_orkestrel_contract.isString)(listed) ? listed : [];
|
|
3077
|
+
throw new ToolboxError("TOOL", `unknown terminal ${(0, _orkestrel_contract.preview)(call.to)}; ${known.length === 0 ? "no mounted terminal name is available" : `send to as one of ${renderCandidates(known)}`}`, {
|
|
3078
|
+
to: call.to,
|
|
3079
|
+
known,
|
|
3080
|
+
count: options.manager.count
|
|
3081
|
+
});
|
|
3082
|
+
}
|
|
3083
|
+
throw new ToolboxError("TOOL", `asking ${(0, _orkestrel_contract.preview)(call.to)} failed; ask again`, { to: call.to });
|
|
2733
3084
|
}
|
|
2734
3085
|
}
|
|
2735
3086
|
});
|
|
@@ -2739,8 +3090,9 @@ function createPromptTool(options) {
|
|
|
2739
3090
|
* addressed to {@link import('./types.js').AnswerToolOptions.to}, or answers one of them by id.
|
|
2740
3091
|
*
|
|
2741
3092
|
* @remarks
|
|
2742
|
-
* The universal tool-handler contract:
|
|
2743
|
-
* {@link import('./shapers.js').answerToolShape} (discriminated by `operation`)
|
|
3093
|
+
* The universal tool-handler contract: the Tool door holds the call args to
|
|
3094
|
+
* {@link import('./shapers.js').answerToolShape} (discriminated by `operation`), refusing malformed
|
|
3095
|
+
* args with a `ToolError` carrying code `ARGUMENTS`. `'pending'`
|
|
2744
3096
|
* returns a compact list (`{ id, from, schema }`) of every form addressed to `to`.
|
|
2745
3097
|
* `'answer'` looks the form up by `id`, narrows the supplied `values` through `@orkestrel/form`,
|
|
2746
3098
|
* and applies it through
|
|
@@ -2771,31 +3123,32 @@ function createPromptTool(options) {
|
|
|
2771
3123
|
*/
|
|
2772
3124
|
function createAnswerTool(options) {
|
|
2773
3125
|
const contract = (0, _orkestrel_contract.createContract)(answerToolShape);
|
|
2774
|
-
const parameters = (0, _orkestrel_contract.schemaToParameters)(contract.schema);
|
|
2775
3126
|
return (0, _orkestrel_tool.createTool)({
|
|
2776
3127
|
name: options.name ?? "answer",
|
|
2777
3128
|
description: options.description ?? ANSWER_TOOL_DESCRIPTION,
|
|
2778
3129
|
summary: ANSWER_TOOL_SUMMARY,
|
|
2779
|
-
|
|
3130
|
+
contract: answerToolShape,
|
|
2780
3131
|
async execute(args) {
|
|
2781
|
-
const call = contract
|
|
2782
|
-
if (call === void 0) throw new ToolboxError("TOOL", "malformed answer call", { args });
|
|
3132
|
+
const call = requireToolCall(contract, args);
|
|
2783
3133
|
if (call.operation === "pending") return options.manager.pending(options.to).map((form) => ({
|
|
2784
3134
|
id: form.id,
|
|
2785
3135
|
from: form.from,
|
|
2786
3136
|
schema: form.schema
|
|
2787
3137
|
}));
|
|
2788
|
-
if (options.manager.pending(options.to).find((entry) => entry.id === call.id) === void 0) throw new ToolboxError("ANSWER", `unknown form
|
|
3138
|
+
if (options.manager.pending(options.to).find((entry) => entry.id === call.id) === void 0) throw new ToolboxError("ANSWER", `unknown form ${(0, _orkestrel_contract.preview)(call.id)}; call { "operation": "pending" } for the ids you can answer`, {
|
|
2789
3139
|
id: call.id,
|
|
2790
3140
|
reason: "unknown"
|
|
2791
3141
|
});
|
|
2792
|
-
if (!(0, _orkestrel_form.isFormValues)(call.values)) throw new ToolboxError("TOOL", "malformed form values", { values: call.values });
|
|
3142
|
+
if (!(0, _orkestrel_form.isFormValues)(call.values)) throw new ToolboxError("TOOL", "malformed form values; send values as a record of each field name to a string, a finite number, a boolean, or a list of strings", { values: call.values });
|
|
2793
3143
|
const result = options.manager.answer(options.to, call.id, call.values);
|
|
2794
|
-
if (!result.success)
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
3144
|
+
if (!result.success) {
|
|
3145
|
+
const detail = result.error.reason === "rejected" ? `; correct these fields and answer again: ${(0, _orkestrel_contract.renderList)(result.error.errors.map((fault) => `${(0, _orkestrel_contract.preview)(fault.field)}: ${(0, _orkestrel_contract.preview)(fault.message)}`))}` : "; call { \"operation\": \"pending\" } for the ids you can answer";
|
|
3146
|
+
throw new ToolboxError("ANSWER", `failed to answer form ${(0, _orkestrel_contract.preview)(call.id)}: ${result.error.reason}${detail}`, {
|
|
3147
|
+
id: call.id,
|
|
3148
|
+
reason: result.error.reason,
|
|
3149
|
+
...result.error.reason === "rejected" ? { errors: result.error.errors } : {}
|
|
3150
|
+
});
|
|
3151
|
+
}
|
|
2799
3152
|
return { answered: call.id };
|
|
2800
3153
|
}
|
|
2801
3154
|
});
|
|
@@ -2848,9 +3201,11 @@ function createDatabaseDefinitionStore(driver = (0, _orkestrel_database.createMe
|
|
|
2848
3201
|
* {@link createWorkspaceTool}'s single-tool-many-operations shape).
|
|
2849
3202
|
*
|
|
2850
3203
|
* @remarks
|
|
2851
|
-
* The universal tool-handler contract:
|
|
2852
|
-
* {@link import('./shapers.js').databaseToolShape},
|
|
2853
|
-
*
|
|
3204
|
+
* The universal tool-handler contract: the Tool door holds the call args to
|
|
3205
|
+
* {@link import('./shapers.js').databaseToolShape}, refusing malformed args with a `ToolError`
|
|
3206
|
+
* carrying code `ARGUMENTS`. The handler dispatches to the matching operation and
|
|
3207
|
+
* returns a plain result on success. A row a `'get'` misses is an explicit `null`. A `limit` that is
|
|
3208
|
+
* not a positive safe integer throws `TOOL` at construction. A database is resolved lazily and cached for the tool's
|
|
2854
3209
|
* lifetime — `'create'` mints one from `tables` ({@link import('./compilers.js').expandTables}) and
|
|
2855
3210
|
* a registered `driver` key ({@link import('./types.js').DatabaseToolOptions.drivers}, default
|
|
2856
3211
|
* `{ memory: () => createMemoryDriver() }`); any other operation addressing an uncached id falls
|
|
@@ -2908,30 +3263,28 @@ function createDatabaseDefinitionStore(driver = (0, _orkestrel_database.createMe
|
|
|
2908
3263
|
function createDatabaseTool(options = {}) {
|
|
2909
3264
|
if (options.timeout !== void 0 && (options.timeout < 0 || !(0, _orkestrel_contract.isNumber)(options.timeout) || !Number.isSafeInteger(options.timeout))) throw new ToolboxError("TOOL", "database timeout must be a nonnegative safe integer", { timeout: options.timeout });
|
|
2910
3265
|
const contract = (0, _orkestrel_contract.createContract)(databaseToolShape);
|
|
2911
|
-
const parameters = (0, _orkestrel_contract.schemaToParameters)(contract.schema);
|
|
2912
3266
|
const handles = new Map(Object.entries(options.databases ?? {}));
|
|
2913
3267
|
const drivers = options.drivers ?? { memory: _orkestrel_database.createMemoryDriver };
|
|
2914
3268
|
const generator = options.generator;
|
|
2915
|
-
const cap = options.limit
|
|
3269
|
+
const cap = resolveBound(options.limit, DATABASE_TOOL_LIMIT, "database limit");
|
|
2916
3270
|
const store = options.store;
|
|
2917
3271
|
const resolver = store === void 0 ? new DatabaseResolver(handles, drivers, generator) : new DatabaseResolver(handles, drivers, generator, store);
|
|
2918
3272
|
return (0, _orkestrel_tool.createTool)({
|
|
2919
3273
|
name: options.name ?? "database",
|
|
2920
3274
|
description: options.description ?? DATABASE_TOOL_DESCRIPTION,
|
|
2921
3275
|
summary: DATABASE_TOOL_SUMMARY,
|
|
2922
|
-
|
|
3276
|
+
contract: databaseToolShape,
|
|
2923
3277
|
async execute(args) {
|
|
2924
|
-
const call = contract
|
|
2925
|
-
if (
|
|
2926
|
-
if (options.readonly === true && DATABASE_TOOL_MUTATIONS.includes(call.operation)) throw new ToolboxError("TOOL", `operation '${call.operation}' is disabled in readonly mode`, { operation: call.operation });
|
|
3278
|
+
const call = requireToolCall(contract, args);
|
|
3279
|
+
if (options.readonly === true && DATABASE_TOOL_MUTATIONS.includes(call.operation)) throw new ToolboxError("TOOL", `operation '${call.operation}' is disabled in readonly mode; call tables, get, records, count, or aggregate`, { operation: call.operation });
|
|
2927
3280
|
const read = options.timeout === void 0 ? void 0 : { signal: AbortSignal.timeout(options.timeout) };
|
|
2928
3281
|
try {
|
|
2929
3282
|
switch (call.operation) {
|
|
2930
3283
|
case "create": {
|
|
2931
|
-
if (resolver.has(call.id) || store !== void 0 && await store.get(call.id) !== void 0) throw new ToolboxError("TOOL", `database
|
|
3284
|
+
if (resolver.has(call.id) || store !== void 0 && await store.get(call.id) !== void 0) throw new ToolboxError("TOOL", `database ${(0, _orkestrel_contract.preview)(call.id)} already exists; use it, destroy it first, or send another id`, { id: call.id });
|
|
2932
3285
|
const name = call.driver ?? "memory";
|
|
2933
3286
|
const factory = drivers[name];
|
|
2934
|
-
if (factory === void 0) throw new ToolboxError("TOOL", `unknown driver
|
|
3287
|
+
if (factory === void 0) throw new ToolboxError("TOOL", `unknown driver ${(0, _orkestrel_contract.preview)(name)}; send driver as one of ${renderCandidates(Object.keys(drivers))}`, {
|
|
2935
3288
|
id: call.id,
|
|
2936
3289
|
driver: name
|
|
2937
3290
|
});
|
|
@@ -2978,7 +3331,7 @@ function createDatabaseTool(options = {}) {
|
|
|
2978
3331
|
const table = (await resolver.resolve(call.id)).table(call.table);
|
|
2979
3332
|
const many = (0, _orkestrel_contract.isArray)(call.key);
|
|
2980
3333
|
const keys = (0, _orkestrel_contract.isArray)(call.key) ? call.key : [call.key];
|
|
2981
|
-
const rows = await table.get(keys);
|
|
3334
|
+
const rows = (await table.get(keys)).map((row) => row ?? null);
|
|
2982
3335
|
return many ? { rows } : { row: rows[0] };
|
|
2983
3336
|
}
|
|
2984
3337
|
case "records": {
|
|
@@ -3043,7 +3396,7 @@ function createDatabaseTool(options = {}) {
|
|
|
3043
3396
|
if (isToolboxError(error)) throw error;
|
|
3044
3397
|
const code = inferDatabaseCode(error);
|
|
3045
3398
|
if (code === void 0) throw error;
|
|
3046
|
-
throw new ToolboxError("DATABASE", (0, _orkestrel_contract.isError)(error) ? error.message : String(error), {
|
|
3399
|
+
throw new ToolboxError("DATABASE", limitRefusal((0, _orkestrel_contract.isError)(error) ? error.message : String(error)), {
|
|
3047
3400
|
code,
|
|
3048
3401
|
operation: call.operation,
|
|
3049
3402
|
id: call.id,
|
|
@@ -3059,8 +3412,10 @@ function createDatabaseTool(options = {}) {
|
|
|
3059
3412
|
* single-tool-many-operations shape).
|
|
3060
3413
|
*
|
|
3061
3414
|
* @remarks
|
|
3062
|
-
* The universal tool-handler contract:
|
|
3063
|
-
* {@link import('./shapers.js').relationToolShape},
|
|
3415
|
+
* The universal tool-handler contract: the Tool door holds the call args to
|
|
3416
|
+
* {@link import('./shapers.js').relationToolShape}, refusing malformed args with a `ToolError`
|
|
3417
|
+
* carrying code `ARGUMENTS`. A `limit` or `depth` that is not a positive safe integer throws `TOOL` at
|
|
3418
|
+
* construction, and a row `'load'` misses is an explicit `null`. The handler resolves the addressed
|
|
3064
3419
|
* {@link import('@orkestrel/relation').RelationManagerInterface} — an explicit `manager` field
|
|
3065
3420
|
* must match a key of {@link import('./types.js').RelationToolOptions.managers}, an omitted one
|
|
3066
3421
|
* resolves to the sole registered manager, either miss throwing a typed `TOOL`
|
|
@@ -3106,24 +3461,22 @@ function createDatabaseTool(options = {}) {
|
|
|
3106
3461
|
*/
|
|
3107
3462
|
function createRelationTool(options) {
|
|
3108
3463
|
const contract = (0, _orkestrel_contract.createContract)(relationToolShape);
|
|
3109
|
-
const
|
|
3110
|
-
const
|
|
3111
|
-
const cap = options.limit ?? 1e3;
|
|
3464
|
+
const depth = resolveBound(options.depth, 3, "relation depth");
|
|
3465
|
+
const cap = resolveBound(options.limit, RELATION_TOOL_LIMIT, "relation limit");
|
|
3112
3466
|
return (0, _orkestrel_tool.createTool)({
|
|
3113
3467
|
name: options.name ?? "relation",
|
|
3114
3468
|
description: options.description ?? RELATION_TOOL_DESCRIPTION,
|
|
3115
3469
|
summary: RELATION_TOOL_SUMMARY,
|
|
3116
|
-
|
|
3470
|
+
contract: relationToolShape,
|
|
3117
3471
|
async execute(args) {
|
|
3118
|
-
const call = contract
|
|
3119
|
-
if (call === void 0) throw new ToolboxError("TOOL", "malformed relation call", { args });
|
|
3472
|
+
const call = requireToolCall(contract, args);
|
|
3120
3473
|
try {
|
|
3121
3474
|
const model = resolveRelationModel(resolveRelationManager(options.managers, call.manager), call.model);
|
|
3122
3475
|
switch (call.operation) {
|
|
3123
3476
|
case "load": {
|
|
3124
3477
|
const include = expandInclude(call.include, depth);
|
|
3125
|
-
if ((0, _orkestrel_contract.isString)(call.key) || (0, _orkestrel_contract.isNumber)(call.key)) return { row: await model.load(call.key, include) };
|
|
3126
|
-
return { rows: await model.load(call.key, include) };
|
|
3478
|
+
if ((0, _orkestrel_contract.isString)(call.key) || (0, _orkestrel_contract.isNumber)(call.key)) return { row: await model.load(call.key, include) ?? null };
|
|
3479
|
+
return { rows: (await model.load(call.key, include)).map((row) => row ?? null) };
|
|
3127
3480
|
}
|
|
3128
3481
|
case "find": {
|
|
3129
3482
|
const include = expandInclude(call.include, depth);
|
|
@@ -3165,7 +3518,7 @@ function createRelationTool(options) {
|
|
|
3165
3518
|
} catch (error) {
|
|
3166
3519
|
if (isToolboxError(error)) throw error;
|
|
3167
3520
|
const relation = inferRelationCode(error);
|
|
3168
|
-
if (relation !== void 0) throw new ToolboxError("RELATION", (0, _orkestrel_contract.isError)(error) ? error.message : String(error), {
|
|
3521
|
+
if (relation !== void 0) throw new ToolboxError("RELATION", limitRefusal((0, _orkestrel_contract.isError)(error) ? error.message : String(error)), {
|
|
3169
3522
|
code: relation,
|
|
3170
3523
|
operation: call.operation,
|
|
3171
3524
|
model: call.model,
|
|
@@ -3173,7 +3526,7 @@ function createRelationTool(options) {
|
|
|
3173
3526
|
});
|
|
3174
3527
|
const database = inferDatabaseCode(error);
|
|
3175
3528
|
if (database === void 0) throw error;
|
|
3176
|
-
throw new ToolboxError("DATABASE", (0, _orkestrel_contract.isError)(error) ? error.message : String(error), {
|
|
3529
|
+
throw new ToolboxError("DATABASE", limitRefusal((0, _orkestrel_contract.isError)(error) ? error.message : String(error)), {
|
|
3177
3530
|
code: database,
|
|
3178
3531
|
operation: call.operation
|
|
3179
3532
|
});
|
|
@@ -3187,14 +3540,13 @@ function createRelationTool(options) {
|
|
|
3187
3540
|
* {@link createEndpointTool}, wraps one concrete endpoint).
|
|
3188
3541
|
*
|
|
3189
3542
|
* @remarks
|
|
3190
|
-
* The universal tool-handler contract:
|
|
3543
|
+
* The universal tool-handler contract: the Tool door holds the call args to
|
|
3191
3544
|
* {@link import('./shapers.js').inferToolShape} (`samples` non-empty, `format` / `enum` optional
|
|
3192
|
-
* booleans, `candidates` an optional array),
|
|
3545
|
+
* booleans, `candidates` an optional array), refusing malformed args with a `ToolError` carrying code
|
|
3546
|
+
* `ARGUMENTS`. The handler infers a schema through `@orkestrel/contract`'s
|
|
3193
3547
|
* `samplesToSchema`, wraps a non-object root as `{ value: <schema> }` through `schemaToObject` (mirrors
|
|
3194
3548
|
* the tool-parameters convention every other `create*Tool` factory advertises), and returns the
|
|
3195
|
-
* resulting parameters record.
|
|
3196
|
-
* `contract.parse` returns `undefined` and the handler throws a typed `TOOL`
|
|
3197
|
-
* {@link import('./errors.js').ToolboxError}.
|
|
3549
|
+
* resulting parameters record.
|
|
3198
3550
|
*
|
|
3199
3551
|
* When `candidates` is absent, the return is the bare parameters record — unchanged from before
|
|
3200
3552
|
* this array existed. When `candidates` is present (any array, including empty), the handler
|
|
@@ -3209,18 +3561,14 @@ function createRelationTool(options) {
|
|
|
3209
3561
|
* normalizing parse accept this value", that is would {@link createEndpointTool}'s default enforcement
|
|
3210
3562
|
* admit it (`checker.parse(candidate) !== undefined`) — computed for every candidate regardless of
|
|
3211
3563
|
* `valid`; by the house parse/guard round-trip, a `valid: true` entry is
|
|
3212
|
-
* always also `coercible: true`.
|
|
3213
|
-
*
|
|
3214
|
-
*
|
|
3215
|
-
*
|
|
3216
|
-
*
|
|
3217
|
-
* (a boolean in a string slot), a missing required key, or an out-of-enum value — where
|
|
3218
|
-
* `coercible: false`. `checker.is` / `.parse` / `.explain` are all total over JSON-safe input — a
|
|
3219
|
-
* JSON-safe hostile candidate (a `__proto__`-carrying object, deeply nested data) reaches each of them
|
|
3564
|
+
* always also `coercible: true`. `faults` is `checker.audit(candidate)`, which reports against `is`'s
|
|
3565
|
+
* strictness: a strictly-invalid but coercible candidate (`7` against a string slot) yields
|
|
3566
|
+
* `{ valid: false, coercible: true, faults: [{ reason: 'type', ... }] }`, so every invalid candidate
|
|
3567
|
+
* names why, an undeclared key included. `checker.is` / `.parse` / `.audit` are all total over
|
|
3568
|
+
* JSON-safe input — a JSON-safe hostile candidate (a `__proto__`-carrying object, deeply nested data) reaches each of them
|
|
3220
3569
|
* and yields a bounded, non-throwing per-candidate verdict; a non-JSON-safe candidate (for example a
|
|
3221
|
-
* throwing-getter `Proxy`) never reaches the checker at all —
|
|
3222
|
-
*
|
|
3223
|
-
* `TOOL` {@link import('./errors.js').ToolboxError} a malformed `samples`/`format`/`enum` throws,
|
|
3570
|
+
* throwing-getter `Proxy`) never reaches the checker at all — the Tool door refuses the whole call
|
|
3571
|
+
* with the same `ToolError` a malformed `samples`/`format`/`enum` throws,
|
|
3224
3572
|
* with no per-candidate verdict produced.
|
|
3225
3573
|
*
|
|
3226
3574
|
* @param options - Advertised `name` / `description` overrides (see
|
|
@@ -3260,21 +3608,19 @@ function createRelationTool(options) {
|
|
|
3260
3608
|
*/
|
|
3261
3609
|
function createInferTool(options) {
|
|
3262
3610
|
const contract = (0, _orkestrel_contract.createContract)(inferToolShape);
|
|
3263
|
-
const parameters = (0, _orkestrel_contract.schemaToParameters)(contract.schema);
|
|
3264
3611
|
return (0, _orkestrel_tool.createTool)({
|
|
3265
3612
|
name: options?.name ?? "infer",
|
|
3266
3613
|
description: options?.description ?? INFER_TOOL_DESCRIPTION,
|
|
3267
3614
|
summary: INFER_TOOL_SUMMARY,
|
|
3268
|
-
|
|
3615
|
+
contract: inferToolShape,
|
|
3269
3616
|
async execute(args) {
|
|
3270
|
-
const parsed = contract
|
|
3271
|
-
if (parsed === void 0) throw new ToolboxError("TOOL", "malformed infer arguments", { args });
|
|
3617
|
+
const parsed = requireToolCall(contract, args);
|
|
3272
3618
|
const schema = (0, _orkestrel_contract.samplesToSchema)(parsed.samples, {
|
|
3273
3619
|
format: parsed.format ?? false,
|
|
3274
3620
|
enum: parsed.enum ?? false
|
|
3275
3621
|
});
|
|
3276
3622
|
const result = (0, _orkestrel_contract.schemaToParameters)((0, _orkestrel_contract.schemaToObject)(schema));
|
|
3277
|
-
if (result === void 0) throw new ToolboxError("TOOL", "could not infer a schema", { args });
|
|
3623
|
+
if (result === void 0) throw new ToolboxError("TOOL", "could not infer a schema; send samples that are JSON values of one shape", { args });
|
|
3278
3624
|
if (parsed.candidates === void 0) return result;
|
|
3279
3625
|
const checker = (0, _orkestrel_contract.createContract)((0, _orkestrel_contract.schemaToShape)(schema));
|
|
3280
3626
|
return {
|
|
@@ -3290,7 +3636,7 @@ function createInferTool(options) {
|
|
|
3290
3636
|
index,
|
|
3291
3637
|
valid,
|
|
3292
3638
|
coercible,
|
|
3293
|
-
faults: checker.
|
|
3639
|
+
faults: checker.audit(candidate)
|
|
3294
3640
|
};
|
|
3295
3641
|
})
|
|
3296
3642
|
};
|
|
@@ -3307,20 +3653,19 @@ function createInferTool(options) {
|
|
|
3307
3653
|
* `@orkestrel/contract`'s `samplesToSchema` (tuned by {@link import('./types.js').EndpointToolOptions}'s
|
|
3308
3654
|
* `format` / `enum`), wrapping a non-object root as `{ value: <schema> }` through `schemaToObject` —
|
|
3309
3655
|
* the same object-rooted schema is both the advertised `parameters` and, by default
|
|
3310
|
-
* ({@link import('./types.js').EndpointToolOptions.
|
|
3311
|
-
* `@orkestrel/contract`'s `schemaToShape` compiles it
|
|
3312
|
-
*
|
|
3656
|
+
* ({@link import('./types.js').EndpointToolOptions.strict} `true`), the enforced contract: the Tool
|
|
3657
|
+
* door converts it through `@orkestrel/contract`'s lenient `schemaToShape` and compiles it on the
|
|
3658
|
+
* first call, then runs on every call's `args` before `definition.execute` — a
|
|
3313
3659
|
* normalizing parse, not a strict type check: a scalar is coerced to its inferred type where the
|
|
3314
3660
|
* house parsers coerce (a number to/from a numeric string, a boolean from `'1'`/`'0'`/`'true'`/
|
|
3315
3661
|
* `'false'`/`1`/`0`), so `definition.execute` receives the coerced value (for example `7` sent for a
|
|
3316
|
-
* string slot arrives as `'7'`), not the raw call value. A call whose `args`
|
|
3317
|
-
*
|
|
3318
|
-
*
|
|
3319
|
-
*
|
|
3320
|
-
*
|
|
3321
|
-
*
|
|
3322
|
-
* `
|
|
3323
|
-
* `definition.execute` without re-validation — the raw-passthrough opt-out. The execution context
|
|
3662
|
+
* string slot arrives as `'7'`), not the raw call value. A call whose `args` carries a key outside
|
|
3663
|
+
* the closed inferred schema, misses a required key, or holds a value not coercible to its slot's
|
|
3664
|
+
* type throws a `ToolError` with code `ARGUMENTS` (`@orkestrel/tool`) carrying `context.faults`, and
|
|
3665
|
+
* `definition.execute` is never called. `format` annotations are never asserted (see
|
|
3666
|
+
* {@link import('./types.js').EndpointToolOptions.strict}). With
|
|
3667
|
+
* `strict: false`, the tool's `execute` passes through the model-supplied `args` to
|
|
3668
|
+
* `definition.execute` without validation — the raw-passthrough opt-out. The execution context
|
|
3324
3669
|
* the tool's `execute` receives reaches `definition.execute` unchanged whichever way `args` are
|
|
3325
3670
|
* handled, so a handler that reads `context.signal` observes the caller's cancellation. Either way,
|
|
3326
3671
|
* the definition's return flows back as the tool call's plain result; a throw propagates uncaught,
|
|
@@ -3329,7 +3674,7 @@ function createInferTool(options) {
|
|
|
3329
3674
|
*
|
|
3330
3675
|
* @param definition - The endpoint's identity, non-empty samples, and local handler (see
|
|
3331
3676
|
* {@link import('./types.js').EndpointDefinition})
|
|
3332
|
-
* @param options - Construction-time inference tuning and the
|
|
3677
|
+
* @param options - Construction-time inference tuning and the strict opt-out (see
|
|
3333
3678
|
* {@link import('./types.js').EndpointToolOptions})
|
|
3334
3679
|
* @returns A `ToolInterface` named `definition.name`
|
|
3335
3680
|
*
|
|
@@ -3362,7 +3707,7 @@ function createInferTool(options) {
|
|
|
3362
3707
|
* name: 'lookupUser',
|
|
3363
3708
|
* arguments: { id: true, name: 'Ada' },
|
|
3364
3709
|
* })
|
|
3365
|
-
* // rejected.error -> the
|
|
3710
|
+
* // rejected.error -> the ARGUMENTS ToolError message, which names `id`
|
|
3366
3711
|
* ```
|
|
3367
3712
|
*/
|
|
3368
3713
|
function createEndpointTool(definition, options) {
|
|
@@ -3372,26 +3717,13 @@ function createEndpointTool(definition, options) {
|
|
|
3372
3717
|
enum: options?.enum ?? false
|
|
3373
3718
|
}));
|
|
3374
3719
|
const parameters = (0, _orkestrel_contract.schemaToParameters)(objectSchema);
|
|
3375
|
-
if (!(options?.validate ?? true)) return (0, _orkestrel_tool.createTool)({
|
|
3376
|
-
name: definition.name,
|
|
3377
|
-
description: definition.description,
|
|
3378
|
-
...parameters === void 0 ? {} : { parameters },
|
|
3379
|
-
execute(args, context) {
|
|
3380
|
-
return definition.execute(args, context);
|
|
3381
|
-
}
|
|
3382
|
-
});
|
|
3383
|
-
const contract = (0, _orkestrel_contract.createContract)((0, _orkestrel_contract.schemaToShape)(objectSchema));
|
|
3384
3720
|
return (0, _orkestrel_tool.createTool)({
|
|
3385
3721
|
name: definition.name,
|
|
3386
3722
|
description: definition.description,
|
|
3387
3723
|
...parameters === void 0 ? {} : { parameters },
|
|
3724
|
+
...options?.strict === void 0 ? {} : { strict: options.strict },
|
|
3388
3725
|
execute(args, context) {
|
|
3389
|
-
|
|
3390
|
-
if (parsed === void 0 || !(0, _orkestrel_contract.isRecord)(parsed)) throw new ToolboxError("TOOL", "malformed endpoint call arguments", {
|
|
3391
|
-
name: definition.name,
|
|
3392
|
-
faults: contract.explain(args)
|
|
3393
|
-
});
|
|
3394
|
-
return definition.execute(parsed, context);
|
|
3726
|
+
return definition.execute(args, context);
|
|
3395
3727
|
}
|
|
3396
3728
|
});
|
|
3397
3729
|
}
|
|
@@ -3437,6 +3769,7 @@ exports.WORKSPACE_TOOL_NAME = WORKSPACE_TOOL_NAME;
|
|
|
3437
3769
|
exports.WORKSPACE_TOOL_SUMMARY = WORKSPACE_TOOL_SUMMARY;
|
|
3438
3770
|
exports.agentToolShape = agentToolShape;
|
|
3439
3771
|
exports.answerToolShape = answerToolShape;
|
|
3772
|
+
exports.buildArgumentRefusal = buildArgumentRefusal;
|
|
3440
3773
|
exports.clampQuery = clampQuery;
|
|
3441
3774
|
exports.columnPrimitiveShape = columnPrimitiveShape;
|
|
3442
3775
|
exports.columnSpecShape = columnSpecShape;
|
|
@@ -3463,13 +3796,18 @@ exports.createWorkflowFunctions = createWorkflowFunctions;
|
|
|
3463
3796
|
exports.createWorkflowTool = createWorkflowTool;
|
|
3464
3797
|
exports.createWorkspaceTool = createWorkspaceTool;
|
|
3465
3798
|
exports.databaseToolShape = databaseToolShape;
|
|
3799
|
+
exports.definitionContract = definitionContract;
|
|
3800
|
+
exports.definitionShape = definitionShape;
|
|
3466
3801
|
exports.deriveWorkflowDepth = deriveWorkflowDepth;
|
|
3802
|
+
exports.describeDefinitionMismatch = describeDefinitionMismatch;
|
|
3803
|
+
exports.describeDefinitionRefusal = describeDefinitionRefusal;
|
|
3467
3804
|
exports.describeToolShape = describeToolShape;
|
|
3468
3805
|
exports.expandInclude = expandInclude;
|
|
3469
3806
|
exports.expandSteps = expandSteps;
|
|
3470
3807
|
exports.expandTables = expandTables;
|
|
3471
3808
|
exports.extendLineage = extendLineage;
|
|
3472
3809
|
exports.includeShape = includeShape;
|
|
3810
|
+
exports.indexesShape = indexesShape;
|
|
3473
3811
|
exports.inferDatabaseCode = inferDatabaseCode;
|
|
3474
3812
|
exports.inferRelationCode = inferRelationCode;
|
|
3475
3813
|
exports.inferTerminalCode = inferTerminalCode;
|
|
@@ -3478,17 +3816,26 @@ exports.isAgentFunction = isAgentFunction;
|
|
|
3478
3816
|
exports.isColumnPrimitive = isColumnPrimitive;
|
|
3479
3817
|
exports.isColumnSpec = isColumnSpec;
|
|
3480
3818
|
exports.isDatabaseDefinition = isDatabaseDefinition;
|
|
3819
|
+
exports.isIndexMap = isIndexMap;
|
|
3820
|
+
exports.isPrimaryMap = isPrimaryMap;
|
|
3821
|
+
exports.isTableSpec = isTableSpec;
|
|
3481
3822
|
exports.isToolboxError = isToolboxError;
|
|
3482
3823
|
exports.isWorkflowLineage = isWorkflowLineage;
|
|
3483
3824
|
exports.keyShape = keyShape;
|
|
3825
|
+
exports.limitRefusal = limitRefusal;
|
|
3484
3826
|
exports.managerShape = managerShape;
|
|
3485
3827
|
exports.normalizeLineage = normalizeLineage;
|
|
3486
3828
|
exports.normalizeQuery = normalizeQuery;
|
|
3487
3829
|
exports.orderShape = orderShape;
|
|
3488
3830
|
exports.phaseDraftShape = phaseDraftShape;
|
|
3831
|
+
exports.primaryShape = primaryShape;
|
|
3489
3832
|
exports.promptToolShape = promptToolShape;
|
|
3490
3833
|
exports.queryShape = queryShape;
|
|
3491
3834
|
exports.relationToolShape = relationToolShape;
|
|
3835
|
+
exports.renderCandidates = renderCandidates;
|
|
3836
|
+
exports.requireActiveWorkspace = requireActiveWorkspace;
|
|
3837
|
+
exports.requireToolCall = requireToolCall;
|
|
3838
|
+
exports.resolveBound = resolveBound;
|
|
3492
3839
|
exports.resolveLimit = resolveLimit;
|
|
3493
3840
|
exports.resolveRelationManager = resolveRelationManager;
|
|
3494
3841
|
exports.resolveRelationModel = resolveRelationModel;
|