From 0aff9a502c5c74d89f18998f8b25cf887cc9e5b8 Mon Sep 17 00:00:00 2001 From: decolua Date: Fri, 24 Apr 2026 10:07:08 +0700 Subject: [PATCH] fix: enhance retry logic and configuration for HTTP status codes --- open-sse/config/runtimeConfig.js | 5 +++-- open-sse/executors/base.js | 27 +++++++++++++++++---------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/open-sse/config/runtimeConfig.js b/open-sse/config/runtimeConfig.js index 11108183..369ab2d7 100644 --- a/open-sse/config/runtimeConfig.js +++ b/open-sse/config/runtimeConfig.js @@ -45,8 +45,9 @@ export const RETRY_CONFIG = { // Backward compat: if value is a number, treated as attempts with RETRY_CONFIG.delayMs export const DEFAULT_RETRY_CONFIG = { 429: { attempts: 0, delayMs: 0 }, - 502: { attempts: 1, delayMs: 3000 }, - 503: { attempts: 1, delayMs: 2000 } + 502: { attempts: 3, delayMs: 3000 }, + 503: { attempts: 3, delayMs: 2000 }, + 504: { attempts: 2, delayMs: 3000 } }; // Normalize a retry entry to { attempts, delayMs } diff --git a/open-sse/executors/base.js b/open-sse/executors/base.js index 7dcbc77e..2ba22197 100644 --- a/open-sse/executors/base.js +++ b/open-sse/executors/base.js @@ -104,10 +104,20 @@ export class BaseExecutor { let lastError = null; let lastStatus = 0; const retryAttemptsByUrl = {}; - + // Merge default retry config with provider-specific config const retryConfig = { ...DEFAULT_RETRY_CONFIG, ...this.config.retry }; + // Schedule retry via retryConfig[statusKey]. Returns true when caller should `urlIndex--; continue` + const tryRetry = async (urlIndex, statusKey, reason) => { + const { attempts, delayMs } = resolveRetryEntry(retryConfig[statusKey]); + if (attempts <= 0 || retryAttemptsByUrl[urlIndex] >= attempts) return false; + retryAttemptsByUrl[urlIndex]++; + log?.debug?.("RETRY", `${reason} retry ${retryAttemptsByUrl[urlIndex]}/${attempts} after ${delayMs / 1000}s`); + await new Promise(resolve => setTimeout(resolve, delayMs)); + return true; + }; + for (let urlIndex = 0; urlIndex < fallbackCount; urlIndex++) { const url = this.buildUrl(model, stream, urlIndex, credentials); const transformedBody = this.transformRequest(model, body, stream, credentials); @@ -123,15 +133,7 @@ export class BaseExecutor { signal }, proxyOptions); - // Retry based on status code config - const { attempts: maxRetries, delayMs } = resolveRetryEntry(retryConfig[response.status]); - if (maxRetries > 0 && retryAttemptsByUrl[urlIndex] < maxRetries) { - retryAttemptsByUrl[urlIndex]++; - log?.debug?.("RETRY", `${response.status} retry ${retryAttemptsByUrl[urlIndex]}/${maxRetries} after ${delayMs / 1000}s`); - await new Promise(resolve => setTimeout(resolve, delayMs)); - urlIndex--; - continue; - } + if (await tryRetry(urlIndex, response.status, `status ${response.status}`)) { urlIndex--; continue; } if (this.shouldRetry(response.status, urlIndex)) { log?.debug?.("RETRY", `${response.status} on ${url}, trying fallback ${urlIndex + 1}`); @@ -142,6 +144,11 @@ export class BaseExecutor { return { response, url, headers, transformedBody }; } catch (error) { lastError = error; + if (error.name === "AbortError") throw error; + + // Map network/fetch exceptions to 502 retry config + if (await tryRetry(urlIndex, HTTP_STATUS.BAD_GATEWAY, `network "${error.message}"`)) { urlIndex--; continue; } + if (urlIndex + 1 < fallbackCount) { log?.debug?.("RETRY", `Error on ${url}, trying fallback ${urlIndex + 1}`); continue;