The expanded user breakdown rows already align with the parent table
columns (Requests, Token, Actual, Standard), so the repeated sub-header
wastes vertical space. Remove the <thead> from UserBreakdownSubTable.
TestConnection now reuses antigravityRetryLoop instead of a standalone
HTTP loop, gaining credits overages, smart retry, and 429/503 backoff
for free. AccountSwitchError is caught and surfaced as a friendly
message. Also fixes two slow unit tests caused by MODEL_CAPACITY_EXHAUSTED
60x1s retries and 999s clamp wait.
Remove Sync() calls in logger tests that use os.Pipe for stdout/stderr
capture. On Windows, FlushFileBuffers on pipe handles blocks
indefinitely, causing test timeouts (600s+).
Also use os.MkdirTemp instead of t.TempDir to avoid cleanup failures
when lumberjack still holds file handles.
Click on a group name, model name, or endpoint name in the distribution
tables to expand and show per-user usage breakdown (requests, tokens,
actual cost, standard cost).
Backend: new GET /admin/dashboard/user-breakdown API with group_id,
model, endpoint, endpoint_type filters.
Frontend: clickable rows with expand/collapse sub-table in all three
distribution charts.
The usage stats query defaults to a 30-day rolling window, but the
UI label said "Total"/"累计" implying lifetime aggregation. Rename
to "Last 30d"/"近30天" so the label matches the actual query semantics.
Closes#1060
- Simplify OpenAI rendering: always fetch /usage, prefer fetched data over
codex snapshot (snapshot serves as loading placeholder only)
- Remove dead code: preferFetchedOpenAIUsage, isOpenAICodexSnapshotStale,
and unreachable template branch
- Add today-stats support for key accounts (req/tokens/A/U badges)
- Use formatCompactNumber for consistent number formatting
- Add A/U badge titles for clarity
- Filter zero-value window stats in UsageProgressBar to avoid empty badges
- Update tests to match new fetched-data-first behavior
- Pass todayStats/todayStatsLoading to AccountUsageCell for key accounts
- Propagate usageManualRefreshToken to force usage reload on explicit refresh
- Refresh today stats when toggling usage/today_stats columns visible
Removes hasMeaningfulWindowStats guard so the /usage endpoint consistently
returns WindowStats for both time windows. The frontend now controls
zero-value display filtering at the component level.
The updateRateLimitUsageScript Lua script previously performed
unconditional HINCRBYFLOAT on all usage counters without checking
whether the rate limit window had expired. This caused usage to
accumulate across window boundaries in Redis while the DB correctly
reset on expiration, leading to incorrect 429 rate limiting that
could persist for up to 24 hours.
The Lua script now checks each window timestamp before incrementing:
- If the window has expired, usage is reset to the current cost and
the window timestamp is updated (matching DB-side semantics)
- If the window is still valid, usage is accumulated normally
This also resolves the async race condition where stale HINCRBYFLOAT
tasks from the worker queue could pollute a freshly rebuilt cache
after invalidation, since the script now self-corrects expired windows.
Closes#1049
- Fix gofmt alignment in admin_service.go and trailing newline in
antigravity_credits_overages.go
- Suppress errcheck for fmt.Sscanf in client.go GetMinimumAmount
Remove SimulateClaudeMaxEnabled field and related logic from
admin_service.go, and remove applyClaudeMaxCacheBillingPolicyToUsage,
applyClaudeMaxNonStreamingRewrite, setupClaudeMaxStreamingHook calls
from antigravity_gateway_service.go. These symbols are not yet
available in upstream/main.
Replace process-memory sync.Map + per-model runtime state with a single
"AICredits" key in model_rate_limits, making credits exhaustion fully
isomorphic with model-level rate limiting.
Scheduler: rate-limited accounts with overages enabled + credits available
are now scheduled instead of excluded.
Forwarding: when model is rate-limited + credits available, inject credits
proactively without waiting for a 429 round trip.
Storage: credits exhaustion stored as model_rate_limits["AICredits"] with
5h duration, reusing SetModelRateLimit/isRateLimitActiveForKey.
Frontend: show credits_active (yellow ⚡) when model rate-limited but
credits available, credits_exhausted (red) when AICredits key active.
Tests: add unit tests for shouldMarkCreditsExhausted, injectEnabledCreditTypes,
clearCreditsExhausted, and update existing overages tests.
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
Merge upstream PR #1041 with fix: restore `len(input.Extra) > 0` guard
in UpdateAccount to prevent empty Extra map from accidentally clearing
existing Extra fields on non-Antigravity accounts.
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