Files
patchy631--ai-engineering-hub/open-agent-builder/lib/workflow/validation.ts
T
2026-07-13 12:37:47 +08:00

132 lines
3.4 KiB
TypeScript

import { Workflow, WorkflowNode } from './types';
export interface ValidationError {
nodeId: string;
field: string;
message: string;
}
export function validateWorkflow(workflow: Workflow): ValidationError[] {
const errors: ValidationError[] = [];
// Check if workflow has at least a start and end node
const hasStart = workflow.nodes.some(n => n.type === 'start');
const hasEnd = workflow.nodes.some(n => n.type === 'end');
if (!hasStart) {
errors.push({
nodeId: 'workflow',
field: 'nodes',
message: 'Workflow must have a Start node',
});
}
// Validate each node
workflow.nodes.forEach((node) => {
const nodeErrors = validateNode(node);
errors.push(...nodeErrors);
});
// Check for disconnected nodes (except end nodes)
workflow.nodes.forEach((node) => {
if (node.type !== 'start' && node.type !== 'end') {
const hasIncoming = workflow.edges.some(e => e.target === node.id);
if (!hasIncoming) {
errors.push({
nodeId: node.id,
field: 'connections',
message: 'Node is not connected to workflow',
});
}
}
});
return errors;
}
export function validateNode(node: WorkflowNode): ValidationError[] {
const errors: ValidationError[] = [];
const nodeType = (node.data as any).nodeType || node.type;
switch (nodeType) {
case 'agent':
if (!node.data.instructions || node.data.instructions.trim() === '') {
errors.push({
nodeId: node.id,
field: 'instructions',
message: 'Agent must have instructions',
});
}
if (!node.data.model) {
errors.push({
nodeId: node.id,
field: 'model',
message: 'Agent must have a model selected',
});
}
break;
case 'mcp':
if (!node.data.mcpServers || node.data.mcpServers.length === 0) {
errors.push({
nodeId: node.id,
field: 'mcpServers',
message: 'MCP node must have at least one server configured',
});
}
break;
case 'if-else':
case 'if / else':
if (!node.data.condition || node.data.condition.trim() === '') {
errors.push({
nodeId: node.id,
field: 'condition',
message: 'If/Else must have a condition',
});
}
break;
case 'while':
if (!node.data.condition || node.data.condition.trim() === '') {
errors.push({
nodeId: node.id,
field: 'condition',
message: 'While loop must have a condition',
});
}
break;
case 'transform':
if (!node.data.transformScript || node.data.transformScript.trim() === '') {
errors.push({
nodeId: node.id,
field: 'transformScript',
message: 'Transform must have a script',
});
}
break;
case 'set-state':
case 'set state':
if (!node.data.stateKey || node.data.stateKey.trim() === '') {
errors.push({
nodeId: node.id,
field: 'stateKey',
message: 'Set State must have a variable name',
});
}
break;
}
return errors;
}
export function getNodeValidationStatus(node: WorkflowNode): 'valid' | 'warning' | 'error' {
const errors = validateNode(node);
if (errors.length === 0) return 'valid';
if (errors.some(e => e.field !== 'connections')) return 'error';
return 'warning';
}