From 62ac3e2ae9b941adaa35cc3962c4de54f917e065 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Fri, 30 Jan 2026 08:14:10 -0500 Subject: [PATCH] fix(glmt): extract Retry-After from HTTP headers and cap maxRetries - Extract Retry-After from actual HTTP response headers instead of parsing error messages - Cap GLMT_MAX_RETRIES at 10 to prevent misconfiguration - Prefer header-based Retry-After over message parsing (fallback preserved) Addresses PR review feedback. --- src/glmt/glmt-proxy.ts | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/glmt/glmt-proxy.ts b/src/glmt/glmt-proxy.ts index 73a24315..85d8c22f 100644 --- a/src/glmt/glmt-proxy.ts +++ b/src/glmt/glmt-proxy.ts @@ -109,7 +109,7 @@ export class GlmtProxy { const maxRetries = parseInt(process.env.GLMT_MAX_RETRIES || '3', 10); const baseDelay = parseInt(process.env.GLMT_RETRY_BASE_DELAY || '1000', 10); this.retryConfig = { - maxRetries: isNaN(maxRetries) || maxRetries < 0 ? 3 : maxRetries, + maxRetries: isNaN(maxRetries) || maxRetries < 0 ? 3 : Math.min(maxRetries, 10), baseDelay: isNaN(baseDelay) || baseDelay < 0 ? 1000 : baseDelay, enabled: process.env.GLMT_DISABLE_RETRY !== '1', }; @@ -406,7 +406,11 @@ export class GlmtProxy { private isRetryableError(error: Error): { retryable: boolean; retryAfter?: string } { const message = error.message; if (message.includes('429') || message.toLowerCase().includes('rate limit')) { - // Try to extract Retry-After from error message + // Prefer Retry-After from error property (captured from HTTP header), fallback to parsing message + const errWithRetryAfter = error as Error & { retryAfter?: string }; + if (errWithRetryAfter.retryAfter) { + return { retryable: true, retryAfter: errWithRetryAfter.retryAfter }; + } const retryAfterMatch = message.match(/retry-after:\s*(\d+)/i); return { retryable: true, @@ -565,7 +569,17 @@ export class GlmtProxy { // Check for non-200 status if (res.statusCode !== 200) { - reject(new Error(`Upstream error: ${res.statusCode} ${res.statusMessage}\n${body}`)); + const err = new Error( + `Upstream error: ${res.statusCode} ${res.statusMessage}\n${body}` + ) as Error & { retryAfter?: string }; + // Capture Retry-After header for rate limit handling + const retryAfterHeader = res.headers['retry-after']; + if (retryAfterHeader) { + err.retryAfter = Array.isArray(retryAfterHeader) + ? retryAfterHeader[0] + : retryAfterHeader; + } + reject(err); return; } @@ -635,7 +649,17 @@ export class GlmtProxy { let body = ''; upstreamRes.on('data', (chunk: Buffer) => (body += chunk.toString())); upstreamRes.on('end', () => { - reject(new Error(`Upstream error: ${upstreamRes.statusCode}\n${body}`)); + const err = new Error(`Upstream error: ${upstreamRes.statusCode}\n${body}`) as Error & { + retryAfter?: string; + }; + // Capture Retry-After header for rate limit handling + const retryAfterHeader = upstreamRes.headers['retry-after']; + if (retryAfterHeader) { + err.retryAfter = Array.isArray(retryAfterHeader) + ? retryAfterHeader[0] + : retryAfterHeader; + } + reject(err); }); return; }