UNPKG

route-claudecode

Version:

Advanced routing and transformation system for Claude Code outputs to multiple AI providers

167 lines 6.01 kB
"use strict"; /** * CodeWhisperer 智能重试管理器 * 基于 AIClient-2-API 的指数退避和错误分类重试策略 * 项目所有者: Jason Zhang */ Object.defineProperty(exports, "__esModule", { value: true }); exports.RetryManager = void 0; const enhanced_auth_config_1 = require("./enhanced-auth-config"); class RetryManager { config; logger; constructor(config = {}, logger) { this.config = { ...enhanced_auth_config_1.DEFAULT_RETRY_CONFIG, ...config }; this.logger = logger; } /** * 执行带重试的异步操作 */ async executeWithRetry(operation, context) { const ctx = { attempt: 0, maxRetries: this.config.maxRetries, startTime: Date.now(), ...context }; return this.attemptOperation(operation, ctx); } /** * 尝试执行操作 */ async attemptOperation(operation, context) { try { context.attempt++; this.log('debug', `Attempt ${context.attempt}/${context.maxRetries + 1} starting`); const result = await operation(); if (context.attempt > 1) { const duration = Date.now() - context.startTime; this.log('info', `Operation succeeded after ${context.attempt} attempts (${duration}ms)`); } return result; } catch (error) { context.lastError = error; if (!this.shouldRetry(error, context)) { this.log('error', `Operation failed after ${context.attempt} attempts: ${error instanceof Error ? error.message : String(error)}`); throw error; } const delay = this.calculateDelay(context, error); this.log('warn', `Attempt ${context.attempt} failed (${error instanceof Error ? error.message : String(error)}). ` + `Retrying in ${delay}ms... (${context.maxRetries - context.attempt} attempts remaining)`); await this.sleep(delay); return this.attemptOperation(operation, context); } } /** * 判断是否应该重试 */ shouldRetry(error, context) { // 已达到最大重试次数 if (context.attempt > context.maxRetries) { return false; } // 检查是否为可重试的状态码 if (error.statusCode && this.config.retryableStatuses.includes(error.statusCode)) { return true; } // 检查是否明确标记为可重试 if (error.isRetryable === true) { return true; } // 检查是否明确标记为不可重试 if (error.isRetryable === false) { return false; } // 默认重试网络相关错误 const retryableMessages = [ 'ECONNRESET', 'ECONNREFUSED', 'ETIMEDOUT', 'ENOTFOUND', 'EAI_AGAIN', 'socket hang up', 'network timeout' ]; const errorMessage = error.message?.toLowerCase() || ''; return retryableMessages.some(msg => errorMessage.includes(msg.toLowerCase())); } /** * 计算延迟时间 */ calculateDelay(context, error) { // 如果服务器指定了重试延迟时间(如 429 响应的 Retry-After 头) if (error.retryAfter && error.retryAfter > 0) { return Math.min(error.retryAfter * 1000, 60000); // 最大60秒 } // 指数退避算法 const exponentialDelay = this.config.baseDelay * Math.pow(this.config.backoffMultiplier, context.attempt - 1); // 添加抖动以避免惊群效应 const jitter = Math.random() * 0.1 * exponentialDelay; const totalDelay = exponentialDelay + jitter; // 限制最大延迟时间 return Math.min(totalDelay, 30000); // 最大30秒 } /** * 睡眠指定毫秒数 */ sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * 创建带重试信息的错误 */ static createRetryableError(message, statusCode, isRetryable, retryAfter) { const error = new Error(message); error.statusCode = statusCode; error.isRetryable = isRetryable; error.retryAfter = retryAfter; return error; } /** * 从 HTTP 响应创建重试错误 */ static createRetryableErrorFromResponse(error, defaultMessage = 'Request failed') { const statusCode = error.response?.status || error.status; const retryAfter = this.parseRetryAfter(error.response?.headers?.['retry-after']); let isRetryable; // 根据状态码判断是否可重试 if (statusCode) { if (statusCode === 403) { isRetryable = true; // 403 通常需要刷新 token } else if (statusCode >= 500) { isRetryable = true; // 服务器错误通常可重试 } else if (statusCode === 429) { isRetryable = true; // 限流错误可重试 } else if (statusCode >= 400 && statusCode < 500) { isRetryable = false; // 其他客户端错误通常不可重试 } } return this.createRetryableError(error.message || defaultMessage, statusCode, isRetryable, retryAfter); } /** * 解析 Retry-After 头 */ static parseRetryAfter(retryAfter) { if (!retryAfter) return undefined; const seconds = parseInt(retryAfter, 10); return isNaN(seconds) ? undefined : seconds; } /** * 日志输出方法 */ log(level, message) { if (this.logger) { this.logger[level]?.(message); } else { console.log(`[RetryManager] ${level.toUpperCase()}: ${message}`); } } } exports.RetryManager = RetryManager; //# sourceMappingURL=retry-manager.js.map