Skip to main content
Glama

GemForge-Gemini-Tools-MCP

rate-limiter.js7.56 kB
/** * Type guard for Axios errors */ function isAxiosError(error) { return error && error.isAxiosError === true; } import { MODELS } from '../config/models.js'; import { RATE_LIMITS, ERROR_MESSAGES, API } from '../config/constants.js'; /** * Manages rate limiting for Gemini API requests * Tracks request timestamps, implements exponential backoff, * and retries failed requests when appropriate */ export class RateLimitManager { /** * Creates a new RateLimitManager * @param isPaidTier Whether the user is on paid tier */ constructor(isPaidTier = false) { /** Map to track request timestamps by model ID */ this.requestTimestamps = new Map(); this.isPaidTier = isPaidTier; } /** * Checks if a request can be made for the specified model * @param modelId The model ID to check * @returns true if a request can be made, false otherwise */ canMakeRequest(modelId) { const model = MODELS[modelId]; if (!model) { console.error(`[RateLimitManager] Unknown model: ${modelId}`); return false; // Unknown model, cannot make request } // Get the rate limit for this model const rpm = model.freeRpm; // For now, use free tier RPM // Initialize tracking for this model if it doesn't exist if (!this.requestTimestamps.has(modelId)) { this.requestTimestamps.set(modelId, []); console.error(`[RateLimitManager] Initialized timestamps for ${modelId}.`); return true; } // Get timestamps and filter to those in the last minute const timestamps = this.requestTimestamps.get(modelId); const now = Date.now(); const oneMinuteAgo = now - 60 * 1000; // Prune timestamps older than 1 minute const recentTimestamps = timestamps.filter(t => t >= oneMinuteAgo); this.requestTimestamps.set(modelId, recentTimestamps); console.error(`[RateLimitManager] Checking canMakeRequest for ${modelId}: recentTimestamps=${JSON.stringify(recentTimestamps)}, count=${recentTimestamps.length}, rpm=${rpm}`); // Check if we're under the rate limit return recentTimestamps.length < rpm; } /** * Records a request for the specified model * @param modelId The model ID to record a request for */ recordRequest(modelId) { if (!this.requestTimestamps.has(modelId)) { this.requestTimestamps.set(modelId, []); } const timestamps = this.requestTimestamps.get(modelId); timestamps.push(Date.now()); // Prune old timestamps immediately after adding const now = Date.now(); const oneMinuteAgo = now - 60 * 1000; const prunedTimestamps = timestamps.filter(t => t >= oneMinuteAgo); this.requestTimestamps.set(modelId, prunedTimestamps); console.error(`[RateLimitManager] Recorded request for ${modelId}: prunedTimestamps=${JSON.stringify(prunedTimestamps)}, count=${prunedTimestamps.length}`); } /** * Calculates time until next request slot is available * @param modelId The model ID to check * @returns Time in milliseconds until next available request slot */ getTimeUntilNextSlot(modelId) { if (this.canMakeRequest(modelId)) { return 0; // Can make request now } const model = MODELS[modelId]; if (!model) { return 60000; // Unknown model, wait a minute } const rpm = model.freeRpm; const timestamps = this.requestTimestamps.get(modelId); if (timestamps.length < rpm) { return 0; // Not at limit yet } // Sort timestamps to find the oldest one timestamps.sort((a, b) => a - b); // When will the oldest timestamp expire? const oldestTimestamp = timestamps[timestamps.length - rpm]; const expiryTime = oldestTimestamp + 60000; // 1 minute after the request const now = Date.now(); return Math.max(0, expiryTime - now); } /** * Executes an API call with automatic retry for rate limiting * @param modelId The model ID being used * @param apiCall Function that makes the API call * @returns Promise that resolves to the API response */ async executeWithRetry(modelId, apiCall) { let retries = 0; while (retries <= RATE_LIMITS.MAX_RETRIES) { try { // Check if we can make a request if (!this.canMakeRequest(modelId) && retries === 0) { // If this is our first attempt and we're rate limited, // calculate wait time and throw error const waitTime = this.getTimeUntilNextSlot(modelId); const waitTimeSeconds = Math.ceil(waitTime / 1000); throw new Error(ERROR_MESSAGES.RATE_LIMIT(modelId, waitTimeSeconds)); } // Make the API call const response = await apiCall(); // Record the successful request this.recordRequest(modelId); return response; } catch (error) { // Check if it's a rate limit error from the API if (this.isRateLimitError(error) && retries < RATE_LIMITS.MAX_RETRIES) { // Calculate backoff time with exponential backoff + jitter const backoffTime = this.calculateBackoff(retries); // Log the rate limit issue console.error(`Rate limit hit for ${modelId}. Retrying in ${backoffTime}ms...`); // Wait for the backoff period await this.sleep(backoffTime); // Increment retry counter retries++; } else { // Not a rate limit error or we've reached max retries throw error; } } } // Should not reach here due to throw in the while loop, // but TypeScript requires a return statement throw new Error(`Maximum retries (${RATE_LIMITS.MAX_RETRIES}) reached for ${modelId}`); } /** * Checks if an error is a rate limit error * @param error The error to check * @returns true if it's a rate limit error, false otherwise */ isRateLimitError(error) { return isAxiosError(error) && error.response?.status === 429; } /** * Calculates backoff time with exponential backoff + jitter * @param retry Current retry count * @returns Backoff time in milliseconds */ calculateBackoff(retry) { // Calculate exponential backoff const exponentialDelay = Math.min(RATE_LIMITS.MAX_DELAY_MS, RATE_LIMITS.BASE_DELAY_MS * Math.pow(2, retry)); // Add jitter const jitter = RATE_LIMITS.JITTER_FACTOR; const randomFactor = 1 - jitter + (Math.random() * jitter * 2); return Math.floor(exponentialDelay * randomFactor); } /** * Sleep for a specified duration * @param ms Time to sleep in milliseconds * @returns Promise that resolves after the sleep duration */ sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } } /** * Create and export a singleton instance of the rate limit manager */ export const rateLimitManager = new RateLimitManager(API.IS_PAID_TIER); //# sourceMappingURL=rate-limiter.js.map

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/PV-Bhat/GemForge-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server