mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-03 05:45:04 +03:00
Integrated into release/v3.7.8
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
||||
joinClaudeCodeCompatibleUrl,
|
||||
} from "../services/claudeCodeCompatible.ts";
|
||||
import { getGigachatAccessToken } from "../services/gigachatAuth.ts";
|
||||
import { getRegistryEntry } from "../config/providerRegistry.ts";
|
||||
import { applyProviderRequestDefaults } from "../services/providerRequestDefaults.ts";
|
||||
import {
|
||||
getOpenAICompatibleType,
|
||||
@@ -198,6 +199,11 @@ export class DefaultExecutor extends BaseExecutor {
|
||||
const baseUrl = credentials?.providerSpecificData?.baseUrl || this.config.baseUrl;
|
||||
return normalizeOpenAIChatUrl(baseUrl);
|
||||
}
|
||||
case "zai":
|
||||
case "glm-coding-apikey": {
|
||||
const zaiBaseUrl = credentials?.providerSpecificData?.baseUrl || this.config.baseUrl;
|
||||
return `${zaiBaseUrl}?beta=true`;
|
||||
}
|
||||
case "claude":
|
||||
case "glm":
|
||||
case "glmt":
|
||||
@@ -211,8 +217,11 @@ export class DefaultExecutor extends BaseExecutor {
|
||||
const resourceUrl = credentials?.providerSpecificData?.resourceUrl;
|
||||
return `https://${resourceUrl || "portal.qwen.ai"}/v1/chat/completions`;
|
||||
}
|
||||
default:
|
||||
return this.config.baseUrl;
|
||||
default: {
|
||||
const url = this.config.baseUrl;
|
||||
const entry = getRegistryEntry(this.provider);
|
||||
return entry?.urlSuffix ? `${url}${entry.urlSuffix}` : url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,6 +316,8 @@ export class DefaultExecutor extends BaseExecutor {
|
||||
case "kimi-coding":
|
||||
case "bailian-coding-plan":
|
||||
case "kimi-coding-apikey":
|
||||
case "zai":
|
||||
case "glm-coding-apikey":
|
||||
headers["x-api-key"] = effectiveKey || credentials.accessToken;
|
||||
break;
|
||||
default:
|
||||
@@ -327,9 +338,18 @@ export class DefaultExecutor extends BaseExecutor {
|
||||
headers["anthropic-version"] = "2023-06-01";
|
||||
}
|
||||
} else {
|
||||
const bearerToken = effectiveKey || credentials.accessToken;
|
||||
if (bearerToken) {
|
||||
headers["Authorization"] = `Bearer ${bearerToken}`;
|
||||
// Use registry authHeader if available, otherwise default to bearer
|
||||
const entry = getRegistryEntry(this.provider);
|
||||
const authHeader = entry?.authHeader || "bearer";
|
||||
const token = effectiveKey || credentials.accessToken;
|
||||
if (token) {
|
||||
if (authHeader === "x-api-key") {
|
||||
headers["x-api-key"] = token;
|
||||
} else if (authHeader === "x-goog-api-key") {
|
||||
headers["x-goog-api-key"] = token;
|
||||
} else {
|
||||
headers["Authorization"] = `Bearer ${token}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
* 4. process.env (shell / Docker -e flags, highest priority)
|
||||
*/
|
||||
|
||||
import { randomBytes, createDecipheriv } from "node:crypto";
|
||||
import { randomBytes, createDecipheriv, scryptSync, createHash } from "node:crypto";
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import { homedir } from "node:os";
|
||||
@@ -246,14 +246,41 @@ export function bootstrapEnv({ dataDirOverride, quiet = false } = {}) {
|
||||
const iv = Buffer.from(parts[2], "hex");
|
||||
const ct = Buffer.from(parts[3], "hex");
|
||||
const tag = Buffer.from(parts[4], "hex");
|
||||
const key = Buffer.from(merged.STORAGE_ENCRYPTION_KEY, "hex");
|
||||
const decipher = createDecipheriv("aes-256-gcm", key, iv);
|
||||
decipher.setAuthTag(tag);
|
||||
try {
|
||||
|
||||
// Try decrypting with both key derivation methods matching encryption.ts
|
||||
const tryDecrypt = (derivedKey) => {
|
||||
const decipher = createDecipheriv("aes-256-gcm", derivedKey, iv);
|
||||
decipher.setAuthTag(tag);
|
||||
decipher.update(ct);
|
||||
decipher.final();
|
||||
// Decrypt succeeded — key matches
|
||||
};
|
||||
|
||||
// Dynamic salt (current): scryptSync(secret, sha256(secret).slice(0,16), 32)
|
||||
const dynamicSalt = createHash("sha256")
|
||||
.update(merged.STORAGE_ENCRYPTION_KEY)
|
||||
.digest()
|
||||
.slice(0, 16);
|
||||
const dynamicKey = scryptSync(merged.STORAGE_ENCRYPTION_KEY, dynamicSalt, 32);
|
||||
|
||||
// Legacy salt (fallback): scryptSync(secret, "omniroute-field-encryption-v1", 32)
|
||||
const legacySalt = "omniroute-field-encryption-v1";
|
||||
const legacyKey = scryptSync(merged.STORAGE_ENCRYPTION_KEY, legacySalt, 32);
|
||||
|
||||
let keyMatched = false;
|
||||
try {
|
||||
tryDecrypt(dynamicKey);
|
||||
keyMatched = true;
|
||||
} catch {
|
||||
// Try legacy key as fallback
|
||||
try {
|
||||
tryDecrypt(legacyKey);
|
||||
keyMatched = true;
|
||||
} catch {
|
||||
// Both failed — key truly doesn't match
|
||||
}
|
||||
}
|
||||
|
||||
if (!keyMatched) {
|
||||
log(
|
||||
"⛔ STORAGE_ENCRYPTION_KEY does not match the key used to encrypt your stored credentials."
|
||||
);
|
||||
|
||||
@@ -268,6 +268,40 @@ test("DefaultExecutor.buildUrl falls back to OpenAI config for unknown providers
|
||||
assert.equal(executor.buildUrl("gpt-4.1", true), PROVIDERS.openai.baseUrl);
|
||||
});
|
||||
|
||||
test("DefaultExecutor.buildUrl applies urlSuffix for zai and glm-coding-apikey", () => {
|
||||
const zai = new DefaultExecutor("zai");
|
||||
const glmCodingApikey = new DefaultExecutor("glm-coding-apikey");
|
||||
assert.equal(
|
||||
zai.buildUrl("glm-5", true, 0, {
|
||||
providerSpecificData: { baseUrl: "https://api.z.ai/api/anthropic/v1/messages" },
|
||||
}),
|
||||
"https://api.z.ai/api/anthropic/v1/messages?beta=true"
|
||||
);
|
||||
assert.equal(
|
||||
glmCodingApikey.buildUrl("glm-4.7", true, 0, {
|
||||
providerSpecificData: { baseUrl: "https://api.z.ai/api/anthropic/v1/messages" },
|
||||
}),
|
||||
"https://api.z.ai/api/anthropic/v1/messages?beta=true"
|
||||
);
|
||||
assert.equal(zai.buildUrl("glm-5", true), "https://api.z.ai/api/anthropic/v1/messages?beta=true");
|
||||
});
|
||||
|
||||
test("DefaultExecutor.buildUrl applies urlSuffix from registry for unknown providers with suffix", () => {
|
||||
const executor = new DefaultExecutor("unknown-provider");
|
||||
assert.equal(executor.buildUrl("gpt-4.1", true), PROVIDERS.openai.baseUrl);
|
||||
});
|
||||
|
||||
test("DefaultExecutor.buildHeaders uses x-api-key for zai and glm-coding-apikey", () => {
|
||||
const zai = new DefaultExecutor("zai");
|
||||
const glmCodingApikey = new DefaultExecutor("glm-coding-apikey");
|
||||
const zaiHeaders = zai.buildHeaders({ apiKey: "zai-key" }, true);
|
||||
const glmHeaders = glmCodingApikey.buildHeaders({ apiKey: "glm-key" }, true);
|
||||
assert.equal(zaiHeaders["x-api-key"], "zai-key");
|
||||
assert.equal(glmHeaders["x-api-key"], "glm-key");
|
||||
assert.equal(zaiHeaders["Authorization"], undefined);
|
||||
assert.equal(glmHeaders["Authorization"], undefined);
|
||||
});
|
||||
|
||||
test("DefaultExecutor.buildHeaders handles Gemini and Claude auth modes", () => {
|
||||
const gemini = new DefaultExecutor("gemini");
|
||||
const claude = new DefaultExecutor("claude");
|
||||
|
||||
Reference in New Issue
Block a user