diff --git a/extensions/google-antigravity-auth/README.md b/extensions/google-antigravity-auth/README.md deleted file mode 100644 index 2d33c03dd..000000000 --- a/extensions/google-antigravity-auth/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# Google Antigravity Auth (Clawdbot plugin) - -OAuth provider plugin for **Google Antigravity** (Cloud Code Assist). - -## Enable - -Bundled plugins are disabled by default. Enable this one: - -```bash -clawdbot plugins enable google-antigravity-auth -``` - -Restart the Gateway after enabling. - -## Authenticate - -```bash -clawdbot models auth login --provider google-antigravity --set-default -``` - -## Notes - -- Antigravity uses Google Cloud project quotas. -- If requests fail, ensure Gemini for Google Cloud is enabled. diff --git a/extensions/google-antigravity-auth/clawdbot.plugin.json b/extensions/google-antigravity-auth/clawdbot.plugin.json deleted file mode 100644 index 95dcf051a..000000000 --- a/extensions/google-antigravity-auth/clawdbot.plugin.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "id": "google-antigravity-auth", - "providers": [ - "google-antigravity" - ], - "configSchema": { - "type": "object", - "additionalProperties": false, - "properties": {} - } -} diff --git a/extensions/google-antigravity-auth/index.ts b/extensions/google-antigravity-auth/index.ts deleted file mode 100644 index cd9e00b1a..000000000 --- a/extensions/google-antigravity-auth/index.ts +++ /dev/null @@ -1,437 +0,0 @@ -import { createHash, randomBytes } from "node:crypto"; -import { readFileSync } from "node:fs"; -import { createServer } from "node:http"; -import { emptyPluginConfigSchema } from "clawdbot/plugin-sdk"; - -// OAuth constants - decoded from pi-ai's base64 encoded values to stay in sync -const decode = (s: string) => Buffer.from(s, "base64").toString(); -const CLIENT_ID = decode( - "MTA3MTAwNjA2MDU5MS10bWhzc2luMmgyMWxjcmUyMzV2dG9sb2poNGc0MDNlcC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbQ==", -); -const CLIENT_SECRET = decode("R09DU1BYLUs1OEZXUjQ4NkxkTEoxbUxCOHNYQzR6NnFEQWY="); -const REDIRECT_URI = "http://localhost:51121/oauth-callback"; -const AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth"; -const TOKEN_URL = "https://oauth2.googleapis.com/token"; -const DEFAULT_PROJECT_ID = "rising-fact-p41fc"; -const DEFAULT_MODEL = "google-antigravity/claude-opus-4-5-thinking"; - -const SCOPES = [ - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/userinfo.email", - "https://www.googleapis.com/auth/userinfo.profile", - "https://www.googleapis.com/auth/cclog", - "https://www.googleapis.com/auth/experimentsandconfigs", -]; - -const CODE_ASSIST_ENDPOINTS = [ - "https://cloudcode-pa.googleapis.com", - "https://daily-cloudcode-pa.sandbox.googleapis.com", -]; - -const RESPONSE_PAGE = ` - - - - Moltbot Antigravity OAuth - - -
-

Authentication complete

-

You can return to the terminal.

