Skip to content

feat(quota): add Apertis quota monitoring and refactor openai_responses handling#1793

Merged
looplj merged 11 commits into
looplj:unstablefrom
djdembeck:feat/apertis-quota-monitoring
Jun 10, 2026
Merged

feat(quota): add Apertis quota monitoring and refactor openai_responses handling#1793
looplj merged 11 commits into
looplj:unstablefrom
djdembeck:feat/apertis-quota-monitoring

Conversation

@djdembeck

@djdembeck djdembeck commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Add Apertis provider quota monitoring with subscription + PAYG fallback support, and refactor the frontend quota system to properly handle the openai_responses channel type alongside the existing openai type.

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_responses channels were not properly represented in the quota UI or type system.

Key Changes

Apertis quota monitoring (new feature)

  • Backend: New ApertisQuotaChecker (438 lines) queries GET /v1/dashboard/billing/credits, normalizes subscription+PAYG dual model into unified QuotaData contract; URL detection via exact host api.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 cases
  • Frontend: Subscription cycle quota with plan labels, PAYG fallback spending bars (when enabled), token usage progress bars, account balances — hidden for active subscribers without fallback to avoid noise
  • Schema & i18n: Added apertis to ProviderQuotaStatusProviderType enum; 10 new localization strings in English and Chinese

openai_responses support (refactor + fix)

  • isOpenaiType() helper in quota-badges.tsx replaces 10+ repeated channel.type === 'openai' checks with a single predicate covering both openai and openai_responses
  • Collapsed union variants in quotas.ts — merged 5 paired openai/openai_responses variants into single entries with type: 'openai' | 'openai_responses', halving the discriminated union size
  • parseChannelNode() function replaces untyped inline map() callback — eliminates any casts, properly casts quotaData per provider, and handles openai_responses alongside openai
  • QueryChannelsResponse type replaces graphqlRequest<any> with proper typing
  • Channel deduplication now includes openai_responses in the grouping key (previously openai_responses channels could duplicate openai entries in the quota list)
  • nanogpt_responses variant added to ProviderQuotaChannel union (was missing)

Bug fixes

  • PAYG limit omitted for active subscribers with exhausted cyclebuildApertisLimits now includes PAYG limits when PaygFallbackEnabled is true or CycleQuotaRemaining <= 0, matching determineApertisStatus logic
  • PAYG skipped when is_subscriber=true but subscription is nilshouldCheckPAYG now allows PAYG when subscription details are unavailable
  • Case-sensitive status comparisondetermineApertisStatus uses strings.EqualFold for subscription status checks, matching determineSubscriptionStatus
  • Missing token_used type guard — frontend getApertisPercentage() and display logic now check typeof token_used === 'number' before arithmetic, preventing NaN rendering

Risks

  • Apertis API endpoint structure may differ from assumed /v1/dashboard/billing/credits pattern — base URL configurable via ApertisDefaultBaseURL
  • PAYG fallback relevance gate (isPaygRelevant) prevents noisy display for active subscribers but may hide PAYG info in edge subscription states
  • URL detection uses Pattern B (no dedicated channel type) — relies on getProviderType() consistency across existing providers
  • openai_responses union collapse changes the type shape — downstream consumers expecting separate type: 'openai' discriminants must handle both values

djdembeck added 6 commits May 31, 2026 11:31
- 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
@djdembeck djdembeck changed the title feat(quota): add Apertis provider quota monitoring with PAYG fallback feat(quota): add Apertis quota monitoring and refactor openai_responses handling Jun 6, 2026
@greptile-apps

greptile-apps Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds Apertis provider quota monitoring with a subscription+PAYG dual-model checker, and refactors the frontend quota system to handle openai_responses channels alongside openai throughout the type system and UI.

  • Backend: New ApertisQuotaChecker queries /v1/dashboard/billing/credits, normalizes the dual funding model into QuotaData, and introduces QuotaLimitTypeSubscriptionCycle to correctly separate subscription-cycle limits from PAYG-token limits so EffectiveStatus uses OR-semantics; 750 lines of tests cover the main scenarios.
  • Frontend: isOpenaiType() helper replaces 10+ repeated channel.type === 'openai' guards, parseChannelNode() eliminates any casts, and new ProviderApertisQuotaData type + matching JSX render subscription cycle, PAYG fallback spending, token usage, and account-balance rows with relevance gating to suppress noise for active subscribers.
  • Schema & i18n: apertis added to the ProviderQuotaStatusProviderType enum across ent schema, generated migration, and GraphQL; 10 new localization strings in English and Chinese.

Confidence Score: 4/5

Safe 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 is_subscriber=true but omits the subscription object, determineApertisStatus leaves the status at its initial "exhausted" value (PAYG is never evaluated), while buildApertisLimits correctly includes PAYG limits. The channel is blocked from routing even when PAYG credits exist. The PR description explicitly lists this as a fixed bug, but the fix is absent from the code. Everything else — the QuotaLimitTypeSubscriptionCycle OR-semantics fix, the openai_responses refactor, the ent schema changes, and the 750 lines of tests — is correct and clean.

internal/server/biz/provider_quota/apertis_checker.go — specifically the determineApertisStatus function and the shouldCheckPAYG initialisation path when subscription is nil.

Important Files Changed

Filename Overview
internal/server/biz/provider_quota/apertis_checker.go New Apertis quota checker with subscription+PAYG dual model; determineApertisStatus incorrectly keeps status "exhausted" when is_subscriber=true but subscription is nil, while buildApertisLimits includes PAYG limits for the same scenario, creating a routing-blocking inconsistency
frontend/src/components/quota-badges.tsx Added Apertis quota display with subscription and PAYG fallback UI; getApertisPercentage is missing a token_total > 0 guard that could yield NaN/Infinity for the battery icon percentage
frontend/src/features/system/data/quotas.ts Adds ProviderApertisQuotaData type and collapses paired openai/openai_responses union variants; new parseChannelNode function replaces untyped any casts; QueryChannelsResponse provides proper GraphQL response typing
internal/server/biz/provider_quota/apertis_checker_test.go Comprehensive test suite covering PAYG-only, subscription, suspended states, unlimited tokens, and edge cases; notably does not include a test for the is_subscriber=true+subscription=nil edge case
internal/server/biz/provider_quota/types.go Adds QuotaLimitTypeSubscriptionCycle constant to separate subscription-cycle limits from PAYG token limits, correctly enabling OR-semantics for routing availability
internal/server/biz/provider_quota/url_detection.go Adds api.apertis.ai to the URL-based provider detection map; straightforward, no issues
internal/ent/schema/provider_quota_status.go Extends provider_type enum to include apertis; trailing newline removed (minor style delta)
internal/ent/migrate/schema.go Generated schema migration adding apertis to the provider_type enum; consistent with schema and Go enum changes

Reviews (3): Last reviewed commit: "Merge origin/unstable into feat/apertis-..." | Re-trigger Greptile

Comment thread internal/server/biz/provider_quota/apertis_checker.go
Comment thread internal/server/biz/provider_quota/apertis_checker.go
Comment thread internal/server/biz/provider_quota/apertis_checker.go
djdembeck added 4 commits June 6, 2026 14:48
- 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.
@looplj

looplj commented Jun 7, 2026

Copy link
Copy Markdown
Owner

There are schema conflict, please help to resolve. Thanks.

@djdembeck

Copy link
Copy Markdown
Contributor Author

There are schema conflict, please help to resolve. Thanks.

Done

Comment on lines +195 to +215
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 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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.

@looplj looplj merged commit e5be621 into looplj:unstable Jun 10, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants