Merge pull request #10912 from diegosouzapw/fix/10095-antigravity-multiaccount-quota

fix(domain): treat unreported Antigravity quota fraction as unknown, not exhausted (#10095)
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-08-20 21:18:16 -03:00
committed by GitHub
3 changed files with 130 additions and 6 deletions

View File

@@ -0,0 +1 @@
- fix(domain): stop treating an unreported Antigravity quota fraction (`fractionReported:false`) as 0% remaining in `quotaCache.ts`, which was falsely marking every fresh/newly-connected account as exhausted and blocking multi-account rotation (#10095)

View File

@@ -44,6 +44,13 @@ import { getAntigravityQuotaFamily } from "@omniroute/open-sse/services/antigrav
interface QuotaInfo {
remainingPercentage: number;
resetAt: string | null;
// #10095 — upstream explicitly told us it did NOT report this window's
// fraction (e.g. a fresh Antigravity account or a newly-launched
// -tiered model id Google hasn't wired quota telemetry for yet).
// `undefined`/`true` means the value is a real, upstream-reported
// percentage; `false` means "unknown", so callers must not treat the
// defaulted-to-0 `remainingPercentage` as genuine exhaustion.
fractionReported?: boolean;
}
interface QuotaCacheEntry {
@@ -113,7 +120,10 @@ const MAX_CONCURRENT_REFRESHES = 5;
function isExhausted(quotas: Record<string, QuotaInfo>): boolean {
const entries = Object.values(quotas);
if (entries.length === 0) return false;
return entries.every((q) => q.remainingPercentage <= 0);
// #10095 — a window whose fraction was never reported by upstream must
// never single-handedly flip the whole connection to exhausted; treat it
// as available (mirrors the guard in genericQuotaFetcher.ts).
return entries.every((q) => q.fractionReported !== false && q.remainingPercentage <= 0);
}
/**
@@ -237,6 +247,9 @@ function normalizeQuotas(rawQuotas: Record<string, any>): Record<string, QuotaIn
safePercentage(q.remainingPercentage) ??
(q.total > 0 ? Math.round(((q.total - (q.used || 0)) / q.total) * 100) : 0),
resetAt: q.resetAt || null,
// #10095 — thread through the "did upstream actually report this
// window's fraction" signal (see UsageQuota in usage/quota.ts).
fractionReported: q.fractionReported === false ? false : undefined,
};
}
}
@@ -641,11 +654,14 @@ export function getQuotaWindowStatus(
usedPercentage,
resetAt,
// If reset time has already passed, avoid stale cached percentages blocking selection.
reachedThreshold: windowExpired
? false
: remainingPercentage <= 0
? true
: usedPercentage >= thresholdPercent,
// #10095 — a window whose fraction upstream never reported is "unknown",
// not "0% remaining"; never let it reach the exhaustion threshold.
reachedThreshold:
windowExpired || window.fractionReported === false
? false
: remainingPercentage <= 0
? true
: usedPercentage >= thresholdPercent,
};
}

View File

@@ -0,0 +1,107 @@
// #10095 — Antigravity multi-account "all exhausted" false positive.
//
// src/domain/quotaCache.ts is the FIRST, unconditional gate every chat request
// passes through (src/sse/services/auth.ts::getProviderCredentialsWithQuotaPreflight).
// When Google's Cloud Code API doesn't report `remainingFraction` for a model
// (fresh accounts, newly-launched -tiered model ids), open-sse/services/usage/
// antigravity.ts writes `fractionReported:false` but defaults
// `remainingPercentage` to 0 — quotaCache.ts previously read only the numeric
// percentage and treated that as genuine 0%-remaining exhaustion, so every
// freshly-connected Antigravity account looked simultaneously (and falsely)
// dead, and getProviderCredentials returned "All antigravity accounts have
// exhausted their quota" before ever trying one.
import { test, describe, before, after } from "node:test";
import assert from "node:assert/strict";
describe("#10095 — quotaCache respects Antigravity fractionReported:false", () => {
before(async () => {
const { __clearForTests } = await import("../../src/domain/quotaCache.ts");
__clearForTests();
});
after(async () => {
const { __clearForTests } = await import("../../src/domain/quotaCache.ts");
__clearForTests();
});
test("unreported quota window (fractionReported:false) is NOT treated as exhausted", async () => {
const { setQuotaCache, isQuotaExhaustedForRequest } = await import(
"../../src/domain/quotaCache.ts"
);
const connectionId = "10095-fresh-account";
// Exact shape open-sse/services/usage/antigravity.ts:660-694 writes when
// Google's API omits remainingFraction for this model.
setQuotaCache(connectionId, "antigravity", {
"gemini-3.7-flash-tiered": {
used: 0,
total: 1000,
resetAt: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
remainingPercentage: 0,
unlimited: false,
fractionReported: false,
quotaSource: "fetchAvailableModels",
},
});
const exhausted = isQuotaExhaustedForRequest(
connectionId,
"antigravity",
"agy/gemini-3.7-flash-tiered"
);
assert.equal(exhausted, false, "must NOT treat an unreported quota window as exhausted");
});
test("companion: a REAL 0% window (fractionReported:true) still reports exhausted", async () => {
const { setQuotaCache, isQuotaExhaustedForRequest } = await import(
"../../src/domain/quotaCache.ts"
);
const connectionId = "10095-genuinely-exhausted-account";
setQuotaCache(connectionId, "antigravity", {
"gemini-3.7-flash-tiered": {
used: 1000,
total: 1000,
resetAt: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
remainingPercentage: 0,
unlimited: false,
fractionReported: true,
quotaSource: "retrieveUserQuota",
},
});
const exhausted = isQuotaExhaustedForRequest(
connectionId,
"antigravity",
"agy/gemini-3.7-flash-tiered"
);
assert.equal(
exhausted,
true,
"the fix must not blanket-disable exhaustion detection — a genuinely reported 0% window still exhausts"
);
});
test("issue follow-up model id shape (agy/gemini-3.7-flash-tiered) still resolves its family", async () => {
const { setQuotaCache, isQuotaExhaustedForRequest } = await import(
"../../src/domain/quotaCache.ts"
);
const connectionId = "10095-agy-tiered-family";
setQuotaCache(connectionId, "agy", {
"agy/gemini-3.7-flash-tiered": {
used: 500,
total: 1000,
resetAt: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
remainingPercentage: 50,
unlimited: false,
fractionReported: true,
quotaSource: "retrieveUserQuota",
},
});
const exhausted = isQuotaExhaustedForRequest(
connectionId,
"agy",
"agy/gemini-3.7-flash-tiered"
);
assert.equal(
exhausted,
false,
"non-regression: family resolution for the agy/gemini-3.7-flash-tiered id must keep working"
);
});
});