mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-02 21:32:10 +03:00
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:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user