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