## Problem
When a proxy is unreachable, token refresh retries up to 4 times with
30s timeout each, causing requests to hang for ~2 minutes before
failing with a generic 502 error. The failed account is not marked,
so subsequent requests keep hitting it.
## Changes
### Proxy connection fast-fail
- Set TCP dial timeout to 5s and TLS handshake timeout to 5s on
antigravity client, so proxy connectivity issues fail within 5s
instead of 30s
- Reduce overall HTTP client timeout from 30s to 10s
- Export `IsConnectionError` for service-layer use
- Detect proxy connection errors in `RefreshToken` and return
immediately with "proxy unavailable" error (no retries)
### Token refresh temp-unschedulable
- Add 8s context timeout for token refresh on request path
- Mark account as temp-unschedulable for 10min when refresh fails
(both background `TokenRefreshService` and request-path
`GetAccessToken`)
- Sync temp-unschedulable state to Redis cache for immediate
scheduler effect
- Inject `TempUnschedCache` into `AntigravityTokenProvider`
### Account failover
- Return `UpstreamFailoverError` on `GetAccessToken` failure in
`Forward`/`ForwardGemini` to trigger handler-level account switch
instead of returning 502 directly
### Proxy probe alignment
- Apply same 5s dial/TLS timeout to shared `httpclient` pool
- Reduce proxy probe timeout from 30s to 10s
- Set proxy TCP dial timeout to 5s and TLS handshake timeout to 5s
for quick failure detection instead of waiting 30s
- Reduce overall HTTP client timeout from 30s to 10s
- Detect proxy connection errors (IsConnectionError) and skip retries
immediately with clear "proxy unavailable" error
- Add 8s context timeout for token refresh on request path to cap
total wait time
- Mark account as temp-unschedulable for 10min when token refresh
fails (both background and request path)
- Sync temp-unschedulable state to Redis cache for immediate
scheduler effect
- Return UpstreamFailoverError on GetAccessToken failure to trigger
account failover instead of returning 502 directly
- Apply same 5s dial timeout to httpclient pool (proxy probe/test)
- Reduce proxy probe timeout from 30s to 10s
Replace sync.Map + per-model runtime state with a single AICredits key in
model_rate_limits. Rate-limited accounts with available credits are now
scheduled directly and credits injected without a 429 round trip.
- Scheduler: rate-limited + overages + credits available = allow scheduling
- Forwarding: inject credits proactively when model rate limited
- Storage: credits exhaustion stored as model_rate_limits["AICredits"]
- Frontend: show credits_active (yellow ⚡) vs credits_exhausted states
- Bump version to 0.1.100.4
Introduce OAuthRefreshAPI as the single entry point for all OAuth token
refresh operations, eliminating the race condition where background
refresh and inline refresh could simultaneously use the same
refresh_token (fixes#1035).
Key changes:
- Add OAuthRefreshExecutor interface extending TokenRefresher with CacheKey
- Add OAuthRefreshAPI.RefreshIfNeeded with lock → DB re-read → double-check flow
- Add ProviderRefreshPolicy / BackgroundRefreshPolicy strategy types
- Simplify all 4 TokenProviders to delegate to OAuthRefreshAPI
- Rewrite TokenRefreshService.refreshWithRetry to use unified API path
- Add MergeCredentials and BuildClaudeAccountCredentials helpers
- Add 40 unit tests covering all new and modified code paths
Introduce OAuthRefreshAPI as the single entry point for all OAuth token
refresh operations (both background TokenRefreshService and inline
TokenProvider), eliminating the race condition where two independent
refresh paths consume the same one-time-use refresh_token.
Key changes:
- New OAuthRefreshAPI with distributed lock + DB re-read + double-check
- All 4 platform refreshers implement OAuthRefreshExecutor (CacheKey)
- TokenRefreshService and TokenProviders share the same lock key
- Add errRefreshSkipped sentinel for accurate refresh statistics
- 7 new Path A unit tests covering all RefreshIfNeeded branches
chore: bump version to 0.1.100.2