mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-05 14:52:09 +03:00
fix(qwen): use security.auth format instead of modelProviders (#1677)
Co-authored-by: Benson K B <benzntech@users.noreply.github.com>
This commit is contained in:
@@ -8,7 +8,7 @@ import { getOpenCodeConfigPath } from "@/shared/services/cliRuntime";
|
||||
import { mergeOpenCodeConfigText } from "@/shared/services/opencodeConfig";
|
||||
import { guideSettingsSaveSchema } from "@/shared/validation/schemas";
|
||||
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
|
||||
import { resolveApiKey } from "@/shared/services/apiKeyResolver";
|
||||
import { resolveApiKey, getOrCreateApiKey } from "@/shared/services/apiKeyResolver";
|
||||
|
||||
/**
|
||||
* POST /api/cli-tools/guide-settings/:toolId
|
||||
@@ -43,7 +43,10 @@ export async function POST(request, { params }) {
|
||||
const { baseUrl, model, models, modelLabels } = validation.data;
|
||||
// (#523) Extract keyId BEFORE validation — Zod strips unknown fields!
|
||||
const apiKeyId = typeof rawBody?.keyId === "string" ? rawBody.keyId.trim() : null;
|
||||
const apiKey = await resolveApiKey(apiKeyId, validation.data.apiKey);
|
||||
// If no keyId provided, auto-create a valid DB-backed key instead of using placeholder
|
||||
const apiKey = apiKeyId
|
||||
? await resolveApiKey(apiKeyId, validation.data.apiKey)
|
||||
: await getOrCreateApiKey();
|
||||
|
||||
try {
|
||||
switch (toolId) {
|
||||
@@ -186,50 +189,25 @@ async function saveOpenCodeConfig({ baseUrl, apiKey, model, models, modelLabels
|
||||
}
|
||||
|
||||
/**
|
||||
* Save Qwen Code config to ~/.qwen/settings.json + ~/.qwen/.env
|
||||
* Save Qwen Code config to ~/.qwen/settings.json
|
||||
*
|
||||
* Per official docs, credentials go in .env via envKey references,
|
||||
* not hardcoded in settings.json modelProviders entries.
|
||||
* Writes openai, anthropic, and gemini providers pointing to OmniRoute.
|
||||
* Uses security.auth format (not modelProviders) since Qwen Code
|
||||
* prioritizes security.auth.selectedType over modelProviders entries.
|
||||
* Per official docs: security.auth takes highest precedence.
|
||||
*/
|
||||
async function saveQwenConfig({ baseUrl, apiKey, model }) {
|
||||
const home = os.homedir();
|
||||
const configPath = path.join(home, ".qwen", "settings.json");
|
||||
const envPath = path.join(home, ".qwen", ".env");
|
||||
const configDir = path.dirname(configPath);
|
||||
|
||||
await fs.mkdir(configDir, { recursive: true });
|
||||
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
||||
|
||||
const normalizedBaseUrl = String(baseUrl || "")
|
||||
.trim()
|
||||
.replace(/\/+$/, "");
|
||||
const resolvedApiKey = apiKey || "sk_omniroute";
|
||||
const resolvedModel = model || "coder-model";
|
||||
const resolvedModel = model || "gemini-cli/gemini-3.1-pro-preview";
|
||||
|
||||
// --- Write API keys to .env ---
|
||||
let envContent = "";
|
||||
try {
|
||||
envContent = await fs.readFile(envPath, "utf-8");
|
||||
} catch {
|
||||
// File doesn't exist
|
||||
}
|
||||
|
||||
const envLines = envContent.split("\n").filter((line) => {
|
||||
// Remove old OmniRoute-related keys we're about to write
|
||||
return (
|
||||
!line.startsWith("OPENAI_API_KEY=") &&
|
||||
!line.startsWith("ANTHROPIC_API_KEY=") &&
|
||||
!line.startsWith("GEMINI_API_KEY=")
|
||||
);
|
||||
});
|
||||
|
||||
envLines.push(`OPENAI_API_KEY=${resolvedApiKey}`);
|
||||
envLines.push(`ANTHROPIC_API_KEY=${resolvedApiKey}`);
|
||||
envLines.push(`GEMINI_API_KEY=${resolvedApiKey}`);
|
||||
|
||||
await fs.writeFile(envPath, envLines.join("\n").trim() + "\n", "utf-8");
|
||||
|
||||
// --- Write modelProviders to settings.json ---
|
||||
// Read existing config to preserve other settings (permissions, mcpServers, etc.)
|
||||
let existingConfig: Record<string, any> = {};
|
||||
try {
|
||||
const raw = await fs.readFile(configPath, "utf-8");
|
||||
@@ -238,75 +216,28 @@ async function saveQwenConfig({ baseUrl, apiKey, model }) {
|
||||
// File doesn't exist or invalid JSON
|
||||
}
|
||||
|
||||
if (!existingConfig.modelProviders) existingConfig.modelProviders = {};
|
||||
|
||||
// openai provider — primary, supports all models via OmniRoute
|
||||
const openaiEntry = {
|
||||
id: resolvedModel,
|
||||
name: `${resolvedModel} (OmniRoute)`,
|
||||
envKey: "OPENAI_API_KEY",
|
||||
baseUrl: normalizedBaseUrl,
|
||||
generationConfig: {
|
||||
contextWindowSize: 200000,
|
||||
// Set security.auth for openai auth type with direct credentials
|
||||
// This takes priority over modelProviders entries (per Qwen docs)
|
||||
existingConfig.security = {
|
||||
...existingConfig.security,
|
||||
auth: {
|
||||
selectedType: "openai",
|
||||
apiKey: resolvedApiKey,
|
||||
baseUrl: normalizedBaseUrl,
|
||||
},
|
||||
};
|
||||
|
||||
if (!existingConfig.modelProviders.openai) existingConfig.modelProviders.openai = [];
|
||||
const openaiProviders = existingConfig.modelProviders.openai;
|
||||
const openaiIdx = openaiProviders.findIndex(
|
||||
(p: any) => p && (p.baseUrl === normalizedBaseUrl || p.id === "omniroute")
|
||||
);
|
||||
if (openaiIdx >= 0) {
|
||||
openaiProviders[openaiIdx] = openaiEntry;
|
||||
} else {
|
||||
openaiProviders.push(openaiEntry);
|
||||
}
|
||||
|
||||
// anthropic provider — for Claude models via OmniRoute
|
||||
const anthropicEntry = {
|
||||
id: "claude-sonnet-4-6",
|
||||
name: "Claude Sonnet 4.6 (OmniRoute)",
|
||||
envKey: "ANTHROPIC_API_KEY",
|
||||
baseUrl: normalizedBaseUrl,
|
||||
generationConfig: {
|
||||
contextWindowSize: 200000,
|
||||
},
|
||||
// Set model to the selected model
|
||||
existingConfig.model = {
|
||||
...existingConfig.model,
|
||||
name: resolvedModel,
|
||||
};
|
||||
|
||||
if (!existingConfig.modelProviders.anthropic) existingConfig.modelProviders.anthropic = [];
|
||||
const anthropicProviders = existingConfig.modelProviders.anthropic;
|
||||
const anthropicIdx = anthropicProviders.findIndex(
|
||||
(p: any) => p && p.baseUrl === normalizedBaseUrl
|
||||
);
|
||||
if (anthropicIdx >= 0) {
|
||||
anthropicProviders[anthropicIdx] = anthropicEntry;
|
||||
} else {
|
||||
anthropicProviders.push(anthropicEntry);
|
||||
}
|
||||
|
||||
// gemini provider — for Gemini models via OmniRoute
|
||||
const geminiEntry = {
|
||||
id: "gemini-3-flash",
|
||||
name: "Gemini 3 Flash (OmniRoute)",
|
||||
envKey: "GEMINI_API_KEY",
|
||||
baseUrl: normalizedBaseUrl,
|
||||
};
|
||||
|
||||
if (!existingConfig.modelProviders.gemini) existingConfig.modelProviders.gemini = [];
|
||||
const geminiProviders = existingConfig.modelProviders.gemini;
|
||||
const geminiIdx = geminiProviders.findIndex((p: any) => p && p.baseUrl === normalizedBaseUrl);
|
||||
if (geminiIdx >= 0) {
|
||||
geminiProviders[geminiIdx] = geminiEntry;
|
||||
} else {
|
||||
geminiProviders.push(geminiEntry);
|
||||
}
|
||||
|
||||
await fs.writeFile(configPath, JSON.stringify(existingConfig, null, 2), "utf-8");
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: `Qwen Code config saved to ${configPath} + ${envPath}`,
|
||||
message: `Qwen Code config saved to ${configPath}`,
|
||||
configPath,
|
||||
envPath,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -504,29 +504,17 @@ amp --model "{{model}}"
|
||||
],
|
||||
codeBlock: {
|
||||
language: "json",
|
||||
code: `# ~/.qwen/settings.json — OmniRoute as multi-provider
|
||||
code: `# ~/.qwen/settings.json — OmniRoute via security.auth
|
||||
{
|
||||
"modelProviders": {
|
||||
"openai": [{
|
||||
"id": "{{model}}",
|
||||
"name": "OmniRoute",
|
||||
"envKey": "OPENAI_API_KEY",
|
||||
"baseUrl": "{{baseUrl}}",
|
||||
"generationConfig": { "contextWindowSize": 200000 }
|
||||
}],
|
||||
"anthropic": [{
|
||||
"id": "claude-sonnet-4-6",
|
||||
"name": "Claude Sonnet 4.6",
|
||||
"envKey": "ANTHROPIC_API_KEY",
|
||||
"baseUrl": "{{baseUrl}}",
|
||||
"generationConfig": { "contextWindowSize": 200000 }
|
||||
}],
|
||||
"gemini": [{
|
||||
"id": "gemini-3-flash",
|
||||
"name": "Gemini 3 Flash",
|
||||
"envKey": "GEMINI_API_KEY",
|
||||
"security": {
|
||||
"auth": {
|
||||
"selectedType": "openai",
|
||||
"apiKey": "{{apiKey}}",
|
||||
"baseUrl": "{{baseUrl}}"
|
||||
}]
|
||||
}
|
||||
},
|
||||
"model": {
|
||||
"name": "{{model}}"
|
||||
}
|
||||
}`,
|
||||
},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { getApiKeyById } from "@/lib/db/apiKeys";
|
||||
import { getApiKeyById, createApiKey } from "@/lib/localDb";
|
||||
import { getConsistentMachineId } from "@/shared/utils/machineId";
|
||||
|
||||
export async function resolveApiKey(
|
||||
apiKeyId?: string | null,
|
||||
@@ -14,3 +15,30 @@ export async function resolveApiKey(
|
||||
}
|
||||
return apiKey || "sk_omniroute";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create a DB-backed API key for CLI tool setup.
|
||||
* Returns a valid OmniRoute API key (not a placeholder like "sk_omniroute").
|
||||
* Used when user has not explicitly selected a key from API Manager.
|
||||
*/
|
||||
export async function getOrCreateApiKey(apiKeyId?: string | null): Promise<string> {
|
||||
if (apiKeyId) {
|
||||
try {
|
||||
const keyRecord = await getApiKeyById(apiKeyId);
|
||||
if (keyRecord?.key) return keyRecord.key as string;
|
||||
} catch {
|
||||
/* fall through */
|
||||
}
|
||||
}
|
||||
|
||||
// No key found — auto-create one that will be valid in DB validation
|
||||
let machineId = "unknown";
|
||||
try {
|
||||
machineId = await getConsistentMachineId();
|
||||
const keyRecord = await createApiKey("CLI Auto-Key", machineId);
|
||||
return keyRecord.key as string;
|
||||
} catch {
|
||||
// Fallback: generate a deterministic key if DB write fails
|
||||
return `sk_${machineId}_fallback_${Date.now()}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ test("guide-settings POST creates new qwen settings.json if it doesn't exist", a
|
||||
const req = await buildRequest({
|
||||
baseUrl: "http://my-omni",
|
||||
apiKey: "sk-123",
|
||||
model: "qwen-max",
|
||||
model: "gemini-cli/gemini-3.1-pro-preview",
|
||||
});
|
||||
const response = (await guideSettingsRoute.POST(req, { params: { toolId: "qwen" } })) as Response;
|
||||
const data = (await response.json()) as any;
|
||||
@@ -75,21 +75,11 @@ test("guide-settings POST creates new qwen settings.json if it doesn't exist", a
|
||||
assert.equal(data.success, true);
|
||||
|
||||
const content = JSON.parse(await fs.readFile(QWEN_CONFIG_PATH, "utf-8"));
|
||||
assert.ok(content.modelProviders.openai);
|
||||
|
||||
const omniProvider = content.modelProviders.openai.find(
|
||||
(p: QwenProviderEntry) => p.baseUrl === "http://my-omni"
|
||||
);
|
||||
assert.ok(omniProvider);
|
||||
assert.equal(omniProvider.id, "qwen-max");
|
||||
assert.equal(omniProvider.baseUrl, "http://my-omni");
|
||||
assert.equal(omniProvider.envKey, "OPENAI_API_KEY");
|
||||
assert.equal(omniProvider.generationConfig?.contextWindowSize, 200000);
|
||||
|
||||
const envContent = await fs.readFile(QWEN_ENV_PATH, "utf-8");
|
||||
assert.match(envContent, /^OPENAI_API_KEY=sk-123$/m);
|
||||
assert.match(envContent, /^ANTHROPIC_API_KEY=sk-123$/m);
|
||||
assert.match(envContent, /^GEMINI_API_KEY=sk-123$/m);
|
||||
// Uses security.auth format (not modelProviders)
|
||||
assert.equal(content.security?.auth?.selectedType, "openai");
|
||||
assert.equal(content.security?.auth?.apiKey, "sk-123");
|
||||
assert.equal(content.security?.auth?.baseUrl, "http://my-omni");
|
||||
assert.equal(content.model?.name, "gemini-cli/gemini-3.1-pro-preview");
|
||||
});
|
||||
|
||||
test("guide-settings POST merges into existing qwen settings.json", async () => {
|
||||
@@ -97,38 +87,27 @@ test("guide-settings POST merges into existing qwen settings.json", async () =>
|
||||
await fs.writeFile(
|
||||
QWEN_CONFIG_PATH,
|
||||
JSON.stringify({
|
||||
modelProviders: {
|
||||
openai: [{ id: "other", baseUrl: "https://other" }],
|
||||
},
|
||||
permissions: { allow: ["Bash(*)"] },
|
||||
}),
|
||||
"utf-8"
|
||||
);
|
||||
|
||||
const req = await buildRequest({ baseUrl: "http://my-omni", apiKey: "sk-123", model: "auto" });
|
||||
const response = (await guideSettingsRoute.POST(req, { params: { toolId: "qwen" } })) as Response;
|
||||
const req = await buildRequest({
|
||||
baseUrl: "http://my-omni",
|
||||
apiKey: "sk-456",
|
||||
model: "claude-sonnet-4-6",
|
||||
});
|
||||
const response = await guideSettingsRoute.POST(req, { params: { toolId: "qwen" } });
|
||||
assert.equal(response.status, 200);
|
||||
|
||||
const content = JSON.parse(await fs.readFile(QWEN_CONFIG_PATH, "utf-8"));
|
||||
assert.equal(content.modelProviders.openai.length, 2);
|
||||
|
||||
const otherProvider = content.modelProviders.openai.find(
|
||||
(p: QwenProviderEntry) => p.id === "other"
|
||||
);
|
||||
assert.ok(otherProvider);
|
||||
assert.equal(otherProvider.baseUrl, "https://other");
|
||||
|
||||
const omniProvider = content.modelProviders.openai.find(
|
||||
(p: QwenProviderEntry) => p.baseUrl === "http://my-omni"
|
||||
);
|
||||
assert.ok(omniProvider);
|
||||
assert.equal(omniProvider.id, "auto");
|
||||
assert.equal(omniProvider.envKey, "OPENAI_API_KEY");
|
||||
assert.equal(omniProvider.generationConfig?.contextWindowSize, 200000);
|
||||
|
||||
const envContent = await fs.readFile(QWEN_ENV_PATH, "utf-8");
|
||||
assert.match(envContent, /^OPENAI_API_KEY=sk-123$/m);
|
||||
assert.match(envContent, /^ANTHROPIC_API_KEY=sk-123$/m);
|
||||
assert.match(envContent, /^GEMINI_API_KEY=sk-123$/m);
|
||||
// security.auth format
|
||||
assert.equal(content.security?.auth?.selectedType, "openai");
|
||||
assert.equal(content.security?.auth?.apiKey, "sk-456");
|
||||
assert.equal(content.security?.auth?.baseUrl, "http://my-omni");
|
||||
assert.equal(content.model?.name, "claude-sonnet-4-6");
|
||||
// Preserves other settings
|
||||
assert.deepEqual(content.permissions?.allow, ["Bash(*)"]);
|
||||
});
|
||||
|
||||
test("guide-settings POST writes OpenCode config with current schema and multi-model selection", async () => {
|
||||
|
||||
148
tests/unit/qwen-api-key-auto-create.test.ts
Normal file
148
tests/unit/qwen-api-key-auto-create.test.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs/promises";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
import { SignJWT } from "jose";
|
||||
import { getOrCreateApiKey, resolveApiKey } from "../../src/shared/services/apiKeyResolver";
|
||||
import { validateApiKey } from "../../src/lib/db/apiKeys";
|
||||
import { getDbInstance } from "../../src/lib/db/core";
|
||||
|
||||
const DUMMY_HOME = path.join(os.tmpdir(), "omniroute-qwen-key-test-" + Date.now());
|
||||
const originalJwtSecret = process.env.JWT_SECRET;
|
||||
|
||||
async function createAuthCookie() {
|
||||
process.env.JWT_SECRET = "test-cli-tools-secret";
|
||||
const secret = new TextEncoder().encode(process.env.JWT_SECRET);
|
||||
const token = await new SignJWT({ sub: "test-user" })
|
||||
.setProtectedHeader({ alg: "HS256" })
|
||||
.setIssuedAt()
|
||||
.setExpirationTime("1h")
|
||||
.sign(secret);
|
||||
return `auth_token=${token}`;
|
||||
}
|
||||
|
||||
test.beforeEach(async () => {
|
||||
process.env.DATA_DIR = DUMMY_HOME;
|
||||
await fs.mkdir(DUMMY_HOME, { recursive: true }).catch(() => {});
|
||||
// Initialize DB
|
||||
getDbInstance();
|
||||
});
|
||||
|
||||
test.afterEach(async () => {
|
||||
await fs.rm(DUMMY_HOME, { recursive: true, force: true }).catch(() => {});
|
||||
if (originalJwtSecret === undefined) delete process.env.JWT_SECRET;
|
||||
else process.env.JWT_SECRET = originalJwtSecret;
|
||||
if (process.env.DATA_DIR?.includes("omniroute-qwen-key-test")) {
|
||||
delete process.env.DATA_DIR;
|
||||
}
|
||||
});
|
||||
|
||||
test("getOrCreateApiKey() creates DB-backed key when no keyId provided", async () => {
|
||||
const apiKey = await getOrCreateApiKey(null);
|
||||
|
||||
// Key should NOT be the placeholder "sk_omniroute"
|
||||
assert.notEqual(apiKey, "sk_omniroute", "Should not return placeholder");
|
||||
assert.ok(apiKey.startsWith("sk-"), "Key should start with sk- prefix");
|
||||
|
||||
// Key should be valid in DB
|
||||
const valid = await validateApiKey(apiKey);
|
||||
assert.equal(valid, true, "Auto-created key should validate successfully");
|
||||
});
|
||||
|
||||
test("getOrCreateApiKey() returns existing key when keyId is provided", async () => {
|
||||
// First create a key with a specific keyId
|
||||
const firstKey = await getOrCreateApiKey(null);
|
||||
assert.ok(firstKey.startsWith("sk-"));
|
||||
|
||||
// Create another key and get its ID
|
||||
const secondKey = await getOrCreateApiKey(null);
|
||||
|
||||
// When we pass the same keyId, we should get the same key back
|
||||
// (This tests the keyId resolution path)
|
||||
const resolvedKey = await resolveApiKey(null, firstKey);
|
||||
assert.equal(resolvedKey, firstKey, "Should return same key when resolved");
|
||||
});
|
||||
|
||||
test("Qwen guide-settings POST creates valid DB-backed key (no keyId)", async () => {
|
||||
const guideSettingsRoute =
|
||||
await import("../../src/app/api/cli-tools/guide-settings/[toolId]/route.ts");
|
||||
|
||||
const cookie = await createAuthCookie();
|
||||
const req = new Request("http://localhost/api/cli-tools/guide-settings/qwen", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", cookie },
|
||||
body: JSON.stringify({
|
||||
baseUrl: "http://localhost:20128/v1",
|
||||
model: "qwen3-coder-flash",
|
||||
// No keyId provided - should auto-create
|
||||
}),
|
||||
});
|
||||
|
||||
const response = await guideSettingsRoute.POST(req, { params: { toolId: "qwen" } });
|
||||
assert.equal(response.status, 200, "Response should be OK");
|
||||
|
||||
// Verify settings.json was written with security.auth format and a valid DB-backed key
|
||||
const configPath = path.join(DUMMY_HOME, ".qwen", "settings.json");
|
||||
const content = JSON.parse(await fs.readFile(configPath, "utf-8"));
|
||||
|
||||
assert.equal(content.security?.auth?.selectedType, "openai", "Should use openai auth type");
|
||||
assert.ok(content.security?.auth?.apiKey, "Should have an API key");
|
||||
assert.equal(
|
||||
content.security?.auth?.baseUrl,
|
||||
"http://localhost:20128/v1",
|
||||
"Should have base URL"
|
||||
);
|
||||
assert.equal(content.model?.name, "qwen3-coder-flash", "Should have model name");
|
||||
|
||||
const apiKey = content.security.auth.apiKey;
|
||||
assert.notEqual(apiKey, "sk_omniroute", "Should not use placeholder");
|
||||
assert.ok(apiKey.startsWith("sk-"), "Key should start with sk- prefix");
|
||||
|
||||
// Verify the key is valid in DB
|
||||
const valid = await validateApiKey(apiKey);
|
||||
assert.equal(valid, true, "Auto-created key should validate in DB");
|
||||
});
|
||||
|
||||
test("Qwen guide-settings POST with keyId uses existing key", async () => {
|
||||
const guideSettingsRoute =
|
||||
await import("../../src/app/api/cli-tools/guide-settings/[toolId]/route.ts");
|
||||
|
||||
// Pre-create a key via getOrCreateApiKey
|
||||
const existingKey = await getOrCreateApiKey(null);
|
||||
const keyIdMatch = existingKey.match(/^sk-[^-]+-([^-]+)-/);
|
||||
assert.ok(keyIdMatch, "Key should have ID portion");
|
||||
|
||||
// Get key metadata to find the ID
|
||||
const db = getDbInstance();
|
||||
const stmt = db.prepare("SELECT id FROM api_keys WHERE `key` = ?");
|
||||
const row = stmt.get(existingKey) as { id: string } | undefined;
|
||||
assert.ok(row, "Key should exist in DB");
|
||||
|
||||
const cookie = await createAuthCookie();
|
||||
const req = new Request("http://localhost/api/cli-tools/guide-settings/qwen", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", cookie },
|
||||
body: JSON.stringify({
|
||||
baseUrl: "http://localhost:20128/v1",
|
||||
model: "qwen3-coder-plus",
|
||||
keyId: row.id,
|
||||
}),
|
||||
});
|
||||
|
||||
const response = await guideSettingsRoute.POST(req, { params: { toolId: "qwen" } });
|
||||
assert.equal(response.status, 200, "Response should be OK");
|
||||
|
||||
// Verify settings.json uses security.auth format with the existing key
|
||||
const configPath = path.join(DUMMY_HOME, ".qwen", "settings.json");
|
||||
const content = JSON.parse(await fs.readFile(configPath, "utf-8"));
|
||||
|
||||
assert.equal(content.security?.auth?.selectedType, "openai");
|
||||
assert.equal(
|
||||
content.security?.auth?.apiKey,
|
||||
existingKey,
|
||||
"Should use existing key when keyId provided"
|
||||
);
|
||||
assert.equal(content.security?.auth?.baseUrl, "http://localhost:20128/v1");
|
||||
assert.equal(content.model?.name, "qwen3-coder-plus");
|
||||
});
|
||||
Reference in New Issue
Block a user