contaigents
Version:
Modular AI Content Ecosystem with Audio Generation
267 lines (262 loc) • 11.5 kB
JavaScript
import { BaseTool } from '../base/BaseTool.js';
import { IntelligentFileService } from '../../services/IntelligentFileService.js';
export class PartialFileOperationTool extends BaseTool {
constructor(context) {
super('partial_file_operation', 'Perform intelligent partial file operations including reading/writing specific sections, lines, or semantic blocks', context);
this.intelligentFileService = new IntelligentFileService(context.workingDirectory);
}
async execute(params) {
try {
if ('content' in params) {
return await this.executeWrite(params);
}
else {
return await this.executeRead(params);
}
}
catch (error) {
return this.createErrorResult(`Partial file operation failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
async executeRead(params) {
const { filePath, operation, contextLines = 0 } = params;
if (!filePath) {
return this.createErrorResult('File path is required');
}
// Check if file exists
const exists = await this.intelligentFileService.fileExists(filePath);
if (!exists) {
return this.createErrorResult(`File not found: ${filePath}`);
}
const content = await this.intelligentFileService.readPartialFile(filePath, operation);
let result = {
filePath,
operation,
content,
lineCount: content.split('\n').length
};
// Add context lines if requested
if (contextLines > 0 && operation.searchPattern) {
const fullContent = await this.intelligentFileService.readFile(filePath);
result.contentWithContext = this.addContextLines(fullContent, content, contextLines);
}
// Add section information if reading by section
if (operation.section) {
result.sectionInfo = await this.analyzeSectionStructure(filePath, operation.section);
}
// Add line information if reading by line range
if (operation.startLine !== undefined && operation.endLine !== undefined) {
result.lineRange = {
start: operation.startLine,
end: operation.endLine,
totalLines: (await this.intelligentFileService.readFile(filePath)).split('\n').length
};
}
return this.createSuccessResult(result, {
timestamp: Date.now(),
operationType: 'read'
});
}
async executeWrite(params) {
const { filePath, content, operation, mode = 'replace', backup = true } = params;
if (!filePath || content === undefined) {
return this.createErrorResult('File path and content are required');
}
// Check if file exists
const exists = await this.intelligentFileService.fileExists(filePath);
if (!exists) {
return this.createErrorResult(`File not found: ${filePath}`);
}
// Create backup if requested
if (backup) {
await this.createBackup(filePath);
}
const originalContent = await this.intelligentFileService.readFile(filePath);
const modifiedContent = await this.applyPartialWrite(originalContent, content, operation, mode);
// Save the modified content
await this.intelligentFileService.saveFile({ path: filePath, content: modifiedContent });
const result = {
filePath,
operation,
mode,
originalLength: originalContent.length,
newLength: modifiedContent.length,
linesAdded: modifiedContent.split('\n').length - originalContent.split('\n').length,
backupCreated: backup
};
return this.createSuccessResult(result, {
timestamp: Date.now(),
operationType: 'write'
});
}
async applyPartialWrite(originalContent, newContent, operation, mode) {
const lines = originalContent.split('\n');
// Line-based operations
if (operation.startLine !== undefined && operation.endLine !== undefined) {
const startIdx = Math.max(0, operation.startLine - 1);
const endIdx = Math.min(lines.length, operation.endLine);
switch (mode) {
case 'replace':
lines.splice(startIdx, endIdx - startIdx, ...newContent.split('\n'));
break;
case 'insert':
lines.splice(startIdx, 0, ...newContent.split('\n'));
break;
case 'append':
lines.splice(endIdx, 0, ...newContent.split('\n'));
break;
case 'prepend':
lines.splice(startIdx, 0, ...newContent.split('\n'));
break;
}
return lines.join('\n');
}
// Section-based operations
if (operation.section) {
return await this.applySectionWrite(originalContent, newContent, operation.section, mode);
}
// Search pattern-based operations
if (operation.searchPattern) {
return this.applyPatternWrite(originalContent, newContent, operation.searchPattern, mode);
}
// Default: append to end
return originalContent + '\n' + newContent;
}
async applySectionWrite(originalContent, newContent, sectionName, mode) {
if (!this.context.llm) {
// Fallback: simple text replacement
return originalContent.replace(sectionName, newContent);
}
const prompt = `Modify the "${sectionName}" section in this content:
Original Content:
${originalContent}
New Content for Section:
${newContent}
Mode: ${mode}
Please return the complete modified content with the section ${mode}d appropriately.`;
try {
const response = await this.context.llm.executePrompt(prompt);
return response.content;
}
catch (error) {
// Fallback to simple replacement
return originalContent.replace(sectionName, newContent);
}
}
applyPatternWrite(originalContent, newContent, searchPattern, mode) {
const lines = originalContent.split('\n');
const matchingIndices = [];
// Find all matching lines
lines.forEach((line, index) => {
if (line.toLowerCase().includes(searchPattern.toLowerCase())) {
matchingIndices.push(index);
}
});
if (matchingIndices.length === 0) {
// No matches found, append to end
return originalContent + '\n' + newContent;
}
// Apply modification based on mode
const newContentLines = newContent.split('\n');
let offset = 0;
for (const index of matchingIndices) {
const adjustedIndex = index + offset;
switch (mode) {
case 'replace':
lines.splice(adjustedIndex, 1, ...newContentLines);
offset += newContentLines.length - 1;
break;
case 'insert':
lines.splice(adjustedIndex, 0, ...newContentLines);
offset += newContentLines.length;
break;
case 'append':
lines.splice(adjustedIndex + 1, 0, ...newContentLines);
offset += newContentLines.length;
break;
case 'prepend':
lines.splice(adjustedIndex, 0, ...newContentLines);
offset += newContentLines.length;
break;
}
}
return lines.join('\n');
}
addContextLines(fullContent, matchedContent, contextLines) {
const fullLines = fullContent.split('\n');
const matchedLines = matchedContent.split('\n');
const result = [];
for (const matchedLine of matchedLines) {
const lineIndex = fullLines.findIndex(line => line === matchedLine);
if (lineIndex !== -1) {
const start = Math.max(0, lineIndex - contextLines);
const end = Math.min(fullLines.length, lineIndex + contextLines + 1);
for (let i = start; i < end; i++) {
if (i === lineIndex) {
result.push(`>>> ${fullLines[i]} <<<`); // Mark the matched line
}
else {
result.push(fullLines[i]);
}
}
result.push('---'); // Separator between matches
}
}
return result.join('\n');
}
async analyzeSectionStructure(filePath, sectionName) {
const content = await this.intelligentFileService.readFile(filePath);
const lines = content.split('\n');
const sections = [];
// Basic section detection for markdown files
if (filePath.endsWith('.md') || filePath.endsWith('.mdx')) {
let currentSection = null;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// Detect headings
const headingMatch = line.match(/^(#+)\s+(.+)$/);
if (headingMatch) {
// Save previous section
if (currentSection) {
currentSection.endLine = i - 1;
currentSection.content = lines.slice(currentSection.startLine - 1, i).join('\n');
sections.push(currentSection);
}
// Start new section
currentSection = {
name: headingMatch[2],
startLine: i + 1,
type: 'heading'
};
}
// Detect code blocks
if (line.startsWith('```')) {
const codeBlockEnd = lines.findIndex((l, idx) => idx > i && l.startsWith('```'));
if (codeBlockEnd !== -1) {
sections.push({
name: `Code Block (${i + 1}-${codeBlockEnd + 1})`,
startLine: i + 1,
endLine: codeBlockEnd + 1,
content: lines.slice(i, codeBlockEnd + 1).join('\n'),
type: 'code-block'
});
}
}
}
// Close last section
if (currentSection) {
currentSection.endLine = lines.length;
currentSection.content = lines.slice(currentSection.startLine - 1).join('\n');
sections.push(currentSection);
}
}
// Filter sections that match the requested section name
return sections.filter(section => section.name.toLowerCase().includes(sectionName.toLowerCase()));
}
async createBackup(filePath) {
const content = await this.intelligentFileService.readFile(filePath);
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const backupPath = `${filePath}.backup.${timestamp}`;
await this.intelligentFileService.saveFile({ path: backupPath, content });
}
}