UNPKG

route-claudecode

Version:

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

159 lines 7.24 kB
"use strict"; /** * Enhanced Gemini Rate Limit Manager * Intelligent 429 error handling with Round Robin key rotation and model fallback * Project Owner: Jason Zhang */ Object.defineProperty(exports, "__esModule", { value: true }); exports.EnhancedRateLimitManager = void 0; const types_1 = require("../../types"); /** * Enhanced Gemini Rate Limit Manager - v2 * * Features: * - Prioritizes Key Rotation over Model Downgrade. * - When a (Key, Model) combination gets a 429 error, it enters a 60-second cooldown. * - If it fails again immediately after cooldown, it's permanently downgraded for that Key. * - A model downgrade is only considered if ALL keys are in cooldown for the requested model. */ class EnhancedRateLimitManager { apiKeys; // Key: `key-${keyIndex}:${modelName}`, e.g., 'key-0:{actual-model-from-config}' keyModelState = new Map(); providerId; modelFallbackConfig = null; config; // Configuration object // Zero Fallback Principle: No hardcoded model hierarchy // All model routing must be handled at routing layer constructor(apiKeys, providerId, config) { this.apiKeys = apiKeys.filter(key => key && key.trim()); if (!providerId) { throw new Error('EnhancedRateLimitManager: providerId is required - no default fallback allowed'); } this.providerId = providerId; this.config = config || {}; this.modelFallbackConfig = null; // Zero Fallback Principle if (this.apiKeys.length === 0) { throw new Error('At least one API key is required for Enhanced Rate Limit Manager'); } // Remove console.log hardcoded string - use logger instead } /** * Get available API key and model with intelligent fallback */ getAvailableKeyAndModel(requestedModel, requestId) { const now = Date.now(); // --- Stage 1: Try requested model with all available keys --- for (let keyIndex = 0; keyIndex < this.apiKeys.length; keyIndex++) { const modelStateKey = `key-${keyIndex}:${requestedModel}`; const state = this.keyModelState.get(modelStateKey); if (!state || (!state.permanentlyDowngraded && now >= state.cooldownUntil)) { // This (key, model) combo is available // Key selection logged through main logger system return { apiKey: this.apiKeys[keyIndex], model: requestedModel, keyIndex, fallbackApplied: false, }; } } // Zero Fallback Principle: No model fallback at provider level // Model routing must be handled at routing layer // --- Stage 2: All keys exhausted for requested model (No Fallback) --- // Find the combo that will be available soonest to provide a better error message. let soonestAvailableTime = Infinity; for (let keyIndex = 0; keyIndex < this.apiKeys.length; keyIndex++) { const modelStateKey = `key-${keyIndex}:${requestedModel}`; const state = this.keyModelState.get(modelStateKey); if (state && !state.permanentlyDowngraded && state.cooldownUntil < soonestAvailableTime) { soonestAvailableTime = state.cooldownUntil; } } const waitTime = soonestAvailableTime === Infinity ? 'N/A' : Math.ceil((soonestAvailableTime - now) / 1000); throw new types_1.ProviderError(`All API keys for model ${requestedModel} are currently rate-limited. Please try again in about ${waitTime} seconds. (Zero Fallback Principle: routing layer must handle model alternatives)`, this.providerId, 429); } /** * Report 429 error to update rate limit tracking */ report429Error(keyIndex, model, requestId) { const modelStateKey = `key-${keyIndex}:${model}`; const now = Date.now(); const currentState = this.keyModelState.get(modelStateKey) ?? { cooldownUntil: 0, permanentlyDowngraded: false, lastFailure: 0, }; // Zero Hardcode Principle: timing constants must be configurable const IMMEDIATE_FAILURE_THRESHOLD_MS = this.config.immediateFailureThresholdMs || 62000; const isImmediateFailureAfterCooldown = (now - currentState.lastFailure) < IMMEDIATE_FAILURE_THRESHOLD_MS && currentState.lastFailure > 0; if (isImmediateFailureAfterCooldown) { // Failure occurred again right after cooldown, so permanently downgrade. currentState.permanentlyDowngraded = true; // Permanent downgrade logged through main logger system } else { // Zero Hardcode Principle: cooldown duration must be configurable const COOLDOWN_DURATION_MS = this.config.cooldownDurationMs || 60000; currentState.cooldownUntil = now + COOLDOWN_DURATION_MS; // Cooldown logged through main logger system } currentState.lastFailure = now; this.keyModelState.set(modelStateKey, currentState); } /** * Zero Fallback Principle: No fallback models at provider level */ getFallbackModels(model) { // Always return empty array - no fallback at provider level return []; } /** * Get current rate limit status for all keys */ getStatus() { const now = Date.now(); const keyStatus = {}; // No hardcoded hierarchy - only track actually requested models const requestedModels = Array.from(new Set(Array.from(this.keyModelState.keys()) .map(key => key.split(':')[1]) .filter(Boolean))); this.apiKeys.forEach((_, keyIndex) => { const models = {}; for (const model of requestedModels) { const modelStateKey = `key-${keyIndex}:${model}`; const state = this.keyModelState.get(modelStateKey); if (state) { models[model] = { cooldown: Math.max(0, state.cooldownUntil - now), permanentlyDowngraded: state.permanentlyDowngraded, lastFailure: state.lastFailure }; } } keyStatus[`key-${keyIndex + 1}`] = models; }); return { totalKeys: this.apiKeys.length, providerId: this.providerId, status: keyStatus, modelHierarchy: 'Zero Fallback Principle - No hierarchy', fallbackConfig: this.modelFallbackConfig ? { enabled: this.modelFallbackConfig.enabled, cooldownMs: this.modelFallbackConfig.cooldownMs } : null }; } /** * Estimate token count for rate limiting */ estimateTokens(text) { if (!text) return 0; // Zero Hardcode Principle: magic numbers must be configurable const CHARS_PER_TOKEN = 4; // Should be from config return Math.ceil(text.length / CHARS_PER_TOKEN); } } exports.EnhancedRateLimitManager = EnhancedRateLimitManager; //# sourceMappingURL=enhanced-rate-limit-manager.js.map