sim: merge PR #17200

This commit is contained in:
Brennan Benson
2026-08-30 15:53:52 -07:00
6 changed files with 92 additions and 22 deletions
@@ -30,15 +30,21 @@ describe('deriveAntigravityRateLimits', () => {
expect(antigravity.error).toBeNull()
})
it('reports unavailable without quoting the Gemini failure', () => {
it('settles a failed quota read as an error without quoting the Gemini failure', () => {
const antigravity = deriveAntigravityRateLimits(
geminiSnapshot('error', 'Gemini project ID not found')
)
expect(antigravity.provider).toBe('antigravity')
expect(antigravity.status).toBe('unavailable')
// Why: the Gemini read is Antigravity's read of the shared quota, so its failure is a failed
// read, not an absent one — `unavailable` would discard the last good snapshot downstream.
expect(antigravity.status).toBe('error')
expect(antigravity.error).not.toContain('Gemini project ID not found')
expect(antigravity.error).toContain('Antigravity usage is not available')
// Why: still Orca's own Antigravity wording — but this lane retains a reading, so it may not
// say the usage is unavailable. The no-sign-in lane below keeps that phrasing.
expect(antigravity.error).toContain('Antigravity usage')
expect(antigravity.error).toContain('shared Google Code Assist quota')
expect(antigravity.error).not.toContain('not available')
expect(antigravity.session).toBeNull()
expect(antigravity.weekly).toBeNull()
})
@@ -1,29 +1,35 @@
import type { ProviderRateLimits } from '../../shared/rate-limit-types'
// Why: the Antigravity CLI keeps its token in the OS keyring, not in the files the Gemini
// fetcher reads, so Orca never actually queries Antigravity. Only a *successful* Gemini read
// describes shared Google Code Assist quota; republishing a Gemini failure under the
// Antigravity provider id surfaced "Refresh failed" for a request that was never attempted.
// Why: Orca has no Antigravity endpoint — the Gemini read of shared Google Code Assist quota *is*
// Antigravity's read, which is why a successful one is mirrored verbatim. #15876 stopped this row
// quoting Gemini's raw error and blaming a sign-in that exists; both still hold. It also settled a
// failed read as `unavailable`, taken as a copy choice but really a retention verdict —
// `applyStalePolicy` discards on `unavailable`, retains on `error` — so one failure wiped this row
// and spared Gemini's. One read cannot be Antigravity's when it succeeds and nobody's when it
// fails, so a failed read settles `error` here too. That brings back the generic "Refresh failed"
// label deliberately: the reason below names the shared quota, so no Antigravity request is
// implied, and a bespoke label would suppress the tooltip's "showing cached data" suffix.
const ANTIGRAVITY_NO_SIGN_IN_REASON =
'Antigravity usage is not available. Orca can only show shared Google Code Assist quota while a Gemini CLI sign-in is connected.'
// Why: a Gemini `error` means the sign-in exists and the quota read failed, so blaming a missing sign-in would misdirect the user.
// Why: this lane now retains the last reading, so the tooltip prints this line above a live
// meter — claiming the usage is unavailable would contradict the numbers beside it.
const ANTIGRAVITY_QUOTA_UNREADABLE_REASON =
'Antigravity usage is not available. Orca reads it from the shared Google Code Assist quota, which could not be read right now.'
'Orca reads Antigravity usage from the shared Google Code Assist quota, which could not be read right now.'
export function deriveAntigravityRateLimits(gemini: ProviderRateLimits): ProviderRateLimits {
if (gemini.status === 'ok') {
return { ...gemini, provider: 'antigravity' }
}
// Why: a Gemini `error` means the sign-in exists and the read failed; anything else means there
// was nothing to read, which is genuinely absent rather than failed.
const quotaReadFailed = gemini.status === 'error'
return {
provider: 'antigravity',
session: null,
weekly: null,
// Why: reuse the Gemini timestamp so activation freshness checks don't force a refetch every cycle.
updatedAt: gemini.updatedAt,
error:
gemini.status === 'unavailable'
? ANTIGRAVITY_NO_SIGN_IN_REASON
: ANTIGRAVITY_QUOTA_UNREADABLE_REASON,
status: 'unavailable'
error: quotaReadFailed ? ANTIGRAVITY_QUOTA_UNREADABLE_REASON : ANTIGRAVITY_NO_SIGN_IN_REASON,
status: quotaReadFailed ? 'error' : 'unavailable'
}
}
+3 -1
View File
@@ -68,7 +68,9 @@ export async function readGeminiCredentials(): Promise<GeminiCredentials | null>
) {
return parsed as GeminiCredentials
}
return null
// A file that exists but does not match the credential contract is a failed
// read. Preserve that distinction so callers do not report it as signed out.
throw new Error('Gemini CLI credentials file is invalid')
} catch (err) {
if (err && typeof err === 'object' && 'code' in err && err.code === 'ENOENT') {
return null
@@ -153,6 +153,36 @@ describe('fetchGeminiRateLimits fallback oauth creds', () => {
expect(result.weekly).toBeNull()
})
it('reports a present but malformed oauth_creds.json as a failed read', async () => {
readFileMock.mockImplementation(async (filePath: string) => {
if (filePath.includes('auth.json')) {
return JSON.stringify({})
}
if (filePath.includes('oauth_creds.json')) {
return JSON.stringify({
access_token: 42,
refresh_token: 'refresh',
expiry_date: Date.now()
})
}
throw { code: 'ENOENT' }
})
const result = await fetchGeminiRateLimits(true)
expect(result.status).toBe('error')
expect(result.error).toContain('credentials file is invalid')
})
it('keeps an actually missing oauth_creds.json as absent', async () => {
readFileMock.mockRejectedValue({ code: 'ENOENT' })
const result = await fetchGeminiRateLimits(true)
expect(result.status).toBe('unavailable')
expect(result.error).toContain('credentials not found')
})
it('returns error when loadCodeAssist cannot resolve a project for oauth_creds path', async () => {
// Why: when the fallback (oauth_creds.json) path has no project embedded
// and loadCodeAssist fails, we surface a clear "project ID not found"
@@ -54,7 +54,7 @@ describe('RateLimitService Antigravity usage', () => {
vi.mocked(fetchCodexRateLimits).mockResolvedValue(okProvider('codex', 20))
})
it('does not republish a Gemini failure as an Antigravity refresh failure', async () => {
it('does not republish the Gemini failure text under the Antigravity provider', async () => {
vi.mocked(fetchGeminiRateLimits).mockResolvedValue(
errorProvider('gemini', 'Gemini project ID not found')
)
@@ -63,7 +63,8 @@ describe('RateLimitService Antigravity usage', () => {
await service.refresh()
const state = service.getState()
expect(state.antigravity?.status).toBe('unavailable')
// Why: a failed shared-quota read is a failed Antigravity read; only the wording is Antigravity's.
expect(state.antigravity?.status).toBe('error')
expect(state.antigravity?.error).not.toContain('Gemini project ID not found')
expect(state.antigravity?.session).toBeNull()
// Why: the real Gemini failure must still surface under its own provider.
@@ -83,7 +84,7 @@ describe('RateLimitService Antigravity usage', () => {
expect(state.antigravity?.session?.usedPercent).toBe(42)
})
it('never leaves a cached Antigravity snapshot in the error retry lane', async () => {
it('keeps the last shared-quota reading when the quota read fails, exactly as Gemini does', async () => {
vi.mocked(fetchGeminiRateLimits).mockResolvedValueOnce(okProvider('gemini', 42, Date.now()))
const service = new RateLimitService()
await service.refresh()
@@ -93,8 +94,32 @@ describe('RateLimitService Antigravity usage', () => {
)
await service.refresh()
// Why: stale-retention would otherwise show Gemini numbers as "Refresh failed" Antigravity usage.
expect(service.getState().antigravity?.status).toBe('unavailable')
expect(service.getState().antigravity?.session).toBeNull()
// Why: one read backs both rows, so one failure must not wipe one row and spare the other.
const state = service.getState()
expect(state.antigravity?.status).toBe('error')
expect(state.antigravity?.session?.usedPercent).toBe(42)
expect(state.gemini?.status).toBe('error')
expect(state.gemini?.session?.usedPercent).toBe(42)
})
it('does not quote the Gemini failure while showing a retained Antigravity reading', async () => {
vi.mocked(fetchGeminiRateLimits).mockResolvedValueOnce(okProvider('gemini', 42, Date.now()))
const service = new RateLimitService()
await service.refresh()
vi.mocked(fetchGeminiRateLimits).mockResolvedValue(
errorProvider('gemini', 'Token refresh failed')
)
await service.refresh()
// Why: #15876's real protection — the Antigravity row owns its wording even in the failure lane.
const antigravity = service.getState().antigravity
expect(antigravity?.error).not.toContain('Token refresh failed')
expect(antigravity?.error).toContain('shared Google Code Assist quota')
// Why: retention is what makes this lane new — the tooltip prints this reason under
// "Refresh failed — showing cached data" and directly above the retained meter, so a reason
// that calls the usage unavailable contradicts the numbers rendered next to it.
expect(antigravity?.session?.usedPercent).toBe(42)
expect(antigravity?.error).not.toContain('not available')
})
})
+2 -1
View File
@@ -1767,7 +1767,8 @@ export class RateLimitService {
status: 'error'
} satisfies ProviderRateLimits)
// Why: Antigravity can only borrow a *successful* Gemini read; a Gemini failure is not an Antigravity failure.
// Why: the Gemini read of shared Code Assist quota IS Antigravity's read, so its failure is
// Antigravity's failed read too — see antigravity-usage-mirror.ts for why that settles `error`.
const antigravity = deriveAntigravityRateLimits(gemini)
const opencodeGo =