## 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
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
Recovers features lost during 0.1.91->0.1.95 merge (0.1.92-0.1.94
customizations were skipped because main branch was not updated).
Recovered features:
- Antigravity 403 forbidden/validation/ban status detection and display
- Subscription tier badge in account list
- Client affinity scheduling for Anthropic accounts
- Sora async task API and GDrive storage backend
- Swipe-to-select for admin tables
- OpenAI passthrough account selection fix
- Various quota and usage enhancements
- selectAccountByID: use original task's account for remix instead of
random selection, preventing permission issues in OAuth path
- Upstream error passthrough: forward HTTP status code and body from
upstream instead of always returning 500
- Add accountRepo to SoraTaskService for direct account lookup
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add async task management for Sora video/image generation with two
forwarding modes: OAuth (SDK) and API Key (HTTP upstream). Includes
background worker for status polling and 3-layer storage degradation
(S3 > local disk > upstream URL passthrough).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add colored circle badge showing affinity client count before the account
status indicator. Badge color reflects count severity (gray=0, green=1-5,
yellow=6-15, red=16+). Tooltip shows full client ID list on hover.
Backend: AccountHandler batch queries reverse affinity index via
GetAccountAffinityClientsBatch, returns affinity_client_count and
affinity_clients in DTO. GatewayCache interface extended with the new
batch method backed by a Lua script (get_affinity_clients.lua).
All test mocks synchronized with updated GatewayCache interface.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Split compound "Profile" column into separate Profile ID and Name columns
- Replace "Endpoint" column with "Storage Path" showing bucket/prefix or folder info
- Add GDrive quota display (capacity/used) via new backend API endpoint
- Add video count statistics per storage type via new backend API endpoint
- Add inline test button per profile with 15s timeout and per-profile loading state
- Backend: GetQuotaInfo, CountByStorageType, GetGDriveQuota, GetStorageVideoStats
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Define SoraObjectStorage interface abstracting S3 and GDrive backends
- Implement SoraGDriveStorage with OAuth2 and Service Account auth
- Add SoraStorageRouter to route requests based on active profile provider
- Add GDrive OAuth handler for authorization flow (start + callback)
- Extend profile model with provider, auth_type, and GDrive-specific fields
- Update frontend UI with provider selection, dynamic form fields, and i18n
- Migrate API paths from /sora-s3 to /sora-storage (old paths preserved)
- All existing S3 functionality remains backward compatible
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- add default subscriptions to admin settings
- auto-assign subscriptions on register and admin user creation
- add validation/tests and align settings UI with subscription selector patterns