@vaudience/aigentflow-flow-validator 0.22.0 → 0.24.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 +47 -1
- package/README.md +1 -1
- package/dist/cli.js +173 -23
- package/dist/index.cjs +172 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +172 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/PARITY.md
CHANGED
|
@@ -4,7 +4,47 @@ This document maps every rule in this JavaScript validator back to the AIgentFlo
|
|
|
4
4
|
Go reference implementation, records the intentional divergences, and defines the
|
|
5
5
|
discipline for keeping the two in sync.
|
|
6
6
|
|
|
7
|
-
**Tracks AIgentFlow flow schema: `v2.
|
|
7
|
+
**Tracks AIgentFlow flow schema: `v2.831.0`** (`SPEC_VERSION` in [`src/spec/aigentflow-spec.json`](./src/spec/aigentflow-spec.json)), which adds the rule `unknown_function_operation` and a clearer `unknown_executor_scheme` message (package 0.23.0, below). Earlier: the rule `flow_data_key_reserved` (package 0.22.0). Earlier: the `here` executor scheme, the `strict_templates` flow key and the rule `template_default_piped` (package 0.21.0). Earlier: the warning `compliance_residency_unenforced` (package 0.20.0). `orchestrator.region` / `orchestrator.enforce_region` (package 0.19.0). Earlier: the AigentChat executor was removed (package 0.18.0). Earlier additions: the `examples:` block (package 0.17.0), `server_owned_query_key` (package 0.15.1) from the AIgentFlow release after v2.790.0, `credential_endpoint_unpaired` (package 0.15.2) from AIgentFlow v2.793.0 and the release after it, and `display_name_too_long` (package 0.15.3) from AIgentFlow v2.802.0.
|
|
8
|
+
|
|
9
|
+
> package 0.24.0 — **`potential_infinite_loop` fires only on a cycle with no way out**, in step
|
|
10
|
+
> with the reference. It used to fire on every cycle reachable from the start step, so a deliberate
|
|
11
|
+
> ask-again loop (a step that routes back until a condition sends it onward) carried a warning that
|
|
12
|
+
> told the author nothing. The reference now computes the strongly connected sets (Tarjan) over
|
|
13
|
+
> `next.default` and `next.conditions[].goto` edges reachable from start (markers `null` /
|
|
14
|
+
> `orchestrator` and unknown steps are not followed; `end` is an ordinary step) and warns once when
|
|
15
|
+
> a cyclic set (more than one step, or a self-loop) has no exit. A member leaves the set when it has
|
|
16
|
+
> no `next`, its `next` has `parallel` (a fan-out is treated as an exit, not guessed at), its
|
|
17
|
+
> `next.default` is empty (when no condition matches the flow ends), or any default or condition
|
|
18
|
+
> target is `null`, `orchestrator` or a step outside the set. Code and severity are unchanged; the
|
|
19
|
+
> message now ends "a cycle of steps has no way out" and the suggestion asks for a `next.conditions`
|
|
20
|
+
> branch that leaves the cycle. It cannot prove an exit ever fires; that is undecidable from the
|
|
21
|
+
> YAML. Module: `validators/connectivity.ts`. Gates: ten `valid-cycle-*` / `warn-cycle-*` fixtures
|
|
22
|
+
> (the valid ones use `forbidWarningCodes`, the only way to assert a false-positive risk), shared
|
|
23
|
+
> byte for byte with the Go port. On the 227 bundled example flows both ports report 0 findings.
|
|
24
|
+
|
|
25
|
+
> package 0.23.0 — **`unknown_function_operation`**, in step with the reference (AIgentFlow
|
|
26
|
+
> v2.831.0; `specVersion` 2.831.0). One new **error**: a step or loop sub-step whose executor
|
|
27
|
+
> is `function://<a>/<b>` where `<a>/<b>` is not one of the 32 registered built-ins. Before
|
|
28
|
+
> this, `function://utils/noop` validated and then failed every run with "function
|
|
29
|
+
> 'utils/noop' not found".
|
|
30
|
+
>
|
|
31
|
+
> | Rule (reference) | Code | Severity | Module |
|
|
32
|
+
> | -------------------------------------------- | ---------------------------- | -------- | ---------------------------------------------------- |
|
|
33
|
+
> | `validateFunctionOperationKnown` (parser.go) | `unknown_function_operation` | error | `validators/executors.ts`, spec `functionOperations` |
|
|
34
|
+
>
|
|
35
|
+
> - **Operation** = authority + `/` + path of the executor URL (so `binary-processor/validate-and-store`).
|
|
36
|
+
> The set is closed and sorted in spec `functionOperations.operations`. Templated executors
|
|
37
|
+
> (containing `{{`), unparsable URLs (`invalid_executor_url` owns them) and other schemes are
|
|
38
|
+
> skipped; the scheme match is exact and case-sensitive.
|
|
39
|
+
> - **Finding.** Field `steps.<id>.executor` (loop sub-step: `steps.<id>.loop.steps[i].executor`,
|
|
40
|
+
> step id `<id>.<sub>`); message `step "<id>" uses function://<op>, which is not a built-in
|
|
41
|
+
function. Available: <sorted list, ", ">`; the suggestion carries the list.
|
|
42
|
+
> - **Divergence 23** (below): the reference tolerates this code on its load door.
|
|
43
|
+
> - **`unknown_executor_scheme` text (still a warning).** For `decide` the message appends
|
|
44
|
+
> `Decision models run through ai://{provider}/decide (for example ai://openai/decide), not
|
|
45
|
+
through a scheme of their own.`; for any other unknown scheme with a known scheme within
|
|
46
|
+
> edit distance 2 (case-insensitive; the closest, ties to the alphabetically first) it
|
|
47
|
+
> appends `Did you mean '<scheme>'?`. No verdict changes.
|
|
8
48
|
|
|
9
49
|
> package 0.22.0 — **`flow_data_key_reserved`**, in step with the reference (AIgentFlow
|
|
10
50
|
> v2.828.0; `specVersion` 2.828.0). One new **error**: a key of the top-level `data:` map
|
|
@@ -1110,6 +1150,7 @@ Comparison contract: **error `code` + `valid` verdict**, not message wording. Th
|
|
|
1110
1150
|
| `tool_discovery` vocabulary, mock scenario `delay`, empty `output:` entry | `validateToolDiscoveryVocabulary`, `validateMockScenarioDelays`, `validateOutputParameters` (parser.go) | `validators/saveDoor.ts` | `validate.test.ts`, `conformance.test.ts` |
|
|
1111
1151
|
| `executor_config` `${NAME}` references outside the key's scope (`executor_config_env_scope`) | `ValidateExecutorConfigEnvScopes` (parser.go) | `validators/executorConfigEnv.ts` | `v0150-save-door.test.ts`, `conformance.test.ts` |
|
|
1112
1152
|
| A step or loop sub-step `query:` declaring a server-owned key (`server_owned_query_key`) | `validateNoServerOwnedStepQueryKeys` (validation.go), `IsServerOwnedParamKey` | `validators/serverOwnedQueryKeys.ts` | `v0151-save-door.test.ts`, `conformance.test.ts` |
|
|
1153
|
+
| A `function://` operation outside the built-in set (`unknown_function_operation`) | `validateFunctionOperationKnown` (parser.go) | `validators/executors.ts` | `v0230-function-operation.test.ts`, `conformance.test.ts` |
|
|
1113
1154
|
| A top-level `data:` key naming engine state (`flow_data_key_reserved`) | `FlowDataKeyReservation`, `validateFlowDataKeysAreNotReserved` (validation.flowdata.go) | `validators/flowDataKeys.ts` | `v0220-flow-data-keys.test.ts`, `conformance.test.ts` |
|
|
1114
1155
|
| An endpoint a server-supplied credential will never be sent to (`credential_endpoint_unpaired`, warning) | `validateCredentialEndpointPairing`, `validateFamilyCredentialEndpointPairing` (validation.go) | `validators/credentialEndpoint.ts`, `validators/goUrl.ts` | `v0152-credential-endpoint.test.ts`, `conformance.test.ts` |
|
|
1115
1156
|
| The `examples:` block: 58 codes, one walker (ids, inputs, references, expectations, checkpoints, variants) | the examples walker (`ValidateExamples`) | `validators/examples.ts`, `validators/goUrl.ts` | `examples.test.ts`, `conformance.test.ts` |
|
|
@@ -1380,6 +1421,11 @@ number their shared divergences alike._
|
|
|
1380
1421
|
- The recursion bound of 32 on a nested value is the reference's own and is not in the
|
|
1381
1422
|
spec.
|
|
1382
1423
|
|
|
1424
|
+
23. **`unknown_function_operation` is tolerated on the reference's load door (0.23.0).** The
|
|
1425
|
+
reference refuses an unknown `function://` operation when a flow is _saved_ but loads
|
|
1426
|
+
(and then fails at run time) a stored flow that already carries one. This validator
|
|
1427
|
+
answers "would this save", so it is an error here, as for every other save-door rule.
|
|
1428
|
+
|
|
1383
1429
|
---
|
|
1384
1430
|
|
|
1385
1431
|
## The examples block
|
package/README.md
CHANGED
|
@@ -116,7 +116,7 @@ Low-level: parse YAML into `{ flow?, parseErrors, parseWarnings }` without valid
|
|
|
116
116
|
|
|
117
117
|
### `SPEC_VERSION: string`
|
|
118
118
|
|
|
119
|
-
The AIgentFlow flow-schema version this build tracks (e.g. `"2.
|
|
119
|
+
The AIgentFlow flow-schema version this build tracks (e.g. `"2.831.0"`).
|
|
120
120
|
|
|
121
121
|
### Options
|
|
122
122
|
|
package/dist/cli.js
CHANGED
|
@@ -438,7 +438,7 @@ function validateBasicStructure(flow, issues) {
|
|
|
438
438
|
message: "Executor is required for each step",
|
|
439
439
|
code: "missing_required_field",
|
|
440
440
|
stepId: stepID,
|
|
441
|
-
suggestion: "Specify an executor URL (e.g., 'function://
|
|
441
|
+
suggestion: "Specify an executor URL (e.g., 'function://utils/log')"
|
|
442
442
|
});
|
|
443
443
|
} else if (executorText === null) {
|
|
444
444
|
issues.error({
|
|
@@ -455,7 +455,7 @@ function validateBasicStructure(flow, issues) {
|
|
|
455
455
|
// src/spec/aigentflow-spec.json
|
|
456
456
|
var aigentflow_spec_default = {
|
|
457
457
|
$comment: "Vendored enum surface mirrored from the AIgentFlow Go reference implementation. Regenerate / review on every flow-YAML schema change. See PARITY.md. Values here are public protocol/type names only.",
|
|
458
|
-
specVersion: "2.
|
|
458
|
+
specVersion: "2.831.0",
|
|
459
459
|
inputSchemaVersion: 1,
|
|
460
460
|
executorSchemes: [
|
|
461
461
|
"a2a",
|
|
@@ -965,6 +965,47 @@ var aigentflow_spec_default = {
|
|
|
965
965
|
stepFieldFormat: "steps.%s.query.%s",
|
|
966
966
|
loopStepFieldFormat: "steps.%s.loop.steps.%s.query.%s"
|
|
967
967
|
},
|
|
968
|
+
functionOperations: {
|
|
969
|
+
$comment: `unknown_function_operation (ERROR): a step or loop sub-step whose executor is function://<authority>/<path> where authority+"/"+path is not in operations. Templated executors (containing templateActionOpen), unparsable URLs (invalid_executor_url owns them) and other schemes are skipped; the scheme match is exact and case-sensitive, as in the reference. The message is messageFormat with the stepId, the operation, then operations joined by ', '. Reference: validateFunctionOperationKnown (parser.go), functionSchemaOperations.`,
|
|
970
|
+
code: "unknown_function_operation",
|
|
971
|
+
scheme: "function",
|
|
972
|
+
messageFormat: "step %s uses function://%s, which is not a built-in function. Available: %s",
|
|
973
|
+
suggestionFormat: "Use one of the built-in function operations: %s. For custom logic use a script://, http:// or ai:// executor instead.",
|
|
974
|
+
operations: [
|
|
975
|
+
"binary-processor/validate-and-store",
|
|
976
|
+
"data/create",
|
|
977
|
+
"data/from-json",
|
|
978
|
+
"data/to-json",
|
|
979
|
+
"data/validate",
|
|
980
|
+
"demo/adult-processor",
|
|
981
|
+
"demo/calculator",
|
|
982
|
+
"demo/data-processor",
|
|
983
|
+
"demo/finalizer",
|
|
984
|
+
"demo/senior-processor",
|
|
985
|
+
"demo/youth-processor",
|
|
986
|
+
"document/finalize",
|
|
987
|
+
"document/prepare",
|
|
988
|
+
"json/validate",
|
|
989
|
+
"math/basic",
|
|
990
|
+
"math/calculate",
|
|
991
|
+
"math/sum",
|
|
992
|
+
"results-compiler/aggregate",
|
|
993
|
+
"system/info",
|
|
994
|
+
"text/analysis",
|
|
995
|
+
"text/count-characters",
|
|
996
|
+
"text/count-lines",
|
|
997
|
+
"text/count-occurrences",
|
|
998
|
+
"text/count-words",
|
|
999
|
+
"text/find-pattern",
|
|
1000
|
+
"text/generate",
|
|
1001
|
+
"text/replace-text",
|
|
1002
|
+
"text/search",
|
|
1003
|
+
"utils/datetime",
|
|
1004
|
+
"utils/format-date",
|
|
1005
|
+
"utils/log",
|
|
1006
|
+
"utils/parse-date"
|
|
1007
|
+
]
|
|
1008
|
+
},
|
|
968
1009
|
flowDataReservedKeys: {
|
|
969
1010
|
$comment: "flow_data_key_reserved (ERROR, no loop demotion): a key of the top-level data: map that names engine state. data: is seeded into the mission's .data namespace, where the engine keeps its own bookkeeping, so a flow declaring such a key would forge it. Matching is exact and case-sensitive. A key is reserved when it is one of exact (each with its reason), or starts with one of prefixes (checked in order, first wins; each with its reason). The finding's field is fieldFormat with the key; the message is messageFormat with the key (JSON-quoted, as Go's %q) then the reason; the suggestion is verbatim. Findings are sorted by key. Reference: FlowDataKeyReservation, validateFlowDataKeysAreNotReserved (aigentflow#247 review).",
|
|
970
1011
|
code: "flow_data_key_reserved",
|
|
@@ -2080,9 +2121,51 @@ var TEMPLATE_DEFAULT_PIPED = {
|
|
|
2080
2121
|
messageFormat: aigentflow_spec_default.templateDefaultPiped.messageFormat,
|
|
2081
2122
|
suggestion: aigentflow_spec_default.templateDefaultPiped.suggestion
|
|
2082
2123
|
};
|
|
2124
|
+
var FUNCTION_OPERATIONS = {
|
|
2125
|
+
code: aigentflow_spec_default.functionOperations.code,
|
|
2126
|
+
scheme: aigentflow_spec_default.functionOperations.scheme,
|
|
2127
|
+
messageFormat: aigentflow_spec_default.functionOperations.messageFormat,
|
|
2128
|
+
suggestionFormat: aigentflow_spec_default.functionOperations.suggestionFormat,
|
|
2129
|
+
operations: new Set(aigentflow_spec_default.functionOperations.operations),
|
|
2130
|
+
sorted: aigentflow_spec_default.functionOperations.operations
|
|
2131
|
+
};
|
|
2083
2132
|
|
|
2084
2133
|
// src/validators/executors.ts
|
|
2085
2134
|
var SCHEME_SEPARATOR = "://";
|
|
2135
|
+
var UNKNOWN_SCHEME_BASE = (scheme) => `Unknown executor scheme '${scheme}'. It may be valid in a newer AIgentFlow release, or it could be a typo.`;
|
|
2136
|
+
var DECIDE_SCHEME = "decide";
|
|
2137
|
+
var DECIDE_HINT = "Decision models run through ai://{provider}/decide (for example ai://openai/decide), not through a scheme of their own.";
|
|
2138
|
+
var MAX_SCHEME_EDIT_DISTANCE = 2;
|
|
2139
|
+
function editDistance(a, b) {
|
|
2140
|
+
let prev = Array.from({ length: b.length + 1 }, (_, j) => j);
|
|
2141
|
+
for (let i = 1; i <= a.length; i++) {
|
|
2142
|
+
const cur = [i];
|
|
2143
|
+
for (let j = 1; j <= b.length; j++) {
|
|
2144
|
+
cur[j] = Math.min(
|
|
2145
|
+
prev[j] + 1,
|
|
2146
|
+
cur[j - 1] + 1,
|
|
2147
|
+
prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1)
|
|
2148
|
+
);
|
|
2149
|
+
}
|
|
2150
|
+
prev = cur;
|
|
2151
|
+
}
|
|
2152
|
+
return prev[b.length];
|
|
2153
|
+
}
|
|
2154
|
+
function unknownSchemeMessage(scheme) {
|
|
2155
|
+
const lower = scheme.toLowerCase();
|
|
2156
|
+
const base = UNKNOWN_SCHEME_BASE(scheme);
|
|
2157
|
+
if (lower === DECIDE_SCHEME) return `${base} ${DECIDE_HINT}`;
|
|
2158
|
+
let best;
|
|
2159
|
+
let bestDist = MAX_SCHEME_EDIT_DISTANCE + 1;
|
|
2160
|
+
for (const known of [...EXECUTOR_SCHEMES].sort()) {
|
|
2161
|
+
const d = editDistance(lower, known);
|
|
2162
|
+
if (d < bestDist) {
|
|
2163
|
+
best = known;
|
|
2164
|
+
bestDist = d;
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
return best === void 0 ? base : `${base} Did you mean '${best}'?`;
|
|
2168
|
+
}
|
|
2086
2169
|
function checkExecutor(executor, field, stepID, issues) {
|
|
2087
2170
|
const sepIdx = executor.indexOf(SCHEME_SEPARATOR);
|
|
2088
2171
|
const scheme = sepIdx > 0 ? executor.slice(0, sepIdx) : "";
|
|
@@ -2101,11 +2184,28 @@ function checkExecutor(executor, field, stepID, issues) {
|
|
|
2101
2184
|
if (!EXECUTOR_SCHEMES.has(scheme.toLowerCase())) {
|
|
2102
2185
|
issues.warn({
|
|
2103
2186
|
field,
|
|
2104
|
-
message:
|
|
2187
|
+
message: unknownSchemeMessage(scheme),
|
|
2105
2188
|
code: "unknown_executor_scheme",
|
|
2106
2189
|
stepId: stepID
|
|
2107
2190
|
});
|
|
2191
|
+
return;
|
|
2108
2192
|
}
|
|
2193
|
+
checkFunctionOperation(executor, field, stepID, templated, issues);
|
|
2194
|
+
}
|
|
2195
|
+
function checkFunctionOperation(executor, field, stepID, templated, issues) {
|
|
2196
|
+
if (templated) return;
|
|
2197
|
+
const m = EXECUTOR_URL_PATTERN.exec(executor);
|
|
2198
|
+
if (m === null || m[1] !== FUNCTION_OPERATIONS.scheme) return;
|
|
2199
|
+
const op = `${m[2]}/${m[3]}`;
|
|
2200
|
+
if (FUNCTION_OPERATIONS.operations.has(op)) return;
|
|
2201
|
+
const list = FUNCTION_OPERATIONS.sorted.join(", ");
|
|
2202
|
+
issues.error({
|
|
2203
|
+
field,
|
|
2204
|
+
message: FUNCTION_OPERATIONS.messageFormat.replace("%s", () => JSON.stringify(stepID)).replace("%s", () => op).replace("%s", () => list),
|
|
2205
|
+
code: FUNCTION_OPERATIONS.code,
|
|
2206
|
+
stepId: stepID,
|
|
2207
|
+
suggestion: FUNCTION_OPERATIONS.suggestionFormat.replace("%s", () => list)
|
|
2208
|
+
});
|
|
2109
2209
|
}
|
|
2110
2210
|
function validateExecutors(flow, issues) {
|
|
2111
2211
|
const steps = flow.steps;
|
|
@@ -2632,32 +2732,82 @@ function validateConnectivity(flow, issues) {
|
|
|
2632
2732
|
});
|
|
2633
2733
|
}
|
|
2634
2734
|
}
|
|
2635
|
-
|
|
2636
|
-
const recStack = /* @__PURE__ */ new Set();
|
|
2637
|
-
const hasCycle = (stepID) => {
|
|
2638
|
-
if (recStack.has(stepID)) return true;
|
|
2639
|
-
if (visited.has(stepID)) return false;
|
|
2640
|
-
visited.add(stepID);
|
|
2641
|
-
recStack.add(stepID);
|
|
2642
|
-
const step = steps[stepID];
|
|
2643
|
-
if (isRecord(step) && isRecord(step.next)) {
|
|
2644
|
-
const next = step.next;
|
|
2645
|
-
for (const target of cycleTargets(next, `steps.${stepID}.next`, issues)) {
|
|
2646
|
-
if (hasCycle(target)) return true;
|
|
2647
|
-
}
|
|
2648
|
-
}
|
|
2649
|
-
recStack.delete(stepID);
|
|
2650
|
-
return false;
|
|
2651
|
-
};
|
|
2652
|
-
if (hasCycle(start)) {
|
|
2735
|
+
if (cycleWithoutExit(steps, start, issues)) {
|
|
2653
2736
|
issues.warn({
|
|
2654
2737
|
field: "steps",
|
|
2655
|
-
message: "Potential infinite loop detected in step flow",
|
|
2738
|
+
message: "Potential infinite loop detected in step flow: a cycle of steps has no way out",
|
|
2656
2739
|
code: "potential_infinite_loop",
|
|
2657
|
-
suggestion: "
|
|
2740
|
+
suggestion: "Give one step in the cycle a `next.conditions` branch that leaves it (or end the flow there)"
|
|
2658
2741
|
});
|
|
2659
2742
|
}
|
|
2660
2743
|
}
|
|
2744
|
+
function cycleEdges(step, stepID, issues) {
|
|
2745
|
+
if (!isRecord(step) || !isRecord(step.next)) return [];
|
|
2746
|
+
const next = step.next;
|
|
2747
|
+
const nextPath = `steps.${stepID}.next`;
|
|
2748
|
+
const out = [];
|
|
2749
|
+
const def = issues.stringOf(next, "default", nextPath);
|
|
2750
|
+
if (def !== null && def !== "") out.push(def);
|
|
2751
|
+
if (Array.isArray(next.conditions)) {
|
|
2752
|
+
next.conditions.forEach((cond, i) => {
|
|
2753
|
+
if (!isRecord(cond)) return;
|
|
2754
|
+
const goto = issues.stringOf(cond, "goto", `${nextPath}.conditions[${i}]`);
|
|
2755
|
+
if (goto !== null && goto !== "") out.push(goto);
|
|
2756
|
+
});
|
|
2757
|
+
}
|
|
2758
|
+
return out;
|
|
2759
|
+
}
|
|
2760
|
+
function stepLeavesCycle(steps, stepID, inCycle, issues) {
|
|
2761
|
+
const step = steps[stepID];
|
|
2762
|
+
if (!isRecord(step) || !isRecord(step.next)) return true;
|
|
2763
|
+
const next = step.next;
|
|
2764
|
+
if (next.parallel !== void 0 && next.parallel !== null) return true;
|
|
2765
|
+
const def = issues.stringOf(next, "default", `steps.${stepID}.next`);
|
|
2766
|
+
if (def === null || def === "") return true;
|
|
2767
|
+
for (const target of cycleEdges(step, stepID, issues)) {
|
|
2768
|
+
if (isMarker(target) || !inCycle.has(target)) return true;
|
|
2769
|
+
}
|
|
2770
|
+
return false;
|
|
2771
|
+
}
|
|
2772
|
+
function cycleWithoutExit(steps, start, issues) {
|
|
2773
|
+
let index = 0;
|
|
2774
|
+
const indices = /* @__PURE__ */ new Map();
|
|
2775
|
+
const low = /* @__PURE__ */ new Map();
|
|
2776
|
+
const onStack = /* @__PURE__ */ new Set();
|
|
2777
|
+
const stack = [];
|
|
2778
|
+
let found = false;
|
|
2779
|
+
const strong = (id) => {
|
|
2780
|
+
indices.set(id, index);
|
|
2781
|
+
low.set(id, index);
|
|
2782
|
+
index++;
|
|
2783
|
+
stack.push(id);
|
|
2784
|
+
onStack.add(id);
|
|
2785
|
+
for (const target of cycleEdges(steps[id], id, issues)) {
|
|
2786
|
+
if (isMarker(target) || !Object.prototype.hasOwnProperty.call(steps, target)) continue;
|
|
2787
|
+
if (!indices.has(target)) {
|
|
2788
|
+
strong(target);
|
|
2789
|
+
low.set(id, Math.min(low.get(id), low.get(target)));
|
|
2790
|
+
} else if (onStack.has(target)) {
|
|
2791
|
+
low.set(id, Math.min(low.get(id), indices.get(target)));
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
if (low.get(id) !== indices.get(id)) return;
|
|
2795
|
+
const component = [];
|
|
2796
|
+
for (; ; ) {
|
|
2797
|
+
const top = stack.pop();
|
|
2798
|
+
onStack.delete(top);
|
|
2799
|
+
component.push(top);
|
|
2800
|
+
if (top === id) break;
|
|
2801
|
+
}
|
|
2802
|
+
const inCycle = new Set(component);
|
|
2803
|
+
const cyclic = component.length > 1 || cycleEdges(steps[id], id, issues).includes(id);
|
|
2804
|
+
if (!cyclic) return;
|
|
2805
|
+
if (component.some((member) => stepLeavesCycle(steps, member, inCycle, issues))) return;
|
|
2806
|
+
found = true;
|
|
2807
|
+
};
|
|
2808
|
+
if (Object.prototype.hasOwnProperty.call(steps, start)) strong(start);
|
|
2809
|
+
return found;
|
|
2810
|
+
}
|
|
2661
2811
|
|
|
2662
2812
|
// src/validators/nextLogic.ts
|
|
2663
2813
|
var NEXT_NULL = "null";
|
package/dist/index.cjs
CHANGED
|
@@ -435,7 +435,7 @@ function validateBasicStructure(flow, issues) {
|
|
|
435
435
|
message: "Executor is required for each step",
|
|
436
436
|
code: "missing_required_field",
|
|
437
437
|
stepId: stepID,
|
|
438
|
-
suggestion: "Specify an executor URL (e.g., 'function://
|
|
438
|
+
suggestion: "Specify an executor URL (e.g., 'function://utils/log')"
|
|
439
439
|
});
|
|
440
440
|
} else if (executorText === null) {
|
|
441
441
|
issues.error({
|
|
@@ -451,7 +451,7 @@ function validateBasicStructure(flow, issues) {
|
|
|
451
451
|
|
|
452
452
|
// src/spec/aigentflow-spec.json
|
|
453
453
|
var aigentflow_spec_default = {
|
|
454
|
-
specVersion: "2.
|
|
454
|
+
specVersion: "2.831.0",
|
|
455
455
|
inputSchemaVersion: 1,
|
|
456
456
|
executorSchemes: [
|
|
457
457
|
"a2a",
|
|
@@ -948,6 +948,46 @@ var aigentflow_spec_default = {
|
|
|
948
948
|
stepFieldFormat: "steps.%s.query.%s",
|
|
949
949
|
loopStepFieldFormat: "steps.%s.loop.steps.%s.query.%s"
|
|
950
950
|
},
|
|
951
|
+
functionOperations: {
|
|
952
|
+
code: "unknown_function_operation",
|
|
953
|
+
scheme: "function",
|
|
954
|
+
messageFormat: "step %s uses function://%s, which is not a built-in function. Available: %s",
|
|
955
|
+
suggestionFormat: "Use one of the built-in function operations: %s. For custom logic use a script://, http:// or ai:// executor instead.",
|
|
956
|
+
operations: [
|
|
957
|
+
"binary-processor/validate-and-store",
|
|
958
|
+
"data/create",
|
|
959
|
+
"data/from-json",
|
|
960
|
+
"data/to-json",
|
|
961
|
+
"data/validate",
|
|
962
|
+
"demo/adult-processor",
|
|
963
|
+
"demo/calculator",
|
|
964
|
+
"demo/data-processor",
|
|
965
|
+
"demo/finalizer",
|
|
966
|
+
"demo/senior-processor",
|
|
967
|
+
"demo/youth-processor",
|
|
968
|
+
"document/finalize",
|
|
969
|
+
"document/prepare",
|
|
970
|
+
"json/validate",
|
|
971
|
+
"math/basic",
|
|
972
|
+
"math/calculate",
|
|
973
|
+
"math/sum",
|
|
974
|
+
"results-compiler/aggregate",
|
|
975
|
+
"system/info",
|
|
976
|
+
"text/analysis",
|
|
977
|
+
"text/count-characters",
|
|
978
|
+
"text/count-lines",
|
|
979
|
+
"text/count-occurrences",
|
|
980
|
+
"text/count-words",
|
|
981
|
+
"text/find-pattern",
|
|
982
|
+
"text/generate",
|
|
983
|
+
"text/replace-text",
|
|
984
|
+
"text/search",
|
|
985
|
+
"utils/datetime",
|
|
986
|
+
"utils/format-date",
|
|
987
|
+
"utils/log",
|
|
988
|
+
"utils/parse-date"
|
|
989
|
+
]
|
|
990
|
+
},
|
|
951
991
|
flowDataReservedKeys: {
|
|
952
992
|
code: "flow_data_key_reserved",
|
|
953
993
|
fieldFormat: "data.%s",
|
|
@@ -1982,9 +2022,51 @@ var TEMPLATE_DEFAULT_PIPED = {
|
|
|
1982
2022
|
messageFormat: aigentflow_spec_default.templateDefaultPiped.messageFormat,
|
|
1983
2023
|
suggestion: aigentflow_spec_default.templateDefaultPiped.suggestion
|
|
1984
2024
|
};
|
|
2025
|
+
var FUNCTION_OPERATIONS = {
|
|
2026
|
+
code: aigentflow_spec_default.functionOperations.code,
|
|
2027
|
+
scheme: aigentflow_spec_default.functionOperations.scheme,
|
|
2028
|
+
messageFormat: aigentflow_spec_default.functionOperations.messageFormat,
|
|
2029
|
+
suggestionFormat: aigentflow_spec_default.functionOperations.suggestionFormat,
|
|
2030
|
+
operations: new Set(aigentflow_spec_default.functionOperations.operations),
|
|
2031
|
+
sorted: aigentflow_spec_default.functionOperations.operations
|
|
2032
|
+
};
|
|
1985
2033
|
|
|
1986
2034
|
// src/validators/executors.ts
|
|
1987
2035
|
var SCHEME_SEPARATOR = "://";
|
|
2036
|
+
var UNKNOWN_SCHEME_BASE = (scheme) => `Unknown executor scheme '${scheme}'. It may be valid in a newer AIgentFlow release, or it could be a typo.`;
|
|
2037
|
+
var DECIDE_SCHEME = "decide";
|
|
2038
|
+
var DECIDE_HINT = "Decision models run through ai://{provider}/decide (for example ai://openai/decide), not through a scheme of their own.";
|
|
2039
|
+
var MAX_SCHEME_EDIT_DISTANCE = 2;
|
|
2040
|
+
function editDistance(a, b) {
|
|
2041
|
+
let prev = Array.from({ length: b.length + 1 }, (_, j) => j);
|
|
2042
|
+
for (let i = 1; i <= a.length; i++) {
|
|
2043
|
+
const cur = [i];
|
|
2044
|
+
for (let j = 1; j <= b.length; j++) {
|
|
2045
|
+
cur[j] = Math.min(
|
|
2046
|
+
prev[j] + 1,
|
|
2047
|
+
cur[j - 1] + 1,
|
|
2048
|
+
prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1)
|
|
2049
|
+
);
|
|
2050
|
+
}
|
|
2051
|
+
prev = cur;
|
|
2052
|
+
}
|
|
2053
|
+
return prev[b.length];
|
|
2054
|
+
}
|
|
2055
|
+
function unknownSchemeMessage(scheme) {
|
|
2056
|
+
const lower = scheme.toLowerCase();
|
|
2057
|
+
const base = UNKNOWN_SCHEME_BASE(scheme);
|
|
2058
|
+
if (lower === DECIDE_SCHEME) return `${base} ${DECIDE_HINT}`;
|
|
2059
|
+
let best;
|
|
2060
|
+
let bestDist = MAX_SCHEME_EDIT_DISTANCE + 1;
|
|
2061
|
+
for (const known of [...EXECUTOR_SCHEMES].sort()) {
|
|
2062
|
+
const d = editDistance(lower, known);
|
|
2063
|
+
if (d < bestDist) {
|
|
2064
|
+
best = known;
|
|
2065
|
+
bestDist = d;
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
return best === void 0 ? base : `${base} Did you mean '${best}'?`;
|
|
2069
|
+
}
|
|
1988
2070
|
function checkExecutor(executor, field, stepID, issues) {
|
|
1989
2071
|
const sepIdx = executor.indexOf(SCHEME_SEPARATOR);
|
|
1990
2072
|
const scheme = sepIdx > 0 ? executor.slice(0, sepIdx) : "";
|
|
@@ -2003,11 +2085,28 @@ function checkExecutor(executor, field, stepID, issues) {
|
|
|
2003
2085
|
if (!EXECUTOR_SCHEMES.has(scheme.toLowerCase())) {
|
|
2004
2086
|
issues.warn({
|
|
2005
2087
|
field,
|
|
2006
|
-
message:
|
|
2088
|
+
message: unknownSchemeMessage(scheme),
|
|
2007
2089
|
code: "unknown_executor_scheme",
|
|
2008
2090
|
stepId: stepID
|
|
2009
2091
|
});
|
|
2092
|
+
return;
|
|
2010
2093
|
}
|
|
2094
|
+
checkFunctionOperation(executor, field, stepID, templated, issues);
|
|
2095
|
+
}
|
|
2096
|
+
function checkFunctionOperation(executor, field, stepID, templated, issues) {
|
|
2097
|
+
if (templated) return;
|
|
2098
|
+
const m = EXECUTOR_URL_PATTERN.exec(executor);
|
|
2099
|
+
if (m === null || m[1] !== FUNCTION_OPERATIONS.scheme) return;
|
|
2100
|
+
const op = `${m[2]}/${m[3]}`;
|
|
2101
|
+
if (FUNCTION_OPERATIONS.operations.has(op)) return;
|
|
2102
|
+
const list = FUNCTION_OPERATIONS.sorted.join(", ");
|
|
2103
|
+
issues.error({
|
|
2104
|
+
field,
|
|
2105
|
+
message: FUNCTION_OPERATIONS.messageFormat.replace("%s", () => JSON.stringify(stepID)).replace("%s", () => op).replace("%s", () => list),
|
|
2106
|
+
code: FUNCTION_OPERATIONS.code,
|
|
2107
|
+
stepId: stepID,
|
|
2108
|
+
suggestion: FUNCTION_OPERATIONS.suggestionFormat.replace("%s", () => list)
|
|
2109
|
+
});
|
|
2011
2110
|
}
|
|
2012
2111
|
function validateExecutors(flow, issues) {
|
|
2013
2112
|
const steps = flow.steps;
|
|
@@ -2534,32 +2633,82 @@ function validateConnectivity(flow, issues) {
|
|
|
2534
2633
|
});
|
|
2535
2634
|
}
|
|
2536
2635
|
}
|
|
2537
|
-
|
|
2538
|
-
const recStack = /* @__PURE__ */ new Set();
|
|
2539
|
-
const hasCycle = (stepID) => {
|
|
2540
|
-
if (recStack.has(stepID)) return true;
|
|
2541
|
-
if (visited.has(stepID)) return false;
|
|
2542
|
-
visited.add(stepID);
|
|
2543
|
-
recStack.add(stepID);
|
|
2544
|
-
const step = steps[stepID];
|
|
2545
|
-
if (isRecord(step) && isRecord(step.next)) {
|
|
2546
|
-
const next = step.next;
|
|
2547
|
-
for (const target of cycleTargets(next, `steps.${stepID}.next`, issues)) {
|
|
2548
|
-
if (hasCycle(target)) return true;
|
|
2549
|
-
}
|
|
2550
|
-
}
|
|
2551
|
-
recStack.delete(stepID);
|
|
2552
|
-
return false;
|
|
2553
|
-
};
|
|
2554
|
-
if (hasCycle(start)) {
|
|
2636
|
+
if (cycleWithoutExit(steps, start, issues)) {
|
|
2555
2637
|
issues.warn({
|
|
2556
2638
|
field: "steps",
|
|
2557
|
-
message: "Potential infinite loop detected in step flow",
|
|
2639
|
+
message: "Potential infinite loop detected in step flow: a cycle of steps has no way out",
|
|
2558
2640
|
code: "potential_infinite_loop",
|
|
2559
|
-
suggestion: "
|
|
2641
|
+
suggestion: "Give one step in the cycle a `next.conditions` branch that leaves it (or end the flow there)"
|
|
2560
2642
|
});
|
|
2561
2643
|
}
|
|
2562
2644
|
}
|
|
2645
|
+
function cycleEdges(step, stepID, issues) {
|
|
2646
|
+
if (!isRecord(step) || !isRecord(step.next)) return [];
|
|
2647
|
+
const next = step.next;
|
|
2648
|
+
const nextPath = `steps.${stepID}.next`;
|
|
2649
|
+
const out = [];
|
|
2650
|
+
const def = issues.stringOf(next, "default", nextPath);
|
|
2651
|
+
if (def !== null && def !== "") out.push(def);
|
|
2652
|
+
if (Array.isArray(next.conditions)) {
|
|
2653
|
+
next.conditions.forEach((cond, i) => {
|
|
2654
|
+
if (!isRecord(cond)) return;
|
|
2655
|
+
const goto = issues.stringOf(cond, "goto", `${nextPath}.conditions[${i}]`);
|
|
2656
|
+
if (goto !== null && goto !== "") out.push(goto);
|
|
2657
|
+
});
|
|
2658
|
+
}
|
|
2659
|
+
return out;
|
|
2660
|
+
}
|
|
2661
|
+
function stepLeavesCycle(steps, stepID, inCycle, issues) {
|
|
2662
|
+
const step = steps[stepID];
|
|
2663
|
+
if (!isRecord(step) || !isRecord(step.next)) return true;
|
|
2664
|
+
const next = step.next;
|
|
2665
|
+
if (next.parallel !== void 0 && next.parallel !== null) return true;
|
|
2666
|
+
const def = issues.stringOf(next, "default", `steps.${stepID}.next`);
|
|
2667
|
+
if (def === null || def === "") return true;
|
|
2668
|
+
for (const target of cycleEdges(step, stepID, issues)) {
|
|
2669
|
+
if (isMarker(target) || !inCycle.has(target)) return true;
|
|
2670
|
+
}
|
|
2671
|
+
return false;
|
|
2672
|
+
}
|
|
2673
|
+
function cycleWithoutExit(steps, start, issues) {
|
|
2674
|
+
let index = 0;
|
|
2675
|
+
const indices = /* @__PURE__ */ new Map();
|
|
2676
|
+
const low = /* @__PURE__ */ new Map();
|
|
2677
|
+
const onStack = /* @__PURE__ */ new Set();
|
|
2678
|
+
const stack = [];
|
|
2679
|
+
let found = false;
|
|
2680
|
+
const strong = (id) => {
|
|
2681
|
+
indices.set(id, index);
|
|
2682
|
+
low.set(id, index);
|
|
2683
|
+
index++;
|
|
2684
|
+
stack.push(id);
|
|
2685
|
+
onStack.add(id);
|
|
2686
|
+
for (const target of cycleEdges(steps[id], id, issues)) {
|
|
2687
|
+
if (isMarker(target) || !Object.prototype.hasOwnProperty.call(steps, target)) continue;
|
|
2688
|
+
if (!indices.has(target)) {
|
|
2689
|
+
strong(target);
|
|
2690
|
+
low.set(id, Math.min(low.get(id), low.get(target)));
|
|
2691
|
+
} else if (onStack.has(target)) {
|
|
2692
|
+
low.set(id, Math.min(low.get(id), indices.get(target)));
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
if (low.get(id) !== indices.get(id)) return;
|
|
2696
|
+
const component = [];
|
|
2697
|
+
for (; ; ) {
|
|
2698
|
+
const top = stack.pop();
|
|
2699
|
+
onStack.delete(top);
|
|
2700
|
+
component.push(top);
|
|
2701
|
+
if (top === id) break;
|
|
2702
|
+
}
|
|
2703
|
+
const inCycle = new Set(component);
|
|
2704
|
+
const cyclic = component.length > 1 || cycleEdges(steps[id], id, issues).includes(id);
|
|
2705
|
+
if (!cyclic) return;
|
|
2706
|
+
if (component.some((member) => stepLeavesCycle(steps, member, inCycle, issues))) return;
|
|
2707
|
+
found = true;
|
|
2708
|
+
};
|
|
2709
|
+
if (Object.prototype.hasOwnProperty.call(steps, start)) strong(start);
|
|
2710
|
+
return found;
|
|
2711
|
+
}
|
|
2563
2712
|
|
|
2564
2713
|
// src/validators/nextLogic.ts
|
|
2565
2714
|
var NEXT_NULL = "null";
|