@datatechsolutions/ui 2.11.53 → 2.11.55
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/astrlabe/index.d.mts +1 -1
- package/dist/astrlabe/index.d.ts +1 -1
- package/dist/astrlabe/index.js +11 -3
- package/dist/astrlabe/index.mjs +1 -1
- package/dist/astrlabe/utils.d.mts +27 -23
- package/dist/astrlabe/utils.d.ts +27 -23
- package/dist/astrlabe/utils.js +11 -3
- package/dist/astrlabe/utils.mjs +1 -1
- package/dist/chunk-53SRKVKQ.mjs +542 -0
- package/dist/chunk-53SRKVKQ.mjs.map +1 -0
- package/dist/chunk-5UU3RQRB.js +547 -0
- package/dist/chunk-5UU3RQRB.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-3GE3MBUZ.js +0 -279
- package/dist/chunk-3GE3MBUZ.js.map +0 -1
- package/dist/chunk-BLNXRUC4.mjs +0 -276
- package/dist/chunk-BLNXRUC4.mjs.map +0 -1
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// src/astrlabe/utils/workflow-validator.ts
|
|
5
|
+
var EN_MESSAGES = {
|
|
6
|
+
"graph.noAgents": "At least one agent node is required",
|
|
7
|
+
"graph.missingSource": 'Edge "{edgeId}" references non-existent source node "{source}"',
|
|
8
|
+
"graph.missingTarget": 'Edge "{edgeId}" references non-existent target node "{target}"',
|
|
9
|
+
"graph.orphanNode": 'Node "{label}" ({nodeType}) is not connected to any other node',
|
|
10
|
+
"graph.toolToNonAgent": 'Tool "{label}" can only connect to agent nodes, not {targetType} nodes',
|
|
11
|
+
"graph.ruleNoAgent": 'Rule "{label}" must have an incoming connection from an agent',
|
|
12
|
+
"graph.cycleDetected": "Agent nodes form a cycle; workflows must be a DAG",
|
|
13
|
+
"graph.multipleStartNodes": "Only one Start node is allowed",
|
|
14
|
+
"graph.multipleEndNodes": "Only one End node is allowed",
|
|
15
|
+
"graph.startHasIncoming": "Start node cannot have incoming connections",
|
|
16
|
+
"graph.endHasOutgoing": "End node cannot have outgoing connections",
|
|
17
|
+
"graph.iterationNoOutgoing": "Iteration node must have at least one outgoing connection",
|
|
18
|
+
"node.required": "is required",
|
|
19
|
+
"node.requiredWhenLanguageSet": "is required when language is set",
|
|
20
|
+
"node.codeShapeRequired": "either {language, code} (UI) or {operation, ...} (engine) must be set",
|
|
21
|
+
"node.mustBeArray": "must be an array",
|
|
22
|
+
"node.mustBeReferenceArray": "must be an array of reference strings",
|
|
23
|
+
"node.mustBeNonEmptyReferenceArray": "must be a non-empty array of reference strings",
|
|
24
|
+
"node.assignmentNeedsSourceOrValue": "must set either `source` or `value`",
|
|
25
|
+
"node.invalidAggregationMode": "must be one of: array | object | concatenate",
|
|
26
|
+
"node.invalidListOperation": "must be one of: filter | map | sort | limit | deduplicate | flatten | count | sum | avg",
|
|
27
|
+
"node.invalidLogicalOperator": "must be `and` or `or`",
|
|
28
|
+
"node.mustBeNumber": "must be a number",
|
|
29
|
+
"node.invalidHttpMethod": "must be one of: GET | POST | PUT | PATCH | DELETE",
|
|
30
|
+
"node.providerIdentityRequired": "must set `connectionId`, `modelId`, or inline `provider` + `model`",
|
|
31
|
+
"node.inputOrInstructionsRequired": "is required (or `instructions`)",
|
|
32
|
+
"node.invalidExtractionMode": "must be one of: text | table | structured",
|
|
33
|
+
"node.unknownType": "unknown node type `{nodeType}`"
|
|
34
|
+
};
|
|
35
|
+
var PT_BR_MESSAGES = {
|
|
36
|
+
"graph.noAgents": "Pelo menos um n\xF3 de agente \xE9 necess\xE1rio",
|
|
37
|
+
"graph.missingSource": 'A aresta "{edgeId}" referencia um n\xF3 de origem inexistente "{source}"',
|
|
38
|
+
"graph.missingTarget": 'A aresta "{edgeId}" referencia um n\xF3 de destino inexistente "{target}"',
|
|
39
|
+
"graph.orphanNode": 'O n\xF3 "{label}" ({nodeType}) n\xE3o est\xE1 conectado a nenhum outro n\xF3',
|
|
40
|
+
"graph.toolToNonAgent": 'A ferramenta "{label}" s\xF3 pode se conectar a n\xF3s de agente, n\xE3o a n\xF3s {targetType}',
|
|
41
|
+
"graph.ruleNoAgent": 'A regra "{label}" deve ter uma conex\xE3o de entrada vinda de um agente',
|
|
42
|
+
"graph.cycleDetected": "Os n\xF3s de agente formam um ciclo; workflows devem ser um DAG",
|
|
43
|
+
"graph.multipleStartNodes": "Apenas um n\xF3 Start \xE9 permitido",
|
|
44
|
+
"graph.multipleEndNodes": "Apenas um n\xF3 End \xE9 permitido",
|
|
45
|
+
"graph.startHasIncoming": "O n\xF3 Start n\xE3o pode ter conex\xF5es de entrada",
|
|
46
|
+
"graph.endHasOutgoing": "O n\xF3 End n\xE3o pode ter conex\xF5es de sa\xEDda",
|
|
47
|
+
"graph.iterationNoOutgoing": "O n\xF3 Iteration deve ter pelo menos uma conex\xE3o de sa\xEDda",
|
|
48
|
+
"node.required": "\xE9 obrigat\xF3rio",
|
|
49
|
+
"node.requiredWhenLanguageSet": "\xE9 obrigat\xF3rio quando language est\xE1 definido",
|
|
50
|
+
"node.codeShapeRequired": "\xE9 necess\xE1rio definir {language, code} (UI) ou {operation, ...} (engine)",
|
|
51
|
+
"node.mustBeArray": "deve ser um array",
|
|
52
|
+
"node.mustBeReferenceArray": "deve ser um array de strings de refer\xEAncia",
|
|
53
|
+
"node.mustBeNonEmptyReferenceArray": "deve ser um array n\xE3o vazio de strings de refer\xEAncia",
|
|
54
|
+
"node.assignmentNeedsSourceOrValue": "deve definir `source` ou `value`",
|
|
55
|
+
"node.invalidAggregationMode": "deve ser um de: array | object | concatenate",
|
|
56
|
+
"node.invalidListOperation": "deve ser um de: filter | map | sort | limit | deduplicate | flatten | count | sum | avg",
|
|
57
|
+
"node.invalidLogicalOperator": "deve ser `and` ou `or`",
|
|
58
|
+
"node.mustBeNumber": "deve ser um n\xFAmero",
|
|
59
|
+
"node.invalidHttpMethod": "deve ser um de: GET | POST | PUT | PATCH | DELETE",
|
|
60
|
+
"node.providerIdentityRequired": "deve definir `connectionId`, `modelId` ou `provider` + `model` inline",
|
|
61
|
+
"node.inputOrInstructionsRequired": "\xE9 obrigat\xF3rio (ou `instructions`)",
|
|
62
|
+
"node.invalidExtractionMode": "deve ser um de: text | table | structured",
|
|
63
|
+
"node.unknownType": "tipo de n\xF3 desconhecido `{nodeType}`"
|
|
64
|
+
};
|
|
65
|
+
var MESSAGE_CATALOG = {
|
|
66
|
+
en: EN_MESSAGES,
|
|
67
|
+
"pt-BR": PT_BR_MESSAGES,
|
|
68
|
+
es: EN_MESSAGES,
|
|
69
|
+
fr: EN_MESSAGES,
|
|
70
|
+
de: EN_MESSAGES,
|
|
71
|
+
it: EN_MESSAGES
|
|
72
|
+
};
|
|
73
|
+
function normalizeLocale(locale) {
|
|
74
|
+
if (!locale) {
|
|
75
|
+
return "en";
|
|
76
|
+
}
|
|
77
|
+
if (locale in MESSAGE_CATALOG) {
|
|
78
|
+
return locale;
|
|
79
|
+
}
|
|
80
|
+
const lowercase = locale.toLowerCase();
|
|
81
|
+
if (lowercase.startsWith("pt")) {
|
|
82
|
+
return "pt-BR";
|
|
83
|
+
}
|
|
84
|
+
if (lowercase.startsWith("es")) {
|
|
85
|
+
return "es";
|
|
86
|
+
}
|
|
87
|
+
if (lowercase.startsWith("fr")) {
|
|
88
|
+
return "fr";
|
|
89
|
+
}
|
|
90
|
+
if (lowercase.startsWith("de")) {
|
|
91
|
+
return "de";
|
|
92
|
+
}
|
|
93
|
+
if (lowercase.startsWith("it")) {
|
|
94
|
+
return "it";
|
|
95
|
+
}
|
|
96
|
+
return "en";
|
|
97
|
+
}
|
|
98
|
+
function template(messageKey, params, locale) {
|
|
99
|
+
const catalog = MESSAGE_CATALOG[normalizeLocale(locale)] ?? EN_MESSAGES;
|
|
100
|
+
const message = catalog[messageKey] ?? EN_MESSAGES[messageKey];
|
|
101
|
+
return message.replace(/\{([^}]+)\}/g, (_, key) => String(params?.[key] ?? ""));
|
|
102
|
+
}
|
|
103
|
+
function isRecord(value) {
|
|
104
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
105
|
+
}
|
|
106
|
+
function getConfig(node) {
|
|
107
|
+
return isRecord(node.data.config) ? node.data.config : {};
|
|
108
|
+
}
|
|
109
|
+
function getString(config, field) {
|
|
110
|
+
const value = config[field];
|
|
111
|
+
return typeof value === "string" ? value : void 0;
|
|
112
|
+
}
|
|
113
|
+
function hasNonEmptyString(config, field) {
|
|
114
|
+
const value = getString(config, field);
|
|
115
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
116
|
+
}
|
|
117
|
+
function getArray(config, field) {
|
|
118
|
+
const value = config[field];
|
|
119
|
+
return Array.isArray(value) ? value : void 0;
|
|
120
|
+
}
|
|
121
|
+
function getNumber(config, field) {
|
|
122
|
+
const value = config[field];
|
|
123
|
+
return typeof value === "number" ? value : void 0;
|
|
124
|
+
}
|
|
125
|
+
function pushNodeIssue(issues, node, locale, code, field, params) {
|
|
126
|
+
issues.push({
|
|
127
|
+
nodeId: node.id,
|
|
128
|
+
nodeType: node.type,
|
|
129
|
+
field,
|
|
130
|
+
code,
|
|
131
|
+
params,
|
|
132
|
+
message: template(code, params, locale)
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
function pushIssue(issues, locale, code, params) {
|
|
136
|
+
issues.push({
|
|
137
|
+
code,
|
|
138
|
+
params,
|
|
139
|
+
message: template(code, params, locale)
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
function ensureProviderIdentifiable(node, config, issues, locale) {
|
|
143
|
+
const hasConnection = hasNonEmptyString(config, "connectionId");
|
|
144
|
+
const hasModelId = hasNonEmptyString(config, "modelId");
|
|
145
|
+
const hasInline = hasNonEmptyString(config, "provider") && hasNonEmptyString(config, "model");
|
|
146
|
+
if (!hasConnection && !hasModelId && !hasInline) {
|
|
147
|
+
pushNodeIssue(issues, node, locale, "node.providerIdentityRequired", "provider");
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function formatNodeIssue(issue, nodeMap) {
|
|
151
|
+
const label = nodeMap.get(issue.nodeId)?.data.label ?? issue.nodeId;
|
|
152
|
+
const fieldRef = issue.field ? ` (${issue.field})` : "";
|
|
153
|
+
return `Node "${label}" [${issue.nodeType}]${fieldRef}: ${issue.message}`;
|
|
154
|
+
}
|
|
155
|
+
function validateNodeConfig(node, options = {}) {
|
|
156
|
+
const locale = options.locale;
|
|
157
|
+
const config = getConfig(node);
|
|
158
|
+
const issues = [];
|
|
159
|
+
const nodeType = node.type;
|
|
160
|
+
switch (nodeType) {
|
|
161
|
+
case "start":
|
|
162
|
+
case "note":
|
|
163
|
+
case "group":
|
|
164
|
+
case "dashboard_output":
|
|
165
|
+
break;
|
|
166
|
+
case "end": {
|
|
167
|
+
if ("outputVariables" in config && !Array.isArray(config.outputVariables)) {
|
|
168
|
+
pushNodeIssue(issues, node, locale, "node.mustBeReferenceArray", "outputVariables");
|
|
169
|
+
} else if (Array.isArray(config.outputs)) {
|
|
170
|
+
config.outputs.forEach((entry, index) => {
|
|
171
|
+
if (!isRecord(entry) || typeof entry.key !== "string" || entry.key.trim().length === 0) {
|
|
172
|
+
pushNodeIssue(issues, node, locale, "node.required", `outputs[${index}].key`);
|
|
173
|
+
}
|
|
174
|
+
if (!isRecord(entry) || typeof entry.from !== "string" || entry.from.trim().length === 0) {
|
|
175
|
+
pushNodeIssue(issues, node, locale, "node.required", `outputs[${index}].from`);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
case "code": {
|
|
182
|
+
const hasLanguage = "language" in config;
|
|
183
|
+
const hasOperation = "operation" in config;
|
|
184
|
+
if (hasLanguage) {
|
|
185
|
+
if (!hasNonEmptyString(config, "code")) {
|
|
186
|
+
pushNodeIssue(issues, node, locale, "node.requiredWhenLanguageSet", "code");
|
|
187
|
+
}
|
|
188
|
+
} else if (!hasOperation) {
|
|
189
|
+
pushNodeIssue(issues, node, locale, "node.codeShapeRequired", "operation");
|
|
190
|
+
}
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
case "iteration_start": {
|
|
194
|
+
for (const field of ["iteratorVariable", "itemVariable", "indexVariable"]) {
|
|
195
|
+
if (!hasNonEmptyString(config, field)) {
|
|
196
|
+
pushNodeIssue(issues, node, locale, "node.required", field);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
case "entity":
|
|
202
|
+
if (!hasNonEmptyString(config, "entityMasterId")) {
|
|
203
|
+
pushNodeIssue(issues, node, locale, "node.required", "entityMasterId");
|
|
204
|
+
}
|
|
205
|
+
break;
|
|
206
|
+
case "datasource":
|
|
207
|
+
if (!hasNonEmptyString(config, "datasourceId")) {
|
|
208
|
+
pushNodeIssue(issues, node, locale, "node.required", "datasourceId");
|
|
209
|
+
}
|
|
210
|
+
break;
|
|
211
|
+
case "model_provider":
|
|
212
|
+
if (!hasNonEmptyString(config, "providerType")) {
|
|
213
|
+
pushNodeIssue(issues, node, locale, "node.required", "providerType");
|
|
214
|
+
}
|
|
215
|
+
break;
|
|
216
|
+
case "variable_assigner": {
|
|
217
|
+
const assignments = getArray(config, "assignments");
|
|
218
|
+
if (!assignments) {
|
|
219
|
+
pushNodeIssue(issues, node, locale, "node.mustBeArray", "assignments");
|
|
220
|
+
return issues;
|
|
221
|
+
}
|
|
222
|
+
assignments.forEach((entry, index) => {
|
|
223
|
+
const record = isRecord(entry) ? entry : {};
|
|
224
|
+
const target = typeof record.target === "string" && record.target.trim().length > 0;
|
|
225
|
+
const legacyTarget = typeof record.key === "string" && record.key.trim().length > 0;
|
|
226
|
+
if (!target && !legacyTarget) {
|
|
227
|
+
pushNodeIssue(issues, node, locale, "node.required", `assignments[${index}].target`);
|
|
228
|
+
}
|
|
229
|
+
const source = typeof record.source === "string" && record.source.trim().length > 0;
|
|
230
|
+
const legacySource = typeof record.from === "string" && record.from.trim().length > 0;
|
|
231
|
+
const hasValue = "value" in record;
|
|
232
|
+
if (!source && !legacySource && !hasValue) {
|
|
233
|
+
pushNodeIssue(issues, node, locale, "node.assignmentNeedsSourceOrValue", `assignments[${index}]`);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
case "variable_aggregator": {
|
|
239
|
+
if ("inputVariables" in config) {
|
|
240
|
+
const inputVariables = getArray(config, "inputVariables");
|
|
241
|
+
if (!inputVariables || inputVariables.length === 0) {
|
|
242
|
+
pushNodeIssue(issues, node, locale, "node.mustBeNonEmptyReferenceArray", "inputVariables");
|
|
243
|
+
}
|
|
244
|
+
if (!hasNonEmptyString(config, "outputVariable")) {
|
|
245
|
+
pushNodeIssue(issues, node, locale, "node.required", "outputVariable");
|
|
246
|
+
}
|
|
247
|
+
const aggregationMode = getString(config, "aggregationMode");
|
|
248
|
+
if (aggregationMode && !["array", "object", "concatenate"].includes(aggregationMode)) {
|
|
249
|
+
pushNodeIssue(issues, node, locale, "node.invalidAggregationMode", "aggregationMode");
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
case "list_operator": {
|
|
255
|
+
const operation = getString(config, "operation") ?? "";
|
|
256
|
+
if (!operation) {
|
|
257
|
+
pushNodeIssue(issues, node, locale, "node.required", "operation");
|
|
258
|
+
} else if (!["filter", "map", "sort", "limit", "deduplicate", "flatten", "count", "sum", "avg"].includes(operation)) {
|
|
259
|
+
pushNodeIssue(issues, node, locale, "node.invalidListOperation", "operation");
|
|
260
|
+
}
|
|
261
|
+
const inputVariable = getString(config, "inputVariable");
|
|
262
|
+
const legacySource = getString(config, "source");
|
|
263
|
+
if (!(inputVariable && inputVariable.trim()) && !(legacySource && legacySource.trim())) {
|
|
264
|
+
pushNodeIssue(issues, node, locale, "node.required", "inputVariable");
|
|
265
|
+
}
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
case "template_transform":
|
|
269
|
+
if (!hasNonEmptyString(config, "template")) {
|
|
270
|
+
pushNodeIssue(issues, node, locale, "node.required", "template");
|
|
271
|
+
}
|
|
272
|
+
break;
|
|
273
|
+
case "answer": {
|
|
274
|
+
const outputTemplate = getString(config, "outputTemplate");
|
|
275
|
+
const legacyAnswer = getString(config, "answer");
|
|
276
|
+
if (!(outputTemplate && outputTemplate.trim()) && !(legacyAnswer && legacyAnswer.trim())) {
|
|
277
|
+
pushNodeIssue(issues, node, locale, "node.required", "outputTemplate");
|
|
278
|
+
}
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
case "if_else": {
|
|
282
|
+
const conditions = getArray(config, "conditions");
|
|
283
|
+
if (!conditions || conditions.length === 0) {
|
|
284
|
+
pushNodeIssue(issues, node, locale, "node.mustBeArray", "conditions");
|
|
285
|
+
} else {
|
|
286
|
+
conditions.forEach((condition, index) => {
|
|
287
|
+
const record = isRecord(condition) ? condition : {};
|
|
288
|
+
if (typeof record.variable !== "string" || record.variable.trim().length === 0) {
|
|
289
|
+
pushNodeIssue(issues, node, locale, "node.required", `conditions[${index}].variable`);
|
|
290
|
+
}
|
|
291
|
+
if (typeof record.operator !== "string" || record.operator.trim().length === 0) {
|
|
292
|
+
pushNodeIssue(issues, node, locale, "node.required", `conditions[${index}].operator`);
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
const logicalOperator = getString(config, "logicalOperator");
|
|
297
|
+
if (logicalOperator && !["and", "or"].includes(logicalOperator)) {
|
|
298
|
+
pushNodeIssue(issues, node, locale, "node.invalidLogicalOperator", "logicalOperator");
|
|
299
|
+
}
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
case "iteration": {
|
|
303
|
+
const iteratorVariable = getString(config, "iteratorVariable");
|
|
304
|
+
const legacySource = getString(config, "source");
|
|
305
|
+
if (!(iteratorVariable && iteratorVariable.trim()) && !(legacySource && legacySource.trim())) {
|
|
306
|
+
pushNodeIssue(issues, node, locale, "node.required", "iteratorVariable");
|
|
307
|
+
}
|
|
308
|
+
if ("maxIterations" in config && getNumber(config, "maxIterations") === void 0) {
|
|
309
|
+
pushNodeIssue(issues, node, locale, "node.mustBeNumber", "maxIterations");
|
|
310
|
+
}
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
case "http_request": {
|
|
314
|
+
const method = (getString(config, "method") ?? "").toUpperCase();
|
|
315
|
+
if (!["GET", "POST", "PUT", "PATCH", "DELETE"].includes(method)) {
|
|
316
|
+
pushNodeIssue(issues, node, locale, "node.invalidHttpMethod", "method");
|
|
317
|
+
}
|
|
318
|
+
if (!hasNonEmptyString(config, "url")) {
|
|
319
|
+
pushNodeIssue(issues, node, locale, "node.required", "url");
|
|
320
|
+
}
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
case "rule":
|
|
324
|
+
break;
|
|
325
|
+
case "agent":
|
|
326
|
+
if (!hasNonEmptyString(config, "userPrompt")) {
|
|
327
|
+
pushNodeIssue(issues, node, locale, "node.required", "userPrompt");
|
|
328
|
+
}
|
|
329
|
+
ensureProviderIdentifiable(node, config, issues, locale);
|
|
330
|
+
break;
|
|
331
|
+
case "question_classifier": {
|
|
332
|
+
ensureProviderIdentifiable(node, config, issues, locale);
|
|
333
|
+
const input = getString(config, "input");
|
|
334
|
+
const instructions = getString(config, "instructions");
|
|
335
|
+
if (!(input && input.trim()) && !(instructions && instructions.trim())) {
|
|
336
|
+
pushNodeIssue(issues, node, locale, "node.inputOrInstructionsRequired", "input");
|
|
337
|
+
}
|
|
338
|
+
const categories = getArray(config, "categories") ?? getArray(config, "classes");
|
|
339
|
+
if (!categories || categories.length === 0) {
|
|
340
|
+
pushNodeIssue(issues, node, locale, "node.mustBeArray", "categories");
|
|
341
|
+
}
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
case "parameter_extractor": {
|
|
345
|
+
ensureProviderIdentifiable(node, config, issues, locale);
|
|
346
|
+
if (!hasNonEmptyString(config, "input")) {
|
|
347
|
+
pushNodeIssue(issues, node, locale, "node.required", "input");
|
|
348
|
+
}
|
|
349
|
+
const parameters = getArray(config, "parameters");
|
|
350
|
+
if (!parameters || parameters.length === 0) {
|
|
351
|
+
pushNodeIssue(issues, node, locale, "node.mustBeArray", "parameters");
|
|
352
|
+
}
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
case "tool":
|
|
356
|
+
if (!hasNonEmptyString(config, "toolId")) {
|
|
357
|
+
pushNodeIssue(issues, node, locale, "node.required", "toolId");
|
|
358
|
+
}
|
|
359
|
+
break;
|
|
360
|
+
case "agent_tool":
|
|
361
|
+
if (!hasNonEmptyString(config, "agentId")) {
|
|
362
|
+
pushNodeIssue(issues, node, locale, "node.required", "agentId");
|
|
363
|
+
}
|
|
364
|
+
if (!hasNonEmptyString(config, "toolId")) {
|
|
365
|
+
pushNodeIssue(issues, node, locale, "node.required", "toolId");
|
|
366
|
+
}
|
|
367
|
+
break;
|
|
368
|
+
case "knowledge_base":
|
|
369
|
+
if (!hasNonEmptyString(config, "sourceId")) {
|
|
370
|
+
pushNodeIssue(issues, node, locale, "node.required", "sourceId");
|
|
371
|
+
}
|
|
372
|
+
if ("topK" in config && getNumber(config, "topK") === void 0) {
|
|
373
|
+
pushNodeIssue(issues, node, locale, "node.mustBeNumber", "topK");
|
|
374
|
+
}
|
|
375
|
+
if ("similarityThreshold" in config && getNumber(config, "similarityThreshold") === void 0) {
|
|
376
|
+
pushNodeIssue(issues, node, locale, "node.mustBeNumber", "similarityThreshold");
|
|
377
|
+
}
|
|
378
|
+
break;
|
|
379
|
+
case "document_extractor": {
|
|
380
|
+
const extractionMode = getString(config, "extractionMode");
|
|
381
|
+
if (extractionMode && !["text", "table", "structured"].includes(extractionMode)) {
|
|
382
|
+
pushNodeIssue(issues, node, locale, "node.invalidExtractionMode", "extractionMode");
|
|
383
|
+
}
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
default:
|
|
387
|
+
pushNodeIssue(issues, node, locale, "node.unknownType", void 0, { nodeType });
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
return issues;
|
|
391
|
+
}
|
|
392
|
+
function validateGraphNodeConfigs(graph, options = {}) {
|
|
393
|
+
return graph.nodes.flatMap((node) => validateNodeConfig(node, options));
|
|
394
|
+
}
|
|
395
|
+
function validateWorkflowGraph(graph, options = {}) {
|
|
396
|
+
const locale = options.locale;
|
|
397
|
+
const issues = [];
|
|
398
|
+
const nodeMap = /* @__PURE__ */ new Map();
|
|
399
|
+
for (const node of graph.nodes) {
|
|
400
|
+
nodeMap.set(node.id, node);
|
|
401
|
+
}
|
|
402
|
+
const agentNodes = graph.nodes.filter((node) => node.type === "agent");
|
|
403
|
+
if (agentNodes.length === 0) {
|
|
404
|
+
pushIssue(issues, locale, "graph.noAgents");
|
|
405
|
+
}
|
|
406
|
+
for (const edge of graph.edges) {
|
|
407
|
+
if (!nodeMap.has(edge.source)) {
|
|
408
|
+
pushIssue(issues, locale, "graph.missingSource", { edgeId: edge.id, source: edge.source });
|
|
409
|
+
}
|
|
410
|
+
if (!nodeMap.has(edge.target)) {
|
|
411
|
+
pushIssue(issues, locale, "graph.missingTarget", { edgeId: edge.id, target: edge.target });
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
const connectedNodeIds = /* @__PURE__ */ new Set();
|
|
415
|
+
for (const edge of graph.edges) {
|
|
416
|
+
connectedNodeIds.add(edge.source);
|
|
417
|
+
connectedNodeIds.add(edge.target);
|
|
418
|
+
}
|
|
419
|
+
for (const node of graph.nodes) {
|
|
420
|
+
if (node.type === "note") continue;
|
|
421
|
+
if (!connectedNodeIds.has(node.id)) {
|
|
422
|
+
pushIssue(issues, locale, "graph.orphanNode", {
|
|
423
|
+
label: node.data.label ?? node.id,
|
|
424
|
+
nodeType: node.type
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
for (const edge of graph.edges) {
|
|
429
|
+
const sourceNode = nodeMap.get(edge.source);
|
|
430
|
+
const targetNode = nodeMap.get(edge.target);
|
|
431
|
+
if (!sourceNode || !targetNode) continue;
|
|
432
|
+
if (sourceNode.type === "tool" && targetNode.type !== "agent") {
|
|
433
|
+
pushIssue(issues, locale, "graph.toolToNonAgent", {
|
|
434
|
+
label: sourceNode.data.label ?? sourceNode.id,
|
|
435
|
+
targetType: targetNode.type
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
const ruleNodes = graph.nodes.filter((node) => node.type === "rule");
|
|
440
|
+
for (const ruleNode of ruleNodes) {
|
|
441
|
+
const incomingEdges = graph.edges.filter((edge) => edge.target === ruleNode.id);
|
|
442
|
+
const hasAgentSource = incomingEdges.some((edge) => nodeMap.get(edge.source)?.type === "agent");
|
|
443
|
+
if (incomingEdges.length === 0 || !hasAgentSource) {
|
|
444
|
+
pushIssue(issues, locale, "graph.ruleNoAgent", {
|
|
445
|
+
label: ruleNode.data.label ?? ruleNode.id
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
if (agentNodes.length > 1) {
|
|
450
|
+
const cycleIssue = detectCycle(agentNodes, graph.edges, nodeMap, locale);
|
|
451
|
+
if (cycleIssue) {
|
|
452
|
+
issues.push(cycleIssue);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
const startNodes = graph.nodes.filter((node) => node.type === "start");
|
|
456
|
+
if (startNodes.length > 1) {
|
|
457
|
+
pushIssue(issues, locale, "graph.multipleStartNodes");
|
|
458
|
+
}
|
|
459
|
+
const endNodes = graph.nodes.filter((node) => node.type === "end");
|
|
460
|
+
if (endNodes.length > 1) {
|
|
461
|
+
pushIssue(issues, locale, "graph.multipleEndNodes");
|
|
462
|
+
}
|
|
463
|
+
for (const startNode of startNodes) {
|
|
464
|
+
const incomingEdges = graph.edges.filter((edge) => edge.target === startNode.id);
|
|
465
|
+
if (incomingEdges.length > 0) {
|
|
466
|
+
pushIssue(issues, locale, "graph.startHasIncoming");
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
for (const endNode of endNodes) {
|
|
470
|
+
const outgoingEdges = graph.edges.filter((edge) => edge.source === endNode.id);
|
|
471
|
+
if (outgoingEdges.length > 0) {
|
|
472
|
+
pushIssue(issues, locale, "graph.endHasOutgoing");
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
const iterationNodes = graph.nodes.filter((node) => node.type === "iteration");
|
|
476
|
+
for (const iterationNode of iterationNodes) {
|
|
477
|
+
const outgoingEdges = graph.edges.filter((edge) => edge.source === iterationNode.id);
|
|
478
|
+
if (outgoingEdges.length === 0) {
|
|
479
|
+
pushIssue(issues, locale, "graph.iterationNoOutgoing");
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
const nodeIssues = validateGraphNodeConfigs(graph, options);
|
|
483
|
+
const errors = [
|
|
484
|
+
...issues.map((issue) => issue.message),
|
|
485
|
+
...nodeIssues.map((issue) => formatNodeIssue(issue, nodeMap))
|
|
486
|
+
];
|
|
487
|
+
return {
|
|
488
|
+
valid: errors.length === 0,
|
|
489
|
+
errors,
|
|
490
|
+
issues,
|
|
491
|
+
nodeIssues
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
function topologicalSortAgents(agentNodes, edges, nodeMap) {
|
|
495
|
+
const adjacency = /* @__PURE__ */ new Map();
|
|
496
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
497
|
+
for (const node of agentNodes) {
|
|
498
|
+
adjacency.set(node.id, []);
|
|
499
|
+
inDegree.set(node.id, 0);
|
|
500
|
+
}
|
|
501
|
+
for (const edge of edges) {
|
|
502
|
+
const sourceNode = nodeMap.get(edge.source);
|
|
503
|
+
const targetNode = nodeMap.get(edge.target);
|
|
504
|
+
if (!sourceNode || !targetNode) continue;
|
|
505
|
+
if (sourceNode.type === "agent" && targetNode.type === "agent") {
|
|
506
|
+
adjacency.get(edge.source)?.push(edge.target);
|
|
507
|
+
inDegree.set(edge.target, (inDegree.get(edge.target) ?? 0) + 1);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
const queue = agentNodes.filter((node) => (inDegree.get(node.id) ?? 0) === 0).map((node) => node.id);
|
|
511
|
+
const sortedIds = [];
|
|
512
|
+
while (queue.length > 0) {
|
|
513
|
+
const nodeId = queue.shift();
|
|
514
|
+
if (!nodeId) break;
|
|
515
|
+
sortedIds.push(nodeId);
|
|
516
|
+
const neighbors = adjacency.get(nodeId) ?? [];
|
|
517
|
+
for (const neighborId of neighbors) {
|
|
518
|
+
const newInDegree = (inDegree.get(neighborId) ?? 1) - 1;
|
|
519
|
+
inDegree.set(neighborId, newInDegree);
|
|
520
|
+
if (newInDegree === 0) {
|
|
521
|
+
queue.push(neighborId);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
if (sortedIds.length !== agentNodes.length) {
|
|
526
|
+
throw new Error("Cycle detected in agent graph");
|
|
527
|
+
}
|
|
528
|
+
return sortedIds.map((nodeId) => nodeMap.get(nodeId)).filter((node) => Boolean(node));
|
|
529
|
+
}
|
|
530
|
+
function detectCycle(agentNodes, edges, nodeMap, locale) {
|
|
531
|
+
try {
|
|
532
|
+
topologicalSortAgents(agentNodes, edges, nodeMap);
|
|
533
|
+
return null;
|
|
534
|
+
} catch {
|
|
535
|
+
return {
|
|
536
|
+
code: "graph.cycleDetected",
|
|
537
|
+
message: template("graph.cycleDetected", void 0, locale)
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
exports.topologicalSortAgents = topologicalSortAgents;
|
|
543
|
+
exports.validateGraphNodeConfigs = validateGraphNodeConfigs;
|
|
544
|
+
exports.validateNodeConfig = validateNodeConfig;
|
|
545
|
+
exports.validateWorkflowGraph = validateWorkflowGraph;
|
|
546
|
+
//# sourceMappingURL=chunk-5UU3RQRB.js.map
|
|
547
|
+
//# sourceMappingURL=chunk-5UU3RQRB.js.map
|