- Add groupID parameter to ListAccounts and ListWithFilters methods
- Implement account filtering by group ID in repository query
- Add group query parameter parsing in account handler
- Update all ListAccounts/ListWithFilters call sites with groupID parameter
- Add group filter UI component to AccountTableFilters
- Add i18n translations for group filter label in English and Chinese
- Update API contract and test stubs to reflect new signature
- Enable filtering accounts by their assigned groups in admin panel
- Add used_by_email column to redeem code export CSV for better user identification
- Implement rate_limited status filter in account listing with RateLimitResetAt check
- Extend redeem code search to include user email in addition to code matching
- Add API key search capability to user listing filters
- Display user email in redeem code table used_by column for improved visibility
- Update search placeholders in UI to reflect expanded search capabilities (email, username, notes, API key)
- Improve Chinese and English localization strings for search hints
For retryable transient errors (Google 400 "invalid project resource name"
and empty stream responses), retry on the same account up to 2 times
(with 500ms delay) before switching to another account.
- Add RetryableOnSameAccount field to UpstreamFailoverError
- Add same-account retry loop in both Gemini and Claude/OpenAI handler paths
- Move temp-unschedule from service layer to handler layer (only after
all same-account retries exhausted)
- Reduce temp-unschedule cooldown from 30 minutes to 1 minute
Merge functional changes from develop branch:
- Remove AntigravityQuotaScope system (claude/gemini_text/gemini_image)
- Replace with per-model rate limiting using resolveAntigravityModelKey
- Remove model load statistics (IncrModelCallCount/GetModelLoadBatch)
- Simplify account selection to unified priority→load→LRU algorithm
- Remove SetAntigravityQuotaScopeLimit from AccountRepository
- Clean up scope-related UI indicators and API fields
ParseGatewayRequest only parsed Anthropic format (system/messages),
ignoring Gemini native format (systemInstruction/contents). This caused
GenerateSessionHash to produce identical hashes for all Gemini sessions.
Add protocol parameter to ParseGatewayRequest to branch between
Anthropic and Gemini parsing. Update GenerateSessionHash message
traversal to extract text from both formats.
Mix SessionContext (ClientIP, UserAgent, APIKeyID) into
GenerateSessionHash 3rd-level fallback to differentiate requests
from different users sending identical content.
Also switch hashContent from SHA256-truncated to XXHash64 for
better performance, and optimize Trie Lua script to match from
longest prefix first.
Previously, thoughtSignature cleanup only applied to Gemini CLI
requests (detected via x-gemini-api-privileged-user-id header or
tmp dir pattern). This caused 400 errors for non-CLI clients when
session cache expired and they sent stale signatures.
Remove the isGeminiCLIRequest guard so all clients benefit from
proactive thoughtSignature cleanup on session binding miss.
- Add `sort_order` field to groups table with migration
- Add `PUT /api/v1/admin/groups/sort-order` API for batch update
- Implement drag-and-drop UI using vue-draggable-plus
- All queries now order groups by sort_order
- Add i18n support (en/zh) for sort-related UI text
- Update test stubs to satisfy new interface methods
Upstream accounts now use the standard APIKey type instead of a dedicated
upstream type. GetBaseURL() and new GetGeminiBaseURL() automatically append
/antigravity for Antigravity platform APIKey accounts, eliminating the need
for separate upstream forwarding methods.
- Remove ForwardUpstream, ForwardUpstreamGemini, testUpstreamConnection
- Remove upstream branch guards in Forward/ForwardGemini/TestConnection
- Add migration 052 to convert existing upstream accounts to apikey
- Update frontend CreateAccountModal to create apikey type
- Add unit tests for GetBaseURL and GetGeminiBaseURL
Previously the /v1/usage endpoint aggregated usage stats (today/total
tokens, cost, RPM/TPM) across all API Keys belonging to the user.
This made it impossible to distinguish usage from different API Keys
(e.g. balance vs subscription keys).
Now the usage stats are filtered by the current request's API Key ID,
so each key only sees its own usage data. The balance/remaining fields
are unaffected and still reflect the user-level wallet balance.
Changes:
- Add GetAPIKeyDashboardStats to repository interface and implementation
- Add getPerformanceStatsByAPIKey helper (also fixes TPM to include
cache_creation_tokens and cache_read_tokens)
- Add GetAPIKeyDashboardStats to UsageService
- Update Usage handler to call GetAPIKeyDashboardStats(apiKey.ID)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This feature allows API Keys to have their own quota limits and expiration
times, independent of the user's balance.
Backend:
- Add quota, quota_used, expires_at fields to api_key schema
- Implement IsExpired() and IsQuotaExhausted() checks in middleware
- Add ResetQuota and ClearExpiration API endpoints
- Integrate quota billing in gateway handlers (OpenAI, Anthropic, Gemini)
- Include quota/expiration fields in auth cache for performance
- Expiration check returns 403, quota exhausted returns 429
Frontend:
- Add quota and expiration inputs to key create/edit dialog
- Add quick-select buttons for expiration (+7, +30, +90 days)
- Add reset quota confirmation dialog
- Add expires_at column to keys list
- Add i18n translations for new features (en/zh)
Migration:
- Add 045_add_api_key_quota.sql for new columns