Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions src/mitm/antigravityIdeVersion.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"use strict";

// Rewrite Antigravity IDE markers so upstream AG 2.x backend accepts the request.
// User-Agent header (antigravity/<old>) and body.metadata.ideVersion are forced
// to a known-good IDE version. Hardcoded MVP — toggle/version configurable later.
// Rewrite Antigravity IDE markers on generation requests so upstream AG 2.x
// backend accepts them. Catalog and other passthrough requests retain the
// client's current identity. Hardcoded MVP — toggle/version configurable later.

const ANTIGRAVITY_IDE_VERSION = "1.23.2";
const ANTIGRAVITY_IDE_VERSION_OVERRIDE_ENABLED = true;
Expand All @@ -19,8 +19,10 @@ function rewriteAntigravityUserAgent(userAgent, version) {
return userAgent.replace(/antigravity\/[^\s]+/, `antigravity/${version}`);
}

function applyAntigravityIdeVersionOverride(bodyBuffer, headers) {
if (!ANTIGRAVITY_IDE_VERSION_OVERRIDE_ENABLED) {
function applyAntigravityIdeVersionOverride(bodyBuffer, headers, requestUrl) {
const isGenerationEndpoint = requestUrl?.includes(":generateContent") ||
requestUrl?.includes(":streamGenerateContent");
if (!ANTIGRAVITY_IDE_VERSION_OVERRIDE_ENABLED || !isGenerationEndpoint) {
return { bodyBuffer, headers, applied: false, version: ANTIGRAVITY_IDE_VERSION };
}

Expand Down
2 changes: 1 addition & 1 deletion src/mitm/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ async function passthrough(req, res, bodyBuffer, onResponse) {

const tool = getToolForHost(req.headers.host);
const versionOverride = tool === "antigravity"
? applyAntigravityIdeVersionOverride(bodyBuffer, req.headers)
? applyAntigravityIdeVersionOverride(bodyBuffer, req.headers, req.url)
: { bodyBuffer, headers: req.headers };
const bodyForForwarding = versionOverride.bodyBuffer;
const headersForForwarding = { ...versionOverride.headers, host: targetHost };
Expand Down
89 changes: 89 additions & 0 deletions tests/unit/antigravity-ide-version.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { createRequire } from "module";
import { describe, expect, it } from "vitest";

const require = createRequire(import.meta.url);
const {
ANTIGRAVITY_IDE_VERSION,
applyAntigravityIdeVersionOverride,
} = require("../../src/mitm/antigravityIdeVersion.js");

const CURRENT_VERSION = "2.11.0";

function makeRequest(metadata = { ideName: "antigravity", ideVersion: CURRENT_VERSION }) {
const bodyBuffer = Buffer.from(JSON.stringify({ metadata, request: { contents: [] } }));
const headers = {
"content-type": "application/json",
"content-length": String(bodyBuffer.length),
"user-agent": `antigravity/${CURRENT_VERSION}`,
};
return { bodyBuffer, headers };
}

describe("Antigravity IDE version override", () => {
it("preserves catalog request identity and bytes", () => {
const { bodyBuffer, headers } = makeRequest();

const result = applyAntigravityIdeVersionOverride(
bodyBuffer,
headers,
"/v1internal:fetchAvailableModels"
);

expect(result.applied).toBe(false);
expect(result.bodyBuffer).toBe(bodyBuffer);
expect(result.headers).toBe(headers);
expect(result.headers["user-agent"]).toBe(`antigravity/${CURRENT_VERSION}`);
expect(JSON.parse(result.bodyBuffer.toString()).metadata.ideVersion).toBe(CURRENT_VERSION);
expect(result.headers["content-length"]).toBe(String(bodyBuffer.length));
});

it.each([":generateContent", ":streamGenerateContent"])(
"rewrites identity for %s requests",
(endpoint) => {
const { bodyBuffer, headers } = makeRequest();

const result = applyAntigravityIdeVersionOverride(
bodyBuffer,
headers,
`/v1internal/models/gemini-3.7-flash-tiered${endpoint}`
);

expect(result.applied).toBe(true);
expect(result.bodyBuffer).not.toBe(bodyBuffer);
expect(result.headers["user-agent"]).toBe(`antigravity/${ANTIGRAVITY_IDE_VERSION}`);
expect(JSON.parse(result.bodyBuffer.toString()).metadata.ideVersion).toBe(ANTIGRAVITY_IDE_VERSION);
}
);

it("preserves malformed non-generation request content byte-for-byte", () => {
const bodyBuffer = Buffer.from([0xff, 0x00, 0x7b, 0x6e, 0x6f, 0x74, 0x2d, 0x6a, 0x73, 0x6f, 0x6e]);
const headers = {
"content-type": "application/octet-stream",
"content-length": String(bodyBuffer.length),
"user-agent": `antigravity/${CURRENT_VERSION}`,
};

const result = applyAntigravityIdeVersionOverride(bodyBuffer, headers, "/v1internal:loadCodeAssist");

expect(result.applied).toBe(false);
expect(result.bodyBuffer).toBe(bodyBuffer);
expect(result.headers).toBe(headers);
});

it("does not synthesize missing Antigravity identity", () => {
const bodyBuffer = Buffer.from(JSON.stringify({ metadata: {}, request: { contents: [] } }));
const headers = { "content-type": "application/json", "content-length": String(bodyBuffer.length) };

const result = applyAntigravityIdeVersionOverride(
bodyBuffer,
headers,
"/v1internal/models/gemini-3.7-flash-tiered:generateContent"
);

expect(result.applied).toBe(false);
expect(result.bodyBuffer).toBe(bodyBuffer);
expect(result.headers).toEqual(headers);
expect(result.headers).not.toHaveProperty("user-agent");
expect(JSON.parse(result.bodyBuffer.toString()).metadata).not.toHaveProperty("ideVersion");
});
});