mirror of
https://github.com/stablyai/orca.git
synced 2026-10-03 08:02:12 +00:00
sim: merge PR #17200
This commit is contained in:
@@ -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'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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 =
|
||||
|
||||
Reference in New Issue
Block a user