diff --git a/packages/ai/src/usage/google-antigravity.ts b/packages/ai/src/usage/google-antigravity.ts index 9e8887c5e..435e039a4 100644 --- a/packages/ai/src/usage/google-antigravity.ts +++ b/packages/ai/src/usage/google-antigravity.ts @@ -312,9 +312,14 @@ const ANTIGRAVITY_DAILY_WINDOW_MS = 24 * 60 * 60 * 1000; * Anthropic / OpenAI) per tier per window, and {@link fetchAntigravityUsage} * sorts them ascending by `remainingFraction` — so `limits[0]` is always the * most-pressured counter for the credential, and `limits[1]` (when present) - * is the next-most-pressured. Mapping those to {primary, secondary} lets the - * ranker compare two credentials on both their bottleneck counter and their - * runner-up before falling back to round-robin. + * is the next-most-pressured counter. + * + * `AuthStorage` compares the `secondary*` ranking metrics before `primary*` + * because other providers model a long-window budget as secondary. Antigravity + * does not expose a short/long split; every counter is a sibling bottleneck. + * Therefore the most-pressured counter goes in `secondary`, with the runner-up + * in `primary`, so proactive account selection always ranks the bottleneck + * before any healthier sibling counter. * * The Antigravity API exposes `resetTime` but not window duration, so the * drain-rate calculation depends on `windowDefaults`. Antigravity quotas are @@ -324,7 +329,7 @@ const ANTIGRAVITY_DAILY_WINDOW_MS = 24 * 60 * 60 * 1000; */ export const antigravityRankingStrategy: CredentialRankingStrategy = { findWindowLimits(report) { - return { primary: report.limits[0], secondary: report.limits[1] }; + return { primary: report.limits[1], secondary: report.limits[0] }; }, windowDefaults: { primaryMs: ANTIGRAVITY_DAILY_WINDOW_MS, diff --git a/packages/ai/test/google-antigravity-usage.test.ts b/packages/ai/test/google-antigravity-usage.test.ts index 4a734cb9c..83bbe8b8c 100644 --- a/packages/ai/test/google-antigravity-usage.test.ts +++ b/packages/ai/test/google-antigravity-usage.test.ts @@ -257,18 +257,20 @@ describe("antigravity ranking strategy", () => { }; } - it("maps the most-pressured counter to primary and the runner-up to secondary", () => { + it("maps the most-pressured counter to secondary because AuthStorage compares secondary first", () => { // fetchAntigravityUsage sorts ascending by remainingFraction, so a real - // report's limits[0] is always the bottleneck — the ranking strategy - // MUST surface that as the primary signal, not the first model alphabetically. + // report's limits[0] is always the bottleneck. AuthStorage compares the + // secondary ranking metrics before primary, so Antigravity must put the + // bottleneck there; otherwise [5%, 90%] remaining can beat [40%, 40%] + // because the runner-up counter looks healthier. const report = { provider: "google-antigravity" as const, fetchedAt: Date.now(), limits: [makeLimit(0.05, "Anthropic"), makeLimit(0.4, "Google"), makeLimit(0.9, "OpenAI")], }; const { primary, secondary } = antigravityRankingStrategy.findWindowLimits(report); - expect(primary?.label).toBe("Anthropic"); - expect(secondary?.label).toBe("Google"); + expect(secondary?.label).toBe("Anthropic"); + expect(primary?.label).toBe("Google"); }); it("returns undefined windows when the credential has no usage limits", () => {