rip out antigravity from SF + pi-coding-agent UI/config layer

Antigravity (Google's IDE sandbox product, different from Gemini CLI) is
removed from:

  src/onboarding.ts                         — drop from LLM_PROVIDER_IDS + OAuth-flow picker
  src/pi-migration.ts                       — drop from LLM_PROVIDER_IDS migration list
  src/web/onboarding-service.ts             — drop from web-UI provider list
  src/tests/integration/web-onboarding-contract.test.ts — update contract
  src/resources/extensions/sf/doctor-providers.ts — drop from CLI_AUTH_PROVIDERS
  src/resources/extensions/sf/key-manager.ts      — drop UI listing
  src/resources/extensions/sf-usage-bar/index.ts  — delete entire quota fetcher block (~200 lines)
  packages/pi-coding-agent/src/cli/args.ts        — drop PI_AI_ANTIGRAVITY_VERSION doc
  packages/pi-coding-agent/src/utils/proxy-server.ts — drop from claude provider chain

Reason: antigravity has no vendor-published core library we can embed
(unlike @google/gemini-cli-core for the Gemini CLI). Continuing to
hand-roll OAuth against daily-cloudcode-pa.sandbox.googleapis.com is
exactly the pattern Google has started banning for third-party tools.
Removing the code removes the ban risk.

pi-ai provider code, OAuth util, and models.generated entries for
google-antigravity are removed in follow-up commits (separated for
reviewability — each layer verified independently).

Build passes. Note: this is a breaking change for any user who had
google-antigravity configured — they'll need to migrate to
google-gemini-cli (OAuth), google (API key), or google-vertex.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Mikael Hugo 2026-04-19 10:39:36 +02:00
parent 233432d486
commit 59806f8cc5
9 changed files with 4 additions and 209 deletions

View file

@ -338,7 +338,6 @@ ${chalk.bold("Environment Variables:")}
PI_PACKAGE_DIR - Override package directory (for Nix/Guix store paths)
PI_OFFLINE - Disable startup network operations when set to 1/true/yes
PI_SHARE_VIEWER_URL - Base URL for /share command (default: https://pi.dev/session/)
PI_AI_ANTIGRAVITY_VERSION - Override Antigravity User-Agent version (e.g., 1.23.0)
${chalk.bold("Available Tools (default: read, bash, edit, write):")}
read - Read file contents

View file

@ -33,8 +33,8 @@ const PROXY_FAMILY_PRIORITY: Array<{ match: RegExp; providers: string[] }> = [
{ match: /^kimi-/i, providers: ["kimi-coding", "opencode", "opencode-go"] },
// Gemini/Gemma: google direct > vertex (enterprise) > CLI (OAuth) > copilot
{ match: /^gemini-|^gemma-/i, providers: ["google", "google-vertex", "google-gemini-cli", "github-copilot"] },
// Claude: anthropic direct > opencode > google-antigravity > copilot
{ match: /^claude-/i, providers: ["anthropic", "opencode", "google-antigravity", "github-copilot"] },
// Claude: anthropic direct > opencode > copilot
{ match: /^claude-/i, providers: ["anthropic", "opencode", "github-copilot"] },
// GPT/OpenAI: openai direct > azure > copilot
{ match: /^gpt-|^o[0-9]|^codex-/i, providers: ["openai", "azure-openai-responses", "github-copilot"] },
];

View file

@ -70,7 +70,6 @@ const LLM_PROVIDER_IDS = [
'github-copilot',
'openai-codex',
'google-gemini-cli',
'google-antigravity',
'google',
'groq',
'xai',
@ -360,7 +359,6 @@ async function runLlmStep(p: ClackModule, pc: PicoModule, authStorage: AuthStora
{ value: 'github-copilot', label: 'GitHub Copilot' },
{ value: 'openai-codex', label: 'ChatGPT Plus/Pro (Codex)' },
{ value: 'google-gemini-cli', label: 'Google Gemini CLI' },
{ value: 'google-antigravity', label: 'Antigravity (Gemini 3, Claude, GPT-OSS)' },
],
})
if (p.isCancel(provider)) return false

View file

@ -18,7 +18,6 @@ const LLM_PROVIDER_IDS = [
'github-copilot',
'openai-codex',
'google-gemini-cli',
'google-antigravity',
'google',
'groq',
'xai',

View file

@ -402,202 +402,6 @@ async function fetchGeminiUsage(_modelRegistry: any): Promise<UsageSnapshot> {
}
}
// ============================================================================
// Antigravity Usage
// ============================================================================
type AntigravityAuth = {
accessToken: string;
refreshToken?: string;
expiresAt?: number;
projectId?: string;
};
function loadAntigravityAuthFromAuthJson(): AntigravityAuth | undefined {
const data = loadAuthJson();
if (!data) return undefined;
// Provider is called "google-antigravity" in sf/pi.
const cred = data["google-antigravity"] ?? data["antigravity"] ?? data["anti-gravity"];
if (!cred) return undefined;
const accessToken = typeof cred.access === "string" ? cred.access : undefined;
if (!accessToken) return undefined;
return {
accessToken,
refreshToken: typeof cred.refresh === "string" ? cred.refresh : undefined,
expiresAt: typeof cred.expires === "number" ? cred.expires : undefined,
projectId: typeof cred.projectId === "string" ? cred.projectId : typeof cred.project_id === "string" ? cred.project_id : undefined,
};
}
async function loadAntigravityAuth(modelRegistry: any): Promise<AntigravityAuth | undefined> {
// Prefer model registry auth storage first (may auto-refresh).
try {
const accessToken = await Promise.resolve(modelRegistry?.authStorage?.getApiKey?.("google-antigravity"));
const raw = await Promise.resolve(modelRegistry?.authStorage?.get?.("google-antigravity"));
const projectId = typeof raw?.projectId === "string" ? raw.projectId : undefined;
const refreshToken = typeof raw?.refresh === "string" ? raw.refresh : undefined;
const expiresAt = typeof raw?.expires === "number" ? raw.expires : undefined;
if (typeof accessToken === "string" && accessToken.length > 0) {
return { accessToken, projectId, refreshToken, expiresAt };
}
} catch {}
// Fallback to auth.json
const fromAuth = loadAntigravityAuthFromAuthJson();
if (fromAuth) return fromAuth;
// Last resort: env var (won't have projectId; request will likely fail)
if (process.env.ANTIGRAVITY_API_KEY) {
return { accessToken: process.env.ANTIGRAVITY_API_KEY };
}
return undefined;
}
async function refreshAntigravityAccessToken(refreshToken: string): Promise<{ accessToken: string; expiresAt?: number } | null> {
try {
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);
// From the reference snippet in CodexBar issue #129.
const clientId = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com";
const clientSecret = "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";
const res = await fetch("https://oauth2.googleapis.com/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
client_id: clientId,
client_secret: clientSecret,
refresh_token: refreshToken,
grant_type: "refresh_token",
}).toString(),
signal: controller.signal,
});
if (!res.ok) return null;
const data = (await res.json()) as any;
const accessToken = typeof data.access_token === "string" ? data.access_token : undefined;
if (!accessToken) return null;
const expiresIn = typeof data.expires_in === "number" ? data.expires_in : undefined;
return {
accessToken,
expiresAt: expiresIn ? Date.now() + expiresIn * 1000 : undefined,
};
} catch {
return null;
}
}
async function fetchAntigravityUsage(modelRegistry: any): Promise<UsageSnapshot> {
const auth = await loadAntigravityAuth(modelRegistry);
if (!auth?.accessToken) {
return { provider: "antigravity", displayName: "Antigravity", windows: [], error: "No credentials" };
}
if (!auth.projectId) {
return { provider: "antigravity", displayName: "Antigravity", windows: [], error: "Missing projectId" };
}
let accessToken = auth.accessToken;
// Refresh if likely expired.
if (auth.refreshToken && auth.expiresAt && auth.expiresAt < Date.now() + 5 * 60 * 1000) {
const refreshed = await refreshAntigravityAccessToken(auth.refreshToken);
if (refreshed?.accessToken) accessToken = refreshed.accessToken;
}
const fetchModels = async (token: string): Promise<Response> => {
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);
return fetch("https://cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels", {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
"User-Agent": "antigravity/1.12.4",
"X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1",
Accept: "application/json",
},
body: JSON.stringify({ project: auth.projectId }),
signal: controller.signal,
});
};
try {
let res = await fetchModels(accessToken);
if ((res.status === 401 || res.status === 403) && auth.refreshToken) {
const refreshed = await refreshAntigravityAccessToken(auth.refreshToken);
if (refreshed?.accessToken) {
accessToken = refreshed.accessToken;
res = await fetchModels(accessToken);
}
}
if (res.status === 401 || res.status === 403) {
return { provider: "antigravity", displayName: "Antigravity", windows: [], error: "Unauthorized" };
}
if (!res.ok) {
return { provider: "antigravity", displayName: "Antigravity", windows: [], error: `HTTP ${res.status}` };
}
const data = (await res.json()) as any;
const models: Record<string, any> = data.models || {};
const getQuotaInfo = (modelKeys: string[]): { usedPercent: number; resetDescription?: string } | null => {
for (const key of modelKeys) {
const qi = models?.[key]?.quotaInfo;
if (!qi) continue;
// In practice (CodexBar issue #129), some models only provide resetTime.
// Treat missing remainingFraction as 0% remaining (100% used), which matches Antigravity's behavior when quota is exhausted.
const remainingFraction = typeof qi.remainingFraction === "number" ? qi.remainingFraction : 0;
const usedPercent = Math.min(100, Math.max(0, (1 - remainingFraction) * 100));
const resetTime = qi.resetTime ? new Date(qi.resetTime) : undefined;
return { usedPercent, resetDescription: resetTime ? formatReset(resetTime) : undefined };
}
return null;
};
// Quota groups from the reference snippet in CodexBar issue #129.
const windows: RateWindow[] = [];
const claudeOrGptOss = getQuotaInfo([
"claude-sonnet-4-5",
"claude-sonnet-4-5-thinking",
"claude-opus-4-5-thinking",
"gpt-oss-120b-medium",
]);
if (claudeOrGptOss) {
windows.push({ label: "Claude", usedPercent: claudeOrGptOss.usedPercent, resetDescription: claudeOrGptOss.resetDescription });
}
const gemini3Pro = getQuotaInfo(["gemini-3-pro-high", "gemini-3-pro-low", "gemini-3-pro-preview"]);
if (gemini3Pro) {
windows.push({ label: "G3 Pro", usedPercent: gemini3Pro.usedPercent, resetDescription: gemini3Pro.resetDescription });
}
const gemini3Flash = getQuotaInfo(["gemini-3-flash"]);
if (gemini3Flash) {
windows.push({ label: "G3 Flash", usedPercent: gemini3Flash.usedPercent, resetDescription: gemini3Flash.resetDescription });
}
if (windows.length === 0) {
return { provider: "antigravity", displayName: "Antigravity", windows: [], error: "No quota data" };
}
return { provider: "antigravity", displayName: "Antigravity", windows };
} catch (e) {
return { provider: "antigravity", displayName: "Antigravity", windows: [], error: String(e) };
}
}
// ============================================================================
// Codex (OpenAI) Usage
@ -954,12 +758,11 @@ class UsageComponent {
Promise.race([p, new Promise<T>((r) => setTimeout(() => r(fallback), ms))]);
// Fetch usage and status in parallel
const [claude, copilot, gemini, codex, antigravity, kiro, zai, claudeStatus, copilotStatus, geminiStatus, codexStatus] = await Promise.all([
const [claude, copilot, gemini, codex, kiro, zai, claudeStatus, copilotStatus, geminiStatus, codexStatus] = await Promise.all([
timeout(fetchClaudeUsage(), 6000, { provider: "anthropic", displayName: "Claude", windows: [], error: "Timeout" }),
timeout(fetchCopilotUsage(this.modelRegistry), 6000, { provider: "copilot", displayName: "Copilot", windows: [], error: "Timeout" }),
timeout(fetchGeminiUsage(this.modelRegistry), 6000, { provider: "gemini", displayName: "Gemini", windows: [], error: "Timeout" }),
timeout(fetchCodexUsage(this.modelRegistry), 6000, { provider: "codex", displayName: "Codex", windows: [], error: "Timeout" }),
timeout(fetchAntigravityUsage(this.modelRegistry), 6000, { provider: "antigravity", displayName: "Antigravity", windows: [], error: "Timeout" }),
timeout(fetchKiroUsage(), 6000, { provider: "kiro", displayName: "Kiro", windows: [], error: "Timeout" }),
timeout(fetchZaiUsage(), 6000, { provider: "zai", displayName: "z.ai", windows: [], error: "Timeout" }),
timeout(fetchProviderStatus("anthropic"), 3000, { indicator: "unknown" as const }),
@ -975,7 +778,7 @@ class UsageComponent {
codex.status = codexStatus;
// Filter out providers with no data and no error (not configured)
const allUsages = [claude, copilot, gemini, codex, antigravity, kiro, zai];
const allUsages = [claude, copilot, gemini, codex, kiro, zai];
this.usages = allUsages.filter(u => u.windows.length > 0 || u.error !== "No credentials" && u.error !== "kiro-cli not found" && u.error !== "No API key");
this.loading = false;
this.tui.requestRender();

View file

@ -193,7 +193,6 @@ const CLI_AUTH_PROVIDERS = new Set([
"claude-code",
"openai-codex",
"google-gemini-cli",
"google-antigravity",
]);
function checkLlmProviders(): ProviderCheckResult[] {

View file

@ -39,7 +39,6 @@ export const PROVIDER_REGISTRY: ProviderInfo[] = [
{ id: "github-copilot", label: "GitHub Copilot", category: "llm", envVar: "GITHUB_TOKEN", hasOAuth: true },
{ id: "openai-codex", label: "ChatGPT Plus/Pro (Codex)",category: "llm", hasOAuth: true },
{ id: "google-gemini-cli",label: "Google Gemini CLI", category: "llm", hasOAuth: true },
{ id: "google-antigravity",label: "Antigravity", category: "llm", hasOAuth: true },
{ id: "google", label: "Google (Gemini)", category: "llm", envVar: "GEMINI_API_KEY", dashboardUrl: "aistudio.google.com/apikey" },
{ id: "groq", label: "Groq", category: "llm", envVar: "GROQ_API_KEY", dashboardUrl: "console.groq.com" },
{ id: "xai", label: "xAI (Grok)", category: "llm", envVar: "XAI_API_KEY", dashboardUrl: "console.x.ai" },

View file

@ -339,7 +339,6 @@ test("boot and onboarding routes expose locked required state plus explicitly sk
"github-copilot",
"openai-codex",
"google-gemini-cli",
"google-antigravity",
"google",
"groq",
"xai",

View file

@ -147,7 +147,6 @@ const REQUIRED_PROVIDER_CATALOG: RequiredProviderCatalogEntry[] = [
{ id: "github-copilot", label: "GitHub Copilot", supportsApiKey: false, supportsOAuth: true },
{ id: "openai-codex", label: "ChatGPT Plus/Pro (Codex Subscription)", supportsApiKey: false, supportsOAuth: true },
{ id: "google-gemini-cli", label: "Google Cloud Code Assist (Gemini CLI)", supportsApiKey: false, supportsOAuth: true },
{ id: "google-antigravity", label: "Antigravity (Gemini 3, Claude, GPT-OSS)", supportsApiKey: false, supportsOAuth: true },
{ id: "google", label: "Google (Gemini API)", supportsApiKey: true, supportsOAuth: false },
{ id: "groq", label: "Groq", supportsApiKey: true, supportsOAuth: false },
{ id: "xai", label: "xAI (Grok)", supportsApiKey: true, supportsOAuth: false },