UNPKG

contaigents

Version:

Modular AI Content Ecosystem with Audio Generation

267 lines (262 loc) 11.5 kB
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 }); } }