/** * Modular Tool System for Terminal Execution * Inspired by AGIAgent, AutoGen, and ReAct patterns * * Architecture: * - Base Tool interface for extensibility * - Tool Registry for managing available tools * - Enhanced Intent Analysis for smart tool selection * - Structured error handling and output formatting */ /** * Base Tool Interface * All tools must extend this class */ class BaseTool { /** * @param {Object} config - Tool configuration * @param {string} config.name - Unique tool name * @param {string} config.description - What this tool does * @param {Array} config.parameters - Parameter definitions * @param {Object} config.options - Tool-specific options */ constructor(config) { if (!config.name) { throw new Error('Tool must have a name'); } this.name = config.name; this.description = config.description || ''; this.parameters = config.parameters || []; this.options = config.options || {}; this.enabled = config.enabled !== false; } /** * Execute the tool with given parameters * Must be implemented by subclasses * * @param {Object} params - Execution parameters * @returns {Promise} Execution result */ async execute(params) { throw new Error(`Tool ${this.name} must implement execute() method`); } /** * Validate parameters before execution * * @param {Object} params - Parameters to validate * @returns {Object} Validation result with valid flag and errors array */ validate(params) { const errors = []; for (const param of this.parameters) { const value = params[param.name]; // Check required parameters if (param.required && value === undefined) { errors.push(`Required parameter '${param.name}' is missing`); continue; } // Type validation if (value !== undefined && param.type) { const actualType = Array.isArray(value) ? 'array' : typeof value; if (actualType !== param.type) { errors.push( `Parameter '${param.name}' should be ${param.type}, got ${actualType}` ); } } } return { valid: errors.length === 0, errors }; } /** * Get tool metadata */ getMetadata() { return { name: this.name, description: this.description, parameters: this.parameters, enabled: this.enabled }; } } /** * Tool Result Structure */ class ToolResult { /** * @param {Object} result * @param {boolean} result.success - Whether execution succeeded * @param {*} result.data - Result data * @param {string} result.output - Formatted output string * @param {Error} result.error - Error if failed * @param {Object} result.metadata - Additional metadata */ constructor(result) { this.success = result.success !== false; this.data = result.data; this.output = result.output || ''; this.error = result.error; this.metadata = result.metadata || {}; this.timestamp = new Date().toISOString(); } /** * Create a success result */ static success(data, output = '', metadata = {}) { return new ToolResult({ success: true, data, output, metadata }); } /** * Create a failure result */ static failure(error, output = '', metadata = {}) { return new ToolResult({ success: false, error, output, metadata }); } toJSON() { return { success: this.success, data: this.data, output: this.output, error: this.error ? this.error.message : null, metadata: this.metadata, timestamp: this.timestamp }; } } /** * Tool Registry * Manages available tools and their execution */ class ToolRegistry { constructor() { this.tools = new Map(); this.middlewares = []; this.executionHistory = []; this.maxHistorySize = 100; } /** * Register a new tool * * @param {BaseTool} tool - Tool instance to register */ register(tool) { if (!(tool instanceof BaseTool)) { throw new Error('Tool must extend BaseTool'); } if (this.tools.has(tool.name)) { throw new Error(`Tool '${tool.name}' is already registered`); } this.tools.set(tool.name, tool); return this; } /** * Unregister a tool * * @param {string} name - Tool name to unregister */ unregister(name) { return this.tools.delete(name); } /** * Get a tool by name * * @param {string} name - Tool name * @returns {BaseTool|null} */ get(name) { return this.tools.get(name) || null; } /** * Check if a tool exists and is enabled * * @param {string} name - Tool name */ has(name) { const tool = this.tools.get(name); return tool && tool.enabled; } /** * List all available tools * * @param {Object} options - Listing options * @param {boolean} options.includeDisabled - Include disabled tools */ list(options = {}) { const tools = Array.from(this.tools.values()); if (!options.includeDisabled) { return tools.filter(t => t.enabled); } return tools; } /** * List tools metadata */ listMetadata() { return this.list().map(tool => tool.getMetadata()); } /** * Execute a tool by name * * @param {string} name - Tool name * @param {Object} params - Execution parameters * @returns {Promise} */ async execute(name, params = {}) { const startTime = Date.now(); try { // Get tool const tool = this.get(name); if (!tool) { throw new Error(`Tool '${name}' not found or disabled`); } // Validate parameters const validation = tool.validate(params); if (!validation.valid) { throw new Error(`Parameter validation failed: ${validation.errors.join(', ')}`); } // Run before middlewares for (const mw of this.middlewares) { if (mw.before) { await mw.before(name, params); } } // Execute tool let result = await tool.execute(params); // Run after middlewares for (const mw of this.middlewares) { if (mw.after) { result = await mw.after(name, params, result) || result; } } // Record history this.recordExecution({ tool: name, params, result: result.toJSON(), duration: Date.now() - startTime, timestamp: new Date().toISOString() }); return result; } catch (error) { const result = ToolResult.failure(error, error.message); // Record failure this.recordExecution({ tool: name, params, result: result.toJSON(), duration: Date.now() - startTime, timestamp: new Date().toISOString() }); return result; } } /** * Add middleware for execution hooks * * @param {Object} middleware - Middleware with before/after hooks */ use(middleware) { this.middlewares.push(middleware); return this; } /** * Record execution in history */ recordExecution(record) { this.executionHistory.push(record); // Limit history size if (this.executionHistory.length > this.maxHistorySize) { this.executionHistory.shift(); } } /** * Get execution history */ getHistory(options = {}) { let history = this.executionHistory; if (options.tool) { history = history.filter(r => r.tool === options.tool); } if (options.limit) { history = history.slice(-options.limit); } return history; } /** * Clear execution history */ clearHistory() { this.executionHistory = []; } /** * Get statistics */ getStats() { const stats = { totalExecutions: this.executionHistory.length, toolUsage: {}, successRate: 0, avgDuration: 0 }; let successCount = 0; let totalDuration = 0; for (const record of this.executionHistory) { stats.toolUsage[record.tool] = (stats.toolUsage[record.tool] || 0) + 1; if (record.result.success) { successCount++; } totalDuration += record.duration; } if (stats.totalExecutions > 0) { stats.successRate = (successCount / stats.totalExecutions * 100).toFixed(2); stats.avgDuration = (totalDuration / stats.totalExecutions).toFixed(2); } return stats; } } /** * Export all classes */ module.exports = { BaseTool, ToolResult, ToolRegistry };