mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
Merge branch 'main' resolving upstream conflicts (#2408)
Integrated into release/v3.8.0
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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" },
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user