Skip to content

Commit f7e0c50

Browse files
committed
- Changed ideType and platform in headers to reflect actual environment
- Updated Gemini CLI headers to match expected formats and behaviors - Removed Linux support and adjusted fingerprint generation accordingly - Cleaned up unused functions and constants - Enhanced tests to validate new header behaviors
1 parent b6710bc commit f7e0c50

10 files changed

Lines changed: 142 additions & 102 deletions

File tree

CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,27 @@
11
# Changelog
22

3+
## [1.5.1] - 2026-02-11
4+
5+
### Changed
6+
7+
- **Header Identity Alignment** - `ideType` changed from `IDE_UNSPECIFIED` to `ANTIGRAVITY` and `platform` from `PLATFORM_UNSPECIFIED` to dynamic `WINDOWS`/`MACOS` (based on `process.platform`) across all header sources (`getAntigravityHeaders`, `oauth.ts`, `project.ts`). Now matches Antigravity Manager behavior
8+
9+
- **Gemini CLI `Client-Metadata` Header** - Gemini CLI requests now include `Client-Metadata` header, aligning with actual `gemini-cli` behavior. Previously only Antigravity-style requests sent this header
10+
11+
- **Gemini CLI User-Agent Format** - Updated from `GeminiCLI/{ver}/{model}` to `GeminiCLI/{ver}/{model} ({platform}; {arch})` to match real `gemini-cli` UA strings. Version pool updated from `1.2.0/1.1.0/1.0.0` to `0.28.0/0.27.4/0.27.3` to align with actual release numbers
12+
13+
- **Randomized Headers Model-Aware** - `getRandomizedHeaders()` now accepts an optional `model` parameter, embedding the actual model name in Gemini CLI User-Agent strings instead of a hardcoded default
14+
15+
- **Fingerprint Platform Alignment** - Antigravity-style `Client-Metadata` platform now consistently matches the randomized User-Agent platform, fixing a potential mismatch where headers could disagree on reported platform
16+
17+
### Removed
18+
19+
- **Linux Fingerprints** - Removed `linux/amd64` and `linux/arm64` from `ANTIGRAVITY_PLATFORMS` and fingerprint generation. Linux users now masquerade as macOS (Antigravity does not support Linux as a native platform)
20+
21+
- **`getAntigravityUserAgents()` Function** - Removed unused helper that had no callers in the codebase
22+
23+
- **`X-Opencode-Tools-Debug` Header** - Removed debug telemetry header from outgoing requests
24+
325
## [1.5.0] - 2026-02-11
426

527
### Added

