'use strict'; // Retry an async function with exponential backoff up to a max delay. // No attempt limit: keeps retrying on transient failures forever. // Transient = network errors, 5xx, 429. 4xx client errors are NOT retried by default. const TRANSIENT_NETWORK_CODES = new Set([ 'ECONNREFUSED', 'ECONNRESET', 'ETIMEDOUT', 'EAI_AGAIN', 'ENOTFOUND', 'EPIPE', 'ERR_SOCKET_TIMEOUT', 'ETIMEOUT', ]); function isRetryable(error){ // Network / DNS / socket errors if(error && error.code && TRANSIENT_NETWORK_CODES.has(error.code)) return true; // HTTP status codes worth retrying let status = Number(error && error.status) || Number(error && error.status_code); if(status >= 500 && status < 600) return true; if(status === 429) return true; if(status === 502 || status === 503 || status === 504) return true; // ResponseError from the ollama package carries status_code if(error && error.name === 'ResponseError' && status >= 500) return true; return false; } async function sleep(ms){ return new Promise(resolve => setTimeout(resolve, ms)); } async function withRetry(fn, options = {}){ let baseDelay = options.baseDelay || 1000; let maxDelay = options.maxDelay || 30000; let multiplier = options.multiplier || 2; let jitter = options.jitter || 0.2; let label = options.label || 'operation'; let shouldRetry = options.shouldRetry || isRetryable; let attempt = 0; let delay = baseDelay; while(true){ try{ return await fn(); }catch(error){ attempt++; if(!shouldRetry(error)){ console.error(`${label} attempt ${attempt} failed permanently:`, error.message); throw error; } console.error(`${label} attempt ${attempt} failed, retrying in ${delay}ms:`, error.message); let jittered = Math.round(delay * (1 + (Math.random() * 2 - 1) * jitter)); await sleep(jittered); delay = Math.min(delay * multiplier, maxDelay); } } } // Convenience: retry a fetch call. On success returns the Response. async function fetchWithRetry(url, options = {}, retryOptions = {}){ return await withRetry(async () => { let res = await fetch(url, options); if(!res.ok){ let error = new Error(`UpstreamError`); error.message = `Request to ${url.split('?')[0]} failed( ${res.status})`; error.status = res.status; throw error; } return res; }, { label: `fetch ${url.split('?')[0]}`, ...retryOptions }); } // Convenience: retry a fetch call and parse JSON response. async function fetchJSONWithRetry(url, options = {}, retryOptions = {}){ let res = await fetchWithRetry(url, options, retryOptions); return await res.json(); } module.exports = { withRetry, fetchWithRetry, fetchJSONWithRetry, isRetryable, };