fix litellm metadata discovery auth

This commit is contained in:
Daniel 2026-05-09 15:15:47 +02:00
parent 90cdf17bd9
commit 8d69fe57a5
5 changed files with 46 additions and 7 deletions

View file

@ -325,6 +325,11 @@
<button id="btn-test-assistant-image-model" class="btn-sm btn-primary" type="button"><i class="fas fa-image"></i> Test</button>
</div>
<div id="assistant-image-test-result" style="font-size:12px;color:var(--g500);"></div>
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:-6px;">
<label style="font-size:12px;font-weight:600;color:var(--g600);min-width:130px;">Custom image model</label>
<input id="assistant-custom-image-model" type="text" placeholder="e.g. openrouter-gpt-5-image" style="font-size:13px;padding:5px 8px;border:1px solid var(--g300);border-radius:6px;flex:1;max-width:420px;">
<button id="btn-use-custom-assistant-image-model" class="btn-sm btn-ghost" type="button"><i class="fas fa-plus"></i> Use Custom</button>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;">
<div>
<label style="font-size:12px;font-weight:600;color:var(--g600);display:block;margin-bottom:4px;">Search result limit</label>

View file

@ -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...';

View file

@ -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];

View file

@ -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
};

View file

@ -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');
});
});