diff --git a/open-sse/config/antigravityModelAliases.ts b/open-sse/config/antigravityModelAliases.ts index ed19f31dbb..eccc1be5b4 100644 --- a/open-sse/config/antigravityModelAliases.ts +++ b/open-sse/config/antigravityModelAliases.ts @@ -1,18 +1,28 @@ export const ANTIGRAVITY_PUBLIC_MODELS = Object.freeze([ + // Gemini 3.5 Flash — flagship model in Antigravity 2.0 (May 2026) { - id: "claude-opus-4-6-thinking", - name: "Claude Opus 4.6 (Thinking)", - contextLength: 250000, - maxOutputTokens: 64000, + id: "gemini-3.5-flash-preview", + name: "Gemini 3.5 Flash (High)", + contextLength: 1048576, + maxOutputTokens: 65536, supportsReasoning: true, supportsVision: true, toolCalling: true, }, { - id: "claude-sonnet-4-6", - name: "Claude Sonnet 4.6 (Thinking)", - contextLength: 250000, - maxOutputTokens: 64000, + id: "gemini-3.5-flash-low", + name: "Gemini 3.5 Flash (Low)", + contextLength: 1048576, + maxOutputTokens: 65536, + supportsReasoning: true, + supportsVision: true, + toolCalling: true, + }, + { + id: "gemini-3-flash-agent", + name: "Gemini 3.5 Flash Agent", + contextLength: 1048576, + maxOutputTokens: 65536, supportsReasoning: true, supportsVision: true, toolCalling: true, @@ -135,10 +145,12 @@ export const ANTIGRAVITY_PUBLIC_MODELS = Object.freeze([ export const ANTIGRAVITY_MODEL_ALIASES = Object.freeze({ "gemini-3-pro-preview": "gemini-3.1-pro-high", - "gemini-3.5-flash-preview": "gemini-3-flash-agent", + "gemini-3.5-flash-preview": "gemini-3.5-flash-high", "gemini-3-flash-preview": "gemini-3-flash", "gemini-3-pro-image-preview": "gemini-3-pro-image", "gemini-2.5-computer-use-preview-10-2025": "rev19-uic3-1p", + // Deprecated: Claude models were removed from Antigravity 2.0 (May 2026). + // These aliases are kept for backward compatibility but will 404 on new requests. "gemini-claude-sonnet-4-5": "claude-sonnet-4-6", "gemini-claude-sonnet-4-5-thinking": "claude-sonnet-4-6", "gemini-claude-opus-4-5-thinking": "claude-opus-4-6-thinking", @@ -148,6 +160,7 @@ type AntigravityModelAliasMap = Record; export const ANTIGRAVITY_REVERSE_MODEL_ALIASES: AntigravityModelAliasMap = Object.freeze({ "gemini-3.1-pro-high": "gemini-3-pro-preview", + "gemini-3.5-flash-high": "gemini-3.5-flash-preview", "gemini-3-flash-agent": "gemini-3.5-flash-preview", "gemini-3-flash": "gemini-3-flash-preview", "gemini-3-pro-image": "gemini-3-pro-image-preview", diff --git a/src/app/api/settings/mitm/route.ts b/src/app/api/settings/mitm/route.ts index 88c1ee4099..bbd5622455 100644 --- a/src/app/api/settings/mitm/route.ts +++ b/src/app/api/settings/mitm/route.ts @@ -8,6 +8,7 @@ import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { resolveApiKey } from "@/shared/services/apiKeyResolver"; import { resolveMitmDataDir } from "@/mitm/dataDir"; import { KIRO_MITM_PROFILE } from "@/mitm/targets/kiro"; +import { ANTIGRAVITY_MITM_PROFILE } from "@/mitm/targets/antigravity"; type MitmTargetRoute = { id: string; @@ -70,14 +71,18 @@ function getKeyPath() { } function defaultTargets(port = DEFAULT_PORT): MitmTargetRoute[] { + const allHosts = [ + ANTIGRAVITY_MITM_PROFILE.targetHost, + ...(ANTIGRAVITY_MITM_PROFILE.additionalHosts || []), + ]; return [ { - id: "antigravity", - name: "Antigravity", - targetHost: "daily-cloudcode-pa.googleapis.com", - targetPort: 443, + id: ANTIGRAVITY_MITM_PROFILE.id, + name: ANTIGRAVITY_MITM_PROFILE.name, + targetHost: allHosts.join(", "), + targetPort: ANTIGRAVITY_MITM_PROFILE.targetPort, localPort: port, - endpoints: [":generateContent", ":streamGenerateContent"], + endpoints: ANTIGRAVITY_MITM_PROFILE.apiEndpoints, enabled: true, }, { diff --git a/src/mitm/dns/dnsConfig.ts b/src/mitm/dns/dnsConfig.ts index 3aac62efd5..14223c7f18 100644 --- a/src/mitm/dns/dnsConfig.ts +++ b/src/mitm/dns/dnsConfig.ts @@ -13,6 +13,10 @@ const HOSTS_FILE = IS_WIN ? path.join(process.env.SystemRoot || "C:\\Windows", "System32", "drivers", "etc", "hosts") : "/etc/hosts"; +// Both IPv4 and IPv6 entries are needed — modern Windows apps often resolve +// to IPv6 first, bypassing an IPv4-only MITM redirect. +const DNS_ENTRIES = [`127.0.0.1 ${TARGET_HOST}`, `::1 ${TARGET_HOST}`]; + const REMOVE_HOSTS_ENTRY_SCRIPT = ` const fs = require("fs"); const filePath = process.argv[1]; @@ -25,34 +29,45 @@ const filtered = content.split(/\\r?\\n/).filter((line) => { fs.writeFileSync(filePath, filtered.join("\\n").replace(/\\n*$/, "\\n")); `; -/** - * Check if DNS entry already exists - */ export function checkDNSEntry(): boolean { try { const hostsContent = fs.readFileSync(HOSTS_FILE, "utf8"); const lines = hostsContent.split(/\r?\n/); - return lines.some((line) => { - const parts = line.trim().split(/\s+/); - return parts.length >= 2 && parts[0] === "127.0.0.1" && parts.some((p) => p === TARGET_HOST); + return DNS_ENTRIES.every((entry) => { + const entryIp = entry.split(/\s+/)[0]; + return lines.some((line) => { + const parts = line.trim().split(/\s+/); + return parts.length >= 2 && parts[0] === entryIp && parts.some((p) => p === TARGET_HOST); + }); }); } catch { return false; } } -/** - * Add DNS entry to hosts file - */ export async function addDNSEntry(sudoPassword: string): Promise { if (checkDNSEntry()) { - console.log(`DNS entry for ${TARGET_HOST} already exists`); + console.log(`DNS entries for ${TARGET_HOST} already exist (IPv4 + IPv6)`); return; } - const entry = `127.0.0.1 ${TARGET_HOST}`; + const entriesToAdd = DNS_ENTRIES.filter((entry) => { + const entryIp = entry.split(/\s+/)[0]; + try { + const hostsContent = fs.readFileSync(HOSTS_FILE, "utf8"); + const lines = hostsContent.split(/\r?\n/); + return !lines.some((line) => { + const parts = line.trim().split(/\s+/); + return parts.length >= 2 && parts[0] === entryIp && parts.some((p) => p === TARGET_HOST); + }); + } catch { + return true; + } + }); - try { + if (entriesToAdd.length === 0) return; + + for (const entry of entriesToAdd) { if (IS_WIN) { await runElevatedPowerShell( `Add-Content -LiteralPath ${quotePowerShell(HOSTS_FILE)} -Value ${quotePowerShell(entry)}` @@ -65,9 +80,7 @@ export async function addDNSEntry(sudoPassword: string): Promise { `${entry}\n` ); } - console.log(`✅ Added DNS entry: ${entry}`); - } catch (error) { - throw new Error(`Failed to add DNS entry: ${getErrorMessage(error)}`); + console.log(`Added DNS entry: ${entry}`); } } diff --git a/src/mitm/server.cjs b/src/mitm/server.cjs index 9b6ac9b02b..9e77e60825 100644 --- a/src/mitm/server.cjs +++ b/src/mitm/server.cjs @@ -13,10 +13,12 @@ function getDataDir() { } // Configuration +// Keep in sync with src/mitm/targets/antigravity.ts const TARGET_HOSTS = new Set([ "daily-cloudcode-pa.sandbox.googleapis.com", "daily-cloudcode-pa.googleapis.com", "cloudcode-pa.googleapis.com", + "autopush-cloudcode-pa.sandbox.googleapis.com", ]); const parsedLocalPort = Number.parseInt(process.env.MITM_LOCAL_PORT || "443", 10); const LOCAL_PORT = @@ -288,25 +290,34 @@ const server = https.createServer(sslOptions, async (req, res) => { writeStats(); const bodyBuffer = await collectBodyRaw(req); + const host = String(req.headers.host || "").split(":")[0].toLowerCase(); + const model = bodyBuffer.length > 0 ? extractModel(bodyBuffer) : null; + + console.log(`[MITM] ${req.method} ${host}${req.url} | body: ${bodyBuffer.length}B | model: ${model || "N/A"}`); - // Save request log if enabled if (bodyBuffer.length > 0) saveRequestLog(req.url, bodyBuffer); - // Anti-loop: requests from OmniRoute bypass interception if (req.headers["x-omniroute-source"] === "omniroute") { + console.log(`[MITM] → PASSTHROUGH (OmniRoute source loop)`); + return passthrough(req, res, bodyBuffer); + } + + if (!TARGET_HOSTS.has(host)) { + console.log(`[MITM] → PASSTHROUGH (host ${host} not in target list)`); return passthrough(req, res, bodyBuffer); } const isChatRequest = CHAT_URL_PATTERNS.some((p) => req.url.includes(p)); if (!isChatRequest) { + console.log(`[MITM] → PASSTHROUGH (URL ${req.url} does not match chat patterns)`); return passthrough(req, res, bodyBuffer); } - const model = extractModel(bodyBuffer); const mappedModel = getMappedModel(model); if (!mappedModel) { + console.log(`[MITM] → PASSTHROUGH (model "${model}" has no MITM alias mapping)`); return passthrough(req, res, bodyBuffer); } @@ -314,7 +325,7 @@ const server = https.createServer(sslOptions, async (req, res) => { stats.lastInterceptAt = new Date().toISOString(); writeStats(); - console.log(`🔀 ${model} → ${mappedModel}`); + console.log(`[MITM] INTERCEPTED ${model} → ${mappedModel}`); return intercept(req, res, bodyBuffer, mappedModel); }); diff --git a/src/mitm/targets/antigravity.ts b/src/mitm/targets/antigravity.ts new file mode 100644 index 0000000000..7c8535a65f --- /dev/null +++ b/src/mitm/targets/antigravity.ts @@ -0,0 +1,39 @@ +export interface MitmTarget { + id: string; + name: string; + description: string; + targetHost: string; + targetPort: number; + localPort: number; + userAgentPattern: string | null; + apiEndpoints: string[]; + authHeader: string; + additionalHosts?: string[]; + instructions: string[]; + referenceIde?: string; +} + +export const ANTIGRAVITY_MITM_PROFILE: MitmTarget = { + id: "antigravity", + name: "Antigravity IDE", + description: + "Intercepts Antigravity IDE requests to cloudcode-pa.googleapis.com and routes them through OmniRoute.", + targetHost: "daily-cloudcode-pa.googleapis.com", + targetPort: 443, + localPort: 443, + userAgentPattern: null, + apiEndpoints: [ + "/v1internal:generateContent", + "/v1internal:streamGenerateContent", + "/v1internal:loadCodeAssist", + "/v1internal:onboardUser", + ], + authHeader: "authorization", + additionalHosts: ["cloudcode-pa.googleapis.com", "daily-cloudcode-pa.sandbox.googleapis.com"], + instructions: [ + "1. Install OmniRoute's root certificate", + "2. Start the MITM proxy via Dashboard or CLI", + "3. Configure model mappings in Dashboard → CLI Tools → Antigravity", + "4. Open Antigravity IDE — API calls will be routed through OmniRoute", + ], +};