import * as plugins from './plugins.js'; import { createLocalToolExecutionContext, formatToolOutput, type IToolExecutionContext, } from './tool.context.js'; export interface IFilesystemToolOptions { /** Restrict file access to this directory. Default: process.cwd() */ rootDir?: string; /** Execution context. Defaults to a local Node.js context. */ context?: IToolExecutionContext; /** Include delete_file. Default: true for compatibility. */ includeDelete?: boolean; /** Maximum output lines before truncating. */ maxLines?: number; /** Maximum output bytes before truncating. */ maxBytes?: number; } export interface ICreateFilesystemToolsOptions { /** Include delete_file. Default: true. */ includeDelete?: boolean; /** Maximum output lines before truncating. */ maxLines?: number; /** Maximum output bytes before truncating. */ maxBytes?: number; } export function createFilesystemTools(context: IToolExecutionContext, options: ICreateFilesystemToolsOptions = {}): plugins.ToolSet { const truncate = (output: unknown) => plugins.truncateOutput(formatToolOutput(output), { maxLines: options.maxLines, maxBytes: options.maxBytes, }).content; const tools: plugins.ToolSet = { read_file: plugins.tool({ description: 'Read a UTF-8 file in the active workspace. Paths may be absolute or relative to the workspace root.', inputSchema: plugins.z.object({ path: plugins.z.string().describe('File path'), startLine: plugins.z .number() .optional() .describe('First line (1-indexed, inclusive)'), endLine: plugins.z .number() .optional() .describe('Last line (1-indexed, inclusive)'), }), execute: async ({ path: filePath, startLine, endLine, }: { path: string; startLine?: number; endLine?: number; }) => { if (!context.fs) { throw new Error('Filesystem tools are not available in this execution context.'); } return truncate(await context.fs.readFile(filePath, { startLine, endLine })); }, }), write_file: plugins.tool({ description: 'Write UTF-8 content to a file in the active workspace. Creates parent directories and overwrites existing content. Requires host permission when configured.', inputSchema: plugins.z.object({ path: plugins.z.string().describe('File path'), content: plugins.z.string().describe('Complete file content to write'), }), execute: async ({ path: filePath, content }: { path: string; content: string }) => { if (!context.fs) { throw new Error('Filesystem tools are not available in this execution context.'); } await context.requestPermission?.({ type: 'write', title: 'Write file', metadata: { path: filePath, bytes: Buffer.byteLength(content, 'utf8') }, }); const result = await context.fs.writeFile(filePath, content); return truncate(result ?? `Written ${Buffer.byteLength(content, 'utf8')} bytes to ${filePath}`); }, }), list_directory: plugins.tool({ description: 'List files and directories in the active workspace. Paths may be absolute or relative to the workspace root.', inputSchema: plugins.z.object({ path: plugins.z.string().default('.').describe('Directory path to list'), recursive: plugins.z .boolean() .optional() .describe('List recursively. Default: false'), }), execute: async ({ path: directoryPath, recursive }: { path: string; recursive?: boolean }) => { if (!context.fs) { throw new Error('Filesystem tools are not available in this execution context.'); } const result = await context.fs.listDirectory(directoryPath, { recursive }); return truncate(Array.isArray(result) ? result.join('\n') : result); }, }), }; if (options.includeDelete !== false) { tools.delete_file = plugins.tool({ description: 'Delete a file or empty directory in the active workspace. Requires host permission when configured.', inputSchema: plugins.z.object({ path: plugins.z.string().describe('Path to delete'), }), execute: async ({ path: targetPath }: { path: string }) => { if (!context.fs?.deletePath) { throw new Error('Deleting files is not available in this execution context.'); } await context.requestPermission?.({ type: 'delete', title: 'Delete file', metadata: { path: targetPath }, }); const result = await context.fs.deletePath(targetPath); return truncate(result ?? `Deleted ${targetPath}`); }, }); } return tools; } export function filesystemTool(options?: IFilesystemToolOptions): plugins.ToolSet { const context = options?.context ?? createLocalToolExecutionContext({ rootDir: options?.rootDir }); return createFilesystemTools(context, options); }