UNPKG

claude-flow

Version:

Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration

552 lines 21.8 kB
/** * Enhanced Model Router with Agent Booster AST Integration * * Implements ADR-026: 3-tier intelligent model routing: * - Tier 1: Agent Booster (WASM) - <1ms, $0 for simple transforms * - Tier 2: Haiku - ~500ms for low complexity * - Tier 3: Sonnet/Opus - 2-5s for high complexity * * @module enhanced-model-router */ import { existsSync, readFileSync, writeFileSync } from 'fs'; import { extname, isAbsolute, resolve as resolvePath } from 'path'; import { getModelRouter } from './model-router.js'; import { applyCodemod, isDeterministicCodemod } from './codemods/engine.js'; /** Map a file path to the codemod engine's language, falling back to a hint. */ function codemodLanguageFor(filePath, fallback) { const ext = extname(filePath).toLowerCase(); if (ext === '.tsx') return 'tsx'; if (ext === '.jsx') return 'jsx'; if (ext === '.ts' || ext === '.mts' || ext === '.cts') return 'typescript'; if (ext === '.js' || ext === '.mjs' || ext === '.cjs') return 'javascript'; return fallback === 'typescript' ? 'typescript' : 'javascript'; } // ============================================================================ // Intent Detection Patterns // ============================================================================ /** * Pattern definitions for Agent Booster intent detection */ const INTENT_PATTERNS = { 'var-to-const': { patterns: [ /convert\s+var\s+to\s+const/i, /change\s+var\s+to\s+const/i, /change\s+var\s+declarations?\s+to\s+const/i, /replace\s+var\s+with\s+const/i, /var\s*(?:→|->|to)\s*const/i, /use\s+const\s+instead\s+of\s+var/i, ], weight: 1.0, description: 'Convert var declarations to const/let', }, 'add-types': { patterns: [ /add\s+type\s+annotations?/i, /add\s+typescript\s+types?/i, /type\s+this\s+function/i, /add\s+types?\s+to/i, /annotate\s+with\s+types?/i, ], weight: 0.9, description: 'Add TypeScript type annotations', }, 'add-error-handling': { patterns: [ /add\s+error\s+handling/i, /wrap\s+in\s+try\s*[/-]?\s*catch/i, /add\s+try\s*[/-]?\s*catch/i, /handle\s+errors?/i, /add\s+exception\s+handling/i, ], weight: 0.7, // Lower weight - often needs more context description: 'Wrap code in try/catch blocks', }, 'async-await': { patterns: [ /convert\s+to\s+async\s*[/-]?\s*await/i, /convert\s+\w+\s+to\s+async/i, /use\s+async\s*[/-]?\s*await/i, /change\s+promises?\s+to\s+async/i, /refactor\s+to\s+async/i, /\.then\s*(?:→|->|to)\s*await/i, /callback\s+to\s+async/i, /callbacks?\s+to\s+async/i, ], weight: 0.8, description: 'Convert callbacks/promises to async/await', }, 'add-logging': { patterns: [ /add\s+logging/i, /add\s+console\.log/i, /add\s+debug\s+logs?/i, /log\s+this\s+function/i, /add\s+trace\s+logging/i, ], weight: 0.85, description: 'Add console.log or logging statements', }, 'remove-console': { patterns: [ /remove\s+(?:all\s+)?console\.log/i, /remove\s+(?:all\s+)?console\s+statements?/i, /delete\s+(?:all\s+)?console\s+statements?/i, /strip\s+console/i, /clean\s+up\s+console/i, /clean\s+up\s+debug\s+logs?/i, /remove\s+(?:all\s+)?debug\s+logs?/i, /delete\s+(?:all\s+)?console\.log/i, ], weight: 0.95, description: 'Remove console.* calls', }, }; /** * File path extraction patterns */ const FILE_PATH_PATTERNS = [ /(?:in|from|to|file|path)\s+[`"']?([a-zA-Z0-9_./\\-]+\.[a-zA-Z]+)[`"']?/i, /[`"']([a-zA-Z0-9_./\\-]+\.[a-zA-Z]+)[`"']/, /(\S+\.[tj]sx?)\b/i, /(\S+\.(?:js|ts|jsx|tsx|py|rb|go|rs|java|kt|swift|c|cpp|h))\b/i, ]; /** * Language detection by extension */ /** * High-complexity keywords that indicate Tier 3 (Opus) routing * These tasks require deep reasoning and architectural understanding */ const TIER3_KEYWORDS = [ // Architecture & Design /\b(microservices?|architecture|system\s+design|distributed)\b/i, /\b(design|architect|plan)\s+(a|an|the|complex)\b/i, /\b(design)\s+\w+\s+(schema|system|architecture)\b/i, // Security /\b(oauth2?|pkce|jwt|rbac|authentication\s+system|security\s+audit)\b/i, /\b(refresh\s+token|token\s+rotation|role-based|permission|authorization)\b/i, /\b(encryption|cryptograph|certificate|ssl|tls)\b/i, /\b(end-to-end\s+encryption|key\s+rotation|secure\s+channel)\b/i, // Distributed Systems /\b(consensus|distributed|byzantine|raft|paxos)\b/i, /\b(replication|sharding|partitioning|eventual\s+consistency)\b/i, /\b(load\s+balanc|fault[- ]toleran|high\s+availability)\b/i, /\b(message\s+queue|event\s+sourc|cqrs|saga)\b/i, // Complex Algorithms /\b(algorithm|machine\s+learning|neural|optimization)\b/i, /\b(graph\s+algorithm|tree\s+traversal|dynamic\s+programming)\b/i, // Database Design /\b(schema\s+design|database\s+architect|data\s+model)\b/i, /\b(database\s+schema|multi[- ]tenant)\b/i, /\b(normalization|denormalization|index\s+strateg)\b/i, // Performance Critical /\b(performance\s+critical|low\s+latency|high\s+throughput)\b/i, /\b(memory\s+optimi|cache\s+strateg|concurrent)\b/i, ]; const LANGUAGE_MAP = { '.js': 'javascript', '.jsx': 'javascript', '.ts': 'typescript', '.tsx': 'typescript', '.py': 'python', '.rb': 'ruby', '.go': 'go', '.rs': 'rust', '.java': 'java', '.kt': 'kotlin', '.swift': 'swift', '.c': 'c', '.cpp': 'cpp', '.h': 'c', }; // ============================================================================ // Enhanced Model Router Implementation // ============================================================================ /** * Enhanced Model Router with Agent Booster AST integration * * Provides intelligent 3-tier routing: * - Tier 1: Agent Booster for simple code transforms (352x faster, $0) * - Tier 2: Haiku for low complexity tasks * - Tier 3: Sonnet/Opus for complex reasoning tasks */ export class EnhancedModelRouter { config; tinyDancerRouter; constructor(config) { this.config = { agentBoosterEnabled: true, agentBoosterConfidenceThreshold: 0.7, enabledIntents: [ 'var-to-const', 'add-types', 'add-error-handling', 'async-await', 'add-logging', 'remove-console', ], complexityThresholds: { haiku: 0.3, sonnet: 0.6, opus: 1.0, }, preferCost: false, preferQuality: false, ...config, }; this.tinyDancerRouter = getModelRouter(); } /** * Detect code editing intent from task description */ detectIntent(task) { const taskLower = task.toLowerCase(); let bestIntent = null; let bestScore = 0; for (const [intentType, config] of Object.entries(INTENT_PATTERNS)) { if (!this.config.enabledIntents.includes(intentType)) { continue; } for (const pattern of config.patterns) { if (pattern.test(taskLower)) { const score = config.weight; if (score > bestScore) { bestScore = score; bestIntent = { type: intentType, confidence: score, description: config.description, }; } } } } // Extract file path if intent found if (bestIntent) { const filePath = this.extractFilePath(task); if (filePath) { bestIntent.filePath = filePath; bestIntent.language = this.detectLanguage(filePath); // Boost confidence if file exists if (existsSync(filePath)) { bestIntent.confidence = Math.min(1.0, bestIntent.confidence + 0.1); } } } return bestIntent; } /** * Extract file path from task description */ extractFilePath(task) { for (const pattern of FILE_PATH_PATTERNS) { const match = task.match(pattern); if (match && match[1]) { return match[1]; } } return null; } /** * Detect language from file extension */ detectLanguage(filePath) { const ext = extname(filePath).toLowerCase(); return LANGUAGE_MAP[ext] || 'javascript'; } /** * Check if task contains Tier 3 (Opus) keywords */ containsTier3Keywords(task) { let count = 0; for (const pattern of TIER3_KEYWORDS) { if (pattern.test(task)) { count++; } } return { matches: count > 0, count }; } /** * Route a task to the optimal tier and handler */ async route(task, context) { // Step 1: Deterministic codemod detection (ADR-143). // Only intents that a codemod can apply *deterministically and safely* skip // the LLM. Intents that need inference (add-types, add-error-handling, // async-await) are detected but fall through to model routing below. if (this.config.agentBoosterEnabled) { const intent = this.detectIntent(task); if (intent && intent.confidence >= this.config.agentBoosterConfidenceThreshold && isDeterministicCodemod(intent.type)) { // Route-time dry-run (ADR-143 #3): when a target file is known, only // claim Tier-1 if the codemod actually changes something. This avoids // recommending [CODEMOD_AVAILABLE] for no-ops (e.g. "remove console" on // a file with no console calls). With no file to check, recommend Tier-1 // best-effort — the executor (hooks_codemod) verifies before writing. // Prefer the caller-provided path (authoritative, usually absolute) over // the path heuristically extracted from the task text; resolve relatives. const fpRaw = context?.filePath || intent.filePath; const fp = fpRaw ? (isAbsolute(fpRaw) ? fpRaw : resolvePath(process.cwd(), fpRaw)) : undefined; let tier1 = true; let edits; if (fp && existsSync(fp)) { try { const code = readFileSync(fp, 'utf-8'); const res = applyCodemod(intent.type, code, { language: codemodLanguageFor(fp, intent.language) }); if (res.success && res.changed) { edits = res.edits; } else { tier1 = false; // verified no-op / can't apply → fall through to model routing } } catch { // read error → leave best-effort Tier-1 (executor will verify) } } if (tier1) { const editsNote = edits !== undefined ? ` (${edits} edit${edits === 1 ? '' : 's'})` : ''; return { tier: 1, handler: 'codemod', confidence: intent.confidence, reasoning: `Deterministic codemod can apply "${intent.type}" with ${(intent.confidence * 100).toFixed(0)}% confidence — $0, no LLM${editsNote}`, codemodIntent: intent, agentBoosterIntent: intent, deterministic: true, canSkipLLM: true, estimatedLatencyMs: 1, estimatedCost: 0, }; } // verified no-op: fall through to model routing below } } // Step 2: Check for Tier 3 keywords (architecture, security, distributed) const tier3Check = this.containsTier3Keywords(task); if (tier3Check.matches && tier3Check.count >= 2) { // Strong signal for Opus - multiple complex keywords return { tier: 3, handler: 'opus', model: 'opus', confidence: Math.min(0.95, 0.7 + tier3Check.count * 0.1), complexity: 0.8 + tier3Check.count * 0.05, reasoning: `High complexity task (${tier3Check.count} architectural keywords) - using opus`, canSkipLLM: false, estimatedLatencyMs: 5000, estimatedCost: 0.015, }; } // Step 3: AST complexity analysis (if file path provided) let astComplexity; const targetFile = context?.filePath || this.extractFilePath(task); if (targetFile && existsSync(targetFile)) { try { astComplexity = await this.analyzeASTComplexity(targetFile); } catch { // AST analysis not available, continue with text-based routing } } // Step 4: Text-based complexity + tiny-dancer routing const tinyDancerResult = await this.tinyDancerRouter.route(task); // Step 5: Combine AST complexity with tiny-dancer result // Also boost if single tier3 keyword found let finalComplexity = astComplexity !== undefined ? (astComplexity + tinyDancerResult.complexity) / 2 : tinyDancerResult.complexity; // Boost complexity if tier3 keywords found (even just one) if (tier3Check.matches) { finalComplexity = Math.min(1.0, finalComplexity + 0.25); } // Step 6: Determine tier based on complexity const { haiku, sonnet } = this.config.complexityThresholds; if (finalComplexity < haiku) { return { tier: 2, handler: 'haiku', model: 'haiku', confidence: tinyDancerResult.confidence, complexity: finalComplexity, reasoning: `Low complexity (${(finalComplexity * 100).toFixed(0)}%) - using haiku`, canSkipLLM: false, estimatedLatencyMs: 500, estimatedCost: 0.0002, }; } if (finalComplexity < sonnet) { return { tier: 2, handler: 'sonnet', model: 'sonnet', confidence: tinyDancerResult.confidence, complexity: finalComplexity, reasoning: `Medium complexity (${(finalComplexity * 100).toFixed(0)}%) - using sonnet`, canSkipLLM: false, estimatedLatencyMs: 2000, estimatedCost: 0.003, }; } return { tier: 3, handler: 'opus', model: 'opus', confidence: tinyDancerResult.confidence, complexity: finalComplexity, reasoning: `High complexity (${(finalComplexity * 100).toFixed(0)}%) - using opus`, canSkipLLM: false, estimatedLatencyMs: 5000, estimatedCost: 0.015, }; } /** * Analyze AST complexity of a file * Returns normalized complexity score (0-1) */ async analyzeASTComplexity(filePath) { try { const content = readFileSync(filePath, 'utf-8'); const lines = content.split('\n'); // Simple heuristics for complexity let complexity = 0; // Line count contribution complexity += Math.min(0.3, lines.length / 1000); // Nesting depth estimation (count indentation) const avgIndent = lines .filter((l) => l.trim().length > 0) .map((l) => l.match(/^(\s*)/)?.[1].length || 0) .reduce((sum, indent) => sum + indent, 0) / Math.max(1, lines.length); complexity += Math.min(0.2, avgIndent / 20); // Control flow complexity (count keywords) const controlFlowCount = (content.match(/\b(if|else|for|while|switch|case|try|catch|async|await)\b/g) || []).length; complexity += Math.min(0.3, controlFlowCount / 100); // Function/class count const functionCount = (content.match(/\b(function|class|=>)\b/g) || []).length; complexity += Math.min(0.2, functionCount / 50); return Math.min(1, complexity); } catch { return 0.5; // Default to medium complexity on error } } /** * Execute task using the appropriate tier. * * For Tier-1 deterministic intents this applies the codemod directly (writing * the file back when a path is given) — $0, no LLM. Otherwise it returns the * routing result and the caller invokes the recommended model. */ async execute(task, context) { const routeResult = await this.route(task, context); const intent = routeResult.codemodIntent ?? routeResult.agentBoosterIntent; if (routeResult.tier === 1 && routeResult.deterministic && intent) { const cm = this.tryCodemod(intent, context); if (cm.success) { return { result: { applied: true, changed: cm.changed, edits: cm.edits }, routeResult, }; } // Codemod could not apply (no file / parse issue) — fall back to a model. routeResult.tier = 2; routeResult.handler = 'sonnet'; routeResult.model = 'sonnet'; routeResult.deterministic = false; routeResult.canSkipLLM = false; routeResult.reasoning += ' (codemod fallback to LLM)'; } // Return routing result - caller handles LLM invocation return { result: routeResult.reasoning, routeResult }; } /** * Apply a deterministic codemod to the intent's target file. * * This is the real Tier-1 execution path (ADR-143). It uses the in-process * TypeScript-compiler codemod engine — no `agent-booster` import, no subprocess, * no LLM. Writes the transformed source back to disk when it changes. */ tryCodemod(intent, context) { const filePath = intent.filePath || context?.filePath; if (!filePath || !existsSync(filePath)) { return { success: false, changed: false, edits: 0 }; } const originalCode = context?.originalCode ?? readFileSync(filePath, 'utf-8'); const language = codemodLanguageFor(filePath, intent.language); const result = applyCodemod(intent.type, originalCode, { language }); if (!result.success) { return { success: false, changed: false, edits: 0 }; } if (result.changed && !context?.originalCode) { writeFileSync(filePath, result.output, 'utf-8'); } return { success: true, changed: result.changed, edits: result.edits }; } /** * Get router statistics */ getStats() { return { config: { ...this.config }, tinyDancerStats: this.tinyDancerRouter.getStats(), }; } } // ============================================================================ // Singleton & Factory Functions // ============================================================================ let enhancedRouterInstance = null; /** * Get or create the singleton EnhancedModelRouter instance */ export function getEnhancedModelRouter(config) { if (!enhancedRouterInstance) { enhancedRouterInstance = new EnhancedModelRouter(config); } return enhancedRouterInstance; } /** * Reset the singleton instance */ export function resetEnhancedModelRouter() { enhancedRouterInstance = null; } /** * Create a new EnhancedModelRouter instance (non-singleton) */ export function createEnhancedModelRouter(config) { return new EnhancedModelRouter(config); } // ============================================================================ // Convenience Functions // ============================================================================ /** * Quick route function with enhanced routing */ export async function enhancedRouteToModel(task, context) { const router = getEnhancedModelRouter(); return router.route(task, context); } /** * Detect if a task can be applied by a deterministic, $0 codemod (ADR-143). * Only the deterministic intents qualify — others need a model. */ export function canUseCodemod(task) { const router = getEnhancedModelRouter(); const intent = router.detectIntent(task); if (intent && intent.confidence >= 0.7 && isDeterministicCodemod(intent.type)) { return { canUse: true, intent }; } return { canUse: false }; } /** * @deprecated Agent Booster never performed these transforms. Use {@link canUseCodemod}. */ export const canUseAgentBooster = canUseCodemod; //# sourceMappingURL=enhanced-model-router.js.map