docs/ANTIGRAVITY_API_SPEC.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ Authorization: Bearer {access_token}
6464
Content-Type: application/json
6565
User-Agent: antigravity/1.15.8 windows/amd64
6666
X-Goog-Api-Client: google-cloud-sdk vscode_cloudshelleditor/0.1
67-
Client-Metadata: {"ideType":"IDE_UNSPECIFIED","platform":"PLATFORM_UNSPECIFIED","pluginType":"GEMINI"}
67+
Client-Metadata: {"ideType":"ANTIGRAVITY","platform":"MACOS","pluginType":"GEMINI"}
6868
```
6969

7070
For streaming requests, also include:

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "opencode-antigravity-auth",
3-
"version": "1.5.0",
3+
"version": "1.5.1",
44
"description": "Google Antigravity IDE OAuth auth plugin for Opencode - access Gemini 3 Pro and Claude 4.5 using Google credentials",
55
"main": "./dist/index.js",
66
"types": "./dist/index.d.ts",

src/antigravity/oauth.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,8 @@ async function fetchProjectID(accessToken: string): Promise<string> {
150150
headers: loadHeaders,
151151
body: JSON.stringify({
152152
metadata: {
153-
ideType: "IDE_UNSPECIFIED",
154-
platform: "PLATFORM_UNSPECIFIED",
153+
ideType: "ANTIGRAVITY",
154+
platform: process.platform === "win32" ? "WINDOWS" : "MACOS",
155155
pluginType: "GEMINI",
156156
},
157157
}),

src/constants.test.ts

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,45 +6,29 @@ import {
66
} from "./constants.ts"
77

88
describe("GEMINI_CLI_HEADERS", () => {
9-
it("contains only User-Agent with GeminiCLI format", () => {
9+
it("matches Code Assist headers from opencode-gemini-auth", () => {
1010
expect(GEMINI_CLI_HEADERS).toEqual({
11-
"User-Agent": "GeminiCLI/1.0.0/gemini-2.5-flash",
11+
"User-Agent": "google-api-nodejs-client/9.15.1",
12+
"X-Goog-Api-Client": "gl-node/22.17.0",
13+
"Client-Metadata": "ideType=IDE_UNSPECIFIED,platform=PLATFORM_UNSPECIFIED,pluginType=GEMINI",
1214
})
1315
})
14-
15-
it("does not contain X-Goog-Api-Client", () => {
16-
expect(GEMINI_CLI_HEADERS).not.toHaveProperty("X-Goog-Api-Client")
17-
})
18-
19-
it("does not contain Client-Metadata", () => {
20-
expect(GEMINI_CLI_HEADERS).not.toHaveProperty("Client-Metadata")
21-
})
2216
})
2317

2418
describe("getRandomizedHeaders", () => {
2519
describe("gemini-cli style", () => {
26-
it("returns only User-Agent header", () => {
27-
const headers = getRandomizedHeaders("gemini-cli")
28-
expect(headers).toHaveProperty("User-Agent")
29-
expect(headers["X-Goog-Api-Client"]).toBeUndefined()
30-
expect(headers["Client-Metadata"]).toBeUndefined()
20+
it("returns static Code Assist headers", () => {
21+
const headers = getRandomizedHeaders("gemini-cli", "gemini-2.5-pro")
22+
expect(headers).toEqual({
23+
"User-Agent": "google-api-nodejs-client/9.15.1",
24+
"X-Goog-Api-Client": "gl-node/22.17.0",
25+
"Client-Metadata": "ideType=IDE_UNSPECIFIED,platform=PLATFORM_UNSPECIFIED,pluginType=GEMINI",
26+
})
3127
})
3228

33-
it("returns a User-Agent in GeminiCLI format", () => {
34-
const headers = getRandomizedHeaders("gemini-cli")
35-
expect(headers["User-Agent"]).toMatch(/^GeminiCLI\/\d+\.\d+\.\d+\/gemini-2\.5-flash$/)
36-
})
37-
38-
it("returns one of the expected GeminiCLI user agents", () => {
39-
const expectedAgents = [
40-
"GeminiCLI/1.2.0/gemini-2.5-flash",
41-
"GeminiCLI/1.1.0/gemini-2.5-flash",
42-
"GeminiCLI/1.0.0/gemini-2.5-flash",
43-
]
44-
for (let i = 0; i < 20; i++) {
45-
const headers = getRandomizedHeaders("gemini-cli")
46-
expect(expectedAgents).toContain(headers["User-Agent"])
47-
}
29+
it("ignores requested model and keeps static User-Agent", () => {
30+
const headers = getRandomizedHeaders("gemini-cli", "gemini-3-pro-preview")
31+
expect(headers["User-Agent"]).toBe("google-api-nodejs-client/9.15.1")
4832
})
4933
})
5034

@@ -60,6 +44,26 @@ describe("getRandomizedHeaders", () => {
6044
const headers = getRandomizedHeaders("antigravity")
6145
expect(headers["User-Agent"]).toMatch(/^antigravity\//)
6246
})
47+
48+
it("aligns Client-Metadata platform with User-Agent platform", () => {
49+
for (let i = 0; i < 50; i++) {
50+
const headers = getRandomizedHeaders("antigravity")
51+
const ua = headers["User-Agent"]!
52+
const metadata = JSON.parse(headers["Client-Metadata"]!)
53+
if (ua.includes("windows/")) {
54+
expect(metadata.platform).toBe("WINDOWS")
55+
} else {
56+
expect(metadata.platform).toBe("MACOS")
57+
}
58+
}
59+
})
60+
61+
it("never produces a linux User-Agent", () => {
62+
for (let i = 0; i < 50; i++) {
63+
const headers = getRandomizedHeaders("antigravity")
64+
expect(headers["User-Agent"]).not.toMatch(/linux\//)
65+
}
66+
})
6367
})
6468
})
6569

src/constants.ts

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -93,39 +93,31 @@ export function getAntigravityHeaders(): HeaderSet & { "Client-Metadata": string
9393
return {
9494
"User-Agent": `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Antigravity/${getAntigravityVersion()} Chrome/138.0.7204.235 Electron/37.3.1 Safari/537.36`,
9595
"X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1",
96-
"Client-Metadata": '{"ideType":"IDE_UNSPECIFIED","platform":"PLATFORM_UNSPECIFIED","pluginType":"GEMINI"}',
96+
"Client-Metadata": `{"ideType":"ANTIGRAVITY","platform":"${process.platform === "win32" ? "WINDOWS" : "MACOS"}","pluginType":"GEMINI"}`,
9797
};
9898
}
9999

100100
/** @deprecated Use getAntigravityHeaders() for runtime access. */
101101
export const ANTIGRAVITY_HEADERS = {
102102
"User-Agent": `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Antigravity/${ANTIGRAVITY_VERSION} Chrome/138.0.7204.235 Electron/37.3.1 Safari/537.36`,
103103
"X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1",
104-
"Client-Metadata": '{"ideType":"IDE_UNSPECIFIED","platform":"PLATFORM_UNSPECIFIED","pluginType":"GEMINI"}',
104+
"Client-Metadata": `{"ideType":"ANTIGRAVITY","platform":"${process.platform === "win32" ? "WINDOWS" : "MACOS"}","pluginType":"GEMINI"}`,
105105
} as const;
106106

107107
export const GEMINI_CLI_HEADERS = {
108-
"User-Agent": "GeminiCLI/1.0.0/gemini-2.5-flash",
108+
"User-Agent": "google-api-nodejs-client/9.15.1",
109+
"X-Goog-Api-Client": "gl-node/22.17.0",
110+
"Client-Metadata": "ideType=IDE_UNSPECIFIED,platform=PLATFORM_UNSPECIFIED,pluginType=GEMINI",
109111
} as const;
110112

111-
const ANTIGRAVITY_PLATFORMS = ["windows/amd64", "darwin/arm64", "linux/amd64", "darwin/amd64", "linux/arm64"] as const;
112-
113-
function getAntigravityUserAgents(): string[] {
114-
return ANTIGRAVITY_PLATFORMS.map(platform => `antigravity/${getAntigravityVersion()} ${platform}`);
115-
}
113+
const ANTIGRAVITY_PLATFORMS = ["windows/amd64", "darwin/arm64", "darwin/amd64"] as const;
116114

117115
const ANTIGRAVITY_API_CLIENTS = [
118116
"google-cloud-sdk vscode_cloudshelleditor/0.1",
119117
"google-cloud-sdk vscode/1.96.0",
120118
"google-cloud-sdk vscode/1.95.0",
121119
] as const;
122120

123-
const GEMINI_CLI_USER_AGENTS = [
124-
"GeminiCLI/1.2.0/gemini-2.5-flash",
125-
"GeminiCLI/1.1.0/gemini-2.5-flash",
126-
"GeminiCLI/1.0.0/gemini-2.5-flash",
127-
] as const;
128-
129121
function randomFrom<T>(arr: readonly T[]): T {
130122
return arr[Math.floor(Math.random() * arr.length)]!;
131123
}
@@ -136,16 +128,20 @@ export type HeaderSet = {
136128
"Client-Metadata"?: string;
137129
};
138130

139-
export function getRandomizedHeaders(style: HeaderStyle): HeaderSet {
131+
export function getRandomizedHeaders(style: HeaderStyle, model?: string): HeaderSet {
140132
if (style === "gemini-cli") {
141133
return {
142-
"User-Agent": randomFrom(GEMINI_CLI_USER_AGENTS),
134+
"User-Agent": GEMINI_CLI_HEADERS["User-Agent"],
135+
"X-Goog-Api-Client": GEMINI_CLI_HEADERS["X-Goog-Api-Client"],
136+
"Client-Metadata": GEMINI_CLI_HEADERS["Client-Metadata"],
143137
};
144138
}
139+
const platform = randomFrom(ANTIGRAVITY_PLATFORMS);
140+
const metadataPlatform = platform.startsWith("windows") ? "WINDOWS" : "MACOS";
145141
return {
146-
"User-Agent": randomFrom(getAntigravityUserAgents()),
142+
"User-Agent": `antigravity/${getAntigravityVersion()} ${platform}`,
147143
"X-Goog-Api-Client": randomFrom(ANTIGRAVITY_API_CLIENTS),
148-
"Client-Metadata": getAntigravityHeaders()["Client-Metadata"],
144+
"Client-Metadata": `{"ideType":"ANTIGRAVITY","platform":"${metadataPlatform}","pluginType":"GEMINI"}`,
149145
};
150146
}
151147

@@ -262,4 +258,3 @@ You are pair programming with a USER to solve their coding task. The task may re
262258
263259
<priority>IMPORTANT: The instructions that follow supersede all above. Follow them as your primary directives.</priority>
264260
`;
265-

