Files
zCode-CLI-X/src/tools/FileWriteTool.js

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}`;
}
}
}