diff --git a/api/server/services/Files/Audio/STTService.js b/api/server/services/Files/Audio/STTService.js index fb4ffd4858..4ba62a7eeb 100644 --- a/api/server/services/Files/Audio/STTService.js +++ b/api/server/services/Files/Audio/STTService.js @@ -3,8 +3,8 @@ const fs = require('fs').promises; const FormData = require('form-data'); const { Readable } = require('stream'); const { logger } = require('@librechat/data-schemas'); -const { genAzureEndpoint } = require('@librechat/api'); const { HttpsProxyAgent } = require('https-proxy-agent'); +const { genAzureEndpoint, logAxiosError } = require('@librechat/api'); const { extractEnvVariable, STTProviders } = require('librechat-data-provider'); const { getAppConfig } = require('~/server/services/Config'); @@ -35,6 +35,34 @@ const MIME_TO_EXTENSION_MAP = { 'audio/x-flac': 'flac', }; +/** + * Validates and extracts ISO-639-1 language code from a locale string. + * @param {string} language - The language/locale string (e.g., "en-US", "en", "zh-CN") + * @returns {string|null} The ISO-639-1 language code (e.g., "en") or null if invalid + */ +function getValidatedLanguageCode(language) { + try { + if (!language) { + return null; + } + + const normalizedLanguage = language.toLowerCase(); + const isValidLocaleCode = /^[a-z]{2}(-[a-z]{2})?$/.test(normalizedLanguage); + + if (isValidLocaleCode) { + return normalizedLanguage.split('-')[0]; + } + + logger.warn( + `[STT] Invalid language format "${language}". Expected ISO-639-1 locale code like "en-US" or "en". Skipping language parameter.`, + ); + return null; + } catch (error) { + logger.error(`[STT] Error validating language code "${language}":`, error); + return null; + } +} + /** * Gets the file extension from the MIME type. * @param {string} mimeType - The MIME type. @@ -173,10 +201,9 @@ class STTService { model: sttSchema.model, }; - if (language) { - /** Converted locale code (e.g., "en-US") to ISO-639-1 format (e.g., "en") */ - const isoLanguage = language.split('-')[0]; - data.language = isoLanguage; + const validLanguage = getValidatedLanguageCode(language); + if (validLanguage) { + data.language = validLanguage; } const headers = { @@ -221,10 +248,9 @@ class STTService { contentType: audioFile.mimetype, }); - if (language) { - /** Converted locale code (e.g., "en-US") to ISO-639-1 format (e.g., "en") */ - const isoLanguage = language.split('-')[0]; - formData.append('language', isoLanguage); + const validLanguage = getValidatedLanguageCode(language); + if (validLanguage) { + formData.append('language', validLanguage); } const headers = { @@ -286,7 +312,7 @@ class STTService { return response.data.text.trim(); } catch (error) { - logger.error(`STT request failed for provider ${provider}:`, error); + logAxiosError({ message: `STT request failed for provider ${provider}:`, error }); throw error; } } @@ -316,7 +342,7 @@ class STTService { const text = await this.sttRequest(provider, sttSchema, { audioBuffer, audioFile, language }); res.json({ text }); } catch (error) { - logger.error('An error occurred while processing the audio:', error); + logAxiosError({ message: 'An error occurred while processing the audio:', error }); res.sendStatus(500); } finally { try { diff --git a/api/server/services/Files/Audio/TTSService.js b/api/server/services/Files/Audio/TTSService.js index e147b057f4..2c932968c6 100644 --- a/api/server/services/Files/Audio/TTSService.js +++ b/api/server/services/Files/Audio/TTSService.js @@ -1,7 +1,7 @@ const axios = require('axios'); const { logger } = require('@librechat/data-schemas'); -const { genAzureEndpoint } = require('@librechat/api'); const { HttpsProxyAgent } = require('https-proxy-agent'); +const { genAzureEndpoint, logAxiosError } = require('@librechat/api'); const { extractEnvVariable, TTSProviders } = require('librechat-data-provider'); const { getRandomVoiceId, createChunkProcessor, splitTextIntoChunks } = require('./streamAudio'); const { getAppConfig } = require('~/server/services/Config'); @@ -274,7 +274,7 @@ class TTSService { try { return await axios.post(url, data, options); } catch (error) { - logger.error(`TTS request failed for provider ${provider}:`, error); + logAxiosError({ message: `TTS request failed for provider ${provider}:`, error }); throw error; } } @@ -330,7 +330,10 @@ class TTSService { break; } } catch (innerError) { - logger.error('Error processing manual update:', chunk, innerError); + logAxiosError({ + message: `[TTS] Error processing manual update for chunk: ${chunk?.text?.substring(0, 50)}...`, + error: innerError, + }); if (!res.headersSent) { return res.status(500).end(); } @@ -342,7 +345,7 @@ class TTSService { res.end(); } } catch (error) { - logger.error('Error creating the audio stream:', error); + logAxiosError({ message: '[TTS] Error creating the audio stream:', error }); if (!res.headersSent) { return res.status(500).send('An error occurred'); } @@ -412,7 +415,10 @@ class TTSService { break; } } catch (innerError) { - logger.error('Error processing audio stream update:', update, innerError); + logAxiosError({ + message: `[TTS] Error processing audio stream update: ${update?.text?.substring(0, 50)}...`, + error: innerError, + }); if (!res.headersSent) { return res.status(500).end(); } @@ -429,7 +435,7 @@ class TTSService { res.end(); } } catch (error) { - logger.error('Failed to fetch audio:', error); + logAxiosError({ message: '[TTS] Failed to fetch audio:', error }); if (!res.headersSent) { res.status(500).end(); }