contaigents
Version:
Modular AI Content Ecosystem with Audio Generation
233 lines (232 loc) • 9.02 kB
JavaScript
import { BaseTool } from '../base/BaseTool.js';
import { IntelligentFileService } from '../../services/IntelligentFileService.js';
/**
* Tool for intelligent file writing with validation and backup
*/
export class WriteFileTool extends BaseTool {
constructor() {
const definition = {
name: 'write_file',
description: 'Write content to file with intelligent validation and backup options',
category: 'file',
parameters: [
{
name: 'path',
type: 'string',
description: 'Path to the file to write',
required: true
},
{
name: 'content',
type: 'string',
description: 'Content to write to the file',
required: true
},
{
name: 'mode',
type: 'string',
description: 'Write mode: replace, append, insert, or partial',
required: false,
default: 'replace'
},
{
name: 'startLine',
type: 'number',
description: 'Starting line for partial write',
required: false
},
{
name: 'endLine',
type: 'number',
description: 'Ending line for partial write',
required: false
},
{
name: 'insertAt',
type: 'number',
description: 'Line number to insert content at',
required: false
},
{
name: 'createBackup',
type: 'boolean',
description: 'Create backup before writing',
required: false,
default: true
},
{
name: 'validateSyntax',
type: 'boolean',
description: 'Validate syntax for code files',
required: false,
default: true
}
]
};
super(definition);
this.fileService = new IntelligentFileService();
}
async execute(parameters, context) {
try {
const { path, content, mode = 'replace', startLine, endLine, insertAt, createBackup = true, validateSyntax = true } = parameters;
// Validate file path
if (!this.isFilePathValid(path, context)) {
return {
success: false,
error: `Invalid file path: ${path}`
};
}
let result = {
operation: mode,
filePath: path
};
// Create backup if requested and file exists
if (createBackup) {
try {
const existingContent = await this.fileService.readFile(path);
const backupPath = `${path}.backup.${Date.now()}`;
await this.fileService.saveFile({ path: backupPath, content: existingContent });
result.backupPath = backupPath;
}
catch (error) {
// File doesn't exist, no backup needed
result.backupCreated = false;
}
}
// Validate syntax if requested
if (validateSyntax && this.isCodeFile(path)) {
const syntaxValidation = await this.validateSyntax(content, path);
if (!syntaxValidation.valid) {
return {
success: false,
error: `Syntax validation failed: ${syntaxValidation.errors.join(', ')}`,
data: { syntaxErrors: syntaxValidation.errors }
};
}
result.syntaxValid = true;
}
// Write content based on mode
switch (mode) {
case 'replace':
await this.fileService.saveFile({ path, content });
break;
case 'append':
await this.fileService.writeFilePartial(path, content, { append: true });
break;
case 'insert':
if (insertAt === undefined) {
return {
success: false,
error: 'insertAt parameter required for insert mode'
};
}
await this.fileService.writeFilePartial(path, content, { insertAt });
break;
case 'partial':
if (startLine === undefined || endLine === undefined) {
return {
success: false,
error: 'startLine and endLine parameters required for partial mode'
};
}
await this.fileService.writeFilePartial(path, content, { startLine, endLine });
break;
default:
return {
success: false,
error: `Unknown write mode: ${mode}`
};
}
// Get file stats after writing
const newContent = await this.fileService.readFile(path);
result.linesWritten = newContent.split('\n').length;
result.bytesWritten = Buffer.byteLength(newContent, 'utf8');
return {
success: true,
data: result,
metadata: {
operation: 'write',
timestamp: Date.now(),
mode
}
};
}
catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error occurred'
};
}
}
isFilePathValid(filePath, context) {
// Basic validation - no parent directory traversal, within project scope
if (filePath.includes('..') || filePath.includes('~')) {
return false;
}
// Check if within project scope (unless absolute path is explicitly allowed)
if (!filePath.startsWith('/') && !filePath.startsWith(context.projectPath)) {
return true; // Relative paths are generally OK
}
return true;
}
isCodeFile(filePath) {
const codeExtensions = ['.js', '.ts', '.py', '.json', '.html', '.css', '.jsx', '.tsx'];
const ext = filePath.substring(filePath.lastIndexOf('.'));
return codeExtensions.includes(ext.toLowerCase());
}
async validateSyntax(content, filePath) {
const ext = filePath.substring(filePath.lastIndexOf('.')).toLowerCase();
const errors = [];
try {
switch (ext) {
case '.json':
JSON.parse(content);
break;
case '.js':
case '.ts':
// Basic syntax check - look for common issues
if (this.hasUnmatchedBrackets(content)) {
errors.push('Unmatched brackets detected');
}
break;
default:
// No specific validation for other file types
break;
}
}
catch (error) {
errors.push(error instanceof Error ? error.message : 'Syntax error');
}
return {
valid: errors.length === 0,
errors
};
}
hasUnmatchedBrackets(content) {
const brackets = { '(': ')', '[': ']', '{': '}' };
const stack = [];
for (const char of content) {
if (char in brackets) {
stack.push(char);
}
else if (Object.values(brackets).includes(char)) {
const last = stack.pop();
if (!last || brackets[last] !== char) {
return true;
}
}
}
return stack.length > 0;
}
getExamples() {
return [
'write_file({"path": "new-file.md", "content": "# Hello World"})',
'write_file({"path": "config.json", "content": "{}", "validateSyntax": true})',
'write_file({"path": "app.js", "content": "console.log(\'test\');", "mode": "append"})',
'write_file({"path": "README.md", "content": "New section", "mode": "insert", "insertAt": 10})'
];
}
getEstimatedExecutionTime() {
return 3000; // 3 seconds for validation and writing
}
}