-
- -`; - -function generatePkce(): { verifier: string; challenge: string } { - const verifier = randomBytes(32).toString("hex"); - const challenge = createHash("sha256").update(verifier).digest("base64url"); - return { verifier, challenge }; -} - -function isWSL(): boolean { - if (process.platform !== "linux") return false; - try { - const release = readFileSync("/proc/version", "utf8").toLowerCase(); - return release.includes("microsoft") || release.includes("wsl"); - } catch { - return false; - } -} - -function isWSL2(): boolean { - if (!isWSL()) return false; - try { - const version = readFileSync("/proc/version", "utf8").toLowerCase(); - return version.includes("wsl2") || version.includes("microsoft-standard"); - } catch { - return false; - } -} - -function shouldUseManualOAuthFlow(isRemote: boolean): boolean { - return isRemote || isWSL2(); -} - -function buildAuthUrl(params: { challenge: string; state: string }): string { - const url = new URL(AUTH_URL); - url.searchParams.set("client_id", CLIENT_ID); - url.searchParams.set("response_type", "code"); - url.searchParams.set("redirect_uri", REDIRECT_URI); - url.searchParams.set("scope", SCOPES.join(" ")); - url.searchParams.set("code_challenge", params.challenge); - url.searchParams.set("code_challenge_method", "S256"); - url.searchParams.set("state", params.state); - url.searchParams.set("access_type", "offline"); - url.searchParams.set("prompt", "consent"); - return url.toString(); -} - -function parseCallbackInput( - input: string, -): { code: string; state: string } | { error: string } { - const trimmed = input.trim(); - if (!trimmed) return { error: "No input provided" }; - - try { - const url = new URL(trimmed); - const code = url.searchParams.get("code"); - const state = url.searchParams.get("state"); - if (!code) return { error: "Missing 'code' parameter in URL" }; - if (!state) return { error: "Missing 'state' parameter in URL" }; - return { code, state }; - } catch { - return { error: "Paste the full redirect URL (not just the code)." }; - } -} - -async function startCallbackServer(params: { timeoutMs: number }) { - const redirect = new URL(REDIRECT_URI); - const port = redirect.port ? Number(redirect.port) : 51121; - - let settled = false; - let resolveCallback: (url: URL) => void; - let rejectCallback: (err: Error) => void; - - const callbackPromise = new Promise((resolve, reject) => { - resolveCallback = (url) => { - if (settled) return; - settled = true; - resolve(url); - }; - rejectCallback = (err) => { - if (settled) return; - settled = true; - reject(err); - }; - }); - - const timeout = setTimeout(() => { - rejectCallback(new Error("Timed out waiting for OAuth callback")); - }, params.timeoutMs); - timeout.unref?.(); - - const server = createServer((request, response) => { - if (!request.url) { - response.writeHead(400, { "Content-Type": "text/plain" }); - response.end("Missing URL"); - return; - } - - const url = new URL(request.url, `${redirect.protocol}//${redirect.host}`); - if (url.pathname !== redirect.pathname) { - response.writeHead(404, { "Content-Type": "text/plain" }); - response.end("Not found"); - return; - } - - response.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }); - response.end(RESPONSE_PAGE); - resolveCallback(url); - - setImmediate(() => { - server.close(); - }); - }); - - await new Promise((resolve, reject) => { - const onError = (err: Error) => { - server.off("error", onError); - reject(err); - }; - server.once("error", onError); - server.listen(port, "127.0.0.1", () => { - server.off("error", onError); - resolve(); - }); - }); - - return { - waitForCallback: () => callbackPromise, - close: () => - new Promise((resolve) => { - server.close(() => resolve()); - }), - }; -} - -async function exchangeCode(params: { - code: string; - verifier: string; -}): Promise<{ access: string; refresh: string; expires: number }> { - const response = await fetch(TOKEN_URL, { - method: "POST", - headers: { "Content-Type": "application/x-www-form-urlencoded" }, - body: new URLSearchParams({ - client_id: CLIENT_ID, - client_secret: CLIENT_SECRET, - code: params.code, - grant_type: "authorization_code", - redirect_uri: REDIRECT_URI, - code_verifier: params.verifier, - }), - }); - - if (!response.ok) { - const text = await response.text(); - throw new Error(`Token exchange failed: ${text}`); - } - - const data = (await response.json()) as { - access_token?: string; - refresh_token?: string; - expires_in?: number; - }; - - const access = data.access_token?.trim(); - const refresh = data.refresh_token?.trim(); - const expiresIn = data.expires_in ?? 0; - - if (!access) throw new Error("Token exchange returned no access_token"); - if (!refresh) throw new Error("Token exchange returned no refresh_token"); - - const expires = Date.now() + expiresIn * 1000 - 5 * 60 * 1000; - return { access, refresh, expires }; -} - -async function fetchUserEmail(accessToken: string): Promise { - try { - const response = await fetch("https://www.googleapis.com/oauth2/v1/userinfo?alt=json", { - headers: { Authorization: `Bearer ${accessToken}` }, - }); - if (!response.ok) return undefined; - const data = (await response.json()) as { email?: string }; - return data.email; - } catch { - return undefined; - } -} - -async function fetchProjectId(accessToken: string): Promise { - const headers = { - Authorization: `Bearer ${accessToken}`, - "Content-Type": "application/json", - "User-Agent": "google-api-nodejs-client/9.15.1", - "X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1", - "Client-Metadata": JSON.stringify({ - ideType: "IDE_UNSPECIFIED", - platform: "PLATFORM_UNSPECIFIED", - pluginType: "GEMINI", - }), - }; - - for (const endpoint of CODE_ASSIST_ENDPOINTS) { - try { - const response = await fetch(`${endpoint}/v1internal:loadCodeAssist`, { - method: "POST", - headers, - body: JSON.stringify({ - metadata: { - ideType: "IDE_UNSPECIFIED", - platform: "PLATFORM_UNSPECIFIED", - pluginType: "GEMINI", - }, - }), - }); - - if (!response.ok) continue; - const data = (await response.json()) as { - cloudaicompanionProject?: string | { id?: string }; - }; - - if (typeof data.cloudaicompanionProject === "string") { - return data.cloudaicompanionProject; - } - if ( - data.cloudaicompanionProject && - typeof data.cloudaicompanionProject === "object" && - data.cloudaicompanionProject.id - ) { - return data.cloudaicompanionProject.id; - } - } catch { - // ignore - } - } - - return DEFAULT_PROJECT_ID; -} - -async function loginAntigravity(params: { - isRemote: boolean; - openUrl: (url: string) => Promise; - prompt: (message: string) => Promise; - note: (message: string, title?: string) => Promise; - log: (message: string) => void; - progress: { update: (msg: string) => void; stop: (msg?: string) => void }; -}): Promise<{ - access: string; - refresh: string; - expires: number; - email?: string; - projectId: string; -}> { - const { verifier, challenge } = generatePkce(); - const state = randomBytes(16).toString("hex"); - const authUrl = buildAuthUrl({ challenge, state }); - - let callbackServer: Awaited> | null = null; - const needsManual = shouldUseManualOAuthFlow(params.isRemote); - if (!needsManual) { - try { - callbackServer = await startCallbackServer({ timeoutMs: 5 * 60 * 1000 }); - } catch { - callbackServer = null; - } - } - - if (!callbackServer) { - await params.note( - [ - "Open the URL in your local browser.", - "After signing in, copy the full redirect URL and paste it back here.", - "", - `Auth URL: ${authUrl}`, - `Redirect URI: ${REDIRECT_URI}`, - ].join("\n"), - "Google Antigravity OAuth", - ); - // Output raw URL below the box for easy copying (fixes #1772) - params.log(""); - params.log("Copy this URL:"); - params.log(authUrl); - params.log(""); - } - - if (!needsManual) { - params.progress.update("Opening Google sign-in…"); - try { - await params.openUrl(authUrl); - } catch { - // ignore - } - } - - let code = ""; - let returnedState = ""; - - if (callbackServer) { - params.progress.update("Waiting for OAuth callback…"); - const callback = await callbackServer.waitForCallback(); - code = callback.searchParams.get("code") ?? ""; - returnedState = callback.searchParams.get("state") ?? ""; - await callbackServer.close(); - } else { - params.progress.update("Waiting for redirect URL…"); - const input = await params.prompt("Paste the redirect URL: "); - const parsed = parseCallbackInput(input); - if ("error" in parsed) throw new Error(parsed.error); - code = parsed.code; - returnedState = parsed.state; - } - - if (!code) throw new Error("Missing OAuth code"); - if (returnedState !== state) { - throw new Error("OAuth state mismatch. Please try again."); - } - - params.progress.update("Exchanging code for tokens…"); - const tokens = await exchangeCode({ code, verifier }); - const email = await fetchUserEmail(tokens.access); - const projectId = await fetchProjectId(tokens.access); - - params.progress.stop("Antigravity OAuth complete"); - return { ...tokens, email, projectId }; -} - -const antigravityPlugin = { - id: "google-antigravity-auth", - name: "Google Antigravity Auth", - description: "OAuth flow for Google Antigravity (Cloud Code Assist)", - configSchema: emptyPluginConfigSchema(), - register(api) { - api.registerProvider({ - id: "google-antigravity", - label: "Google Antigravity", - docsPath: "/providers/models", - aliases: ["antigravity"], - auth: [ - { - id: "oauth", - label: "Google OAuth", - hint: "PKCE + localhost callback", - kind: "oauth", - run: async (ctx) => { - const spin = ctx.prompter.progress("Starting Antigravity OAuth…"); - try { - const result = await loginAntigravity({ - isRemote: ctx.isRemote, - openUrl: ctx.openUrl, - prompt: async (message) => String(await ctx.prompter.text({ message })), - note: ctx.prompter.note, - log: (message) => ctx.runtime.log(message), - progress: spin, - }); - - const profileId = `google-antigravity:${result.email ?? "default"}`; - return { - profiles: [ - { - profileId, - credential: { - type: "oauth", - provider: "google-antigravity", - access: result.access, - refresh: result.refresh, - expires: result.expires, - email: result.email, - projectId: result.projectId, - }, - }, - ], - configPatch: { - agents: { - defaults: { - models: { - [DEFAULT_MODEL]: {}, - }, - }, - }, - }, - defaultModel: DEFAULT_MODEL, - notes: [ - "Antigravity uses Google Cloud project quotas.", - "Enable Gemini for Google Cloud on your project if requests fail.", - ], - }; - } catch (err) { - spin.stop("Antigravity OAuth failed"); - throw err; - } - }, - }, - ], - }); - }, -}; - -export default antigravityPlugin; diff --git a/extensions/google-antigravity-auth/package.json b/extensions/google-antigravity-auth/package.json deleted file mode 100644 index 8b13861ec..000000000 --- a/extensions/google-antigravity-auth/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "@moltbot/google-antigravity-auth", - "version": "2026.1.27-beta.1", - "type": "module", - "description": "Moltbot Google Antigravity OAuth provider plugin", - "moltbot": { - "extensions": [ - "./index.ts" - ] - } -} diff --git a/src/commands/auth-choice-options.ts b/src/commands/auth-choice-options.ts index 6b49ff17b..5348d243b 100644 --- a/src/commands/auth-choice-options.ts +++ b/src/commands/auth-choice-options.ts @@ -75,7 +75,7 @@ const AUTH_CHOICE_GROUP_DEFS: { value: "google", label: "Google", hint: "Gemini API key + OAuth", - choices: ["gemini-api-key", "google-antigravity", "google-gemini-cli"], + choices: ["gemini-api-key", "google-gemini-cli"], }, { value: "copilot", @@ -153,11 +153,6 @@ export function buildAuthChoiceOptions(params: { hint: "Uses GitHub device flow", }); options.push({ value: "gemini-api-key", label: "Google Gemini API key" }); - options.push({ - value: "google-antigravity", - label: "Google Antigravity OAuth", - hint: "Uses the bundled Antigravity auth plugin", - }); options.push({ value: "google-gemini-cli", label: "Google Gemini CLI OAuth", diff --git a/src/commands/auth-choice.apply.google-antigravity.ts b/src/commands/auth-choice.apply.google-antigravity.ts deleted file mode 100644 index 6011b74e0..000000000 --- a/src/commands/auth-choice.apply.google-antigravity.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js"; -import { applyAuthChoicePluginProvider } from "./auth-choice.apply.plugin-provider.js"; - -export async function applyAuthChoiceGoogleAntigravity( - params: ApplyAuthChoiceParams, -): Promise { - return await applyAuthChoicePluginProvider(params, { - authChoice: "google-antigravity", - pluginId: "google-antigravity-auth", - providerId: "google-antigravity", - methodId: "oauth", - label: "Google Antigravity", - }); -} diff --git a/src/commands/auth-choice.apply.ts b/src/commands/auth-choice.apply.ts index f139b509f..77223d3eb 100644 --- a/src/commands/auth-choice.apply.ts +++ b/src/commands/auth-choice.apply.ts @@ -5,7 +5,6 @@ import { applyAuthChoiceAnthropic } from "./auth-choice.apply.anthropic.js"; import { applyAuthChoiceApiProviders } from "./auth-choice.apply.api-providers.js"; import { applyAuthChoiceCopilotProxy } from "./auth-choice.apply.copilot-proxy.js"; import { applyAuthChoiceGitHubCopilot } from "./auth-choice.apply.github-copilot.js"; -import { applyAuthChoiceGoogleAntigravity } from "./auth-choice.apply.google-antigravity.js"; import { applyAuthChoiceGoogleGeminiCli } from "./auth-choice.apply.google-gemini-cli.js"; import { applyAuthChoiceMiniMax } from "./auth-choice.apply.minimax.js"; import { applyAuthChoiceOAuth } from "./auth-choice.apply.oauth.js"; @@ -42,7 +41,6 @@ export async function applyAuthChoice( applyAuthChoiceApiProviders, applyAuthChoiceMiniMax, applyAuthChoiceGitHubCopilot, - applyAuthChoiceGoogleAntigravity, applyAuthChoiceGoogleGeminiCli, applyAuthChoiceCopilotProxy, applyAuthChoiceQwenPortal, diff --git a/src/config/plugin-auto-enable.ts b/src/config/plugin-auto-enable.ts index a7632e41f..2959c113c 100644 --- a/src/config/plugin-auto-enable.ts +++ b/src/config/plugin-auto-enable.ts @@ -29,7 +29,6 @@ const CHANNEL_PLUGIN_IDS = Array.from( ); const PROVIDER_PLUGIN_IDS: Array<{ pluginId: string; providerId: string }> = [ - { pluginId: "google-antigravity-auth", providerId: "google-antigravity" }, { pluginId: "google-gemini-cli-auth", providerId: "google-gemini-cli" }, { pluginId: "qwen-portal-auth", providerId: "qwen-portal" }, { pluginId: "copilot-proxy", providerId: "copilot-proxy" }, diff --git a/src/infra/provider-usage.auth.ts b/src/infra/provider-usage.auth.ts index 90d73bb59..da985e539 100644 --- a/src/infra/provider-usage.auth.ts +++ b/src/infra/provider-usage.auth.ts @@ -130,7 +130,7 @@ async function resolveOAuthToken(params: { }); if (!resolved?.apiKey) continue; let token = resolved.apiKey; - if (params.provider === "google-gemini-cli" || params.provider === "google-antigravity") { + if (params.provider === "google-gemini-cli") { const parsed = parseGoogleToken(resolved.apiKey); token = parsed?.token ?? resolved.apiKey; } @@ -159,7 +159,6 @@ function resolveOAuthProviders(agentDir?: string): UsageProviderId[] { "anthropic", "github-copilot", "google-gemini-cli", - "google-antigravity", "openai-codex", ] satisfies UsageProviderId[]; const isOAuthLikeCredential = (id: string) => { diff --git a/src/infra/provider-usage.fetch.antigravity.test.ts b/src/infra/provider-usage.fetch.antigravity.test.ts deleted file mode 100644 index a3c108021..000000000 --- a/src/infra/provider-usage.fetch.antigravity.test.ts +++ /dev/null @@ -1,578 +0,0 @@ -import { describe, expect, it, vi } from "vitest"; -import { fetchAntigravityUsage } from "./provider-usage.fetch.antigravity.js"; - -const makeResponse = (status: number, body: unknown): Response => { - const payload = typeof body === "string" ? body : JSON.stringify(body); - const headers = typeof body === "string" ? undefined : { "Content-Type": "application/json" }; - return new Response(payload, { status, headers }); -}; - -describe("fetchAntigravityUsage", () => { - it("returns 3 windows when both endpoints succeed", async () => { - const mockFetch = vi.fn, ReturnType>(async (input) => { - const url = - typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; - - if (url.includes("loadCodeAssist")) { - return makeResponse(200, { - availablePromptCredits: 750, - planInfo: { monthlyPromptCredits: 1000 }, - planType: "Standard", - currentTier: { id: "tier1", name: "Standard Tier" }, - }); - } - - if (url.includes("fetchAvailableModels")) { - return makeResponse(200, { - models: { - "gemini-pro-1.5": { - quotaInfo: { - remainingFraction: 0.6, - resetTime: "2026-01-08T00:00:00Z", - isExhausted: false, - }, - }, - "gemini-flash-2.0": { - quotaInfo: { - remainingFraction: 0.8, - resetTime: "2026-01-08T00:00:00Z", - isExhausted: false, - }, - }, - }, - }); - } - - return makeResponse(404, "not found"); - }); - - const snapshot = await fetchAntigravityUsage("token-123", 5000, mockFetch); - - expect(snapshot.provider).toBe("google-antigravity"); - expect(snapshot.displayName).toBe("Antigravity"); - expect(snapshot.windows).toHaveLength(3); - expect(snapshot.plan).toBe("Standard Tier"); - expect(snapshot.error).toBeUndefined(); - - const creditsWindow = snapshot.windows.find((w) => w.label === "Credits"); - expect(creditsWindow?.usedPercent).toBe(25); // (1000 - 750) / 1000 * 100 - - const proWindow = snapshot.windows.find((w) => w.label === "gemini-pro-1.5"); - expect(proWindow?.usedPercent).toBe(40); // (1 - 0.6) * 100 - expect(proWindow?.resetAt).toBe(new Date("2026-01-08T00:00:00Z").getTime()); - - const flashWindow = snapshot.windows.find((w) => w.label === "gemini-flash-2.0"); - expect(flashWindow?.usedPercent).toBeCloseTo(20, 1); // (1 - 0.8) * 100 - expect(flashWindow?.resetAt).toBe(new Date("2026-01-08T00:00:00Z").getTime()); - - expect(mockFetch).toHaveBeenCalledTimes(2); - }); - - it("returns Credits only when loadCodeAssist succeeds but fetchAvailableModels fails", async () => { - const mockFetch = vi.fn, ReturnType>(async (input) => { - const url = - typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; - - if (url.includes("loadCodeAssist")) { - return makeResponse(200, { - availablePromptCredits: 250, - planInfo: { monthlyPromptCredits: 1000 }, - currentTier: { name: "Free" }, - }); - } - - if (url.includes("fetchAvailableModels")) { - return makeResponse(403, { error: { message: "Permission denied" } }); - } - - return makeResponse(404, "not found"); - }); - - const snapshot = await fetchAntigravityUsage("token-123", 5000, mockFetch); - - expect(snapshot.provider).toBe("google-antigravity"); - expect(snapshot.windows).toHaveLength(1); - expect(snapshot.plan).toBe("Free"); - expect(snapshot.error).toBeUndefined(); - - const creditsWindow = snapshot.windows[0]; - expect(creditsWindow?.label).toBe("Credits"); - expect(creditsWindow?.usedPercent).toBe(75); // (1000 - 250) / 1000 * 100 - - expect(mockFetch).toHaveBeenCalledTimes(2); - }); - - it("returns model IDs when fetchAvailableModels succeeds but loadCodeAssist fails", async () => { - const mockFetch = vi.fn, ReturnType>(async (input) => { - const url = - typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; - - if (url.includes("loadCodeAssist")) { - return makeResponse(500, "Internal server error"); - } - - if (url.includes("fetchAvailableModels")) { - return makeResponse(200, { - models: { - "gemini-pro-1.5": { - quotaInfo: { remainingFraction: 0.5, resetTime: "2026-01-08T00:00:00Z" }, - }, - "gemini-flash-2.0": { - quotaInfo: { remainingFraction: 0.7, resetTime: "2026-01-08T00:00:00Z" }, - }, - }, - }); - } - - return makeResponse(404, "not found"); - }); - - const snapshot = await fetchAntigravityUsage("token-123", 5000, mockFetch); - - expect(snapshot.provider).toBe("google-antigravity"); - expect(snapshot.windows).toHaveLength(2); - expect(snapshot.error).toBeUndefined(); - - const proWindow = snapshot.windows.find((w) => w.label === "gemini-pro-1.5"); - expect(proWindow?.usedPercent).toBe(50); // (1 - 0.5) * 100 - - const flashWindow = snapshot.windows.find((w) => w.label === "gemini-flash-2.0"); - expect(flashWindow?.usedPercent).toBeCloseTo(30, 1); // (1 - 0.7) * 100 - - expect(mockFetch).toHaveBeenCalledTimes(2); - }); - - it("uses cloudaicompanionProject string as project id", async () => { - let capturedBody: string | undefined; - const mockFetch = vi.fn, ReturnType>( - async (input, init) => { - const url = - typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; - - if (url.includes("loadCodeAssist")) { - return makeResponse(200, { - availablePromptCredits: 900, - planInfo: { monthlyPromptCredits: 1000 }, - cloudaicompanionProject: "projects/alpha", - }); - } - - if (url.includes("fetchAvailableModels")) { - capturedBody = init?.body?.toString(); - return makeResponse(200, { models: {} }); - } - - return makeResponse(404, "not found"); - }, - ); - - await fetchAntigravityUsage("token-123", 5000, mockFetch); - - expect(capturedBody).toBe(JSON.stringify({ project: "projects/alpha" })); - }); - - it("uses cloudaicompanionProject object id when present", async () => { - let capturedBody: string | undefined; - const mockFetch = vi.fn, ReturnType>( - async (input, init) => { - const url = - typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; - - if (url.includes("loadCodeAssist")) { - return makeResponse(200, { - availablePromptCredits: 900, - planInfo: { monthlyPromptCredits: 1000 }, - cloudaicompanionProject: { id: "projects/beta" }, - }); - } - - if (url.includes("fetchAvailableModels")) { - capturedBody = init?.body?.toString(); - return makeResponse(200, { models: {} }); - } - - return makeResponse(404, "not found"); - }, - ); - - await fetchAntigravityUsage("token-123", 5000, mockFetch); - - expect(capturedBody).toBe(JSON.stringify({ project: "projects/beta" })); - }); - - it("returns error snapshot when both endpoints fail", async () => { - const mockFetch = vi.fn, ReturnType>(async (input) => { - const url = - typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; - - if (url.includes("loadCodeAssist")) { - return makeResponse(403, { error: { message: "Access denied" } }); - } - - if (url.includes("fetchAvailableModels")) { - return makeResponse(403, "Forbidden"); - } - - return makeResponse(404, "not found"); - }); - - const snapshot = await fetchAntigravityUsage("token-123", 5000, mockFetch); - - expect(snapshot.provider).toBe("google-antigravity"); - expect(snapshot.windows).toHaveLength(0); - expect(snapshot.error).toBe("Access denied"); - - expect(mockFetch).toHaveBeenCalledTimes(2); - }); - - it("returns Token expired when fetchAvailableModels returns 401 and no windows", async () => { - const mockFetch = vi.fn, ReturnType>(async (input) => { - const url = - typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; - - if (url.includes("loadCodeAssist")) { - return makeResponse(500, "Boom"); - } - - if (url.includes("fetchAvailableModels")) { - return makeResponse(401, { error: { message: "Unauthorized" } }); - } - - return makeResponse(404, "not found"); - }); - - const snapshot = await fetchAntigravityUsage("token-123", 5000, mockFetch); - - expect(snapshot.error).toBe("Token expired"); - expect(snapshot.windows).toHaveLength(0); - }); - - it("extracts plan info from currentTier.name", async () => { - const mockFetch = vi.fn, ReturnType>(async (input) => { - const url = - typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; - - if (url.includes("loadCodeAssist")) { - return makeResponse(200, { - availablePromptCredits: 500, - planInfo: { monthlyPromptCredits: 1000 }, - planType: "Basic", - currentTier: { id: "tier2", name: "Premium Tier" }, - }); - } - - if (url.includes("fetchAvailableModels")) { - return makeResponse(500, "Error"); - } - - return makeResponse(404, "not found"); - }); - - const snapshot = await fetchAntigravityUsage("token-123", 5000, mockFetch); - - expect(snapshot.plan).toBe("Premium Tier"); - }); - - it("falls back to planType when currentTier.name is missing", async () => { - const mockFetch = vi.fn, ReturnType>(async (input) => { - const url = - typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; - - if (url.includes("loadCodeAssist")) { - return makeResponse(200, { - availablePromptCredits: 500, - planInfo: { monthlyPromptCredits: 1000 }, - planType: "Basic Plan", - }); - } - - if (url.includes("fetchAvailableModels")) { - return makeResponse(500, "Error"); - } - - return makeResponse(404, "not found"); - }); - - const snapshot = await fetchAntigravityUsage("token-123", 5000, mockFetch); - - expect(snapshot.plan).toBe("Basic Plan"); - }); - - it("includes reset times in model windows", async () => { - const resetTime = "2026-01-10T12:00:00Z"; - const mockFetch = vi.fn, ReturnType>(async (input) => { - const url = - typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; - - if (url.includes("loadCodeAssist")) { - return makeResponse(500, "Error"); - } - - if (url.includes("fetchAvailableModels")) { - return makeResponse(200, { - models: { - "gemini-pro-experimental": { - quotaInfo: { remainingFraction: 0.3, resetTime }, - }, - }, - }); - } - - return makeResponse(404, "not found"); - }); - - const snapshot = await fetchAntigravityUsage("token-123", 5000, mockFetch); - - const proWindow = snapshot.windows.find((w) => w.label === "gemini-pro-experimental"); - expect(proWindow?.resetAt).toBe(new Date(resetTime).getTime()); - }); - - it("parses string numbers correctly", async () => { - const mockFetch = vi.fn, ReturnType>(async (input) => { - const url = - typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; - - if (url.includes("loadCodeAssist")) { - return makeResponse(200, { - availablePromptCredits: "600", - planInfo: { monthlyPromptCredits: "1000" }, - }); - } - - if (url.includes("fetchAvailableModels")) { - return makeResponse(200, { - models: { - "gemini-flash-lite": { - quotaInfo: { remainingFraction: "0.9" }, - }, - }, - }); - } - - return makeResponse(404, "not found"); - }); - - const snapshot = await fetchAntigravityUsage("token-123", 5000, mockFetch); - - expect(snapshot.windows).toHaveLength(2); - - const creditsWindow = snapshot.windows.find((w) => w.label === "Credits"); - expect(creditsWindow?.usedPercent).toBe(40); // (1000 - 600) / 1000 * 100 - - const flashWindow = snapshot.windows.find((w) => w.label === "gemini-flash-lite"); - expect(flashWindow?.usedPercent).toBeCloseTo(10, 1); // (1 - 0.9) * 100 - }); - - it("skips internal models", async () => { - const mockFetch = vi.fn, ReturnType>(async (input) => { - const url = - typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; - - if (url.includes("loadCodeAssist")) { - return makeResponse(200, { - availablePromptCredits: 500, - planInfo: { monthlyPromptCredits: 1000 }, - cloudaicompanionProject: "projects/internal", - }); - } - - if (url.includes("fetchAvailableModels")) { - return makeResponse(200, { - models: { - chat_hidden: { quotaInfo: { remainingFraction: 0.1 } }, - tab_hidden: { quotaInfo: { remainingFraction: 0.2 } }, - "gemini-pro-1.5": { quotaInfo: { remainingFraction: 0.7 } }, - }, - }); - } - - return makeResponse(404, "not found"); - }); - - const snapshot = await fetchAntigravityUsage("token-123", 5000, mockFetch); - - expect(snapshot.windows.map((w) => w.label)).toEqual(["Credits", "gemini-pro-1.5"]); - }); - - it("sorts models by usage and shows individual model IDs", async () => { - const mockFetch = vi.fn, ReturnType>(async (input) => { - const url = - typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; - - if (url.includes("loadCodeAssist")) { - return makeResponse(500, "Error"); - } - - if (url.includes("fetchAvailableModels")) { - return makeResponse(200, { - models: { - "gemini-pro-1.0": { - quotaInfo: { remainingFraction: 0.8 }, - }, - "gemini-pro-1.5": { - quotaInfo: { remainingFraction: 0.3 }, - }, - "gemini-flash-1.5": { - quotaInfo: { remainingFraction: 0.6 }, - }, - "gemini-flash-2.0": { - quotaInfo: { remainingFraction: 0.9 }, - }, - }, - }); - } - - return makeResponse(404, "not found"); - }); - - const snapshot = await fetchAntigravityUsage("token-123", 5000, mockFetch); - - expect(snapshot.windows).toHaveLength(4); - // Should be sorted by usage (highest first) - expect(snapshot.windows[0]?.label).toBe("gemini-pro-1.5"); - expect(snapshot.windows[0]?.usedPercent).toBe(70); // (1 - 0.3) * 100 - expect(snapshot.windows[1]?.label).toBe("gemini-flash-1.5"); - expect(snapshot.windows[1]?.usedPercent).toBe(40); // (1 - 0.6) * 100 - expect(snapshot.windows[2]?.label).toBe("gemini-pro-1.0"); - expect(snapshot.windows[2]?.usedPercent).toBeCloseTo(20, 1); // (1 - 0.8) * 100 - expect(snapshot.windows[3]?.label).toBe("gemini-flash-2.0"); - expect(snapshot.windows[3]?.usedPercent).toBeCloseTo(10, 1); // (1 - 0.9) * 100 - }); - - it("returns Token expired error on 401 from loadCodeAssist", async () => { - const mockFetch = vi.fn, ReturnType>(async (input) => { - const url = - typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; - - if (url.includes("loadCodeAssist")) { - return makeResponse(401, { error: { message: "Unauthorized" } }); - } - - return makeResponse(404, "not found"); - }); - - const snapshot = await fetchAntigravityUsage("token-123", 5000, mockFetch); - - expect(snapshot.error).toBe("Token expired"); - expect(snapshot.windows).toHaveLength(0); - expect(mockFetch).toHaveBeenCalledTimes(1); // Should stop early on 401 - }); - - it("handles empty models array gracefully", async () => { - const mockFetch = vi.fn, ReturnType>(async (input) => { - const url = - typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; - - if (url.includes("loadCodeAssist")) { - return makeResponse(200, { - availablePromptCredits: 800, - planInfo: { monthlyPromptCredits: 1000 }, - }); - } - - if (url.includes("fetchAvailableModels")) { - return makeResponse(200, { models: {} }); - } - - return makeResponse(404, "not found"); - }); - - const snapshot = await fetchAntigravityUsage("token-123", 5000, mockFetch); - - expect(snapshot.windows).toHaveLength(1); - const creditsWindow = snapshot.windows[0]; - expect(creditsWindow?.label).toBe("Credits"); - expect(creditsWindow?.usedPercent).toBe(20); - }); - - it("handles missing credits fields gracefully", async () => { - const mockFetch = vi.fn, ReturnType>(async (input) => { - const url = - typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; - - if (url.includes("loadCodeAssist")) { - return makeResponse(200, { planType: "Free" }); - } - - if (url.includes("fetchAvailableModels")) { - return makeResponse(200, { - models: { - "gemini-flash-experimental": { - quotaInfo: { remainingFraction: 0.5 }, - }, - }, - }); - } - - return makeResponse(404, "not found"); - }); - - const snapshot = await fetchAntigravityUsage("token-123", 5000, mockFetch); - - expect(snapshot.windows).toHaveLength(1); - const flashWindow = snapshot.windows[0]; - expect(flashWindow?.label).toBe("gemini-flash-experimental"); - expect(flashWindow?.usedPercent).toBe(50); - expect(snapshot.plan).toBe("Free"); - }); - - it("handles invalid reset time gracefully", async () => { - const mockFetch = vi.fn, ReturnType>(async (input) => { - const url = - typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; - - if (url.includes("loadCodeAssist")) { - return makeResponse(500, "Error"); - } - - if (url.includes("fetchAvailableModels")) { - return makeResponse(200, { - models: { - "gemini-pro-test": { - quotaInfo: { remainingFraction: 0.4, resetTime: "invalid-date" }, - }, - }, - }); - } - - return makeResponse(404, "not found"); - }); - - const snapshot = await fetchAntigravityUsage("token-123", 5000, mockFetch); - - const proWindow = snapshot.windows.find((w) => w.label === "gemini-pro-test"); - expect(proWindow?.usedPercent).toBe(60); - expect(proWindow?.resetAt).toBeUndefined(); - }); - - it("handles network errors with graceful degradation", async () => { - const mockFetch = vi.fn, ReturnType>(async (input) => { - const url = - typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; - - if (url.includes("loadCodeAssist")) { - throw new Error("Network failure"); - } - - if (url.includes("fetchAvailableModels")) { - return makeResponse(200, { - models: { - "gemini-flash-stable": { - quotaInfo: { remainingFraction: 0.85 }, - }, - }, - }); - } - - return makeResponse(404, "not found"); - }); - - const snapshot = await fetchAntigravityUsage("token-123", 5000, mockFetch); - - expect(snapshot.windows).toHaveLength(1); - const flashWindow = snapshot.windows[0]; - expect(flashWindow?.label).toBe("gemini-flash-stable"); - expect(flashWindow?.usedPercent).toBeCloseTo(15, 1); - expect(snapshot.error).toBeUndefined(); - }); -}); diff --git a/src/infra/provider-usage.fetch.antigravity.ts b/src/infra/provider-usage.fetch.antigravity.ts deleted file mode 100644 index b40b6d91e..000000000 --- a/src/infra/provider-usage.fetch.antigravity.ts +++ /dev/null @@ -1,284 +0,0 @@ -import { logDebug } from "../logger.js"; -import { fetchJson } from "./provider-usage.fetch.shared.js"; -import { clampPercent, PROVIDER_LABELS } from "./provider-usage.shared.js"; -import type { ProviderUsageSnapshot, UsageWindow } from "./provider-usage.types.js"; - -type LoadCodeAssistResponse = { - availablePromptCredits?: number | string; - planInfo?: { monthlyPromptCredits?: number | string }; - planType?: string; - currentTier?: { id?: string; name?: string }; - cloudaicompanionProject?: string | { id?: string }; -}; - -type FetchAvailableModelsResponse = { - models?: Record< - string, - { - displayName?: string; - quotaInfo?: { - remainingFraction?: number | string; - resetTime?: string; - isExhausted?: boolean; - }; - } - >; -}; - -type ModelQuota = { - remainingFraction: number; - resetTime?: number; -}; - -type CreditsInfo = { - available: number; - monthly: number; -}; - -const BASE_URL = "https://cloudcode-pa.googleapis.com"; -const LOAD_CODE_ASSIST_PATH = "/v1internal:loadCodeAssist"; -const FETCH_AVAILABLE_MODELS_PATH = "/v1internal:fetchAvailableModels"; - -const METADATA = { - ideType: "ANTIGRAVITY", - platform: "PLATFORM_UNSPECIFIED", - pluginType: "GEMINI", -}; - -function parseNumber(value: number | string | undefined): number | undefined { - if (typeof value === "number" && Number.isFinite(value)) return value; - if (typeof value === "string") { - const parsed = Number.parseFloat(value); - if (Number.isFinite(parsed)) return parsed; - } - return undefined; -} - -function parseEpochMs(isoString: string | undefined): number | undefined { - if (!isoString?.trim()) return undefined; - try { - const ms = Date.parse(isoString); - if (Number.isFinite(ms)) return ms; - } catch { - // ignore parse errors - } - return undefined; -} - -async function parseErrorMessage(res: Response): Promise { - try { - const data = (await res.json()) as { error?: { message?: string } }; - const message = data?.error?.message?.trim(); - if (message) return message; - } catch { - // ignore parse errors - } - return `HTTP ${res.status}`; -} - -function extractCredits(data: LoadCodeAssistResponse): CreditsInfo | undefined { - const available = parseNumber(data.availablePromptCredits); - const monthly = parseNumber(data.planInfo?.monthlyPromptCredits); - if (available === undefined || monthly === undefined || monthly <= 0) return undefined; - return { available, monthly }; -} - -function extractPlanInfo(data: LoadCodeAssistResponse): string | undefined { - const tierName = data.currentTier?.name?.trim(); - if (tierName) return tierName; - const planType = data.planType?.trim(); - if (planType) return planType; - return undefined; -} - -function extractProjectId(data: LoadCodeAssistResponse): string | undefined { - const project = data.cloudaicompanionProject; - if (!project) return undefined; - if (typeof project === "string") return project.trim() ? project : undefined; - const projectId = typeof project.id === "string" ? project.id.trim() : undefined; - return projectId || undefined; -} - -function extractModelQuotas(data: FetchAvailableModelsResponse): Map { - const result = new Map(); - if (!data.models || typeof data.models !== "object") return result; - - for (const [modelId, modelInfo] of Object.entries(data.models)) { - const quotaInfo = modelInfo.quotaInfo; - if (!quotaInfo) continue; - - const remainingFraction = parseNumber(quotaInfo.remainingFraction); - if (remainingFraction === undefined) continue; - - const resetTime = parseEpochMs(quotaInfo.resetTime); - result.set(modelId, { remainingFraction, resetTime }); - } - - return result; -} - -function buildUsageWindows(opts: { - credits?: CreditsInfo; - modelQuotas?: Map; -}): UsageWindow[] { - const windows: UsageWindow[] = []; - - // Credits window (overall) - if (opts.credits) { - const { available, monthly } = opts.credits; - const used = monthly - available; - const usedPercent = clampPercent((used / monthly) * 100); - windows.push({ label: "Credits", usedPercent }); - } - - // Individual model windows - if (opts.modelQuotas && opts.modelQuotas.size > 0) { - const modelWindows: UsageWindow[] = []; - - for (const [modelId, quota] of opts.modelQuotas) { - const lowerModelId = modelId.toLowerCase(); - - // Skip internal models - if (lowerModelId.includes("chat_") || lowerModelId.includes("tab_")) { - continue; - } - - const usedPercent = clampPercent((1 - quota.remainingFraction) * 100); - const window: UsageWindow = { label: modelId, usedPercent }; - if (quota.resetTime) window.resetAt = quota.resetTime; - modelWindows.push(window); - } - - // Sort by usage (highest first) and take top 10 - modelWindows.sort((a, b) => b.usedPercent - a.usedPercent); - const topModels = modelWindows.slice(0, 10); - logDebug( - `[antigravity] Built ${topModels.length} model windows from ${opts.modelQuotas.size} total models`, - ); - for (const w of topModels) { - logDebug( - `[antigravity] ${w.label}: ${w.usedPercent.toFixed(1)}% used${w.resetAt ? ` (resets at ${new Date(w.resetAt).toISOString()})` : ""}`, - ); - } - windows.push(...topModels); - } - - return windows; -} - -export async function fetchAntigravityUsage( - token: string, - timeoutMs: number, - fetchFn: typeof fetch, -): Promise { - const headers: Record = { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - "User-Agent": "antigravity", - "X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1", - }; - - let credits: CreditsInfo | undefined; - let modelQuotas: Map | undefined; - let planInfo: string | undefined; - let lastError: string | undefined; - let projectId: string | undefined; - - // Fetch loadCodeAssist (credits + plan info) - try { - const res = await fetchJson( - `${BASE_URL}${LOAD_CODE_ASSIST_PATH}`, - { method: "POST", headers, body: JSON.stringify({ metadata: METADATA }) }, - timeoutMs, - fetchFn, - ); - - if (res.ok) { - const data = (await res.json()) as LoadCodeAssistResponse; - - // Extract project ID for subsequent calls - projectId = extractProjectId(data); - - credits = extractCredits(data); - planInfo = extractPlanInfo(data); - logDebug( - `[antigravity] Credits: ${credits ? `${credits.available}/${credits.monthly}` : "none"}${planInfo ? ` (plan: ${planInfo})` : ""}`, - ); - } else { - lastError = await parseErrorMessage(res); - // Fatal auth errors - stop early - if (res.status === 401) { - return { - provider: "google-antigravity", - displayName: PROVIDER_LABELS["google-antigravity"], - windows: [], - error: "Token expired", - }; - } - } - } catch { - lastError = "Network error"; - } - - // Fetch fetchAvailableModels (model quotas) - if (!projectId) { - logDebug("[antigravity] Missing project id; requesting available models without project"); - } - try { - const body = JSON.stringify(projectId ? { project: projectId } : {}); - const res = await fetchJson( - `${BASE_URL}${FETCH_AVAILABLE_MODELS_PATH}`, - { method: "POST", headers, body }, - timeoutMs, - fetchFn, - ); - - if (res.ok) { - const data = (await res.json()) as FetchAvailableModelsResponse; - modelQuotas = extractModelQuotas(data); - logDebug(`[antigravity] Extracted ${modelQuotas.size} model quotas from API`); - for (const [modelId, quota] of modelQuotas) { - logDebug( - `[antigravity] ${modelId}: ${(quota.remainingFraction * 100).toFixed(1)}% remaining${quota.resetTime ? ` (resets ${new Date(quota.resetTime).toISOString()})` : ""}`, - ); - } - } else { - const err = await parseErrorMessage(res); - if (res.status === 401) { - lastError = "Token expired"; - } else if (!lastError) { - lastError = err; - } - } - } catch { - if (!lastError) lastError = "Network error"; - } - - // Build windows from available data - const windows = buildUsageWindows({ credits, modelQuotas }); - - // Return error only if we got nothing - if (windows.length === 0 && lastError) { - logDebug(`[antigravity] Returning error snapshot: ${lastError}`); - return { - provider: "google-antigravity", - displayName: PROVIDER_LABELS["google-antigravity"], - windows: [], - error: lastError, - }; - } - - const snapshot: ProviderUsageSnapshot = { - provider: "google-antigravity", - displayName: PROVIDER_LABELS["google-antigravity"], - windows, - plan: planInfo, - }; - - logDebug( - `[antigravity] Returning snapshot with ${windows.length} windows${planInfo ? ` (plan: ${planInfo})` : ""}`, - ); - logDebug(`[antigravity] Snapshot: ${JSON.stringify(snapshot, null, 2)}`); - - return snapshot; -} diff --git a/src/infra/provider-usage.fetch.ts b/src/infra/provider-usage.fetch.ts index 070396554..e0bcd60c9 100644 --- a/src/infra/provider-usage.fetch.ts +++ b/src/infra/provider-usage.fetch.ts @@ -1,4 +1,3 @@ -export { fetchAntigravityUsage } from "./provider-usage.fetch.antigravity.js"; export { fetchClaudeUsage } from "./provider-usage.fetch.claude.js"; export { fetchCodexUsage } from "./provider-usage.fetch.codex.js"; export { fetchCopilotUsage } from "./provider-usage.fetch.copilot.js"; diff --git a/src/infra/provider-usage.load.ts b/src/infra/provider-usage.load.ts index 39a97a86c..4a118deec 100644 --- a/src/infra/provider-usage.load.ts +++ b/src/infra/provider-usage.load.ts @@ -1,6 +1,5 @@ import { type ProviderAuth, resolveProviderAuths } from "./provider-usage.auth.js"; import { - fetchAntigravityUsage, fetchClaudeUsage, fetchCodexUsage, fetchCopilotUsage, @@ -58,8 +57,6 @@ export async function loadProviderUsageSummary( return await fetchClaudeUsage(auth.token, timeoutMs, fetchFn); case "github-copilot": return await fetchCopilotUsage(auth.token, timeoutMs, fetchFn); - case "google-antigravity": - return await fetchAntigravityUsage(auth.token, timeoutMs, fetchFn); case "google-gemini-cli": return await fetchGeminiUsage(auth.token, timeoutMs, fetchFn, auth.provider); case "openai-codex": diff --git a/src/infra/provider-usage.shared.ts b/src/infra/provider-usage.shared.ts index 6c8c1d9bb..70562c83b 100644 --- a/src/infra/provider-usage.shared.ts +++ b/src/infra/provider-usage.shared.ts @@ -7,7 +7,6 @@ export const PROVIDER_LABELS: Record = { anthropic: "Claude", "github-copilot": "Copilot", "google-gemini-cli": "Gemini", - "google-antigravity": "Antigravity", minimax: "MiniMax", "openai-codex": "Codex", zai: "z.ai", @@ -17,7 +16,6 @@ export const usageProviders: UsageProviderId[] = [ "anthropic", "github-copilot", "google-gemini-cli", - "google-antigravity", "minimax", "openai-codex", "zai", diff --git a/src/infra/provider-usage.types.ts b/src/infra/provider-usage.types.ts index cef446ceb..f5cde17d7 100644 --- a/src/infra/provider-usage.types.ts +++ b/src/infra/provider-usage.types.ts @@ -21,7 +21,6 @@ export type UsageProviderId = | "anthropic" | "github-copilot" | "google-gemini-cli" - | "google-antigravity" | "minimax" | "openai-codex" | "zai";