feat(quota): add Apertis quota monitoring and refactor openai_responses handling#1793
Conversation
- Add Apertis quota checker for monitoring PAYG credits and subscription cycles - Implement URL-based detection for api.apertis.ai - Add ent schema support for 'apertis' provider type - Add TypeScript types and quota badge rendering for frontend - Fix test helper naming conflict in apertis_checker_test.go
- Remove orphaned code causing TS1128 errors in quota badge helpers - Use ApertisProviderType and ApertisDefaultBaseURL constants instead of hardcoded strings - Fix warning threshold comparison operators (>= to >) - Add mockApertisResponse test helper to reduce code duplication
- Backend: fix determinePaygStatus returning 'exhausted' for unlimited+0 credits (token_is_unlimited now short-circuits to 'available') - Backend: skip redundant PAYG limit in buildApertisLimits when subscription is active (subscription is the real limiting factor) - Frontend: hide PAYG sections for active subscribers unless they have real credits (account_credits > 0 or token_used > 0) or payg_fallback_enabled — eliminates noise from 0-credit/unlimited PAYG - Add test for subscriber with unlimited PAYG + 0 credits scenario - Update existing WithSubscription test (1 limit, not 2)
- Handle openai_responses channel type in parseChannelNode with proper providerType routing (wafer, synthetic, neuralwatt, apertis) - Add openai_responses variants to ProviderQuotaChannel type union - Update dedup logic to merge openai + openai_responses by providerType - Update all QuotaRow/getChannelPercentage checks to recognize openai_responses alongside openai for each provider
- Bug fix: PAYG limit omitted in buildApertisLimits for active subscribers with exhausted cycle
- Bug fix: PAYG skipped when is_subscriber=true but subscription is nil
- Bug fix: Case-sensitive status comparison inconsistent with EqualFold in determineSubscriptionStatus
- Bug fix: Missing type guard on token_used before toFixed/division in frontend
- Smell fix: Redundant !payg.TokenIsUnlimited guard removed in determinePaygStatus
- Smell fix: Duplicate apertisRoundTripFunc removed, using existing roundTripFunc
- Smell fix: Removed inconsistent ApertisProviderType constant, using raw string convention
- Smell fix: Extracted isOpenaiType helper for repeated type-check pattern
- Smell fix: Collapsed paired openai/openai_responses union variants in quotas.ts
- Smell fix: Deduplicated parseChannelNode openai vs openai_responses branches
- Smell fix: Hoisted betterStatus rank map to package-level var
- Smell fix: Removed mockApertisResponse helper, inlined per convention
- Smell fix: Added subdomain and port URL detection test cases
- Lint fix: interface{} → any, nolint for cancelled API domain value
- Format: gofmt + prettier
Restore original formatting from unstable branch in quotas.ts and quota-badges.tsx, keeping only substantive changes: - ProviderApertisQuotaData type - Collapsed openai/openai_responses union variants - QueryChannelsResponse type and parseChannelNode function - isOpenaiType helper and getApertisPercentage function - Apertis quota display section
Greptile SummaryThis PR adds Apertis provider quota monitoring with a subscription+PAYG dual-model checker, and refactors the frontend quota system to handle
Confidence Score: 4/5Safe to merge after fixing the is_subscriber+subscription=nil edge case in determineApertisStatus; the rest of the change is well-tested and architecturally sound. One logic gap in the new Apertis checker: when the API returns internal/server/biz/provider_quota/apertis_checker.go — specifically the Important Files Changed
Reviews (3): Last reviewed commit: "Merge origin/unstable into feat/apertis-..." | Re-trigger Greptile |
- gci: fix import formatting
- gofumpt: add blank line between functions, remove extra blank line in test
- godot: add period to comment
- misspell: use American 'canceled' in comments (API string 'cancelled' has nolint)
- modernize: replace interface{} with any in toFloat64
…ed status in limits P1: Dual token limits caused wrong worst-case routing status. EffectiveStatus picks worst across all limits of a given type, but Apertis uses OR-semantics (available if EITHER source has quota). Adding QuotaLimitTypeSubscriptionCycle separates the subscription cycle from the PAYG token pool so they are never merged by EffectiveStatus(QuotaLimitTypeToken). P2: Suspended/cancelled subscriptions were not checked when computing subscription limit status. The limit would show 'available' based on remaining cycle quota even though the subscription is exhausted. Now returns 'exhausted' for suspended/cancelled subscriptions. Also fixes gci formatting in types.go.
Separate third-party imports (stretchr) from local module imports (looplj) with a blank line, matching the gci config's localmodule section that CI enforces.
|
There are schema conflict, please help to resolve. Thanks. |
Done |
| subStatus := determineSubscriptionStatus(resp.Subscription) | ||
| bestStatus = betterStatus(bestStatus, subStatus) | ||
| } | ||
|
|
||
| // --- PAYG path (always checked) --- | ||
| // PAYG is the fallback whenever subscription quota is unavailable. | ||
| // For subscribers, PAYG is only reachable when payg_fallback_enabled is true | ||
| // or when the subscription is suspended/canceled (no cycle quota left). | ||
| // For non-subscribers, PAYG is the only path. | ||
| if resp.Payg != nil { | ||
| shouldCheckPAYG := !resp.IsSubscriber | ||
| if resp.Subscription != nil { | ||
| // PAYG is available when fallback is enabled, or when | ||
| // the subscription itself is exhausted (suspended/canceled/cycle used up) | ||
| if resp.Subscription.PaygFallbackEnabled { | ||
| shouldCheckPAYG = true | ||
| } | ||
| if strings.EqualFold(resp.Subscription.Status, "suspended") || strings.EqualFold(resp.Subscription.Status, "cancelled") { //nolint:misspell // API domain value | ||
| shouldCheckPAYG = true | ||
| } | ||
| if resp.Subscription.CycleQuotaRemaining <= 0 { |
There was a problem hiding this comment.
is_subscriber=true + subscription=nil produces "exhausted" while PAYG limits show available
shouldCheckPAYG is initialised to !resp.IsSubscriber (i.e., false for subscribers). When resp.Subscription == nil the inner if block is never entered, so shouldCheckPAYG stays false and PAYG is never evaluated for the overall status. With no subscription to contribute a status, bestStatus stays at its initial "exhausted".
At the same time, buildApertisLimits does include PAYG limits for this case because shouldSkipPAYG requires resp.Subscription != nil (which is false). The result is status="exhausted" but limits showing PAYG as available — blocking routing even when the account has PAYG credits.
The PR description calls this out as a fixed bug ("shouldCheckPAYG now allows PAYG when subscription details are unavailable"), but the fix is absent. Adding else if resp.IsSubscriber { shouldCheckPAYG = true } after the if resp.Subscription != nil block would close the gap and match buildApertisLimits behaviour.
Summary
Add Apertis provider quota monitoring with subscription + PAYG fallback support, and refactor the frontend quota system to properly handle the
openai_responseschannel type alongside the existingopenaitype.Spirit/Intent
Give Apertis users full visibility into their dual-model quota consumption (subscription cycle + PAYG fallback), while fixing the long-standing gap where
openai_responseschannels were not properly represented in the quota UI or type system.Key Changes
Apertis quota monitoring (new feature)
ApertisQuotaChecker(438 lines) queriesGET /v1/dashboard/billing/credits, normalizes subscription+PAYG dual model into unifiedQuotaDatacontract; URL detection via exact hostapi.apertis.ai;determineApertisStatus()picks better status between subscription and PAYG paths;buildApertisLimits()includes PAYG fallback when cycle is exhausted; 750 lines of tests covering PAYG scenarios, unlimited tokens, edge casesapertistoProviderQuotaStatusProviderTypeenum; 10 new localization strings in English and Chineseopenai_responsessupport (refactor + fix)isOpenaiType()helper inquota-badges.tsxreplaces 10+ repeatedchannel.type === 'openai'checks with a single predicate covering bothopenaiandopenai_responsesquotas.ts— merged 5 pairedopenai/openai_responsesvariants into single entries withtype: 'openai' | 'openai_responses', halving the discriminated union sizeparseChannelNode()function replaces untyped inlinemap()callback — eliminatesanycasts, properly castsquotaDataper provider, and handlesopenai_responsesalongsideopenaiQueryChannelsResponsetype replacesgraphqlRequest<any>with proper typingopenai_responsesin the grouping key (previouslyopenai_responseschannels could duplicateopenaientries in the quota list)nanogpt_responsesvariant added toProviderQuotaChannelunion (was missing)Bug fixes
buildApertisLimitsnow includes PAYG limits whenPaygFallbackEnabledis true orCycleQuotaRemaining <= 0, matchingdetermineApertisStatuslogicis_subscriber=truebutsubscriptionis nil —shouldCheckPAYGnow allows PAYG when subscription details are unavailabledetermineApertisStatususesstrings.EqualFoldfor subscription status checks, matchingdetermineSubscriptionStatustoken_usedtype guard — frontendgetApertisPercentage()and display logic now checktypeof token_used === 'number'before arithmetic, preventing NaN renderingRisks
/v1/dashboard/billing/creditspattern — base URL configurable viaApertisDefaultBaseURLisPaygRelevant) prevents noisy display for active subscribers but may hide PAYG info in edge subscription statesgetProviderType()consistency across existing providersopenai_responsesunion collapse changes the type shape — downstream consumers expecting separatetype: 'openai'discriminants must handle both values