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