feat: rotate API instances on rate limits and errors

This commit is contained in:
Julien Maille 2026-01-18 23:21:32 +01:00
parent 661abbc00f
commit 024f44aa05

View file

@ -39,29 +39,21 @@ export class LosslessAPI {
throw new Error(`No API instances configured for type: ${type}`); throw new Error(`No API instances configured for type: ${type}`);
} }
const maxRetries = 3; const maxTotalAttempts = instances.length * 2; // Allow some retries across instances
let lastError = null; let lastError = null;
let instanceIndex = 0;
for (const baseUrl of instances) { for (let attempt = 1; attempt <= maxTotalAttempts; attempt++) {
const baseUrl = instances[instanceIndex % instances.length];
const url = baseUrl.endsWith('/') ? `${baseUrl}${relativePath.substring(1)}` : `${baseUrl}${relativePath}`; const url = baseUrl.endsWith('/') ? `${baseUrl}${relativePath.substring(1)}` : `${baseUrl}${relativePath}`;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try { try {
const response = await fetch(url, { signal: options.signal }); const response = await fetch(url, { signal: options.signal });
if (response.status === 429) { if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After'); console.warn(`Rate limit hit on ${baseUrl}. Trying next instance...`);
let waitTime = 2000 * attempt; // Default exponential backoff instanceIndex++;
await delay(500); // Small delay before trying next instance
if (retryAfter) {
const seconds = parseInt(retryAfter, 10);
if (!isNaN(seconds)) {
waitTime = seconds * 1000;
}
}
console.warn(`Rate limit hit. Waiting ${waitTime}ms before retry ${attempt}/${maxRetries}...`);
await delay(waitTime);
continue; continue;
} }
@ -71,34 +63,27 @@ export class LosslessAPI {
if (response.status === 401) { if (response.status === 401) {
let errorData = await response.clone().json(); let errorData = await response.clone().json();
if (errorData?.subStatus === 11002) { if (errorData?.subStatus === 11002) {
lastError = new Error(errorData?.userMessage || 'Authentication failed'); console.warn(`Auth failed on ${baseUrl}. Trying next instance...`);
if (attempt < maxRetries) { instanceIndex++;
await delay(200 * attempt);
continue; continue;
} }
} }
}
if (response.status >= 500 && attempt < maxRetries) { if (response.status >= 500) {
await delay(200 * attempt); console.warn(`Server error ${response.status} on ${baseUrl}. Trying next instance...`);
instanceIndex++;
continue; continue;
} }
lastError = new Error(`Request failed with status ${response.status}`); lastError = new Error(`Request failed with status ${response.status}`);
break; instanceIndex++;
} catch (error) { } catch (error) {
if (error.name === 'AbortError') { if (error.name === 'AbortError') throw error;
throw error;
}
lastError = error; lastError = error;
console.warn(`Network error on ${baseUrl}: ${error.message}. Trying next instance...`);
if (attempt < maxRetries) { instanceIndex++;
await delay(200 * attempt); await delay(200);
}
}
} }
} }