Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions apps/server/src/provider/antigravityUsageLimits.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,52 @@ describe("Antigravity subscription limits", () => {
}).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
);

it.effect("adds Gemini model quotas when no endpoint's summary reports Gemini", () =>
Effect.gen(function* () {
const { probe, requests } = yield* fixture({
// Both endpoints have answered with only the Claude/GPT group while
// the account's Gemini models still carried a quota.
summary: {
groups: [
{
displayName: "Claude and GPT models",
buckets: [
{ bucketId: "3p-weekly", window: "weekly", remainingFraction: 1 },
{ bucketId: "3p-5h", window: "5h", remainingFraction: 1 },
],
},
],
},
models: {
models: {
"gemini-a": {
modelProvider: "MODEL_PROVIDER_GOOGLE",
quotaInfo: { remainingFraction: 0.99, resetTime: "2026-10-24T04:29:07Z" },
},
"claude-a": {
modelProvider: "MODEL_PROVIDER_ANTHROPIC",
quotaInfo: { remainingFraction: 1, resetTime: "2026-10-24T06:50:54Z" },
},
},
},
});
const limits = yield* probe;
expect(limits.windows.map((window) => window.id).toSorted()).toEqual([
"3p-5h",
"3p-weekly",
"gemini-quota",
]);
expect(limits.windows.find((window) => window.id === "gemini-quota")).toMatchObject({
label: "Gemini · Reported quota",
kind: "other",
resetsAt: "2026-10-24T04:29:07.000Z",
});
expect(
requests.filter((request) => request.url.endsWith(":fetchAvailableModels")),
).toHaveLength(2);
}).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
);

it.effect("keeps one endpoint's quotas when the other fails", () =>
Effect.gen(function* () {
const { probe } = yield* fixture({
Expand Down
13 changes: 10 additions & 3 deletions apps/server/src/provider/antigravityUsageLimits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ function summaryWindows(summary: typeof Summary.Type) {
return [...windows.values()];
}

/** Older accounts expose model quotas without a window length. Do not invent a reset cadence. */
/** Model quotas carry no window length. Do not invent a reset cadence. */
function modelWindows(models: typeof Models.Type) {
const windows = new Map<string, ServerProviderUsageWindow>();
for (const [modelId, model] of Object.entries(models.models)) {
Expand Down Expand Up @@ -282,11 +282,18 @@ export const makeAntigravityUsageProbe = Effect.fn("makeAntigravityUsageProbe")(
const summary = yield* fetchWindows("retrieveUserQuotaSummary", (response) =>
decodeSummary(response).pipe(Effect.map(summaryWindows)),
);
const windows = summary.length
? summary
// Every endpoint's summary has also answered with only the Claude/GPT
// group while the Gemini models still carried a quota, so read Gemini from
// the model list whenever no summary reports it.
const reportsGemini = summary.some((window) => window.id.startsWith("gemini-"));
const models = reportsGemini
? []
: yield* fetchWindows("fetchAvailableModels", (response) =>
decodeModels(response).pipe(Effect.map(modelWindows)),
);
const windows = summary.length
? [...summary, ...models.filter((window) => window.id.startsWith("gemini-"))]
: models;
if (windows.length === 0) {
return makeUnavailableUsageLimits({
checkedAt,
Expand Down
Loading