UNPKG

@just-every/ensemble

Version:

LLM provider abstraction layer with unified streaming interface

124 lines 3.89 kB
export const DEFAULT_RETRY_OPTIONS = { maxRetries: 3, initialDelay: 1000, maxDelay: 30000, backoffMultiplier: 2, retryableErrors: new Set([ 'ECONNRESET', 'ETIMEDOUT', 'ENOTFOUND', 'ECONNREFUSED', 'EPIPE', 'EHOSTUNREACH', 'EAI_AGAIN', 'ENETUNREACH', 'ECONNABORTED', 'ESOCKETTIMEDOUT', ]), retryableStatusCodes: new Set([ 408, 429, 500, 502, 503, 504, 522, 524, ]), }; export function isRetryableError(error, options = {}) { const opts = { ...DEFAULT_RETRY_OPTIONS, ...options }; if (error.code && opts.retryableErrors.has(error.code)) { return true; } if (error.status && opts.retryableStatusCodes.has(error.status)) { return true; } if (error.message && (error.message.includes('fetch failed') || error.message.includes('network error') || error.message.includes('ECONNRESET') || error.message.includes('ETIMEDOUT'))) { return true; } if (error.message && (error.message.includes('Incomplete JSON segment') || error.message.includes('Connection error') || error.message.includes('Request timeout'))) { return true; } return false; } export function calculateDelay(attempt, options = {}) { const opts = { ...DEFAULT_RETRY_OPTIONS, ...options }; const baseDelay = opts.initialDelay * Math.pow(opts.backoffMultiplier, attempt - 1); const delay = Math.min(baseDelay, opts.maxDelay); const jitter = delay * 0.1 * (Math.random() * 2 - 1); return Math.round(delay + jitter); } export async function retryWithBackoff(fn, options = {}) { const opts = { ...DEFAULT_RETRY_OPTIONS, ...options }; let lastError; for (let attempt = 1; attempt <= opts.maxRetries + 1; attempt++) { try { return await fn(); } catch (error) { lastError = error; if (!isRetryableError(error, opts) || attempt > opts.maxRetries) { throw error; } const delay = calculateDelay(attempt, opts); if (opts.onRetry) { opts.onRetry(error, attempt); } await new Promise(resolve => setTimeout(resolve, delay)); } } throw lastError; } export async function* retryStreamWithBackoff(createStream, options = {}) { const opts = { ...DEFAULT_RETRY_OPTIONS, ...options }; let lastError; const buffer = []; let hasStartedYielding = false; for (let attempt = 1; attempt <= opts.maxRetries + 1; attempt++) { try { const stream = createStream(); if (hasStartedYielding) { let skipCount = buffer.length; for await (const item of stream) { if (skipCount > 0) { skipCount--; continue; } yield item; } } else { for await (const item of stream) { buffer.push(item); hasStartedYielding = true; yield item; } } return; } catch (error) { lastError = error; if (hasStartedYielding) { throw error; } if (!isRetryableError(error, opts) || attempt > opts.maxRetries) { throw error; } const delay = calculateDelay(attempt, opts); if (opts.onRetry) { opts.onRetry(error, attempt); } await new Promise(resolve => setTimeout(resolve, delay)); } } throw lastError; } //# sourceMappingURL=retry_handler.js.map