Security - nodemailer 9.0.1 -> 9.1.1, clearing four high advisories, two of which are delivery bugs that matter for an app that sends mail: recipient-domain validation bypass via RFC 5322 comments, and an IDN/punycode allow-list bypass, both of which can route mail to an attacker-controlled domain. Live transcription - The Settings picker was a hardcoded list of six ids (local-whisper-*, local-parakeet-v3, gemini-*). None of them resolve on this gateway, and /api/transcribe prefers the user's choice over the admin default, so picking one broke every recording with "Invalid model name". Verified against the live gateway: local-whisper-large-v3-turbo -> 400. - The picker now lists what /model/info advertises as audio_transcription, cached for five minutes, with the built-in list kept only as a fallback and the admin default marked. - The pipeline itself is healthy: local-kokoro-tts produced 92KB of speech and mistral-voxtral-mini-transcribe returned the sentence back verbatim. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
70 lines
2.4 KiB
JavaScript
70 lines
2.4 KiB
JavaScript
const LITELLM_STT_MODELS = ['local-whisper-large-v3-turbo', 'local-whisper-large-v3', 'local-parakeet-v3', 'local-whisper-1', 'gemini-2.0-flash', 'gemini-2.5-flash'];
|
|
|
|
function getSTTDependencies() {
|
|
var { litellmClient } = require('./ai');
|
|
return {
|
|
litellm: !!litellmClient
|
|
};
|
|
}
|
|
|
|
function getSTTProvider(deps) {
|
|
var env = process.env.TRANSCRIBE_PROVIDER || 'auto';
|
|
if (env === 'litellm') return 'litellm';
|
|
|
|
var configured = deps || getSTTDependencies();
|
|
if (configured.litellm) return 'litellm';
|
|
return 'none';
|
|
}
|
|
|
|
function getSTTModelLists() {
|
|
return {
|
|
litellm: LITELLM_STT_MODELS
|
|
};
|
|
}
|
|
|
|
function isLiteLLMSTTModel(model) {
|
|
var mode = model && model.model_info && model.model_info.mode ? String(model.model_info.mode) : '';
|
|
return mode === 'audio_transcription';
|
|
}
|
|
|
|
function getLiteLLMSTTModels(models) {
|
|
return (models || [])
|
|
.filter(isLiteLLMSTTModel)
|
|
.map(function(model) { return model && (model.id || model.model_name) ? (model.id || model.model_name) : String(model || ''); });
|
|
}
|
|
|
|
// What the gateway actually offers, so Settings cannot present a user with a
|
|
// model that does not exist. The hardcoded list above is only a last resort:
|
|
// on this deployment none of its six ids resolve, and picking one returns
|
|
// "Invalid model name" from /audio/transcriptions.
|
|
var sttDiscoveryCache = { at: 0, models: [] };
|
|
var STT_DISCOVERY_TTL_MS = 5 * 60 * 1000;
|
|
|
|
async function discoverSTTModels(options) {
|
|
var now = Date.now();
|
|
var fresh = !(options && options.force) && (now - sttDiscoveryCache.at) < STT_DISCOVERY_TTL_MS;
|
|
if (fresh && sttDiscoveryCache.models.length) return sttDiscoveryCache.models.slice();
|
|
if (getSTTProvider() !== 'litellm' || !process.env.LITELLM_API_BASE) return [];
|
|
try {
|
|
var axios = require('axios');
|
|
var { getLiteLLMAdminHeaders } = require('./litellm');
|
|
var base = String(process.env.LITELLM_API_BASE || '').replace(/\/+$/, '').replace(/\/v1\/?$/, '');
|
|
var resp = await axios.get(base + '/model/info', { headers: getLiteLLMAdminHeaders(), timeout: 10000 });
|
|
var ids = getLiteLLMSTTModels(resp.data && resp.data.data);
|
|
if (ids.length) sttDiscoveryCache = { at: now, models: ids };
|
|
return ids;
|
|
} catch (e) {
|
|
// A gateway hiccup must not empty the picker; the caller falls back.
|
|
return sttDiscoveryCache.models.slice();
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
LITELLM_STT_MODELS,
|
|
discoverSTTModels,
|
|
getSTTDependencies,
|
|
getLiteLLMSTTModels,
|
|
getSTTModelLists,
|
|
getSTTProvider,
|
|
isLiteLLMSTTModel
|
|
};
|