fix(runtime-fallback): classify more provider quota error names
Closes #3937. Adds three small classification gaps to `classifyErrorType` so that quota-exhaustion errors from a wider range of providers trigger configured fallback chains instead of looping retry attempts: - Normalize error names by stripping `_` and `-` so snake_case / SCREAMING_SNAKE_CASE provider names (`insufficient_quota`, `RESOURCE_EXHAUSTED`, `rate_limit_exceeded`) match the existing alphanumeric `.includes()` checks. - Add `resourceexhausted` to the quota error-name allow-list to cover Google Generative AI's gRPC code 8 / `ResourceExhausted` surface. - Add `/resource.?exhausted/i` to the quota message-pattern list so the same error surface is caught when the provider only sets a generic error name but puts the signal in the message. Three new regression tests in `quota-error-classifier.regression.test.ts` cover: - Google `RESOURCE_EXHAUSTED` (gRPC error name + quota-shaped message) - Google `ResourceExhausted` message form without HTTP status - OpenAI snake_case `insufficient_quota` error name No existing tests were touched; the underscore normalization preserves all existing `.includes()` matches by rewriting the one underscore-bearing literal (`ai_loadapikeyerror` → `ailoadapikeyerror`) so previously matched names still resolve. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -106,10 +106,13 @@ function isLocalizedQuotaExhaustionMessage(message: string): boolean {
|
||||
|
||||
export function classifyErrorType(error: unknown): string | undefined {
|
||||
const message = getErrorMessage(error)
|
||||
const errorName = extractErrorName(error)?.toLowerCase()
|
||||
// Normalize by stripping underscores and dashes so snake_case / kebab-case
|
||||
// provider error names (e.g. "insufficient_quota", "RESOURCE_EXHAUSTED")
|
||||
// match the existing alphanumeric .includes() checks below.
|
||||
const errorName = extractErrorName(error)?.toLowerCase().replace(/[_-]/g, "")
|
||||
|
||||
if (
|
||||
errorName?.includes("ai_loadapikeyerror") ||
|
||||
errorName?.includes("ailoadapikeyerror") ||
|
||||
errorName?.includes("loadapi") ||
|
||||
(/api.?key.?is.?missing/i.test(message) && /environment variable/i.test(message))
|
||||
) {
|
||||
@@ -132,6 +135,7 @@ export function classifyErrorType(error: unknown): string | undefined {
|
||||
errorName?.includes("quotaexceeded") ||
|
||||
errorName?.includes("insufficientquota") ||
|
||||
errorName?.includes("billingerror") ||
|
||||
errorName?.includes("resourceexhausted") ||
|
||||
/quota.?exceeded/i.test(message) ||
|
||||
/exceeded.*quota/i.test(message) ||
|
||||
/usage\s*quota/i.test(message) ||
|
||||
@@ -139,6 +143,7 @@ export function classifyErrorType(error: unknown): string | undefined {
|
||||
/insufficient.?(?:quota|balance|funds?)/i.test(message) ||
|
||||
/billing.?(?:hard.?)?limit/i.test(message) ||
|
||||
/exhausted\s+your\s+capacity/i.test(message) ||
|
||||
/resource.?exhausted/i.test(message) ||
|
||||
/out\s+of\s+credits?/i.test(message) ||
|
||||
/payment.?required/i.test(message) ||
|
||||
/usage\s+limit/i.test(message) ||
|
||||
|
||||
Reference in New Issue
Block a user