| const path = require('path'); |
| const axios = require('axios'); |
| const yaml = require('js-yaml'); |
| const keyBy = require('lodash/keyBy'); |
| const { loadYaml } = require('@librechat/api'); |
| const { logger } = require('@librechat/data-schemas'); |
| const { |
| configSchema, |
| paramSettings, |
| EImageOutputType, |
| agentParamSettings, |
| validateSettingDefinitions, |
| } = require('librechat-data-provider'); |
|
|
| const projectRoot = path.resolve(__dirname, '..', '..', '..', '..'); |
| const defaultConfigPath = path.resolve(projectRoot, 'librechat.yaml'); |
|
|
| let i = 0; |
|
|
| |
| |
| |
| |
| |
| |
| async function loadCustomConfig(printConfig = true) { |
| |
| const configPath = process.env.CONFIG_PATH || defaultConfigPath; |
|
|
| let customConfig; |
|
|
| if (/^https?:\/\//.test(configPath)) { |
| try { |
| const response = await axios.get(configPath); |
| customConfig = response.data; |
| } catch (error) { |
| i === 0 && logger.error(`Failed to fetch the remote config file from ${configPath}`, error); |
| i === 0 && i++; |
| return null; |
| } |
| } else { |
| customConfig = loadYaml(configPath); |
| if (!customConfig) { |
| i === 0 && |
| logger.info( |
| 'Custom config file missing or YAML format invalid.\n\nCheck out the latest config file guide for configurable options and features.\nhttps://www.librechat.ai/docs/configuration/librechat_yaml\n\n', |
| ); |
| i === 0 && i++; |
| return null; |
| } |
|
|
| if (customConfig.reason || customConfig.stack) { |
| i === 0 && logger.error('Config file YAML format is invalid:', customConfig); |
| i === 0 && i++; |
| return null; |
| } |
| } |
|
|
| if (typeof customConfig === 'string') { |
| try { |
| customConfig = yaml.load(customConfig); |
| } catch (parseError) { |
| i === 0 && logger.info(`Failed to parse the YAML config from ${configPath}`, parseError); |
| i === 0 && i++; |
| return null; |
| } |
| } |
|
|
| const result = configSchema.strict().safeParse(customConfig); |
| if (result?.error?.errors?.some((err) => err?.path && err.path?.includes('imageOutputType'))) { |
| throw new Error( |
| ` |
| Please specify a correct \`imageOutputType\` value (case-sensitive). |
| |
| The available options are: |
| - ${EImageOutputType.JPEG} |
| - ${EImageOutputType.PNG} |
| - ${EImageOutputType.WEBP} |
| |
| Refer to the latest config file guide for more information: |
| https://www.librechat.ai/docs/configuration/librechat_yaml`, |
| ); |
| } |
| if (!result.success) { |
| let errorMessage = `Invalid custom config file at ${configPath}: |
| ${JSON.stringify(result.error, null, 2)}`; |
|
|
| if (i === 0) { |
| logger.error(errorMessage); |
| const speechError = result.error.errors.find( |
| (err) => |
| err.code === 'unrecognized_keys' && |
| (err.message?.includes('stt') || err.message?.includes('tts')), |
| ); |
|
|
| if (speechError) { |
| logger.warn(` |
| The Speech-to-text and Text-to-speech configuration format has recently changed. |
| If you're getting this error, please refer to the latest documentation: |
| |
| https://www.librechat.ai/docs/configuration/stt_tts`); |
| } |
|
|
| i++; |
| } |
|
|
| return null; |
| } else { |
| if (printConfig) { |
| logger.info('Custom config file loaded:'); |
| logger.info(JSON.stringify(customConfig, null, 2)); |
| logger.debug('Custom config:', customConfig); |
| } |
| } |
|
|
| (customConfig.endpoints?.custom ?? []) |
| .filter((endpoint) => endpoint.customParams) |
| .forEach((endpoint) => parseCustomParams(endpoint.name, endpoint.customParams)); |
|
|
| if (result.data.modelSpecs) { |
| customConfig.modelSpecs = result.data.modelSpecs; |
| } |
|
|
| return customConfig; |
| } |
|
|
| |
| function parseCustomParams(endpointName, customParams) { |
| const paramEndpoint = customParams.defaultParamsEndpoint; |
| customParams.paramDefinitions = customParams.paramDefinitions || []; |
|
|
| |
| const validEndpoints = new Set([ |
| ...Object.keys(paramSettings), |
| ...Object.keys(agentParamSettings), |
| ]); |
| if (!validEndpoints.has(paramEndpoint)) { |
| throw new Error( |
| `defaultParamsEndpoint of "${endpointName}" endpoint is invalid. ` + |
| `Valid options are ${Array.from(validEndpoints).join(', ')}`, |
| ); |
| } |
|
|
| |
| const regularParams = paramSettings[paramEndpoint] ?? []; |
| const agentParams = agentParamSettings[paramEndpoint] ?? []; |
| const defaultParams = regularParams.concat(agentParams); |
| const defaultParamsMap = keyBy(defaultParams, 'key'); |
|
|
| |
| |
| const validKeys = new Set(Object.keys(defaultParamsMap)); |
| const paramKeys = customParams.paramDefinitions.map((param) => param.key); |
| if (paramKeys.some((key) => !validKeys.has(key))) { |
| throw new Error( |
| `paramDefinitions of "${endpointName}" endpoint contains invalid key(s). ` + |
| `Valid parameter keys are ${Array.from(validKeys).join(', ')}`, |
| ); |
| } |
|
|
| |
| customParams.paramDefinitions = customParams.paramDefinitions.map((param) => { |
| return { ...defaultParamsMap[param.key], ...param, optionType: 'custom' }; |
| }); |
|
|
| try { |
| validateSettingDefinitions(customParams.paramDefinitions); |
| } catch (e) { |
| throw new Error( |
| `Custom parameter definitions for "${endpointName}" endpoint is malformed: ${e.message}`, |
| ); |
| } |
| } |
|
|
| module.exports = loadCustomConfig; |
|
|