From bae6553e6779d955fc973488fa7d8a64c05d7eda Mon Sep 17 00:00:00 2001 From: Mikael Hugo Date: Sun, 19 Apr 2026 10:45:44 +0200 Subject: [PATCH] pi-ai: remove google-antigravity provider entirely Continues the antigravity rip-out (previous commit covered SF + pi-coding- agent UI layer). This commit removes the code from pi-ai: - Delete packages/pi-ai/src/utils/oauth/google-antigravity.ts (313 lines) - Update oauth/index.ts: drop antigravityOAuthProvider, refreshAntigravityToken, loginAntigravity exports + registry entry. Add comment explaining why (no vendor core lib + Google ban risk). - google-gemini-cli.ts: strip ANTIGRAVITY_* constants, ANTIGRAVITY_ENDPOINT_FALLBACKS, getAntigravityHeaders(), ANTIGRAVITY_SYSTEM_INSTRUCTION, and all isAntigravity branching from streamGoogleGeminiCli + buildRequest. File header rewritten. needsClaudeThinkingBetaHeader() collapses to always-false (antigravity was the only path that needed it). - google-shared.ts: strip stale Antigravity comments (file still shared between google, google-gemini-cli, google-vertex). - types.ts: drop "google-antigravity" from Api / KnownProvider union. - models.generated.ts: remove google-antigravity provider block (~170 lines, 4 claude-* models that were only served via Antigravity). - models.generated.test.ts: drop from expected-providers snapshot. - scripts/generate-models.ts: remove antigravity model emission + context- window override so future regenerations don't re-add it. Reasoning (same as previous commit): Antigravity has no vendor-published core library we can embed. Hand-rolled OAuth against daily-cloudcode-pa.sandbox.googleapis.com was exactly the pattern Google is banning for third-party tools. Removing it eliminates the risk surface. Breaking change: users with google-antigravity configured in their models.* block will need to migrate to google-gemini-cli (OAuth via the real `gemini` CLI), google (API key), or google-vertex (GCP auth). Build passes. Next commit wires the google-gemini-cli provider to @google/gemini-cli-core per the plan. Co-Authored-By: Claude Sonnet 4.6 --- packages/pi-ai/scripts/generate-models.ts | 122 ------- packages/pi-ai/src/models.generated.test.ts | 1 - packages/pi-ai/src/models.generated.ts | 172 ---------- .../pi-ai/src/providers/google-gemini-cli.ts | 65 +--- packages/pi-ai/src/providers/google-shared.ts | 8 +- packages/pi-ai/src/types.ts | 1 - .../src/utils/oauth/google-antigravity.ts | 313 ------------------ .../src/utils/oauth/google-oauth-utils.ts | 2 +- packages/pi-ai/src/utils/oauth/index.ts | 11 +- 9 files changed, 27 insertions(+), 668 deletions(-) delete mode 100644 packages/pi-ai/src/utils/oauth/google-antigravity.ts diff --git a/packages/pi-ai/scripts/generate-models.ts b/packages/pi-ai/scripts/generate-models.ts index 839428bcb..9fd2d2576 100644 --- a/packages/pi-ai/scripts/generate-models.ts +++ b/packages/pi-ai/scripts/generate-models.ts @@ -681,12 +681,6 @@ async function generateModels() { ) { candidate.contextWindow = 1000000; } - if ( - candidate.provider === "google-antigravity" && - (candidate.id === "claude-opus-4-6-thinking" || candidate.id === "claude-sonnet-4-6") - ) { - candidate.contextWindow = 1000000; - } // OpenCode variants list Claude Sonnet 4/4.5 with 1M context, actual limit is 200K if ( (candidate.provider === "opencode" || candidate.provider === "opencode-go") && @@ -1145,122 +1139,6 @@ async function generateModels() { ]; allModels.push(...cloudCodeAssistModels); - // Antigravity models (Gemini 3, Claude, GPT-OSS via Google Cloud) - // Uses sandbox endpoint and different OAuth credentials for access to additional models - const ANTIGRAVITY_ENDPOINT = "https://daily-cloudcode-pa.sandbox.googleapis.com"; - const antigravityModels: Model<"google-gemini-cli">[] = [ - { - id: "gemini-3.1-pro-high", - name: "Gemini 3.1 Pro High (Antigravity)", - api: "google-gemini-cli", - provider: "google-antigravity", - baseUrl: ANTIGRAVITY_ENDPOINT, - reasoning: true, - input: ["text", "image"], - // the Model type doesn't seem to support having extended-context costs, so I'm just using the pricing for <200k input - cost: { input: 2, output: 12, cacheRead: 0.2, cacheWrite: 2.375 }, - contextWindow: 1048576, - maxTokens: 65535, - }, - { - id: "gemini-3.1-pro-low", - name: "Gemini 3.1 Pro Low (Antigravity)", - api: "google-gemini-cli", - provider: "google-antigravity", - baseUrl: ANTIGRAVITY_ENDPOINT, - reasoning: true, - input: ["text", "image"], - // the Model type doesn't seem to support having extended-context costs, so I'm just using the pricing for <200k input - cost: { input: 2, output: 12, cacheRead: 0.2, cacheWrite: 2.375 }, - contextWindow: 1048576, - maxTokens: 65535, - }, - { - id: "gemini-3-flash", - name: "Gemini 3 Flash (Antigravity)", - api: "google-gemini-cli", - provider: "google-antigravity", - baseUrl: ANTIGRAVITY_ENDPOINT, - reasoning: true, - input: ["text", "image"], - cost: { input: 0.5, output: 3, cacheRead: 0.5, cacheWrite: 0 }, - contextWindow: 1048576, - maxTokens: 65535, - }, - { - id: "claude-sonnet-4-5", - name: "Claude Sonnet 4.5 (Antigravity)", - api: "google-gemini-cli", - provider: "google-antigravity", - baseUrl: ANTIGRAVITY_ENDPOINT, - reasoning: false, - input: ["text", "image"], - cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 }, - contextWindow: 200000, - maxTokens: 64000, - }, - { - id: "claude-sonnet-4-5-thinking", - name: "Claude Sonnet 4.5 Thinking (Antigravity)", - api: "google-gemini-cli", - provider: "google-antigravity", - baseUrl: ANTIGRAVITY_ENDPOINT, - reasoning: true, - input: ["text", "image"], - cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 }, - contextWindow: 200000, - maxTokens: 64000, - }, - { - id: "claude-opus-4-5-thinking", - name: "Claude Opus 4.5 Thinking (Antigravity)", - api: "google-gemini-cli", - provider: "google-antigravity", - baseUrl: ANTIGRAVITY_ENDPOINT, - reasoning: true, - input: ["text", "image"], - cost: { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 }, - contextWindow: 200000, - maxTokens: 64000, - }, - { - id: "claude-opus-4-6-thinking", - name: "Claude Opus 4.6 Thinking (Antigravity)", - api: "google-gemini-cli", - provider: "google-antigravity", - baseUrl: ANTIGRAVITY_ENDPOINT, - reasoning: true, - input: ["text", "image"], - cost: { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 }, - contextWindow: 200000, - maxTokens: 128000, - }, - { - id: "claude-sonnet-4-6", - name: "Claude Sonnet 4.6 (Antigravity)", - api: "google-gemini-cli", - provider: "google-antigravity", - baseUrl: ANTIGRAVITY_ENDPOINT, - reasoning: true, - input: ["text", "image"], - cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 }, - contextWindow: 200000, - maxTokens: 64000, - }, - { - id: "gpt-oss-120b-medium", - name: "GPT-OSS 120B Medium (Antigravity)", - api: "google-gemini-cli", - provider: "google-antigravity", - baseUrl: ANTIGRAVITY_ENDPOINT, - reasoning: false, - input: ["text"], - cost: { input: 0.09, output: 0.36, cacheRead: 0, cacheWrite: 0 }, - contextWindow: 131072, - maxTokens: 32768, - }, - ]; - allModels.push(...antigravityModels); const VERTEX_BASE_URL = "https://{location}-aiplatform.googleapis.com"; const vertexModels: Model<"google-vertex">[] = [ diff --git a/packages/pi-ai/src/models.generated.test.ts b/packages/pi-ai/src/models.generated.test.ts index bfba0704d..4e98426a6 100644 --- a/packages/pi-ai/src/models.generated.test.ts +++ b/packages/pi-ai/src/models.generated.test.ts @@ -278,7 +278,6 @@ describe("MODELS registry shape", () => { "cerebras", "github-copilot", "google", - "google-antigravity", "google-gemini-cli", "google-vertex", "groq", diff --git a/packages/pi-ai/src/models.generated.ts b/packages/pi-ai/src/models.generated.ts index 6a1bb8a76..ffaab0880 100644 --- a/packages/pi-ai/src/models.generated.ts +++ b/packages/pi-ai/src/models.generated.ts @@ -3600,178 +3600,6 @@ export const MODELS = { maxTokens: 8192, } satisfies Model<"google-generative-ai">, }, - "google-antigravity": { - "claude-opus-4-5-thinking": { - id: "claude-opus-4-5-thinking", - name: "Claude Opus 4.5 Thinking (Antigravity)", - api: "google-gemini-cli", - provider: "google-antigravity", - baseUrl: "https://daily-cloudcode-pa.sandbox.googleapis.com", - reasoning: true, - input: ["text", "image"], - cost: { - input: 5, - output: 25, - cacheRead: 0.5, - cacheWrite: 6.25, - }, - contextWindow: 200000, - maxTokens: 64000, - } satisfies Model<"google-gemini-cli">, - "claude-opus-4-7-thinking": { - id: "claude-opus-4-7-thinking", - name: "Claude Opus 4.7 Thinking (Antigravity)", - api: "google-gemini-cli", - provider: "google-antigravity", - baseUrl: "https://daily-cloudcode-pa.sandbox.googleapis.com", - reasoning: true, - input: ["text", "image"], - cost: { - input: 5, - output: 25, - cacheRead: 0.5, - cacheWrite: 6.25, - }, - contextWindow: 1000000, - maxTokens: 128000, - } satisfies Model<"google-gemini-cli">, - "claude-opus-4-6-thinking": { - id: "claude-opus-4-6-thinking", - name: "Claude Opus 4.6 Thinking (Antigravity)", - api: "google-gemini-cli", - provider: "google-antigravity", - baseUrl: "https://daily-cloudcode-pa.sandbox.googleapis.com", - reasoning: true, - input: ["text", "image"], - cost: { - input: 5, - output: 25, - cacheRead: 0.5, - cacheWrite: 6.25, - }, - contextWindow: 200000, - maxTokens: 128000, - } satisfies Model<"google-gemini-cli">, - "claude-sonnet-4-5": { - id: "claude-sonnet-4-5", - name: "Claude Sonnet 4.5 (Antigravity)", - api: "google-gemini-cli", - provider: "google-antigravity", - baseUrl: "https://daily-cloudcode-pa.sandbox.googleapis.com", - reasoning: false, - input: ["text", "image"], - cost: { - input: 3, - output: 15, - cacheRead: 0.3, - cacheWrite: 3.75, - }, - contextWindow: 200000, - maxTokens: 64000, - } satisfies Model<"google-gemini-cli">, - "claude-sonnet-4-5-thinking": { - id: "claude-sonnet-4-5-thinking", - name: "Claude Sonnet 4.5 Thinking (Antigravity)", - api: "google-gemini-cli", - provider: "google-antigravity", - baseUrl: "https://daily-cloudcode-pa.sandbox.googleapis.com", - reasoning: true, - input: ["text", "image"], - cost: { - input: 3, - output: 15, - cacheRead: 0.3, - cacheWrite: 3.75, - }, - contextWindow: 200000, - maxTokens: 64000, - } satisfies Model<"google-gemini-cli">, - "claude-sonnet-4-6": { - id: "claude-sonnet-4-6", - name: "Claude Sonnet 4.6 (Antigravity)", - api: "google-gemini-cli", - provider: "google-antigravity", - baseUrl: "https://daily-cloudcode-pa.sandbox.googleapis.com", - reasoning: true, - input: ["text", "image"], - cost: { - input: 3, - output: 15, - cacheRead: 0.3, - cacheWrite: 3.75, - }, - contextWindow: 200000, - maxTokens: 64000, - } satisfies Model<"google-gemini-cli">, - "gemini-3-flash": { - id: "gemini-3-flash", - name: "Gemini 3 Flash (Antigravity)", - api: "google-gemini-cli", - provider: "google-antigravity", - baseUrl: "https://daily-cloudcode-pa.sandbox.googleapis.com", - reasoning: true, - input: ["text", "image"], - cost: { - input: 0.5, - output: 3, - cacheRead: 0.5, - cacheWrite: 0, - }, - contextWindow: 1048576, - maxTokens: 65535, - } satisfies Model<"google-gemini-cli">, - "gemini-3.1-pro-high": { - id: "gemini-3.1-pro-high", - name: "Gemini 3.1 Pro High (Antigravity)", - api: "google-gemini-cli", - provider: "google-antigravity", - baseUrl: "https://daily-cloudcode-pa.sandbox.googleapis.com", - reasoning: true, - input: ["text", "image"], - cost: { - input: 2, - output: 12, - cacheRead: 0.2, - cacheWrite: 2.375, - }, - contextWindow: 1048576, - maxTokens: 65535, - } satisfies Model<"google-gemini-cli">, - "gemini-3.1-pro-low": { - id: "gemini-3.1-pro-low", - name: "Gemini 3.1 Pro Low (Antigravity)", - api: "google-gemini-cli", - provider: "google-antigravity", - baseUrl: "https://daily-cloudcode-pa.sandbox.googleapis.com", - reasoning: true, - input: ["text", "image"], - cost: { - input: 2, - output: 12, - cacheRead: 0.2, - cacheWrite: 2.375, - }, - contextWindow: 1048576, - maxTokens: 65535, - } satisfies Model<"google-gemini-cli">, - "gpt-oss-120b-medium": { - id: "gpt-oss-120b-medium", - name: "GPT-OSS 120B Medium (Antigravity)", - api: "google-gemini-cli", - provider: "google-antigravity", - baseUrl: "https://daily-cloudcode-pa.sandbox.googleapis.com", - reasoning: false, - input: ["text"], - cost: { - input: 0.09, - output: 0.36, - cacheRead: 0, - cacheWrite: 0, - }, - contextWindow: 131072, - maxTokens: 32768, - } satisfies Model<"google-gemini-cli">, - }, "google-gemini-cli": { "gemini-2.0-flash": { id: "gemini-2.0-flash", diff --git a/packages/pi-ai/src/providers/google-gemini-cli.ts b/packages/pi-ai/src/providers/google-gemini-cli.ts index 318c46fe0..f98a73dc6 100644 --- a/packages/pi-ai/src/providers/google-gemini-cli.ts +++ b/packages/pi-ai/src/providers/google-gemini-cli.ts @@ -1,7 +1,10 @@ /** - * Google Gemini CLI / Antigravity provider. - * Shared implementation for both google-gemini-cli and google-antigravity providers. - * Uses the Cloud Code Assist API endpoint to access Gemini and Claude models. + * Google Gemini CLI provider. + * Uses the Cloud Code Assist API endpoint (cloudcode-pa.googleapis.com) to + * access Gemini models with the user's OAuth credentials from the real + * `gemini` CLI. Follow-up work will re-platform this on top of + * @google/gemini-cli-core so our requests are indistinguishable from the + * official CLI's. See google-gemini-cli-core-plan.md. */ import type { Content, ThinkingConfig } from "@google/genai"; @@ -58,13 +61,6 @@ export interface GoogleGeminiCliOptions extends StreamOptions { } const DEFAULT_ENDPOINT = "https://cloudcode-pa.googleapis.com"; -const ANTIGRAVITY_DAILY_ENDPOINT = "https://daily-cloudcode-pa.sandbox.googleapis.com"; -const ANTIGRAVITY_AUTOPUSH_ENDPOINT = "https://autopush-cloudcode-pa.sandbox.googleapis.com"; -const ANTIGRAVITY_ENDPOINT_FALLBACKS = [ - ANTIGRAVITY_DAILY_ENDPOINT, - ANTIGRAVITY_AUTOPUSH_ENDPOINT, - DEFAULT_ENDPOINT, -] as const; // Headers for Gemini CLI (prod endpoint) const GEMINI_CLI_HEADERS = { "User-Agent": "google-cloud-sdk vscode_cloudshelleditor/0.1", @@ -76,23 +72,6 @@ const GEMINI_CLI_HEADERS = { }), }; -// Headers for Antigravity (sandbox endpoint) - requires specific User-Agent -const DEFAULT_ANTIGRAVITY_VERSION = "1.18.4"; - -function getAntigravityHeaders() { - const version = process.env.PI_AI_ANTIGRAVITY_VERSION || DEFAULT_ANTIGRAVITY_VERSION; - return { - "User-Agent": `antigravity/${version} darwin/arm64`, - }; -} - -// Antigravity system instruction (compact version from CLIProxyAPI). -const ANTIGRAVITY_SYSTEM_INSTRUCTION = - "You are Antigravity, a powerful agentic AI coding assistant designed by the Google Deepmind team working on Advanced Agentic Coding." + - "You are pair programming with a USER to solve their coding task. The task may require creating a new codebase, modifying or debugging an existing codebase, or simply answering a question." + - "**Absolute paths only**" + - "**Proactiveness**"; - // Counter for generating unique tool call IDs let toolCallCounter = 0; @@ -203,8 +182,11 @@ function extractRetryDelay(errorText: string, response?: Response | Headers): nu return undefined; } -function needsClaudeThinkingBetaHeader(model: Model<"google-gemini-cli">): boolean { - return model.provider === "google-antigravity" && model.id.startsWith("claude-") && model.reasoning; +function needsClaudeThinkingBetaHeader(_model: Model<"google-gemini-cli">): boolean { + // Antigravity-only path. Gemini CLI doesn't serve Claude models so this is + // always false now. Kept as a no-op to minimise downstream edits; can be + // inlined away once the streaming function is rewritten on cli-core. + return false; } function isGemini3ProModel(modelId: string): boolean { @@ -365,16 +347,15 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli", GoogleGe throw new Error("Missing token or projectId in Google Cloud credentials. Use /login to re-authenticate."); } - const isAntigravity = model.provider === "google-antigravity"; const baseUrl = model.baseUrl?.trim(); - const endpoints = baseUrl ? [baseUrl] : isAntigravity ? ANTIGRAVITY_ENDPOINT_FALLBACKS : [DEFAULT_ENDPOINT]; + const endpoints = baseUrl ? [baseUrl] : [DEFAULT_ENDPOINT]; - let requestBody = buildRequest(model, context, projectId, options, isAntigravity); + let requestBody = buildRequest(model, context, projectId, options); const nextRequestBody = await options?.onPayload?.(requestBody, model); if (nextRequestBody !== undefined) { requestBody = nextRequestBody as CloudCodeAssistRequest; } - const headers = isAntigravity ? getAntigravityHeaders() : GEMINI_CLI_HEADERS; + const headers = GEMINI_CLI_HEADERS; const requestHeaders = { Authorization: `Bearer ${accessToken}`, @@ -870,7 +851,6 @@ function buildRequest( context: Context, projectId: string, options: GoogleGeminiCliOptions = {}, - isAntigravity = false, ): CloudCodeAssistRequest { const contents = convertMessages(model, context); @@ -927,25 +907,12 @@ function buildRequest( } } - if (isAntigravity) { - const existingParts = request.systemInstruction?.parts ?? []; - request.systemInstruction = { - role: "user", - parts: [ - { text: ANTIGRAVITY_SYSTEM_INSTRUCTION }, - { text: `Please ignore following [ignore]${ANTIGRAVITY_SYSTEM_INSTRUCTION}[/ignore]` }, - ...existingParts, - ], - }; - } - return { project: projectId, model: model.id, request, - ...(isAntigravity ? { requestType: "agent" } : {}), - userAgent: isAntigravity ? "antigravity" : "pi-coding-agent", - requestId: `${isAntigravity ? "agent" : "pi"}-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`, + userAgent: "pi-coding-agent", + requestId: `pi-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`, }; } diff --git a/packages/pi-ai/src/providers/google-shared.ts b/packages/pi-ai/src/providers/google-shared.ts index 7984bdd4b..ccea85c4e 100644 --- a/packages/pi-ai/src/providers/google-shared.ts +++ b/packages/pi-ai/src/providers/google-shared.ts @@ -116,7 +116,7 @@ export function convertMessages(model: Model, contex for (const block of msg.content) { if (block.type === "text") { - // Skip empty text blocks - they can cause issues with some models (e.g. Claude via Antigravity) + // Skip empty text blocks - they can cause issues with some models if (!block.text || block.text.trim() === "") continue; const thoughtSignature = resolveThoughtSignature(isSameProviderAndModel, block.textSignature); parts.push({ @@ -144,7 +144,7 @@ export function convertMessages(model: Model, contex const thoughtSignature = resolveThoughtSignature(isSameProviderAndModel, block.thoughtSignature); // Gemini 3 requires thoughtSignature on all function calls when thinking mode is enabled. // Use the skip_thought_signature_validator sentinel for unsigned function calls - // (e.g. replayed from providers without thought signatures like Claude via Antigravity). + // (e.g. replayed from providers without thought signatures). const isGemini3 = model.id.toLowerCase().includes("gemini-3"); const effectiveSignature = thoughtSignature || (isGemini3 ? SKIP_THOUGHT_SIGNATURE : undefined); const part: Part = { @@ -234,8 +234,8 @@ export function convertMessages(model: Model, contex * - Removes all `patternProperties` fields * - Converts `const: "value"` to `enum: ["value"]` in anyOf/oneOf blocks * - * This is needed for providers like `google-antigravity` when proxying Claude models, - * since Google Cloud Code Assist uses a restricted subset of JSON Schema. + * Needed because Google Cloud Code Assist (google-gemini-cli provider) uses a + * restricted subset of JSON Schema and rejects patternProperties / const. */ export function sanitizeSchemaForGoogle(schema: unknown): unknown { if (!schema || typeof schema !== "object") { diff --git a/packages/pi-ai/src/types.ts b/packages/pi-ai/src/types.ts index 2c6ca9530..3f0999af2 100644 --- a/packages/pi-ai/src/types.ts +++ b/packages/pi-ai/src/types.ts @@ -24,7 +24,6 @@ export type KnownProvider = | "anthropic-vertex" | "google" | "google-gemini-cli" - | "google-antigravity" | "google-vertex" | "openai" | "azure-openai-responses" diff --git a/packages/pi-ai/src/utils/oauth/google-antigravity.ts b/packages/pi-ai/src/utils/oauth/google-antigravity.ts deleted file mode 100644 index f4f946963..000000000 --- a/packages/pi-ai/src/utils/oauth/google-antigravity.ts +++ /dev/null @@ -1,313 +0,0 @@ -/** - * Antigravity OAuth flow (Gemini 3, Claude, GPT-OSS via Google Cloud) - * Uses different OAuth credentials than google-gemini-cli for access to additional models. - * - * NOTE: This module uses Node.js http.createServer for the OAuth callback. - * It is only intended for CLI use, not browser environments. - */ - -import { - type CallbackServerInfo, - getGoogleUserEmail, - parseRedirectUrl, - refreshGoogleOAuthToken, - startCallbackServer, -} from "./google-oauth-utils.js"; -import { generatePKCE } from "./pkce.js"; -import type { OAuthCredentials, OAuthLoginCallbacks, OAuthProviderInterface } from "./types.js"; - -type AntigravityCredentials = OAuthCredentials & { - projectId: string; -}; - -// Antigravity OAuth credentials (different from Gemini CLI) -const decode = (s: string) => atob(s); -const CLIENT_ID = decode( - "MTA3MTAwNjA2MDU5MS10bWhzc2luMmgyMWxjcmUyMzV2dG9sb2poNGc0MDNlcC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbQ==", -); -const CLIENT_SECRET = decode("R09DU1BYLUs1OEZXUjQ4NkxkTEoxbUxCOHNYQzR6NnFEQWY="); -const REDIRECT_URI = "http://localhost:51121/oauth-callback"; - -// Antigravity requires additional scopes -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 AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth"; -const TOKEN_URL = "https://oauth2.googleapis.com/token"; - -// Callback server configuration -const CALLBACK_PORT = 51121; -const CALLBACK_PATH = "/oauth-callback"; - -// Fallback project ID when discovery fails -const DEFAULT_PROJECT_ID = "rising-fact-p41fc"; - -interface LoadCodeAssistPayload { - cloudaicompanionProject?: string | { id?: string }; - currentTier?: { id?: string }; - allowedTiers?: Array<{ id?: string; isDefault?: boolean }>; -} - -/** - * Discover or provision a project for the user - */ -async function discoverProject(accessToken: string, onProgress?: (message: string) => void): 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", - }), - }; - - // Try endpoints in order: prod first, then sandbox - const endpoints = ["https://cloudcode-pa.googleapis.com", "https://daily-cloudcode-pa.sandbox.googleapis.com"]; - - onProgress?.("Checking for existing project..."); - - for (const endpoint of endpoints) { - try { - const loadResponse = await fetch(`${endpoint}/v1internal:loadCodeAssist`, { - method: "POST", - headers, - body: JSON.stringify({ - metadata: { - ideType: "IDE_UNSPECIFIED", - platform: "PLATFORM_UNSPECIFIED", - pluginType: "GEMINI", - }, - }), - signal: AbortSignal.timeout(30_000), - }); - - if (loadResponse.ok) { - const data = (await loadResponse.json()) as LoadCodeAssistPayload; - - // Handle both string and object formats - if (typeof data.cloudaicompanionProject === "string" && data.cloudaicompanionProject) { - return data.cloudaicompanionProject; - } - if ( - data.cloudaicompanionProject && - typeof data.cloudaicompanionProject === "object" && - data.cloudaicompanionProject.id - ) { - return data.cloudaicompanionProject.id; - } - } - } catch { - // Try next endpoint - } - } - - // Use fallback project ID - onProgress?.("Using default project..."); - return DEFAULT_PROJECT_ID; -} - -/** - * Refresh Antigravity token - */ -export async function refreshAntigravityToken(refreshToken: string, projectId: string): Promise { - return refreshGoogleOAuthToken(refreshToken, CLIENT_ID, CLIENT_SECRET, "Antigravity", { projectId }); -} - -/** - * Login with Antigravity OAuth - * - * @param onAuth - Callback with URL and optional instructions - * @param onProgress - Optional progress callback - * @param onManualCodeInput - Optional promise that resolves with user-pasted redirect URL. - * Races with browser callback - whichever completes first wins. - */ -export async function loginAntigravity( - onAuth: (info: { url: string; instructions?: string }) => void, - onProgress?: (message: string) => void, - onManualCodeInput?: () => Promise, -): Promise { - const { verifier, challenge } = await generatePKCE(); - - // Start local server for callback - onProgress?.("Starting local server for OAuth callback..."); - const server: CallbackServerInfo = await startCallbackServer(CALLBACK_PORT, CALLBACK_PATH, "Antigravity"); - - let code: string | undefined; - - try { - // Build authorization URL - const authParams = new URLSearchParams({ - client_id: CLIENT_ID, - response_type: "code", - redirect_uri: REDIRECT_URI, - scope: SCOPES.join(" "), - code_challenge: challenge, - code_challenge_method: "S256", - state: verifier, - access_type: "offline", - prompt: "consent", - }); - - const authUrl = `${AUTH_URL}?${authParams.toString()}`; - - // Notify caller with URL to open - onAuth({ - url: authUrl, - instructions: "Complete the sign-in in your browser.", - }); - - // Wait for the callback, racing with manual input if provided - onProgress?.("Waiting for OAuth callback..."); - - if (onManualCodeInput) { - // Race between browser callback and manual input - let manualInput: string | undefined; - let manualError: Error | undefined; - const manualPromise = onManualCodeInput() - .then((input) => { - manualInput = input; - server.cancelWait(); - }) - .catch((err) => { - manualError = err instanceof Error ? err : new Error(String(err)); - server.cancelWait(); - }); - - const result = await server.waitForCode(); - - // If manual input was cancelled, throw that error - if (manualError) { - throw manualError; - } - - if (result?.code) { - // Browser callback won - verify state - if (result.state !== verifier) { - throw new Error("OAuth state mismatch - possible CSRF attack"); - } - code = result.code; - } else if (manualInput) { - // Manual input won - const parsed = parseRedirectUrl(manualInput); - if (parsed.state && parsed.state !== verifier) { - throw new Error("OAuth state mismatch - possible CSRF attack"); - } - code = parsed.code; - } - - // If still no code, wait for manual promise and try that - if (!code) { - await manualPromise; - if (manualError) { - throw manualError; - } - if (manualInput) { - const parsed = parseRedirectUrl(manualInput); - if (parsed.state && parsed.state !== verifier) { - throw new Error("OAuth state mismatch - possible CSRF attack"); - } - code = parsed.code; - } - } - } else { - // Original flow: just wait for callback - const result = await server.waitForCode(); - if (result?.code) { - if (result.state !== verifier) { - throw new Error("OAuth state mismatch - possible CSRF attack"); - } - code = result.code; - } - } - - if (!code) { - throw new Error("No authorization code received"); - } - - // Exchange code for tokens - onProgress?.("Exchanging authorization code for tokens..."); - const tokenResponse = 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, - grant_type: "authorization_code", - redirect_uri: REDIRECT_URI, - code_verifier: verifier, - }), - signal: AbortSignal.timeout(30_000), - }); - - if (!tokenResponse.ok) { - const error = await tokenResponse.text(); - throw new Error(`Token exchange failed: ${error}`); - } - - const tokenData = (await tokenResponse.json()) as { - access_token: string; - refresh_token: string; - expires_in: number; - }; - - if (!tokenData.refresh_token) { - throw new Error("No refresh token received. Please try again."); - } - - // Get user email - onProgress?.("Getting user info..."); - const email = await getGoogleUserEmail(tokenData.access_token); - - // Discover project - const projectId = await discoverProject(tokenData.access_token, onProgress); - - // Calculate expiry time (current time + expires_in seconds - 5 min buffer) - const expiresAt = Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000; - - const credentials: OAuthCredentials = { - refresh: tokenData.refresh_token, - access: tokenData.access_token, - expires: expiresAt, - projectId, - email, - }; - - return credentials; - } finally { - server.server.close(); - } -} - -export const antigravityOAuthProvider: OAuthProviderInterface = { - id: "google-antigravity", - name: "Antigravity (Gemini 3, Claude, GPT-OSS)", - usesCallbackServer: true, - - async login(callbacks: OAuthLoginCallbacks): Promise { - return loginAntigravity(callbacks.onAuth, callbacks.onProgress, callbacks.onManualCodeInput); - }, - - async refreshToken(credentials: OAuthCredentials): Promise { - const creds = credentials as AntigravityCredentials; - if (!creds.projectId) { - throw new Error("Antigravity credentials missing projectId"); - } - return refreshAntigravityToken(creds.refresh, creds.projectId); - }, - - getApiKey(credentials: OAuthCredentials): string { - const creds = credentials as AntigravityCredentials; - return JSON.stringify({ token: creds.access, projectId: creds.projectId }); - }, -}; diff --git a/packages/pi-ai/src/utils/oauth/google-oauth-utils.ts b/packages/pi-ai/src/utils/oauth/google-oauth-utils.ts index 0188145d7..1bf1dcc8a 100644 --- a/packages/pi-ai/src/utils/oauth/google-oauth-utils.ts +++ b/packages/pi-ai/src/utils/oauth/google-oauth-utils.ts @@ -1,5 +1,5 @@ /** - * Shared utilities for Google OAuth providers (Gemini CLI and Antigravity). + * Shared utilities for Google OAuth providers (Gemini CLI). * * NOTE: This module uses Node.js http.createServer for the OAuth callback. * It is only intended for CLI use, not browser environments. diff --git a/packages/pi-ai/src/utils/oauth/index.ts b/packages/pi-ai/src/utils/oauth/index.ts index 715b4910c..a30198153 100644 --- a/packages/pi-ai/src/utils/oauth/index.ts +++ b/packages/pi-ai/src/utils/oauth/index.ts @@ -5,10 +5,15 @@ * for OAuth-based providers: * - GitHub Copilot * - Google Cloud Code Assist (Gemini CLI) - * - Antigravity (Gemini 3, Claude, GPT-OSS via Google Cloud) * * Note: Anthropic OAuth was removed per TOS compliance (see docs/user-docs/claude-code-auth-compliance.md). * Use API keys or the local Claude Code CLI for Anthropic access. + * + * Note: Antigravity OAuth was removed because Google does not publish a + * vendor core library for it and hand-rolled OAuth against its endpoints + * is at risk of ban per Google's third-party-tool policy. Users wanting + * Gemini access should authenticate via the real `gemini` CLI + * (google-gemini-cli provider) or use the google API key. */ // GitHub Copilot @@ -19,8 +24,6 @@ export { normalizeDomain, refreshGitHubCopilotToken, } from "./github-copilot.js"; -// Google Antigravity -export { antigravityOAuthProvider, loginAntigravity, refreshAntigravityToken } from "./google-antigravity.js"; // Google Gemini CLI export { geminiCliOAuthProvider, loginGeminiCli, refreshGoogleCloudToken } from "./google-gemini-cli.js"; // OpenAI Codex (ChatGPT OAuth) @@ -33,7 +36,6 @@ export * from "./types.js"; // ============================================================================ import { githubCopilotOAuthProvider } from "./github-copilot.js"; -import { antigravityOAuthProvider } from "./google-antigravity.js"; import { geminiCliOAuthProvider } from "./google-gemini-cli.js"; import { openaiCodexOAuthProvider } from "./openai-codex.js"; import type { OAuthCredentials, OAuthProviderId, OAuthProviderInterface } from "./types.js"; @@ -41,7 +43,6 @@ import type { OAuthCredentials, OAuthProviderId, OAuthProviderInterface } from " const BUILT_IN_OAUTH_PROVIDERS: OAuthProviderInterface[] = [ githubCopilotOAuthProvider, geminiCliOAuthProvider, - antigravityOAuthProvider, openaiCodexOAuthProvider, ];