diff --git a/extensions/index.ts b/extensions/index.ts index d198e56..5066b77 100644 --- a/extensions/index.ts +++ b/extensions/index.ts @@ -37,6 +37,14 @@ const CONFIG_DEFAULTS: MemoryConfig = { timeoutMs: 30_000, }; +// Retry su errori transitori (429/5xx/timeout): backoff esponenziale + jitter +const MAX_RETRIES = 3; +const RETRY_BASE_MS = 500; + +function sleep(ms: number): Promise { + return new Promise((r) => setTimeout(r, ms)); +} + function loadConfig(): MemoryConfig { try { return { ...CONFIG_DEFAULTS, ...JSON.parse(fs.readFileSync(CONFIG_FILE, "utf8")) }; @@ -67,14 +75,41 @@ async function gatewayRequest( "X-API-Key": cfg.apiKey, }; if (idempotencyKey) headers["Idempotency-Key"] = idempotencyKey; - const res = await fetch(`${cfg.url}${route}`, { - method, - signal, - headers, - body: body ? JSON.stringify(body) : undefined, - }); - const data = await res.json().catch(() => ({})); - return { ok: res.ok, status: res.status, data }; + + let lastError: unknown = null; + for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) { + // timeout fallback: se pi non fornisce un signal, usa timeoutMs dalla config + const timeoutSignal = signal ?? AbortSignal.timeout(cfg.timeoutMs ?? 30_000); + try { + const res = await fetch(`${cfg.url}${route}`, { + method, + signal: timeoutSignal, + headers, + body: body ? JSON.stringify(body) : undefined, + }); + const data = await res.json().catch(() => ({})); + // retry solo su errori transitori (429/5xx), rispettando Retry-After + if ((res.status === 429 || res.status >= 500) && attempt < MAX_RETRIES) { + const retryAfter = res.headers.get("retry-after"); + const delay = retryAfter + ? Math.min(Number(retryAfter) * 1000 || RETRY_BASE_MS, 10_000) + : RETRY_BASE_MS * 2 ** attempt + Math.random() * 200; + await sleep(delay); + continue; + } + return { ok: res.ok, status: res.status, data }; + } catch (e) { + // annullamento utente: propaga, non ritentare + if (e instanceof Error && e.name === "AbortError") throw e; + // errore di rete/timeout: retry con backoff + lastError = e; + if (attempt < MAX_RETRIES) { + await sleep(RETRY_BASE_MS * 2 ** attempt + Math.random() * 200); + continue; + } + } + } + return { ok: false, status: 0, data: { error: "network_error", detail: String(lastError ?? "unknown") } }; } // ---------------------------------------------------------------------------