Merge pull request #4113 from PeterPonyu/fix/3937-runtime-fallback-quota-patterns
fix(runtime-fallback): classify more provider quota error names
This commit is contained in:
@@ -106,10 +106,13 @@ function isLocalizedQuotaExhaustionMessage(message: string): boolean {
|
|||||||
|
|
||||||
export function classifyErrorType(error: unknown): string | undefined {
|
export function classifyErrorType(error: unknown): string | undefined {
|
||||||
const message = getErrorMessage(error)
|
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 (
|
if (
|
||||||
errorName?.includes("ai_loadapikeyerror") ||
|
errorName?.includes("ailoadapikeyerror") ||
|
||||||
errorName?.includes("loadapi") ||
|
errorName?.includes("loadapi") ||
|
||||||
(/api.?key.?is.?missing/i.test(message) && /environment variable/i.test(message))
|
(/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("quotaexceeded") ||
|
||||||
errorName?.includes("insufficientquota") ||
|
errorName?.includes("insufficientquota") ||
|
||||||
errorName?.includes("billingerror") ||
|
errorName?.includes("billingerror") ||
|
||||||
|
errorName?.includes("resourceexhausted") ||
|
||||||
/quota.?exceeded/i.test(message) ||
|
/quota.?exceeded/i.test(message) ||
|
||||||
/exceeded.*quota/i.test(message) ||
|
/exceeded.*quota/i.test(message) ||
|
||||||
/usage\s*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) ||
|
/insufficient.?(?:quota|balance|funds?)/i.test(message) ||
|
||||||
/billing.?(?:hard.?)?limit/i.test(message) ||
|
/billing.?(?:hard.?)?limit/i.test(message) ||
|
||||||
/exhausted\s+your\s+capacity/i.test(message) ||
|
/exhausted\s+your\s+capacity/i.test(message) ||
|
||||||
|
/resource.?exhausted/i.test(message) ||
|
||||||
/out\s+of\s+credits?/i.test(message) ||
|
/out\s+of\s+credits?/i.test(message) ||
|
||||||
/payment.?required/i.test(message) ||
|
/payment.?required/i.test(message) ||
|
||||||
/usage\s+limit/i.test(message) ||
|
/usage\s+limit/i.test(message) ||
|
||||||
|
|||||||
@@ -89,4 +89,58 @@ describe("runtime-fallback quota error regressions", () => {
|
|||||||
expect(errorType).toBe("quota_exceeded")
|
expect(errorType).toBe("quota_exceeded")
|
||||||
expect(retryable).toBe(true)
|
expect(retryable).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("classifies Google RESOURCE_EXHAUSTED (gRPC code 8) as quota_exceeded via error name only", () => {
|
||||||
|
//#given
|
||||||
|
// Bare provider error: only the error name carries the quota signal.
|
||||||
|
// Message is intentionally generic so the test fails if the new
|
||||||
|
// `resourceexhausted` name allow-list entry is removed.
|
||||||
|
const error = {
|
||||||
|
name: "RESOURCE_EXHAUSTED",
|
||||||
|
message: "Request failed.",
|
||||||
|
}
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const errorType = classifyErrorType(error)
|
||||||
|
const retryable = isRetryableError(error, [429, 500, 502, 503, 504])
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(errorType).toBe("quota_exceeded")
|
||||||
|
expect(retryable).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("classifies Google ResourceExhausted message without HTTP status as quota_exceeded", () => {
|
||||||
|
//#given
|
||||||
|
const error = {
|
||||||
|
name: "GoogleGenerativeAIError",
|
||||||
|
message: "Resource exhausted: Please try again later.",
|
||||||
|
}
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const errorType = classifyErrorType(error)
|
||||||
|
const retryable = isRetryableError(error, [429, 500, 502, 503, 504])
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(errorType).toBe("quota_exceeded")
|
||||||
|
expect(retryable).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("classifies snake_case OpenAI insufficient_quota error name as quota_exceeded via name only", () => {
|
||||||
|
//#given
|
||||||
|
// Bare provider error: only the snake_case error name carries the quota signal.
|
||||||
|
// Message is intentionally generic so the test fails if the underscore
|
||||||
|
// normalization (`insufficient_quota` -> `insufficientquota`) regresses.
|
||||||
|
const error = {
|
||||||
|
name: "insufficient_quota",
|
||||||
|
message: "Request failed.",
|
||||||
|
}
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const errorType = classifyErrorType(error)
|
||||||
|
const retryable = isRetryableError(error, [429, 500, 502, 503, 504])
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(errorType).toBe("quota_exceeded")
|
||||||
|
expect(retryable).toBe(true)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user