| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178 |
- /**
- * WorkflowGenerationPipeline — workflow-driven code generation
- *
- * Delegates to WorkflowExecutor (backed by VL-Workflow-Engine) for execution.
- * The workflow JSON (e.g., meta-direct-codegen.json) defines the DAG of LLM
- * calls, loops, branches, and file writes.
- *
- * Fallback: If workflow execution fails, transparently falls back
- * to the original JS GenerationPipeline.
- */
- import path from 'path';
- import fs from 'fs';
- import { resolveSeedWorkflowDir } from '../utils/bundle-paths.js';
- // Workflow file search order for generation (large/complex projects)
- const GENERATION_WORKFLOW_NAMES = [
- 'fullstack-codegen-v2.json',
- 'fullstack-codegen.json',
- 'fullstack-parallel.json',
- '9-file-codegen.json',
- '3-file-codegen.json',
- ];
- // Simplified workflow for small projects (Meta-direct path)
- const META_DIRECT_WORKFLOW = 'meta-direct-codegen.json';
- // Complexity threshold: below this, use meta-direct path
- const SMALL_PROJECT_THRESHOLD = {
- maxPages: 5,
- maxServices: 3,
- maxDescriptionLength: 2000,
- };
- export class WorkflowGenerationPipeline {
- /**
- * @param {Object} config - VL-Code configuration
- */
- constructor(config) {
- this.config = config;
- this._fallbackPipeline = null;
- }
- /**
- * Run the generation pipeline.
- * @param {Object} params - { userRequest, targetLang, mode }
- * @param {Object} callbacks - { onStepStart, onStepDone, onFileWritten, onToken, onDone, onError, onMetadataReady }
- */
- async run(params, callbacks = {}) {
- const useMetaDirect = params.mode === 'meta-direct' || (
- params.mode !== 'auxiliary' && this._isSmallProject(params)
- );
- const workflowFile = useMetaDirect
- ? this._findWorkflowByName(META_DIRECT_WORKFLOW)
- : this._findGenerationWorkflow();
- if (workflowFile) {
- callbacks.onStepStart?.({
- id: 'mode-select',
- title: useMetaDirect ? 'Using Meta-Direct path (small project)' : 'Using auxiliary-file path (complex project)',
- });
- try {
- return await this._runViaEngine(workflowFile, params, callbacks);
- } catch (err) {
- callbacks.onError?.(`Engine execution failed (${err.message}), falling back to JS pipeline`);
- }
- }
- // Fallback to original JS pipeline
- return this._runFallback(params, callbacks);
- }
- /**
- * Execute the generation workflow via WorkflowExecutor (VL-Workflow-Engine).
- */
- async _runViaEngine(workflowFile, params, callbacks) {
- const cb = callbacks;
- const wfJson = JSON.parse(fs.readFileSync(workflowFile, 'utf-8'));
- cb.onStepStart?.({ id: 'engine', title: 'Starting workflow engine execution...' });
- const { WorkflowExecutor } = await import('./workflow-executor.js');
- const executor = new WorkflowExecutor(this.config);
- await executor.execute(wfJson, {
- userRequest: params.userRequest || '',
- targetLang: params.targetLang || 'en',
- }, {
- onNodeStart: (info) => {
- cb.onStepStart?.({ id: info.nodeId, title: info.title || info.nodeId });
- },
- onNodeDone: (info) => {
- cb.onStepDone?.({ id: info.nodeId, title: info.title || info.nodeId });
- },
- onNodeError: (info) => {
- cb.onError?.(`Step ${info.nodeId} failed: ${info.error || 'unknown error'}`);
- },
- onToolStart: (info) => cb.onToolStart?.(info),
- onToolDone: (info) => cb.onToolDone?.(info),
- onToolError: (info) => cb.onToolError?.(info),
- onToolMessage: (info) => cb.onToolMessage?.(info),
- onToken: (token) => cb.onToken?.(token),
- onFileWritten: (fp) => cb.onFileWritten?.(fp),
- onDone: (summary) => {
- cb.onDone?.({
- filesWritten: summary.filesWritten,
- stageCount: summary.stageCount,
- });
- },
- onError: (msg) => cb.onError?.(msg),
- onText: (text) => {
- cb.onText?.(text);
- cb.onToken?.(text);
- },
- });
- }
- /**
- * Fallback to the original JS GenerationPipeline.
- */
- async _runFallback(params, callbacks) {
- if (!this._fallbackPipeline) {
- const { GenerationPipeline } = await import('./generation-pipeline.js');
- this._fallbackPipeline = new GenerationPipeline(this.config);
- }
- return this._fallbackPipeline.run(params, callbacks);
- }
- /**
- * Find a generation workflow JSON file.
- */
- _findGenerationWorkflow() {
- const searchDirs = [
- path.join(this.config.workDir, '.vl-code', 'workflows'),
- resolveSeedWorkflowDir(),
- ];
- for (const dir of searchDirs) {
- for (const name of GENERATION_WORKFLOW_NAMES) {
- const candidate = path.join(dir, name);
- if (fs.existsSync(candidate)) return candidate;
- }
- }
- return null;
- }
- /**
- * Find a specific workflow by name.
- */
- _findWorkflowByName(name) {
- const searchDirs = [
- path.join(this.config.workDir, '.vl-code', 'workflows'),
- resolveSeedWorkflowDir(),
- ];
- for (const dir of searchDirs) {
- const candidate = path.join(dir, name);
- if (fs.existsSync(candidate)) return candidate;
- }
- return null;
- }
- /**
- * Heuristic to determine if a project is "small" (use Meta-direct path).
- */
- _isSmallProject(params) {
- const req = params.userRequest || '';
- if (req.length > SMALL_PROJECT_THRESHOLD.maxDescriptionLength) return false;
- const pageKeywords = req.match(/page|screen|view|form|list|dashboard/gi) || [];
- const serviceKeywords = req.match(/service|api|backend|database/gi) || [];
- if (pageKeywords.length > SMALL_PROJECT_THRESHOLD.maxPages) return false;
- if (serviceKeywords.length > SMALL_PROJECT_THRESHOLD.maxServices) return false;
- return true;
- }
- }
|