From ff1ca1412a0a39157727a476eb67af2515d27563 Mon Sep 17 00:00:00 2001 From: lunar sheep <102860538+dylenyedc@users.noreply.github.com> Date: Tue, 31 Mar 2026 03:30:45 +0800 Subject: [PATCH] feat(secrets): update readSecret function to accept optional secret ID (#5356) * feat(secrets): update readSecret function to accept optional secret ID * add secret_id to ConnectionManagerRequestService payload * fix: pass secret_id for Text Completion types --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com> --- public/scripts/extensions/shared.js | 2 + src/additional-headers.js | 79 ++++++++++++-------- src/endpoints/backends/chat-completions.js | 86 +++++++++++----------- src/endpoints/secrets.js | 5 +- 4 files changed, 95 insertions(+), 77 deletions(-) diff --git a/public/scripts/extensions/shared.js b/public/scripts/extensions/shared.js index 420495161..dc8491afa 100644 --- a/public/scripts/extensions/shared.js +++ b/public/scripts/extensions/shared.js @@ -435,6 +435,7 @@ export class ConnectionManagerRequestService { max_tokens: maxTokens, model: profile.model, chat_completion_source: selectedApiMap.source, + secret_id: profile['secret-id'], custom_url: profile['api-url'], vertexai_region: profile['api-url'], zai_endpoint: profile['api-url'], @@ -459,6 +460,7 @@ export class ConnectionManagerRequestService { model: profile.model, api_type: selectedApiMap.type, api_server: profile['api-url'], + secret_id: profile['secret-id'], ...overridePayload, }, { instructName: includeInstruct ? profile.instruct : undefined, diff --git a/src/additional-headers.js b/src/additional-headers.js index 1ea5d035f..8914435ea 100644 --- a/src/additional-headers.js +++ b/src/additional-headers.js @@ -5,10 +5,11 @@ import { getConfigValue } from './util.js'; /** * Gets the headers for the Mancer API. * @param {import('./users.js').UserDirectoryList} directories User directories + * @param {string|null} secretId Secret ID for the request (optional, used to determine which secret to use) * @returns {object} Headers for the request */ -function getMancerHeaders(directories) { - const apiKey = readSecret(directories, SECRET_KEYS.MANCER); +function getMancerHeaders(directories, secretId = null) { + const apiKey = readSecret(directories, SECRET_KEYS.MANCER, secretId); return apiKey ? ({ 'X-API-KEY': apiKey, @@ -19,10 +20,11 @@ function getMancerHeaders(directories) { /** * Gets the headers for the TogetherAI API. * @param {import('./users.js').UserDirectoryList} directories User directories + * @param {string|null} secretId Secret ID for the request (optional, used to determine which secret to use) * @returns {object} Headers for the request */ -function getTogetherAIHeaders(directories) { - const apiKey = readSecret(directories, SECRET_KEYS.TOGETHERAI); +function getTogetherAIHeaders(directories, secretId = null) { + const apiKey = readSecret(directories, SECRET_KEYS.TOGETHERAI, secretId); return apiKey ? ({ 'Authorization': `Bearer ${apiKey}`, @@ -32,10 +34,11 @@ function getTogetherAIHeaders(directories) { /** * Gets the headers for the InfermaticAI API. * @param {import('./users.js').UserDirectoryList} directories User directories + * @param {string|null} secretId Secret ID for the request (optional, used to determine which secret to use) * @returns {object} Headers for the request */ -function getInfermaticAIHeaders(directories) { - const apiKey = readSecret(directories, SECRET_KEYS.INFERMATICAI); +function getInfermaticAIHeaders(directories, secretId = null) { + const apiKey = readSecret(directories, SECRET_KEYS.INFERMATICAI, secretId); return apiKey ? ({ 'Authorization': `Bearer ${apiKey}`, @@ -45,10 +48,11 @@ function getInfermaticAIHeaders(directories) { /** * Gets the headers for the DreamGen API. * @param {import('./users.js').UserDirectoryList} directories User directories + * @param {string|null} secretId Secret ID for the request (optional, used to determine which secret to use) * @returns {object} Headers for the request */ -function getDreamGenHeaders(directories) { - const apiKey = readSecret(directories, SECRET_KEYS.DREAMGEN); +function getDreamGenHeaders(directories, secretId = null) { + const apiKey = readSecret(directories, SECRET_KEYS.DREAMGEN, secretId); return apiKey ? ({ 'Authorization': `Bearer ${apiKey}`, @@ -58,10 +62,11 @@ function getDreamGenHeaders(directories) { /** * Gets the headers for the OpenRouter API. * @param {import('./users.js').UserDirectoryList} directories User directories + * @param {string|null} secretId Secret ID for the request (optional, used to determine which secret to use) * @returns {object} Headers for the request */ -function getOpenRouterHeaders(directories) { - const apiKey = readSecret(directories, SECRET_KEYS.OPENROUTER); +function getOpenRouterHeaders(directories, secretId = null) { + const apiKey = readSecret(directories, SECRET_KEYS.OPENROUTER, secretId); const baseHeaders = { ...OPENROUTER_HEADERS }; return apiKey ? Object.assign(baseHeaders, { 'Authorization': `Bearer ${apiKey}` }) : baseHeaders; @@ -70,10 +75,11 @@ function getOpenRouterHeaders(directories) { /** * Gets the headers for the vLLM API. * @param {import('./users.js').UserDirectoryList} directories User directories + * @param {string|null} secretId Secret ID for the request (optional, used to determine which secret to use) * @returns {object} Headers for the request */ -function getVllmHeaders(directories) { - const apiKey = readSecret(directories, SECRET_KEYS.VLLM); +function getVllmHeaders(directories, secretId = null) { + const apiKey = readSecret(directories, SECRET_KEYS.VLLM, secretId); return apiKey ? ({ 'Authorization': `Bearer ${apiKey}`, @@ -83,10 +89,11 @@ function getVllmHeaders(directories) { /** * Gets the headers for the Aphrodite API. * @param {import('./users.js').UserDirectoryList} directories User directories + * @param {string|null} secretId Secret ID for the request (optional, used to determine which secret to use) * @returns {object} Headers for the request */ -function getAphroditeHeaders(directories) { - const apiKey = readSecret(directories, SECRET_KEYS.APHRODITE); +function getAphroditeHeaders(directories, secretId = null) { + const apiKey = readSecret(directories, SECRET_KEYS.APHRODITE, secretId); return apiKey ? ({ 'X-API-KEY': apiKey, @@ -97,10 +104,11 @@ function getAphroditeHeaders(directories) { /** * Gets the headers for the Tabby API. * @param {import('./users.js').UserDirectoryList} directories User directories + * @param {string|null} secretId Secret ID for the request (optional, used to determine which secret to use) * @returns {object} Headers for the request */ -function getTabbyHeaders(directories) { - const apiKey = readSecret(directories, SECRET_KEYS.TABBY); +function getTabbyHeaders(directories, secretId = null) { + const apiKey = readSecret(directories, SECRET_KEYS.TABBY, secretId); return apiKey ? ({ 'x-api-key': apiKey, @@ -111,10 +119,11 @@ function getTabbyHeaders(directories) { /** * Gets the headers for the LlamaCPP API. * @param {import('./users.js').UserDirectoryList} directories User directories + * @param {string|null} secretId Secret ID for the request (optional, used to determine which secret to use) * @returns {object} Headers for the request */ -function getLlamaCppHeaders(directories) { - const apiKey = readSecret(directories, SECRET_KEYS.LLAMACPP); +function getLlamaCppHeaders(directories, secretId = null) { + const apiKey = readSecret(directories, SECRET_KEYS.LLAMACPP, secretId); return apiKey ? ({ 'Authorization': `Bearer ${apiKey}`, @@ -123,11 +132,12 @@ function getLlamaCppHeaders(directories) { /** * Gets the headers for the Ooba API. - * @param {import('./users.js').UserDirectoryList} directories + * @param {import('./users.js').UserDirectoryList} directories User directories + * @param {string|null} secretId Secret ID for the request (optional, used to determine which secret to use) * @returns {object} Headers for the request */ -function getOobaHeaders(directories) { - const apiKey = readSecret(directories, SECRET_KEYS.OOBA); +function getOobaHeaders(directories, secretId = null) { + const apiKey = readSecret(directories, SECRET_KEYS.OOBA, secretId); return apiKey ? ({ 'Authorization': `Bearer ${apiKey}`, @@ -137,10 +147,11 @@ function getOobaHeaders(directories) { /** * Gets the headers for the KoboldCpp API. * @param {import('./users.js').UserDirectoryList} directories + * @param {string|null} secretId Secret ID for the request (optional, used to determine which secret to use) * @returns {object} Headers for the request */ -function getKoboldCppHeaders(directories) { - const apiKey = readSecret(directories, SECRET_KEYS.KOBOLDCPP); +function getKoboldCppHeaders(directories, secretId = null) { + const apiKey = readSecret(directories, SECRET_KEYS.KOBOLDCPP, secretId); return apiKey ? ({ 'Authorization': `Bearer ${apiKey}`, @@ -150,10 +161,11 @@ function getKoboldCppHeaders(directories) { /** * Gets the headers for the Featherless API. * @param {import('./users.js').UserDirectoryList} directories + * @param {string|null} secretId Secret ID for the request (optional, used to determine which secret to use) * @returns {object} Headers for the request */ -function getFeatherlessHeaders(directories) { - const apiKey = readSecret(directories, SECRET_KEYS.FEATHERLESS); +function getFeatherlessHeaders(directories, secretId = null) { + const apiKey = readSecret(directories, SECRET_KEYS.FEATHERLESS, secretId); const baseHeaders = { ...FEATHERLESS_HEADERS }; return apiKey ? Object.assign(baseHeaders, { 'Authorization': `Bearer ${apiKey}` }) : baseHeaders; @@ -162,10 +174,11 @@ function getFeatherlessHeaders(directories) { /** * Gets the headers for the HuggingFace API. * @param {import('./users.js').UserDirectoryList} directories + * @param {string|null} secretId Secret ID for the request (optional, used to determine which secret to use) * @returns {object} Headers for the request */ -function getHuggingFaceHeaders(directories) { - const apiKey = readSecret(directories, SECRET_KEYS.HUGGINGFACE); +function getHuggingFaceHeaders(directories, secretId = null) { + const apiKey = readSecret(directories, SECRET_KEYS.HUGGINGFACE, secretId); return apiKey ? ({ 'Authorization': `Bearer ${apiKey}`, @@ -175,10 +188,11 @@ function getHuggingFaceHeaders(directories) { /** * Gets the headers for the Generic text completion API. * @param {import('./users.js').UserDirectoryList} directories + * @param {string|null} secretId Secret ID for the request (optional, used to determine which secret to use) * @returns {object} Headers for the request */ -function getGenericHeaders(directories) { - const apiKey = readSecret(directories, SECRET_KEYS.GENERIC); +function getGenericHeaders(directories, secretId = null) { + const apiKey = readSecret(directories, SECRET_KEYS.GENERIC, secretId); return apiKey ? ({ 'Authorization': `Bearer ${apiKey}`, @@ -202,7 +216,7 @@ export function getOverrideHeaders(urlHost) { * @param {string|null} server API server for new request */ export function setAdditionalHeaders(request, args, server) { - setAdditionalHeadersByType(args.headers, request.body.api_type, server, request.user.directories); + setAdditionalHeadersByType(args.headers, request.body.api_type, server, request.user.directories, request.body.secret_id); } /** @@ -211,8 +225,9 @@ export function setAdditionalHeaders(request, args, server) { * @param {string} type API type * @param {string|null} server API server for new request * @param {import('./users.js').UserDirectoryList} directories User directories + * @param {string|null} secretId Secret ID for the request (optional, used for some API types to determine which secret to use) */ -export function setAdditionalHeadersByType(requestHeaders, type, server, directories) { +export function setAdditionalHeadersByType(requestHeaders, type, server, directories, secretId = null) { const headerGetters = { [TEXTGEN_TYPES.MANCER]: getMancerHeaders, [TEXTGEN_TYPES.VLLM]: getVllmHeaders, @@ -231,7 +246,7 @@ export function setAdditionalHeadersByType(requestHeaders, type, server, directo }; const getHeaders = headerGetters[type]; - const headers = getHeaders ? getHeaders(directories) : {}; + const headers = getHeaders ? getHeaders(directories, secretId) : {}; if (typeof server === 'string' && server.length > 0) { try { diff --git a/src/endpoints/backends/chat-completions.js b/src/endpoints/backends/chat-completions.js index d294a3c1f..a39974e16 100644 --- a/src/endpoints/backends/chat-completions.js +++ b/src/endpoints/backends/chat-completions.js @@ -208,7 +208,7 @@ function setJsonObjectFormat(bodyParams, messages, jsonSchema) { */ async function sendClaudeRequest(request, response) { const apiUrl = new URL(request.body.reverse_proxy || API_CLAUDE).toString(); - const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.CLAUDE); + const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.CLAUDE, request.body.secret_id); const divider = '-'.repeat(process.stdout.columns); if (!apiKey) { @@ -425,7 +425,7 @@ async function sendMakerSuiteRequest(request, response) { } } else { apiUrl = new URL(request.body.reverse_proxy || API_MAKERSUITE); - apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.MAKERSUITE); + apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.MAKERSUITE, request.body.secret_id); if (!request.body.reverse_proxy && !apiKey) { console.warn(`${apiName} API key is missing.`); @@ -641,7 +641,7 @@ async function sendMakerSuiteRequest(request, response) { } else if (authType === 'full') { // For Full mode (service account authentication), use project-specific URL // Get project ID from Service Account JSON - const serviceAccountJson = readSecret(request.user.directories, SECRET_KEYS.VERTEXAI_SERVICE_ACCOUNT); + const serviceAccountJson = readSecret(request.user.directories, SECRET_KEYS.VERTEXAI_SERVICE_ACCOUNT, request.body.secret_id); if (!serviceAccountJson) { console.warn('Vertex AI Service Account JSON is missing.'); return response.status(400).send({ error: true }); @@ -742,7 +742,7 @@ async function sendMakerSuiteRequest(request, response) { async function sendAI21Request(request, response) { if (!request.body) return response.sendStatus(400); - const apiKey = readSecret(request.user.directories, SECRET_KEYS.AI21); + const apiKey = readSecret(request.user.directories, SECRET_KEYS.AI21, request.body.secret_id); if (!apiKey) { console.warn('AI21 API key is missing.'); return response.status(400).send({ error: true }); @@ -822,7 +822,7 @@ async function sendAI21Request(request, response) { */ async function sendMistralAIRequest(request, response) { const apiUrl = new URL(request.body.reverse_proxy || API_MISTRAL).toString(); - const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.MISTRALAI); + const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.MISTRALAI, request.body.secret_id); if (!apiKey) { console.warn('MistralAI API key is missing.'); @@ -911,7 +911,7 @@ async function sendMistralAIRequest(request, response) { * @param {express.Response} response Express response */ async function sendCohereRequest(request, response) { - const apiKey = readSecret(request.user.directories, SECRET_KEYS.COHERE); + const apiKey = readSecret(request.user.directories, SECRET_KEYS.COHERE, request.body.secret_id); const controller = new AbortController(); request.socket.removeAllListeners('close'); request.socket.on('close', function () { @@ -1012,7 +1012,7 @@ async function sendCohereRequest(request, response) { */ async function sendDeepSeekRequest(request, response) { const apiUrl = new URL(request.body.reverse_proxy || API_DEEPSEEK).toString(); - const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.DEEPSEEK); + const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.DEEPSEEK, request.body.secret_id); if (!apiKey && !request.body.reverse_proxy) { console.warn('DeepSeek API key is missing.'); @@ -1122,7 +1122,7 @@ async function sendDeepSeekRequest(request, response) { */ async function sendXaiRequest(request, response) { const apiUrl = new URL(request.body.reverse_proxy || API_XAI).toString(); - const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.XAI); + const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.XAI, request.body.secret_id); if (!apiKey && !request.body.reverse_proxy) { console.warn('xAI API key is missing.'); @@ -1228,7 +1228,7 @@ async function sendXaiRequest(request, response) { */ async function sendAimlapiRequest(request, response) { const apiUrl = API_AIMLAPI; - const apiKey = readSecret(request.user.directories, SECRET_KEYS.AIMLAPI); + const apiKey = readSecret(request.user.directories, SECRET_KEYS.AIMLAPI, request.body.secret_id); if (!apiKey) { console.warn('AI/ML API key is missing.'); @@ -1333,7 +1333,7 @@ async function sendAimlapiRequest(request, response) { */ async function sendElectronHubRequest(request, response) { const apiUrl = API_ELECTRONHUB; - const apiKey = readSecret(request.user.directories, SECRET_KEYS.ELECTRONHUB); + const apiKey = readSecret(request.user.directories, SECRET_KEYS.ELECTRONHUB, request.body.secret_id); if (!apiKey) { console.warn('Electron Hub key is missing.'); @@ -1445,7 +1445,7 @@ async function sendElectronHubRequest(request, response) { */ async function sendChutesRequest(request, response) { const apiUrl = API_CHUTES; - const apiKey = readSecret(request.user.directories, SECRET_KEYS.CHUTES); + const apiKey = readSecret(request.user.directories, SECRET_KEYS.CHUTES, request.body.secret_id); if (!apiKey) { console.warn('Chutes key is missing.'); @@ -1547,7 +1547,7 @@ async function sendChutesRequest(request, response) { async function sendAzureOpenAIRequest(request, response) { // 1. GATHER & VALIDATE SETTINGS const { azure_base_url, azure_deployment_name, azure_api_version } = request.body; - const apiKey = readSecret(request.user.directories, SECRET_KEYS.AZURE_OPENAI); + const apiKey = readSecret(request.user.directories, SECRET_KEYS.AZURE_OPENAI, request.body.secret_id); if (!azure_base_url || !azure_deployment_name || !azure_api_version || !apiKey) { return response.status(400).send({ error: { @@ -1646,74 +1646,74 @@ router.post('/status', async function (request, statusResponse) { if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.OPENAI) { apiUrl = new URL(request.body.reverse_proxy || API_OPENAI).toString(); - apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.OPENAI); + apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.OPENAI, request.body.secret_id); headers = {}; } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.OPENROUTER) { apiUrl = 'https://openrouter.ai/api/v1'; - apiKey = readSecret(request.user.directories, SECRET_KEYS.OPENROUTER); + apiKey = readSecret(request.user.directories, SECRET_KEYS.OPENROUTER, request.body.secret_id); // OpenRouter needs to pass the Referer and X-Title: https://openrouter.ai/docs#requests headers = { ...OPENROUTER_HEADERS }; } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.MISTRALAI) { apiUrl = new URL(request.body.reverse_proxy || API_MISTRAL).toString(); - apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.MISTRALAI); + apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.MISTRALAI, request.body.secret_id); headers = {}; } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.CUSTOM) { apiUrl = request.body.custom_url; - apiKey = readSecret(request.user.directories, SECRET_KEYS.CUSTOM); + apiKey = readSecret(request.user.directories, SECRET_KEYS.CUSTOM, request.body.secret_id); headers = {}; mergeObjectWithYaml(headers, request.body.custom_include_headers); } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.COHERE) { apiUrl = API_COHERE_V1; - apiKey = readSecret(request.user.directories, SECRET_KEYS.COHERE); + apiKey = readSecret(request.user.directories, SECRET_KEYS.COHERE, request.body.secret_id); headers = {}; } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.CHUTES) { apiUrl = API_CHUTES; - apiKey = readSecret(request.user.directories, SECRET_KEYS.CHUTES); + apiKey = readSecret(request.user.directories, SECRET_KEYS.CHUTES, request.body.secret_id); headers = {}; } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.ELECTRONHUB) { apiUrl = API_ELECTRONHUB; - apiKey = readSecret(request.user.directories, SECRET_KEYS.ELECTRONHUB); + apiKey = readSecret(request.user.directories, SECRET_KEYS.ELECTRONHUB, request.body.secret_id); headers = {}; } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.NANOGPT) { apiUrl = API_NANOGPT; - apiKey = readSecret(request.user.directories, SECRET_KEYS.NANOGPT); + apiKey = readSecret(request.user.directories, SECRET_KEYS.NANOGPT, request.body.secret_id); headers = {}; queryParams = { detailed: true }; } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.DEEPSEEK) { apiUrl = new URL(request.body.reverse_proxy || API_DEEPSEEK.replace('/beta', '')).toString(); - apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.DEEPSEEK); + apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.DEEPSEEK, request.body.secret_id); headers = {}; } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.XAI) { apiUrl = new URL(request.body.reverse_proxy || API_XAI).toString(); - apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.XAI); + apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.XAI, request.body.secret_id); headers = {}; } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.AIMLAPI) { apiUrl = API_AIMLAPI; - apiKey = readSecret(request.user.directories, SECRET_KEYS.AIMLAPI); + apiKey = readSecret(request.user.directories, SECRET_KEYS.AIMLAPI, request.body.secret_id); headers = { ...AIMLAPI_HEADERS }; } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.POLLINATIONS) { apiUrl = 'https://gen.pollinations.ai/text'; - apiKey = readSecret(request.user.directories, SECRET_KEYS.POLLINATIONS); + apiKey = readSecret(request.user.directories, SECRET_KEYS.POLLINATIONS, request.body.secret_id); headers = {}; } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.GROQ) { apiUrl = API_GROQ; - apiKey = readSecret(request.user.directories, SECRET_KEYS.GROQ); + apiKey = readSecret(request.user.directories, SECRET_KEYS.GROQ, request.body.secret_id); headers = {}; } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.COMETAPI) { apiUrl = API_COMETAPI; - apiKey = readSecret(request.user.directories, SECRET_KEYS.COMETAPI); + apiKey = readSecret(request.user.directories, SECRET_KEYS.COMETAPI, request.body.secret_id); headers = {}; throw new Error('This provider is temporarily disabled.'); } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.MOONSHOT) { apiUrl = new URL(request.body.reverse_proxy || API_MOONSHOT).toString(); - apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.MOONSHOT); + apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.MOONSHOT, request.body.secret_id); headers = {}; } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.FIREWORKS) { apiUrl = API_FIREWORKS; - apiKey = readSecret(request.user.directories, SECRET_KEYS.FIREWORKS); + apiKey = readSecret(request.user.directories, SECRET_KEYS.FIREWORKS, request.body.secret_id); headers = {}; } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.MAKERSUITE) { - apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.MAKERSUITE); + apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.MAKERSUITE, request.body.secret_id); apiUrl = trimTrailingSlash(request.body.reverse_proxy || API_MAKERSUITE); const apiVersion = getConfigValue('gemini.apiVersion', 'v1beta'); const modelsUrl = !apiKey && request.body.reverse_proxy @@ -1750,7 +1750,7 @@ router.post('/status', async function (request, statusResponse) { } } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.AZURE_OPENAI) { const { azure_base_url, azure_deployment_name, azure_api_version } = request.body; - const apiKey = readSecret(request.user.directories, SECRET_KEYS.AZURE_OPENAI); + const apiKey = readSecret(request.user.directories, SECRET_KEYS.AZURE_OPENAI, request.body.secret_id); // 1) Validate configuration from the frontend if (!apiKey || !azure_base_url || !azure_deployment_name || !azure_api_version) { @@ -1830,7 +1830,7 @@ router.post('/status', async function (request, statusResponse) { const defaultApiUrl = request.body.siliconflow_endpoint === SILICONFLOW_ENDPOINT.CN ? API_SILICONFLOW_CN : API_SILICONFLOW; apiUrl = defaultApiUrl; - apiKey = readSecret(request.user.directories, SECRET_KEYS.SILICONFLOW); + apiKey = readSecret(request.user.directories, SECRET_KEYS.SILICONFLOW, request.body.secret_id); headers = {}; queryParams = { type: 'text', sub_type: 'chat' }; } else { @@ -2053,7 +2053,7 @@ router.post('/generate', async function (request, response) { if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.OPENAI) { apiUrl = new URL(request.body.reverse_proxy || API_OPENAI).toString(); - apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.OPENAI); + apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.OPENAI, request.body.secret_id); headers = {}; bodyParams = { logprobs: request.body.logprobs, @@ -2073,7 +2073,7 @@ router.post('/generate', async function (request, response) { embedOpenRouterMedia(request.body.messages, { audio: true, video: false }); } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.OPENROUTER) { apiUrl = 'https://openrouter.ai/api/v1'; - apiKey = readSecret(request.user.directories, SECRET_KEYS.OPENROUTER); + apiKey = readSecret(request.user.directories, SECRET_KEYS.OPENROUTER, request.body.secret_id); // OpenRouter needs to pass the Referer and X-Title: https://openrouter.ai/docs#requests headers = { ...OPENROUTER_HEADERS }; const includeReasoning = Boolean(request.body.include_reasoning); @@ -2161,7 +2161,7 @@ router.post('/generate', async function (request, response) { } } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.CUSTOM) { apiUrl = request.body.custom_url; - apiKey = readSecret(request.user.directories, SECRET_KEYS.CUSTOM); + apiKey = readSecret(request.user.directories, SECRET_KEYS.CUSTOM, request.body.secret_id); headers = {}; bodyParams = { logprobs: request.body.logprobs, @@ -2179,7 +2179,7 @@ router.post('/generate', async function (request, response) { embedOpenRouterMedia(request.body.messages, { audio: true, video: false }); } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.PERPLEXITY) { apiUrl = API_PERPLEXITY; - apiKey = readSecret(request.user.directories, SECRET_KEYS.PERPLEXITY); + apiKey = readSecret(request.user.directories, SECRET_KEYS.PERPLEXITY, request.body.secret_id); headers = {}; bodyParams = { reasoning_effort: request.body.reasoning_effort, @@ -2195,7 +2195,7 @@ router.post('/generate', async function (request, response) { } } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.GROQ) { apiUrl = API_GROQ; - apiKey = readSecret(request.user.directories, SECRET_KEYS.GROQ); + apiKey = readSecret(request.user.directories, SECRET_KEYS.GROQ, request.body.secret_id); headers = {}; bodyParams = {}; if (request.body.json_schema) { @@ -2211,7 +2211,7 @@ router.post('/generate', async function (request, response) { } } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.FIREWORKS) { apiUrl = API_FIREWORKS; - apiKey = readSecret(request.user.directories, SECRET_KEYS.FIREWORKS); + apiKey = readSecret(request.user.directories, SECRET_KEYS.FIREWORKS, request.body.secret_id); headers = {}; bodyParams = {}; if (request.body.json_schema) { @@ -2227,7 +2227,7 @@ router.post('/generate', async function (request, response) { } } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.NANOGPT) { apiUrl = API_NANOGPT; - apiKey = readSecret(request.user.directories, SECRET_KEYS.NANOGPT); + apiKey = readSecret(request.user.directories, SECRET_KEYS.NANOGPT, request.body.secret_id); headers = {}; bodyParams = {}; if (request.body.enable_web_search && !/:online$/.test(request.body.model)) { @@ -2256,7 +2256,7 @@ router.post('/generate', async function (request, response) { } } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.POLLINATIONS) { apiUrl = API_POLLINATIONS; - apiKey = readSecret(request.user.directories, SECRET_KEYS.POLLINATIONS); + apiKey = readSecret(request.user.directories, SECRET_KEYS.POLLINATIONS, request.body.secret_id); headers = {}; bodyParams = { reasoning_effort: request.body.reasoning_effort, @@ -2272,7 +2272,7 @@ router.post('/generate', async function (request, response) { } } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.MOONSHOT) { apiUrl = new URL(request.body.reverse_proxy || API_MOONSHOT).toString(); - apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.MOONSHOT); + apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.MOONSHOT, request.body.secret_id); headers = {}; bodyParams = { thinking: { @@ -2284,7 +2284,7 @@ router.post('/generate', async function (request, response) { : addAssistantPrefix(request.body.messages, [], 'partial'); } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.COMETAPI) { apiUrl = API_COMETAPI; - apiKey = readSecret(request.user.directories, SECRET_KEYS.COMETAPI); + apiKey = readSecret(request.user.directories, SECRET_KEYS.COMETAPI, request.body.secret_id); headers = {}; bodyParams = { reasoning_effort: request.body.reasoning_effort, @@ -2293,7 +2293,7 @@ router.post('/generate', async function (request, response) { } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.ZAI) { const defaultApiUrl = request.body.zai_endpoint === ZAI_ENDPOINT.CODING ? API_ZAI_CODING : API_ZAI_COMMON; apiUrl = new URL(request.body.reverse_proxy || defaultApiUrl).toString(); - apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.ZAI); + apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.ZAI, request.body.secret_id); headers = { 'Accept-Language': 'en-US,en', }; @@ -2309,7 +2309,7 @@ router.post('/generate', async function (request, response) { const defaultApiUrl = request.body.siliconflow_endpoint === SILICONFLOW_ENDPOINT.CN ? API_SILICONFLOW_CN : API_SILICONFLOW; apiUrl = defaultApiUrl; - apiKey = readSecret(request.user.directories, SECRET_KEYS.SILICONFLOW); + apiKey = readSecret(request.user.directories, SECRET_KEYS.SILICONFLOW, request.body.secret_id); headers = {}; bodyParams = {}; if (request.body.json_schema) { diff --git a/src/endpoints/secrets.js b/src/endpoints/secrets.js index 35e1c57bc..d909d639b 100644 --- a/src/endpoints/secrets.js +++ b/src/endpoints/secrets.js @@ -441,10 +441,11 @@ export function deleteSecret(directories, key) { * Reads a secret from the secrets file * @param {import('../users.js').UserDirectoryList} directories User directories * @param {string} key Secret key + * @param {string?} id Secret ID (optional) * @returns {string} Secret value */ -export function readSecret(directories, key) { - return new SecretManager(directories).readSecret(key, null); +export function readSecret(directories, key, id = null) { + return new SecretManager(directories).readSecret(key, id); } /**