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