fix(oauth+errors): OAuth silent refresh, quota STOP patterns, compaction loop cap

Bug fixes:
1. OAuth token refresh (#3149): buildHttpRequestInit() now attempts silent refresh
   via refresh_token before triggering full browser re-auth. Added refresh() method
   to McpOAuthProvider. Includes test isolation fix for discovery mock.

2. Quota error STOP (#3126): Added STOP_MESSAGE_PATTERNS in model-error-classifier
   that take precedence over RETRYABLE_MESSAGE_PATTERNS. Message-only quota errors
   now non-retryable. Runtime-fallback: quota_exceeded with 'retrying in' signal
   still triggers fallback (provider-managed auto-retry). Restored removed patterns.

3. Compaction loop (#3127): MAX_RECOVERY_ATTEMPTS=3 cap + additional suppression
   guard from opencode session in degradation monitor.

Also: refactored extractAutoRetrySignal to auto-retry-signal.ts, new regression
tests for quota classifier and compaction degradation monitor.
This commit is contained in:
YeonGyu-Kim
2026-04-06 17:38:37 +09:00
parent 6c4e0b69a5
commit 61083d499d
15 changed files with 611 additions and 531 deletions
@@ -0,0 +1,56 @@
import { describe, expect, test } from "bun:test"
import { classifyErrorType, isRetryableError } from "./error-classifier"
describe("runtime-fallback quota error regressions", () => {
test("classifies subscription quota errors as quota_exceeded and stops retry", () => {
//#given
const error = {
name: "AI_APICallError",
message: "Subscription quota exceeded. You can continue using free models.",
}
//#when
const errorType = classifyErrorType(error)
const retryable = isRetryableError(error, [429, 500, 502, 503, 504])
//#then
expect(errorType).toBe("quota_exceeded")
expect(retryable).toBe(false)
})
test("treats HTTP 402 payment required as non-retryable", () => {
//#given
const error = { statusCode: 402, message: "Payment Required" }
//#when
const retryable = isRetryableError(error, [429, 500, 502, 503, 504])
//#then
expect(retryable).toBe(false)
})
test("keeps HTTP 429 rate limit retryable", () => {
//#given
const error = { statusCode: 429, message: "Too Many Requests: rate limit reached" }
//#when
const retryable = isRetryableError(error, [429, 500, 502, 503, 504])
//#then
expect(retryable).toBe(true)
})
test("classifies quota error names as quota_exceeded without retry", () => {
//#given
const error = { name: "QuotaExceededError", 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(false)
})
})