diff --git a/open-sse/services/usage.ts b/open-sse/services/usage.ts index 3f2c49e05e..5a442b4e6c 100644 --- a/open-sse/services/usage.ts +++ b/open-sse/services/usage.ts @@ -1424,6 +1424,7 @@ export const USAGE_FETCHER_PROVIDERS = [ "github", "gemini-cli", "antigravity", + "agy", "claude", "codex", "cursor", @@ -1467,6 +1468,7 @@ export async function getUsageForProvider( case "gemini-cli": return await getGeminiUsage(accessToken, providerSpecificData, projectId); case "antigravity": + case "agy": return await getAntigravityUsage(accessToken, providerSpecificData, projectId, id, options); case "claude": return await getClaudeUsage(accessToken); diff --git a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.tsx b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.tsx index 4579ecfd22..159c8bd46e 100644 --- a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.tsx +++ b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.tsx @@ -260,6 +260,7 @@ export function parseQuotaData(provider, data) { break; case "antigravity": + case "agy": if (data.quotas) { Object.entries(data.quotas).forEach(([modelKey, quota]: [string, any]) => { if (modelKey === "credits") { diff --git a/src/lib/usage/fetcher.ts b/src/lib/usage/fetcher.ts index 092de749d7..21a329d867 100644 --- a/src/lib/usage/fetcher.ts +++ b/src/lib/usage/fetcher.ts @@ -37,6 +37,7 @@ export async function getUsageForProvider(connection) { case "gemini-cli": return await getGeminiUsage(accessToken); case "antigravity": + case "agy": return await getAntigravityUsage( accessToken, providerSpecificData, diff --git a/src/lib/usage/providerLimits.ts b/src/lib/usage/providerLimits.ts index fa139381ce..bf6edd7df5 100644 --- a/src/lib/usage/providerLimits.ts +++ b/src/lib/usage/providerLimits.ts @@ -323,7 +323,7 @@ async function syncAntigravitySubscriptionIfNeeded( connection: ProviderConnectionLike, usage: JsonRecord ): Promise { - if (connection.provider !== "antigravity") return connection; + if (connection.provider !== "antigravity" && connection.provider !== "agy") return connection; const subscriptionInfo = usage.subscriptionInfo; if (!subscriptionInfo) return connection; diff --git a/tests/unit/agy-usage-quota.test.ts b/tests/unit/agy-usage-quota.test.ts new file mode 100644 index 0000000000..61213df837 --- /dev/null +++ b/tests/unit/agy-usage-quota.test.ts @@ -0,0 +1,82 @@ +import test, { mock } from "node:test"; +import assert from "node:assert/strict"; + +const usageModule = await import("../../open-sse/services/usage.ts"); +const providerLimitUtils = + await import("../../src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.tsx"); + +test("agy is registered for the background usage fetcher", () => { + assert.ok( + usageModule.USAGE_FETCHER_PROVIDERS.includes("agy"), + "agy should be fetched by the generic quota refresher" + ); +}); + +test("getUsageForProvider routes agy through the Antigravity usage implementation", async () => { + const fetchMock = mock.method(globalThis, "fetch", async () => ({ + ok: true, + json: async () => ({ + models: { + "gemini-3.5-flash-preview": { + quotaInfo: { + remainingFraction: 0.75, + resetTime: "2026-06-06T00:00:00Z", + }, + }, + }, + }), + })); + + try { + const result = await usageModule.getUsageForProvider( + { + id: "agy-test-conn", + provider: "agy", + accessToken: "fake-token", + providerSpecificData: {}, + }, + { forceRefresh: true } + ); + + assert.ok(result && typeof result === "object"); + assert.notEqual( + (result as { message?: string }).message, + "Usage API not implemented for agy", + "agy must not fall through to the unsupported-provider branch" + ); + assert.ok("quotas" in result, "agy should return quota data when upstream responds"); + + const quota = (result as { quotas: Record }).quotas["gemini-3.5-flash-preview"]; + assert.ok(quota, "should expose the Antigravity per-model quota for agy"); + assert.equal(quota.remainingPercentage, 75); + } finally { + fetchMock.mock.restore(); + } +}); + +test("parseQuotaData treats agy quota payloads like Antigravity", () => { + const parsed = providerLimitUtils.parseQuotaData("agy", { + quotas: { + credits: { remaining: 42 }, + "gemini-3.5-flash-preview": { + used: 250, + total: 1000, + remainingPercentage: 75, + }, + models: { used: 0, total: 0 }, + }, + }); + + assert.equal( + parsed.length, + 2, + "credits and model quota should be rendered, models summary skipped" + ); + const credits = parsed.find((quota: any) => quota.name === "credits"); + assert.ok(credits, "credits quota should be rendered"); + assert.equal(credits.isCredits, true); + + const modelQuota = parsed.find((quota: any) => quota.name === "gemini-3.5-flash-preview"); + assert.ok(modelQuota, "model quota should be rendered"); + assert.equal(modelQuota.remainingPercentage, 75); +});