/** * Request Classifier * * Fast request analysis for delegation decisions. * Classifies requests by complexity and determines optimal routing. */ import { RequestClassification, RequestComplexity, ClassifiableRequest, AgentType } from './types'; // ============================================================================ // Classification Rules // ============================================================================ interface ClassificationRule { pattern: RegExp | string; complexity: RequestComplexity; agentType: AgentType; weight: number; } const CLASSIFICATION_RULES: ClassificationRule[] = [ // Quick patterns - instant response { pattern: /^(what is|explain|show me|list|get|find)\s/i, complexity: 'quick', agentType: 'fast-responder', weight: 0.9 }, { pattern: /status|help|version|info$/i, complexity: 'quick', agentType: 'fast-responder', weight: 0.95 }, { pattern: /^(yes|no|ok|thanks|done)$/i, complexity: 'quick', agentType: 'fast-responder', weight: 0.99 }, // Code patterns - moderate complexity { pattern: /(review|check|analyze|inspect)\s+(this|the|my)?\s*(code|file|function)/i, complexity: 'moderate', agentType: 'reviewer', weight: 0.85 }, { pattern: /(fix|solve|debug|resolve)\s+/i, complexity: 'moderate', agentType: 'analyzer', weight: 0.8 }, { pattern: /(add|update|modify|change)\s+(a|the|this)?\s*(function|method|class)/i, complexity: 'moderate', agentType: 'coder', weight: 0.85 }, // Research patterns - streaming { pattern: /(research|investigate|explore|search)\s+/i, complexity: 'streaming', agentType: 'researcher', weight: 0.8 }, { pattern: /(analyze|audit|examine)\s+(the|entire|whole)?\s*(codebase|project|repo)/i, complexity: 'streaming', agentType: 'explorer', weight: 0.85 }, // Complex patterns - need main agent or parallel delegation { pattern: /(refactor|rewrite|restructure|redesign)\s+/i, complexity: 'complex', agentType: 'planner', weight: 0.9 }, { pattern: /(implement|build|create|develop)\s+(a|an|the)?\s*(new|feature|system)/i, complexity: 'complex', agentType: 'planner', weight: 0.85 }, { pattern: /(architect|design|plan)\s+/i, complexity: 'complex', agentType: 'planner', weight: 0.9 }, { pattern: /migrate|upgrade|port\s+/i, complexity: 'complex', agentType: 'planner', weight: 0.85 }, ]; // Keywords that indicate complexity const COMPLEXITY_INDICATORS = { high: ['architecture', 'system', 'multiple', 'integrate', 'refactor', 'migrate', 'redesign'], medium: ['implement', 'feature', 'function', 'module', 'component', 'service', 'api'], low: ['fix', 'update', 'change', 'add', 'remove', 'rename', 'comment'] }; // Time estimates by complexity (ms) const TIME_ESTIMATES: Record = { quick: { min: 100, max: 2000 }, moderate: { min: 2000, max: 15000 }, complex: { min: 15000, max: 120000 }, streaming: { min: 5000, max: 60000 } }; // ============================================================================ // Request Classifier Class // ============================================================================ export class RequestClassifier { private cachedClassifications: Map = new Map(); private readonly cacheMaxSize: number = 1000; // Capability mapping by request type private readonly typeCapabilities: Record = { code: ['syntax', 'semantics', 'patterns', 'best-practices'], question: ['knowledge', 'explanation', 'examples'], task: ['execution', 'planning', 'coordination'], analysis: ['inspection', 'metrics', 'patterns', 'security'], review: ['quality', 'standards', 'best-practices', 'security'], refactor: ['patterns', 'optimization', 'clean-code'], debug: ['tracing', 'analysis', 'diagnosis'] }; /** * Classify a request for delegation decisions */ async classify(request: ClassifiableRequest): Promise { // Check cache first const cacheKey = this.getCacheKey(request); const cached = this.cachedClassifications.get(cacheKey); if (cached) { return { ...cached, confidence: Math.max(0, cached.confidence - 0.1) }; // Slightly lower confidence for cached } // Analyze request const analysis = await this.analyzeRequest(request); // Determine complexity const complexity = this.determineComplexity(request, analysis); // Get recommended agent const recommendedAgent = this.getRecommendedAgent(request, complexity, analysis); // Calculate estimated time const estimatedTime = this.estimateTime(complexity, analysis); // Determine capabilities needed const requiredCapabilities = this.getRequiredCapabilities(request, analysis); // Build classification const classification: RequestClassification = { complexity, score: analysis.complexityScore, confidence: analysis.confidence, recommendedAgent, estimatedTime, canDelegate: this.canDelegate(complexity, analysis), delegationPriority: this.getPriority(request, analysis), requiredCapabilities, contextRequirements: { files: analysis.fileCount, depth: analysis.contextDepth, history: analysis.needsHistory } }; // Cache the result this.cacheClassification(cacheKey, classification); return classification; } /** * Quick classification for fast-path decisions (< 50ms target) */ quickClassify(content: string): { complexity: RequestComplexity; agent: AgentType } { // Fast pattern matching for (const rule of CLASSIFICATION_RULES) { if (typeof rule.pattern === 'string') { if (content.toLowerCase().includes(rule.pattern.toLowerCase())) { return { complexity: rule.complexity, agent: rule.agentType }; } } else { if (rule.pattern.test(content)) { return { complexity: rule.complexity, agent: rule.agentType }; } } } // Default based on length const length = content.length; if (length < 100) return { complexity: 'quick', agent: 'fast-responder' }; if (length < 500) return { complexity: 'moderate', agent: 'coder' }; return { complexity: 'complex', agent: 'planner' }; } // ============================================================================ // Private Methods // ============================================================================ private async analyzeRequest(request: ClassifiableRequest): Promise<{ complexityScore: number; confidence: number; fileCount: number; contextDepth: 'shallow' | 'medium' | 'deep'; needsHistory: boolean; matchedRules: ClassificationRule[]; keywordDensity: Record; }> { const content = request.content.toLowerCase(); // Match rules const matchedRules: ClassificationRule[] = []; for (const rule of CLASSIFICATION_RULES) { const pattern = typeof rule.pattern === 'string' ? new RegExp(rule.pattern, 'i') : rule.pattern; if (pattern.test(content)) { matchedRules.push(rule); } } // Calculate keyword density const keywordDensity: Record = {}; for (const [level, keywords] of Object.entries(COMPLEXITY_INDICATORS)) { keywordDensity[level] = keywords.reduce((count, kw) => { return count + (content.includes(kw) ? 1 : 0); }, 0) / keywords.length; } // Calculate complexity score (0-1) let complexityScore = 0; // Length factor complexityScore += Math.min(request.content.length / 2000, 0.3); // Rule matching factor if (matchedRules.length > 0) { const avgWeight = matchedRules.reduce((sum, r) => sum + r.weight, 0) / matchedRules.length; complexityScore += avgWeight * 0.4; } // Keyword density factor complexityScore += keywordDensity.high * 0.2; complexityScore += keywordDensity.medium * 0.1; // File count factor const fileCount = request.files?.length || 0; complexityScore += Math.min(fileCount / 10, 0.1); // Normalize complexityScore = Math.min(complexityScore, 1); // Determine context depth let contextDepth: 'shallow' | 'medium' | 'deep' = 'shallow'; if (complexityScore > 0.6 || fileCount > 3) contextDepth = 'deep'; else if (complexityScore > 0.3 || fileCount > 1) contextDepth = 'medium'; // Check if history is needed const needsHistory = /context|previous|earlier|before|last|history/i.test(content); // Calculate confidence let confidence = 0.5; if (matchedRules.length > 0) confidence += 0.3; if (keywordDensity.high > 0 || keywordDensity.medium > 0) confidence += 0.1; confidence = Math.min(confidence, 0.95); return { complexityScore, confidence, fileCount, contextDepth, needsHistory, matchedRules, keywordDensity }; } private determineComplexity( request: ClassifiableRequest, analysis: { complexityScore: number; matchedRules: ClassificationRule[] } ): RequestComplexity { // Check matched rules first if (analysis.matchedRules.length > 0) { // Get the highest weight rule's complexity const topRule = analysis.matchedRules.reduce((best, rule) => rule.weight > best.weight ? rule : best ); return topRule.complexity; } // Fall back to score-based classification if (analysis.complexityScore < 0.25) return 'quick'; if (analysis.complexityScore < 0.5) return 'moderate'; if (analysis.complexityScore < 0.75) return 'streaming'; return 'complex'; } private getRecommendedAgent( request: ClassifiableRequest, complexity: RequestComplexity, analysis: { matchedRules: ClassificationRule[] } ): AgentType { // Check matched rules if (analysis.matchedRules.length > 0) { const topRule = analysis.matchedRules.reduce((best, rule) => rule.weight > best.weight ? rule : best ); return topRule.agentType; } // Map request type to agent const typeToAgent: Record = { code: 'coder', question: 'fast-responder', task: 'executor', analysis: 'analyzer', review: 'reviewer', refactor: 'coder', debug: 'analyzer' }; return typeToAgent[request.type] || 'fast-responder'; } private estimateTime( complexity: RequestComplexity, analysis: { complexityScore: number; fileCount: number } ): number { const base = TIME_ESTIMATES[complexity]; let estimate = base.min + (base.max - base.min) * analysis.complexityScore; // Add time for files estimate += analysis.fileCount * 500; return Math.round(estimate); } private canDelegate(complexity: RequestComplexity, analysis: any): boolean { // Quick and moderate can always be delegated if (complexity === 'quick' || complexity === 'moderate') return true; // Streaming can be delegated with progress if (complexity === 'streaming') return true; // Complex depends on confidence return analysis.confidence > 0.7; } private getPriority( request: ClassifiableRequest, analysis: any ): 'low' | 'medium' | 'high' | 'critical' { // Check metadata for explicit priority if (request.metadata?.priority) { return request.metadata.priority; } // Determine from analysis if (analysis.keywordDensity?.high > 0.5) return 'high'; if (analysis.complexityScore > 0.7) return 'high'; if (analysis.complexityScore > 0.4) return 'medium'; return 'low'; } private getRequiredCapabilities( request: ClassifiableRequest, analysis: any ): string[] { const capabilities = new Set(); // Add type-based capabilities const typeCaps = this.typeCapabilities[request.type] || []; typeCaps.forEach(c => capabilities.add(c)); // Add based on content analysis if (/security|auth|encrypt/i.test(request.content)) capabilities.add('security'); if (/test|spec|coverage/i.test(request.content)) capabilities.add('testing'); if (/performance|optim|speed/i.test(request.content)) capabilities.add('performance'); if (/doc|comment|readme/i.test(request.content)) capabilities.add('documentation'); return Array.from(capabilities); } private getCacheKey(request: ClassifiableRequest): string { return `${request.type}:${request.content.slice(0, 100)}:${request.files?.length || 0}`; } private cacheClassification(key: string, classification: RequestClassification): void { // Enforce max size if (this.cachedClassifications.size >= this.cacheMaxSize) { // Remove oldest entry const firstKey = this.cachedClassifications.keys().next().value; if (firstKey) { this.cachedClassifications.delete(firstKey); } } this.cachedClassifications.set(key, classification); } /** * Clear classification cache */ clearCache(): void { this.cachedClassifications.clear(); } } // ============================================================================ // Factory Function // ============================================================================ export function createRequestClassifier(): RequestClassifier { return new RequestClassifier(); } // ============================================================================ // Export // ============================================================================ export default RequestClassifier;