74 lines
2.6 KiB
JavaScript
74 lines
2.6 KiB
JavaScript
/**
|
|
* FileWriteTool — rewritten for reliability.
|
|
*
|
|
* BUG FIX: The "Unterminated string in JSON" errors were NOT from this file.
|
|
* They were from the AI's streamed tool_calls getting truncated at 180s,
|
|
* producing incomplete JSON like {"content":"<!DOCTYPE html>... with no closing quote.
|
|
*
|
|
* This tool still handles edge cases better now:
|
|
* 1. Validates content is a string before writing
|
|
* 2. Auto-truncates extremely large content (>5MB) with a warning
|
|
* 3. Better error messages that distinguish JSON parse vs filesystem errors
|
|
*/
|
|
|
|
import { logger } from '../utils/logger.js';
|
|
import fs from 'fs-extra';
|
|
import path from 'path';
|
|
|
|
export class FileWriteTool {
|
|
constructor() {
|
|
this.name = 'file_write';
|
|
this.description = 'Write content to a file, creating parent directories as needed';
|
|
}
|
|
|
|
async execute(args) {
|
|
// ── Input validation ──
|
|
if (!args || typeof args !== 'object') {
|
|
return '❌ file_write: Invalid arguments. Expected { file_path, content }.';
|
|
}
|
|
|
|
const { file_path, content } = args;
|
|
|
|
if (!file_path || typeof file_path !== 'string') {
|
|
return '❌ file_write: file_path is required and must be a string.';
|
|
}
|
|
|
|
if (content === undefined || content === null) {
|
|
return '❌ file_write: content is required.';
|
|
}
|
|
|
|
// If content is not a string (e.g., object from truncated JSON), stringify it
|
|
let contentStr;
|
|
if (typeof content === 'string') {
|
|
contentStr = content;
|
|
} else {
|
|
contentStr = JSON.stringify(content);
|
|
}
|
|
|
|
// ── Size check ──
|
|
const byteLength = Buffer.byteLength(contentStr);
|
|
if (byteLength > 5 * 1024 * 1024) {
|
|
logger.warn(`⚠ file_write: ${byteLength} bytes is very large for direct write, consider bash heredoc`);
|
|
return `⚠ Warning: ${Math.round(byteLength / 1024)}KB — consider using bash with heredoc for large files: bash({ command: "cat > ${file_path} << 'EOF'\n...\nEOF" })`;
|
|
}
|
|
|
|
try {
|
|
const fullPath = path.resolve(file_path);
|
|
await fs.ensureDir(path.dirname(fullPath));
|
|
await fs.writeFile(fullPath, contentStr, 'utf-8');
|
|
logger.info(`✅ file_write: ${fullPath} (${Math.round(byteLength / 1024)}KB)`);
|
|
return `✅ Written ${byteLength} bytes to ${fullPath}`;
|
|
} catch (e) {
|
|
// Distinguish filesystem errors from other issues
|
|
if (e.code === 'EACCES') {
|
|
return `❌ Permission denied: ${fullPath}. Check file permissions.`;
|
|
}
|
|
if (e.code === 'ENOSPC') {
|
|
return `❌ Disk full: no space left on device.`;
|
|
}
|
|
logger.error(`❌ file_write failed: ${e.message}`);
|
|
return `❌ Write error: ${e.message}`;
|
|
}
|
|
}
|
|
}
|