fix(sse): classify hour quota errors as QUOTA_EXHAUSTED

This commit is contained in:
diegosouzapw
2026-05-10 09:44:05 -03:00
parent e0928f6b37
commit 01aafd348c
3 changed files with 72 additions and 1 deletions

View File

@@ -105,6 +105,18 @@ export const ERROR_RULES: ErrorRule[] = [
backoff: true,
reason: "rate_limit_exceeded",
},
{
id: "hour_quota_exceeded",
text: "hour quota",
backoff: true,
reason: "quota_exhausted",
},
{
id: "quota_has_been_exceeded",
text: "quota has been exceeded",
backoff: true,
reason: "quota_exhausted",
},
{
id: "quota_exceeded",
text: "quota exceeded",

View File

@@ -755,6 +755,8 @@ export function classifyErrorText(errorText) {
lower.includes("quota depleted") ||
lower.includes("quota will reset") ||
lower.includes("your quota will reset") ||
lower.includes("quota has been exceeded") ||
lower.includes("hour quota") ||
lower.includes("billing")
) {
return RateLimitReason.QUOTA_EXHAUSTED;
@@ -1038,7 +1040,9 @@ export function checkFallbackError(
status === HTTP_STATUS.FORBIDDEN &&
provider &&
getProviderCategory(provider) === "apikey" &&
!errorStr.toLowerCase().includes("has not been used in project")
!errorStr.toLowerCase().includes("has not been used in project") &&
!errorStr.toLowerCase().includes("hour quota") &&
!errorStr.toLowerCase().includes("quota has been exceeded")
) {
return buildRetryableFallback(RateLimitReason.AUTH_ERROR);
}

View File

@@ -759,3 +759,58 @@ test("recordModelLockoutFailure uses regular backoff for non-quota reasons", ()
clearModelLock("modelscope", "test-conn-modelscope-2", "qwen/Qwen2.5-Coder-32B-Instruct");
}
});
// Test for hour quota related error messages
test("checkFallbackError classifies hour quota errors correctly", () => {
// For OAuth providers (e.g., codex), hour quota errors should be QUOTA_EXHAUSTED
const result1 = checkFallbackError(
429,
"Coding Plan hour quota has been exceeded",
0,
null,
"codex"
);
assert.equal(result1.shouldFallback, true);
assert.equal(result1.reason, RateLimitReason.QUOTA_EXHAUSTED);
const result2 = checkFallbackError(429, "hour quota exceeded", 0, null, "codex");
assert.equal(result2.shouldFallback, true);
assert.equal(result2.reason, RateLimitReason.QUOTA_EXHAUSTED);
const result3 = checkFallbackError(429, "Your hour quota is exceeded", 0, null, "codex");
assert.equal(result3.shouldFallback, true);
assert.equal(result3.reason, RateLimitReason.QUOTA_EXHAUSTED);
const result4 = checkFallbackError(429, "hour quota depleted", 0, null, "codex");
assert.equal(result4.shouldFallback, true);
assert.equal(result4.reason, RateLimitReason.QUOTA_EXHAUSTED);
// For API-key providers with 402 status, hour quota errors should be QUOTA_EXHAUSTED
const result5 = checkFallbackError(402, "hour quota has been exceeded", 0, null, "openai");
assert.equal(result5.shouldFallback, true);
assert.equal(result5.reason, RateLimitReason.QUOTA_EXHAUSTED);
const result6 = checkFallbackError(
403,
"Coding Plan hour quota has been exceeded",
0,
null,
"openai"
);
assert.equal(result6.shouldFallback, true);
assert.equal(result6.reason, RateLimitReason.QUOTA_EXHAUSTED);
});
// Test for classifyErrorText function with hour quota
test("classifyErrorText handles hour quota messages", () => {
const { classifyErrorText } = accountFallback;
assert.equal(
classifyErrorText("Coding Plan hour quota has been exceeded"),
RateLimitReason.QUOTA_EXHAUSTED
);
assert.equal(classifyErrorText("hour quota exceeded"), RateLimitReason.QUOTA_EXHAUSTED);
assert.equal(classifyErrorText("Your hour quota is exceeded"), RateLimitReason.QUOTA_EXHAUSTED);
assert.equal(classifyErrorText("hour quota has been exceeded"), RateLimitReason.QUOTA_EXHAUSTED);
assert.equal(classifyErrorText("quota has been exceeded"), RateLimitReason.QUOTA_EXHAUSTED);
});