Merge branch 'main' resolving upstream conflicts (#2408)

Integrated into release/v3.8.0
This commit is contained in:
Benson K B
2026-05-19 18:18:16 +05:30
committed by GitHub
parent 81fb3f50e8
commit 6959e067fa
8 changed files with 98 additions and 20 deletions

View File

@@ -280,9 +280,14 @@ function installUpdate() {
// ── Content Security Policy (#15) ──────────────────────────
function setupContentSecurityPolicy() {
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
const scriptSrc = isDev
// Determine if the request is for the Next.js development server
const isDevUrl = details.url.includes("localhost:20128") || details.url.includes("127.0.0.1:20128");
// React/Next.js requires unsafe-eval for source maps and Hot Module Replacement (HMR) during development.
const scriptSrc = isDevUrl
? "script-src 'self' 'unsafe-inline' 'unsafe-eval' blob:"
: "script-src 'self' 'unsafe-inline' blob:";
const csp = [
"default-src 'self'",
"base-uri 'self'",
@@ -291,6 +296,7 @@ function setupContentSecurityPolicy() {
"frame-src 'none'",
"child-src 'none'",
"form-action 'self'",
`connect-src 'self' http://localhost:* ws://localhost:* wss://localhost:* https://*.omniroute.online https://*.omniroute.dev`,
scriptSrc,
"script-src-attr 'none'",
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",

View File

@@ -704,8 +704,8 @@ export const REGISTRY: Record<string, RegistryEntry> = {
const action = stream ? "streamGenerateContent?alt=sse" : "generateContent";
return `${base}:${action}`;
},
authType: "oauth",
authHeader: "bearer",
authType: "apikey",
authHeader: "x-goog-api-key",
defaultContextLength: 1048576,
oauth: {
clientIdEnv: "GEMINI_CLI_OAUTH_CLIENT_ID",
@@ -714,6 +714,8 @@ export const REGISTRY: Record<string, RegistryEntry> = {
clientSecretDefault: resolvePublicCred("gemini_alt"),
},
models: [
{ id: "gemini-1.5-pro", name: "Gemini 1.5 Pro" },
{ id: "gemini-1.5-flash", name: "Gemini 1.5 Flash" },
{ id: "gemini-3.1-pro-preview", name: "Gemini 3.1 Pro Preview" },
{ id: "gemini-3.1-pro-preview-customtools", name: "Gemini 3.1 Pro Preview Custom Tools" },
{ id: "gemini-3-flash-preview", name: "Gemini 3 Flash Preview" },

View File

@@ -133,11 +133,24 @@ export class GeminiCLIExecutor extends BaseExecutor {
model?: string
) {
void clientHeaders;
// Fallback to internal tracker if model not explicitly passed (matches older interface calls)
const activeModel = model || this._currentModel || "unknown";
const raw = getGeminiCliHeaders(
normalizeGeminiModel(model || "unknown"),
normalizeGeminiModel(activeModel),
credentials.accessToken,
stream ? "*/*" : "application/json"
);
if (credentials.apiKey) {
raw["x-goog-api-key"] = credentials.apiKey;
// getGeminiCliHeaders adds Authorization: Bearer undefined if accessToken is empty, so we clean it up
if (!credentials.accessToken) {
delete raw["Authorization"];
}
}
return scrubProxyAndFingerprintHeaders(raw);
}

View File

@@ -1,12 +1,15 @@
import { getModelAliases, setModelAlias } from "@/lib/db/models";
export const DEFAULT_MODEL_ALIAS_SEED = Object.freeze({
"gemini-3-pro-high": "antigravity/gemini-3-pro-preview",
"gemini-3-pro-low": "antigravity/gemini-3.1-pro-low",
"gemini-3-pro-preview": "antigravity/gemini-3-pro-preview",
"gemini-3.1-pro-preview": "antigravity/gemini-3-pro-preview",
"gemini-3.1-pro-preview-customtools": "antigravity/gemini-3-pro-preview",
"gemini-3-flash-preview": "antigravity/gemini-3-flash-preview",
"gemini-1.5-pro": "gemini-cli/gemini-1.5-pro",
"gemini-1.5-flash": "gemini-cli/gemini-1.5-flash",
"gemini-3-pro-high": "gemini-cli/gemini-3.1-pro-preview",
"gemini-3-pro-low": "gemini-cli/gemini-3.1-flash-lite-preview",
"gemini-3-pro-preview": "gemini-cli/gemini-3.1-pro-preview",
"gemini-3.1-pro-preview": "gemini-cli/gemini-3.1-pro-preview",
"gemini-3.1-pro-preview-customtools": "gemini-cli/gemini-3.1-pro-preview-customtools",
"gemini-3-flash-preview": "gemini-cli/gemini-3-flash-preview",
"gemini-3.1-flash-lite-preview": "gemini-cli/gemini-3.1-flash-lite-preview",
});
type SeedLogger = {

View File

@@ -732,14 +732,31 @@ async function validateGeminiLikeProvider({
isLocal = false,
}: any) {
try {
if (!baseUrl) {
return { valid: false, error: "Missing base URL" };
}
const requestUrl =
typeof providerSpecificData?.modelsUrl === "string" &&
providerSpecificData.modelsUrl.trim() !== ""
? providerSpecificData.modelsUrl.trim()
: `${baseUrl}/models`;
const urlWithKey =
authType === "query" ? `${requestUrl}?key=${encodeURIComponent(apiKey)}` : requestUrl;
const headers = authType === "header" ? { "x-goog-api-key": apiKey } : {};
// Use the correct auth header based on provider config:
// - gemini / gemini-cli (API key): x-goog-api-key
// - gemini-cli (OAuth): Bearer token
const headers: Record<string, string> = {};
if (authType === "header" || authType === "apikey") {
headers["x-goog-api-key"] = apiKey;
} else if (authType === "oauth" || apiKey.startsWith("ya29.")) {
headers["Authorization"] = `Bearer ${apiKey}`;
}
applyCustomUserAgent(headers, providerSpecificData);
const response = await validationRead(
urlWithKey,
@@ -749,10 +766,6 @@ async function validateGeminiLikeProvider({
isLocal
);
if (!baseUrl) {
return { valid: false, error: "Missing base URL" };
}
if (response.ok) {
return { valid: true, error: null };
}

View File

@@ -18,11 +18,11 @@ test("getProviderCategory: OAuth providers return 'oauth'", () => {
assert.equal(getProviderCategory("antigravity"), "oauth");
assert.equal(getProviderCategory("cursor"), "oauth");
assert.equal(getProviderCategory("kiro"), "oauth");
assert.equal(getProviderCategory("gemini-cli"), "oauth");
assert.equal(getProviderCategory("cline"), "oauth");
});
test("getProviderCategory: API key providers return 'apikey'", () => {
assert.equal(getProviderCategory("gemini-cli"), "apikey");
assert.equal(getProviderCategory("groq"), "apikey");
assert.equal(getProviderCategory("fireworks"), "apikey");
assert.equal(getProviderCategory("cerebras"), "apikey");

View File

@@ -40,6 +40,17 @@ test("GeminiCLIExecutor.buildUrl and buildHeaders match the native Gemini CLI fi
);
assert.equal(headers.Authorization, "Bearer gcli-token");
assert.equal(headers.Accept, "*/*");
const apiKeyHeaders = executor.buildHeaders(
{ apiKey: "gcli-api-key" },
false,
undefined,
"models/gemini-2.5-flash"
);
assert.equal(apiKeyHeaders["x-goog-api-key"], "gcli-api-key");
assert.equal(apiKeyHeaders.Authorization, undefined);
assert.equal(apiKeyHeaders.Accept, "application/json");
assert.match(
headers["User-Agent"],
new RegExp(
@@ -108,6 +119,14 @@ test("GeminiCLIExecutor.refreshProject caches loadCodeAssist lookups and transfo
assert.equal(second, "fresh-project-id");
assert.equal(calls, 1);
assert.equal(transformed.project, "fresh-project-id");
const apiKeyTransformed = await executor.transformRequest(
"gemini-2.5-flash",
{ request: { contents: [] } },
true,
{ apiKey: "gcli-api-key" }
);
assert.equal(apiKeyTransformed.project, undefined);
} finally {
globalThis.fetch = originalFetch;
}

View File

@@ -408,7 +408,7 @@ test("gemini validation rejects invalid keys via UNAUTHENTICATED status", async
assert.equal(result.error, "Invalid API key");
});
test("gemini-cli validation uses Bearer auth (OAuth)", async () => {
test("gemini-cli validation uses x-goog-api-key by default", async () => {
const calls = [];
globalThis.fetch = async (url, init) => {
calls.push({ url: String(url), headers: init?.headers });
@@ -422,12 +422,34 @@ test("gemini-cli validation uses Bearer auth (OAuth)", async () => {
const result = await validateProviderApiKey({
provider: "gemini-cli",
apiKey: "oauth-access-token",
apiKey: "gcli-api-key",
});
assert.equal(result.valid, true);
assert.equal(result.error, null);
assert.equal(calls.length, 1);
assert.equal(calls[0].headers["Authorization"], "Bearer oauth-access-token");
assert.equal(calls[0].headers["x-goog-api-key"], undefined);
assert.equal(calls[0].headers["x-goog-api-key"], "gcli-api-key");
assert.equal(calls[0].headers["Authorization"], undefined);
});
test("gemini-cli validation supports Bearer auth if token starts with ya29.", async () => {
const calls = [];
globalThis.fetch = async (url, init) => {
calls.push({ url: String(url), headers: init?.headers });
return new Response(
JSON.stringify({
models: [{ name: "models/gemini-2.5-flash" }],
}),
{ status: 200 }
);
};
const result = await validateProviderApiKey({
provider: "gemini-cli",
apiKey: "ya29.oauth-access-token",
});
assert.equal(result.valid, true);
assert.equal(calls.length, 1);
assert.equal(calls[0].headers["Authorization"], "Bearer ya29.oauth-access-token");
});