From 06a53071609be190563d893742bf4340f5b5fb6b Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 10 Mar 2026 16:16:12 +0700 Subject: [PATCH] feat(gemini-cli): add proper User-Agent and X-Goog-Api-Client headers Match native GeminiCLI client fingerprint to avoid upstream rejection. Also fix base executor to call transformRequest before buildHeaders so subclasses can store model context for header generation. Made-with: Cursor --- open-sse/config/constants.js | 28 ++++++++++++++++++++++++++++ open-sse/executors/base.js | 2 +- open-sse/executors/gemini-cli.js | 8 ++++++-- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/open-sse/config/constants.js b/open-sse/config/constants.js index e5d15ad6..63f29919 100644 --- a/open-sse/config/constants.js +++ b/open-sse/config/constants.js @@ -19,6 +19,34 @@ function mapStainlessArch() { } } +// === Gemini CLI Version Constants === +export const GEMINI_CLI_VERSION = "0.31.0"; +export const GEMINI_CLI_API_CLIENT = "google-genai-sdk/1.41.0 gl-node/v22.19.0"; + +function mapGeminiCLIOs() { + switch (platform()) { + case "darwin": return "darwin"; + case "win32": return "windows"; + case "linux": return "linux"; + case "freebsd": return "freebsd"; + default: return platform(); + } +} + +function mapGeminiCLIArch() { + switch (arch()) { + case "x64": return "x64"; + case "arm64": return "arm64"; + case "ia32": return "x86"; + default: return arch(); + } +} + +/** Returns User-Agent matching native Gemini CLI format: GeminiCLI// (; ) */ +export function geminiCLIUserAgent(model = "unknown") { + return `GeminiCLI/${GEMINI_CLI_VERSION}/${model || "unknown"} (${mapGeminiCLIOs()}; ${mapGeminiCLIArch()})`; +} + // === GitHub Copilot Version Constants === export const GITHUB_COPILOT = { VSCODE_VERSION: "1.110.0", diff --git a/open-sse/executors/base.js b/open-sse/executors/base.js index 7fc96496..17f351de 100644 --- a/open-sse/executors/base.js +++ b/open-sse/executors/base.js @@ -84,8 +84,8 @@ export class BaseExecutor { for (let urlIndex = 0; urlIndex < fallbackCount; urlIndex++) { const url = this.buildUrl(model, stream, urlIndex, credentials); - const headers = this.buildHeaders(credentials, stream); const transformedBody = this.transformRequest(model, body, stream, credentials); + const headers = this.buildHeaders(credentials, stream); if (!retryAttemptsByUrl[urlIndex]) retryAttemptsByUrl[urlIndex] = 0; diff --git a/open-sse/executors/gemini-cli.js b/open-sse/executors/gemini-cli.js index 0fbe2c66..e6fe3d35 100644 --- a/open-sse/executors/gemini-cli.js +++ b/open-sse/executors/gemini-cli.js @@ -1,5 +1,5 @@ import { BaseExecutor } from "./base.js"; -import { PROVIDERS, OAUTH_ENDPOINTS } from "../config/constants.js"; +import { PROVIDERS, OAUTH_ENDPOINTS, GEMINI_CLI_API_CLIENT, geminiCLIUserAgent } from "../config/constants.js"; export class GeminiCLIExecutor extends BaseExecutor { constructor() { @@ -15,11 +15,15 @@ export class GeminiCLIExecutor extends BaseExecutor { return { "Content-Type": "application/json", "Authorization": `Bearer ${credentials.accessToken}`, - ...(stream && { "Accept": "text/event-stream" }) + "User-Agent": geminiCLIUserAgent(this._currentModel), + "X-Goog-Api-Client": GEMINI_CLI_API_CLIENT, + "Accept": stream ? "text/event-stream" : "application/json" }; } transformRequest(model, body, stream, credentials) { + // Store model for use in buildHeaders (called by base.execute after transformRequest) + this._currentModel = model; if (!body.project && credentials?.projectId) { body.project = credentials.projectId; }