@dudousxd/nestjs-agent-core 0.19.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/guardrails/index.d.cts +1 -1
- package/dist/guardrails/index.d.ts +1 -1
- package/dist/index.cjs +622 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +151 -5
- package/dist/index.d.ts +151 -5
- package/dist/index.js +609 -42
- package/dist/index.js.map +1 -1
- package/dist/{tool-Q2rmGeaG.d.cts → tool-CwXibnce.d.cts} +164 -3
- package/dist/{tool-Q2rmGeaG.d.ts → tool-CwXibnce.d.ts} +164 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -215,6 +215,63 @@ function truncateDetailContent(content) {
|
|
|
215
215
|
}
|
|
216
216
|
__name(truncateDetailContent, "truncateDetailContent");
|
|
217
217
|
|
|
218
|
+
// src/spi/approval-policy.ts
|
|
219
|
+
var REQUESTER_APPROVER = "requester";
|
|
220
|
+
var DefaultApprovalPolicy = class {
|
|
221
|
+
static {
|
|
222
|
+
__name(this, "DefaultApprovalPolicy");
|
|
223
|
+
}
|
|
224
|
+
requirementFor(tool) {
|
|
225
|
+
return {
|
|
226
|
+
required: tool.kind === "action",
|
|
227
|
+
approver: REQUESTER_APPROVER
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
function defaultCanDecide(actor, decision) {
|
|
232
|
+
if (decision.approver === REQUESTER_APPROVER) {
|
|
233
|
+
return actor.id === decision.requesterRef;
|
|
234
|
+
}
|
|
235
|
+
return actor.roles?.includes(decision.approver) === true;
|
|
236
|
+
}
|
|
237
|
+
__name(defaultCanDecide, "defaultCanDecide");
|
|
238
|
+
async function mayDecideApproval(policy, actor, decision) {
|
|
239
|
+
if (policy?.canDecide !== void 0) {
|
|
240
|
+
return policy.canDecide(actor, decision);
|
|
241
|
+
}
|
|
242
|
+
return defaultCanDecide(actor, decision);
|
|
243
|
+
}
|
|
244
|
+
__name(mayDecideApproval, "mayDecideApproval");
|
|
245
|
+
function toolCallApprovalFromRow(row) {
|
|
246
|
+
if (row.approver === null || row.approver === void 0) {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
const status = row.status === "pending_approval" ? "pending" : row.status === "rejected" ? "rejected" : row.status === "expired" ? "expired" : "approved";
|
|
250
|
+
const expiresAt = row.expiresAt instanceof Date ? row.expiresAt.toISOString() : typeof row.expiresAt === "string" ? row.expiresAt : void 0;
|
|
251
|
+
const decided = status === "approved" || status === "rejected";
|
|
252
|
+
return {
|
|
253
|
+
toolCallId: row.toolCallId,
|
|
254
|
+
approver: row.approver,
|
|
255
|
+
status,
|
|
256
|
+
...expiresAt !== void 0 ? {
|
|
257
|
+
expiresAt
|
|
258
|
+
} : {},
|
|
259
|
+
...row.remember === true ? {
|
|
260
|
+
remember: true
|
|
261
|
+
} : {},
|
|
262
|
+
...decided && typeof row.executedByRef === "string" ? {
|
|
263
|
+
decidedBy: row.executedByRef
|
|
264
|
+
} : {},
|
|
265
|
+
...decided && typeof row.decidedVia === "string" ? {
|
|
266
|
+
decidedVia: row.decidedVia
|
|
267
|
+
} : {},
|
|
268
|
+
...status === "rejected" && typeof row.error === "string" ? {
|
|
269
|
+
reason: row.error
|
|
270
|
+
} : {}
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
__name(toolCallApprovalFromRow, "toolCallApprovalFromRow");
|
|
274
|
+
|
|
218
275
|
// src/governance/compute.ts
|
|
219
276
|
function estimateCost(usage, price) {
|
|
220
277
|
if (price === void 0) {
|
|
@@ -774,11 +831,235 @@ ${detail}`;
|
|
|
774
831
|
}
|
|
775
832
|
__name(repairInstruction, "repairInstruction");
|
|
776
833
|
|
|
777
|
-
// src/elicitation.ts
|
|
778
|
-
|
|
779
|
-
|
|
834
|
+
// src/elicitation-input.ts
|
|
835
|
+
var ELICITATION_INPUT_TYPES = [
|
|
836
|
+
"text",
|
|
837
|
+
"textarea",
|
|
838
|
+
"number",
|
|
839
|
+
"boolean",
|
|
840
|
+
"date",
|
|
841
|
+
"email",
|
|
842
|
+
"url",
|
|
843
|
+
"select"
|
|
844
|
+
];
|
|
845
|
+
function questionOptions(question) {
|
|
846
|
+
return question.options ?? [];
|
|
847
|
+
}
|
|
848
|
+
__name(questionOptions, "questionOptions");
|
|
849
|
+
function isTypedQuestion(question) {
|
|
850
|
+
return question.input !== void 0 && question.input.type !== "select";
|
|
851
|
+
}
|
|
852
|
+
__name(isTypedQuestion, "isTypedQuestion");
|
|
853
|
+
var DATE_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
854
|
+
var EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
855
|
+
var NUMBER_PATTERN = /^-?(\d+\.?\d*|\.\d+)(e[+-]?\d+)?$/i;
|
|
856
|
+
function isDate(value) {
|
|
857
|
+
const match = DATE_PATTERN.exec(value);
|
|
858
|
+
if (match === null) {
|
|
859
|
+
return false;
|
|
860
|
+
}
|
|
861
|
+
const [, year, month, day] = match;
|
|
862
|
+
const date = new Date(Date.UTC(Number(year), Number(month) - 1, Number(day)));
|
|
863
|
+
return date.getUTCFullYear() === Number(year) && date.getUTCMonth() === Number(month) - 1 && date.getUTCDate() === Number(day);
|
|
864
|
+
}
|
|
865
|
+
__name(isDate, "isDate");
|
|
866
|
+
function isHttpUrl(value) {
|
|
867
|
+
try {
|
|
868
|
+
const url = new URL(value);
|
|
869
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
870
|
+
} catch {
|
|
871
|
+
return false;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
__name(isHttpUrl, "isHttpUrl");
|
|
875
|
+
function fullMatch(pattern, value) {
|
|
876
|
+
try {
|
|
877
|
+
return new RegExp(`^(?:${pattern})$`, "u").test(value);
|
|
878
|
+
} catch {
|
|
879
|
+
return void 0;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
__name(fullMatch, "fullMatch");
|
|
883
|
+
function asNumber(bound) {
|
|
884
|
+
if (typeof bound === "number" && Number.isFinite(bound)) {
|
|
885
|
+
return bound;
|
|
886
|
+
}
|
|
887
|
+
if (typeof bound === "string" && NUMBER_PATTERN.test(bound.trim())) {
|
|
888
|
+
return Number(bound);
|
|
889
|
+
}
|
|
890
|
+
return void 0;
|
|
891
|
+
}
|
|
892
|
+
__name(asNumber, "asNumber");
|
|
893
|
+
function validateElicitationValue(question, value) {
|
|
894
|
+
const input = question.input;
|
|
895
|
+
if (input === void 0 || input.type === "select") {
|
|
896
|
+
if (question.allowFreeText === true) {
|
|
897
|
+
return null;
|
|
898
|
+
}
|
|
899
|
+
return questionOptions(question).some((option) => option.value === value) ? null : "must be one of the offered options";
|
|
900
|
+
}
|
|
901
|
+
switch (input.type) {
|
|
902
|
+
case "number": {
|
|
903
|
+
if (!NUMBER_PATTERN.test(value.trim()) || !Number.isFinite(Number(value))) {
|
|
904
|
+
return "must be a number";
|
|
905
|
+
}
|
|
906
|
+
const number = Number(value);
|
|
907
|
+
const min = asNumber(input.min);
|
|
908
|
+
const max = asNumber(input.max);
|
|
909
|
+
if (min !== void 0 && number < min) {
|
|
910
|
+
return `must be at least ${min}`;
|
|
911
|
+
}
|
|
912
|
+
if (max !== void 0 && number > max) {
|
|
913
|
+
return `must be at most ${max}`;
|
|
914
|
+
}
|
|
915
|
+
return null;
|
|
916
|
+
}
|
|
917
|
+
case "boolean":
|
|
918
|
+
return value === "true" || value === "false" ? null : 'must be "true" or "false"';
|
|
919
|
+
case "date": {
|
|
920
|
+
if (!isDate(value)) {
|
|
921
|
+
return "must be a date as YYYY-MM-DD";
|
|
922
|
+
}
|
|
923
|
+
if (typeof input.min === "string" && isDate(input.min) && value < input.min) {
|
|
924
|
+
return `must be on or after ${input.min}`;
|
|
925
|
+
}
|
|
926
|
+
if (typeof input.max === "string" && isDate(input.max) && value > input.max) {
|
|
927
|
+
return `must be on or before ${input.max}`;
|
|
928
|
+
}
|
|
929
|
+
return null;
|
|
930
|
+
}
|
|
931
|
+
case "email":
|
|
932
|
+
if (!EMAIL_PATTERN.test(value)) {
|
|
933
|
+
return "must be an email address";
|
|
934
|
+
}
|
|
935
|
+
break;
|
|
936
|
+
case "url":
|
|
937
|
+
if (!isHttpUrl(value)) {
|
|
938
|
+
return "must be an http(s) URL";
|
|
939
|
+
}
|
|
940
|
+
break;
|
|
941
|
+
case "text":
|
|
942
|
+
case "textarea": {
|
|
943
|
+
const min = asNumber(input.min);
|
|
944
|
+
const max = asNumber(input.max);
|
|
945
|
+
if (min !== void 0 && value.length < min) {
|
|
946
|
+
return `must be at least ${min} characters`;
|
|
947
|
+
}
|
|
948
|
+
if (max !== void 0 && value.length > max) {
|
|
949
|
+
return `must be at most ${max} characters`;
|
|
950
|
+
}
|
|
951
|
+
break;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
if (input.pattern !== void 0 && fullMatch(input.pattern, value) === false) {
|
|
955
|
+
return "does not match the expected format";
|
|
956
|
+
}
|
|
957
|
+
return null;
|
|
958
|
+
}
|
|
959
|
+
__name(validateElicitationValue, "validateElicitationValue");
|
|
960
|
+
function validateElicitationAnswer(question, values) {
|
|
961
|
+
const present = values.filter((value) => value !== "");
|
|
962
|
+
if (present.length === 0) {
|
|
963
|
+
return question.input?.required === true ? "requires an answer" : null;
|
|
964
|
+
}
|
|
965
|
+
if (question.multiple !== true && present.length > 1) {
|
|
966
|
+
return "takes a single value";
|
|
967
|
+
}
|
|
968
|
+
for (const value of present) {
|
|
969
|
+
const problem = validateElicitationValue(question, value);
|
|
970
|
+
if (problem !== null) {
|
|
971
|
+
return problem;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
return null;
|
|
975
|
+
}
|
|
976
|
+
__name(validateElicitationAnswer, "validateElicitationAnswer");
|
|
977
|
+
function readElicitationInput(raw) {
|
|
978
|
+
if (typeof raw !== "object" || raw === null) {
|
|
979
|
+
return void 0;
|
|
980
|
+
}
|
|
981
|
+
const candidate = raw;
|
|
982
|
+
const type = candidate.type;
|
|
983
|
+
if (typeof type !== "string" || !ELICITATION_INPUT_TYPES.includes(type)) {
|
|
984
|
+
return void 0;
|
|
985
|
+
}
|
|
986
|
+
const bound = /* @__PURE__ */ __name((value) => typeof value === "number" && Number.isFinite(value) || typeof value === "string" ? value : void 0, "bound");
|
|
987
|
+
const min = bound(candidate.min);
|
|
988
|
+
const max = bound(candidate.max);
|
|
989
|
+
return {
|
|
990
|
+
type,
|
|
991
|
+
...typeof candidate.placeholder === "string" ? {
|
|
992
|
+
placeholder: candidate.placeholder
|
|
993
|
+
} : {},
|
|
994
|
+
...candidate.required === true ? {
|
|
995
|
+
required: true
|
|
996
|
+
} : {},
|
|
997
|
+
...min !== void 0 ? {
|
|
998
|
+
min
|
|
999
|
+
} : {},
|
|
1000
|
+
...max !== void 0 ? {
|
|
1001
|
+
max
|
|
1002
|
+
} : {},
|
|
1003
|
+
...typeof candidate.pattern === "string" ? {
|
|
1004
|
+
pattern: candidate.pattern
|
|
1005
|
+
} : {}
|
|
1006
|
+
};
|
|
780
1007
|
}
|
|
781
|
-
__name(
|
|
1008
|
+
__name(readElicitationInput, "readElicitationInput");
|
|
1009
|
+
function readElicitationQuestions(input) {
|
|
1010
|
+
if (typeof input !== "object" || input === null) {
|
|
1011
|
+
return [];
|
|
1012
|
+
}
|
|
1013
|
+
const raw = input.questions;
|
|
1014
|
+
if (!Array.isArray(raw)) {
|
|
1015
|
+
return [];
|
|
1016
|
+
}
|
|
1017
|
+
const questions = [];
|
|
1018
|
+
for (const item of raw) {
|
|
1019
|
+
if (typeof item !== "object" || item === null) {
|
|
1020
|
+
continue;
|
|
1021
|
+
}
|
|
1022
|
+
const candidate = item;
|
|
1023
|
+
if (typeof candidate.id !== "string" || typeof candidate.prompt !== "string") {
|
|
1024
|
+
continue;
|
|
1025
|
+
}
|
|
1026
|
+
const options = Array.isArray(candidate.options) ? candidate.options.flatMap((option) => {
|
|
1027
|
+
if (typeof option !== "object" || option === null) {
|
|
1028
|
+
return [];
|
|
1029
|
+
}
|
|
1030
|
+
const { value, label } = option;
|
|
1031
|
+
return typeof value === "string" && typeof label === "string" ? [
|
|
1032
|
+
{
|
|
1033
|
+
value,
|
|
1034
|
+
label
|
|
1035
|
+
}
|
|
1036
|
+
] : [];
|
|
1037
|
+
}) : [];
|
|
1038
|
+
const typed = readElicitationInput(candidate.input);
|
|
1039
|
+
const defaults = Array.isArray(candidate.defaults) ? candidate.defaults.filter((value) => typeof value === "string") : [];
|
|
1040
|
+
questions.push({
|
|
1041
|
+
id: candidate.id,
|
|
1042
|
+
prompt: candidate.prompt,
|
|
1043
|
+
options,
|
|
1044
|
+
...typed !== void 0 ? {
|
|
1045
|
+
input: typed
|
|
1046
|
+
} : {},
|
|
1047
|
+
...defaults.length > 0 ? {
|
|
1048
|
+
defaults
|
|
1049
|
+
} : {},
|
|
1050
|
+
...candidate.multiple === true ? {
|
|
1051
|
+
multiple: true
|
|
1052
|
+
} : {},
|
|
1053
|
+
...candidate.allowFreeText === true ? {
|
|
1054
|
+
allowFreeText: true
|
|
1055
|
+
} : {}
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
return questions;
|
|
1059
|
+
}
|
|
1060
|
+
__name(readElicitationQuestions, "readElicitationQuestions");
|
|
1061
|
+
|
|
1062
|
+
// src/elicitation.ts
|
|
782
1063
|
function normalizeElicitationReply(reply) {
|
|
783
1064
|
if (typeof reply !== "object" || reply === null) {
|
|
784
1065
|
return {
|
|
@@ -814,8 +1095,7 @@ function resolveElicitation(request, raw) {
|
|
|
814
1095
|
defaulted.push(question.id);
|
|
815
1096
|
continue;
|
|
816
1097
|
}
|
|
817
|
-
const
|
|
818
|
-
const valid = question.allowFreeText === true ? submitted : submitted.filter((value) => allowed.has(value));
|
|
1098
|
+
const valid = submitted.filter((value) => value !== "" && validateElicitationValue(question, value) === null);
|
|
819
1099
|
answers[question.id] = question.multiple === true ? valid : valid.slice(0, 1);
|
|
820
1100
|
}
|
|
821
1101
|
return {
|
|
@@ -836,7 +1116,7 @@ __name(settleElicitation, "settleElicitation");
|
|
|
836
1116
|
function renderElicitationAnswers(request, outcome) {
|
|
837
1117
|
const lines = request.questions.map((question) => {
|
|
838
1118
|
const chosen = outcome.answers[question.id] ?? [];
|
|
839
|
-
const labels = chosen.map((value) => question.
|
|
1119
|
+
const labels = chosen.map((value) => questionOptions(question).find((option) => option.value === value)?.label ?? value);
|
|
840
1120
|
return `${question.prompt} \u2192 ${labels.length > 0 ? labels.join(", ") : "(no answer)"}`;
|
|
841
1121
|
});
|
|
842
1122
|
const preface = outcome.skipped ? "The user declined to answer and asked you to proceed on these assumptions:" : "The user answered:";
|
|
@@ -902,7 +1182,23 @@ function parseQuestion(raw, path, issues) {
|
|
|
902
1182
|
], "must be a non-empty string"));
|
|
903
1183
|
return void 0;
|
|
904
1184
|
}
|
|
905
|
-
|
|
1185
|
+
let input;
|
|
1186
|
+
if (candidate.input !== void 0) {
|
|
1187
|
+
input = readElicitationInput(candidate.input);
|
|
1188
|
+
if (input === void 0) {
|
|
1189
|
+
issues.push(issue([
|
|
1190
|
+
...path,
|
|
1191
|
+
"input",
|
|
1192
|
+
"type"
|
|
1193
|
+
], `must be one of ${ELICITATION_INPUT_TYPES.join(", ")}`));
|
|
1194
|
+
return void 0;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
const described = typeof candidate.description === "string" && candidate.description.length > 0 ? {
|
|
1198
|
+
description: candidate.description
|
|
1199
|
+
} : {};
|
|
1200
|
+
const typed = input !== void 0 && input.type !== "select";
|
|
1201
|
+
if (!typed && (!Array.isArray(candidate.options) || candidate.options.length === 0)) {
|
|
906
1202
|
issues.push(issue([
|
|
907
1203
|
...path,
|
|
908
1204
|
"options"
|
|
@@ -910,7 +1206,7 @@ function parseQuestion(raw, path, issues) {
|
|
|
910
1206
|
return void 0;
|
|
911
1207
|
}
|
|
912
1208
|
const options = [];
|
|
913
|
-
for (const [index, rawOption] of candidate.options.entries()) {
|
|
1209
|
+
for (const [index, rawOption] of (Array.isArray(candidate.options) ? candidate.options : []).entries()) {
|
|
914
1210
|
const option = parseOption(rawOption, [
|
|
915
1211
|
...path,
|
|
916
1212
|
"options",
|
|
@@ -920,6 +1216,33 @@ function parseQuestion(raw, path, issues) {
|
|
|
920
1216
|
options.push(option);
|
|
921
1217
|
}
|
|
922
1218
|
}
|
|
1219
|
+
if (typed) {
|
|
1220
|
+
const question = {
|
|
1221
|
+
id: candidate.id,
|
|
1222
|
+
prompt: candidate.prompt,
|
|
1223
|
+
...described,
|
|
1224
|
+
...options.length > 0 ? {
|
|
1225
|
+
options
|
|
1226
|
+
} : {},
|
|
1227
|
+
input,
|
|
1228
|
+
...candidate.multiple === true ? {
|
|
1229
|
+
multiple: true
|
|
1230
|
+
} : {}
|
|
1231
|
+
};
|
|
1232
|
+
const offered2 = Array.isArray(candidate.defaults) ? candidate.defaults.filter((value) => typeof value === "string") : [];
|
|
1233
|
+
const defaults2 = offered2.filter((value) => validateElicitationValue(question, value) === null);
|
|
1234
|
+
if (offered2.length > 0 && defaults2.length === 0) {
|
|
1235
|
+
issues.push(issue([
|
|
1236
|
+
...path,
|
|
1237
|
+
"defaults"
|
|
1238
|
+
], `must be valid ${input.type} values`));
|
|
1239
|
+
return void 0;
|
|
1240
|
+
}
|
|
1241
|
+
return defaults2.length > 0 ? {
|
|
1242
|
+
...question,
|
|
1243
|
+
defaults: defaults2
|
|
1244
|
+
} : question;
|
|
1245
|
+
}
|
|
923
1246
|
if (!Array.isArray(candidate.defaults) || candidate.defaults.length === 0) {
|
|
924
1247
|
issues.push(issue([
|
|
925
1248
|
...path,
|
|
@@ -939,7 +1262,11 @@ function parseQuestion(raw, path, issues) {
|
|
|
939
1262
|
return {
|
|
940
1263
|
id: candidate.id,
|
|
941
1264
|
prompt: candidate.prompt,
|
|
1265
|
+
...described,
|
|
942
1266
|
options,
|
|
1267
|
+
...input !== void 0 ? {
|
|
1268
|
+
input
|
|
1269
|
+
} : {},
|
|
943
1270
|
defaults,
|
|
944
1271
|
...candidate.multiple === true ? {
|
|
945
1272
|
multiple: true
|
|
@@ -967,9 +1294,7 @@ var ASK_JSON_SCHEMA = {
|
|
|
967
1294
|
additionalProperties: false,
|
|
968
1295
|
required: [
|
|
969
1296
|
"id",
|
|
970
|
-
"prompt"
|
|
971
|
-
"options",
|
|
972
|
-
"defaults"
|
|
1297
|
+
"prompt"
|
|
973
1298
|
],
|
|
974
1299
|
properties: {
|
|
975
1300
|
id: {
|
|
@@ -979,6 +1304,50 @@ var ASK_JSON_SCHEMA = {
|
|
|
979
1304
|
prompt: {
|
|
980
1305
|
type: "string"
|
|
981
1306
|
},
|
|
1307
|
+
description: {
|
|
1308
|
+
type: "string",
|
|
1309
|
+
description: "A line of help under the prompt."
|
|
1310
|
+
},
|
|
1311
|
+
input: {
|
|
1312
|
+
type: "object",
|
|
1313
|
+
additionalProperties: false,
|
|
1314
|
+
required: [
|
|
1315
|
+
"type"
|
|
1316
|
+
],
|
|
1317
|
+
description: 'Ask for a typed value instead of offering options. Omit for a pick from options. Answers come back as strings: numbers as decimals, booleans as "true"/"false", dates as YYYY-MM-DD.',
|
|
1318
|
+
properties: {
|
|
1319
|
+
type: {
|
|
1320
|
+
type: "string",
|
|
1321
|
+
enum: [
|
|
1322
|
+
...ELICITATION_INPUT_TYPES
|
|
1323
|
+
]
|
|
1324
|
+
},
|
|
1325
|
+
placeholder: {
|
|
1326
|
+
type: "string"
|
|
1327
|
+
},
|
|
1328
|
+
required: {
|
|
1329
|
+
type: "boolean"
|
|
1330
|
+
},
|
|
1331
|
+
min: {
|
|
1332
|
+
type: [
|
|
1333
|
+
"number",
|
|
1334
|
+
"string"
|
|
1335
|
+
],
|
|
1336
|
+
description: "number: minimum; text: minimum length; date: earliest YYYY-MM-DD."
|
|
1337
|
+
},
|
|
1338
|
+
max: {
|
|
1339
|
+
type: [
|
|
1340
|
+
"number",
|
|
1341
|
+
"string"
|
|
1342
|
+
],
|
|
1343
|
+
description: "number: maximum; text: maximum length; date: latest YYYY-MM-DD."
|
|
1344
|
+
},
|
|
1345
|
+
pattern: {
|
|
1346
|
+
type: "string",
|
|
1347
|
+
description: "A regex the whole value must match."
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
},
|
|
982
1351
|
multiple: {
|
|
983
1352
|
type: "boolean",
|
|
984
1353
|
description: "Allow more than one option to be chosen."
|
|
@@ -993,7 +1362,7 @@ var ASK_JSON_SCHEMA = {
|
|
|
993
1362
|
items: {
|
|
994
1363
|
type: "string"
|
|
995
1364
|
},
|
|
996
|
-
description: "REQUIRED
|
|
1365
|
+
description: "REQUIRED for a pick from options: the option values you would pick yourself, so the user can confirm rather than decide. For a typed input, the value you would enter, when there is a sensible one."
|
|
997
1366
|
},
|
|
998
1367
|
options: {
|
|
999
1368
|
type: "array",
|
|
@@ -2765,15 +3134,33 @@ async function claimToolCall(turn, call) {
|
|
|
2765
3134
|
const spec = deps.registry.spec(call.name);
|
|
2766
3135
|
const kind = call.kind ?? declaredKind(deps, call.name);
|
|
2767
3136
|
const awaitsHuman = kind === "action" || kind === "ask";
|
|
3137
|
+
const approval = kind === "action" ? await claimApproval(turn, call, spec) : void 0;
|
|
3138
|
+
const parks = kind === "ask" || approval?.mode === "ask";
|
|
2768
3139
|
await deps.store.recordToolCall({
|
|
2769
3140
|
toolCallId: call.id,
|
|
2770
3141
|
messageId,
|
|
2771
3142
|
toolName: call.name,
|
|
2772
3143
|
toolType: awaitsHuman ? "action" : "read",
|
|
2773
3144
|
input: call.input,
|
|
2774
|
-
status:
|
|
2775
|
-
runId: hooks.runId
|
|
3145
|
+
status: parks ? "pending_approval" : "auto_executed",
|
|
3146
|
+
runId: hooks.runId,
|
|
3147
|
+
...approval !== void 0 && approval.mode !== "auto" ? {
|
|
3148
|
+
approver: approval.approver
|
|
3149
|
+
} : {},
|
|
3150
|
+
...approval?.mode === "ask" && approval.expiresAt !== void 0 ? {
|
|
3151
|
+
expiresAt: approval.expiresAt
|
|
3152
|
+
} : {}
|
|
2776
3153
|
});
|
|
3154
|
+
if (approval?.mode === "ask") {
|
|
3155
|
+
await turn.writer.write(encodeStreamEvent({
|
|
3156
|
+
kind: "approval-requested",
|
|
3157
|
+
id: call.id,
|
|
3158
|
+
approver: approval.approver,
|
|
3159
|
+
...approval.expiresAt !== void 0 ? {
|
|
3160
|
+
expiresAt: approval.expiresAt
|
|
3161
|
+
} : {}
|
|
3162
|
+
}));
|
|
3163
|
+
}
|
|
2777
3164
|
return {
|
|
2778
3165
|
kind,
|
|
2779
3166
|
...spec?.targetAgent !== void 0 ? {
|
|
@@ -2783,6 +3170,9 @@ async function claimToolCall(turn, call) {
|
|
|
2783
3170
|
// deployment with no detached edge writes the same bytes here it always has.
|
|
2784
3171
|
...spec?.detached === true ? {
|
|
2785
3172
|
detached: true
|
|
3173
|
+
} : {},
|
|
3174
|
+
...approval !== void 0 ? {
|
|
3175
|
+
approval
|
|
2786
3176
|
} : {}
|
|
2787
3177
|
};
|
|
2788
3178
|
});
|
|
@@ -2802,10 +3192,52 @@ async function claimToolCall(turn, call) {
|
|
|
2802
3192
|
...persisted?.detached === true ? {
|
|
2803
3193
|
detached: true
|
|
2804
3194
|
} : {},
|
|
3195
|
+
...toolType === "action" && persisted?.approval !== void 0 ? {
|
|
3196
|
+
approval: persisted.approval
|
|
3197
|
+
} : {},
|
|
2805
3198
|
ctx: toolContext(deps, input, hooks)
|
|
2806
3199
|
};
|
|
2807
3200
|
}
|
|
2808
3201
|
__name(claimToolCall, "claimToolCall");
|
|
3202
|
+
async function claimApproval(turn, call, spec) {
|
|
3203
|
+
const { deps, input, hooks } = turn;
|
|
3204
|
+
const policy = deps.approvalPolicy ?? new DefaultApprovalPolicy();
|
|
3205
|
+
const requirement = await policy.requirementFor({
|
|
3206
|
+
name: call.name,
|
|
3207
|
+
kind: "action",
|
|
3208
|
+
...spec !== void 0 ? {
|
|
3209
|
+
spec
|
|
3210
|
+
} : {}
|
|
3211
|
+
}, input.actor, {
|
|
3212
|
+
threadId: input.threadId,
|
|
3213
|
+
runId: hooks.runId,
|
|
3214
|
+
...input.agentName !== void 0 ? {
|
|
3215
|
+
agentName: input.agentName
|
|
3216
|
+
} : {}
|
|
3217
|
+
});
|
|
3218
|
+
if (!requirement.required) {
|
|
3219
|
+
return {
|
|
3220
|
+
mode: "auto"
|
|
3221
|
+
};
|
|
3222
|
+
}
|
|
3223
|
+
const remembered = await deps.store.rememberedApprovals?.(input.threadId) ?? [];
|
|
3224
|
+
if (remembered.includes(call.name)) {
|
|
3225
|
+
return {
|
|
3226
|
+
mode: "remembered",
|
|
3227
|
+
approver: requirement.approver
|
|
3228
|
+
};
|
|
3229
|
+
}
|
|
3230
|
+
const ttlMs = requirement.ttlMs !== void 0 && Number.isFinite(requirement.ttlMs) && requirement.ttlMs > 0 ? Math.floor(requirement.ttlMs) : void 0;
|
|
3231
|
+
return {
|
|
3232
|
+
mode: "ask",
|
|
3233
|
+
approver: requirement.approver,
|
|
3234
|
+
...ttlMs !== void 0 ? {
|
|
3235
|
+
ttlMs,
|
|
3236
|
+
expiresAt: new Date(Date.now() + ttlMs).toISOString()
|
|
3237
|
+
} : {}
|
|
3238
|
+
};
|
|
3239
|
+
}
|
|
3240
|
+
__name(claimApproval, "claimApproval");
|
|
2809
3241
|
async function loadSkillIntoTurn(turn, claimed, startedAt) {
|
|
2810
3242
|
const { deps, input, hooks } = turn;
|
|
2811
3243
|
const { call } = claimed;
|
|
@@ -2989,17 +3421,50 @@ async function invokeClaimedTool(turn, claimed) {
|
|
|
2989
3421
|
}
|
|
2990
3422
|
}
|
|
2991
3423
|
__name(invokeClaimedTool, "invokeClaimedTool");
|
|
2992
|
-
async function recordToolOutcome(turn, claimed, outcome, deciderRef) {
|
|
3424
|
+
async function recordToolOutcome(turn, claimed, outcome, deciderRef, meta) {
|
|
2993
3425
|
const { deps, hooks } = turn;
|
|
2994
3426
|
const { call } = claimed;
|
|
2995
3427
|
const toolType = claimed.toolType === "action" ? "action" : "read";
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3428
|
+
const decided = meta === void 0 ? {} : {
|
|
3429
|
+
executedByRef: meta.decidedBy,
|
|
3430
|
+
...meta.decidedVia !== void 0 ? {
|
|
3431
|
+
decidedVia: meta.decidedVia
|
|
3432
|
+
} : {},
|
|
3433
|
+
...meta.remember === true ? {
|
|
3434
|
+
remember: true
|
|
3435
|
+
} : {}
|
|
3436
|
+
};
|
|
3437
|
+
const settle = /* @__PURE__ */ __name(async () => {
|
|
3438
|
+
if (meta === void 0 || !meta.streamed) {
|
|
3439
|
+
return;
|
|
3440
|
+
}
|
|
3441
|
+
await turn.writer.write(encodeStreamEvent({
|
|
3442
|
+
kind: "approval-settled",
|
|
3443
|
+
id: call.id,
|
|
3444
|
+
status: "approved",
|
|
3445
|
+
...meta.approver !== void 0 ? {
|
|
3446
|
+
approver: meta.approver
|
|
3447
|
+
} : {},
|
|
3448
|
+
decidedBy: meta.decidedBy,
|
|
3449
|
+
...meta.decidedVia !== void 0 ? {
|
|
3450
|
+
decidedVia: meta.decidedVia
|
|
3451
|
+
} : {},
|
|
3452
|
+
...meta.remember === true ? {
|
|
3453
|
+
remember: true
|
|
3454
|
+
} : {}
|
|
3002
3455
|
}));
|
|
3456
|
+
}, "settle");
|
|
3457
|
+
if (outcome.status === "failed") {
|
|
3458
|
+
await hooks.step(`persist:toolfail:${call.id}`, async () => {
|
|
3459
|
+
await deps.store.updateToolCall({
|
|
3460
|
+
toolCallId: call.id,
|
|
3461
|
+
status: "failed",
|
|
3462
|
+
error: outcome.error,
|
|
3463
|
+
executionMs: outcome.executionMs,
|
|
3464
|
+
...decided
|
|
3465
|
+
});
|
|
3466
|
+
await settle();
|
|
3467
|
+
});
|
|
3003
3468
|
publishAgentToolCall({
|
|
3004
3469
|
runId: hooks.runId,
|
|
3005
3470
|
toolName: call.name,
|
|
@@ -3014,15 +3479,19 @@ async function recordToolOutcome(turn, claimed, outcome, deciderRef) {
|
|
|
3014
3479
|
error: outcome.error
|
|
3015
3480
|
};
|
|
3016
3481
|
}
|
|
3017
|
-
await hooks.step(`persist:toolexec:${call.id}`, () =>
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3482
|
+
await hooks.step(`persist:toolexec:${call.id}`, async () => {
|
|
3483
|
+
await deps.store.updateToolCall({
|
|
3484
|
+
toolCallId: call.id,
|
|
3485
|
+
status: "executed",
|
|
3486
|
+
output: outcome.output,
|
|
3487
|
+
executionMs: outcome.executionMs,
|
|
3488
|
+
...toolType === "action" ? {
|
|
3489
|
+
executedByRef: deciderRef
|
|
3490
|
+
} : {},
|
|
3491
|
+
...decided
|
|
3492
|
+
});
|
|
3493
|
+
await settle();
|
|
3494
|
+
});
|
|
3026
3495
|
publishAgentToolCall({
|
|
3027
3496
|
runId: hooks.runId,
|
|
3028
3497
|
toolName: call.name,
|
|
@@ -3160,18 +3629,53 @@ async function runClaimedToolCall(turn, claimed) {
|
|
|
3160
3629
|
return elicitToolCall(turn, claimed);
|
|
3161
3630
|
}
|
|
3162
3631
|
let deciderRef = input.actor.id;
|
|
3163
|
-
|
|
3164
|
-
|
|
3632
|
+
let meta;
|
|
3633
|
+
if (toolType === "action" && claimed.approval?.mode === "remembered") {
|
|
3634
|
+
meta = {
|
|
3635
|
+
approver: claimed.approval.approver,
|
|
3636
|
+
decidedBy: input.actor.id,
|
|
3637
|
+
decidedVia: "remembered",
|
|
3638
|
+
remember: true,
|
|
3639
|
+
streamed: true
|
|
3640
|
+
};
|
|
3641
|
+
} else if (toolType === "action" && claimed.approval?.mode !== "auto") {
|
|
3642
|
+
const ttlMs = claimed.approval?.mode === "ask" ? claimed.approval.ttlMs : void 0;
|
|
3643
|
+
const decision = ttlMs !== void 0 ? await hooks.awaitApproval(call, ctx, {
|
|
3644
|
+
timeoutMs: ttlMs
|
|
3645
|
+
}) : await hooks.awaitApproval(call, ctx);
|
|
3165
3646
|
deciderRef = decision.executedByRef ?? input.actor.id;
|
|
3647
|
+
const streamsSettlement = claimed.approval !== void 0;
|
|
3648
|
+
if (decision.expired === true) {
|
|
3649
|
+
return expireToolCall(turn, claimed);
|
|
3650
|
+
}
|
|
3166
3651
|
if (!decision.approved) {
|
|
3167
|
-
await hooks.step(`persist:toolreject:${call.id}`, () =>
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3652
|
+
await hooks.step(`persist:toolreject:${call.id}`, async () => {
|
|
3653
|
+
await deps.store.updateToolCall({
|
|
3654
|
+
toolCallId: call.id,
|
|
3655
|
+
status: "rejected",
|
|
3656
|
+
executedByRef: deciderRef,
|
|
3657
|
+
...decision.reason !== void 0 ? {
|
|
3658
|
+
error: decision.reason
|
|
3659
|
+
} : {},
|
|
3660
|
+
...decision.decidedVia !== void 0 ? {
|
|
3661
|
+
decidedVia: decision.decidedVia
|
|
3662
|
+
} : {}
|
|
3663
|
+
});
|
|
3664
|
+
if (streamsSettlement) {
|
|
3665
|
+
await turn.writer.write(encodeStreamEvent({
|
|
3666
|
+
kind: "approval-settled",
|
|
3667
|
+
id: call.id,
|
|
3668
|
+
status: "rejected",
|
|
3669
|
+
decidedBy: deciderRef,
|
|
3670
|
+
...decision.decidedVia !== void 0 ? {
|
|
3671
|
+
decidedVia: decision.decidedVia
|
|
3672
|
+
} : {},
|
|
3673
|
+
...decision.reason !== void 0 ? {
|
|
3674
|
+
reason: decision.reason
|
|
3675
|
+
} : {}
|
|
3676
|
+
}));
|
|
3677
|
+
}
|
|
3678
|
+
});
|
|
3175
3679
|
publishAgentToolCall({
|
|
3176
3680
|
runId: hooks.runId,
|
|
3177
3681
|
toolName: call.name,
|
|
@@ -3194,10 +3698,60 @@ async function runClaimedToolCall(turn, claimed) {
|
|
|
3194
3698
|
error: refusalNarrative(decision.reason)
|
|
3195
3699
|
};
|
|
3196
3700
|
}
|
|
3701
|
+
meta = {
|
|
3702
|
+
decidedBy: deciderRef,
|
|
3703
|
+
...decision.decidedVia !== void 0 ? {
|
|
3704
|
+
decidedVia: decision.decidedVia
|
|
3705
|
+
} : {},
|
|
3706
|
+
...decision.remember === true ? {
|
|
3707
|
+
remember: true
|
|
3708
|
+
} : {},
|
|
3709
|
+
streamed: streamsSettlement
|
|
3710
|
+
};
|
|
3197
3711
|
}
|
|
3198
|
-
return recordToolOutcome(turn, claimed, await invokeClaimedTool(turn, claimed), deciderRef);
|
|
3712
|
+
return recordToolOutcome(turn, claimed, await invokeClaimedTool(turn, claimed), deciderRef, meta);
|
|
3199
3713
|
}
|
|
3200
3714
|
__name(runClaimedToolCall, "runClaimedToolCall");
|
|
3715
|
+
async function expireToolCall(turn, claimed) {
|
|
3716
|
+
const { deps, hooks } = turn;
|
|
3717
|
+
const { call } = claimed;
|
|
3718
|
+
await hooks.step(`persist:toolreject:${call.id}`, async () => {
|
|
3719
|
+
await deps.store.updateToolCall({
|
|
3720
|
+
toolCallId: call.id,
|
|
3721
|
+
status: "expired",
|
|
3722
|
+
error: APPROVAL_EXPIRED_REASON
|
|
3723
|
+
});
|
|
3724
|
+
await turn.writer.write(encodeStreamEvent({
|
|
3725
|
+
kind: "approval-settled",
|
|
3726
|
+
id: call.id,
|
|
3727
|
+
status: "expired"
|
|
3728
|
+
}));
|
|
3729
|
+
});
|
|
3730
|
+
publishAgentToolCall({
|
|
3731
|
+
runId: hooks.runId,
|
|
3732
|
+
toolName: call.name,
|
|
3733
|
+
toolType: "action",
|
|
3734
|
+
status: "rejected"
|
|
3735
|
+
});
|
|
3736
|
+
return {
|
|
3737
|
+
id: call.id,
|
|
3738
|
+
name: call.name,
|
|
3739
|
+
output: {
|
|
3740
|
+
rejected: true,
|
|
3741
|
+
expired: true,
|
|
3742
|
+
reason: APPROVAL_EXPIRED_REASON
|
|
3743
|
+
},
|
|
3744
|
+
denied: true,
|
|
3745
|
+
expired: true,
|
|
3746
|
+
error: expiryNarrative()
|
|
3747
|
+
};
|
|
3748
|
+
}
|
|
3749
|
+
__name(expireToolCall, "expireToolCall");
|
|
3750
|
+
var APPROVAL_EXPIRED_REASON = "approval expired";
|
|
3751
|
+
function expiryNarrative() {
|
|
3752
|
+
return "This action needed approval, and the approval request expired before anyone decided. Nothing ran and nothing changed. Do not run it again on your own; tell the person it was not approved in time and ask whether they still want it.";
|
|
3753
|
+
}
|
|
3754
|
+
__name(expiryNarrative, "expiryNarrative");
|
|
3201
3755
|
function outputFrame(result) {
|
|
3202
3756
|
if (result.denied === true) {
|
|
3203
3757
|
const reason = refusalReason(result);
|
|
@@ -3870,6 +4424,7 @@ export {
|
|
|
3870
4424
|
AGENT_SPAN_EVENTS,
|
|
3871
4425
|
AGENT_STORE,
|
|
3872
4426
|
AGENT_TOOL_REGISTRY,
|
|
4427
|
+
APPROVAL_EXPIRED_REASON,
|
|
3873
4428
|
ASK_TOOL_DESCRIPTION,
|
|
3874
4429
|
ASK_TOOL_NAME,
|
|
3875
4430
|
AgentRegistry,
|
|
@@ -3884,7 +4439,9 @@ export {
|
|
|
3884
4439
|
DEFAULT_STRUCTURED_OUTPUT_INSTRUCTION,
|
|
3885
4440
|
DEFAULT_TOOL_TRANSIENT_RETRY_ATTEMPTS,
|
|
3886
4441
|
DEFAULT_TOOL_TRANSIENT_RETRY_BACKOFF_MS,
|
|
4442
|
+
DefaultApprovalPolicy,
|
|
3887
4443
|
DefaultRolesPolicy,
|
|
4444
|
+
ELICITATION_INPUT_TYPES,
|
|
3888
4445
|
GLOBAL_SCOPE,
|
|
3889
4446
|
MAX_ASK_QUESTIONS,
|
|
3890
4447
|
OutputRejectedError,
|
|
@@ -3892,6 +4449,7 @@ export {
|
|
|
3892
4449
|
QuotaExceededError,
|
|
3893
4450
|
REMEMBER_TOOL_DESCRIPTION,
|
|
3894
4451
|
REMEMBER_TOOL_NAME,
|
|
4452
|
+
REQUESTER_APPROVER,
|
|
3895
4453
|
RunCancelledError,
|
|
3896
4454
|
SKILL_TOOL_DESCRIPTION,
|
|
3897
4455
|
SKILL_TOOL_NAME,
|
|
@@ -3919,6 +4477,7 @@ export {
|
|
|
3919
4477
|
createIncrementalGate,
|
|
3920
4478
|
dayBoundsUtc,
|
|
3921
4479
|
decodeStreamEvent,
|
|
4480
|
+
defaultCanDecide,
|
|
3922
4481
|
defaultScopeResolver,
|
|
3923
4482
|
detachedDelivered,
|
|
3924
4483
|
detachedStarted,
|
|
@@ -3938,7 +4497,9 @@ export {
|
|
|
3938
4497
|
isReplayIntegrityError,
|
|
3939
4498
|
isToolEnabled,
|
|
3940
4499
|
isTransientToolError,
|
|
4500
|
+
isTypedQuestion,
|
|
3941
4501
|
loadSkill,
|
|
4502
|
+
mayDecideApproval,
|
|
3942
4503
|
memoryForgetVerdict,
|
|
3943
4504
|
memoryWriteVerdict,
|
|
3944
4505
|
normalizeDelegation,
|
|
@@ -3958,6 +4519,9 @@ export {
|
|
|
3958
4519
|
publishAgentSkillsResolved,
|
|
3959
4520
|
publishAgentToolCall,
|
|
3960
4521
|
publishAgentToolRetry,
|
|
4522
|
+
questionOptions,
|
|
4523
|
+
readElicitationInput,
|
|
4524
|
+
readElicitationQuestions,
|
|
3961
4525
|
releaseGatedFrames,
|
|
3962
4526
|
rememberInputSchema,
|
|
3963
4527
|
rememberToolDefinition,
|
|
@@ -3984,9 +4548,12 @@ export {
|
|
|
3984
4548
|
staticSkillProvider,
|
|
3985
4549
|
summarizeWithModel,
|
|
3986
4550
|
tenantScope,
|
|
4551
|
+
toolCallApprovalFromRow,
|
|
3987
4552
|
traceLlmTurn,
|
|
3988
4553
|
traceToolExecution,
|
|
3989
4554
|
truncateDetailContent,
|
|
4555
|
+
validateElicitationAnswer,
|
|
4556
|
+
validateElicitationValue,
|
|
3990
4557
|
validateStructured,
|
|
3991
4558
|
windowHistory,
|
|
3992
4559
|
withAskTool,
|