contaigents
Version:
Modular AI Content Ecosystem with Audio Generation
203 lines (198 loc) • 8.13 kB
JavaScript
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;
}
}