src/plugin/fingerprint.ts

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,12 @@ const ARCHITECTURES = ["x64", "arm64"];
2222

2323
const IDE_TYPES = [
2424
"ANTIGRAVITY",
25-
"IDE_UNSPECIFIED",
26-
];
25+
] as const;
2726

2827
const PLATFORMS = [
29-
"PLATFORM_UNSPECIFIED",
3028
"WINDOWS",
3129
"MACOS",
32-
"LINUX",
33-
];
30+
] as const;
3431

3532
const SDK_CLIENTS = [
3633
"google-cloud-sdk vscode_cloudshelleditor/0.1",
@@ -90,18 +87,14 @@ function generateSessionToken(): string {
9087
* Each fingerprint represents a unique "device" identity.
9188
*/
9289
export function generateFingerprint(): Fingerprint {
93-
const platform = randomFrom(["darwin", "win32", "linux"]);
90+
const platform = randomFrom(["darwin", "win32"] as const);
9491
const arch = randomFrom(ARCHITECTURES);
95-
const osVersion = randomFrom(OS_VERSIONS[platform] ?? OS_VERSIONS.linux!);
92+
const osVersion = randomFrom(OS_VERSIONS[platform] ?? OS_VERSIONS.darwin!);
9693

9794
const matchingPlatform =
98-
platform === "darwin"
99-
? "MACOS"
100-
: platform === "win32"
101-
? "WINDOWS"
102-
: platform === "linux"
103-
? "LINUX"
104-
: randomFrom(PLATFORMS);
95+
platform === "win32"
96+
? "WINDOWS"
97+
: "MACOS";
10598

10699
return {
107100
deviceId: generateDeviceId(),
@@ -126,13 +119,9 @@ export function collectCurrentFingerprint(): Fingerprint {
126119
const arch = os.arch();
127120

128121
const matchingPlatform =
129-
platform === "darwin"
130-
? "MACOS"
131-
: platform === "win32"
132-
? "WINDOWS"
133-
: platform === "linux"
134-
? "LINUX"
135-
: "PLATFORM_UNSPECIFIED";
122+
platform === "win32"
123+
? "WINDOWS"
124+
: "MACOS";
136125

137126
return {
138127
deviceId: generateDeviceId(),

src/plugin/project.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const projectContextPendingCache = new Map<string, Promise<ProjectContextResult>
1515

1616
const CODE_ASSIST_METADATA = {
1717
ideType: "ANTIGRAVITY",
18-
platform: "PLATFORM_UNSPECIFIED",
18+
platform: process.platform === "win32" ? "WINDOWS" : "MACOS",
1919
pluginType: "GEMINI",
2020
} as const;
2121

src/plugin/request.test.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -596,7 +596,7 @@ it("removes x-api-key header", () => {
596596
expect(headers.get("x-goog-user-project")).toBeNull();
597597
});
598598

599-
it("removes x-goog-user-project header for gemini-cli headerStyle", () => {
599+
it("preserves x-goog-user-project header for gemini-cli headerStyle", () => {
600600
const result = prepareAntigravityRequest(
601601
"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent",
602602
{ method: "POST", body: JSON.stringify({ contents: [] }), headers: { "x-goog-user-project": "my-project" } },
@@ -606,7 +606,40 @@ it("removes x-api-key header", () => {
606606
"gemini-cli"
607607
);
608608
const headers = result.init.headers as Headers;
609-
expect(headers.get("x-goog-user-project")).toBeNull();
609+
expect(headers.get("x-goog-user-project")).toBe("my-project");
610+
});
611+
612+
it("uses exact Code Assist headers for gemini-cli headerStyle", () => {
613+
const result = prepareAntigravityRequest(
614+
"https://generativelanguage.googleapis.com/v1beta/models/gemini-3-flash:generateContent",
615+
{ method: "POST", body: JSON.stringify({ contents: [] }) },
616+
mockAccessToken,
617+
mockProjectId,
618+
undefined,
619+
"gemini-cli"
620+
);
621+
const headers = result.init.headers as Headers;
622+
expect(headers.get("User-Agent")).toBe("google-api-nodejs-client/9.15.1");
623+
expect(headers.get("X-Goog-Api-Client")).toBe("gl-node/22.17.0");
624+
expect(headers.get("Client-Metadata")).toBe("ideType=IDE_UNSPECIFIED,platform=PLATFORM_UNSPECIFIED,pluginType=GEMINI");
625+
});
626+
627+
it("builds gemini-cli wrapped body without antigravity-only fields", () => {
628+
const result = prepareAntigravityRequest(
629+
"https://generativelanguage.googleapis.com/v1beta/models/gemini-3-flash:generateContent",
630+
{ method: "POST", body: JSON.stringify({ contents: [{ role: "user", parts: [{ text: "hi" }] }] }) },
631+
mockAccessToken,
632+
"",
633+
undefined,
634+
"gemini-cli"
635+
);
636+
const parsed = JSON.parse(result.init.body as string);
637+
expect(parsed).toHaveProperty("project", "");
638+
expect(parsed).toHaveProperty("model");
639+
expect(parsed).toHaveProperty("request");
640+
expect(parsed.requestType).toBeUndefined();
641+
expect(parsed.userAgent).toBeUndefined();
642+
expect(parsed.requestId).toBeUndefined();
610643
});
611644

612645
it("identifies Claude models correctly", () => {

0 commit comments

Comments
 (0)