mirror of
https://github.com/stablyai/orca.git
synced 2026-10-03 00:02:19 +00:00
fix(status-bar): show Antigravity's model-group pools instead of an empty segment (#24074)
The verbose bucket allowlist was written for Gemini's experimental models, and its fallback window was session-or-monthly. Antigravity reports one pool per model group and some tiers meter weekly only, so a signed-in account with an exhausted pool rendered an icon and no number. Antigravity bypasses the allowlist by provider, because its group names come from the account's tier and cannot be enumerated. Cursor stays name-matched so an unrecognised pool still falls back to the plan total. Refs #22511 Refs #16704
This commit is contained in:
@@ -165,9 +165,16 @@ function getProviderLetter(provider: ProviderRateLimits['provider']): string {
|
||||
// Why: Gemini exposes extra experimental buckets that made the pre-existing verbose footer noisy.
|
||||
const STATUS_BAR_BUCKET_NAMES = new Set(['Flash', 'Pro', '1.5 Pro'])
|
||||
|
||||
// Why: the allowlist above is Gemini's. Cursor's pools are its whole meter — filtering
|
||||
// them out leaves a signed-in account with an icon and no number at all.
|
||||
function isVisibleStatusBarBucket(name: string): boolean {
|
||||
/**
|
||||
* Why Antigravity is matched by provider and not by name: its pools are one per model group, and the
|
||||
* group names come from the account's own tier ("Gemini Models", "Claude and GPT models" today), so
|
||||
* there is no list to allow. Cursor stays name-matched on purpose — a pool Orca does not recognise
|
||||
* is filtered so the segment can fall back to the plan total instead of showing an unlabelled row.
|
||||
*/
|
||||
function isVisibleStatusBarBucket(name: string, provider: ProviderRateLimits['provider']): boolean {
|
||||
if (provider === 'antigravity') {
|
||||
return true
|
||||
}
|
||||
return STATUS_BAR_BUCKET_NAMES.has(name) || isCursorUsageBucket(name)
|
||||
}
|
||||
|
||||
@@ -179,10 +186,15 @@ function VerboseProviderUsage({
|
||||
display: UsagePercentageDisplay
|
||||
}): React.JSX.Element {
|
||||
if (p.buckets && p.buckets.length > 0) {
|
||||
const visibleBuckets = p.buckets.filter((bucket) => isVisibleStatusBarBucket(bucket.name))
|
||||
const visibleBuckets = p.buckets.filter((bucket) =>
|
||||
isVisibleStatusBarBucket(bucket.name, p.provider)
|
||||
)
|
||||
// Why: a provider whose buckets are all filtered out still has a headline
|
||||
// window worth showing rather than rendering an empty segment.
|
||||
const fallbackWindow = p.session ?? p.monthly ?? null
|
||||
// Why weekly is in the chain: a tier metered weekly only (Antigravity reports no 5h pool on
|
||||
// some tiers) has no session window, and omitting weekly rendered an empty segment for an
|
||||
// account that does have a limit worth showing.
|
||||
const fallbackWindow = p.session ?? p.monthly ?? p.weekly ?? null
|
||||
return (
|
||||
<>
|
||||
{visibleBuckets.map((bucket, index) => (
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
import { renderToStaticMarkup } from 'react-dom/server'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import type { ProviderRateLimits, RateLimitWindow } from '../../../../shared/rate-limit-types'
|
||||
|
||||
vi.mock('@/i18n/i18n', () => ({
|
||||
i18n: { language: 'en' },
|
||||
translate: (_key: string, fallback: string, values?: Record<string, string>) => {
|
||||
let result = fallback
|
||||
for (const [key, value] of Object.entries(values ?? {})) {
|
||||
result = result.replace(`{{${key}}}`, value)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@/lib/agent-catalog', () => ({
|
||||
AgentIcon: () => null
|
||||
}))
|
||||
|
||||
vi.mock('../../store', () => ({
|
||||
useAppStore: (selector: (state: { usagePercentageDisplay: 'used' | 'remaining' }) => unknown) =>
|
||||
selector({ usagePercentageDisplay: 'used' })
|
||||
}))
|
||||
|
||||
const WEEKLY_MINUTES = 10_080
|
||||
|
||||
function weeklyWindow(usedPercent: number): RateLimitWindow {
|
||||
return { usedPercent, windowMinutes: WEEKLY_MINUTES, resetsAt: null, resetDescription: null }
|
||||
}
|
||||
|
||||
/**
|
||||
* The record `agy -p "/usage"` produces for a tier metered weekly only, with the Gemini pool
|
||||
* genuinely exhausted — captured from agy 1.2.11.
|
||||
*/
|
||||
function antigravityLimits(overrides: Partial<ProviderRateLimits> = {}): ProviderRateLimits {
|
||||
return {
|
||||
provider: 'antigravity',
|
||||
session: null,
|
||||
weekly: weeklyWindow(100),
|
||||
buckets: [
|
||||
{ name: 'Gemini Models', ...weeklyWindow(100) },
|
||||
{ name: 'Claude and GPT models', ...weeklyWindow(0) }
|
||||
],
|
||||
updatedAt: Date.now(),
|
||||
error: null,
|
||||
status: 'ok',
|
||||
...overrides
|
||||
}
|
||||
}
|
||||
|
||||
describe('Antigravity status-bar segment', () => {
|
||||
it('renders both model-group pools by name', async () => {
|
||||
// Why: the verbose bucket allowlist was written for Gemini's experimental models, so
|
||||
// Antigravity's pools — whose names come from the account's tier and cannot be enumerated
|
||||
// ahead of time — were filtered out and the segment showed no number at all.
|
||||
const { ProviderSegment } = await import('./StatusBar')
|
||||
const markup = renderToStaticMarkup(
|
||||
<ProviderSegment p={antigravityLimits()} compact={false} display="used" mode="verbose" />
|
||||
)
|
||||
|
||||
expect(markup).toContain('Gemini Models')
|
||||
expect(markup).toContain('Claude and GPT models')
|
||||
expect(markup).toContain('100%')
|
||||
})
|
||||
|
||||
it('shows the weekly window when a tier reports no session pool', async () => {
|
||||
// Why: the verbose fallback chain was `session ?? monthly`, so a weekly-only provider with a
|
||||
// real limit rendered an empty segment.
|
||||
const { ProviderSegment } = await import('./StatusBar')
|
||||
const markup = renderToStaticMarkup(
|
||||
<ProviderSegment
|
||||
p={antigravityLimits({ buckets: [] })}
|
||||
compact={false}
|
||||
display="used"
|
||||
mode="verbose"
|
||||
/>
|
||||
)
|
||||
|
||||
expect(markup).toContain('100%')
|
||||
})
|
||||
|
||||
it('still renders a reading when only the weekly window is known', async () => {
|
||||
const { ProviderSegment } = await import('./StatusBar')
|
||||
const markup = renderToStaticMarkup(
|
||||
<ProviderSegment
|
||||
p={antigravityLimits({ buckets: undefined })}
|
||||
compact={false}
|
||||
display="used"
|
||||
mode="verbose"
|
||||
/>
|
||||
)
|
||||
|
||||
expect(markup).toContain('100%')
|
||||
})
|
||||
|
||||
it('does not widen the allowlist for providers that rely on it', async () => {
|
||||
// Why: Gemini's experimental buckets are still filtered; only providers whose buckets are the
|
||||
// whole meter bypass the allowlist.
|
||||
const { ProviderSegment } = await import('./StatusBar')
|
||||
const markup = renderToStaticMarkup(
|
||||
<ProviderSegment
|
||||
p={{
|
||||
provider: 'gemini',
|
||||
session: weeklyWindow(12),
|
||||
weekly: null,
|
||||
buckets: [
|
||||
{ name: 'Pro', ...weeklyWindow(30) },
|
||||
{ name: 'Some Experimental Model', ...weeklyWindow(80) }
|
||||
],
|
||||
updatedAt: Date.now(),
|
||||
error: null,
|
||||
status: 'ok'
|
||||
}}
|
||||
compact={false}
|
||||
display="used"
|
||||
mode="verbose"
|
||||
/>
|
||||
)
|
||||
|
||||
expect(markup).toContain('Pro')
|
||||
expect(markup).not.toContain('Some Experimental Model')
|
||||
})
|
||||
})
|
||||
@@ -137,7 +137,11 @@ function createPendingProviderSnapshot(providerId: UsageProviderId): ProviderRat
|
||||
session: null,
|
||||
weekly: null,
|
||||
...(providerId === 'opencode-go' ? { monthly: null } : {}),
|
||||
...(providerId === 'gemini' || providerId === 'cursor' ? { buckets: [] } : {}),
|
||||
// Why antigravity joins these: it reports one pool per model group, so its pending skeleton
|
||||
// has to be bucket-shaped too or the segment changes shape once the first reading lands.
|
||||
...(providerId === 'gemini' || providerId === 'cursor' || providerId === 'antigravity'
|
||||
? { buckets: [] }
|
||||
: {}),
|
||||
updatedAt: 0,
|
||||
error: null,
|
||||
status: 'fetching'
|
||||
|
||||
Reference in New Issue
Block a user