UNPKG

contaigents

Version:

Modular AI Content Ecosystem with Audio Generation

327 lines (326 loc) 13.5 kB
import { BaseTool } from './BaseTool.js'; import { FileService } from '../fileService.js'; import { CinematicWorkflowTool } from './CinematicWorkflowTool.js'; import path from 'path'; export class FileWriteTool extends BaseTool { constructor(baseDir) { super(); this.supportedExtensions = ['.txt', '.md', '.mdx', '.js', '.ts', '.jsx', '.tsx', '.json', '.yaml', '.yml', '.css', '.scss', '.html', '.xml', '.py', '.java', '.cpp', '.c', '.h', '.go', '.rs', '.php', '.rb', '.sh', '.sql']; this.baseDir = baseDir || process.cwd(); this.fileService = new FileService(this.baseDir); this.cinematicWorkflowTool = new CinematicWorkflowTool(this.baseDir); } getName() { return 'write_file'; } getDescription() { return 'Write content to files with support for file creation, full rewrite, partial write using line numbers, and append to end.'; } getParameters() { return [ { name: 'operation_type', type: 'string', description: 'Type of write operation: "create" (new file), "rewrite" (replace entire file), "partial" (replace specific lines), or "append" (add to end of file)', required: true }, { name: 'file_path', type: 'string', description: 'The path to the file to write (relative to the current working directory)', required: true }, { name: 'content', type: 'string', description: 'The content to write to the file', required: true }, { name: 'start_line', type: 'number', description: 'Starting line number for partial write (1-based, inclusive). Required for partial operations.', required: false }, { name: 'end_line', type: 'number', description: 'Ending line number for partial write (1-based, inclusive). If not provided, replaces from start_line to end of file.', required: false } ]; } async execute(parameters) { // Validate parameters const validationError = this.validateParameters(parameters); if (validationError) { return validationError; } const { operation_type, file_path, content, start_line, end_line } = parameters; try { // Validate operation type if (!['create', 'rewrite', 'partial', 'append'].includes(operation_type)) { return { success: false, error: `Invalid operation type: ${operation_type}. Must be 'create', 'rewrite', 'partial', or 'append'` }; } // Check if file extension is supported const fileExtension = path.extname(file_path).toLowerCase(); if (!this.supportedExtensions.includes(fileExtension)) { return { success: false, error: `Unsupported file type: ${fileExtension}. Supported types: ${this.supportedExtensions.join(', ')}` }; } // Validate partial operation parameters if (operation_type === 'partial') { if (start_line === undefined) { return { success: false, error: 'start_line is required for partial write operations' }; } if (start_line < 1) { return { success: false, error: 'start_line must be 1 or greater' }; } if (end_line !== undefined && end_line < start_line) { return { success: false, error: 'end_line must be greater than or equal to start_line' }; } } // Execute the appropriate operation switch (operation_type) { case 'create': return await this.createFile(file_path, content); case 'rewrite': return await this.rewriteFile(file_path, content); case 'partial': return await this.partialWrite(file_path, content, start_line, end_line); case 'append': return await this.appendToFile(file_path, content); default: return { success: false, error: `Unknown operation type: ${operation_type}` }; } } catch (error) { return { success: false, error: `Failed to write file: ${error.message}` }; } } async createFile(filePath, content) { try { // Check if file already exists const fileExists = await this.fileExists(filePath); if (fileExists) { return { success: false, error: `File already exists: ${filePath}. Use 'rewrite' operation to overwrite existing files.` }; } // Check if this is a cinematic script file const isCinematicScript = this.isCinematicScript(filePath, content); if (isCinematicScript) { // Use cinematic workflow for script files return await this.cinematicWorkflowTool.execute({ operation: 'create_scene', scene_file_path: filePath, content: content, auto_generate_images: true, image_style: 'cinematic' }); } // Create the file normally await this.fileService.saveFile({ path: filePath, content }); return { success: true, data: { operation: 'create', file_path: filePath, content_length: content.length, lines_written: content.split('\n').length }, message: `Successfully created file: ${filePath} (${content.split('\n').length} lines, ${content.length} characters)` }; } catch (error) { return { success: false, error: `Failed to create file: ${error.message}` }; } } async rewriteFile(filePath, content) { try { // Check if this is a cinematic script file const isCinematicScript = this.isCinematicScript(filePath, content); if (isCinematicScript) { // Use cinematic workflow for script files return await this.cinematicWorkflowTool.execute({ operation: 'update_scene', scene_file_path: filePath, content: content, auto_generate_images: true, image_style: 'cinematic' }); } // Write the new content normally await this.fileService.saveFile({ path: filePath, content }); return { success: true, data: { operation: 'rewrite', file_path: filePath, content_length: content.length, lines_written: content.split('\n').length }, message: `Successfully rewrote file: ${filePath} (${content.split('\n').length} lines, ${content.length} characters)` }; } catch (error) { return { success: false, error: `Failed to rewrite file: ${error.message}` }; } } async partialWrite(filePath, newContent, startLine, endLine) { try { // Check if file exists if (!await this.fileExists(filePath)) { return { success: false, error: `File does not exist: ${filePath}. Use 'create' operation to create new files.` }; } // Read existing content const existingContent = await this.fileService.readFile(filePath); const existingLines = existingContent.split('\n'); // Validate line numbers if (startLine > existingLines.length) { return { success: false, error: `start_line (${startLine}) is beyond file length (${existingLines.length} lines)` }; } const actualEndLine = endLine || existingLines.length; if (actualEndLine > existingLines.length) { return { success: false, error: `end_line (${actualEndLine}) is beyond file length (${existingLines.length} lines)` }; } // Perform partial replacement const newContentLines = newContent.split('\n'); const beforeLines = existingLines.slice(0, startLine - 1); const afterLines = existingLines.slice(actualEndLine); const finalLines = [...beforeLines, ...newContentLines, ...afterLines]; const finalContent = finalLines.join('\n'); // Write the modified content await this.fileService.saveFile({ path: filePath, content: finalContent }); return { success: true, data: { operation: 'partial', file_path: filePath, lines_replaced: actualEndLine - startLine + 1, lines_inserted: newContentLines.length, start_line: startLine, end_line: actualEndLine, total_lines: finalLines.length }, message: `Successfully updated lines ${startLine}-${actualEndLine} in ${filePath}. Replaced ${actualEndLine - startLine + 1} lines with ${newContentLines.length} lines.` }; } catch (error) { return { success: false, error: `Failed to perform partial write: ${error.message}` }; } } async fileExists(filePath) { try { await this.fileService.readFile(filePath); return true; } catch { return false; } } async appendToFile(filePath, content) { try { // Check if file exists if (!await this.fileExists(filePath)) { return { success: false, error: `File does not exist: ${filePath}. Use 'create' operation to create new files.` }; } // Read existing content const existingContent = await this.fileService.readFile(filePath); // Append content to the end const newContent = existingContent + '\n' + content; // Write the updated content await this.fileService.saveFile({ path: filePath, content: newContent }); const existingLines = existingContent.split('\n').length; const newLines = content.split('\n').length; return { success: true, data: { operation: 'append', file_path: filePath, lines_added: newLines, total_lines: existingLines + newLines, content_added: content.length }, message: `Successfully appended ${newLines} lines to ${filePath}. Total lines: ${existingLines + newLines}.` }; } catch (error) { return { success: false, error: `Failed to append to file: ${error.message}` }; } } /** * Detect if a file is a cinematic script based on path and content */ isCinematicScript(filePath, content) { // Check file path patterns const fileName = path.basename(filePath).toLowerCase(); const pathPatterns = [ 'script', 'scene', 'film', 'movie', 'cinematic', 'screenplay', 'short_film' ]; const hasScriptPath = pathPatterns.some(pattern => fileName.includes(pattern)); // Check content patterns const contentPatterns = [ /## SCENE \d+:/i, /### Scene Description/i, /### Visual Sketch Description/i, /\*\*Duration:\*\*/i, /\*\*Location:\*\*/i, /\*\*WIDE SHOT\*\*/i, /\*\*CLOSE-UP\*\*/i, /\*\*MEDIUM SHOT\*\*/i, /\*\*LIGHTING\*\*/i, /\*\*SOUND\*\*/i, /## Key Dialogue/i, /## Story Function/i ]; const hasScriptContent = contentPatterns.some(pattern => pattern.test(content)); // Must have either script-like path OR multiple script content patterns return hasScriptPath || (contentPatterns.filter(pattern => pattern.test(content)).length >= 3); } }