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