File size: 3,889 Bytes
dd480ef | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | /**
* Workflow Compiler Agent β UPGRADED
* Converts WorkflowGraph (IR) β valid n8n JSON
* Schema-aware, version-aware, credential-aware
* Provider-agnostic LLM Gateway β NO direct OpenAI dependency
* STRICT: Only uses nodes from registry. Zero hallucinated params.
*/
import { type LLMGateway } from '@wfo/integrations/llm-providers/index';
import type { WorkflowGraph, WorkflowIntent, N8nWorkflow } from '../types/workflow';
import { COMPILER_PROMPT } from '../prompts/compiler';
import { N8N_NODE_REGISTRY, isValidNodeType } from '../knowledge/nodeRegistry';
export class WorkflowCompiler {
private llm: LLMGateway;
constructor(llm: LLMGateway) {
this.llm = llm;
}
async compile(graph: WorkflowGraph, intent: WorkflowIntent): Promise<N8nWorkflow> {
// Build node schema context from registry (ONLY nodes used in graph)
const nodeTypes = [...new Set(graph.nodes.map((n) => n.n8nNodeType))];
const validNodeTypes = nodeTypes.filter(isValidNodeType);
const nodeSchemas = validNodeTypes
.map((type) => N8N_NODE_REGISTRY[type])
.filter(Boolean)
.map((s) => JSON.stringify(s, null, 2))
.join('\n\n');
const workflow = await this.llm.completeJSON<N8nWorkflow>([
{ role: 'system', content: COMPILER_PROMPT },
{
role: 'user',
content: `Compile this WorkflowGraph to a valid n8n JSON workflow.
GRAPH:
${JSON.stringify(graph, null, 2)}
INTENT:
${JSON.stringify(intent, null, 2)}
REGISTERED NODE SCHEMAS (use ONLY these β parameters MUST match schema):
${nodeSchemas || '(No pre-built schemas β use standard n8n defaults)'}
CRITICAL COMPILATION RULES:
1. active MUST be false β NEVER true
2. All node IDs must be unique UUID strings (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
3. connections MUST wire ALL graph edges correctly
4. NEVER invent parameters not in the node schema above
5. Every node MUST have real expressions for dynamic fields β NO static placeholder text
6. Add retryOnFail: true, maxTries: 3, waitBetweenTries: 1000 on all critical/external nodes
7. Add meaningful node notes explaining what each node does
8. USE these expression patterns:
- Input from trigger: {{$json?.body?.fieldName ?? $json?.fieldName ?? ""}}
- Previous node: {{$node["NodeName"].json?.field ?? ""}}
- Array item: {{$json?.items?.[0]?.value ?? ""}}
- Conditional: {{$json?.status === "active" ? "yes" : "no"}}
9. SET nodes MUST have explicit field mappings β NOT empty values array
10. IF nodes MUST have real boolean conditions referencing actual fields
11. CODE nodes MUST contain real JavaScript logic β NOT placeholder comments
12. AI Agent nodes MUST include system message AND dynamic user message from previous node data
13. Return ONLY complete, importable n8n JSON workflow`,
},
], {
temperature: 0.0,
retries: 3,
});
return this.ensureSafeDefaults(workflow);
}
/**
* Enforce safety defaults regardless of LLM output
* CRITICAL: active = false, proper settings, error handling
*/
private ensureSafeDefaults(workflow: N8nWorkflow): N8nWorkflow {
return {
...workflow,
active: false, // NEVER activate on compile β ABSOLUTE safety rule
settings: {
callerPolicy: 'workflowsFromSameOwner',
errorWorkflow: workflow.settings?.errorWorkflow,
timezone: workflow.settings?.timezone ?? 'UTC',
...workflow.settings,
// Force these even if LLM overrides:
executionOrder: 'v1',
saveManualExecutions: true,
},
nodes: workflow.nodes.map((node) => ({
...node,
onError: node.onError ?? 'continueErrorOutput',
// Ensure critical external nodes have retry
retryOnFail: node.retryOnFail ?? false,
maxTries: node.maxTries ?? 3,
waitBetweenTries: node.waitBetweenTries ?? 1000,
})),
};
}
}
|