Anthropic OAuth/setup-token accounts are throttled by actual proxy
connection (host:port:username). Other platforms bypass the queue
entirely. Also adds unit tests for affinity and usage queue features.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- New API: GET /accounts/:id/affinity-clients returns client list with
last_active timestamps from Redis ZSET scores
- New Lua script: get_affinity_clients_with_scores.lua uses ZREVRANGEBYSCORE
WITHSCORES to return both client IDs and their timestamps
- New frontend component: AffinityBadge.vue replaces inline badge with
hover popover that lazy-loads client details on first hover
- Shows client ID (monospace) + relative time (e.g. "3h ago")
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Widen the per-group delay between Anthropic usage API calls from
1-1.5s to 1-2s to avoid upstream 429 rate limiting when multiple
accounts share the same proxy exit.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pipeline mode only sends EVALSHA (no automatic fallback to EVAL).
If Redis restarts and script cache is lost, all pipeline Lua calls
silently fail with NOSCRIPT. Add ensureScriptLoaded() to pre-load
scripts before pipeline execution.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously the count was only returned when Redis had data, causing
the badge to not display for accounts with zero affinity clients.
Now returns count=0 for all affinity-enabled accounts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
GO-2026-4602 (os), GO-2026-4601 (net/url), GO-2026-4600 and
GO-2026-4599 (crypto/x509). The crypto/x509 fixes are only
available in go1.26.1+, not backported to go1.25.x.
- Add missing CountByStorageType method to stubSoraGenRepo test mock
- Suppress SA1019 for WithCredentialsJSON (admin-controlled source)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix struct tag alignment in dto/types.go (AffinityClientCount)
- Fix struct literal alignment in sora_generation_service.go
- Replace deprecated google.CredentialsFromJSONWithParams with
option.WithCredentialsJSON for service account auth (SA1019)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix struct field alignment in dto/types.go, settings_view.go,
sora_generation_service.go for gofmt compliance
- Remove embedded field "Account" from selector in account_handler.go (QF1008)
- Remove unused settingService field from SoraGDriveOAuthService
- Replace deprecated google.CredentialsFromJSON with CredentialsFromJSONWithParams (SA1019)
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>
- Remove RemoveClientAffinity from GatewayCache interface, implementation,
Lua script, and all test mocks (never called in production code)
- Add 26 unit tests for affinity scheduling: filterByMinAffinityCount,
populateAffinityCounts, Layer 1/2 sort chain integration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Separate clientAffinityTTL (24h) from stickySessionTTL (1h)
- Atomic Lua dual-write for forward (client→accounts) and reverse
(account→clients) affinity indexes with lazy expiry cleanup
- Extract Lua scripts to standalone .lua files with //go:embed
- Add filterByMinAffinityCount to Layer 1 & 2 scheduling so new
clients are routed to accounts with fewest existing affinities
- Skip Redis query when no affinity-enabled accounts exist (zero
overhead for non-affinity users)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The client affinity setting was only available when editing an account.
Now it's also available during account creation for all Anthropic
platform types (OAuth, Setup Token, API Key).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix gofmt: align struct field comments in UsageCache, trim trailing
whitespace on const comments
- Fix errcheck: use comma-ok on type assertion for singleflight result
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Prevents 429 rate-limit retry storms and reduces upstream correlation risk
for Anthropic usage API queries.
Three changes:
1. Negative caching (1 min TTL) — 429/error responses are now cached,
preventing every subsequent page load from re-triggering failed API calls.
2. singleflight dedup — concurrent requests for the same account are
collapsed into a single upstream call, preventing cache stampede.
3. Random jitter (0–800 ms) — staggers multi-account cache-miss bursts so
requests from different accounts don't hit upstream simultaneously with
identical TLS fingerprints, reducing anti-abuse correlation risk.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Move storage provider selection (S3/GDrive) from separate modal into
the drawer itself, shown as the first step when creating a new profile
- Provider type is locked once selected (read-only badge in header)
- Remove default_storage_quota_gb field from form and table column
- Remove unused formatStorageQuotaGB function and providerSelectOpen ref
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add quota_daily_limit/used and quota_weekly_limit/used to DTO and
frontend Account type
- AccountCapacityCell shows D (daily), W (weekly), and $ (total) badges
with color-coded status (green/yellow/red)
- AccountActionMenu shows reset button when any quota dimension is set
- Extract quotaBadgeClass/quotaBadgeTooltip as shared functions
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>
When customErrorCodes is disabled or modelMapping is empty, explicitly
delete the fields inherited from currentCredentials spread to avoid
preserving stale values.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>