vibe-coder-mcp
Version:
Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.
273 lines (272 loc) • 10.5 kB
JavaScript
import logger from '../../../logger.js';
export class TimeoutManager {
static instance = null;
config = null;
constructor() { }
static getInstance() {
if (!TimeoutManager.instance) {
TimeoutManager.instance = new TimeoutManager();
}
return TimeoutManager.instance;
}
static isInstanceInitialized() {
return TimeoutManager.instance !== null && TimeoutManager.instance.isInitialized();
}
initialize(config) {
this.config = config;
logger.debug('TimeoutManager initialized with configuration');
}
isInitialized() {
return this.config !== null;
}
getTimeout(operation) {
if (!this.config) {
const fallbacks = {
taskExecution: 300000,
taskDecomposition: 600000,
recursiveTaskDecomposition: 720000,
taskRefinement: 180000,
agentCommunication: 30000,
llmRequest: 60000,
fileOperations: 10000,
databaseOperations: 15000,
networkOperations: 20000
};
logger.warn({ operation }, 'Using fallback timeout value - config not initialized');
return fallbacks[operation];
}
return this.config.timeouts[operation];
}
getComplexityAdjustedTimeout(operation, complexity, estimatedHours) {
const baseTimeout = this.getTimeout(operation);
const complexityMultipliers = {
simple: 1.0,
moderate: 1.5,
complex: 2.0,
critical: 3.0
};
let adjustedTimeout = baseTimeout * complexityMultipliers[complexity];
if (operation === 'taskExecution' && estimatedHours) {
const hourMultiplier = Math.max(1.0, estimatedHours / 2);
adjustedTimeout = Math.max(adjustedTimeout, baseTimeout * hourMultiplier);
}
const maxTimeout = operation === 'taskExecution' ? 14400000 : baseTimeout * 5;
return Math.min(adjustedTimeout, maxTimeout);
}
getRetryConfig() {
if (!this.config) {
logger.warn('Using fallback retry config - config not initialized');
return {
maxRetries: 3,
backoffMultiplier: 2.0,
initialDelayMs: 1000,
maxDelayMs: 30000,
enableExponentialBackoff: true
};
}
return this.config.retryPolicy;
}
getComplexityAdjustedRetryConfig(complexity) {
const baseConfig = this.getRetryConfig();
const complexityAdjustments = {
simple: { maxRetries: baseConfig.maxRetries, backoffMultiplier: baseConfig.backoffMultiplier },
moderate: { maxRetries: baseConfig.maxRetries + 1, backoffMultiplier: baseConfig.backoffMultiplier * 0.9 },
complex: { maxRetries: baseConfig.maxRetries + 2, backoffMultiplier: baseConfig.backoffMultiplier * 0.8 },
critical: { maxRetries: baseConfig.maxRetries + 3, backoffMultiplier: baseConfig.backoffMultiplier * 0.7 }
};
const adjustment = complexityAdjustments[complexity];
return {
...baseConfig,
maxRetries: Math.min(adjustment.maxRetries, 10),
backoffMultiplier: Math.max(adjustment.backoffMultiplier, 1.2),
maxDelayMs: Math.min(baseConfig.maxDelayMs * 2, 120000)
};
}
async executeWithTimeout(operation, operationFn, customTimeout, customRetryConfig) {
const timeout = customTimeout || this.getTimeout(operation);
const retryConfig = { ...this.getRetryConfig(), ...customRetryConfig };
let retryCount = 0;
let lastError;
const startTime = Date.now();
while (retryCount <= retryConfig.maxRetries) {
try {
const result = await this.executeWithTimeoutOnce(operationFn, timeout);
if (result.success) {
const duration = Date.now() - startTime;
logger.debug({
operation,
duration,
retryCount,
timeout
}, 'Operation completed successfully');
return {
success: true,
data: result.data,
timedOut: false,
duration,
retryCount
};
}
lastError = result.error;
if (result.timedOut && retryCount < retryConfig.maxRetries) {
const delay = this.calculateDelay(retryCount, retryConfig);
logger.warn({
operation,
retryCount: retryCount + 1,
delay,
timeout,
error: lastError
}, 'Operation timed out, retrying');
await this.delay(delay);
}
}
catch (error) {
lastError = error instanceof Error ? error.message : 'Unknown error';
if (retryCount < retryConfig.maxRetries) {
const delay = this.calculateDelay(retryCount, retryConfig);
logger.warn({
operation,
retryCount: retryCount + 1,
delay,
error: lastError
}, 'Operation failed, retrying');
await this.delay(delay);
}
}
retryCount++;
}
const duration = Date.now() - startTime;
logger.error({
operation,
retryCount,
duration,
timeout,
error: lastError
}, 'Operation failed after all retries');
return {
success: false,
error: lastError || 'Operation failed after maximum retries',
timedOut: true,
duration,
retryCount
};
}
async executeWithTimeoutOnce(operationFn, timeout) {
return new Promise((resolve) => {
let completed = false;
const timeoutHandle = setTimeout(() => {
if (!completed) {
completed = true;
resolve({
success: false,
error: `Operation timed out after ${timeout}ms`,
timedOut: true
});
}
}, timeout);
operationFn()
.then((result) => {
if (!completed) {
completed = true;
clearTimeout(timeoutHandle);
resolve({
success: true,
data: result,
timedOut: false
});
}
})
.catch((error) => {
if (!completed) {
completed = true;
clearTimeout(timeoutHandle);
resolve({
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
timedOut: false
});
}
});
});
}
calculateDelay(retryCount, config) {
if (!config.enableExponentialBackoff) {
return config.initialDelayMs;
}
const delay = config.initialDelayMs * Math.pow(config.backoffMultiplier, retryCount);
return Math.min(delay, config.maxDelayMs);
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
createTimeoutPromise(operation, customTimeout) {
const timeout = customTimeout || this.getTimeout(operation);
return new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(`${operation} operation timed out after ${timeout}ms`));
}, timeout);
});
}
async raceWithTimeout(operation, operationPromise, customTimeout) {
const timeoutPromise = this.createTimeoutPromise(operation, customTimeout);
return Promise.race([operationPromise, timeoutPromise]);
}
getTimeoutSummary() {
const operations = [
'taskExecution',
'taskDecomposition',
'recursiveTaskDecomposition',
'taskRefinement',
'agentCommunication',
'llmRequest',
'fileOperations',
'databaseOperations',
'networkOperations'
];
const summary = {};
for (const operation of operations) {
summary[operation] = this.getTimeout(operation);
}
return summary;
}
validateTimeouts() {
const issues = [];
if (!this.config) {
issues.push('Timeout configuration not initialized');
return { valid: false, issues };
}
const timeouts = this.config.timeouts;
if (timeouts.taskExecution < 10000) {
issues.push('Task execution timeout is too low (< 10 seconds)');
}
if (timeouts.taskExecution > 3600000) {
issues.push('Task execution timeout is too high (> 1 hour)');
}
if (timeouts.llmRequest < 5000) {
issues.push('LLM request timeout is too low (< 5 seconds)');
}
if (timeouts.fileOperations < 1000) {
issues.push('File operations timeout is too low (< 1 second)');
}
const retry = this.config.retryPolicy;
if (retry.maxRetries < 0 || retry.maxRetries > 10) {
issues.push('Max retries should be between 0 and 10');
}
if (retry.backoffMultiplier < 1.0 || retry.backoffMultiplier > 5.0) {
issues.push('Backoff multiplier should be between 1.0 and 5.0');
}
if (retry.initialDelayMs < 100 || retry.initialDelayMs > 10000) {
issues.push('Initial delay should be between 100ms and 10 seconds');
}
return {
valid: issues.length === 0,
issues
};
}
}
export function isTimeoutManagerInitialized() {
return TimeoutManager.isInstanceInitialized();
}
export function getTimeoutManager() {
return TimeoutManager.getInstance();
}