codecrucible-synth
Version:
Production-Ready AI Development Platform with Multi-Voice Synthesis, Smithery MCP Integration, Enterprise Security, and Zero-Timeout Reliability
451 lines (425 loc) • 13.1 kB
text/typescript
/**
* Filesystem Tools for AI Model Function Calling
* Provides secure file system operations that AI models can use
*/
import {
Tool,
ToolCategory,
ToolMetadata,
ToolContext,
ToolResult,
} from './advanced-tool-orchestrator.js';
import { MCPServerManager } from '../../mcp-servers/mcp-server-manager.js';
import { Logger } from '../logger.js';
import { join, relative, resolve } from 'path';
const logger = new Logger('FilesystemTools');
export class FilesystemTools {
private mcpManager: MCPServerManager;
constructor(mcpManager: MCPServerManager) {
this.mcpManager = mcpManager;
}
/**
* Get all filesystem tools
*/
getTools(): Tool[] {
return [
this.createReadFileTool(),
this.createWriteFileTool(),
this.createListDirectoryTool(),
this.createFileStatsTool(),
this.createFindFilesTool(),
];
}
private createReadFileTool(): Tool {
return {
id: 'filesystem_read_file',
name: 'Read File',
description: 'Read the contents of a text file',
category: ToolCategory.FILE_SYSTEM,
inputSchema: {
type: 'object',
properties: {
filePath: {
type: 'string',
description: 'Path to the file to read (relative to project root)',
},
},
required: ['filePath'],
},
outputSchema: {
type: 'object',
properties: {
content: { type: 'string' },
filePath: { type: 'string' },
size: { type: 'number' },
},
},
execute: async (input: any, context: ToolContext): Promise<ToolResult> => {
try {
const absolutePath = this.resolvePath(input.filePath);
const content = await this.mcpManager.readFileSecure(absolutePath);
return {
toolId: 'filesystem_read_file',
success: true,
output: {
content,
filePath: input.filePath,
size: content.length,
},
metadata: {
executionTime: Date.now() - Date.now(),
memoryUsed: content.length,
cost: 1,
version: '1.0.0',
},
};
} catch (error) {
return {
toolId: 'filesystem_read_file',
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
metadata: {
executionTime: Date.now() - Date.now(),
memoryUsed: 0,
cost: 1,
version: '1.0.0',
},
};
}
},
metadata: this.createMetadata('filesystem', 1, 100, 0.95),
};
}
private createWriteFileTool(): Tool {
return {
id: 'filesystem_write_file',
name: 'Write File',
description: 'Write content to a text file',
category: ToolCategory.FILE_SYSTEM,
inputSchema: {
type: 'object',
properties: {
filePath: {
type: 'string',
description: 'Path to the file to write (relative to project root)',
},
content: {
type: 'string',
description: 'Content to write to the file',
},
},
required: ['filePath', 'content'],
},
outputSchema: {
type: 'object',
properties: {
success: { type: 'boolean' },
filePath: { type: 'string' },
bytesWritten: { type: 'number' },
},
},
execute: async (input: any, context: ToolContext): Promise<ToolResult> => {
try {
const absolutePath = this.resolvePath(input.filePath);
await this.mcpManager.writeFileSecure(absolutePath, input.content);
return {
toolId: 'filesystem_write_file',
success: true,
output: {
success: true,
filePath: input.filePath,
bytesWritten: input.content.length,
},
metadata: {
executionTime: 200,
memoryUsed: input.content.length,
cost: 2,
version: '1.0.0',
},
};
} catch (error) {
return {
toolId: 'filesystem_write_file',
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
metadata: {
executionTime: 100,
memoryUsed: 0,
cost: 2,
version: '1.0.0',
},
};
}
},
metadata: this.createMetadata('filesystem', 2, 200, 0.95, ['filesystem_read_file']),
};
}
private createListDirectoryTool(): Tool {
return {
id: 'filesystem_list_directory',
name: 'List Directory',
description: 'List files and directories in a given path',
category: ToolCategory.FILE_SYSTEM,
inputSchema: {
type: 'object',
properties: {
dirPath: {
type: 'string',
description:
'Path to the directory to list (relative to project root, defaults to current directory)',
default: '.',
},
},
},
outputSchema: {
type: 'object',
properties: {
files: {
type: 'array',
items: { type: 'string' },
},
dirPath: { type: 'string' },
},
},
execute: async (input: any, context: ToolContext): Promise<ToolResult> => {
try {
const dirPath = input.dirPath || '.';
const absolutePath = this.resolvePath(dirPath);
const files = await this.mcpManager.listDirectorySecure(absolutePath);
return {
toolId: 'filesystem_list_directory',
success: true,
output: {
files,
dirPath,
},
metadata: {
executionTime: 50,
memoryUsed: files.length * 50,
cost: 1,
version: '1.0.0',
},
};
} catch (error) {
return {
toolId: 'filesystem_list_directory',
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
metadata: {
executionTime: 50,
memoryUsed: 0,
cost: 1,
version: '1.0.0',
},
};
}
},
metadata: this.createMetadata('filesystem', 1, 50, 0.98),
};
}
private createFileStatsTool(): Tool {
return {
id: 'filesystem_file_stats',
name: 'Get File Stats',
description: 'Get detailed information about a file or directory',
category: ToolCategory.FILE_SYSTEM,
inputSchema: {
type: 'object',
properties: {
filePath: {
type: 'string',
description: 'Path to the file or directory',
},
},
required: ['filePath'],
},
outputSchema: {
type: 'object',
properties: {
exists: { type: 'boolean' },
isFile: { type: 'boolean' },
isDirectory: { type: 'boolean' },
size: { type: 'number' },
modified: { type: 'string' },
},
},
execute: async (input: any, context: ToolContext): Promise<ToolResult> => {
try {
const absolutePath = this.resolvePath(input.filePath);
const stats = await this.mcpManager.getFileStats(absolutePath);
return {
toolId: 'filesystem_file_stats',
success: true,
output: stats,
metadata: {
executionTime: 30,
memoryUsed: 100,
cost: 1,
version: '1.0.0',
},
};
} catch (error) {
return {
toolId: 'filesystem_file_stats',
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
metadata: {
executionTime: 30,
memoryUsed: 0,
cost: 1,
version: '1.0.0',
},
};
}
},
metadata: this.createMetadata('filesystem', 1, 30, 0.99),
};
}
private createFindFilesTool(): Tool {
return {
id: 'filesystem_find_files',
name: 'Find Files',
description: 'Find files matching a pattern in the project',
category: ToolCategory.FILE_SYSTEM,
inputSchema: {
type: 'object',
properties: {
pattern: {
type: 'string',
description: 'File pattern to search for (e.g., "*.ts", "src/**/*.js")',
},
directory: {
type: 'string',
description: 'Directory to search in (defaults to project root)',
default: '.',
},
},
required: ['pattern'],
},
outputSchema: {
type: 'object',
properties: {
files: {
type: 'array',
items: { type: 'string' },
},
count: { type: 'number' },
},
},
execute: async (input: any, context: ToolContext): Promise<ToolResult> => {
const startTime = Date.now();
try {
const directory = input.directory || '.';
const absolutePath = this.resolvePath(directory);
const files = await this.findFiles(absolutePath, input.pattern);
return {
toolId: 'filesystem_find_files',
success: true,
output: {
files,
count: files.length,
},
metadata: {
executionTime: Date.now() - startTime,
memoryUsed: files.length * 100,
cost: 3,
version: '1.0.0',
},
};
} catch (error) {
return {
toolId: 'filesystem_find_files',
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
metadata: {
executionTime: Date.now() - startTime,
memoryUsed: 0,
cost: 3,
version: '1.0.0',
},
};
}
},
metadata: this.createMetadata('filesystem', 3, 500, 0.9, ['filesystem_list_directory']),
};
}
private resolvePath(inputPath: string): string {
// Resolve relative to current working directory
if (inputPath.startsWith('./') || inputPath.startsWith('../') || !inputPath.startsWith('/')) {
return resolve(process.cwd(), inputPath);
}
return inputPath;
}
private async findFiles(directory: string, pattern: string): Promise<string[]> {
// Simple file finding implementation
// In a production system, you'd use a more sophisticated glob library
const files: string[] = [];
const items = await this.mcpManager.listDirectorySecure(directory);
for (const item of items) {
const itemPath = join(directory, item);
try {
const stats = await this.mcpManager.getFileStats(itemPath);
if (stats.isFile && this.matchesPattern(item, pattern)) {
files.push(relative(process.cwd(), itemPath));
} else if (stats.isDirectory && !item.startsWith('.')) {
// Recursively search subdirectories
const subFiles = await this.findFiles(itemPath, pattern);
files.push(...subFiles);
}
} catch (error) {
// Skip items we can't access
continue;
}
}
return files;
}
private matchesPattern(filename: string, pattern: string): boolean {
// Simple pattern matching - convert glob-like pattern to regex
const regexPattern = pattern.replace(/\./g, '\\.').replace(/\*/g, '.*').replace(/\?/g, '.');
const regex = new RegExp(`^${regexPattern}$`, 'i');
return regex.test(filename);
}
private createMetadata(
category: string,
cost: number,
latency: number,
reliability: number,
dependencies: string[] = []
): ToolMetadata {
return {
version: '1.0.0',
author: 'CodeCrucible Synth',
tags: [category, 'filesystem', 'file-operations'],
cost,
latency,
reliability,
dependencies,
conflictsWith: [],
capabilities: [
{
type: 'read',
scope: 'filesystem',
permissions: ['filesystem:read', 'path:resolve'],
},
{
type: 'write',
scope: 'filesystem',
permissions: ['filesystem:write', 'path:resolve'],
},
],
requirements: [{ type: 'resource', value: 'filesystem', optional: false }],
};
}
}
// Add getFileStats method to MCPServerManager if it doesn't exist
declare module '../../mcp-servers/mcp-server-manager.js' {
interface MCPServerManager {
getFileStats(filePath: string): Promise<{
exists: boolean;
isFile: boolean;
isDirectory: boolean;
size: number;
modified: string;
}>;
}
}