From d7cb0fc59885fcddf910b52d34b0d4d8136b02ab Mon Sep 17 00:00:00 2001 From: n3wr1ch <40690535+n3wr1ch@users.noreply.github.com> Date: Sat, 11 Apr 2026 13:46:17 +0900 Subject: [PATCH 1/2] fix(antigravity): prefer userTier.name over legacy planInfo.planName The planInfo.planName field inherited from Windsurf/Codeium always returns 'Pro' for all paid tiers, including Google AI Ultra subscribers. The userTier object added by Google contains the accurate subscription name. This fix reads userTier.name first and falls back to planInfo.planName for backward compatibility when userTier is absent. Fixes #368 --- plugins/antigravity/plugin.js | 14 ++++++-- plugins/antigravity/plugin.test.js | 53 ++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/plugins/antigravity/plugin.js b/plugins/antigravity/plugin.js index 0b6ac696..884bbe78 100644 --- a/plugins/antigravity/plugin.js +++ b/plugins/antigravity/plugin.js @@ -428,9 +428,17 @@ var plan = null if (hasUserStatus) { - var ps = data.userStatus.planStatus || {} - var pi = ps.planInfo || {} - plan = pi.planName || null + // Prefer userTier.name (Google's own subscription system) over the legacy + // planInfo.planName field inherited from Windsurf/Codeium, which always + // returns "Pro" for all paid tiers including Google AI Ultra. + var ut = data.userStatus.userTier + if (ut && ut.name) { + plan = ut.name + } else { + var ps = data.userStatus.planStatus || {} + var pi = ps.planInfo || {} + plan = pi.planName || null + } } return { plan: plan, lines: lines } diff --git a/plugins/antigravity/plugin.test.js b/plugins/antigravity/plugin.test.js index 534a4a88..9e6a7823 100644 --- a/plugins/antigravity/plugin.test.js +++ b/plugins/antigravity/plugin.test.js @@ -69,6 +69,7 @@ function makeUserStatusResponse(overrides) { if (overrides.planName !== undefined) base.userStatus.planStatus.planInfo.planName = overrides.planName if (overrides.configs !== undefined) base.userStatus.cascadeModelConfigData.clientModelConfigs = overrides.configs if (overrides.planStatus !== undefined) base.userStatus.planStatus = overrides.planStatus + if (overrides.userTier !== undefined) base.userStatus.userTier = overrides.userTier } return base } @@ -197,6 +198,9 @@ describe("antigravity plugin", () => { expect(result.plan).toBe("Pro") + // No userTier in default fixture → falls back to planInfo.planName + expect(result.plan).toBe("Pro") + // Model lines exist — 3 pool lines const labels = result.lines.map((l) => l.label) expect(labels).toEqual(["Gemini Pro", "Gemini Flash", "Claude"]) @@ -654,6 +658,7 @@ describe("antigravity plugin", () => { const plugin = await loadPlugin() const result = plugin.probe(ctx) + // No userTier in default fixture → falls back to planInfo.planName expect(result.plan).toBe("Pro") const calls = ctx.host.http.request.mock.calls.map((c) => String(c[0].url)) const ccCalls = calls.filter((u) => u.includes("fetchAvailableModels")) @@ -1286,6 +1291,7 @@ describe("antigravity plugin", () => { const plugin = await loadPlugin() const result = plugin.probe(ctx) + // No userTier in default fixture → falls back to planInfo.planName expect(result.plan).toBe("Pro") const calls = ctx.host.http.request.mock.calls.map((c) => String(c[0].url)) expect(calls.filter((u) => u.includes("fetchAvailableModels")).length).toBe(0) @@ -1353,4 +1359,51 @@ describe("antigravity plugin", () => { expect(result.lines.length).toBeGreaterThan(0) expect(ccCalls).toBe(2) }) + + it("prefers userTier.name over legacy planInfo.planName for Ultra subscribers", async () => { + const ctx = makeCtx() + const discovery = makeDiscovery() + const response = makeUserStatusResponse({ + userTier: { + id: "g1-ultra-tier", + name: "Google AI Ultra", + description: "Google AI Ultra", + upgradeSubscriptionText: "You are subscribed to the best plan.", + }, + }) + setupLsMock(ctx, discovery, response) + + const plugin = await loadPlugin() + const result = plugin.probe(ctx) + + expect(result.plan).toBe("Google AI Ultra") + const labels = result.lines.map((l) => l.label) + expect(labels).toEqual(["Gemini Pro", "Gemini Flash", "Claude"]) + }) + + it("falls back to planInfo.planName when userTier is absent", async () => { + const ctx = makeCtx() + const discovery = makeDiscovery() + const response = makeUserStatusResponse() // no userTier override + setupLsMock(ctx, discovery, response) + + const plugin = await loadPlugin() + const result = plugin.probe(ctx) + + expect(result.plan).toBe("Pro") + }) + + it("falls back to planInfo.planName when userTier.name is empty", async () => { + const ctx = makeCtx() + const discovery = makeDiscovery() + const response = makeUserStatusResponse({ + userTier: { id: "g1-pro-tier", name: "" }, + }) + setupLsMock(ctx, discovery, response) + + const plugin = await loadPlugin() + const result = plugin.probe(ctx) + + expect(result.plan).toBe("Pro") + }) }) From cb63b20fa59152a4432220d95942c9a610b4e039 Mon Sep 17 00:00:00 2001 From: n3wr1ch <40690535+n3wr1ch@users.noreply.github.com> Date: Sun, 12 Apr 2026 11:40:01 +0900 Subject: [PATCH 2/2] =?UTF-8?q?refactor:=20apply=20Copilot=20review=20?= =?UTF-8?q?=E2=80=94=20add=20typeof/trim=20guards,=20remove=20duplicate=20?= =?UTF-8?q?assertion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Gate userTier.name and planInfo.planName with typeof === 'string' and .trim() to prevent non-string or whitespace-only plan labels - Remove duplicate expect(result.plan).toBe('Pro') in test --- plugins/antigravity/plugin.js | 9 ++++++--- plugins/antigravity/plugin.test.js | 2 -- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/plugins/antigravity/plugin.js b/plugins/antigravity/plugin.js index 884bbe78..87574e60 100644 --- a/plugins/antigravity/plugin.js +++ b/plugins/antigravity/plugin.js @@ -432,12 +432,15 @@ // planInfo.planName field inherited from Windsurf/Codeium, which always // returns "Pro" for all paid tiers including Google AI Ultra. var ut = data.userStatus.userTier - if (ut && ut.name) { - plan = ut.name + var userTierName = + ut && typeof ut.name === "string" && ut.name.trim() ? ut.name.trim() : null + if (userTierName) { + plan = userTierName } else { var ps = data.userStatus.planStatus || {} var pi = ps.planInfo || {} - plan = pi.planName || null + plan = + typeof pi.planName === "string" && pi.planName.trim() ? pi.planName.trim() : null } } diff --git a/plugins/antigravity/plugin.test.js b/plugins/antigravity/plugin.test.js index 9e6a7823..10f2dc08 100644 --- a/plugins/antigravity/plugin.test.js +++ b/plugins/antigravity/plugin.test.js @@ -196,8 +196,6 @@ describe("antigravity plugin", () => { const plugin = await loadPlugin() const result = plugin.probe(ctx) - expect(result.plan).toBe("Pro") - // No userTier in default fixture → falls back to planInfo.planName expect(result.plan).toBe("Pro")