UNPKG

contaigents

Version:

Modular AI Content Ecosystem with Audio Generation

203 lines (198 loc) 8.13 kB
import { BaseTool } from './BaseTool.js'; import { FileService } from '../fileService.js'; export class FileSearchTool extends BaseTool { constructor(baseDir) { super(); this.fileService = new FileService(baseDir); } getName() { return 'file_search'; } getDescription() { return 'Search for patterns within a file using text, regex, or fuzzy matching. Returns matching lines with configurable context lines before and after each match. Ideal for finding specific code patterns, functions, or content without reading entire files.'; } getAgentGuidance() { return ` ## 🔍 FILE SEARCH BEST PRACTICES ### 🎯 WHEN TO USE: - **Finding specific functions**: Search for function definitions, class names, or method calls - **Locating patterns**: Find specific code patterns, imports, or configurations - **Context-aware reading**: Get surrounding context without reading entire files - **Debugging**: Locate error messages, TODO comments, or specific variables ### 📝 SEARCH TYPES: - **text**: Simple text matching (case-insensitive by default) - **regex**: Regular expression patterns for complex matching - **fuzzy**: Approximate matching for typos or variations ### 💡 OPTIMIZATION TIPS: - Use specific patterns to reduce noise - Adjust context_lines for appropriate surrounding code - Use max_results to limit output for large files - Combine with file_read_lines for precise line reading after search ### 🚀 EXAMPLE PATTERNS: - Functions: "function\\s+\\w+\\(" (regex) - Imports: "import.*from" (regex) - TODO items: "TODO|FIXME|BUG" (regex) - React hooks: "use[A-Z]\\w+" (regex) `; } getParameters() { return [ { name: 'file_path', type: 'string', description: 'Path to the file to search (relative to project root)', required: true }, { name: 'search_pattern', type: 'string', description: 'Pattern to search for (text, regex, or fuzzy pattern)', required: true }, { name: 'search_type', type: 'string', description: 'Type of search: "text", "regex", or "fuzzy"', required: false, default: 'text' }, { name: 'context_lines_before', type: 'number', description: 'Number of lines to include before each match (default: 3)', required: false, default: 3 }, { name: 'context_lines_after', type: 'number', description: 'Number of lines to include after each match (default: 3)', required: false, default: 3 }, { name: 'case_sensitive', type: 'boolean', description: 'Whether search should be case sensitive (default: false)', required: false, default: false }, { name: 'max_results', type: 'number', description: 'Maximum number of matches to return (default: 20)', required: false, default: 20 } ]; } async execute(parameters) { // Validate parameters const validationError = this.validateParameters(parameters); if (validationError) { return validationError; } const { file_path, search_pattern, search_type = 'text', context_lines_before = 3, context_lines_after = 3, case_sensitive = false, max_results = 20 } = parameters; try { // Validate search type const validSearchTypes = ['text', 'regex', 'fuzzy']; if (!validSearchTypes.includes(search_type)) { return { success: false, error: `Invalid search_type "${search_type}". Must be one of: ${validSearchTypes.join(', ')}` }; } // Validate numeric parameters if (context_lines_before < 0 || context_lines_after < 0 || max_results < 1) { return { success: false, error: 'context_lines_before and context_lines_after must be >= 0, max_results must be >= 1' }; } // Read file content const fileContent = await this.fileService.readFile(file_path); // Split content into lines const lines = fileContent.split('\n'); const matches = []; // Perform search based on type for (let i = 0; i < lines.length && matches.length < max_results; i++) { const line = lines[i]; let isMatch = false; switch (search_type) { case 'text': isMatch = case_sensitive ? line.includes(search_pattern) : line.toLowerCase().includes(search_pattern.toLowerCase()); break; case 'regex': try { const flags = case_sensitive ? 'g' : 'gi'; const regex = new RegExp(search_pattern, flags); isMatch = regex.test(line); } catch (error) { return { success: false, error: `Invalid regex pattern: ${error.message}` }; } break; case 'fuzzy': isMatch = this.fuzzyMatch(line, search_pattern, case_sensitive); break; } if (isMatch) { // Extract context lines const contextBefore = []; const contextAfter = []; // Get lines before for (let j = Math.max(0, i - context_lines_before); j < i; j++) { contextBefore.push(lines[j]); } // Get lines after for (let j = i + 1; j <= Math.min(lines.length - 1, i + context_lines_after); j++) { contextAfter.push(lines[j]); } matches.push({ line_number: i + 1, // 1-based line numbers match_text: line, context_before: contextBefore, context_after: contextAfter }); } } return { success: true, data: { file_path, search_pattern, search_type, total_matches: matches.length, matches, truncated: matches.length === max_results && matches.length < lines.length }, message: `Found ${matches.length} match(es) for "${search_pattern}" in ${file_path}` }; } catch (error) { return { success: false, error: `Search failed: ${error.message}` }; } } /** * Simple fuzzy matching algorithm */ fuzzyMatch(text, pattern, caseSensitive) { const textToSearch = caseSensitive ? text : text.toLowerCase(); const patternToMatch = caseSensitive ? pattern : pattern.toLowerCase(); let patternIndex = 0; for (let i = 0; i < textToSearch.length && patternIndex < patternToMatch.length; i++) { if (textToSearch[i] === patternToMatch[patternIndex]) { patternIndex++; } } return patternIndex === patternToMatch.length; } }