Files
superdesign/src/tools/write-tool.ts
2025-08-27 17:30:13 +08:00

101 lines
3.5 KiB
TypeScript

import * as fs from 'fs';
import * as path from 'path';
import { tool } from 'ai';
import { z } from 'zod';
import { ExecutionContext } from '../types/agent';
import {
handleToolError,
validateWorkspacePath,
resolveWorkspacePath,
createSuccessResponse,
ToolResponse
} from './tool-utils';
/**
* Write tool result with metadata
*/
export interface WriteToolResult {
file_path: string;
absolute_path: string;
is_new_file: boolean;
lines_written: number;
bytes_written: number;
}
// Path validation is now handled by validateWorkspacePath in tool-utils
/**
* Create SuperDesign write tool with execution context
*/
export function createWriteTool(context: ExecutionContext) {
return tool({
description: 'Write content to a file in the SuperDesign workspace. Creates parent directories if needed.',
parameters: z.object({
file_path: z.string().describe('Path to the file to write to (relative to workspace root, or absolute path within workspace)'),
content: z.string().describe('Content to write to the file'),
create_dirs: z.boolean().optional().default(true).describe('Whether to create parent directories if they don\'t exist (default: true)')
}),
execute: async ({ file_path, content, create_dirs = true }): Promise<ToolResponse> => {
const startTime = Date.now();
try {
// Validate workspace path (handles both absolute and relative paths)
const pathError = validateWorkspacePath(file_path, context);
if (pathError) {
return pathError;
}
// Resolve absolute path within workspace
const absolutePath = resolveWorkspacePath(file_path, context);
context.outputChannel.appendLine(`[write] Writing to file: ${file_path}`);
// Check if target is a directory
if (fs.existsSync(absolutePath)) {
const stats = fs.lstatSync(absolutePath);
if (stats.isDirectory()) {
return handleToolError(`Target path is a directory, not a file: ${file_path}`, 'Path validation', 'validation');
}
}
// Create parent directories if needed and requested
if (create_dirs) {
const dirName = path.dirname(absolutePath);
if (!fs.existsSync(dirName)) {
fs.mkdirSync(dirName, { recursive: true });
context.outputChannel.appendLine(`[write] Created parent directories for: ${file_path}`);
}
}
// Determine if this is a new file or overwrite
const isNewFile = !fs.existsSync(absolutePath);
// Write the file
fs.writeFileSync(absolutePath, content, 'utf8');
const duration = Date.now() - startTime;
const lines = content.split('\n').length;
const size = Buffer.byteLength(content, 'utf8');
context.outputChannel.appendLine(`[write] ${isNewFile ? 'Created' : 'Updated'} file: ${file_path} (${lines} lines, ${size} bytes) in ${duration}ms`);
const result: WriteToolResult = {
file_path,
absolute_path: absolutePath,
is_new_file: isNewFile,
lines_written: lines,
bytes_written: size
};
return createSuccessResponse(result);
} catch (error) {
const duration = Date.now() - startTime;
const errorMessage = error instanceof Error ? error.message : String(error);
context.outputChannel.appendLine(`[write] Error writing file: ${errorMessage} (${duration}ms)`);
return handleToolError(error, 'Write tool execution', 'execution');
}
}
});
}