diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bfadb1189a..d07e9868b63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ Docs: https://docs.openclaw.ai ### Breaking +- **BREAKING:** removed Google Antigravity provider support and the bundled `google-antigravity-auth` plugin. Existing `google-antigravity/*` model/profile configs no longer work; migrate to `google-gemini-cli` or other supported providers. - **BREAKING:** tool-failure replies now hide raw error details by default. OpenClaw still sends a failure summary, but detailed error suffixes (for example provider/runtime messages and local path fragments) now require `/verbose on` or `/verbose full`. - **BREAKING:** CLI local onboarding now sets `session.dmScope` to `per-channel-peer` by default for new/implicit DM scope configuration. If you depend on shared DM continuity across senders, explicitly set `session.dmScope` to `main`. (#23468) Thanks @bmendonca3. - **BREAKING:** unify channel preview-streaming config to `channels..streaming` with enum values `off | partial | block | progress`, and move Slack native stream toggle to `channels.slack.nativeStreaming`. Legacy keys (`streamMode`, Slack boolean `streaming`) are still read and migrated by `openclaw doctor --fix`, but canonical saved config/docs now use the unified names. diff --git a/extensions/google-antigravity-auth/README.md b/extensions/google-antigravity-auth/README.md deleted file mode 100644 index 4e1dee975ea..00000000000 --- a/extensions/google-antigravity-auth/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# Google Antigravity Auth (OpenClaw plugin) - -OAuth provider plugin for **Google Antigravity** (Cloud Code Assist). - -## Enable - -Bundled plugins are disabled by default. Enable this one: - -```bash -openclaw plugins enable google-antigravity-auth -``` - -Restart the Gateway after enabling. - -## Authenticate - -```bash -openclaw 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/index.ts b/extensions/google-antigravity-auth/index.ts deleted file mode 100644 index 055cb15e00b..00000000000 --- a/extensions/google-antigravity-auth/index.ts +++ /dev/null @@ -1,424 +0,0 @@ -import { createHash, randomBytes } from "node:crypto"; -import { createServer } from "node:http"; -import { - buildOauthProviderAuthResult, - emptyPluginConfigSchema, - isWSL2Sync, - type OpenClawPluginApi, - type ProviderAuthContext, -} from "openclaw/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-6-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 = ` - - - - OpenClaw 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 shouldUseManualOAuthFlow(isRemote: boolean): boolean { - return isRemote || isWSL2Sync(); -} - -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: OpenClawPluginApi) { - 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: ProviderAuthContext) => { - 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, - }); - - return buildOauthProviderAuthResult({ - providerId: "google-antigravity", - defaultModel: DEFAULT_MODEL, - access: result.access, - refresh: result.refresh, - expires: result.expires, - email: result.email, - credentialExtra: { projectId: result.projectId }, - 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/openclaw.plugin.json b/extensions/google-antigravity-auth/openclaw.plugin.json deleted file mode 100644 index 2ef207f0486..00000000000 --- a/extensions/google-antigravity-auth/openclaw.plugin.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "id": "google-antigravity-auth", - "providers": ["google-antigravity"], - "configSchema": { - "type": "object", - "additionalProperties": false, - "properties": {} - } -} diff --git a/extensions/google-antigravity-auth/package.json b/extensions/google-antigravity-auth/package.json deleted file mode 100644 index e730f4dcbe4..00000000000 --- a/extensions/google-antigravity-auth/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "@openclaw/google-antigravity-auth", - "version": "2026.2.22", - "private": true, - "description": "OpenClaw Google Antigravity OAuth provider plugin", - "type": "module", - "devDependencies": { - "openclaw": "workspace:*" - }, - "openclaw": { - "extensions": [ - "./index.ts" - ] - } -} diff --git a/src/agents/auth-profiles/oauth.ts b/src/agents/auth-profiles/oauth.ts index 37ca04745c3..a4f10b6a587 100644 --- a/src/agents/auth-profiles/oauth.ts +++ b/src/agents/auth-profiles/oauth.ts @@ -55,7 +55,7 @@ function isProfileConfigCompatible(params: { } function buildOAuthApiKey(provider: string, credentials: OAuthCredentials): string { - const needsProjectId = provider === "google-gemini-cli" || provider === "google-antigravity"; + const needsProjectId = provider === "google-gemini-cli"; return needsProjectId ? JSON.stringify({ token: credentials.access, diff --git a/src/agents/live-model-filter.ts b/src/agents/live-model-filter.ts index c4ad0957d81..26ee0adfa00 100644 --- a/src/agents/live-model-filter.ts +++ b/src/agents/live-model-filter.ts @@ -56,10 +56,6 @@ export function isModernModelRef(ref: ModelRef): boolean { return matchesPrefix(id, GOOGLE_PREFIXES); } - if (provider === "google-antigravity") { - return matchesPrefix(id, GOOGLE_PREFIXES) || matchesPrefix(id, ANTHROPIC_PREFIXES); - } - if (provider === "zai") { return matchesPrefix(id, ZAI_PREFIXES); } diff --git a/src/agents/model-forward-compat.antigravity-gemini31.test.ts b/src/agents/model-forward-compat.antigravity-gemini31.test.ts deleted file mode 100644 index 256d20cbf34..00000000000 --- a/src/agents/model-forward-compat.antigravity-gemini31.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -import type { Api, Model } from "@mariozechner/pi-ai"; -import { describe, expect, it } from "vitest"; -import { resolveForwardCompatModel } from "./model-forward-compat.js"; -import type { ModelRegistry } from "./pi-model-discovery.js"; - -function makeRegistry(): ModelRegistry { - const templates = new Map>(); - templates.set("google-antigravity/gemini-3-pro-high", { - id: "gemini-3-pro-high", - name: "Gemini 3 Pro High", - provider: "google-antigravity", - api: "google-antigravity", - input: ["text", "image"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: 200000, - maxTokens: 64000, - reasoning: true, - } as Model); - templates.set("google-antigravity/gemini-3-pro-low", { - id: "gemini-3-pro-low", - name: "Gemini 3 Pro Low", - provider: "google-antigravity", - api: "google-antigravity", - input: ["text", "image"], - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, - contextWindow: 200000, - maxTokens: 64000, - reasoning: true, - } as Model); - - const registry = { - find: (provider: string, modelId: string) => templates.get(`${provider}/${modelId}`) ?? null, - } as unknown as ModelRegistry; - return registry; -} - -describe("resolveForwardCompatModel (google-antigravity Gemini 3.1)", () => { - it("resolves gemini-3-1-pro-high from gemini-3-pro-high template", () => { - const model = resolveForwardCompatModel( - "google-antigravity", - "gemini-3-1-pro-high", - makeRegistry(), - ); - expect(model?.provider).toBe("google-antigravity"); - expect(model?.id).toBe("gemini-3-1-pro-high"); - }); - - it("resolves gemini-3-1-pro-low from gemini-3-pro-low template", () => { - const model = resolveForwardCompatModel( - "google-antigravity", - "gemini-3-1-pro-low", - makeRegistry(), - ); - expect(model?.provider).toBe("google-antigravity"); - expect(model?.id).toBe("gemini-3-1-pro-low"); - }); - - it("supports dot-notation model ids", () => { - const high = resolveForwardCompatModel( - "google-antigravity", - "gemini-3.1-pro-high", - makeRegistry(), - ); - const low = resolveForwardCompatModel( - "google-antigravity", - "gemini-3.1-pro-low", - makeRegistry(), - ); - expect(high?.id).toBe("gemini-3.1-pro-high"); - expect(low?.id).toBe("gemini-3.1-pro-low"); - }); -}); diff --git a/src/agents/model-forward-compat.ts b/src/agents/model-forward-compat.ts index 93e6a57b855..a160302f7eb 100644 --- a/src/agents/model-forward-compat.ts +++ b/src/agents/model-forward-compat.ts @@ -17,51 +17,6 @@ const ANTHROPIC_SONNET_TEMPLATE_MODEL_IDS = ["claude-sonnet-4-5", "claude-sonnet const ZAI_GLM5_MODEL_ID = "glm-5"; const ZAI_GLM5_TEMPLATE_MODEL_IDS = ["glm-4.7"] as const; -const ANTIGRAVITY_OPUS_46_MODEL_ID = "claude-opus-4-6"; -const ANTIGRAVITY_OPUS_46_DOT_MODEL_ID = "claude-opus-4.6"; -const ANTIGRAVITY_OPUS_TEMPLATE_MODEL_IDS = ["claude-opus-4-5", "claude-opus-4.5"] as const; -const ANTIGRAVITY_OPUS_46_THINKING_MODEL_ID = "claude-opus-4-6-thinking"; -const ANTIGRAVITY_OPUS_46_DOT_THINKING_MODEL_ID = "claude-opus-4.6-thinking"; -const ANTIGRAVITY_OPUS_THINKING_TEMPLATE_MODEL_IDS = [ - "claude-opus-4-5-thinking", - "claude-opus-4.5-thinking", -] as const; -const ANTIGRAVITY_GEMINI_31_PRO_HIGH_MODEL_ID = "gemini-3-1-pro-high"; -const ANTIGRAVITY_GEMINI_31_PRO_DOT_HIGH_MODEL_ID = "gemini-3.1-pro-high"; -const ANTIGRAVITY_GEMINI_31_PRO_LOW_MODEL_ID = "gemini-3-1-pro-low"; -const ANTIGRAVITY_GEMINI_31_PRO_DOT_LOW_MODEL_ID = "gemini-3.1-pro-low"; -const ANTIGRAVITY_GEMINI_31_PRO_HIGH_TEMPLATE_MODEL_IDS = ["gemini-3-pro-high"] as const; -const ANTIGRAVITY_GEMINI_31_PRO_LOW_TEMPLATE_MODEL_IDS = ["gemini-3-pro-low"] as const; - -export const ANTIGRAVITY_OPUS_46_FORWARD_COMPAT_CANDIDATES = [ - { - id: ANTIGRAVITY_OPUS_46_THINKING_MODEL_ID, - templatePrefixes: [ - "google-antigravity/claude-opus-4-5-thinking", - "google-antigravity/claude-opus-4.5-thinking", - ], - availabilityAliasIds: [] as const, - }, - { - id: ANTIGRAVITY_OPUS_46_MODEL_ID, - templatePrefixes: ["google-antigravity/claude-opus-4-5", "google-antigravity/claude-opus-4.5"], - availabilityAliasIds: [] as const, - }, -] as const; - -export const ANTIGRAVITY_GEMINI_31_FORWARD_COMPAT_CANDIDATES = [ - { - id: ANTIGRAVITY_GEMINI_31_PRO_HIGH_MODEL_ID, - templatePrefixes: ["google-antigravity/gemini-3-pro-high"], - availabilityAliasIds: [ANTIGRAVITY_GEMINI_31_PRO_DOT_HIGH_MODEL_ID], - }, - { - id: ANTIGRAVITY_GEMINI_31_PRO_LOW_MODEL_ID, - templatePrefixes: ["google-antigravity/gemini-3-pro-low"], - availabilityAliasIds: [ANTIGRAVITY_GEMINI_31_PRO_DOT_LOW_MODEL_ID], - }, -] as const; - function cloneFirstTemplateModel(params: { normalizedProvider: string; trimmedModelId: string; @@ -245,94 +200,6 @@ function resolveZaiGlm5ForwardCompatModel( } as Model); } -function resolveAntigravityOpus46ForwardCompatModel( - provider: string, - modelId: string, - modelRegistry: ModelRegistry, -): Model | undefined { - const normalizedProvider = normalizeProviderId(provider); - if (normalizedProvider !== "google-antigravity") { - return undefined; - } - - const trimmedModelId = modelId.trim(); - const lower = trimmedModelId.toLowerCase(); - const isOpus46 = - lower === ANTIGRAVITY_OPUS_46_MODEL_ID || - lower === ANTIGRAVITY_OPUS_46_DOT_MODEL_ID || - lower.startsWith(`${ANTIGRAVITY_OPUS_46_MODEL_ID}-`) || - lower.startsWith(`${ANTIGRAVITY_OPUS_46_DOT_MODEL_ID}-`); - const isOpus46Thinking = - lower === ANTIGRAVITY_OPUS_46_THINKING_MODEL_ID || - lower === ANTIGRAVITY_OPUS_46_DOT_THINKING_MODEL_ID || - lower.startsWith(`${ANTIGRAVITY_OPUS_46_THINKING_MODEL_ID}-`) || - lower.startsWith(`${ANTIGRAVITY_OPUS_46_DOT_THINKING_MODEL_ID}-`); - if (!isOpus46 && !isOpus46Thinking) { - return undefined; - } - - const templateIds: string[] = []; - if (lower.startsWith(ANTIGRAVITY_OPUS_46_MODEL_ID)) { - templateIds.push(lower.replace(ANTIGRAVITY_OPUS_46_MODEL_ID, "claude-opus-4-5")); - } - if (lower.startsWith(ANTIGRAVITY_OPUS_46_DOT_MODEL_ID)) { - templateIds.push(lower.replace(ANTIGRAVITY_OPUS_46_DOT_MODEL_ID, "claude-opus-4.5")); - } - if (lower.startsWith(ANTIGRAVITY_OPUS_46_THINKING_MODEL_ID)) { - templateIds.push( - lower.replace(ANTIGRAVITY_OPUS_46_THINKING_MODEL_ID, "claude-opus-4-5-thinking"), - ); - } - if (lower.startsWith(ANTIGRAVITY_OPUS_46_DOT_THINKING_MODEL_ID)) { - templateIds.push( - lower.replace(ANTIGRAVITY_OPUS_46_DOT_THINKING_MODEL_ID, "claude-opus-4.5-thinking"), - ); - } - templateIds.push(...ANTIGRAVITY_OPUS_TEMPLATE_MODEL_IDS); - templateIds.push(...ANTIGRAVITY_OPUS_THINKING_TEMPLATE_MODEL_IDS); - - return cloneFirstTemplateModel({ - normalizedProvider, - trimmedModelId, - templateIds, - modelRegistry, - }); -} - -function resolveAntigravityGemini31ForwardCompatModel( - provider: string, - modelId: string, - modelRegistry: ModelRegistry, -): Model | undefined { - const normalizedProvider = normalizeProviderId(provider); - if (normalizedProvider !== "google-antigravity") { - return undefined; - } - - const trimmedModelId = modelId.trim(); - const lower = trimmedModelId.toLowerCase(); - const isGemini31High = - lower === ANTIGRAVITY_GEMINI_31_PRO_HIGH_MODEL_ID || - lower === ANTIGRAVITY_GEMINI_31_PRO_DOT_HIGH_MODEL_ID; - const isGemini31Low = - lower === ANTIGRAVITY_GEMINI_31_PRO_LOW_MODEL_ID || - lower === ANTIGRAVITY_GEMINI_31_PRO_DOT_LOW_MODEL_ID; - if (!isGemini31High && !isGemini31Low) { - return undefined; - } - - const templateIds = isGemini31High - ? [...ANTIGRAVITY_GEMINI_31_PRO_HIGH_TEMPLATE_MODEL_IDS] - : [...ANTIGRAVITY_GEMINI_31_PRO_LOW_TEMPLATE_MODEL_IDS]; - - return cloneFirstTemplateModel({ - normalizedProvider, - trimmedModelId, - templateIds, - modelRegistry, - }); -} - export function resolveForwardCompatModel( provider: string, modelId: string, @@ -342,8 +209,6 @@ export function resolveForwardCompatModel( resolveOpenAICodexGpt53FallbackModel(provider, modelId, modelRegistry) ?? resolveAnthropicOpus46ForwardCompatModel(provider, modelId, modelRegistry) ?? resolveAnthropicSonnet46ForwardCompatModel(provider, modelId, modelRegistry) ?? - resolveZaiGlm5ForwardCompatModel(provider, modelId, modelRegistry) ?? - resolveAntigravityOpus46ForwardCompatModel(provider, modelId, modelRegistry) ?? - resolveAntigravityGemini31ForwardCompatModel(provider, modelId, modelRegistry) + resolveZaiGlm5ForwardCompatModel(provider, modelId, modelRegistry) ); } diff --git a/src/agents/pi-embedded-helpers/google.ts b/src/agents/pi-embedded-helpers/google.ts index f62095f0bc3..46367b98a5a 100644 --- a/src/agents/pi-embedded-helpers/google.ts +++ b/src/agents/pi-embedded-helpers/google.ts @@ -1,22 +1,7 @@ import { sanitizeGoogleTurnOrdering } from "./bootstrap.js"; export function isGoogleModelApi(api?: string | null): boolean { - return ( - api === "google-gemini-cli" || api === "google-generative-ai" || api === "google-antigravity" - ); -} - -export function isAntigravityClaude(params: { - api?: string | null; - provider?: string | null; - modelId?: string; -}): boolean { - const provider = params.provider?.toLowerCase(); - const api = params.api?.toLowerCase(); - if (provider !== "google-antigravity" && api !== "google-antigravity") { - return false; - } - return params.modelId?.toLowerCase().includes("claude") ?? false; + return api === "google-gemini-cli" || api === "google-generative-ai"; } export { sanitizeGoogleTurnOrdering }; diff --git a/src/agents/pi-embedded-runner.google-sanitize-thinking.test.ts b/src/agents/pi-embedded-runner.google-sanitize-thinking.test.ts deleted file mode 100644 index 4e08b49cbd0..00000000000 --- a/src/agents/pi-embedded-runner.google-sanitize-thinking.test.ts +++ /dev/null @@ -1,309 +0,0 @@ -import type { AgentMessage } from "@mariozechner/pi-agent-core"; -import { SessionManager } from "@mariozechner/pi-coding-agent"; -import { describe, expect, it } from "vitest"; -import { sanitizeSessionHistory } from "./pi-embedded-runner/google.js"; - -type AssistantContentBlock = { - type?: string; - text?: string; - thinking?: string; - thinkingSignature?: string; - thought_signature?: string; - thoughtSignature?: string; - id?: string; - name?: string; - arguments?: unknown; -}; - -function getAssistantMessage(out: AgentMessage[]) { - const assistant = out.find((msg) => (msg as { role?: string }).role === "assistant") as - | { content?: AssistantContentBlock[] } - | undefined; - if (!assistant) { - throw new Error("Expected assistant message in sanitized history"); - } - return assistant; -} - -async function sanitizeGoogleAssistantWithContent(content: unknown[]) { - const sessionManager = SessionManager.inMemory(); - const input = [ - { - role: "user", - content: "hi", - }, - { - role: "assistant", - content, - }, - ] as unknown as AgentMessage[]; - - const out = await sanitizeSessionHistory({ - messages: input, - modelApi: "google-antigravity", - sessionManager, - sessionId: "session:google", - }); - - return getAssistantMessage(out); -} - -async function sanitizeSimpleSession(params: { - modelApi: string; - sessionId: string; - content: unknown[]; - modelId?: string; - provider?: string; -}) { - const sessionManager = SessionManager.inMemory(); - const input = [ - { - role: "user", - content: "hi", - }, - { - role: "assistant", - content: params.content, - }, - ] as unknown as AgentMessage[]; - - return sanitizeSessionHistory({ - messages: input, - modelApi: params.modelApi, - provider: params.provider, - modelId: params.modelId, - sessionManager, - sessionId: params.sessionId, - }); -} - -function geminiThoughtSignatureInput() { - return [ - { type: "text", text: "hello", thought_signature: "msg_abc123" }, - { type: "thinking", thinking: "ok", thought_signature: "c2ln" }, - { - type: "toolCall", - id: "call_1", - name: "read", - arguments: { path: "/tmp/foo" }, - thoughtSignature: '{"id":1}', - }, - { - type: "toolCall", - id: "call_2", - name: "read", - arguments: { path: "/tmp/bar" }, - thoughtSignature: "c2ln", - }, - ]; -} - -describe("sanitizeSessionHistory (google thinking)", () => { - it("keeps thinking blocks without signatures for Google models", async () => { - const assistant = await sanitizeGoogleAssistantWithContent([ - { type: "thinking", thinking: "reasoning" }, - ]); - expect(assistant.content?.map((block) => block.type)).toEqual(["thinking"]); - expect(assistant.content?.[0]?.thinking).toBe("reasoning"); - }); - - it("keeps thinking blocks with signatures for Google models", async () => { - const assistant = await sanitizeGoogleAssistantWithContent([ - { type: "thinking", thinking: "reasoning", thinkingSignature: "sig" }, - ]); - expect(assistant.content?.map((block) => block.type)).toEqual(["thinking"]); - expect(assistant.content?.[0]?.thinking).toBe("reasoning"); - expect(assistant.content?.[0]?.thinkingSignature).toBe("sig"); - }); - - it("keeps thinking blocks with Anthropic-style signatures for Google models", async () => { - const assistant = await sanitizeGoogleAssistantWithContent([ - { type: "thinking", thinking: "reasoning", signature: "sig" }, - ]); - expect(assistant.content?.map((block) => block.type)).toEqual(["thinking"]); - expect(assistant.content?.[0]?.thinking).toBe("reasoning"); - }); - - it("converts unsigned thinking blocks to text for Antigravity Claude", async () => { - const out = await sanitizeSimpleSession({ - modelApi: "google-antigravity", - modelId: "anthropic/claude-3.5-sonnet", - sessionId: "session:antigravity-claude", - content: [{ type: "thinking", thinking: "reasoning" }], - }); - - const assistant = out.find((msg) => (msg as { role?: string }).role === "assistant") as { - content?: Array<{ type?: string; text?: string }>; - }; - expect(assistant.content).toEqual([{ type: "text", text: "reasoning" }]); - }); - - it("maps base64 signatures to thinkingSignature for Antigravity Claude", async () => { - const out = await sanitizeSimpleSession({ - modelApi: "google-antigravity", - modelId: "anthropic/claude-3.5-sonnet", - sessionId: "session:antigravity-claude", - content: [{ type: "thinking", thinking: "reasoning", signature: "c2ln" }], - }); - - const assistant = getAssistantMessage(out); - expect(assistant.content?.map((block) => block.type)).toEqual(["thinking"]); - expect(assistant.content?.[0]?.thinking).toBe("reasoning"); - expect(assistant.content?.[0]?.thinkingSignature).toBe("c2ln"); - }); - - it("preserves order for mixed assistant content", async () => { - const sessionManager = SessionManager.inMemory(); - const input = [ - { - role: "user", - content: "hi", - }, - { - role: "assistant", - content: [ - { type: "text", text: "hello" }, - { type: "thinking", thinking: "internal note" }, - { type: "text", text: "world" }, - ], - }, - ] as unknown as AgentMessage[]; - - const out = await sanitizeSessionHistory({ - messages: input, - modelApi: "google-antigravity", - sessionManager, - sessionId: "session:google-mixed", - }); - - const assistant = out.find((msg) => (msg as { role?: string }).role === "assistant") as { - content?: Array<{ type?: string; text?: string; thinking?: string }>; - }; - expect(assistant.content?.map((block) => block.type)).toEqual(["text", "thinking", "text"]); - expect(assistant.content?.[1]?.thinking).toBe("internal note"); - }); - - it("strips non-base64 thought signatures for OpenRouter Gemini", async () => { - const out = await sanitizeSimpleSession({ - modelApi: "openrouter", - provider: "openrouter", - modelId: "google/gemini-1.5-pro", - sessionId: "session:openrouter-gemini", - content: geminiThoughtSignatureInput(), - }); - - const assistant = getAssistantMessage(out); - expect(assistant.content).toEqual([ - { type: "text", text: "hello" }, - { type: "thinking", thinking: "ok", thought_signature: "c2ln" }, - { - type: "toolCall", - id: "call_1", - name: "read", - arguments: { path: "/tmp/foo" }, - }, - { - type: "toolCall", - id: "call_2", - name: "read", - arguments: { path: "/tmp/bar" }, - thoughtSignature: "c2ln", - }, - ]); - }); - - it("strips non-base64 thought signatures for native Google Gemini", async () => { - const out = await sanitizeSimpleSession({ - modelApi: "google-generative-ai", - provider: "google", - modelId: "gemini-2.0-flash", - sessionId: "session:google-gemini", - content: geminiThoughtSignatureInput(), - }); - - const assistant = getAssistantMessage(out); - expect(assistant.content).toEqual([ - { type: "text", text: "hello" }, - { type: "thinking", thinking: "ok", thought_signature: "c2ln" }, - { - type: "toolCall", - id: "call1", - name: "read", - arguments: { path: "/tmp/foo" }, - }, - { - type: "toolCall", - id: "call2", - name: "read", - arguments: { path: "/tmp/bar" }, - thoughtSignature: "c2ln", - }, - ]); - }); - - it("keeps mixed signed/unsigned thinking blocks for Google models", async () => { - const assistant = await sanitizeGoogleAssistantWithContent([ - { type: "thinking", thinking: "signed", thinkingSignature: "sig" }, - { type: "thinking", thinking: "unsigned" }, - ]); - expect(assistant.content?.map((block) => block.type)).toEqual(["thinking", "thinking"]); - expect(assistant.content?.[0]?.thinking).toBe("signed"); - expect(assistant.content?.[1]?.thinking).toBe("unsigned"); - }); - - it("keeps empty thinking blocks for Google models", async () => { - const assistant = await sanitizeGoogleAssistantWithContent([ - { type: "thinking", thinking: " " }, - ]); - expect(assistant?.content?.map((block) => block.type)).toEqual(["thinking"]); - }); - - it("keeps thinking blocks for non-Google models", async () => { - const out = await sanitizeSimpleSession({ - modelApi: "openai", - sessionId: "session:openai", - content: [{ type: "thinking", thinking: "reasoning" }], - }); - - const assistant = out.find((msg) => (msg as { role?: string }).role === "assistant") as { - content?: Array<{ type?: string }>; - }; - expect(assistant.content?.map((block) => block.type)).toEqual(["thinking"]); - }); - - it("sanitizes tool call ids for Google APIs", async () => { - const sessionManager = SessionManager.inMemory(); - const longId = `call_${"a".repeat(60)}`; - const input = [ - { - role: "assistant", - content: [{ type: "toolCall", id: longId, name: "read", arguments: {} }], - }, - { - role: "toolResult", - toolCallId: longId, - toolName: "read", - content: [{ type: "text", text: "ok" }], - }, - ] as unknown as AgentMessage[]; - - const out = await sanitizeSessionHistory({ - messages: input, - modelApi: "google-antigravity", - sessionManager, - sessionId: "session:google", - }); - - const assistant = out.find( - (msg) => (msg as { role?: unknown }).role === "assistant", - ) as Extract; - const toolCall = assistant.content?.[0] as { id?: string }; - expect(toolCall.id).toBeDefined(); - expect(toolCall.id?.length).toBeLessThanOrEqual(40); - - const toolResult = out.find( - (msg) => (msg as { role?: unknown }).role === "toolResult", - ) as Extract; - expect(toolResult.toolCallId).toBe(toolCall.id); - }); -}); diff --git a/src/agents/pi-embedded-runner/google.test.ts b/src/agents/pi-embedded-runner/google.test.ts index 76e067a3764..d0a04665c68 100644 --- a/src/agents/pi-embedded-runner/google.test.ts +++ b/src/agents/pi-embedded-runner/google.test.ts @@ -42,26 +42,6 @@ describe("sanitizeToolsForGoogle", () => { expectFormatRemoved(sanitized, "additionalProperties"); }); - it("strips unsupported schema keywords for google-antigravity", () => { - const tool = createTool({ - type: "object", - patternProperties: { - "^x-": { type: "string" }, - }, - properties: { - foo: { - type: "string", - format: "uuid", - }, - }, - }); - const [sanitized] = sanitizeToolsForGoogle({ - tools: [tool], - provider: "google-antigravity", - }); - expectFormatRemoved(sanitized, "patternProperties"); - }); - it("returns original tools for non-google providers", () => { const tool = createTool({ type: "object", diff --git a/src/agents/pi-embedded-runner/google.ts b/src/agents/pi-embedded-runner/google.ts index ce702d63b51..42970ea4ef6 100644 --- a/src/agents/pi-embedded-runner/google.ts +++ b/src/agents/pi-embedded-runner/google.ts @@ -25,7 +25,7 @@ import { import type { TranscriptPolicy } from "../transcript-policy.js"; import { resolveTranscriptPolicy } from "../transcript-policy.js"; import { log } from "./logger.js"; -import { dropThinkingBlocks, isAssistantMessageWithContent } from "./thinking.js"; +import { dropThinkingBlocks } from "./thinking.js"; import { describeUnknownError } from "./utils.js"; const GOOGLE_TURN_ORDERING_CUSTOM_TYPE = "google-turn-ordering-bootstrap"; @@ -52,85 +52,8 @@ const GOOGLE_SCHEMA_UNSUPPORTED_KEYWORDS = new Set([ "maxProperties", ]); -const ANTIGRAVITY_SIGNATURE_RE = /^[A-Za-z0-9+/]+={0,2}$/; const INTER_SESSION_PREFIX_BASE = "[Inter-session message]"; -function isValidAntigravitySignature(value: unknown): value is string { - if (typeof value !== "string") { - return false; - } - const trimmed = value.trim(); - if (!trimmed) { - return false; - } - if (trimmed.length % 4 !== 0) { - return false; - } - return ANTIGRAVITY_SIGNATURE_RE.test(trimmed); -} - -export function sanitizeAntigravityThinkingBlocks(messages: AgentMessage[]): AgentMessage[] { - let touched = false; - const out: AgentMessage[] = []; - for (const msg of messages) { - if (!isAssistantMessageWithContent(msg)) { - out.push(msg); - continue; - } - const assistant = msg; - type AssistantContentBlock = Extract["content"][number]; - const nextContent: AssistantContentBlock[] = []; - let contentChanged = false; - for (const block of assistant.content) { - if ( - !block || - typeof block !== "object" || - (block as { type?: unknown }).type !== "thinking" - ) { - nextContent.push(block); - continue; - } - const rec = block as { - thinkingSignature?: unknown; - signature?: unknown; - thought_signature?: unknown; - thoughtSignature?: unknown; - }; - const candidate = - rec.thinkingSignature ?? rec.signature ?? rec.thought_signature ?? rec.thoughtSignature; - if (!isValidAntigravitySignature(candidate)) { - // Preserve reasoning content as plain text when signatures are invalid/missing. - // Antigravity Claude rejects unsigned thinking blocks, but dropping them loses context. - const thinkingText = (block as { thinking?: unknown }).thinking; - if (typeof thinkingText === "string" && thinkingText.trim()) { - nextContent.push({ type: "text", text: thinkingText } as AssistantContentBlock); - } - contentChanged = true; - continue; - } - if (rec.thinkingSignature !== candidate) { - const nextBlock = { - ...(block as unknown as Record), - thinkingSignature: candidate, - } as AssistantContentBlock; - nextContent.push(nextBlock); - contentChanged = true; - } else { - nextContent.push(block); - } - } - if (contentChanged) { - touched = true; - } - if (nextContent.length === 0) { - touched = true; - continue; - } - out.push(contentChanged ? { ...assistant, content: nextContent } : msg); - } - return touched ? out : messages; -} - function buildInterSessionPrefix(message: AgentMessage): string { const provenance = normalizeInputProvenance((message as { provenance?: unknown }).provenance); if (!provenance) { @@ -284,7 +207,7 @@ export function sanitizeToolsForGoogle< // AND Claude models. This field does not support JSON Schema keywords such as // patternProperties, additionalProperties, $ref, etc. We must clean schemas // for every provider that routes through this path. - if (params.provider !== "google-gemini-cli" && params.provider !== "google-antigravity") { + if (params.provider !== "google-gemini-cli") { return params.tools; } return params.tools.map((tool) => { @@ -301,7 +224,7 @@ export function sanitizeToolsForGoogle< } export function logToolSchemasForGoogle(params: { tools: AgentTool[]; provider: string }) { - if (params.provider !== "google-antigravity" && params.provider !== "google-gemini-cli") { + if (params.provider !== "google-gemini-cli") { return; } const toolNames = params.tools.map((tool, index) => `${index}:${tool.name}`); @@ -481,10 +404,7 @@ export async function sanitizeSessionHistory(params: { const droppedThinking = policy.dropThinkingBlocks ? dropThinkingBlocks(sanitizedImages) : sanitizedImages; - const sanitizedThinking = policy.sanitizeThinkingSignatures - ? sanitizeAntigravityThinkingBlocks(droppedThinking) - : droppedThinking; - const sanitizedToolCalls = sanitizeToolCallInputs(sanitizedThinking, { + const sanitizedToolCalls = sanitizeToolCallInputs(droppedThinking, { allowedToolNames: params.allowedToolNames, }); const repairedTools = policy.repairToolUseResultPairing diff --git a/src/agents/pi-embedded-runner/model.test.ts b/src/agents/pi-embedded-runner/model.test.ts index 31b3d6511b0..f0fb134263d 100644 --- a/src/agents/pi-embedded-runner/model.test.ts +++ b/src/agents/pi-embedded-runner/model.test.ts @@ -232,62 +232,6 @@ describe("resolveModel", () => { }); }); - it("builds an antigravity forward-compat fallback for claude-opus-4-6-thinking", () => { - mockDiscoveredModel({ - provider: "google-antigravity", - modelId: "claude-opus-4-5-thinking", - templateModel: buildForwardCompatTemplate({ - id: "claude-opus-4-5-thinking", - name: "Claude Opus 4.5 Thinking", - provider: "google-antigravity", - api: "google-gemini-cli", - baseUrl: "https://daily-cloudcode-pa.sandbox.googleapis.com", - }), - }); - - expectResolvedForwardCompatFallback({ - provider: "google-antigravity", - id: "claude-opus-4-6-thinking", - expectedModel: { - provider: "google-antigravity", - id: "claude-opus-4-6-thinking", - api: "google-gemini-cli", - baseUrl: "https://daily-cloudcode-pa.sandbox.googleapis.com", - reasoning: true, - contextWindow: 200000, - maxTokens: 64000, - }, - }); - }); - - it("builds an antigravity forward-compat fallback for claude-opus-4-6", () => { - mockDiscoveredModel({ - provider: "google-antigravity", - modelId: "claude-opus-4-5", - templateModel: buildForwardCompatTemplate({ - id: "claude-opus-4-5", - name: "Claude Opus 4.5", - provider: "google-antigravity", - api: "google-gemini-cli", - baseUrl: "https://daily-cloudcode-pa.sandbox.googleapis.com", - }), - }); - - expectResolvedForwardCompatFallback({ - provider: "google-antigravity", - id: "claude-opus-4-6", - expectedModel: { - provider: "google-antigravity", - id: "claude-opus-4-6", - api: "google-gemini-cli", - baseUrl: "https://daily-cloudcode-pa.sandbox.googleapis.com", - reasoning: true, - contextWindow: 200000, - maxTokens: 64000, - }, - }); - }); - it("builds a zai forward-compat fallback for glm-5", () => { mockDiscoveredModel({ provider: "zai", diff --git a/src/agents/pi-embedded-runner/run/attempt.ts b/src/agents/pi-embedded-runner/run/attempt.ts index e98b3607b30..ab9c557f84a 100644 --- a/src/agents/pi-embedded-runner/run/attempt.ts +++ b/src/agents/pi-embedded-runner/run/attempt.ts @@ -82,7 +82,6 @@ import { buildEmbeddedExtensionFactories } from "../extensions.js"; import { applyExtraParamsToAgent } from "../extra-params.js"; import { logToolSchemasForGoogle, - sanitizeAntigravityThinkingBlocks, sanitizeSessionHistory, sanitizeToolsForGoogle, } from "../google.js"; @@ -1062,10 +1061,7 @@ export async function runEmbeddedAttempt( sessionManager.resetLeaf(); } const sessionContext = sessionManager.buildSessionContext(); - const sanitizedOrphan = transcriptPolicy.sanitizeThinkingSignatures - ? sanitizeAntigravityThinkingBlocks(sessionContext.messages) - : sessionContext.messages; - activeSession.agent.replaceMessages(sanitizedOrphan); + activeSession.agent.replaceMessages(sessionContext.messages); log.warn( `Removed orphaned user message to prevent consecutive user turns. ` + `runId=${params.runId} sessionId=${params.sessionId}`, diff --git a/src/agents/pi-tools.schema.ts b/src/agents/pi-tools.schema.ts index 41fdefb766e..f17d0077626 100644 --- a/src/agents/pi-tools.schema.ts +++ b/src/agents/pi-tools.schema.ts @@ -78,16 +78,14 @@ export function normalizeToolParameters( // - Gemini rejects several JSON Schema keywords, so we scrub those. // - OpenAI rejects function tool schemas unless the *top-level* is `type: "object"`. // (TypeBox root unions compile to `{ anyOf: [...] }` without `type`). - // - Anthropic (google-antigravity) expects full JSON Schema draft 2020-12 compliance. + // - Anthropic expects full JSON Schema draft 2020-12 compliance. // // Normalize once here so callers can always pass `tools` through unchanged. const isGeminiProvider = options?.modelProvider?.toLowerCase().includes("google") || options?.modelProvider?.toLowerCase().includes("gemini"); - const isAnthropicProvider = - options?.modelProvider?.toLowerCase().includes("anthropic") || - options?.modelProvider?.toLowerCase().includes("google-antigravity"); + const isAnthropicProvider = options?.modelProvider?.toLowerCase().includes("anthropic"); // If schema already has type + properties (no top-level anyOf to merge), // clean it for Gemini compatibility (but only if using Gemini, not Anthropic) diff --git a/src/agents/transcript-policy.ts b/src/agents/transcript-policy.ts index 0458c3d1a24..a94d7eb2c9f 100644 --- a/src/agents/transcript-policy.ts +++ b/src/agents/transcript-policy.ts @@ -1,5 +1,5 @@ import { normalizeProviderId } from "./model-selection.js"; -import { isAntigravityClaude, isGoogleModelApi } from "./pi-embedded-helpers/google.js"; +import { isGoogleModelApi } from "./pi-embedded-helpers/google.js"; import type { ToolCallIdMode } from "./tool-call-id.js"; export type TranscriptSanitizeMode = "full" | "images-only"; @@ -88,12 +88,6 @@ export function resolveTranscriptPolicy(params: { const isOpenRouterGemini = (provider === "openrouter" || provider === "opencode") && modelId.toLowerCase().includes("gemini"); - const isAntigravityClaudeModel = isAntigravityClaude({ - api: params.modelApi, - provider, - modelId, - }); - const isCopilotClaude = provider === "github-copilot" && modelId.toLowerCase().includes("claude"); // GitHub Copilot's Claude endpoints can reject persisted `thinking` blocks with @@ -112,16 +106,15 @@ export function resolveTranscriptPolicy(params: { const repairToolUseResultPairing = isGoogle || isAnthropic; const sanitizeThoughtSignatures = isOpenRouterGemini || isGoogle ? { allowBase64Only: true, includeCamelCase: true } : undefined; - const sanitizeThinkingSignatures = isAntigravityClaudeModel; return { sanitizeMode: isOpenAi ? "images-only" : needsNonImageSanitize ? "full" : "images-only", sanitizeToolCallIds: !isOpenAi && sanitizeToolCallIds, toolCallIdMode, repairToolUseResultPairing: !isOpenAi && repairToolUseResultPairing, - preserveSignatures: isAntigravityClaudeModel, + preserveSignatures: false, sanitizeThoughtSignatures: isOpenAi ? undefined : sanitizeThoughtSignatures, - sanitizeThinkingSignatures, + sanitizeThinkingSignatures: false, dropThinkingBlocks, applyGoogleTurnOrdering: !isOpenAi && isGoogle, validateGeminiTurns: !isOpenAi && isGoogle, diff --git a/src/auto-reply/reply/agent-runner.misc.runreplyagent.test.ts b/src/auto-reply/reply/agent-runner.misc.runreplyagent.test.ts index 5d04655525c..14819dd9c79 100644 --- a/src/auto-reply/reply/agent-runner.misc.runreplyagent.test.ts +++ b/src/auto-reply/reply/agent-runner.misc.runreplyagent.test.ts @@ -1179,8 +1179,8 @@ describe("runReplyAgent fallback reasoning tags", () => { }); runWithModelFallbackMock.mockImplementationOnce( async ({ run }: RunWithModelFallbackParams) => ({ - result: await run("google-antigravity", "gemini-3"), - provider: "google-antigravity", + result: await run("google-gemini-cli", "gemini-3"), + provider: "google-gemini-cli", model: "gemini-3", }), ); @@ -1199,8 +1199,8 @@ describe("runReplyAgent fallback reasoning tags", () => { return { payloads: [{ text: "ok" }], meta: {} }; }); runWithModelFallbackMock.mockImplementation(async ({ run }: RunWithModelFallbackParams) => ({ - result: await run("google-antigravity", "gemini-3"), - provider: "google-antigravity", + result: await run("google-gemini-cli", "gemini-3"), + provider: "google-gemini-cli", model: "gemini-3", })); diff --git a/src/commands/auth-choice-options.ts b/src/commands/auth-choice-options.ts index 0bc5c299cc1..ea2f7218cb7 100644 --- a/src/commands/auth-choice-options.ts +++ b/src/commands/auth-choice-options.ts @@ -62,7 +62,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: "xai", @@ -254,11 +254,6 @@ const BASE_AUTH_CHOICE_OPTIONS: ReadonlyArray = [ hint: "Uses GitHub device flow", }, { value: "gemini-api-key", label: "Google Gemini API key" }, - { - value: "google-antigravity", - label: "Google Antigravity OAuth", - hint: "Uses the bundled Antigravity auth plugin", - }, { 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 6011b74e060..00000000000 --- 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 7d9791f34dc..e6dfa9ed52a 100644 --- a/src/commands/auth-choice.apply.ts +++ b/src/commands/auth-choice.apply.ts @@ -6,7 +6,6 @@ import { applyAuthChoiceApiProviders } from "./auth-choice.apply.api-providers.j import { applyAuthChoiceBytePlus } from "./auth-choice.apply.byteplus.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"; @@ -44,7 +43,6 @@ export async function applyAuthChoice( applyAuthChoiceApiProviders, applyAuthChoiceMiniMax, applyAuthChoiceGitHubCopilot, - applyAuthChoiceGoogleAntigravity, applyAuthChoiceGoogleGeminiCli, applyAuthChoiceCopilotProxy, applyAuthChoiceQwenPortal, diff --git a/src/commands/auth-choice.preferred-provider.ts b/src/commands/auth-choice.preferred-provider.ts index 5b3abd6d183..68e442044d5 100644 --- a/src/commands/auth-choice.preferred-provider.ts +++ b/src/commands/auth-choice.preferred-provider.ts @@ -18,7 +18,6 @@ const PREFERRED_PROVIDER_BY_AUTH_CHOICE: Partial> = { "moonshot-api-key-cn": "moonshot", "kimi-code-api-key": "kimi-coding", "gemini-api-key": "google", - "google-antigravity": "google-antigravity", "google-gemini-cli": "google-gemini-cli", "mistral-api-key": "mistral", "zai-api-key": "zai", diff --git a/src/commands/models.auth.provider-resolution.test.ts b/src/commands/models.auth.provider-resolution.test.ts index 11cc6d934ac..f03a99bb4cb 100644 --- a/src/commands/models.auth.provider-resolution.test.ts +++ b/src/commands/models.auth.provider-resolution.test.ts @@ -13,24 +13,24 @@ function makeProvider(params: { id: string; label?: string; aliases?: string[] } describe("resolveRequestedLoginProviderOrThrow", () => { it("returns null when no provider was requested", () => { - const providers = [makeProvider({ id: "google-antigravity" })]; + const providers = [makeProvider({ id: "google-gemini-cli" })]; const result = resolveRequestedLoginProviderOrThrow(providers, undefined); expect(result).toBeNull(); }); it("resolves requested provider by id", () => { const providers = [ - makeProvider({ id: "google-antigravity" }), makeProvider({ id: "google-gemini-cli" }), + makeProvider({ id: "qwen-portal" }), ]; - const result = resolveRequestedLoginProviderOrThrow(providers, "google-antigravity"); - expect(result?.id).toBe("google-antigravity"); + const result = resolveRequestedLoginProviderOrThrow(providers, "google-gemini-cli"); + expect(result?.id).toBe("google-gemini-cli"); }); it("resolves requested provider by alias", () => { - const providers = [makeProvider({ id: "google-antigravity", aliases: ["antigravity"] })]; - const result = resolveRequestedLoginProviderOrThrow(providers, "antigravity"); - expect(result?.id).toBe("google-antigravity"); + const providers = [makeProvider({ id: "google-gemini-cli", aliases: ["gemini-cli"] })]; + const result = resolveRequestedLoginProviderOrThrow(providers, "gemini-cli"); + expect(result?.id).toBe("google-gemini-cli"); }); it("throws when requested provider is not loaded", () => { diff --git a/src/commands/models.list.test.ts b/src/commands/models.list.test.ts index 8e9df0035b4..da64561de3f 100644 --- a/src/commands/models.list.test.ts +++ b/src/commands/models.list.test.ts @@ -200,30 +200,6 @@ describe("models list/status", () => { return JSON.parse(String(runtime.log.mock.calls[0]?.[0])); } - async function runAvailabilityFallbackCase(params: { - setup?: () => void; - expectedErrorDetail: string; - }) { - configureGoogleAntigravityModel("claude-opus-4-6-thinking"); - enableGoogleAntigravityAuthProfile(); - const runtime = makeRuntime(); - - modelRegistryState.models = [ - makeGoogleAntigravityTemplate("claude-opus-4-5-thinking", "Claude Opus 4.5 Thinking"), - ]; - modelRegistryState.available = []; - params.setup?.(); - await modelsListCommand({ json: true }, runtime); - - expect(runtime.error).toHaveBeenCalledTimes(1); - expect(runtime.error.mock.calls[0]?.[0]).toContain("falling back to auth heuristics"); - expect(runtime.error.mock.calls[0]?.[0]).toContain(params.expectedErrorDetail); - const payload = parseJsonLog(runtime); - expect(payload.models[0]?.key).toBe("google-antigravity/claude-opus-4-6-thinking"); - expect(payload.models[0]?.missing).toBe(false); - expect(payload.models[0]?.available).toBe(true); - } - async function expectZaiProviderFilter(provider: string) { setDefaultZaiRegistry(); const runtime = makeRuntime(); @@ -242,66 +218,6 @@ describe("models list/status", () => { modelRegistryState.available = available ? [ZAI_MODEL, OPENAI_MODEL] : []; } - function setupGoogleAntigravityTemplateCase(params: { - configuredModelId: string; - templateId: string; - templateName: string; - available?: boolean; - }) { - configureGoogleAntigravityModel(params.configuredModelId); - const template = makeGoogleAntigravityTemplate(params.templateId, params.templateName); - modelRegistryState.models = [template]; - modelRegistryState.available = params.available ? [template] : []; - return template; - } - - async function runGoogleAntigravityListCase(params: { - configuredModelId: string; - templateId: string; - templateName: string; - available?: boolean; - withAuthProfile?: boolean; - }) { - setupGoogleAntigravityTemplateCase(params); - if (params.withAuthProfile) { - enableGoogleAntigravityAuthProfile(); - } - const runtime = makeRuntime(); - await modelsListCommand({ json: true }, runtime); - return parseJsonLog(runtime); - } - - const GOOGLE_ANTIGRAVITY_OPUS_46_CASES = [ - { - name: "thinking", - configuredModelId: "claude-opus-4-6-thinking", - templateId: "claude-opus-4-5-thinking", - templateName: "Claude Opus 4.5 Thinking", - expectedKey: "google-antigravity/claude-opus-4-6-thinking", - }, - { - name: "non-thinking", - configuredModelId: "claude-opus-4-6", - templateId: "claude-opus-4-5", - templateName: "Claude Opus 4.5", - expectedKey: "google-antigravity/claude-opus-4-6", - }, - ] as const; - - function expectAntigravityModel( - payload: Record, - params: { key: string; available: boolean; includesTags?: boolean }, - ) { - const model = (payload.models as Array>)[0] ?? {}; - expect(model.key).toBe(params.key); - expect(model.missing).toBe(false); - expect(model.available).toBe(params.available); - if (params.includesTags) { - expect(model.tags).toContain("default"); - expect(model.tags).toContain("configured"); - } - } - beforeAll(async () => { ({ modelsListCommand } = await import("./models/list.list-command.js")); ({ loadModelRegistry, toModelRow } = await import("./models/list.registry.js")); @@ -357,177 +273,6 @@ describe("models list/status", () => { expect(payload.models[0]?.available).toBe(false); }); - it.each(GOOGLE_ANTIGRAVITY_OPUS_46_CASES)( - "models list resolves antigravity opus 4.6 $name from 4.5 template", - async ({ configuredModelId, templateId, templateName, expectedKey }) => { - const payload = await runGoogleAntigravityListCase({ - configuredModelId, - templateId, - templateName, - }); - expectAntigravityModel(payload, { - key: expectedKey, - available: false, - includesTags: true, - }); - }, - ); - - it.each(GOOGLE_ANTIGRAVITY_OPUS_46_CASES)( - "models list marks synthesized antigravity opus 4.6 $name as available when template is available", - async ({ configuredModelId, templateId, templateName, expectedKey }) => { - const payload = await runGoogleAntigravityListCase({ - configuredModelId, - templateId, - templateName, - available: true, - }); - expectAntigravityModel(payload, { - key: expectedKey, - available: true, - }); - }, - ); - - it.each([ - { - name: "high", - configuredModelId: "gemini-3-1-pro-high", - templateId: "gemini-3-pro-high", - templateName: "Gemini 3 Pro High", - expectedKey: "google-antigravity/gemini-3-1-pro-high", - }, - { - name: "low", - configuredModelId: "gemini-3-1-pro-low", - templateId: "gemini-3-pro-low", - templateName: "Gemini 3 Pro Low", - expectedKey: "google-antigravity/gemini-3-1-pro-low", - }, - ] as const)( - "models list resolves antigravity gemini 3.1 $name from gemini 3 template", - async ({ configuredModelId, templateId, templateName, expectedKey }) => { - const payload = await runGoogleAntigravityListCase({ - configuredModelId, - templateId, - templateName, - }); - expectAntigravityModel(payload, { - key: expectedKey, - available: false, - includesTags: true, - }); - }, - ); - - it.each([ - { - name: "high", - configuredModelId: "gemini-3-1-pro-high", - templateId: "gemini-3-pro-high", - templateName: "Gemini 3 Pro High", - expectedKey: "google-antigravity/gemini-3-1-pro-high", - }, - { - name: "low", - configuredModelId: "gemini-3-1-pro-low", - templateId: "gemini-3-pro-low", - templateName: "Gemini 3 Pro Low", - expectedKey: "google-antigravity/gemini-3-1-pro-low", - }, - ] as const)( - "models list marks synthesized antigravity gemini 3.1 $name as available when template is available", - async ({ configuredModelId, templateId, templateName, expectedKey }) => { - const payload = await runGoogleAntigravityListCase({ - configuredModelId, - templateId, - templateName, - available: true, - }); - expectAntigravityModel(payload, { - key: expectedKey, - available: true, - }); - }, - ); - - it.each([ - { - name: "high", - configuredModelId: "gemini-3.1-pro-high", - templateId: "gemini-3-pro-high", - templateName: "Gemini 3 Pro High", - expectedKey: "google-antigravity/gemini-3.1-pro-high", - }, - { - name: "low", - configuredModelId: "gemini-3.1-pro-low", - templateId: "gemini-3-pro-low", - templateName: "Gemini 3 Pro Low", - expectedKey: "google-antigravity/gemini-3.1-pro-low", - }, - ] as const)( - "models list marks dot-notation antigravity gemini 3.1 $name as available when template is available", - async ({ configuredModelId, templateId, templateName, expectedKey }) => { - const payload = await runGoogleAntigravityListCase({ - configuredModelId, - templateId, - templateName, - available: true, - }); - expectAntigravityModel(payload, { - key: expectedKey, - available: true, - }); - }, - ); - - it("models list prefers registry availability over provider auth heuristics", async () => { - const payload = await runGoogleAntigravityListCase({ - configuredModelId: "claude-opus-4-6-thinking", - templateId: "claude-opus-4-5-thinking", - templateName: "Claude Opus 4.5 Thinking", - withAuthProfile: true, - }); - expectAntigravityModel(payload, { - key: "google-antigravity/claude-opus-4-6-thinking", - available: false, - }); - listProfilesForProvider.mockReturnValue([]); - }); - - it("models list falls back to auth heuristics when registry availability is unavailable", async () => { - await runAvailabilityFallbackCase({ - setup: () => { - modelRegistryState.getAvailableError = Object.assign( - new Error("availability unsupported: getAvailable failed"), - { code: "MODEL_AVAILABILITY_UNAVAILABLE" }, - ); - }, - expectedErrorDetail: "getAvailable failed", - }); - }); - - it("models list falls back to auth heuristics when getAvailable returns invalid shape", async () => { - await runAvailabilityFallbackCase({ - setup: () => { - modelRegistryState.available = { bad: true } as unknown as Array>; - }, - expectedErrorDetail: "non-array value", - }); - }); - - it("models list falls back to auth heuristics when getAvailable throws", async () => { - await runAvailabilityFallbackCase({ - setup: () => { - modelRegistryState.getAvailableError = new Error( - "availability unsupported: getAvailable failed", - ); - }, - expectedErrorDetail: "availability unsupported: getAvailable failed", - }); - }); - it("models list does not treat availability-unavailable code as discovery fallback", async () => { configureGoogleAntigravityModel("claude-opus-4-6-thinking"); modelRegistryState.getAllError = Object.assign(new Error("model discovery failed"), { diff --git a/src/commands/models/list.registry.ts b/src/commands/models/list.registry.ts index b0daded3db7..23cef29485c 100644 --- a/src/commands/models/list.registry.ts +++ b/src/commands/models/list.registry.ts @@ -7,11 +7,6 @@ import { resolveAwsSdkEnvVarName, resolveEnvApiKey, } from "../../agents/model-auth.js"; -import { - ANTIGRAVITY_GEMINI_31_FORWARD_COMPAT_CANDIDATES, - ANTIGRAVITY_OPUS_46_FORWARD_COMPAT_CANDIDATES, - resolveForwardCompatModel, -} from "../../agents/model-forward-compat.js"; import { ensureOpenClawModelsJson } from "../../agents/models-config.js"; import { ensurePiAuthJsonFromAuthProfiles } from "../../agents/pi-auth-json.js"; import type { ModelRegistry } from "../../agents/pi-model-discovery.js"; @@ -106,23 +101,13 @@ export async function loadModelRegistry(cfg: OpenClawConfig) { await ensurePiAuthJsonFromAuthProfiles(agentDir); const authStorage = discoverAuthStorage(agentDir); const registry = discoverModels(authStorage, agentDir); - const appended = appendAntigravityForwardCompatModels(registry.getAll(), registry); - const models = appended.models; - const synthesizedForwardCompat = appended.synthesizedForwardCompat; + const models = registry.getAll(); let availableKeys: Set | undefined; let availabilityErrorMessage: string | undefined; try { const availableModels = loadAvailableModels(registry); availableKeys = new Set(availableModels.map((model) => modelKey(model.provider, model.id))); - for (const synthesized of synthesizedForwardCompat) { - if (hasAvailableTemplate(availableKeys, synthesized.templatePrefixes)) { - availableKeys.add(synthesized.key); - for (const aliasKey of synthesized.availabilityAliasKeys) { - availableKeys.add(aliasKey); - } - } - } } catch (err) { if (!shouldFallbackToAuthHeuristics(err)) { throw err; @@ -138,60 +123,6 @@ export async function loadModelRegistry(cfg: OpenClawConfig) { return { registry, models, availableKeys, availabilityErrorMessage }; } -type SynthesizedForwardCompat = { - key: string; - templatePrefixes: readonly string[]; - availabilityAliasKeys: readonly string[]; -}; - -function appendAntigravityForwardCompatModels( - models: Model[], - modelRegistry: ModelRegistry, -): { models: Model[]; synthesizedForwardCompat: SynthesizedForwardCompat[] } { - const nextModels = [...models]; - const synthesizedForwardCompat: SynthesizedForwardCompat[] = []; - const candidates = [ - ...ANTIGRAVITY_OPUS_46_FORWARD_COMPAT_CANDIDATES, - ...ANTIGRAVITY_GEMINI_31_FORWARD_COMPAT_CANDIDATES, - ]; - - for (const candidate of candidates) { - const key = modelKey("google-antigravity", candidate.id); - const hasForwardCompat = nextModels.some((model) => modelKey(model.provider, model.id) === key); - if (hasForwardCompat) { - continue; - } - - const fallback = resolveForwardCompatModel("google-antigravity", candidate.id, modelRegistry); - if (!fallback) { - continue; - } - - nextModels.push(fallback); - synthesizedForwardCompat.push({ - key, - templatePrefixes: candidate.templatePrefixes, - availabilityAliasKeys: candidate.availabilityAliasIds.map((id) => - modelKey("google-antigravity", id), - ), - }); - } - - return { models: nextModels, synthesizedForwardCompat }; -} - -function hasAvailableTemplate( - availableKeys: Set, - templatePrefixes: readonly string[], -): boolean { - for (const key of availableKeys) { - if (templatePrefixes.some((prefix) => key.startsWith(prefix))) { - return true; - } - } - return false; -} - export function toModelRow(params: { model?: Model; key: string; diff --git a/src/commands/onboard-types.ts b/src/commands/onboard-types.ts index 96bee13fce7..bb3bdb471d8 100644 --- a/src/commands/onboard-types.ts +++ b/src/commands/onboard-types.ts @@ -26,7 +26,6 @@ export type AuthChoice = | "codex-cli" | "apiKey" | "gemini-api-key" - | "google-antigravity" | "google-gemini-cli" | "zai-api-key" | "zai-coding-global" diff --git a/src/config/plugin-auto-enable.test.ts b/src/config/plugin-auto-enable.test.ts index a0979b537d0..7f5779a1818 100644 --- a/src/config/plugin-auto-enable.test.ts +++ b/src/config/plugin-auto-enable.test.ts @@ -92,8 +92,8 @@ describe("applyPluginAutoEnable", () => { config: { auth: { profiles: { - "google-antigravity:default": { - provider: "google-antigravity", + "google-gemini-cli:default": { + provider: "google-gemini-cli", mode: "oauth", }, }, @@ -102,7 +102,7 @@ describe("applyPluginAutoEnable", () => { env: {}, }); - expect(result.config.plugins?.entries?.["google-antigravity-auth"]?.enabled).toBe(true); + expect(result.config.plugins?.entries?.["google-gemini-cli-auth"]?.enabled).toBe(true); }); it("skips when plugins are globally disabled", () => { diff --git a/src/config/plugin-auto-enable.ts b/src/config/plugin-auto-enable.ts index 50fb9dac90a..63657e3ea21 100644 --- a/src/config/plugin-auto-enable.ts +++ b/src/config/plugin-auto-enable.ts @@ -31,7 +31,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.normalizes-keys.test.ts b/src/infra/provider-usage.auth.normalizes-keys.test.ts index 80068d3d8f5..3dccd2bf1be 100644 --- a/src/infra/provider-usage.auth.normalizes-keys.test.ts +++ b/src/infra/provider-usage.auth.normalizes-keys.test.ts @@ -216,17 +216,17 @@ describe("resolveProviderAuths key normalization", () => { it("keeps raw google token when token payload is not JSON", async () => { await withSuiteHome(async (home) => { await writeAuthProfiles(home, { - "google-antigravity:default": { + "google-gemini-cli:default": { type: "token", - provider: "google-antigravity", + provider: "google-gemini-cli", token: "plain-google-token", }, }); const auths = await resolveProviderAuths({ - providers: ["google-antigravity"], + providers: ["google-gemini-cli"], }); - expect(auths).toEqual([{ provider: "google-antigravity", token: "plain-google-token" }]); + expect(auths).toEqual([{ provider: "google-gemini-cli", token: "plain-google-token" }]); }, {}); }); diff --git a/src/infra/provider-usage.auth.ts b/src/infra/provider-usage.auth.ts index 85551bdfc46..ff63c1570f1 100644 --- a/src/infra/provider-usage.auth.ts +++ b/src/infra/provider-usage.auth.ts @@ -158,7 +158,7 @@ async function resolveOAuthToken(params: { }); if (resolved) { 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; } @@ -188,7 +188,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 728d6b74229..00000000000 --- a/src/infra/provider-usage.fetch.antigravity.test.ts +++ /dev/null @@ -1,469 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { createProviderUsageFetch, makeResponse } from "../test-utils/provider-usage-fetch.js"; -import { fetchAntigravityUsage } from "./provider-usage.fetch.antigravity.js"; - -const getRequestBody = (init?: Parameters[1]) => - typeof init?.body === "string" ? init.body : undefined; - -type EndpointHandler = (init?: Parameters[1]) => Promise | Response; - -function createEndpointFetch(spec: { - loadCodeAssist?: EndpointHandler; - fetchAvailableModels?: EndpointHandler; -}) { - return createProviderUsageFetch(async (url, init) => { - if (url.includes("loadCodeAssist")) { - return (await spec.loadCodeAssist?.(init)) ?? makeResponse(404, "not found"); - } - if (url.includes("fetchAvailableModels")) { - return (await spec.fetchAvailableModels?.(init)) ?? makeResponse(404, "not found"); - } - return makeResponse(404, "not found"); - }); -} - -async function runUsage(mockFetch: ReturnType) { - return fetchAntigravityUsage("token-123", 5000, mockFetch as unknown as typeof fetch); -} - -function findWindow(snapshot: Awaited>, label: string) { - return snapshot.windows.find((window) => window.label === label); -} - -function expectTokenExpired(snapshot: Awaited>) { - expect(snapshot.error).toBe("Token expired"); - expect(snapshot.windows).toHaveLength(0); -} - -function expectSingleWindow( - snapshot: Awaited>, - label: string, -) { - expect(snapshot.windows).toHaveLength(1); - expect(snapshot.windows[0]?.label).toBe(label); - return snapshot.windows[0]; -} - -describe("fetchAntigravityUsage", () => { - it("returns 3 windows when both endpoints succeed", async () => { - const mockFetch = createEndpointFetch({ - loadCodeAssist: () => - makeResponse(200, { - availablePromptCredits: 750, - planInfo: { monthlyPromptCredits: 1000 }, - planType: "Standard", - currentTier: { id: "tier1", name: "Standard Tier" }, - }), - fetchAvailableModels: () => - 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, - }, - }, - }, - }), - }); - - const snapshot = await runUsage(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 = findWindow(snapshot, "Credits"); - expect(creditsWindow?.usedPercent).toBe(25); // (1000 - 750) / 1000 * 100 - - const proWindow = findWindow(snapshot, "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 = findWindow(snapshot, "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 = createEndpointFetch({ - loadCodeAssist: () => - makeResponse(200, { - availablePromptCredits: 250, - planInfo: { monthlyPromptCredits: 1000 }, - currentTier: { name: "Free" }, - }), - fetchAvailableModels: () => makeResponse(403, { error: { message: "Permission denied" } }), - }); - - const snapshot = await runUsage(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 = createEndpointFetch({ - loadCodeAssist: () => makeResponse(500, "Internal server error"), - fetchAvailableModels: () => - 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" }, - }, - }, - }), - }); - - const snapshot = await runUsage(mockFetch); - - expect(snapshot.provider).toBe("google-antigravity"); - expect(snapshot.windows).toHaveLength(2); - expect(snapshot.error).toBeUndefined(); - - const proWindow = findWindow(snapshot, "gemini-pro-1.5"); - expect(proWindow?.usedPercent).toBe(50); // (1 - 0.5) * 100 - - const flashWindow = findWindow(snapshot, "gemini-flash-2.0"); - expect(flashWindow?.usedPercent).toBeCloseTo(30, 1); // (1 - 0.7) * 100 - - expect(mockFetch).toHaveBeenCalledTimes(2); - }); - - it.each([ - { - name: "uses cloudaicompanionProject string as project id", - project: "projects/alpha", - expectedBody: JSON.stringify({ project: "projects/alpha" }), - }, - { - name: "uses cloudaicompanionProject object id when present", - project: { id: "projects/beta" }, - expectedBody: JSON.stringify({ project: "projects/beta" }), - }, - ])("project payload: $name", async ({ project, expectedBody }) => { - let capturedBody: string | undefined; - const mockFetch = createEndpointFetch({ - loadCodeAssist: () => - makeResponse(200, { - availablePromptCredits: 900, - planInfo: { monthlyPromptCredits: 1000 }, - cloudaicompanionProject: project, - }), - fetchAvailableModels: (init) => { - capturedBody = getRequestBody(init); - return makeResponse(200, { models: {} }); - }, - }); - - await runUsage(mockFetch); - expect(capturedBody).toBe(expectedBody); - }); - - it("returns error snapshot when both endpoints fail", async () => { - const mockFetch = createEndpointFetch({ - loadCodeAssist: () => makeResponse(403, { error: { message: "Access denied" } }), - fetchAvailableModels: () => makeResponse(403, "Forbidden"), - }); - - const snapshot = await runUsage(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 = createEndpointFetch({ - loadCodeAssist: () => makeResponse(500, "Boom"), - fetchAvailableModels: () => makeResponse(401, { error: { message: "Unauthorized" } }), - }); - - const snapshot = await runUsage(mockFetch); - expectTokenExpired(snapshot); - }); - - it.each([ - { - name: "extracts plan info from currentTier.name", - loadCodeAssist: { - availablePromptCredits: 500, - planInfo: { monthlyPromptCredits: 1000 }, - planType: "Basic", - currentTier: { id: "tier2", name: "Premium Tier" }, - }, - expectedPlan: "Premium Tier", - }, - { - name: "falls back to planType when currentTier.name is missing", - loadCodeAssist: { - availablePromptCredits: 500, - planInfo: { monthlyPromptCredits: 1000 }, - planType: "Basic Plan", - }, - expectedPlan: "Basic Plan", - }, - ])("plan label: $name", async ({ loadCodeAssist, expectedPlan }) => { - const mockFetch = createEndpointFetch({ - loadCodeAssist: () => makeResponse(200, loadCodeAssist), - fetchAvailableModels: () => makeResponse(500, "Error"), - }); - - const snapshot = await runUsage(mockFetch); - expect(snapshot.plan).toBe(expectedPlan); - }); - - it("includes reset times in model windows", async () => { - const resetTime = "2026-01-10T12:00:00Z"; - const mockFetch = createEndpointFetch({ - loadCodeAssist: () => makeResponse(500, "Error"), - fetchAvailableModels: () => - makeResponse(200, { - models: { - "gemini-pro-experimental": { - quotaInfo: { remainingFraction: 0.3, resetTime }, - }, - }, - }), - }); - - const snapshot = await runUsage(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 = createEndpointFetch({ - loadCodeAssist: () => - makeResponse(200, { - availablePromptCredits: "600", - planInfo: { monthlyPromptCredits: "1000" }, - }), - fetchAvailableModels: () => - makeResponse(200, { - models: { - "gemini-flash-lite": { - quotaInfo: { remainingFraction: "0.9" }, - }, - }, - }), - }); - - const snapshot = await runUsage(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 = createEndpointFetch({ - loadCodeAssist: () => - makeResponse(200, { - availablePromptCredits: 500, - planInfo: { monthlyPromptCredits: 1000 }, - cloudaicompanionProject: "projects/internal", - }), - fetchAvailableModels: () => - makeResponse(200, { - models: { - chat_hidden: { quotaInfo: { remainingFraction: 0.1 } }, - tab_hidden: { quotaInfo: { remainingFraction: 0.2 } }, - "gemini-pro-1.5": { quotaInfo: { remainingFraction: 0.7 } }, - }, - }), - }); - - const snapshot = await runUsage(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 = createEndpointFetch({ - loadCodeAssist: () => makeResponse(500, "Error"), - fetchAvailableModels: () => - 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 } }, - }, - }), - }); - - const snapshot = await runUsage(mockFetch); - expect(snapshot.windows).toHaveLength(4); - 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 = createEndpointFetch({ - loadCodeAssist: () => makeResponse(401, { error: { message: "Unauthorized" } }), - }); - - const snapshot = await runUsage(mockFetch); - expectTokenExpired(snapshot); - expect(mockFetch).toHaveBeenCalledTimes(1); // Should stop early on 401 - }); - - it("handles empty models object gracefully", async () => { - const mockFetch = createEndpointFetch({ - loadCodeAssist: () => - makeResponse(200, { - availablePromptCredits: 800, - planInfo: { monthlyPromptCredits: 1000 }, - }), - fetchAvailableModels: () => makeResponse(200, { models: {} }), - }); - - const snapshot = await runUsage(mockFetch); - expect(snapshot.windows).toHaveLength(1); - const creditsWindow = snapshot.windows[0]; - expect(creditsWindow?.label).toBe("Credits"); - expect(creditsWindow?.usedPercent).toBe(20); - }); - - it("handles missing or invalid model quota payloads", async () => { - const mockFetch = createEndpointFetch({ - loadCodeAssist: () => makeResponse(500, "Error"), - fetchAvailableModels: () => - makeResponse(200, { - models: { - no_quota: {}, - missing_fraction: { quotaInfo: {} }, - invalid_fraction: { quotaInfo: { remainingFraction: "oops" } }, - valid_model: { quotaInfo: { remainingFraction: 0.25 } }, - }, - }), - }); - - const snapshot = await runUsage(mockFetch); - expect(snapshot.windows).toEqual([{ label: "valid_model", usedPercent: 75 }]); - }); - - it("handles non-object models payload gracefully", async () => { - const mockFetch = createEndpointFetch({ - loadCodeAssist: () => - makeResponse(200, { - availablePromptCredits: 900, - planInfo: { monthlyPromptCredits: 1000 }, - }), - fetchAvailableModels: () => makeResponse(200, { models: null }), - }); - - const snapshot = await runUsage(mockFetch); - expect(snapshot.windows).toEqual([{ label: "Credits", usedPercent: 10 }]); - }); - - it("handles missing credits fields gracefully", async () => { - const mockFetch = createEndpointFetch({ - loadCodeAssist: () => makeResponse(200, { planType: "Free" }), - fetchAvailableModels: () => - makeResponse(200, { - models: { - "gemini-flash-experimental": { - quotaInfo: { remainingFraction: 0.5 }, - }, - }, - }), - }); - - const snapshot = await runUsage(mockFetch); - const flashWindow = expectSingleWindow(snapshot, "gemini-flash-experimental"); - expect(flashWindow?.usedPercent).toBe(50); - expect(snapshot.plan).toBe("Free"); - }); - - it("handles invalid reset time gracefully", async () => { - const mockFetch = createEndpointFetch({ - loadCodeAssist: () => makeResponse(500, "Error"), - fetchAvailableModels: () => - makeResponse(200, { - models: { - "gemini-pro-test": { - quotaInfo: { remainingFraction: 0.4, resetTime: "invalid-date" }, - }, - }, - }), - }); - - const snapshot = await runUsage(mockFetch); - const proWindow = snapshot.windows.find((w) => w.label === "gemini-pro-test"); - expect(proWindow?.usedPercent).toBe(60); - expect(proWindow?.resetAt).toBeUndefined(); - }); - - it("handles loadCodeAssist network errors with graceful degradation", async () => { - const mockFetch = createEndpointFetch({ - loadCodeAssist: () => { - throw new Error("Network failure"); - }, - fetchAvailableModels: () => - makeResponse(200, { - models: { - "gemini-flash-stable": { - quotaInfo: { remainingFraction: 0.85 }, - }, - }, - }), - }); - - const snapshot = await runUsage(mockFetch); - const flashWindow = expectSingleWindow(snapshot, "gemini-flash-stable"); - expect(flashWindow?.usedPercent).toBeCloseTo(15, 1); - expect(snapshot.error).toBeUndefined(); - }); - - it("handles fetchAvailableModels network errors with graceful degradation", async () => { - const mockFetch = createEndpointFetch({ - loadCodeAssist: () => - makeResponse(200, { - availablePromptCredits: 300, - planInfo: { monthlyPromptCredits: 1000 }, - }), - fetchAvailableModels: () => { - throw new Error("Network failure"); - }, - }); - - const snapshot = await runUsage(mockFetch); - expect(snapshot.windows).toEqual([{ label: "Credits", usedPercent: 70 }]); - 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 ce21f77b798..00000000000 --- a/src/infra/provider-usage.fetch.antigravity.ts +++ /dev/null @@ -1,305 +0,0 @@ -import { logDebug } from "../logger.js"; -import { fetchJson, parseFiniteNumber } 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 { - return parseFiniteNumber(value); -} - -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 07039655463..e0bcd60c94b 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 d4975dc0a06..b62cfec728f 100644 --- a/src/infra/provider-usage.load.ts +++ b/src/infra/provider-usage.load.ts @@ -1,7 +1,6 @@ import { resolveFetch } from "./fetch.js"; 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.test.ts b/src/infra/provider-usage.shared.test.ts index 270e4cc65c4..3de021235be 100644 --- a/src/infra/provider-usage.shared.test.ts +++ b/src/infra/provider-usage.shared.test.ts @@ -4,7 +4,7 @@ import { clampPercent, resolveUsageProviderId, withTimeout } from "./provider-us describe("provider-usage.shared", () => { it("normalizes supported usage provider ids", () => { expect(resolveUsageProviderId("z-ai")).toBe("zai"); - expect(resolveUsageProviderId(" GOOGLE-ANTIGRAVITY ")).toBe("google-antigravity"); + expect(resolveUsageProviderId(" GOOGLE-GEMINI-CLI ")).toBe("google-gemini-cli"); expect(resolveUsageProviderId("unknown-provider")).toBeUndefined(); expect(resolveUsageProviderId()).toBeUndefined(); }); diff --git a/src/infra/provider-usage.shared.ts b/src/infra/provider-usage.shared.ts index 763eca4e8ae..6fa823db630 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", xiaomi: "Xiaomi", @@ -18,7 +17,6 @@ export const usageProviders: UsageProviderId[] = [ "anthropic", "github-copilot", "google-gemini-cli", - "google-antigravity", "minimax", "openai-codex", "xiaomi", diff --git a/src/infra/provider-usage.test.ts b/src/infra/provider-usage.test.ts index 17ce3754c32..86c8213a8c2 100644 --- a/src/infra/provider-usage.test.ts +++ b/src/infra/provider-usage.test.ts @@ -338,7 +338,7 @@ describe("provider usage loading", () => { }); }); - it("loads snapshots for copilot antigravity gemini codex and xiaomi", async () => { + it("loads snapshots for copilot gemini codex and xiaomi", async () => { const mockFetch = createProviderUsageFetch(async (url) => { if (url.includes("api.github.com/copilot_internal/user")) { return makeResponse(200, { @@ -346,14 +346,6 @@ describe("provider usage loading", () => { copilot_plan: "Copilot Pro", }); } - if (url.includes("cloudcode-pa.googleapis.com/v1internal:loadCodeAssist")) { - return makeResponse(200, { - availablePromptCredits: 80, - planInfo: { monthlyPromptCredits: 100 }, - currentTier: { name: "Antigravity Pro" }, - cloudaicompanionProject: "projects/demo", - }); - } if (url.includes("cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels")) { return makeResponse(200, { models: { @@ -380,7 +372,6 @@ describe("provider usage loading", () => { const summary = await loadUsageWithAuth( [ { provider: "github-copilot", token: "copilot-token" }, - { provider: "google-antigravity", token: "antigravity-token" }, { provider: "google-gemini-cli", token: "gemini-token" }, { provider: "openai-codex", token: "codex-token", accountId: "acc-1" }, { provider: "xiaomi", token: "xiaomi-token" }, @@ -390,7 +381,6 @@ describe("provider usage loading", () => { expect(summary.providers.map((provider) => provider.provider)).toEqual([ "github-copilot", - "google-antigravity", "google-gemini-cli", "openai-codex", "xiaomi", @@ -398,10 +388,6 @@ describe("provider usage loading", () => { expect( summary.providers.find((provider) => provider.provider === "github-copilot")?.windows, ).toEqual([{ label: "Chat", usedPercent: 20 }]); - expect( - summary.providers.find((provider) => provider.provider === "google-antigravity")?.windows - .length, - ).toBeGreaterThan(0); expect( summary.providers.find((provider) => provider.provider === "google-gemini-cli")?.windows[0] ?.label, diff --git a/src/infra/provider-usage.types.ts b/src/infra/provider-usage.types.ts index 0a4637a7d47..af5e2e93c8b 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" | "xiaomi" diff --git a/src/plugins/enable.test.ts b/src/plugins/enable.test.ts index 5bdac4d1851..0934992b830 100644 --- a/src/plugins/enable.test.ts +++ b/src/plugins/enable.test.ts @@ -5,9 +5,9 @@ import { enablePluginInConfig } from "./enable.js"; describe("enablePluginInConfig", () => { it("enables a plugin entry", () => { const cfg: OpenClawConfig = {}; - const result = enablePluginInConfig(cfg, "google-antigravity-auth"); + const result = enablePluginInConfig(cfg, "google-gemini-cli-auth"); expect(result.enabled).toBe(true); - expect(result.config.plugins?.entries?.["google-antigravity-auth"]?.enabled).toBe(true); + expect(result.config.plugins?.entries?.["google-gemini-cli-auth"]?.enabled).toBe(true); }); it("adds plugin to allowlist when allowlist is configured", () => { @@ -16,18 +16,18 @@ describe("enablePluginInConfig", () => { allow: ["memory-core"], }, }; - const result = enablePluginInConfig(cfg, "google-antigravity-auth"); + const result = enablePluginInConfig(cfg, "google-gemini-cli-auth"); expect(result.enabled).toBe(true); - expect(result.config.plugins?.allow).toEqual(["memory-core", "google-antigravity-auth"]); + expect(result.config.plugins?.allow).toEqual(["memory-core", "google-gemini-cli-auth"]); }); it("refuses enable when plugin is denylisted", () => { const cfg: OpenClawConfig = { plugins: { - deny: ["google-antigravity-auth"], + deny: ["google-gemini-cli-auth"], }, }; - const result = enablePluginInConfig(cfg, "google-antigravity-auth"); + const result = enablePluginInConfig(cfg, "google-gemini-cli-auth"); expect(result.enabled).toBe(false); expect(result.reason).toBe("blocked by denylist"); }); diff --git a/src/utils/provider-utils.ts b/src/utils/provider-utils.ts index a5544277201..211c515dc16 100644 --- a/src/utils/provider-utils.ts +++ b/src/utils/provider-utils.ts @@ -22,11 +22,6 @@ export function isReasoningTagProvider(provider: string | undefined | null): boo return true; } - // Handle google-antigravity and its model variations (e.g. google-antigravity/gemini-3) - if (normalized.includes("google-antigravity")) { - return true; - } - // Handle Minimax (M2.1 is chatty/reasoning-like) if (normalized.includes("minimax")) { return true; diff --git a/src/utils/utils-misc.test.ts b/src/utils/utils-misc.test.ts index 602db3f6f57..b7128ad2141 100644 --- a/src/utils/utils-misc.test.ts +++ b/src/utils/utils-misc.test.ts @@ -64,12 +64,6 @@ describe("isReasoningTagProvider", () => { value: "google-generative-ai", expected: true, }, - { name: "returns true for google-antigravity", value: "google-antigravity", expected: true }, - { - name: "returns true for google-antigravity model suffixes", - value: "google-antigravity/gemini-3", - expected: true, - }, { name: "returns true for minimax", value: "minimax", expected: true }, { name: "returns true for minimax-cn", value: "minimax-cn", expected: true }, { name: "returns false for null", value: null, expected: false },