diff --git a/src/providers/google-agy.ts b/src/providers/google-agy.ts index b22f3c07..c86fcd7f 100644 --- a/src/providers/google-agy.ts +++ b/src/providers/google-agy.ts @@ -101,6 +101,17 @@ function formatRemainingAmount(value: string | undefined): string | undefined { return `${display} left`; } +export function formatAgyCredentialGroup(credentialName: string, familyLabel: string): string { + const trimmed = credentialName.trim(); + if (!familyLabel?.trim()) return trimmed; + const match = trimmed.match(/^(\[[^\]]+\])(.*)$/); + if (match) { + const inside = match[1]?.slice(1, -1) ?? ""; + return `[${inside}: ${familyLabel}]${match[2] ?? ""}`; + } + return `[${trimmed}: ${familyLabel}]`; +} + async function isAgyConfigured(): Promise { try { return await hasAgyQuotaRuntimeAvailable(); @@ -161,13 +172,15 @@ export const googleAgyProvider: QuotaProvider = { const entries: QuotaToastEntry[] = []; const errors: QuotaProviderResult["errors"] = []; for (const [index, { row, result }] of results.entries()) { - const group = names[index] ?? "Google AGY"; + const credentialName = names[index] ?? "Google AGY"; if (!result) continue; if (!result.success) { - errors.push({ label: group, message: result.error }); + errors.push({ label: credentialName, message: result.error }); continue; } for (const bucket of [...result.buckets].sort(compareBuckets)) { + const familyLabel = formatAgyFamilyLabel(bucket.family); + const group = formatAgyCredentialGroup(credentialName, familyLabel); const right = formatRemainingAmount(bucket.remainingAmount); entries.push({ accounting: { @@ -177,7 +190,7 @@ export const googleAgyProvider: QuotaProvider = { authority: "provider_reported", sourceId: row.id, }, - name: `${group} ${formatAgyFamilyLabel(bucket.family)} ${bucket.windowLabel}`, + name: `${group} ${bucket.windowLabel}`, group, label: `${bucket.windowLabel}:`, sortPriority: windowRank(bucket.window), @@ -188,7 +201,7 @@ export const googleAgyProvider: QuotaProvider = { } errors.push( ...(result.errors ?? []).map((error) => ({ - label: group, + label: credentialName, message: error.error, })), ); diff --git a/tests/providers.google-agy.test.ts b/tests/providers.google-agy.test.ts index 96af7999..7f5ddc53 100644 --- a/tests/providers.google-agy.test.ts +++ b/tests/providers.google-agy.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it, vi } from "vitest"; -import { googleAgyProvider } from "../src/providers/google-agy.js"; +import { formatAgyCredentialGroup, googleAgyProvider } from "../src/providers/google-agy.js"; import { expectAttemptedWithErrorLabel, expectAttemptedWithNoErrors, @@ -251,4 +251,29 @@ describe("google agy provider", () => { expect(googleAgyProvider.matchesCurrentModel?.("google-agy-auth/gpt-4")).toBe(true); expect(googleAgyProvider.matchesCurrentModel?.("google/claude-opus")).toBe(false); }); + + describe("formatAgyCredentialGroup", () => { + it("formats credential names with active markers", () => { + expect( + formatAgyCredentialGroup("[Google AGY Antigravity CLI (plugin) 2] (active)", "Gemini"), + ).toBe("[Google AGY Antigravity CLI (plugin) 2: Gemini] (active)"); + }); + + it("formats simple bracketed names", () => { + expect(formatAgyCredentialGroup("[Google AGY]", "Claude/GPT")).toBe( + "[Google AGY: Claude/GPT]", + ); + }); + + it("formats names without brackets", () => { + expect(formatAgyCredentialGroup("Google AGY Work", "Gemini")).toBe( + "[Google AGY Work: Gemini]", + ); + }); + + it("trims whitespace and handles empty family label", () => { + expect(formatAgyCredentialGroup(" [Google AGY] ", "Gemini")).toBe("[Google AGY: Gemini]"); + expect(formatAgyCredentialGroup("[Google AGY]", "")).toBe("[Google AGY]"); + }); + }); }); diff --git a/tests/providers.google-db-credentials.test.ts b/tests/providers.google-db-credentials.test.ts index 16eae847..698d4cdc 100644 --- a/tests/providers.google-db-credentials.test.ts +++ b/tests/providers.google-db-credentials.test.ts @@ -67,8 +67,27 @@ describe.each([ accountIndex: 0, percentRemaining: 70, }, + { + family: "Claude and GPT models", + window: "weekly", + windowLabel: "Weekly", + accountIndex: 0, + percentRemaining: 50, + }, ], }, + expectedGroups: [ + ["[Google AGY Work: Gemini] (active)", "active-id"], + ["[Google AGY Work: Claude/GPT] (active)", "active-id"], + ["[Google AGY Work 2: Gemini]", "other-id"], + ["[Google AGY Work 2: Claude/GPT]", "other-id"], + ], + expectedNames: [ + "[Google AGY Work: Gemini] (active) Weekly", + "[Google AGY Work: Claude/GPT] (active) Weekly", + "[Google AGY Work 2: Gemini] Weekly", + "[Google AGY Work 2: Claude/GPT] Weekly", + ], }, { name: "Gemini CLI", @@ -79,8 +98,20 @@ describe.each([ success: true, buckets: [{ displayName: "Gemini 2.5", percentRemaining: 70 }], }, + expectedGroups: [ + ["[Gemini CLI Work] (active)", "active-id"], + ["[Gemini CLI Work 2]", "other-id"], + ], + expectedNames: ["[Gemini CLI Work] (active) Gemini 2.5", "[Gemini CLI Work 2] Gemini 2.5"], }, -])("$name DB credentials", ({ integrationId, provider, query, result }) => { +])("$name DB credentials", ({ + integrationId, + provider, + query, + result, + expectedGroups, + expectedNames, +}) => { beforeEach(() => { vi.clearAllMocks(); mocks.rows.mockResolvedValue([ @@ -106,12 +137,9 @@ describe.each([ const output = await provider.fetch({ client: {}, config: {} } as never); expect(query).toHaveBeenCalledTimes(2); - expect(output.entries.map((entry) => [entry.group, entry.accounting.sourceId])).toEqual([ - [ - `[${provider.id === "google-agy" ? "Google AGY" : "Gemini CLI"} Work] (active)`, - "active-id", - ], - [`[${provider.id === "google-agy" ? "Google AGY" : "Gemini CLI"} Work 2]`, "other-id"], - ]); + expect(output.entries.map((entry) => [entry.group, entry.accounting.sourceId])).toEqual( + expectedGroups, + ); + expect(output.entries.map((entry) => entry.name)).toEqual(expectedNames); }); });