superaugment
Version:
Enterprise-grade MCP server with world-class C++ analysis, robust error handling, and production-ready architecture for VS Code Augment
296 lines • 10.1 kB
JavaScript
/**
* Base Tool Class for SuperAugment
*
* Provides a standardized foundation for all SuperAugment tools with
* unified error handling, logging, validation, and performance monitoring.
*/
import { z } from 'zod';
import { logger } from '../utils/logger.js';
import { SuperAugmentError, ToolExecutionError, ErrorCode, } from '../errors/ErrorTypes.js';
import { globalErrorHandler } from '../errors/ErrorHandler.js';
/**
* Abstract base class for all SuperAugment tools
*/
export class BaseTool {
configManager;
metrics;
maxExecutionTime;
maxMemoryUsage;
constructor(configManager, options = {}) {
this.configManager = configManager;
this.maxExecutionTime = options.maxExecutionTime || 300000; // 5 minutes default
this.maxMemoryUsage = options.maxMemoryUsage || 512 * 1024 * 1024; // 512MB default
this.metrics = {
totalExecutions: 0,
successfulExecutions: 0,
failedExecutions: 0,
averageExecutionTime: 0,
totalExecutionTime: 0,
peakMemoryUsage: 0,
};
}
/**
* Main execution method with full error handling and monitoring
*/
async execute(args) {
const context = {
startTime: new Date(),
toolName: this.name,
arguments: args,
requestId: this.generateRequestId(),
};
try {
// Pre-execution setup
await this.preExecute(context);
// Validate input
const validatedArgs = await this.validateInput(args);
// Execute with monitoring
const result = await this.executeWithMonitoring(validatedArgs, context);
// Post-execution cleanup
await this.postExecute(context, result);
return result;
}
catch (error) {
await this.handleExecutionError(error, context);
throw error; // Re-throw after handling
}
}
/**
* Validate input arguments using the tool's schema
*/
async validateInput(args) {
try {
return this.inputSchema.parse(args);
}
catch (error) {
throw new ToolExecutionError(`Invalid input arguments for tool '${this.name}': ${error instanceof Error ? error.message : 'Unknown validation error'}`, this.name, ErrorCode.TOOL_INVALID_ARGUMENTS, { additionalInfo: { validationError: error, providedArgs: args } }, error instanceof Error ? error : undefined);
}
}
/**
* Execute tool with performance monitoring and timeout
*/
async executeWithMonitoring(args, context) {
const startMemory = process.memoryUsage();
// Set up timeout
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => {
reject(new ToolExecutionError(`Tool '${this.name}' execution timed out after ${this.maxExecutionTime}ms`, this.name, ErrorCode.TOOL_TIMEOUT, { additionalInfo: { timeout: this.maxExecutionTime } }));
}, this.maxExecutionTime);
});
try {
// Execute with timeout
const result = await Promise.race([
this.executeInternal(args, context),
timeoutPromise
]);
// Check memory usage
const endMemory = process.memoryUsage();
const memoryUsed = endMemory.heapUsed - startMemory.heapUsed;
if (memoryUsed > this.maxMemoryUsage) {
logger.warn(`Tool '${this.name}' exceeded memory threshold`, {
memoryUsed,
maxMemoryUsage: this.maxMemoryUsage,
toolName: this.name,
});
}
// Update metrics
this.updateMetrics(context, true, memoryUsed);
// Add metadata to result
const executionTime = Date.now() - context.startTime.getTime();
return {
...result,
metadata: {
...result.metadata,
executionTime,
memoryUsage: memoryUsed,
},
};
}
catch (error) {
this.updateMetrics(context, false);
throw error;
}
}
/**
* Pre-execution hook for setup and validation
*/
async preExecute(context) {
logger.info(`Starting execution of tool '${this.name}'`, {
toolName: this.name,
requestId: context.requestId,
arguments: this.sanitizeArgsForLogging(context.arguments),
});
// Check if tool is in a healthy state
await this.healthCheck();
}
/**
* Post-execution hook for cleanup and logging
*/
async postExecute(context, _result) {
const executionTime = Date.now() - context.startTime.getTime();
logger.info(`Completed execution of tool '${this.name}'`, {
toolName: this.name,
requestId: context.requestId,
executionTime,
success: true,
});
}
/**
* Handle execution errors with proper context and recovery
*/
async handleExecutionError(error, context) {
const executionTime = Date.now() - context.startTime.getTime();
const errorContext = {
toolName: this.name,
additionalInfo: {
requestId: context.requestId,
executionTime,
arguments: this.sanitizeArgsForLogging(context.arguments),
},
};
// Use global error handler for consistent error processing
try {
await globalErrorHandler.handleError(error, errorContext);
}
catch (handledError) {
// Log the final error state
logger.error(`Tool '${this.name}' execution failed`, {
toolName: this.name,
requestId: context.requestId,
executionTime,
error: handledError instanceof SuperAugmentError ? handledError.toJSON() : handledError,
});
throw handledError;
}
}
/**
* Health check to ensure tool is ready for execution
*/
async healthCheck() {
// Check if configuration is available
if (!this.configManager) {
throw new ToolExecutionError(`Tool '${this.name}' is not properly configured`, this.name, ErrorCode.INVALID_CONFIGURATION);
}
// Subclasses can override this for specific health checks
}
/**
* Update tool execution metrics
*/
updateMetrics(context, success, memoryUsed = 0) {
const executionTime = Date.now() - context.startTime.getTime();
this.metrics.totalExecutions++;
this.metrics.totalExecutionTime += executionTime;
this.metrics.averageExecutionTime = this.metrics.totalExecutionTime / this.metrics.totalExecutions;
this.metrics.lastExecutionTime = new Date();
if (success) {
this.metrics.successfulExecutions++;
}
else {
this.metrics.failedExecutions++;
}
if (memoryUsed > this.metrics.peakMemoryUsage) {
this.metrics.peakMemoryUsage = memoryUsed;
}
}
/**
* Sanitize arguments for logging (remove sensitive data)
*/
sanitizeArgsForLogging(args) {
const sensitiveKeys = ['password', 'token', 'key', 'secret', 'auth'];
const sanitized = { ...args };
for (const key of Object.keys(sanitized)) {
if (sensitiveKeys.some(sensitive => key.toLowerCase().includes(sensitive))) {
sanitized[key] = '[REDACTED]';
}
}
return sanitized;
}
/**
* Generate unique request ID for tracking
*/
generateRequestId() {
return `${this.name}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
/**
* Get tool execution metrics
*/
getMetrics() {
return { ...this.metrics };
}
/**
* Reset tool metrics
*/
resetMetrics() {
this.metrics = {
totalExecutions: 0,
successfulExecutions: 0,
failedExecutions: 0,
averageExecutionTime: 0,
totalExecutionTime: 0,
peakMemoryUsage: 0,
};
}
/**
* Get tool health status
*/
async getHealthStatus() {
try {
await this.healthCheck();
return {
healthy: true,
metrics: this.getMetrics(),
};
}
catch (error) {
return {
healthy: false,
metrics: this.getMetrics(),
lastError: error instanceof Error ? error.message : 'Unknown error',
};
}
}
/**
* Utility method for creating standardized tool responses
*/
createResponse(text, additionalContent = [], metadata = {}) {
return {
content: [
{
type: 'text',
text,
},
...additionalContent,
],
metadata: {
executionTime: 0,
...metadata,
},
};
}
/**
* Utility method for creating error responses
*/
createErrorResponse(error, includeDetails = false) {
const userMessage = error.getUserMessage();
const content = [
{
type: 'text',
text: `❌ ${userMessage}`,
},
];
if (includeDetails && error.context.additionalInfo) {
content.push({
type: 'text',
text: `\n**Error Details:**\n${JSON.stringify(error.context.additionalInfo, null, 2)}`,
});
}
return {
content,
metadata: {
executionTime: 0,
warnings: [`Error: ${error.code} - ${error.severity}`],
},
};
}
}
//# sourceMappingURL=BaseTool.js.map