feat: update Gemini CLI executor with dynamic UA, header scrubbing, and obfuscation

Matches the Antigravity executor treatment:
- Dynamic User-Agent: GeminiCLI/0.31.0/MODEL (OS; ARCH) per-model
- X-Goog-Api-Client: maintained (was already correct)
- Header scrubbing: removes proxy/fingerprint headers
- Sensitive word obfuscation in user message content
- Tracks current model for per-request UA generation
This commit is contained in:
Ravi Tharuma
2026-04-13 11:41:22 +02:00
parent d8e9c16d83
commit 7eb3056856

View File

@@ -1,5 +1,8 @@
import { BaseExecutor } from "./base.ts";
import { PROVIDERS, OAUTH_ENDPOINTS } from "../config/constants.ts";
import { geminiCLIUserAgent, googApiClientHeader } from "../services/antigravityHeaders.ts";
import { scrubProxyAndFingerprintHeaders } from "../services/antigravityHeaderScrub.ts";
import { obfuscateSensitiveWords } from "../services/antigravityObfuscation.ts";
const LOAD_CODE_ASSIST_URL = "https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist";
const PROJECT_TTL_MS = 30_000; // 30 seconds — matches native Gemini CLI
@@ -22,19 +25,20 @@ export class GeminiCLIExecutor extends BaseExecutor {
}
buildHeaders(credentials, stream = true) {
return {
const raw = {
"Content-Type": "application/json",
Authorization: `Bearer ${credentials.accessToken}`,
// Fingerprint headers matching native GeminiCLI client (prevents upstream rejection)
"User-Agent": "GeminiCLI/0.31.0/unknown (linux; x64)",
"X-Goog-Api-Client": "google-genai-sdk/1.41.0 gl-node/v22.19.0",
// Dynamic headers matching native GeminiCLI client
"User-Agent": geminiCLIUserAgent(this._currentModel || "unknown"),
"X-Goog-Api-Client": googApiClientHeader(),
...(stream && { Accept: "text/event-stream" }),
// NOTE: x-goog-user-project removed — the stored projectId can become stale for
// free-tier accounts, causing 403 "Cloud Code Private API has not been used in
// project X". The API resolves the correct project from the OAuth token alone.
};
return scrubProxyAndFingerprintHeaders(raw);
}
// Track current model for dynamic UA (set by transformRequest)
private _currentModel = "unknown";
/**
* Fetch the current cloudaicompanionProject via loadCodeAssist API.
* Native Gemini CLI refreshes this every 30 seconds — OmniRoute stores it once
@@ -134,15 +138,29 @@ export class GeminiCLIExecutor extends BaseExecutor {
}
async transformRequest(model, body, stream, credentials) {
// Track model for dynamic User-Agent
this._currentModel = model || "unknown";
// Refresh the project ID via loadCodeAssist (cached for 30s).
// The translator builds the envelope with the stale stored projectId —
// we replace it here with the fresh one before sending to the API.
if (body && typeof body === "object" && body.request && credentials.accessToken) {
const freshProject = await this.refreshProject(credentials.accessToken);
if (freshProject) {
body.project = freshProject;
}
// If refresh failed, keep the stale projectId as a best-effort fallback
// Obfuscate sensitive client names in user content
const contents = body.request?.contents;
if (Array.isArray(contents)) {
for (const msg of contents) {
if (Array.isArray(msg.parts)) {
for (const part of msg.parts) {
if (typeof part.text === "string") {
part.text = obfuscateSensitiveWords(part.text);
}
}
}
}
}
}
return body;
}