From 8d69fe57a5082666e89737c75af7c9f7b4dc021f Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 9 May 2026 15:15:47 +0200 Subject: [PATCH] fix litellm metadata discovery auth --- public/components/admin.html | 5 +++++ public/js/admin.js | 17 +++++++++++++++++ src/routes/adminConfig.js | 10 +++++----- src/utils/litellm.js | 11 ++++++++++- test/tts-provider.test.js | 10 +++++++++- 5 files changed, 46 insertions(+), 7 deletions(-) diff --git a/public/components/admin.html b/public/components/admin.html index f104a43..0ef4245 100644 --- a/public/components/admin.html +++ b/public/components/admin.html @@ -325,6 +325,11 @@
+
+ + + +
diff --git a/public/js/admin.js b/public/js/admin.js index 95c6843..52a63c3 100644 --- a/public/js/admin.js +++ b/public/js/admin.js @@ -703,6 +703,7 @@ function adminFlashButtonBackground(btn, color) { if (e.target.closest('#btn-test-assistant-chat-model')) testAssistantChatModel(); if (e.target.closest('#btn-refresh-assistant-image-models')) loadAssistantImageModels(); if (e.target.closest('#btn-test-assistant-image-model')) testAssistantImageModel(); + if (e.target.closest('#btn-use-custom-assistant-image-model')) useCustomAssistantImageModel(); }); function loadAssistantAdmin() { @@ -826,6 +827,22 @@ function adminFlashButtonBackground(btn, color) { }); } + function useCustomAssistantImageModel() { + var input = document.getElementById('assistant-custom-image-model'); + var sel = document.getElementById('assistant-image-model'); + var model = input ? input.value.trim() : ''; + if (!model) { showToast('Enter an image model ID', 'error'); return; } + if (sel && !Array.prototype.some.call(sel.options, function(o) { return o.value === model; })) { + var opt = document.createElement('option'); + opt.value = model; + opt.textContent = model + ' (custom)'; + sel.appendChild(opt); + } + if (sel) sel.value = model; + window._assistantImageModelValue = model; + showToast('Custom image model selected. Save settings to keep it.', 'info'); + } + function saveAssistantAdmin() { var status = document.getElementById('assistant-admin-status'); if (status) status.textContent = 'Saving...'; diff --git a/src/routes/adminConfig.js b/src/routes/adminConfig.js index fadd260..8dc589b 100644 --- a/src/routes/adminConfig.js +++ b/src/routes/adminConfig.js @@ -10,7 +10,7 @@ var PROMPTS = require('../utils/prompts'); var logger = require('../utils/logger'); var { gatewayUrl } = require('../utils/errors'); var { getTTSEnvProvider, getLiteLLMTTSDiscoveryItems, getTTSProvider, getTTSVoiceLists } = require('../utils/ttsProvider'); -var { getLiteLLMHeaders } = require('../utils/litellm'); +var { getLiteLLMHeaders, getLiteLLMAdminHeaders } = require('../utils/litellm'); var { getSTTDependencies, getLiteLLMSTTModels, getSTTModelLists, getSTTProvider } = require('../utils/sttProvider'); var { getLiteLLMEmbeddingModels } = require('../utils/embeddings'); @@ -448,7 +448,7 @@ router.get('/config/image-models/discover', async function(req, res) { var axios = require('axios'); var discovered = []; if (process.env.LITELLM_API_BASE) { - var resp = await axios.get(liteLLMBaseUrl() + '/model/info', { headers: getLiteLLMHeaders(), timeout: 15000 }); + var resp = await axios.get(liteLLMBaseUrl() + '/model/info', { headers: getLiteLLMAdminHeaders(), timeout: 15000 }); var models = (resp.data && resp.data.data) || []; models.forEach(function(m) { if (liteLLMModelMode(m) === 'image_generation') { @@ -535,7 +535,7 @@ router.get('/config/tts/discover', async function(req, res) { var currentModel = dbModel || process.env.LITELLM_TTS_MODEL || ''; var modelInfo = []; try { - var lResp = await axios.get(liteLLMBaseUrl() + '/model/info', { headers: getLiteLLMHeaders(), timeout: 10000 }); + var lResp = await axios.get(liteLLMBaseUrl() + '/model/info', { headers: getLiteLLMAdminHeaders(), timeout: 10000 }); modelInfo = lResp.data && lResp.data.data ? lResp.data.data : []; } catch (e) { logger.warn('LiteLLM TTS model list failed: ' + e.message); } getLiteLLMTTSDiscoveryItems(modelInfo, { currentModel: currentModel, currentVoice: currentVoice }).forEach(function(item) { @@ -616,7 +616,7 @@ router.get('/config/stt/discover', async function(req, res) { if (provider === 'litellm' && process.env.LITELLM_API_BASE) { try { - var sResp = await axios.get(liteLLMBaseUrl() + '/model/info', { headers: getLiteLLMHeaders(), timeout: 10000 }); + var sResp = await axios.get(liteLLMBaseUrl() + '/model/info', { headers: getLiteLLMAdminHeaders(), timeout: 10000 }); getLiteLLMSTTModels(sResp.data && sResp.data.data).forEach(function(id) { discovered.push({ id: id, name: id, source: 'gateway-api' }); }); @@ -712,7 +712,7 @@ router.get('/config/embeddings/discover', async function(req, res) { if (provider === 'litellm') { try { - var eResp = await axios.get(liteLLMBaseUrl() + '/model/info', { headers: getLiteLLMHeaders(), timeout: 10000 }); + var eResp = await axios.get(liteLLMBaseUrl() + '/model/info', { headers: getLiteLLMAdminHeaders(), timeout: 10000 }); var embeddingModels = getLiteLLMEmbeddingModels(eResp.data && eResp.data.data); for (var i = 0; i < embeddingModels.length; i++) { var m = embeddingModels[i]; diff --git a/src/utils/litellm.js b/src/utils/litellm.js index 96b47ea..166e274 100644 --- a/src/utils/litellm.js +++ b/src/utils/litellm.js @@ -6,6 +6,15 @@ function getLiteLLMHeaders(contentType) { return headers; } +function getLiteLLMAdminHeaders(contentType) { + var headers = {}; + if (contentType) headers['Content-Type'] = contentType; + var key = process.env.LITELLM_MASTER_KEY || process.env.LITELLM_API_KEY; + if (key) headers.Authorization = 'Bearer ' + key; + return headers; +} + module.exports = { - getLiteLLMHeaders + getLiteLLMHeaders, + getLiteLLMAdminHeaders }; diff --git a/test/tts-provider.test.js b/test/tts-provider.test.js index 7e62ec2..5d5696b 100644 --- a/test/tts-provider.test.js +++ b/test/tts-provider.test.js @@ -1,7 +1,7 @@ const { test } = require('node:test'); const assert = require('node:assert/strict'); -const ENV_KEYS = ['TTS_PROVIDER', 'LITELLM_API_BASE', 'LITELLM_API_KEY', 'LITELLM_TTS_VOICES']; +const ENV_KEYS = ['TTS_PROVIDER', 'LITELLM_API_BASE', 'LITELLM_API_KEY', 'LITELLM_MASTER_KEY', 'LITELLM_TTS_VOICES']; function withEnv(overrides, fn) { const previous = {}; @@ -112,3 +112,11 @@ test('LiteLLM headers include optional content type without exposing key value', }); }); }); + +test('LiteLLM admin headers prefer master key for metadata routes', () => { + const { getLiteLLMHeaders, getLiteLLMAdminHeaders } = require('../src/utils/litellm'); + withEnv({ LITELLM_API_KEY: 'virtual-token', LITELLM_MASTER_KEY: 'master-token' }, () => { + assert.equal(getLiteLLMHeaders().Authorization, 'Bearer virtual-token'); + assert.equal(getLiteLLMAdminHeaders().Authorization, 'Bearer master-token'); + }); +});