LLMProviders / scripts /fetch-utils.js
CrispStrobe
fix: resolve HTTP 408 timeouts and finalize technical metadata enrichment for MiniMax-M1 and others
e16bbaf
'use strict';
/**
* Robust fetch helper with retries, exponential backoff, and timeout.
*/
async function fetchRobust(url, options = {}) {
const { retries = 5, backoff = 1000, timeout = 120000, ...fetchOptions } = options;
let lastError;
for (let i = 0; i < retries; i++) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeout);
try {
const res = await fetch(url, {
...fetchOptions,
signal: controller.signal,
headers: {
'User-Agent': 'providers-benchmark-fetcher',
...fetchOptions.headers,
},
});
clearTimeout(timer);
if (res.ok) return res;
// Retry on transient status codes: 429 (Rate Limit), 408 (Timeout), and 5xx (Server Errors)
if (res.status === 429 || res.status === 408 || (res.status >= 500 && res.status < 600)) {
lastError = new Error(`HTTP ${res.status} from ${url}`);
if (i < retries - 1) {
const delay = backoff * Math.pow(2, i) + Math.random() * 1000;
process.stdout.write(`\n ⚠ ${lastError.message}. Retrying in ${Math.round(delay)}ms... (${i + 1}/${retries})\n`);
await new Promise((r) => setTimeout(r, delay));
continue;
}
} else {
// Don't retry on other 4xx errors (e.g. 404, 401, 403)
throw new Error(`HTTP ${res.status} from ${url}`);
}
} catch (err) {
clearTimeout(timer);
lastError = err;
const isTimeout = err.name === 'AbortError';
const isAuthOrNotFound = err.message.includes('401') || err.message.includes('404');
if (i < retries - 1 && !isAuthOrNotFound) {
const delay = backoff * Math.pow(2, i) + Math.random() * 1000;
const msg = isTimeout ? `Timeout after ${timeout}ms` : err.message;
process.stdout.write(`\n ⚠ Fetch error from ${url}: ${msg}. Retrying in ${Math.round(delay)}ms... (${i + 1}/${retries})\n`);
await new Promise((r) => setTimeout(r, delay));
continue;
}
}
}
throw lastError;
}
async function getJson(url, options = {}) {
const res = await fetchRobust(url, { ...options, headers: { Accept: 'application/json', ...options.headers } });
return res.json();
}
async function getText(url, options = {}) {
const res = await fetchRobust(url, options);
return res.text();
}
module.exports = {
fetchRobust,
getJson,
getText,
};