feat(providers): implemented time-budget retry for Gemini 429 errors

- Added time-budget-based retry strategy for Google Gemini 429 rate limit errors with 5-minute window.
- Set default maxRetries to 5 for Anthropic, Azure OpenAI, and OpenAI clients.
- Implemented separate 429 rate limit handling with cumulative time budget tracking instead of bounded retry attempts.
This commit is contained in:
can1357
2026-02-20 10:59:50 +01:00
parent 634b61cc65
commit 6bf59bf89c
6 changed files with 30 additions and 6 deletions
+6
View File
@@ -1,6 +1,12 @@
# Changelog
## [Unreleased]
### Changed
- Implemented time-budget-based retry strategy for Google Gemini rate limits (429 errors), allowing retries within a 5-minute window instead of a fixed attempt cap
- Set default `maxRetries` to 5 for Anthropic, Azure OpenAI, and OpenAI client configurations to improve resilience against transient failures
### Fixed
- Improved error messages for OAuth token refresh failures by including detailed error information from the provider
+1
View File
@@ -629,6 +629,7 @@ function createClient(
baseURL: config.baseURL,
defaultHeaders: config.defaultHeaders,
dangerouslyAllowBrowser: config.dangerouslyAllowBrowser,
maxRetries: 5,
});
return { client, isOAuthToken: config.isOAuthToken };
@@ -424,6 +424,7 @@ function createClient(model: Model<"azure-openai-responses">, apiKey: string, op
apiKey,
apiVersion,
dangerouslyAllowBrowser: true,
maxRetries: 5,
defaultHeaders: headers,
baseURL: baseUrl,
});
+20 -6
View File
@@ -99,6 +99,7 @@ const MAX_RETRIES = 3;
const BASE_DELAY_MS = 1000;
const MAX_EMPTY_STREAM_RETRIES = 2;
const EMPTY_STREAM_BASE_DELAY_MS = 500;
const RATE_LIMIT_BUDGET_MS = 5 * 60 * 1000;
const CLAUDE_THINKING_BETA_HEADER = "interleaved-thinking-2025-05-14";
/**
@@ -360,8 +361,9 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
let response: Response | undefined;
let lastError: Error | undefined;
let requestUrl: string | undefined;
let rateLimitTimeSpent = 0;
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
for (let attempt = 0; ; attempt++) {
if (options?.signal?.aborted) {
throw new Error("Request was aborted");
}
@@ -382,13 +384,25 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
const errorText = await response.text();
// Check if retryable
if (attempt < MAX_RETRIES && isRetryableError(response.status, errorText)) {
// Use server-provided delay or exponential backoff
// Handle 429 rate limits with time budget
if (response.status === 429) {
const serverDelay = extractRetryDelay(errorText, response);
if (serverDelay && rateLimitTimeSpent + serverDelay <= RATE_LIMIT_BUDGET_MS) {
rateLimitTimeSpent += serverDelay;
await abortableSleep(serverDelay, options?.signal);
continue;
}
// Fallback: use exponential backoff if no server delay, up to MAX_RETRIES
if (!serverDelay && attempt < MAX_RETRIES) {
await abortableSleep(BASE_DELAY_MS * 2 ** attempt, options?.signal);
continue;
}
} else if (attempt < MAX_RETRIES && isRetryableError(response.status, errorText)) {
// Non-429 retryable errors use standard attempt cap
const serverDelay = extractRetryDelay(errorText, response);
const delayMs = serverDelay ?? BASE_DELAY_MS * 2 ** attempt;
// Check if server delay exceeds max allowed (default: 60s)
// Check if server delay exceeds max allowed (default: 60s) for non-429 errors
const maxDelayMs = options?.maxRetryDelayMs ?? 60000;
if (maxDelayMs > 0 && serverDelay && serverDelay > maxDelayMs) {
const delaySeconds = Math.ceil(serverDelay / 1000);
@@ -401,7 +415,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
continue;
}
// Not retryable or max retries exceeded
// Not retryable or budget exceeded
throw new Error(`Cloud Code Assist API error (${response.status}): ${extractErrorMessage(errorText)}`);
} catch (error) {
// Check for abort - fetch throws AbortError, our code throws "Request was aborted"
@@ -480,6 +480,7 @@ async function createClient(
apiKey,
baseURL: model.baseUrl,
dangerouslyAllowBrowser: true,
maxRetries: 5,
defaultHeaders: headers,
});
}
@@ -397,6 +397,7 @@ function createClient(
apiKey,
baseURL: model.baseUrl,
dangerouslyAllowBrowser: true,
maxRetries: 5,
defaultHeaders: headers,
});
}