route-claudecode
Version:
Advanced routing and transformation system for Claude Code outputs to multiple AI providers
167 lines • 6.01 kB
JavaScript
;
/**
* 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