@vaudience/aigentflow-flow-validator 0.20.0 → 0.22.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/PARITY.md +103 -35
- package/README.md +3 -2
- package/dist/cli.js +226 -4
- package/dist/index.cjs +222 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +222 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -304,6 +304,17 @@ function isValidGoDuration(input) {
|
|
|
304
304
|
var RESERVED_STEP_ID_CHAR = ".";
|
|
305
305
|
var RESERVED_STEP_ID_ORCHESTRATOR = "orchestrator";
|
|
306
306
|
var DISPLAY_NAME_MAX_LEN = 80;
|
|
307
|
+
var YAML11_BOOL_WORDS = new Set(
|
|
308
|
+
["y", "yes", "on", "n", "no", "off"].flatMap((w) => [
|
|
309
|
+
w,
|
|
310
|
+
w.toUpperCase(),
|
|
311
|
+
w[0].toUpperCase() + w.slice(1)
|
|
312
|
+
])
|
|
313
|
+
);
|
|
314
|
+
function isBoolLike(v) {
|
|
315
|
+
if (v === null || typeof v === "boolean") return true;
|
|
316
|
+
return typeof v === "string" && YAML11_BOOL_WORDS.has(v);
|
|
317
|
+
}
|
|
307
318
|
function validateBasicStructure(flow, issues) {
|
|
308
319
|
if (!issues.nonEmptyStringOf(flow, "aigentflow_version", "")) {
|
|
309
320
|
issues.error({
|
|
@@ -333,6 +344,14 @@ function validateBasicStructure(flow, issues) {
|
|
|
333
344
|
});
|
|
334
345
|
}
|
|
335
346
|
}
|
|
347
|
+
if (flow.strict_templates !== void 0 && !isBoolLike(flow.strict_templates)) {
|
|
348
|
+
issues.error({
|
|
349
|
+
field: "strict_templates",
|
|
350
|
+
message: "strict_templates must be a boolean (true or false)",
|
|
351
|
+
code: "invalid_type",
|
|
352
|
+
suggestion: "Write 'strict_templates: true' or remove the key"
|
|
353
|
+
});
|
|
354
|
+
}
|
|
336
355
|
const start = issues.stringOf(flow, "start", "");
|
|
337
356
|
if (start === null || start === "") {
|
|
338
357
|
issues.error({
|
|
@@ -432,7 +451,7 @@ function validateBasicStructure(flow, issues) {
|
|
|
432
451
|
|
|
433
452
|
// src/spec/aigentflow-spec.json
|
|
434
453
|
var aigentflow_spec_default = {
|
|
435
|
-
specVersion: "2.
|
|
454
|
+
specVersion: "2.828.0",
|
|
436
455
|
inputSchemaVersion: 1,
|
|
437
456
|
executorSchemes: [
|
|
438
457
|
"a2a",
|
|
@@ -456,6 +475,7 @@ var aigentflow_spec_default = {
|
|
|
456
475
|
"geocoding",
|
|
457
476
|
"geovisualize",
|
|
458
477
|
"getmd",
|
|
478
|
+
"here",
|
|
459
479
|
"homeassistant",
|
|
460
480
|
"hosting",
|
|
461
481
|
"http",
|
|
@@ -928,6 +948,33 @@ var aigentflow_spec_default = {
|
|
|
928
948
|
stepFieldFormat: "steps.%s.query.%s",
|
|
929
949
|
loopStepFieldFormat: "steps.%s.loop.steps.%s.query.%s"
|
|
930
950
|
},
|
|
951
|
+
flowDataReservedKeys: {
|
|
952
|
+
code: "flow_data_key_reserved",
|
|
953
|
+
fieldFormat: "data.%s",
|
|
954
|
+
messageFormat: "data: key %s is reserved: %s. It would write into the engine's own state, so it is not seeded.",
|
|
955
|
+
suggestion: "Rename the key (no leading `_` or `.`, and not one of the engine namespaces memory., orchestrator., campaign., voiceagent.).",
|
|
956
|
+
exact: {
|
|
957
|
+
org_id: "the mission's organization, set by the engine for Thalamus/Engram calls",
|
|
958
|
+
memory: "the memory scope namespace",
|
|
959
|
+
orchestrator: "the orchestrator's state namespace",
|
|
960
|
+
campaign: "the campaign's state namespace",
|
|
961
|
+
voiceagent: "the voice agent's state namespace",
|
|
962
|
+
current_flow: "engine bookkeeping (the current flow definition)",
|
|
963
|
+
modified_flow: "engine bookkeeping (dynamic flow modification)",
|
|
964
|
+
modification_history: "engine bookkeeping (dynamic flow modification)",
|
|
965
|
+
modification_pending: "engine bookkeeping (dynamic flow modification)",
|
|
966
|
+
modification_requests: "engine bookkeeping (dynamic flow modification)",
|
|
967
|
+
bottleneck_state: "engine bookkeeping (bottleneck analysis)"
|
|
968
|
+
},
|
|
969
|
+
prefixes: [
|
|
970
|
+
{ prefix: "_", reason: "keys starting with `_` are engine bookkeeping (current flow, loop and for_each context, taint lists such as `__untrusted_*`)" },
|
|
971
|
+
{ prefix: ".", reason: "keys starting with `.` are engine step-response slots" },
|
|
972
|
+
{ prefix: "memory.", reason: "the `memory.` namespace belongs to the engine" },
|
|
973
|
+
{ prefix: "orchestrator.", reason: "the `orchestrator.` namespace belongs to the orchestrator" },
|
|
974
|
+
{ prefix: "campaign.", reason: "the `campaign.` namespace belongs to the campaign engine" },
|
|
975
|
+
{ prefix: "voiceagent.", reason: "the `voiceagent.` namespace belongs to the voice agent" }
|
|
976
|
+
]
|
|
977
|
+
},
|
|
931
978
|
credentialEndpointPairing: {
|
|
932
979
|
stepFieldFormat: "steps.%s.query.%s",
|
|
933
980
|
loopStepFieldFormat: "steps.%s.loop.steps.%s.query.%s",
|
|
@@ -1257,6 +1304,11 @@ var aigentflow_spec_default = {
|
|
|
1257
1304
|
}
|
|
1258
1305
|
}
|
|
1259
1306
|
},
|
|
1307
|
+
templateDefaultPiped: {
|
|
1308
|
+
code: "template_default_piped",
|
|
1309
|
+
messageFormat: "`default` takes the value FIRST, and a pipe passes the piped value LAST: `%s` is `default <fallback> <value>`, so the fallback is always the result",
|
|
1310
|
+
suggestion: 'write the value first and the fallback second, as a call: `{{ default .x "fallback" }}`'
|
|
1311
|
+
},
|
|
1260
1312
|
complianceResidency: {
|
|
1261
1313
|
code: "compliance_residency_unenforced",
|
|
1262
1314
|
euResidencyCodes: ["EU", "DE", "FR", "IE", "NL", "IT", "ES"],
|
|
@@ -1555,6 +1607,7 @@ var aigentflow_spec_default = {
|
|
|
1555
1607
|
rate_limit: "FlowRateLimitConfig",
|
|
1556
1608
|
start: "scalar",
|
|
1557
1609
|
steps: "map<StepDefinition>",
|
|
1610
|
+
strict_templates: "scalar",
|
|
1558
1611
|
tags: "list<scalar>",
|
|
1559
1612
|
template: "FlowTemplateMetadata",
|
|
1560
1613
|
tool_discovery: "scalar",
|
|
@@ -1859,6 +1912,14 @@ var SERVER_OWNED_QUERY_KEYS = {
|
|
|
1859
1912
|
stepFieldFormat: aigentflow_spec_default.serverOwnedQueryKeys.stepFieldFormat,
|
|
1860
1913
|
loopStepFieldFormat: aigentflow_spec_default.serverOwnedQueryKeys.loopStepFieldFormat
|
|
1861
1914
|
};
|
|
1915
|
+
var FLOW_DATA_RESERVED_KEYS = {
|
|
1916
|
+
code: aigentflow_spec_default.flowDataReservedKeys.code,
|
|
1917
|
+
fieldFormat: aigentflow_spec_default.flowDataReservedKeys.fieldFormat,
|
|
1918
|
+
messageFormat: aigentflow_spec_default.flowDataReservedKeys.messageFormat,
|
|
1919
|
+
suggestion: aigentflow_spec_default.flowDataReservedKeys.suggestion,
|
|
1920
|
+
exact: aigentflow_spec_default.flowDataReservedKeys.exact,
|
|
1921
|
+
prefixes: aigentflow_spec_default.flowDataReservedKeys.prefixes
|
|
1922
|
+
};
|
|
1862
1923
|
var CREDENTIAL_ENDPOINT_PAIRING = {
|
|
1863
1924
|
stepFieldFormat: aigentflow_spec_default.credentialEndpointPairing.stepFieldFormat,
|
|
1864
1925
|
loopStepFieldFormat: aigentflow_spec_default.credentialEndpointPairing.loopStepFieldFormat,
|
|
@@ -1916,6 +1977,11 @@ var EXAMPLES = {
|
|
|
1916
1977
|
name: p.name,
|
|
1917
1978
|
re: new RegExp(p.expr, p.flags)
|
|
1918
1979
|
}))};
|
|
1980
|
+
var TEMPLATE_DEFAULT_PIPED = {
|
|
1981
|
+
code: aigentflow_spec_default.templateDefaultPiped.code,
|
|
1982
|
+
messageFormat: aigentflow_spec_default.templateDefaultPiped.messageFormat,
|
|
1983
|
+
suggestion: aigentflow_spec_default.templateDefaultPiped.suggestion
|
|
1984
|
+
};
|
|
1919
1985
|
|
|
1920
1986
|
// src/validators/executors.ts
|
|
1921
1987
|
var SCHEME_SEPARATOR = "://";
|
|
@@ -3773,16 +3839,16 @@ function skipSpaces(src, idx) {
|
|
|
3773
3839
|
}
|
|
3774
3840
|
return i;
|
|
3775
3841
|
}
|
|
3776
|
-
function scanString(src, start,
|
|
3842
|
+
function scanString(src, start, quote2) {
|
|
3777
3843
|
let i = start + 1;
|
|
3778
|
-
const raw =
|
|
3844
|
+
const raw = quote2 === "`";
|
|
3779
3845
|
while (i < src.length) {
|
|
3780
3846
|
const c = src[i];
|
|
3781
3847
|
if (!raw && c === "\\") {
|
|
3782
3848
|
i += 2;
|
|
3783
3849
|
continue;
|
|
3784
3850
|
}
|
|
3785
|
-
if (c ===
|
|
3851
|
+
if (c === quote2) return i;
|
|
3786
3852
|
i += 1;
|
|
3787
3853
|
}
|
|
3788
3854
|
return -1;
|
|
@@ -3879,6 +3945,117 @@ function splitPipeline(body) {
|
|
|
3879
3945
|
return stages;
|
|
3880
3946
|
}
|
|
3881
3947
|
|
|
3948
|
+
// src/template/gotmpl-default.ts
|
|
3949
|
+
var FUNCTION_NAME = "default";
|
|
3950
|
+
var PIPE = "|";
|
|
3951
|
+
var OPEN_PAREN = "(";
|
|
3952
|
+
var CLOSE_PAREN = ")";
|
|
3953
|
+
var MAX_NESTING_DEPTH = 100;
|
|
3954
|
+
var PIPELINE_KEYWORDS = /^(?:else\s+if|else\s+with|if|range|with|template|block)(?![\w$.])/;
|
|
3955
|
+
var NO_PIPELINE = /^(?:define|else|end|break|continue)(?![\w$.])/;
|
|
3956
|
+
var DECLARATION = /^\$\w*(?:\s*,\s*\$\w*)*\s*:?=/;
|
|
3957
|
+
function pipedDefaultPipelines(src) {
|
|
3958
|
+
const { actions, fatal } = lexActions(src);
|
|
3959
|
+
if (fatal) return [];
|
|
3960
|
+
const found = /* @__PURE__ */ new Set();
|
|
3961
|
+
try {
|
|
3962
|
+
for (const action of actions) {
|
|
3963
|
+
if (action.isComment || action.body === "") continue;
|
|
3964
|
+
scanAction(action.body, found);
|
|
3965
|
+
}
|
|
3966
|
+
} catch (err) {
|
|
3967
|
+
if (!(err instanceof RangeError)) throw err;
|
|
3968
|
+
return [];
|
|
3969
|
+
}
|
|
3970
|
+
return [...found].sort();
|
|
3971
|
+
}
|
|
3972
|
+
function scanAction(body, found) {
|
|
3973
|
+
let rest = body;
|
|
3974
|
+
const kw = PIPELINE_KEYWORDS.exec(rest);
|
|
3975
|
+
if (!kw && NO_PIPELINE.test(rest)) return;
|
|
3976
|
+
if (kw) {
|
|
3977
|
+
rest = rest.slice(kw[0].length).trimStart();
|
|
3978
|
+
const keyword = kw[0].replace(/\s+/g, " ");
|
|
3979
|
+
if (keyword === "template" || keyword === "block") {
|
|
3980
|
+
const q2 = rest[0];
|
|
3981
|
+
if (q2 === '"' || q2 === "`") {
|
|
3982
|
+
const end = scanString(rest, 0, q2);
|
|
3983
|
+
if (end === -1) return;
|
|
3984
|
+
rest = rest.slice(end + 1).trimStart();
|
|
3985
|
+
}
|
|
3986
|
+
}
|
|
3987
|
+
}
|
|
3988
|
+
if (rest === "") return;
|
|
3989
|
+
parsePipeline(rest, 0, false, found, 0);
|
|
3990
|
+
}
|
|
3991
|
+
function parsePipeline(src, start, nested, found, depth) {
|
|
3992
|
+
if (depth > MAX_NESTING_DEPTH) return { text: src.slice(start), end: src.length };
|
|
3993
|
+
let i = start;
|
|
3994
|
+
const n = src.length;
|
|
3995
|
+
let decl = "";
|
|
3996
|
+
const decMatch = DECLARATION.exec(src.slice(i));
|
|
3997
|
+
if (decMatch) {
|
|
3998
|
+
decl = decMatch[0].replace(/\s+/g, " ") + " ";
|
|
3999
|
+
i += decMatch[0].length;
|
|
4000
|
+
}
|
|
4001
|
+
const commands = [];
|
|
4002
|
+
let current = [];
|
|
4003
|
+
let firstWords = [];
|
|
4004
|
+
let pipedDefault = false;
|
|
4005
|
+
const pushCommand = () => {
|
|
4006
|
+
if (current.length > 0) {
|
|
4007
|
+
commands.push(current);
|
|
4008
|
+
firstWords.push(current[0]);
|
|
4009
|
+
}
|
|
4010
|
+
current = [];
|
|
4011
|
+
};
|
|
4012
|
+
while (i < n) {
|
|
4013
|
+
const c = src[i];
|
|
4014
|
+
if (c === " " || c === " " || c === "\n" || c === "\r") {
|
|
4015
|
+
i += 1;
|
|
4016
|
+
continue;
|
|
4017
|
+
}
|
|
4018
|
+
if (c === '"' || c === "`" || c === "'") {
|
|
4019
|
+
const end = scanString(src, i, c);
|
|
4020
|
+
const stop = end === -1 ? n - 1 : end;
|
|
4021
|
+
current.push(src.slice(i, stop + 1));
|
|
4022
|
+
i = stop + 1;
|
|
4023
|
+
continue;
|
|
4024
|
+
}
|
|
4025
|
+
if (c === OPEN_PAREN) {
|
|
4026
|
+
const inner = parsePipeline(src, i + 1, true, found, depth + 1);
|
|
4027
|
+
current.push(`(${inner.text})`);
|
|
4028
|
+
i = inner.end;
|
|
4029
|
+
continue;
|
|
4030
|
+
}
|
|
4031
|
+
if (c === CLOSE_PAREN) {
|
|
4032
|
+
if (nested) {
|
|
4033
|
+
i += 1;
|
|
4034
|
+
break;
|
|
4035
|
+
}
|
|
4036
|
+
i += 1;
|
|
4037
|
+
continue;
|
|
4038
|
+
}
|
|
4039
|
+
if (c === PIPE) {
|
|
4040
|
+
pushCommand();
|
|
4041
|
+
i += 1;
|
|
4042
|
+
continue;
|
|
4043
|
+
}
|
|
4044
|
+
let j = i;
|
|
4045
|
+
while (j < n && !/[\s|()"`']/.test(src[j])) j += 1;
|
|
4046
|
+
if (j === i) j = i + 1;
|
|
4047
|
+
current.push(src.slice(i, j));
|
|
4048
|
+
i = j;
|
|
4049
|
+
}
|
|
4050
|
+
pushCommand();
|
|
4051
|
+
firstWords.forEach((word, idx) => {
|
|
4052
|
+
if (idx > 0 && word === FUNCTION_NAME) pipedDefault = true;
|
|
4053
|
+
});
|
|
4054
|
+
const text = decl + commands.map((cmd) => cmd.join(" ")).join(` ${PIPE} `);
|
|
4055
|
+
if (pipedDefault) found.add(text);
|
|
4056
|
+
return { text, end: i };
|
|
4057
|
+
}
|
|
4058
|
+
|
|
3882
4059
|
// src/validators/processingOperations.ts
|
|
3883
4060
|
var PROCESSING_OP_GUARD_KEY = PROCESSING_OPERATIONS.guardKey;
|
|
3884
4061
|
var PROCESSING_PHASES = ["pre_processing", "post_processing"];
|
|
@@ -4025,6 +4202,17 @@ function checkTemplateString(value, field, stepID, issues, stats, opts, demote =
|
|
|
4025
4202
|
});
|
|
4026
4203
|
}
|
|
4027
4204
|
}
|
|
4205
|
+
if (errors.length > 0) return;
|
|
4206
|
+
for (const pipe of pipedDefaultPipelines(value)) {
|
|
4207
|
+
report({
|
|
4208
|
+
field,
|
|
4209
|
+
message: TEMPLATE_DEFAULT_PIPED.messageFormat.replace("%s", () => pipe),
|
|
4210
|
+
code: TEMPLATE_DEFAULT_PIPED.code,
|
|
4211
|
+
stepId: stepID,
|
|
4212
|
+
context: `Template: ${value}`,
|
|
4213
|
+
suggestion: TEMPLATE_DEFAULT_PIPED.suggestion
|
|
4214
|
+
});
|
|
4215
|
+
}
|
|
4028
4216
|
}
|
|
4029
4217
|
function walkStrings2(basePath, value, stepID, issues, stats, opts, check, demote = false) {
|
|
4030
4218
|
if (isString(value)) {
|
|
@@ -4263,6 +4451,35 @@ function validateRetiredKeys(flow, issues) {
|
|
|
4263
4451
|
}
|
|
4264
4452
|
}
|
|
4265
4453
|
|
|
4454
|
+
// src/validators/flowDataKeys.ts
|
|
4455
|
+
var KEY_DATA = "data";
|
|
4456
|
+
function quote(key) {
|
|
4457
|
+
return JSON.stringify(key);
|
|
4458
|
+
}
|
|
4459
|
+
function flowDataKeyReservation(key) {
|
|
4460
|
+
if (Object.prototype.hasOwnProperty.call(FLOW_DATA_RESERVED_KEYS.exact, key)) {
|
|
4461
|
+
return FLOW_DATA_RESERVED_KEYS.exact[key];
|
|
4462
|
+
}
|
|
4463
|
+
for (const p of FLOW_DATA_RESERVED_KEYS.prefixes) {
|
|
4464
|
+
if (key.startsWith(p.prefix)) return p.reason;
|
|
4465
|
+
}
|
|
4466
|
+
return void 0;
|
|
4467
|
+
}
|
|
4468
|
+
function validateFlowDataKeys(flow, issues) {
|
|
4469
|
+
const data = flow[KEY_DATA];
|
|
4470
|
+
if (!isRecord(data)) return;
|
|
4471
|
+
for (const key of Object.keys(data).sort()) {
|
|
4472
|
+
const reason = flowDataKeyReservation(key);
|
|
4473
|
+
if (reason === void 0) continue;
|
|
4474
|
+
issues.error({
|
|
4475
|
+
field: FLOW_DATA_RESERVED_KEYS.fieldFormat.replace("%s", () => key),
|
|
4476
|
+
message: FLOW_DATA_RESERVED_KEYS.messageFormat.replace("%s", () => quote(key)).replace("%s", () => reason),
|
|
4477
|
+
code: FLOW_DATA_RESERVED_KEYS.code,
|
|
4478
|
+
suggestion: FLOW_DATA_RESERVED_KEYS.suggestion
|
|
4479
|
+
});
|
|
4480
|
+
}
|
|
4481
|
+
}
|
|
4482
|
+
|
|
4266
4483
|
// src/validators/saveDoor.ts
|
|
4267
4484
|
var TOOL_DISCOVERY_MODES = /* @__PURE__ */ new Set(["eager", "lazy", "off"]);
|
|
4268
4485
|
var TEMPLATE_ACTION_OPEN2 = "{{";
|
|
@@ -6725,6 +6942,7 @@ function validateParsed(flow, opts, sources) {
|
|
|
6725
6942
|
validateSaveDoorExtras(f, issues);
|
|
6726
6943
|
validateExecutorConfigEnvScopes(f, issues);
|
|
6727
6944
|
validateServerOwnedQueryKeys(f, issues);
|
|
6945
|
+
validateFlowDataKeys(f, issues);
|
|
6728
6946
|
validateCredentialEndpointPairing(f, issues);
|
|
6729
6947
|
validateExamples(f, issues);
|
|
6730
6948
|
validateOrchestratorExons(f, issues);
|