UNPKG

contaigents

Version:

Modular AI Content Ecosystem with Audio Generation

244 lines (239 loc) 10.7 kB
import { BaseTool } from './BaseTool.js'; import { FileService } from '../fileService.js'; export class FileWriteLinesTool extends BaseTool { constructor(baseDir) { super(); this.fileService = new FileService(baseDir); } getName() { return 'file_write_lines'; } getDescription() { return 'Perform precise line-based operations on files: insert lines at specific positions, replace line ranges, or delete lines. More surgical than full file rewrites for targeted modifications.'; } getAgentGuidance() { return ` ## ✏️ FILE WRITE LINES BEST PRACTICES ### 🎯 OPERATION TYPES: - **insert**: Add new lines at a specific position - **replace**: Replace a range of lines with new content - **delete**: Remove specific lines or line ranges ### 📝 OPERATION PATTERNS: - **Insert at beginning**: operation="insert", line_position=1 - **Insert at end**: operation="insert", line_position=-1 - **Replace function**: Use file_search to find, then replace those lines - **Delete imports**: Find import lines, then delete them ### 💡 SAFETY TIPS: - Always use file_read_lines first to verify target lines - Use preview mode to see changes before applying - Keep backups for important files - Test operations on small ranges first ### 🚀 WORKFLOW EXAMPLES: 1. Search for function → Read lines → Replace with modified version 2. Find TODO comment → Insert implementation below it 3. Locate old import → Delete it → Insert new import at top `; } getParameters() { return [ { name: 'file_path', type: 'string', description: 'Path to the file to modify (relative to project root)', required: true }, { name: 'operation', type: 'string', description: 'Operation type: "insert", "replace", or "delete"', required: true }, { name: 'line_position', type: 'number', description: 'Line position for insert (1-based), or start line for replace/delete. Use -1 to insert at end.', required: true }, { name: 'line_end', type: 'number', description: 'End line for replace/delete operations (1-based, inclusive). Not used for insert.', required: false }, { name: 'content', type: 'string', description: 'Content to insert or replace with. Each line should be separated by \\n. Not used for delete.', required: false }, { name: 'preview', type: 'boolean', description: 'If true, shows what the operation would do without actually modifying the file', required: false, default: false } ]; } async execute(parameters) { // Validate parameters const validationError = this.validateParameters(parameters); if (validationError) { return validationError; } const { file_path, operation, line_position, line_end, content, preview = false } = parameters; try { // Validate operation type const validOperations = ['insert', 'replace', 'delete']; if (!validOperations.includes(operation)) { return { success: false, error: `Invalid operation "${operation}". Must be one of: ${validOperations.join(', ')}` }; } // Validate parameters based on operation if (operation === 'insert' && content === undefined) { return { success: false, error: 'content parameter is required for insert operation' }; } if (operation === 'replace' && (content === undefined || line_end === undefined)) { return { success: false, error: 'content and line_end parameters are required for replace operation' }; } if (operation === 'delete' && line_end === undefined && line_position !== -1) { // For delete, if line_end is not specified, delete single line // line_end = line_position; } // Read current file content const fileContent = await this.fileService.readFile(file_path); const lines = fileContent.split('\n'); const totalLines = lines.length; let newLines = [...lines]; // Perform operation switch (operation) { case 'insert': const insertLines = content ? content.split('\n') : []; let insertPosition = line_position; if (insertPosition === -1) { insertPosition = totalLines + 1; // Insert at end } if (insertPosition < 1 || insertPosition > totalLines + 1) { return { success: false, error: `Invalid insert position ${insertPosition}. Must be 1-${totalLines + 1} or -1` }; } // Insert lines (convert to 0-based index) newLines.splice(insertPosition - 1, 0, ...insertLines); break; case 'replace': if (line_position < 1 || line_position > totalLines) { return { success: false, error: `Invalid line_position ${line_position}. Must be 1-${totalLines}` }; } if (line_end < line_position || line_end > totalLines) { return { success: false, error: `Invalid line_end ${line_end}. Must be ${line_position}-${totalLines}` }; } const replaceLines = content ? content.split('\n') : []; const replaceDeleteCount = line_end - line_position + 1; // Replace lines (convert to 0-based index) newLines.splice(line_position - 1, replaceDeleteCount, ...replaceLines); break; case 'delete': const deleteEnd = line_end || line_position; if (line_position < 1 || line_position > totalLines) { return { success: false, error: `Invalid line_position ${line_position}. Must be 1-${totalLines}` }; } if (deleteEnd < line_position || deleteEnd > totalLines) { return { success: false, error: `Invalid line_end ${deleteEnd}. Must be ${line_position}-${totalLines}` }; } const deleteDeleteCount = deleteEnd - line_position + 1; // Delete lines (convert to 0-based index) newLines.splice(line_position - 1, deleteDeleteCount); break; } const newContent = newLines.join('\n'); // If preview mode, return the changes without writing if (preview) { return { success: true, data: { file_path, operation, preview: true, original_lines: totalLines, new_lines: newLines.length, changes_preview: newContent, operation_summary: this.getOperationSummary(operation, line_position, line_end, content) }, message: `Preview: ${operation} operation on ${file_path} (${totalLines}${newLines.length} lines)` }; } // Write the modified content await this.fileService.saveFile({ path: file_path, content: newContent }); return { success: true, data: { file_path, operation, original_lines: totalLines, new_lines: newLines.length, lines_affected: this.getLinesAffected(operation, line_position, line_end, content), operation_summary: this.getOperationSummary(operation, line_position, line_end, content) }, message: `Successfully performed ${operation} operation on ${file_path} (${totalLines}${newLines.length} lines)` }; } catch (error) { return { success: false, error: `Line operation failed: ${error.message}` }; } } getOperationSummary(operation, linePosition, lineEnd, content) { switch (operation) { case 'insert': const insertLineCount = content ? content.split('\n').length : 0; return `Inserted ${insertLineCount} line(s) at position ${linePosition}`; case 'replace': const replaceLineCount = content ? content.split('\n').length : 0; const originalCount = lineEnd - linePosition + 1; return `Replaced ${originalCount} line(s) (${linePosition}-${lineEnd}) with ${replaceLineCount} line(s)`; case 'delete': const deleteEnd = lineEnd || linePosition; const deletedCount = deleteEnd - linePosition + 1; return `Deleted ${deletedCount} line(s) (${linePosition}-${deleteEnd})`; default: return `Performed ${operation} operation`; } } getLinesAffected(operation, linePosition, lineEnd, content) { switch (operation) { case 'insert': return content ? content.split('\n').length : 0; case 'replace': return lineEnd - linePosition + 1; case 'delete': const deleteEnd = lineEnd || linePosition; return deleteEnd - linePosition + 1; default: return 0; } } }