WIP: app.locals refactoring

WIP: appConfig

fix: update memory configuration retrieval to use getAppConfig based on user role

fix: update comment for AppConfig interface to clarify purpose
This commit is contained in:
Danny Avila 2025-08-05 18:09:25 -04:00
parent 5a14ee9c6a
commit b992fed16c
No known key found for this signature in database
GPG key ID: BF31EEB2C5CA0956
66 changed files with 706 additions and 366 deletions

View file

@ -11,6 +11,7 @@ const {
Constants, Constants,
} = require('librechat-data-provider'); } = require('librechat-data-provider');
const { getMessages, saveMessage, updateMessage, saveConvo, getConvo } = require('~/models'); const { getMessages, saveMessage, updateMessage, saveConvo, getConvo } = require('~/models');
const { getAppConfig } = require('~/server/services/Config');
const { checkBalance } = require('~/models/balanceMethods'); const { checkBalance } = require('~/models/balanceMethods');
const { truncateToolCallOutputs } = require('./prompts'); const { truncateToolCallOutputs } = require('./prompts');
const { getFiles } = require('~/models/File'); const { getFiles } = require('~/models/File');
@ -567,6 +568,7 @@ class BaseClient {
} }
async sendMessage(message, opts = {}) { async sendMessage(message, opts = {}) {
const appConfig = await getAppConfig({ role: this.options.req?.user?.role });
/** @type {Promise<TMessage>} */ /** @type {Promise<TMessage>} */
let userMessagePromise; let userMessagePromise;
const { user, head, isEdited, conversationId, responseMessageId, saveOptions, userMessage } = const { user, head, isEdited, conversationId, responseMessageId, saveOptions, userMessage } =
@ -653,7 +655,7 @@ class BaseClient {
} }
} }
const balance = this.options.req?.app?.locals?.balance; const balance = appConfig?.balance;
if ( if (
balance?.enabled && balance?.enabled &&
supportsBalanceCheck[this.options.endpointType ?? this.options.endpoint] supportsBalanceCheck[this.options.endpointType ?? this.options.endpoint]

View file

@ -34,6 +34,7 @@ const {
const { extractBaseURL, getModelMaxTokens, getModelMaxOutputTokens } = require('~/utils'); const { extractBaseURL, getModelMaxTokens, getModelMaxOutputTokens } = require('~/utils');
const { encodeAndFormat } = require('~/server/services/Files/images/encode'); const { encodeAndFormat } = require('~/server/services/Files/images/encode');
const { addSpaceIfNeeded, sleep } = require('~/server/utils'); const { addSpaceIfNeeded, sleep } = require('~/server/utils');
const { getAppConfig } = require('~/server/services/Config');
const { spendTokens } = require('~/models/spendTokens'); const { spendTokens } = require('~/models/spendTokens');
const { handleOpenAIErrors } = require('./tools/util'); const { handleOpenAIErrors } = require('./tools/util');
const { createLLM, RunManager } = require('./llm'); const { createLLM, RunManager } = require('./llm');
@ -702,6 +703,7 @@ class OpenAIClient extends BaseClient {
* In case of failure, it will return the default title, "New Chat". * In case of failure, it will return the default title, "New Chat".
*/ */
async titleConvo({ text, conversationId, responseText = '' }) { async titleConvo({ text, conversationId, responseText = '' }) {
const appConfig = await getAppConfig({ role: this.options.req?.user?.role });
this.conversationId = conversationId; this.conversationId = conversationId;
if (this.options.attachments) { if (this.options.attachments) {
@ -731,7 +733,7 @@ class OpenAIClient extends BaseClient {
}; };
/** @type {TAzureConfig | undefined} */ /** @type {TAzureConfig | undefined} */
const azureConfig = this.options?.req?.app?.locals?.[EModelEndpoint.azureOpenAI]; const azureConfig = appConfig?.[EModelEndpoint.azureOpenAI];
const resetTitleOptions = !!( const resetTitleOptions = !!(
(this.azure && azureConfig) || (this.azure && azureConfig) ||
@ -1120,6 +1122,7 @@ ${convo}
} }
async chatCompletion({ payload, onProgress, abortController = null }) { async chatCompletion({ payload, onProgress, abortController = null }) {
const appConfig = await getAppConfig({ role: this.options.req?.user?.role });
let error = null; let error = null;
let intermediateReply = []; let intermediateReply = [];
const errorCallback = (err) => (error = err); const errorCallback = (err) => (error = err);
@ -1166,7 +1169,7 @@ ${convo}
} }
/** @type {TAzureConfig | undefined} */ /** @type {TAzureConfig | undefined} */
const azureConfig = this.options?.req?.app?.locals?.[EModelEndpoint.azureOpenAI]; const azureConfig = appConfig?.[EModelEndpoint.azureOpenAI];
if ( if (
(this.azure && this.isVisionModel && azureConfig) || (this.azure && this.isVisionModel && azureConfig) ||

View file

@ -9,6 +9,7 @@ const { logAxiosError } = require('@librechat/api');
const { logger } = require('@librechat/data-schemas'); const { logger } = require('@librechat/data-schemas');
const { ContentTypes, EImageOutputType } = require('librechat-data-provider'); const { ContentTypes, EImageOutputType } = require('librechat-data-provider');
const { getStrategyFunctions } = require('~/server/services/Files/strategies'); const { getStrategyFunctions } = require('~/server/services/Files/strategies');
const { getAppConfig } = require('~/server/services/Config');
const { extractBaseURL } = require('~/utils'); const { extractBaseURL } = require('~/utils');
const { getFiles } = require('~/models/File'); const { getFiles } = require('~/models/File');
@ -123,7 +124,7 @@ function createAbortHandler() {
* @param {MongoFile[]} [fields.imageFiles] - The images to be used for editing * @param {MongoFile[]} [fields.imageFiles] - The images to be used for editing
* @returns {Array} - Array of image tools * @returns {Array} - Array of image tools
*/ */
function createOpenAIImageTools(fields = {}) { async function createOpenAIImageTools(fields = {}) {
/** @type {boolean} Used to initialize the Tool without necessary variables. */ /** @type {boolean} Used to initialize the Tool without necessary variables. */
const override = fields.override ?? false; const override = fields.override ?? false;
/** @type {boolean} */ /** @type {boolean} */
@ -131,8 +132,9 @@ function createOpenAIImageTools(fields = {}) {
throw new Error('This tool is only available for agents.'); throw new Error('This tool is only available for agents.');
} }
const { req } = fields; const { req } = fields;
const imageOutputType = req?.app.locals.imageOutputType || EImageOutputType.PNG; const appConfig = await getAppConfig({ role: req?.user?.role });
const appFileStrategy = req?.app.locals.fileStrategy; const imageOutputType = appConfig?.imageOutputType || EImageOutputType.PNG;
const appFileStrategy = appConfig?.fileStrategy;
const getApiKey = () => { const getApiKey = () => {
const apiKey = process.env.IMAGE_GEN_OAI_API_KEY ?? ''; const apiKey = process.env.IMAGE_GEN_OAI_API_KEY ?? '';

View file

@ -24,8 +24,8 @@ const {
const { primeFiles: primeCodeFiles } = require('~/server/services/Files/Code/process'); const { primeFiles: primeCodeFiles } = require('~/server/services/Files/Code/process');
const { createFileSearchTool, primeFiles: primeSearchFiles } = require('./fileSearch'); const { createFileSearchTool, primeFiles: primeSearchFiles } = require('./fileSearch');
const { getUserPluginAuthValue } = require('~/server/services/PluginService'); const { getUserPluginAuthValue } = require('~/server/services/PluginService');
const { getCachedTools, getAppConfig } = require('~/server/services/Config');
const { loadAuthValues } = require('~/server/services/Tools/credentials'); const { loadAuthValues } = require('~/server/services/Tools/credentials');
const { getCachedTools } = require('~/server/services/Config');
const { createMCPTool } = require('~/server/services/MCP'); const { createMCPTool } = require('~/server/services/MCP');
/** /**
@ -143,6 +143,7 @@ const loadTools = async ({
functions = true, functions = true,
returnMap = false, returnMap = false,
}) => { }) => {
const appConfig = await getAppConfig({ role: options?.req?.user?.role });
const toolConstructors = { const toolConstructors = {
flux: FluxAPI, flux: FluxAPI,
calculator: Calculator, calculator: Calculator,
@ -272,7 +273,7 @@ const loadTools = async ({
}; };
continue; continue;
} else if (tool === Tools.web_search) { } else if (tool === Tools.web_search) {
const webSearchConfig = options?.req?.app?.locals?.webSearch; const webSearchConfig = appConfig?.webSearch;
const result = await loadWebSearchAuth({ const result = await loadWebSearchAuth({
userId: user, userId: user,
loadAuthValues, loadAuthValues,

View file

@ -6,7 +6,7 @@ const {
filterUniquePlugins, filterUniquePlugins,
convertMCPToolsToPlugins, convertMCPToolsToPlugins,
} = require('@librechat/api'); } = require('@librechat/api');
const { getCustomConfig, getCachedTools } = require('~/server/services/Config'); const { getCustomConfig, getCachedTools, getAppConfig } = require('~/server/services/Config');
const { availableTools, toolkits } = require('~/app/clients/tools'); const { availableTools, toolkits } = require('~/app/clients/tools');
const { getMCPManager, getFlowStateManager } = require('~/config'); const { getMCPManager, getFlowStateManager } = require('~/config');
const { getLogStores } = require('~/cache'); const { getLogStores } = require('~/cache');
@ -20,8 +20,9 @@ const getAvailablePluginsController = async (req, res) => {
return; return;
} }
const appConfig = await getAppConfig({ role: req.user?.role });
/** @type {{ filteredTools: string[], includedTools: string[] }} */ /** @type {{ filteredTools: string[], includedTools: string[] }} */
const { filteredTools = [], includedTools = [] } = req.app.locals; const { filteredTools = [], includedTools = [] } = appConfig;
const pluginManifest = availableTools; const pluginManifest = availableTools;
const uniquePlugins = filterUniquePlugins(pluginManifest); const uniquePlugins = filterUniquePlugins(pluginManifest);

View file

@ -17,11 +17,13 @@ const { needsRefresh, getNewS3URL } = require('~/server/services/Files/S3/crud')
const { Tools, Constants, FileSources } = require('librechat-data-provider'); const { Tools, Constants, FileSources } = require('librechat-data-provider');
const { processDeleteRequest } = require('~/server/services/Files/process'); const { processDeleteRequest } = require('~/server/services/Files/process');
const { Transaction, Balance, User } = require('~/db/models'); const { Transaction, Balance, User } = require('~/db/models');
const { getAppConfig } = require('~/server/services/Config');
const { deleteToolCalls } = require('~/models/ToolCall'); const { deleteToolCalls } = require('~/models/ToolCall');
const { deleteAllSharedLinks } = require('~/models'); const { deleteAllSharedLinks } = require('~/models');
const { getMCPManager } = require('~/config'); const { getMCPManager } = require('~/config');
const getUserController = async (req, res) => { const getUserController = async (req, res) => {
const appConfig = await getAppConfig({ role: req.user?.role });
/** @type {MongoUser} */ /** @type {MongoUser} */
const userData = req.user.toObject != null ? req.user.toObject() : { ...req.user }; const userData = req.user.toObject != null ? req.user.toObject() : { ...req.user };
/** /**
@ -31,7 +33,7 @@ const getUserController = async (req, res) => {
delete userData.password; delete userData.password;
delete userData.totpSecret; delete userData.totpSecret;
delete userData.backupCodes; delete userData.backupCodes;
if (req.app.locals.fileStrategy === FileSources.s3 && userData.avatar) { if (appConfig.fileStrategy === FileSources.s3 && userData.avatar) {
const avatarNeedsRefresh = needsRefresh(userData.avatar, 3600); const avatarNeedsRefresh = needsRefresh(userData.avatar, 3600);
if (!avatarNeedsRefresh) { if (!avatarNeedsRefresh) {
return res.status(200).send(userData); return res.status(200).send(userData);
@ -87,6 +89,7 @@ const deleteUserFiles = async (req) => {
}; };
const updateUserPluginsController = async (req, res) => { const updateUserPluginsController = async (req, res) => {
const appConfig = await getAppConfig({ role: req.user?.role });
const { user } = req; const { user } = req;
const { pluginKey, action, auth, isEntityTool } = req.body; const { pluginKey, action, auth, isEntityTool } = req.body;
try { try {
@ -131,7 +134,7 @@ const updateUserPluginsController = async (req, res) => {
if (pluginKey === Tools.web_search) { if (pluginKey === Tools.web_search) {
/** @type {TCustomConfig['webSearch']} */ /** @type {TCustomConfig['webSearch']} */
const webSearchConfig = req.app.locals?.webSearch; const webSearchConfig = appConfig?.webSearch;
keys = extractWebSearchEnvVars({ keys = extractWebSearchEnvVars({
keys: action === 'install' ? keys : webSearchKeys, keys: action === 'install' ? keys : webSearchKeys,
config: webSearchConfig, config: webSearchConfig,

View file

@ -39,7 +39,12 @@ const {
deleteMemory, deleteMemory,
setMemory, setMemory,
} = require('~/models'); } = require('~/models');
const { getMCPAuthMap, checkCapability, hasCustomUserVars } = require('~/server/services/Config'); const {
hasCustomUserVars,
checkCapability,
getMCPAuthMap,
getAppConfig,
} = require('~/server/services/Config');
const { addCacheControl, createContextHandlers } = require('~/app/clients/prompts'); const { addCacheControl, createContextHandlers } = require('~/app/clients/prompts');
const { initializeAgent } = require('~/server/services/Endpoints/agents/agent'); const { initializeAgent } = require('~/server/services/Endpoints/agents/agent');
const { spendTokens, spendStructuredTokens } = require('~/models/spendTokens'); const { spendTokens, spendStructuredTokens } = require('~/models/spendTokens');
@ -451,17 +456,15 @@ class AgentClient extends BaseClient {
); );
return; return;
} }
/** @type {TCustomConfig['memory']} */ const appConfig = await getAppConfig({ role: user.role });
const memoryConfig = this.options.req?.app?.locals?.memory; const memoryConfig = appConfig.memory;
if (!memoryConfig || memoryConfig.disabled === true) { if (!memoryConfig || memoryConfig.disabled === true) {
return; return;
} }
/** @type {Agent} */ /** @type {Agent} */
let prelimAgent; let prelimAgent;
const allowedProviders = new Set( const allowedProviders = new Set(appConfig?.[EModelEndpoint.agents]?.allowedProviders);
this.options.req?.app?.locals?.[EModelEndpoint.agents]?.allowedProviders,
);
try { try {
if (memoryConfig.agent?.id != null && memoryConfig.agent.id !== this.options.agent.id) { if (memoryConfig.agent?.id != null && memoryConfig.agent.id !== this.options.agent.id) {
prelimAgent = await loadAgent({ prelimAgent = await loadAgent({
@ -582,8 +585,8 @@ class AgentClient extends BaseClient {
if (this.processMemory == null) { if (this.processMemory == null) {
return; return;
} }
/** @type {TCustomConfig['memory']} */ const appConfig = await getAppConfig({ role: this.options.req.user?.role });
const memoryConfig = this.options.req?.app?.locals?.memory; const memoryConfig = appConfig.memory;
const messageWindowSize = memoryConfig?.messageWindowSize ?? 5; const messageWindowSize = memoryConfig?.messageWindowSize ?? 5;
let messagesToProcess = [...messages]; let messagesToProcess = [...messages];
@ -759,8 +762,9 @@ class AgentClient extends BaseClient {
abortController = new AbortController(); abortController = new AbortController();
} }
const appConfig = await getAppConfig({ role: this.options.req.user?.role });
/** @type {TCustomConfig['endpoints']['agents']} */ /** @type {TCustomConfig['endpoints']['agents']} */
const agentsEConfig = this.options.req.app.locals[EModelEndpoint.agents]; const agentsEConfig = appConfig[EModelEndpoint.agents];
config = { config = {
configurable: { configurable: {
@ -1081,6 +1085,7 @@ class AgentClient extends BaseClient {
} }
const { handleLLMEnd, collected: collectedMetadata } = createMetadataAggregator(); const { handleLLMEnd, collected: collectedMetadata } = createMetadataAggregator();
const { req, res, agent } = this.options; const { req, res, agent } = this.options;
const appConfig = await getAppConfig({ role: req.user?.role });
let endpoint = agent.endpoint; let endpoint = agent.endpoint;
/** @type {import('@librechat/agents').ClientOptions} */ /** @type {import('@librechat/agents').ClientOptions} */
@ -1092,7 +1097,7 @@ class AgentClient extends BaseClient {
/** @type {TEndpoint | undefined} */ /** @type {TEndpoint | undefined} */
const endpointConfig = const endpointConfig =
req.app.locals.all ?? req.app.locals[endpoint] ?? titleProviderConfig.customEndpointConfig; appConfig.all ?? appConfig[endpoint] ?? titleProviderConfig.customEndpointConfig;
if (!endpointConfig) { if (!endpointConfig) {
logger.warn( logger.warn(
'[api/server/controllers/agents/client.js #titleConvo] Error getting endpoint config', '[api/server/controllers/agents/client.js #titleConvo] Error getting endpoint config',

View file

@ -31,12 +31,12 @@ const {
grantPermission, grantPermission,
} = require('~/server/services/PermissionService'); } = require('~/server/services/PermissionService');
const { getStrategyFunctions } = require('~/server/services/Files/strategies'); const { getStrategyFunctions } = require('~/server/services/Files/strategies');
const { getCachedTools, getAppConfig } = require('~/server/services/Config');
const { resizeAvatar } = require('~/server/services/Files/images/avatar'); const { resizeAvatar } = require('~/server/services/Files/images/avatar');
const { getFileStrategy } = require('~/server/utils/getFileStrategy'); const { getFileStrategy } = require('~/server/utils/getFileStrategy');
const { refreshS3Url } = require('~/server/services/Files/S3/crud'); const { refreshS3Url } = require('~/server/services/Files/S3/crud');
const { filterFile } = require('~/server/services/Files/process'); const { filterFile } = require('~/server/services/Files/process');
const { updateAction, getActions } = require('~/models/Action'); const { updateAction, getActions } = require('~/models/Action');
const { getCachedTools } = require('~/server/services/Config');
const { deleteFileByFilter } = require('~/models/File'); const { deleteFileByFilter } = require('~/models/File');
const { getCategoriesWithCounts } = require('~/models'); const { getCategoriesWithCounts } = require('~/models');
@ -487,6 +487,7 @@ const getListAgentsHandler = async (req, res) => {
*/ */
const uploadAgentAvatarHandler = async (req, res) => { const uploadAgentAvatarHandler = async (req, res) => {
try { try {
const appConfig = await getAppConfig({ role: req.user?.role });
filterFile({ req, file: req.file, image: true, isAvatar: true }); filterFile({ req, file: req.file, image: true, isAvatar: true });
const { agent_id } = req.params; const { agent_id } = req.params;
if (!agent_id) { if (!agent_id) {
@ -510,9 +511,7 @@ const uploadAgentAvatarHandler = async (req, res) => {
} }
const buffer = await fs.readFile(req.file.path); const buffer = await fs.readFile(req.file.path);
const fileStrategy = getFileStrategy(appConfig, { isAvatar: true });
const fileStrategy = getFileStrategy(req.app.locals, { isAvatar: true });
const resizedBuffer = await resizeAvatar({ const resizedBuffer = await resizeAvatar({
userId: req.user.id, userId: req.user.id,
input: buffer, input: buffer,

View file

@ -29,6 +29,7 @@ const { createRun, StreamRunManager } = require('~/server/services/Runs');
const { addTitle } = require('~/server/services/Endpoints/assistants'); const { addTitle } = require('~/server/services/Endpoints/assistants');
const { createRunBody } = require('~/server/services/createRunBody'); const { createRunBody } = require('~/server/services/createRunBody');
const { sendResponse } = require('~/server/middleware/error'); const { sendResponse } = require('~/server/middleware/error');
const { getAppConfig } = require('~/server/services/Config');
const { getTransactions } = require('~/models/Transaction'); const { getTransactions } = require('~/models/Transaction');
const { checkBalance } = require('~/models/balanceMethods'); const { checkBalance } = require('~/models/balanceMethods');
const { getConvo } = require('~/models/Conversation'); const { getConvo } = require('~/models/Conversation');
@ -47,6 +48,7 @@ const { getOpenAIClient } = require('./helpers');
* @returns {void} * @returns {void}
*/ */
const chatV1 = async (req, res) => { const chatV1 = async (req, res) => {
const appConfig = await getAppConfig({ role: req.user?.role });
logger.debug('[/assistants/chat/] req.body', req.body); logger.debug('[/assistants/chat/] req.body', req.body);
const { const {
@ -251,7 +253,7 @@ const chatV1 = async (req, res) => {
} }
const checkBalanceBeforeRun = async () => { const checkBalanceBeforeRun = async () => {
const balance = req.app?.locals?.balance; const balance = appConfig?.balance;
if (!balance?.enabled) { if (!balance?.enabled) {
return; return;
} }

View file

@ -26,6 +26,7 @@ const validateAuthor = require('~/server/middleware/assistants/validateAuthor');
const { createRun, StreamRunManager } = require('~/server/services/Runs'); const { createRun, StreamRunManager } = require('~/server/services/Runs');
const { addTitle } = require('~/server/services/Endpoints/assistants'); const { addTitle } = require('~/server/services/Endpoints/assistants');
const { createRunBody } = require('~/server/services/createRunBody'); const { createRunBody } = require('~/server/services/createRunBody');
const { getAppConfig } = require('~/server/services/Config');
const { getTransactions } = require('~/models/Transaction'); const { getTransactions } = require('~/models/Transaction');
const { checkBalance } = require('~/models/balanceMethods'); const { checkBalance } = require('~/models/balanceMethods');
const { getConvo } = require('~/models/Conversation'); const { getConvo } = require('~/models/Conversation');
@ -44,6 +45,7 @@ const { getOpenAIClient } = require('./helpers');
*/ */
const chatV2 = async (req, res) => { const chatV2 = async (req, res) => {
logger.debug('[/assistants/chat/] req.body', req.body); logger.debug('[/assistants/chat/] req.body', req.body);
const appConfig = await getAppConfig({ role: req.user?.role });
/** @type {{files: MongoFile[]}} */ /** @type {{files: MongoFile[]}} */
const { const {
@ -126,7 +128,7 @@ const chatV2 = async (req, res) => {
} }
const checkBalanceBeforeRun = async () => { const checkBalanceBeforeRun = async () => {
const balance = req.app?.locals?.balance; const balance = appConfig?.balance;
if (!balance?.enabled) { if (!balance?.enabled) {
return; return;
} }
@ -374,9 +376,9 @@ const chatV2 = async (req, res) => {
}; };
/** @type {undefined | TAssistantEndpoint} */ /** @type {undefined | TAssistantEndpoint} */
const config = req.app.locals[endpoint] ?? {}; const config = appConfig[endpoint] ?? {};
/** @type {undefined | TBaseEndpoint} */ /** @type {undefined | TBaseEndpoint} */
const allConfig = req.app.locals.all; const allConfig = appConfig.all;
const streamRunManager = new StreamRunManager({ const streamRunManager = new StreamRunManager({
req, req,

View file

@ -7,8 +7,8 @@ const {
const { const {
initializeClient: initAzureClient, initializeClient: initAzureClient,
} = require('~/server/services/Endpoints/azureAssistants'); } = require('~/server/services/Endpoints/azureAssistants');
const { getEndpointsConfig, getAppConfig } = require('~/server/services/Config');
const { initializeClient } = require('~/server/services/Endpoints/assistants'); const { initializeClient } = require('~/server/services/Endpoints/assistants');
const { getEndpointsConfig } = require('~/server/services/Config');
/** /**
* @param {Express.Request} req * @param {Express.Request} req
@ -210,6 +210,7 @@ async function getOpenAIClient({ req, res, endpointOption, initAppClient, overri
* @returns {Promise<AssistantListResponse>} 200 - success response - application/json * @returns {Promise<AssistantListResponse>} 200 - success response - application/json
*/ */
const fetchAssistants = async ({ req, res, overrideEndpoint }) => { const fetchAssistants = async ({ req, res, overrideEndpoint }) => {
const appConfig = await getAppConfig({ role: req.user?.role });
const { const {
limit = 100, limit = 100,
order = 'desc', order = 'desc',
@ -230,20 +231,20 @@ const fetchAssistants = async ({ req, res, overrideEndpoint }) => {
if (endpoint === EModelEndpoint.assistants) { if (endpoint === EModelEndpoint.assistants) {
({ body } = await listAllAssistants({ req, res, version, query })); ({ body } = await listAllAssistants({ req, res, version, query }));
} else if (endpoint === EModelEndpoint.azureAssistants) { } else if (endpoint === EModelEndpoint.azureAssistants) {
const azureConfig = req.app.locals[EModelEndpoint.azureOpenAI]; const azureConfig = appConfig[EModelEndpoint.azureOpenAI];
body = await listAssistantsForAzure({ req, res, version, azureConfig, query }); body = await listAssistantsForAzure({ req, res, version, azureConfig, query });
} }
if (req.user.role === SystemRoles.ADMIN) { if (req.user.role === SystemRoles.ADMIN) {
return body; return body;
} else if (!req.app.locals[endpoint]) { } else if (!appConfig[endpoint]) {
return body; return body;
} }
body.data = filterAssistants({ body.data = filterAssistants({
userId: req.user.id, userId: req.user.id,
assistants: body.data, assistants: body.data,
assistantsConfig: req.app.locals[endpoint], assistantsConfig: appConfig[endpoint],
}); });
return body; return body;
}; };

View file

@ -5,9 +5,9 @@ const { uploadImageBuffer, filterFile } = require('~/server/services/Files/proce
const validateAuthor = require('~/server/middleware/assistants/validateAuthor'); const validateAuthor = require('~/server/middleware/assistants/validateAuthor');
const { getStrategyFunctions } = require('~/server/services/Files/strategies'); const { getStrategyFunctions } = require('~/server/services/Files/strategies');
const { deleteAssistantActions } = require('~/server/services/ActionService'); const { deleteAssistantActions } = require('~/server/services/ActionService');
const { getCachedTools, getAppConfig } = require('~/server/services/Config');
const { updateAssistantDoc, getAssistants } = require('~/models/Assistant'); const { updateAssistantDoc, getAssistants } = require('~/models/Assistant');
const { getOpenAIClient, fetchAssistants } = require('./helpers'); const { getOpenAIClient, fetchAssistants } = require('./helpers');
const { getCachedTools } = require('~/server/services/Config');
const { manifestToolMap } = require('~/app/clients/tools'); const { manifestToolMap } = require('~/app/clients/tools');
const { deleteFileByFilter } = require('~/models/File'); const { deleteFileByFilter } = require('~/models/File');
@ -258,8 +258,9 @@ function filterAssistantDocs({ documents, userId, assistantsConfig = {} }) {
*/ */
const getAssistantDocuments = async (req, res) => { const getAssistantDocuments = async (req, res) => {
try { try {
const appConfig = await getAppConfig({ role: req.user?.role });
const endpoint = req.query; const endpoint = req.query;
const assistantsConfig = req.app.locals[endpoint]; const assistantsConfig = appConfig[endpoint];
const documents = await getAssistants( const documents = await getAssistants(
{}, {},
{ {
@ -296,6 +297,7 @@ const getAssistantDocuments = async (req, res) => {
*/ */
const uploadAssistantAvatar = async (req, res) => { const uploadAssistantAvatar = async (req, res) => {
try { try {
const appConfig = await getAppConfig({ role: req.user?.role });
filterFile({ req, file: req.file, image: true, isAvatar: true }); filterFile({ req, file: req.file, image: true, isAvatar: true });
const { assistant_id } = req.params; const { assistant_id } = req.params;
if (!assistant_id) { if (!assistant_id) {
@ -337,7 +339,7 @@ const uploadAssistantAvatar = async (req, res) => {
const metadata = { const metadata = {
..._metadata, ..._metadata,
avatar: image.filepath, avatar: image.filepath,
avatar_source: req.app.locals.fileStrategy, avatar_source: appConfig.fileStrategy,
}; };
const promises = []; const promises = [];
@ -347,7 +349,7 @@ const uploadAssistantAvatar = async (req, res) => {
{ {
avatar: { avatar: {
filepath: image.filepath, filepath: image.filepath,
source: req.app.locals.fileStrategy, source: appConfig.fileStrategy,
}, },
user: req.user.id, user: req.user.id,
}, },

View file

@ -13,6 +13,7 @@ const { processFileURL, uploadImageBuffer } = require('~/server/services/Files/p
const { processCodeOutput } = require('~/server/services/Files/Code/process'); const { processCodeOutput } = require('~/server/services/Files/Code/process');
const { createToolCall, getToolCallsByConvo } = require('~/models/ToolCall'); const { createToolCall, getToolCallsByConvo } = require('~/models/ToolCall');
const { loadAuthValues } = require('~/server/services/Tools/credentials'); const { loadAuthValues } = require('~/server/services/Tools/credentials');
const { getAppConfig } = require('~/server/services/Config');
const { loadTools } = require('~/app/clients/tools/util'); const { loadTools } = require('~/app/clients/tools/util');
const { getRoleByName } = require('~/models/Role'); const { getRoleByName } = require('~/models/Role');
const { getMessage } = require('~/models/Message'); const { getMessage } = require('~/models/Message');
@ -35,9 +36,10 @@ const toolAccessPermType = {
*/ */
const verifyWebSearchAuth = async (req, res) => { const verifyWebSearchAuth = async (req, res) => {
try { try {
const appConfig = await getAppConfig({ role: req.user?.role });
const userId = req.user.id; const userId = req.user.id;
/** @type {TCustomConfig['webSearch']} */ /** @type {TCustomConfig['webSearch']} */
const webSearchConfig = req.app.locals?.webSearch || {}; const webSearchConfig = appConfig?.webSearch || {};
const result = await loadWebSearchAuth({ const result = await loadWebSearchAuth({
userId, userId,
loadAuthValues, loadAuthValues,
@ -110,6 +112,7 @@ const verifyToolAuth = async (req, res) => {
*/ */
const callTool = async (req, res) => { const callTool = async (req, res) => {
try { try {
const appConfig = await getAppConfig({ role: req.user?.role });
const { toolId = '' } = req.params; const { toolId = '' } = req.params;
if (!fieldsMap[toolId]) { if (!fieldsMap[toolId]) {
logger.warn(`[${toolId}/call] User ${req.user.id} attempted call to invalid tool`); logger.warn(`[${toolId}/call] User ${req.user.id} attempted call to invalid tool`);
@ -155,7 +158,7 @@ const callTool = async (req, res) => {
returnMetadata: true, returnMetadata: true,
processFileURL, processFileURL,
uploadImageBuffer, uploadImageBuffer,
fileStrategy: req.app.locals.fileStrategy, fileStrategy: appConfig.fileStrategy,
}, },
}); });

View file

@ -17,6 +17,7 @@ const { jwtLogin, ldapLogin, passportLogin } = require('~/strategies');
const { checkMigrations } = require('./services/start/migration'); const { checkMigrations } = require('./services/start/migration');
const initializeMCPs = require('./services/initializeMCPs'); const initializeMCPs = require('./services/initializeMCPs');
const configureSocialLogins = require('./socialLogins'); const configureSocialLogins = require('./socialLogins');
const { getAppConfig } = require('./services/Config');
const AppService = require('./services/AppService'); const AppService = require('./services/AppService');
const staticCache = require('./utils/staticCache'); const staticCache = require('./utils/staticCache');
const noIndex = require('./middleware/noIndex'); const noIndex = require('./middleware/noIndex');
@ -46,8 +47,8 @@ const startServer = async () => {
app.set('trust proxy', trusted_proxy); app.set('trust proxy', trusted_proxy);
await AppService(app); await AppService(app);
const appConfig = await getAppConfig();
const indexPath = path.join(app.locals.paths.dist, 'index.html'); const indexPath = path.join(appConfig.paths.dist, 'index.html');
const indexHTML = fs.readFileSync(indexPath, 'utf8'); const indexHTML = fs.readFileSync(indexPath, 'utf8');
app.get('/health', (_req, res) => res.status(200).send('OK')); app.get('/health', (_req, res) => res.status(200).send('OK'));
@ -66,10 +67,9 @@ const startServer = async () => {
console.warn('Response compression has been disabled via DISABLE_COMPRESSION.'); console.warn('Response compression has been disabled via DISABLE_COMPRESSION.');
} }
// Serve static assets with aggressive caching app.use(staticCache(appConfig.paths.dist));
app.use(staticCache(app.locals.paths.dist)); app.use(staticCache(appConfig.paths.fonts));
app.use(staticCache(app.locals.paths.fonts)); app.use(staticCache(appConfig.paths.assets));
app.use(staticCache(app.locals.paths.assets));
if (!ALLOW_SOCIAL_LOGIN) { if (!ALLOW_SOCIAL_LOGIN) {
console.warn('Social logins are disabled. Set ALLOW_SOCIAL_LOGIN=true to enable them.'); console.warn('Social logins are disabled. Set ALLOW_SOCIAL_LOGIN=true to enable them.');
@ -146,7 +146,7 @@ const startServer = async () => {
logger.info(`Server listening at http://${host == '0.0.0.0' ? 'localhost' : host}:${port}`); logger.info(`Server listening at http://${host == '0.0.0.0' ? 'localhost' : host}:${port}`);
} }
initializeMCPs(app).then(() => checkMigrations()); initializeMCPs().then(() => checkMigrations());
}); });
}; };

View file

@ -1,5 +1,6 @@
const { v4 } = require('uuid'); const { v4 } = require('uuid');
const { handleAbortError } = require('~/server/middleware/abortMiddleware'); const { handleAbortError } = require('~/server/middleware/abortMiddleware');
const { getAppConfig } = require('~/server/services/Config/getAppConfig');
/** /**
* Checks if the assistant is supported or excluded * Checks if the assistant is supported or excluded
@ -12,8 +13,9 @@ const { handleAbortError } = require('~/server/middleware/abortMiddleware');
const validateAssistant = async (req, res, next) => { const validateAssistant = async (req, res, next) => {
const { endpoint, conversationId, assistant_id, messageId } = req.body; const { endpoint, conversationId, assistant_id, messageId } = req.body;
const appConfig = await getAppConfig({ role: req.user?.role });
/** @type {Partial<TAssistantEndpoint>} */ /** @type {Partial<TAssistantEndpoint>} */
const assistantsConfig = req.app.locals?.[endpoint]; const assistantsConfig = appConfig?.[endpoint];
if (!assistantsConfig) { if (!assistantsConfig) {
return next(); return next();
} }

View file

@ -1,4 +1,5 @@
const { SystemRoles } = require('librechat-data-provider'); const { SystemRoles } = require('librechat-data-provider');
const { getAppConfig } = require('~/server/services/Config/getAppConfig');
const { getAssistant } = require('~/models/Assistant'); const { getAssistant } = require('~/models/Assistant');
/** /**
@ -20,8 +21,9 @@ const validateAuthor = async ({ req, openai, overrideEndpoint, overrideAssistant
const assistant_id = const assistant_id =
overrideAssistantId ?? req.params.id ?? req.body.assistant_id ?? req.query.assistant_id; overrideAssistantId ?? req.params.id ?? req.body.assistant_id ?? req.query.assistant_id;
const appConfig = await getAppConfig({ role: req.user?.role });
/** @type {Partial<TAssistantEndpoint>} */ /** @type {Partial<TAssistantEndpoint>} */
const assistantsConfig = req.app.locals?.[endpoint]; const assistantsConfig = appConfig?.[endpoint];
if (!assistantsConfig) { if (!assistantsConfig) {
return; return;
} }

View file

@ -7,6 +7,7 @@ const {
parseCompactConvo, parseCompactConvo,
} = require('librechat-data-provider'); } = require('librechat-data-provider');
const azureAssistants = require('~/server/services/Endpoints/azureAssistants'); const azureAssistants = require('~/server/services/Endpoints/azureAssistants');
const { getAppConfig } = require('~/server/services/Config/getAppConfig');
const assistants = require('~/server/services/Endpoints/assistants'); const assistants = require('~/server/services/Endpoints/assistants');
const { processFiles } = require('~/server/services/Files/process'); const { processFiles } = require('~/server/services/Files/process');
const anthropic = require('~/server/services/Endpoints/anthropic'); const anthropic = require('~/server/services/Endpoints/anthropic');
@ -40,9 +41,10 @@ async function buildEndpointOption(req, res, next) {
return handleError(res, { text: 'Error parsing conversation' }); return handleError(res, { text: 'Error parsing conversation' });
} }
if (req.app.locals.modelSpecs?.list && req.app.locals.modelSpecs?.enforce) { const appConfig = await getAppConfig({ role: req.user?.role });
if (appConfig.modelSpecs?.list && appConfig.modelSpecs?.enforce) {
/** @type {{ list: TModelSpec[] }}*/ /** @type {{ list: TModelSpec[] }}*/
const { list } = req.app.locals.modelSpecs; const { list } = appConfig.modelSpecs;
const { spec } = parsedBody; const { spec } = parsedBody;
if (!spec) { if (!spec) {

View file

@ -1,5 +1,6 @@
const cookies = require('cookie'); const cookies = require('cookie');
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken');
const { getAppConfig } = require('~/server/services/Config/getAppConfig');
const { logger } = require('~/config'); const { logger } = require('~/config');
const OBJECT_ID_LENGTH = 24; const OBJECT_ID_LENGTH = 24;
@ -24,8 +25,9 @@ function isValidObjectId(id) {
* Middleware to validate image request. * Middleware to validate image request.
* Must be set by `secureImageLinks` via custom config file. * Must be set by `secureImageLinks` via custom config file.
*/ */
function validateImageRequest(req, res, next) { async function validateImageRequest(req, res, next) {
if (!req.app.locals.secureImageLinks) { const appConfig = await getAppConfig({ role: req.user?.role });
if (!appConfig.secureImageLinks) {
return next(); return next();
} }

View file

@ -6,6 +6,7 @@ const { getOpenAIClient } = require('~/server/controllers/assistants/helpers');
const { updateAction, getActions, deleteAction } = require('~/models/Action'); const { updateAction, getActions, deleteAction } = require('~/models/Action');
const { updateAssistantDoc, getAssistant } = require('~/models/Assistant'); const { updateAssistantDoc, getAssistant } = require('~/models/Assistant');
const { isActionDomainAllowed } = require('~/server/services/domains'); const { isActionDomainAllowed } = require('~/server/services/domains');
const { getAppConfig } = require('~/server/services/Config');
const { logger } = require('~/config'); const { logger } = require('~/config');
const router = express.Router(); const router = express.Router();
@ -20,6 +21,7 @@ const router = express.Router();
* @returns {Object} 200 - success response - application/json * @returns {Object} 200 - success response - application/json
*/ */
router.post('/:assistant_id', async (req, res) => { router.post('/:assistant_id', async (req, res) => {
const appConfig = await getAppConfig({ role: req.user?.role });
try { try {
const { assistant_id } = req.params; const { assistant_id } = req.params;
@ -125,7 +127,7 @@ router.post('/:assistant_id', async (req, res) => {
} }
/* Map Azure OpenAI model to the assistant as defined by config */ /* Map Azure OpenAI model to the assistant as defined by config */
if (req.app.locals[EModelEndpoint.azureOpenAI]?.assistants) { if (appConfig[EModelEndpoint.azureOpenAI]?.assistants) {
updatedAssistant = { updatedAssistant = {
...updatedAssistant, ...updatedAssistant,
model: req.body.model, model: req.body.model,

View file

@ -3,6 +3,7 @@ const { isEnabled } = require('@librechat/api');
const { logger } = require('@librechat/data-schemas'); const { logger } = require('@librechat/data-schemas');
const { CacheKeys, defaultSocialLogins, Constants } = require('librechat-data-provider'); const { CacheKeys, defaultSocialLogins, Constants } = require('librechat-data-provider');
const { getCustomConfig } = require('~/server/services/Config/getCustomConfig'); const { getCustomConfig } = require('~/server/services/Config/getCustomConfig');
const { getAppConfig } = require('~/server/services/Config/getAppConfig');
const { getLdapConfig } = require('~/server/services/Config/ldap'); const { getLdapConfig } = require('~/server/services/Config/ldap');
const { getProjectByName } = require('~/models/Project'); const { getProjectByName } = require('~/models/Project');
const { getMCPManager } = require('~/config'); const { getMCPManager } = require('~/config');
@ -43,6 +44,8 @@ router.get('/', async function (req, res) {
const ldap = getLdapConfig(); const ldap = getLdapConfig();
try { try {
const appConfig = await getAppConfig({ role: req.user?.role });
const isOpenIdEnabled = const isOpenIdEnabled =
!!process.env.OPENID_CLIENT_ID && !!process.env.OPENID_CLIENT_ID &&
!!process.env.OPENID_CLIENT_SECRET && !!process.env.OPENID_CLIENT_SECRET &&
@ -58,7 +61,7 @@ router.get('/', async function (req, res) {
/** @type {TStartupConfig} */ /** @type {TStartupConfig} */
const payload = { const payload = {
appTitle: process.env.APP_TITLE || 'LibreChat', appTitle: process.env.APP_TITLE || 'LibreChat',
socialLogins: req.app.locals.socialLogins ?? defaultSocialLogins, socialLogins: appConfig.socialLogins ?? defaultSocialLogins,
discordLoginEnabled: !!process.env.DISCORD_CLIENT_ID && !!process.env.DISCORD_CLIENT_SECRET, discordLoginEnabled: !!process.env.DISCORD_CLIENT_ID && !!process.env.DISCORD_CLIENT_SECRET,
facebookLoginEnabled: facebookLoginEnabled:
!!process.env.FACEBOOK_CLIENT_ID && !!process.env.FACEBOOK_CLIENT_SECRET, !!process.env.FACEBOOK_CLIENT_ID && !!process.env.FACEBOOK_CLIENT_SECRET,
@ -91,10 +94,10 @@ router.get('/', async function (req, res) {
isEnabled(process.env.SHOW_BIRTHDAY_ICON) || isEnabled(process.env.SHOW_BIRTHDAY_ICON) ||
process.env.SHOW_BIRTHDAY_ICON === '', process.env.SHOW_BIRTHDAY_ICON === '',
helpAndFaqURL: process.env.HELP_AND_FAQ_URL || 'https://librechat.ai', helpAndFaqURL: process.env.HELP_AND_FAQ_URL || 'https://librechat.ai',
interface: req.app.locals.interfaceConfig, interface: appConfig.interfaceConfig,
turnstile: req.app.locals.turnstileConfig, turnstile: appConfig.turnstileConfig,
modelSpecs: req.app.locals.modelSpecs, modelSpecs: appConfig.modelSpecs,
balance: req.app.locals.balance, balance: appConfig.balance,
sharedLinksEnabled, sharedLinksEnabled,
publicSharedLinksEnabled, publicSharedLinksEnabled,
analyticsGtmId: process.env.ANALYTICS_GTM_ID, analyticsGtmId: process.env.ANALYTICS_GTM_ID,
@ -128,8 +131,7 @@ router.get('/', async function (req, res) {
} }
} }
/** @type {TCustomConfig['webSearch']} */ const webSearchConfig = appConfig.webSearch;
const webSearchConfig = req.app.locals.webSearch;
if ( if (
webSearchConfig != null && webSearchConfig != null &&
(webSearchConfig.searchProvider || (webSearchConfig.searchProvider ||

View file

@ -2,14 +2,16 @@ const fs = require('fs').promises;
const express = require('express'); const express = require('express');
const { getStrategyFunctions } = require('~/server/services/Files/strategies'); const { getStrategyFunctions } = require('~/server/services/Files/strategies');
const { resizeAvatar } = require('~/server/services/Files/images/avatar'); const { resizeAvatar } = require('~/server/services/Files/images/avatar');
const { filterFile } = require('~/server/services/Files/process');
const { getFileStrategy } = require('~/server/utils/getFileStrategy'); const { getFileStrategy } = require('~/server/utils/getFileStrategy');
const { filterFile } = require('~/server/services/Files/process');
const { getAppConfig } = require('~/server/services/Config');
const { logger } = require('~/config'); const { logger } = require('~/config');
const router = express.Router(); const router = express.Router();
router.post('/', async (req, res) => { router.post('/', async (req, res) => {
try { try {
const appConfig = await getAppConfig({ role: req.user?.role });
filterFile({ req, file: req.file, image: true, isAvatar: true }); filterFile({ req, file: req.file, image: true, isAvatar: true });
const userId = req.user.id; const userId = req.user.id;
const { manual } = req.body; const { manual } = req.body;
@ -19,8 +21,8 @@ router.post('/', async (req, res) => {
throw new Error('User ID is undefined'); throw new Error('User ID is undefined');
} }
const fileStrategy = getFileStrategy(req.app.locals, { isAvatar: true }); const fileStrategy = getFileStrategy(appConfig, { isAvatar: true });
const desiredFormat = req.app.locals.imageOutputType; const desiredFormat = appConfig.imageOutputType;
const resizedBuffer = await resizeAvatar({ const resizedBuffer = await resizeAvatar({
userId, userId,
input, input,
@ -39,7 +41,7 @@ router.post('/', async (req, res) => {
try { try {
await fs.unlink(req.file.path); await fs.unlink(req.file.path);
logger.debug('[/files/images/avatar] Temp. image upload file deleted'); logger.debug('[/files/images/avatar] Temp. image upload file deleted');
} catch (error) { } catch {
logger.debug('[/files/images/avatar] Temp. image upload file already deleted'); logger.debug('[/files/images/avatar] Temp. image upload file already deleted');
} }
} }

View file

@ -26,6 +26,7 @@ const { loadAuthValues } = require('~/server/services/Tools/credentials');
const { refreshS3FileUrls } = require('~/server/services/Files/S3/crud'); const { refreshS3FileUrls } = require('~/server/services/Files/S3/crud');
const { hasAccessToFilesViaAgent } = require('~/server/services/Files'); const { hasAccessToFilesViaAgent } = require('~/server/services/Files');
const { getFiles, batchUpdateFiles } = require('~/models/File'); const { getFiles, batchUpdateFiles } = require('~/models/File');
const { getAppConfig } = require('~/server/services/Config');
const { cleanFileName } = require('~/server/utils/files'); const { cleanFileName } = require('~/server/utils/files');
const { getAssistant } = require('~/models/Assistant'); const { getAssistant } = require('~/models/Assistant');
const { getAgent } = require('~/models/Agent'); const { getAgent } = require('~/models/Agent');
@ -36,8 +37,9 @@ const router = express.Router();
router.get('/', async (req, res) => { router.get('/', async (req, res) => {
try { try {
const appConfig = await getAppConfig({ role: req.user?.role });
const files = await getFiles({ user: req.user.id }); const files = await getFiles({ user: req.user.id });
if (req.app.locals.fileStrategy === FileSources.s3) { if (appConfig.fileStrategy === FileSources.s3) {
try { try {
const cache = getLogStores(CacheKeys.S3_EXPIRY_INTERVAL); const cache = getLogStores(CacheKeys.S3_EXPIRY_INTERVAL);
const alreadyChecked = await cache.get(req.user.id); const alreadyChecked = await cache.get(req.user.id);
@ -114,7 +116,8 @@ router.get('/agent/:agent_id', async (req, res) => {
router.get('/config', async (req, res) => { router.get('/config', async (req, res) => {
try { try {
res.status(200).json(req.app.locals.fileConfig); const appConfig = await getAppConfig({ role: req.user?.role });
res.status(200).json(appConfig.fileConfig);
} catch (error) { } catch (error) {
logger.error('[/files] Error getting fileConfig', error); logger.error('[/files] Error getting fileConfig', error);
res.status(400).json({ message: 'Error in request', error: error.message }); res.status(400).json({ message: 'Error in request', error: error.message });

View file

@ -7,11 +7,13 @@ const {
processImageFile, processImageFile,
processAgentFileUpload, processAgentFileUpload,
} = require('~/server/services/Files/process'); } = require('~/server/services/Files/process');
const { getAppConfig } = require('~/server/services/Config');
const { logger } = require('~/config'); const { logger } = require('~/config');
const router = express.Router(); const router = express.Router();
router.post('/', async (req, res) => { router.post('/', async (req, res) => {
const appConfig = await getAppConfig({ role: req.user?.role });
const metadata = req.body; const metadata = req.body;
try { try {
@ -30,7 +32,7 @@ router.post('/', async (req, res) => {
logger.error('[/files/images] Error processing file:', error); logger.error('[/files/images] Error processing file:', error);
try { try {
const filepath = path.join( const filepath = path.join(
req.app.locals.paths.imageOutput, appConfig.paths.imageOutput,
req.user.id, req.user.id,
path.basename(req.file.filename), path.basename(req.file.filename),
); );
@ -43,7 +45,7 @@ router.post('/', async (req, res) => {
try { try {
await fs.unlink(req.file.path); await fs.unlink(req.file.path);
logger.debug('[/files/images] Temp. image upload file deleted'); logger.debug('[/files/images] Temp. image upload file deleted');
} catch (error) { } catch {
logger.debug('[/files/images] Temp. image upload file already deleted'); logger.debug('[/files/images] Temp. image upload file already deleted');
} }
} }

View file

@ -4,11 +4,12 @@ const crypto = require('crypto');
const multer = require('multer'); const multer = require('multer');
const { sanitizeFilename } = require('@librechat/api'); const { sanitizeFilename } = require('@librechat/api');
const { fileConfig: defaultFileConfig, mergeFileConfig } = require('librechat-data-provider'); const { fileConfig: defaultFileConfig, mergeFileConfig } = require('librechat-data-provider');
const { getCustomConfig } = require('~/server/services/Config'); const { getCustomConfig, getAppConfig } = require('~/server/services/Config');
const storage = multer.diskStorage({ const storage = multer.diskStorage({
destination: function (req, file, cb) { destination: async function (req, file, cb) {
const outputPath = path.join(req.app.locals.paths.uploads, 'temp', req.user.id); const appConfig = await getAppConfig({ role: req.user?.role });
const outputPath = path.join(appConfig.paths.uploads, 'temp', req.user.id);
if (!fs.existsSync(outputPath)) { if (!fs.existsSync(outputPath)) {
fs.mkdirSync(outputPath, { recursive: true }); fs.mkdirSync(outputPath, { recursive: true });
} }

View file

@ -8,6 +8,7 @@ const {
deleteMemory, deleteMemory,
setMemory, setMemory,
} = require('~/models'); } = require('~/models');
const { getAppConfig } = require('~/server/services/Config');
const { requireJwtAuth } = require('~/server/middleware'); const { requireJwtAuth } = require('~/server/middleware');
const { getRoleByName } = require('~/models/Role'); const { getRoleByName } = require('~/models/Role');
@ -60,7 +61,8 @@ router.get('/', checkMemoryRead, async (req, res) => {
return sum + (memory.tokenCount || 0); return sum + (memory.tokenCount || 0);
}, 0); }, 0);
const memoryConfig = req.app.locals?.memory; const appConfig = await getAppConfig({ role: req.user?.role });
const memoryConfig = appConfig?.memory;
const tokenLimit = memoryConfig?.tokenLimit; const tokenLimit = memoryConfig?.tokenLimit;
const charLimit = memoryConfig?.charLimit || 10000; const charLimit = memoryConfig?.charLimit || 10000;
@ -98,7 +100,8 @@ router.post('/', memoryPayloadLimit, checkMemoryCreate, async (req, res) => {
return res.status(400).json({ error: 'Value is required and must be a non-empty string.' }); return res.status(400).json({ error: 'Value is required and must be a non-empty string.' });
} }
const memoryConfig = req.app.locals?.memory; const appConfig = await getAppConfig({ role: req.user?.role });
const memoryConfig = appConfig?.memory;
const charLimit = memoryConfig?.charLimit || 10000; const charLimit = memoryConfig?.charLimit || 10000;
if (key.length > 1000) { if (key.length > 1000) {
@ -117,6 +120,9 @@ router.post('/', memoryPayloadLimit, checkMemoryCreate, async (req, res) => {
const tokenCount = Tokenizer.getTokenCount(value, 'o200k_base'); const tokenCount = Tokenizer.getTokenCount(value, 'o200k_base');
const memories = await getAllUserMemories(req.user.id); const memories = await getAllUserMemories(req.user.id);
const appConfig = await getAppConfig({ role: req.user?.role });
const memoryConfig = appConfig?.memory;
const tokenLimit = memoryConfig?.tokenLimit; const tokenLimit = memoryConfig?.tokenLimit;
if (tokenLimit) { if (tokenLimit) {
@ -200,8 +206,8 @@ router.patch('/:key', memoryPayloadLimit, checkMemoryUpdate, async (req, res) =>
} }
const newKey = bodyKey || urlKey; const newKey = bodyKey || urlKey;
const appConfig = await getAppConfig({ role: req.user?.role });
const memoryConfig = req.app.locals?.memory; const memoryConfig = appConfig?.memory;
const charLimit = memoryConfig?.charLimit || 10000; const charLimit = memoryConfig?.charLimit || 10000;
if (newKey.length > 1000) { if (newKey.length > 1000) {

View file

@ -21,6 +21,7 @@ const { ensureDefaultCategories, seedDefaultRoles, initializeRoles } = require('
const { azureAssistantsDefaults, assistantsConfigSetup } = require('./start/assistants'); const { azureAssistantsDefaults, assistantsConfigSetup } = require('./start/assistants');
const { initializeAzureBlobService } = require('./Files/Azure/initialize'); const { initializeAzureBlobService } = require('./Files/Azure/initialize');
const { initializeFirebase } = require('./Files/Firebase/initialize'); const { initializeFirebase } = require('./Files/Firebase/initialize');
const { initializeAppConfig } = require('./Config/getAppConfig');
const loadCustomConfig = require('./Config/loadCustomConfig'); const loadCustomConfig = require('./Config/loadCustomConfig');
const handleRateLimits = require('./Config/handleRateLimits'); const handleRateLimits = require('./Config/handleRateLimits');
const { loadDefaultInterface } = require('./start/interface'); const { loadDefaultInterface } = require('./start/interface');
@ -35,9 +36,8 @@ const paths = require('~/config/paths');
/** /**
* Loads custom config and initializes app-wide variables. * Loads custom config and initializes app-wide variables.
* @function AppService * @function AppService
* @param {Express.Application} app - The Express application object.
*/ */
const AppService = async (app) => { const AppService = async () => {
await initializeRoles(); await initializeRoles();
await seedDefaultRoles(); await seedDefaultRoles();
await ensureDefaultCategories(); await ensureDefaultCategories();
@ -109,10 +109,11 @@ const AppService = async (app) => {
const agentsDefaults = agentsConfigSetup(config); const agentsDefaults = agentsConfigSetup(config);
if (!Object.keys(config).length) { if (!Object.keys(config).length) {
app.locals = { const appConfig = {
...defaultLocals, ...defaultLocals,
[EModelEndpoint.agents]: agentsDefaults, [EModelEndpoint.agents]: agentsDefaults,
}; };
await initializeAppConfig(appConfig);
return; return;
} }
@ -167,13 +168,15 @@ const AppService = async (app) => {
endpointLocals.all = endpoints.all; endpointLocals.all = endpoints.all;
} }
app.locals = { const appConfig = {
...defaultLocals, ...defaultLocals,
fileConfig: config?.fileConfig, fileConfig: config?.fileConfig,
secureImageLinks: config?.secureImageLinks, secureImageLinks: config?.secureImageLinks,
modelSpecs: processModelSpecs(endpoints, config.modelSpecs, interfaceConfig), modelSpecs: processModelSpecs(endpoints, config.modelSpecs, interfaceConfig),
...endpointLocals, ...endpointLocals,
}; };
await initializeAppConfig(appConfig);
}; };
module.exports = AppService; module.exports = AppService;

View file

@ -16,6 +16,7 @@ const { retrieveAndProcessFile } = require('~/server/services/Files/process');
const { processRequiredActions } = require('~/server/services/ToolService'); const { processRequiredActions } = require('~/server/services/ToolService');
const { RunManager, waitForRun } = require('~/server/services/Runs'); const { RunManager, waitForRun } = require('~/server/services/Runs');
const { processMessages } = require('~/server/services/Threads'); const { processMessages } = require('~/server/services/Threads');
const { getAppConfig } = require('~/server/services/Config');
const { createOnProgress } = require('~/server/utils'); const { createOnProgress } = require('~/server/utils');
const { TextStream } = require('~/app/clients'); const { TextStream } = require('~/app/clients');
@ -350,6 +351,7 @@ async function runAssistant({
accumulatedMessages = [], accumulatedMessages = [],
in_progress: inProgress, in_progress: inProgress,
}) { }) {
const appConfig = await getAppConfig({ role: openai.req.user?.role });
let steps = accumulatedSteps; let steps = accumulatedSteps;
let messages = accumulatedMessages; let messages = accumulatedMessages;
const in_progress = inProgress ?? createInProgressHandler(openai, thread_id, messages); const in_progress = inProgress ?? createInProgressHandler(openai, thread_id, messages);
@ -397,7 +399,7 @@ async function runAssistant({
const { endpoint = EModelEndpoint.azureAssistants } = openai.req.body; const { endpoint = EModelEndpoint.azureAssistants } = openai.req.body;
/** @type {TCustomConfig.endpoints.assistants} */ /** @type {TCustomConfig.endpoints.assistants} */
const assistantsEndpointConfig = openai.req.app.locals?.[endpoint] ?? {}; const assistantsEndpointConfig = appConfig?.[endpoint] ?? {};
const { pollIntervalMs, timeoutMs } = assistantsEndpointConfig; const { pollIntervalMs, timeoutMs } = assistantsEndpointConfig;
const run = await waitForRun({ const run = await waitForRun({

View file

@ -0,0 +1,110 @@
const { logger } = require('@librechat/data-schemas');
const { CacheKeys } = require('librechat-data-provider');
const getLogStores = require('~/cache/getLogStores');
/**
* @typedef {Object} AppConfig
* @property {import('librechat-data-provider').TCustomConfig} config - The main custom configuration
* @property {import('librechat-data-provider').TCustomConfig['ocr']} ocr - OCR configuration
* @property {Object} paths - File paths configuration
* @property {import('librechat-data-provider').TMemoryConfig | undefined} memory - Memory configuration
* @property {import('librechat-data-provider').TCustomConfig['webSearch']} webSearch - Web search configuration
* @property {string} fileStrategy - File storage strategy ('local', 's3', 'firebase', 'azure_blob')
* @property {Array} socialLogins - Social login configurations
* @property {string[]} [filteredTools] - Admin-filtered tools
* @property {string[]} [includedTools] - Admin-included tools
* @property {string} imageOutputType - Image output type configuration
* @property {import('librechat-data-provider').TCustomConfig['interface']} interfaceConfig - Interface configuration
* @property {import('librechat-data-provider').TCustomConfig['registration']} turnstileConfig - Turnstile configuration
* @property {import('librechat-data-provider').TCustomConfig['balance']} balance - Balance configuration
* @property {import('librechat-data-provider').TCustomConfig['mcpServers'] | null} mcpConfig - MCP server configuration
* @property {import('librechat-data-provider').TCustomConfig['fileConfig']} [fileConfig] - File configuration
* @property {import('librechat-data-provider').TCustomConfig['secureImageLinks']} [secureImageLinks] - Secure image links configuration
* @property {import('librechat-data-provider').TCustomConfig['modelSpecs'] | undefined} [modelSpecs] - Processed model specifications
* @property {import('librechat-data-provider').TEndpoint} [openAI] - OpenAI endpoint configuration
* @property {import('librechat-data-provider').TEndpoint} [google] - Google endpoint configuration
* @property {import('librechat-data-provider').TEndpoint} [bedrock] - Bedrock endpoint configuration
* @property {import('librechat-data-provider').TEndpoint} [anthropic] - Anthropic endpoint configuration
* @property {import('librechat-data-provider').TEndpoint} [gptPlugins] - GPT plugins endpoint configuration
* @property {import('librechat-data-provider').TEndpoint} [azureOpenAI] - Azure OpenAI endpoint configuration
* @property {import('librechat-data-provider').TEndpoint} [assistants] - Assistants endpoint configuration
* @property {import('librechat-data-provider').TEndpoint} [azureAssistants] - Azure assistants endpoint configuration
* @property {import('librechat-data-provider').TEndpoint} [agents] - Agents endpoint configuration
* @property {import('librechat-data-provider').TEndpoint} [all] - Global endpoint configuration
*/
/**
* Get the app configuration based on user context
* @param {Object} [options]
* @param {string} [options.role] - User role for role-based config
* @param {boolean} [options.refresh] - Force refresh the cache
* @returns {Promise<AppConfig>}
*/
async function getAppConfig(options = {}) {
const { role, refresh } = options;
const cache = getLogStores(CacheKeys.CONFIG_STORE);
const cacheKey = role ? `${CacheKeys.APP_CONFIG}:${role}` : CacheKeys.APP_CONFIG;
if (!refresh) {
const cached = await cache.get(cacheKey);
if (cached) {
return cached;
}
}
const baseConfig = await cache.get(CacheKeys.APP_CONFIG);
if (!baseConfig) {
throw new Error('App configuration not initialized. Please ensure AppService has been called.');
}
// For now, return the base config
// In the future, this is where we'll apply role-based modifications
if (role) {
// TODO: Apply role-based config modifications
// const roleConfig = await applyRoleBasedConfig(baseConfig, role);
// await cache.set(cacheKey, roleConfig);
// return roleConfig;
}
return baseConfig;
}
/**
* Cache the app configuration
* @param {AppConfig} config - The configuration to cache
* @returns {Promise<void>}
*/
async function cacheAppConfig(config) {
const cache = getLogStores(CacheKeys.CONFIG_STORE);
await cache.set(CacheKeys.APP_CONFIG, config);
logger.debug('[getAppConfig] App configuration cached');
}
/**
* Clear the app configuration cache
* @returns {Promise<boolean>}
*/
async function clearAppConfigCache() {
const cache = getLogStores(CacheKeys.CONFIG_STORE);
const cacheKey = CacheKeys.APP_CONFIG;
return await cache.delete(cacheKey);
}
/**
* Initialize the app configuration during startup
* @param {AppConfig} config - The initial configuration to store
* @returns {Promise<void>}
*/
async function initializeAppConfig(config) {
const cache = getLogStores(CacheKeys.CONFIG_STORE);
await cache.set(CacheKeys.APP_CONFIG, config);
logger.debug('[getAppConfig] App configuration initialized');
}
module.exports = {
getAppConfig,
cacheAppConfig,
clearAppConfigCache,
initializeAppConfig,
};

View file

@ -8,6 +8,7 @@ const {
const loadDefaultEndpointsConfig = require('./loadDefaultEConfig'); const loadDefaultEndpointsConfig = require('./loadDefaultEConfig');
const loadConfigEndpoints = require('./loadConfigEndpoints'); const loadConfigEndpoints = require('./loadConfigEndpoints');
const getLogStores = require('~/cache/getLogStores'); const getLogStores = require('~/cache/getLogStores');
const { getAppConfig } = require('./getAppConfig');
/** /**
* *
@ -23,12 +24,13 @@ async function getEndpointsConfig(req) {
const defaultEndpointsConfig = await loadDefaultEndpointsConfig(req); const defaultEndpointsConfig = await loadDefaultEndpointsConfig(req);
const customConfigEndpoints = await loadConfigEndpoints(req); const customConfigEndpoints = await loadConfigEndpoints(req);
const appConfig = await getAppConfig({ role: req.user?.role });
/** @type {TEndpointsConfig} */ /** @type {TEndpointsConfig} */
const mergedConfig = { ...defaultEndpointsConfig, ...customConfigEndpoints }; const mergedConfig = { ...defaultEndpointsConfig, ...customConfigEndpoints };
if (mergedConfig[EModelEndpoint.assistants] && req.app.locals?.[EModelEndpoint.assistants]) { if (mergedConfig[EModelEndpoint.assistants] && appConfig?.[EModelEndpoint.assistants]) {
const { disableBuilder, retrievalModels, capabilities, version, ..._rest } = const { disableBuilder, retrievalModels, capabilities, version, ..._rest } =
req.app.locals[EModelEndpoint.assistants]; appConfig[EModelEndpoint.assistants];
mergedConfig[EModelEndpoint.assistants] = { mergedConfig[EModelEndpoint.assistants] = {
...mergedConfig[EModelEndpoint.assistants], ...mergedConfig[EModelEndpoint.assistants],
@ -38,9 +40,9 @@ async function getEndpointsConfig(req) {
capabilities, capabilities,
}; };
} }
if (mergedConfig[EModelEndpoint.agents] && req.app.locals?.[EModelEndpoint.agents]) { if (mergedConfig[EModelEndpoint.agents] && appConfig?.[EModelEndpoint.agents]) {
const { disableBuilder, capabilities, allowedProviders, ..._rest } = const { disableBuilder, capabilities, allowedProviders, ..._rest } =
req.app.locals[EModelEndpoint.agents]; appConfig[EModelEndpoint.agents];
mergedConfig[EModelEndpoint.agents] = { mergedConfig[EModelEndpoint.agents] = {
...mergedConfig[EModelEndpoint.agents], ...mergedConfig[EModelEndpoint.agents],
@ -50,12 +52,9 @@ async function getEndpointsConfig(req) {
}; };
} }
if ( if (mergedConfig[EModelEndpoint.azureAssistants] && appConfig?.[EModelEndpoint.azureAssistants]) {
mergedConfig[EModelEndpoint.azureAssistants] &&
req.app.locals?.[EModelEndpoint.azureAssistants]
) {
const { disableBuilder, retrievalModels, capabilities, version, ..._rest } = const { disableBuilder, retrievalModels, capabilities, version, ..._rest } =
req.app.locals[EModelEndpoint.azureAssistants]; appConfig[EModelEndpoint.azureAssistants];
mergedConfig[EModelEndpoint.azureAssistants] = { mergedConfig[EModelEndpoint.azureAssistants] = {
...mergedConfig[EModelEndpoint.azureAssistants], ...mergedConfig[EModelEndpoint.azureAssistants],
@ -66,8 +65,8 @@ async function getEndpointsConfig(req) {
}; };
} }
if (mergedConfig[EModelEndpoint.bedrock] && req.app.locals?.[EModelEndpoint.bedrock]) { if (mergedConfig[EModelEndpoint.bedrock] && appConfig?.[EModelEndpoint.bedrock]) {
const { availableRegions } = req.app.locals[EModelEndpoint.bedrock]; const { availableRegions } = appConfig[EModelEndpoint.bedrock];
mergedConfig[EModelEndpoint.bedrock] = { mergedConfig[EModelEndpoint.bedrock] = {
...mergedConfig[EModelEndpoint.bedrock], ...mergedConfig[EModelEndpoint.bedrock],
availableRegions, availableRegions,

View file

@ -1,3 +1,4 @@
const getAppConfig = require('./getAppConfig');
const { config } = require('./EndpointService'); const { config } = require('./EndpointService');
const getCachedTools = require('./getCachedTools'); const getCachedTools = require('./getCachedTools');
const getCustomConfig = require('./getCustomConfig'); const getCustomConfig = require('./getCustomConfig');
@ -15,6 +16,7 @@ module.exports = {
loadDefaultModels, loadDefaultModels,
loadOverrideConfig, loadOverrideConfig,
loadAsyncEndpoints, loadAsyncEndpoints,
...getAppConfig,
...getCachedTools, ...getCachedTools,
...getCustomConfig, ...getCustomConfig,
...getEndpointsConfig, ...getEndpointsConfig,

View file

@ -2,6 +2,7 @@ const path = require('path');
const { logger } = require('@librechat/data-schemas'); const { logger } = require('@librechat/data-schemas');
const { loadServiceKey, isUserProvided } = require('@librechat/api'); const { loadServiceKey, isUserProvided } = require('@librechat/api');
const { EModelEndpoint } = require('librechat-data-provider'); const { EModelEndpoint } = require('librechat-data-provider');
const { getAppConfig } = require('./getAppConfig');
const { config } = require('./EndpointService'); const { config } = require('./EndpointService');
const { openAIApiKey, azureOpenAIApiKey, useAzurePlugins, userProvidedOpenAI, googleKey } = config; const { openAIApiKey, azureOpenAIApiKey, useAzurePlugins, userProvidedOpenAI, googleKey } = config;
@ -11,6 +12,7 @@ const { openAIApiKey, azureOpenAIApiKey, useAzurePlugins, userProvidedOpenAI, go
* @param {Express.Request} req - The request object * @param {Express.Request} req - The request object
*/ */
async function loadAsyncEndpoints(req) { async function loadAsyncEndpoints(req) {
const appConfig = await getAppConfig({ role: req.user?.role });
let serviceKey, googleUserProvides; let serviceKey, googleUserProvides;
/** Check if GOOGLE_KEY is provided at all(including 'user_provided') */ /** Check if GOOGLE_KEY is provided at all(including 'user_provided') */
@ -34,7 +36,7 @@ async function loadAsyncEndpoints(req) {
const google = serviceKey || isGoogleKeyProvided ? { userProvide: googleUserProvides } : false; const google = serviceKey || isGoogleKeyProvided ? { userProvide: googleUserProvides } : false;
const useAzure = req.app.locals[EModelEndpoint.azureOpenAI]?.plugins; const useAzure = appConfig[EModelEndpoint.azureOpenAI]?.plugins;
const gptPlugins = const gptPlugins =
useAzure || openAIApiKey || azureOpenAIApiKey useAzure || openAIApiKey || azureOpenAIApiKey
? { ? {

View file

@ -1,6 +1,7 @@
const { EModelEndpoint, extractEnvVariable } = require('librechat-data-provider'); const { EModelEndpoint, extractEnvVariable } = require('librechat-data-provider');
const { isUserProvided, normalizeEndpointName } = require('~/server/utils'); const { isUserProvided, normalizeEndpointName } = require('~/server/utils');
const { getCustomConfig } = require('./getCustomConfig'); const { getCustomConfig } = require('./getCustomConfig');
const { getAppConfig } = require('./getAppConfig');
/** /**
* Load config endpoints from the cached configuration object * Load config endpoints from the cached configuration object
@ -14,6 +15,8 @@ async function loadConfigEndpoints(req) {
return {}; return {};
} }
const appConfig = await getAppConfig({ role: req.user?.role });
const { endpoints = {} } = customConfig ?? {}; const { endpoints = {} } = customConfig ?? {};
const endpointsConfig = {}; const endpointsConfig = {};
@ -53,14 +56,14 @@ async function loadConfigEndpoints(req) {
} }
} }
if (req.app.locals[EModelEndpoint.azureOpenAI]) { if (appConfig[EModelEndpoint.azureOpenAI]) {
/** @type {Omit<TConfig, 'order'>} */ /** @type {Omit<TConfig, 'order'>} */
endpointsConfig[EModelEndpoint.azureOpenAI] = { endpointsConfig[EModelEndpoint.azureOpenAI] = {
userProvide: false, userProvide: false,
}; };
} }
if (req.app.locals[EModelEndpoint.azureOpenAI]?.assistants) { if (appConfig[EModelEndpoint.azureOpenAI]?.assistants) {
/** @type {Omit<TConfig, 'order'>} */ /** @type {Omit<TConfig, 'order'>} */
endpointsConfig[EModelEndpoint.azureAssistants] = { endpointsConfig[EModelEndpoint.azureAssistants] = {
userProvide: false, userProvide: false,

View file

@ -2,6 +2,7 @@ const { EModelEndpoint, extractEnvVariable } = require('librechat-data-provider'
const { isUserProvided, normalizeEndpointName } = require('~/server/utils'); const { isUserProvided, normalizeEndpointName } = require('~/server/utils');
const { fetchModels } = require('~/server/services/ModelService'); const { fetchModels } = require('~/server/services/ModelService');
const { getCustomConfig } = require('./getCustomConfig'); const { getCustomConfig } = require('./getCustomConfig');
const { getAppConfig } = require('./getAppConfig');
/** /**
* Load config endpoints from the cached configuration object * Load config endpoints from the cached configuration object
@ -15,10 +16,11 @@ async function loadConfigModels(req) {
return {}; return {};
} }
const appConfig = await getAppConfig({ role: req.user?.role });
const { endpoints = {} } = customConfig ?? {}; const { endpoints = {} } = customConfig ?? {};
const modelsConfig = {}; const modelsConfig = {};
const azureEndpoint = endpoints[EModelEndpoint.azureOpenAI]; const azureEndpoint = endpoints[EModelEndpoint.azureOpenAI];
const azureConfig = req.app.locals[EModelEndpoint.azureOpenAI]; const azureConfig = appConfig[EModelEndpoint.azureOpenAI];
const { modelNames } = azureConfig ?? {}; const { modelNames } = azureConfig ?? {};
if (modelNames && azureEndpoint) { if (modelNames && azureEndpoint) {

View file

@ -16,6 +16,7 @@ const generateArtifactsPrompt = require('~/app/clients/prompts/artifacts');
const { getProviderConfig } = require('~/server/services/Endpoints'); const { getProviderConfig } = require('~/server/services/Endpoints');
const { processFiles } = require('~/server/services/Files/process'); const { processFiles } = require('~/server/services/Files/process');
const { getFiles, getToolFilesByIds } = require('~/models/File'); const { getFiles, getToolFilesByIds } = require('~/models/File');
const { getAppConfig } = require('~/server/services/Config');
const { getConvoFiles } = require('~/models/Conversation'); const { getConvoFiles } = require('~/models/Conversation');
const { getModelMaxTokens } = require('~/utils'); const { getModelMaxTokens } = require('~/utils');
@ -43,6 +44,7 @@ const initializeAgent = async ({
allowedProviders, allowedProviders,
isInitialAgent = false, isInitialAgent = false,
}) => { }) => {
const appConfig = await getAppConfig({ role: req.user?.role });
if ( if (
isAgentsEndpoint(endpointOption?.endpoint) && isAgentsEndpoint(endpointOption?.endpoint) &&
allowedProviders.size > 0 && allowedProviders.size > 0 &&
@ -84,10 +86,11 @@ const initializeAgent = async ({
const { attachments, tool_resources } = await primeResources({ const { attachments, tool_resources } = await primeResources({
req, req,
getFiles, getFiles,
appConfig,
agentId: agent.id,
attachments: currentFiles, attachments: currentFiles,
tool_resources: agent.tool_resources, tool_resources: agent.tool_resources,
requestFileSet: new Set(requestFiles?.map((file) => file.file_id)), requestFileSet: new Set(requestFiles?.map((file) => file.file_id)),
agentId: agent.id,
}); });
const provider = agent.provider; const provider = agent.provider;

View file

@ -11,9 +11,9 @@ const {
createToolEndCallback, createToolEndCallback,
getDefaultHandlers, getDefaultHandlers,
} = require('~/server/controllers/agents/callbacks'); } = require('~/server/controllers/agents/callbacks');
const { getCustomEndpointConfig, getAppConfig } = require('~/server/services/Config');
const { initializeAgent } = require('~/server/services/Endpoints/agents/agent'); const { initializeAgent } = require('~/server/services/Endpoints/agents/agent');
const { getModelsConfig } = require('~/server/controllers/ModelController'); const { getModelsConfig } = require('~/server/controllers/ModelController');
const { getCustomEndpointConfig } = require('~/server/services/Config');
const { loadAgentTools } = require('~/server/services/ToolService'); const { loadAgentTools } = require('~/server/services/ToolService');
const AgentClient = require('~/server/controllers/agents/client'); const AgentClient = require('~/server/controllers/agents/client');
const { getAgent } = require('~/models/Agent'); const { getAgent } = require('~/models/Agent');
@ -50,6 +50,7 @@ const initializeClient = async ({ req, res, endpointOption }) => {
if (!endpointOption) { if (!endpointOption) {
throw new Error('Endpoint option not provided'); throw new Error('Endpoint option not provided');
} }
const appConfig = await getAppConfig({ role: req.user?.role });
// TODO: use endpointOption to determine options/modelOptions // TODO: use endpointOption to determine options/modelOptions
/** @type {Array<UsageMetadata>} */ /** @type {Array<UsageMetadata>} */
@ -90,7 +91,7 @@ const initializeClient = async ({ req, res, endpointOption }) => {
const agentConfigs = new Map(); const agentConfigs = new Map();
/** @type {Set<string>} */ /** @type {Set<string>} */
const allowedProviders = new Set(req?.app?.locals?.[EModelEndpoint.agents]?.allowedProviders); const allowedProviders = new Set(appConfig?.[EModelEndpoint.agents]?.allowedProviders);
const loadTools = createToolLoader(); const loadTools = createToolLoader();
/** @type {Array<MongoFile>} */ /** @type {Array<MongoFile>} */
@ -144,7 +145,7 @@ const initializeClient = async ({ req, res, endpointOption }) => {
} }
} }
let endpointConfig = req.app.locals[primaryConfig.endpoint]; let endpointConfig = appConfig[primaryConfig.endpoint];
if (!isAgentsEndpoint(primaryConfig.endpoint) && !endpointConfig) { if (!isAgentsEndpoint(primaryConfig.endpoint) && !endpointConfig) {
try { try {
endpointConfig = await getCustomEndpointConfig(primaryConfig.endpoint); endpointConfig = await getCustomEndpointConfig(primaryConfig.endpoint);

View file

@ -2,8 +2,10 @@ const { EModelEndpoint } = require('librechat-data-provider');
const { getUserKey, checkUserKeyExpiry } = require('~/server/services/UserService'); const { getUserKey, checkUserKeyExpiry } = require('~/server/services/UserService');
const { getLLMConfig } = require('~/server/services/Endpoints/anthropic/llm'); const { getLLMConfig } = require('~/server/services/Endpoints/anthropic/llm');
const AnthropicClient = require('~/app/clients/AnthropicClient'); const AnthropicClient = require('~/app/clients/AnthropicClient');
const { getAppConfig } = require('~/server/services/Config');
const initializeClient = async ({ req, res, endpointOption, overrideModel, optionsOnly }) => { const initializeClient = async ({ req, res, endpointOption, overrideModel, optionsOnly }) => {
const appConfig = await getAppConfig({ role: req.user?.role });
const { ANTHROPIC_API_KEY, ANTHROPIC_REVERSE_PROXY, PROXY } = process.env; const { ANTHROPIC_API_KEY, ANTHROPIC_REVERSE_PROXY, PROXY } = process.env;
const expiresAt = req.body.key; const expiresAt = req.body.key;
const isUserProvided = ANTHROPIC_API_KEY === 'user_provided'; const isUserProvided = ANTHROPIC_API_KEY === 'user_provided';
@ -23,7 +25,7 @@ const initializeClient = async ({ req, res, endpointOption, overrideModel, optio
let clientOptions = {}; let clientOptions = {};
/** @type {undefined | TBaseEndpoint} */ /** @type {undefined | TBaseEndpoint} */
const anthropicConfig = req.app.locals[EModelEndpoint.anthropic]; const anthropicConfig = appConfig[EModelEndpoint.anthropic];
if (anthropicConfig) { if (anthropicConfig) {
clientOptions.streamRate = anthropicConfig.streamRate; clientOptions.streamRate = anthropicConfig.streamRate;
@ -31,7 +33,7 @@ const initializeClient = async ({ req, res, endpointOption, overrideModel, optio
} }
/** @type {undefined | TBaseEndpoint} */ /** @type {undefined | TBaseEndpoint} */
const allConfig = req.app.locals.all; const allConfig = appConfig.all;
if (allConfig) { if (allConfig) {
clientOptions.streamRate = allConfig.streamRate; clientOptions.streamRate = allConfig.streamRate;
} }

View file

@ -7,6 +7,7 @@ const {
getUserKeyValues, getUserKeyValues,
getUserKeyExpiry, getUserKeyExpiry,
} = require('~/server/services/UserService'); } = require('~/server/services/UserService');
const { getAppConfig } = require('~/server/services/Config');
const OAIClient = require('~/app/clients/OpenAIClient'); const OAIClient = require('~/app/clients/OpenAIClient');
class Files { class Files {
@ -48,6 +49,7 @@ class Files {
} }
const initializeClient = async ({ req, res, version, endpointOption, initAppClient = false }) => { const initializeClient = async ({ req, res, version, endpointOption, initAppClient = false }) => {
const appConfig = await getAppConfig({ role: req.user?.role });
const { PROXY, OPENAI_ORGANIZATION, AZURE_ASSISTANTS_API_KEY, AZURE_ASSISTANTS_BASE_URL } = const { PROXY, OPENAI_ORGANIZATION, AZURE_ASSISTANTS_API_KEY, AZURE_ASSISTANTS_BASE_URL } =
process.env; process.env;
@ -81,7 +83,7 @@ const initializeClient = async ({ req, res, version, endpointOption, initAppClie
}; };
/** @type {TAzureConfig | undefined} */ /** @type {TAzureConfig | undefined} */
const azureConfig = req.app.locals[EModelEndpoint.azureOpenAI]; const azureConfig = appConfig[EModelEndpoint.azureOpenAI];
/** @type {AzureOptions | undefined} */ /** @type {AzureOptions | undefined} */
let azureOptions; let azureOptions;

View file

@ -9,8 +9,10 @@ const {
removeNullishValues, removeNullishValues,
} = require('librechat-data-provider'); } = require('librechat-data-provider');
const { getUserKey, checkUserKeyExpiry } = require('~/server/services/UserService'); const { getUserKey, checkUserKeyExpiry } = require('~/server/services/UserService');
const { getAppConfig } = require('~/server/services/Config');
const getOptions = async ({ req, overrideModel, endpointOption }) => { const getOptions = async ({ req, overrideModel, endpointOption }) => {
const appConfig = await getAppConfig({ role: req.user?.role });
const { const {
BEDROCK_AWS_SECRET_ACCESS_KEY, BEDROCK_AWS_SECRET_ACCESS_KEY,
BEDROCK_AWS_ACCESS_KEY_ID, BEDROCK_AWS_ACCESS_KEY_ID,
@ -50,14 +52,14 @@ const getOptions = async ({ req, overrideModel, endpointOption }) => {
let streamRate = Constants.DEFAULT_STREAM_RATE; let streamRate = Constants.DEFAULT_STREAM_RATE;
/** @type {undefined | TBaseEndpoint} */ /** @type {undefined | TBaseEndpoint} */
const bedrockConfig = req.app.locals[EModelEndpoint.bedrock]; const bedrockConfig = appConfig[EModelEndpoint.bedrock];
if (bedrockConfig && bedrockConfig.streamRate) { if (bedrockConfig && bedrockConfig.streamRate) {
streamRate = bedrockConfig.streamRate; streamRate = bedrockConfig.streamRate;
} }
/** @type {undefined | TBaseEndpoint} */ /** @type {undefined | TBaseEndpoint} */
const allConfig = req.app.locals.all; const allConfig = appConfig.all;
if (allConfig && allConfig.streamRate) { if (allConfig && allConfig.streamRate) {
streamRate = allConfig.streamRate; streamRate = allConfig.streamRate;
} }

View file

@ -8,7 +8,7 @@ const {
const { Providers } = require('@librechat/agents'); const { Providers } = require('@librechat/agents');
const { getOpenAIConfig, createHandleLLMNewToken, resolveHeaders } = require('@librechat/api'); const { getOpenAIConfig, createHandleLLMNewToken, resolveHeaders } = require('@librechat/api');
const { getUserKeyValues, checkUserKeyExpiry } = require('~/server/services/UserService'); const { getUserKeyValues, checkUserKeyExpiry } = require('~/server/services/UserService');
const { getCustomEndpointConfig } = require('~/server/services/Config'); const { getCustomEndpointConfig, getAppConfig } = require('~/server/services/Config');
const { fetchModels } = require('~/server/services/ModelService'); const { fetchModels } = require('~/server/services/ModelService');
const OpenAIClient = require('~/app/clients/OpenAIClient'); const OpenAIClient = require('~/app/clients/OpenAIClient');
const { isUserProvided } = require('~/server/utils'); const { isUserProvided } = require('~/server/utils');
@ -17,6 +17,7 @@ const getLogStores = require('~/cache/getLogStores');
const { PROXY } = process.env; const { PROXY } = process.env;
const initializeClient = async ({ req, res, endpointOption, optionsOnly, overrideEndpoint }) => { const initializeClient = async ({ req, res, endpointOption, optionsOnly, overrideEndpoint }) => {
const appConfig = await getAppConfig({ role: req.user?.role });
const { key: expiresAt } = req.body; const { key: expiresAt } = req.body;
const endpoint = overrideEndpoint ?? req.body.endpoint; const endpoint = overrideEndpoint ?? req.body.endpoint;
@ -118,7 +119,7 @@ const initializeClient = async ({ req, res, endpointOption, optionsOnly, overrid
}; };
/** @type {undefined | TBaseEndpoint} */ /** @type {undefined | TBaseEndpoint} */
const allConfig = req.app.locals.all; const allConfig = appConfig.all;
if (allConfig) { if (allConfig) {
customOptions.streamRate = allConfig.streamRate; customOptions.streamRate = allConfig.streamRate;
} }

View file

@ -2,6 +2,7 @@ const path = require('path');
const { EModelEndpoint, AuthKeys } = require('librechat-data-provider'); const { EModelEndpoint, AuthKeys } = require('librechat-data-provider');
const { getGoogleConfig, isEnabled, loadServiceKey } = require('@librechat/api'); const { getGoogleConfig, isEnabled, loadServiceKey } = require('@librechat/api');
const { getUserKey, checkUserKeyExpiry } = require('~/server/services/UserService'); const { getUserKey, checkUserKeyExpiry } = require('~/server/services/UserService');
const { getAppConfig } = require('~/server/services/Config');
const { GoogleClient } = require('~/app'); const { GoogleClient } = require('~/app');
const initializeClient = async ({ req, res, endpointOption, overrideModel, optionsOnly }) => { const initializeClient = async ({ req, res, endpointOption, overrideModel, optionsOnly }) => {
@ -46,10 +47,11 @@ const initializeClient = async ({ req, res, endpointOption, overrideModel, optio
let clientOptions = {}; let clientOptions = {};
const appConfig = await getAppConfig({ role: req.user?.role });
/** @type {undefined | TBaseEndpoint} */ /** @type {undefined | TBaseEndpoint} */
const allConfig = req.app.locals.all; const allConfig = appConfig.all;
/** @type {undefined | TBaseEndpoint} */ /** @type {undefined | TBaseEndpoint} */
const googleConfig = req.app.locals[EModelEndpoint.google]; const googleConfig = appConfig[EModelEndpoint.google];
if (googleConfig) { if (googleConfig) {
clientOptions.streamRate = googleConfig.streamRate; clientOptions.streamRate = googleConfig.streamRate;

View file

@ -1,7 +1,8 @@
const { isEnabled } = require('@librechat/api');
const { EModelEndpoint, CacheKeys, Constants, googleSettings } = require('librechat-data-provider'); const { EModelEndpoint, CacheKeys, Constants, googleSettings } = require('librechat-data-provider');
const { getAppConfig } = require('~/server/services/Config');
const getLogStores = require('~/cache/getLogStores'); const getLogStores = require('~/cache/getLogStores');
const initializeClient = require('./initialize'); const initializeClient = require('./initialize');
const { isEnabled } = require('~/server/utils');
const { saveConvo } = require('~/models'); const { saveConvo } = require('~/models');
const addTitle = async (req, { text, response, client }) => { const addTitle = async (req, { text, response, client }) => {
@ -14,7 +15,8 @@ const addTitle = async (req, { text, response, client }) => {
return; return;
} }
const { GOOGLE_TITLE_MODEL } = process.env ?? {}; const { GOOGLE_TITLE_MODEL } = process.env ?? {};
const providerConfig = req.app.locals[EModelEndpoint.google]; const appConfig = await getAppConfig({ role: req.user?.role });
const providerConfig = appConfig[EModelEndpoint.google];
let model = let model =
providerConfig?.titleModel ?? providerConfig?.titleModel ??
GOOGLE_TITLE_MODEL ?? GOOGLE_TITLE_MODEL ??

View file

@ -8,6 +8,7 @@ const {
createHandleLLMNewToken, createHandleLLMNewToken,
} = require('@librechat/api'); } = require('@librechat/api');
const { getUserKeyValues, checkUserKeyExpiry } = require('~/server/services/UserService'); const { getUserKeyValues, checkUserKeyExpiry } = require('~/server/services/UserService');
const { getAppConfig } = require('~/server/services/Config');
const OpenAIClient = require('~/app/clients/OpenAIClient'); const OpenAIClient = require('~/app/clients/OpenAIClient');
const initializeClient = async ({ const initializeClient = async ({
@ -18,6 +19,7 @@ const initializeClient = async ({
overrideEndpoint, overrideEndpoint,
overrideModel, overrideModel,
}) => { }) => {
const appConfig = await getAppConfig({ role: req.user?.role });
const { const {
PROXY, PROXY,
OPENAI_API_KEY, OPENAI_API_KEY,
@ -64,7 +66,7 @@ const initializeClient = async ({
const isAzureOpenAI = endpoint === EModelEndpoint.azureOpenAI; const isAzureOpenAI = endpoint === EModelEndpoint.azureOpenAI;
/** @type {false | TAzureConfig} */ /** @type {false | TAzureConfig} */
const azureConfig = isAzureOpenAI && req.app.locals[EModelEndpoint.azureOpenAI]; const azureConfig = isAzureOpenAI && appConfig[EModelEndpoint.azureOpenAI];
let serverless = false; let serverless = false;
if (isAzureOpenAI && azureConfig) { if (isAzureOpenAI && azureConfig) {
const { modelGroupMap, groupMap } = azureConfig; const { modelGroupMap, groupMap } = azureConfig;
@ -113,7 +115,7 @@ const initializeClient = async ({
} }
/** @type {undefined | TBaseEndpoint} */ /** @type {undefined | TBaseEndpoint} */
const openAIConfig = req.app.locals[EModelEndpoint.openAI]; const openAIConfig = appConfig[EModelEndpoint.openAI];
if (!isAzureOpenAI && openAIConfig) { if (!isAzureOpenAI && openAIConfig) {
clientOptions.streamRate = openAIConfig.streamRate; clientOptions.streamRate = openAIConfig.streamRate;
@ -121,7 +123,7 @@ const initializeClient = async ({
} }
/** @type {undefined | TBaseEndpoint} */ /** @type {undefined | TBaseEndpoint} */
const allConfig = req.app.locals.all; const allConfig = appConfig.all;
if (allConfig) { if (allConfig) {
clientOptions.streamRate = allConfig.streamRate; clientOptions.streamRate = allConfig.streamRate;
} }

View file

@ -3,6 +3,7 @@ const path = require('path');
const sharp = require('sharp'); const sharp = require('sharp');
const { logger } = require('@librechat/data-schemas'); const { logger } = require('@librechat/data-schemas');
const { resizeImageBuffer } = require('../images/resize'); const { resizeImageBuffer } = require('../images/resize');
const { getAppConfig } = require('~/server/services/Config');
const { updateUser, updateFile } = require('~/models'); const { updateUser, updateFile } = require('~/models');
const { saveBufferToAzure } = require('./crud'); const { saveBufferToAzure } = require('./crud');
@ -30,6 +31,7 @@ async function uploadImageToAzure({
containerName, containerName,
}) { }) {
try { try {
const appConfig = await getAppConfig({ role: req.user?.role });
const inputFilePath = file.path; const inputFilePath = file.path;
const inputBuffer = await fs.promises.readFile(inputFilePath); const inputBuffer = await fs.promises.readFile(inputFilePath);
const { const {
@ -41,12 +43,12 @@ async function uploadImageToAzure({
const userId = req.user.id; const userId = req.user.id;
let webPBuffer; let webPBuffer;
let fileName = `${file_id}__${path.basename(inputFilePath)}`; let fileName = `${file_id}__${path.basename(inputFilePath)}`;
const targetExtension = `.${req.app.locals.imageOutputType}`; const targetExtension = `.${appConfig.imageOutputType}`;
if (extension.toLowerCase() === targetExtension) { if (extension.toLowerCase() === targetExtension) {
webPBuffer = resizedBuffer; webPBuffer = resizedBuffer;
} else { } else {
webPBuffer = await sharp(resizedBuffer).toFormat(req.app.locals.imageOutputType).toBuffer(); webPBuffer = await sharp(resizedBuffer).toFormat(appConfig.imageOutputType).toBuffer();
const extRegExp = new RegExp(path.extname(fileName) + '$'); const extRegExp = new RegExp(path.extname(fileName) + '$');
fileName = fileName.replace(extRegExp, targetExtension); fileName = fileName.replace(extRegExp, targetExtension);
if (!path.extname(fileName)) { if (!path.extname(fileName)) {

View file

@ -43,8 +43,7 @@ async function getCodeOutputDownloadStream(fileIdentifier, apiKey) {
/** /**
* Uploads a file to the Code Environment server. * Uploads a file to the Code Environment server.
* @param {Object} params - The params object. * @param {Object} params - The params object.
* @param {ServerRequest} params.req - The request object from Express. It should have a `user` property with an `id` * @param {ServerRequest} params.req - The request object from Express. It should have a `user` property with an `id` representing the user
* representing the user, and an `app.locals.paths` object with an `uploads` path.
* @param {import('fs').ReadStream | import('stream').Readable} params.stream - The read stream for the file. * @param {import('fs').ReadStream | import('stream').Readable} params.stream - The read stream for the file.
* @param {string} params.filename - The name of the file. * @param {string} params.filename - The name of the file.
* @param {string} params.apiKey - The API key for authentication. * @param {string} params.apiKey - The API key for authentication.

View file

@ -15,6 +15,7 @@ const { filterFilesByAgentAccess } = require('~/server/services/Files/permission
const { getStrategyFunctions } = require('~/server/services/Files/strategies'); const { getStrategyFunctions } = require('~/server/services/Files/strategies');
const { convertImage } = require('~/server/services/Files/images/convert'); const { convertImage } = require('~/server/services/Files/images/convert');
const { createFile, getFiles, updateFile } = require('~/models/File'); const { createFile, getFiles, updateFile } = require('~/models/File');
const { getAppConfig } = require('~/server/services/Config');
/** /**
* Process OpenAI image files, convert to target format, save and return file metadata. * Process OpenAI image files, convert to target format, save and return file metadata.
@ -38,6 +39,7 @@ const processCodeOutput = async ({
messageId, messageId,
session_id, session_id,
}) => { }) => {
const appConfig = await getAppConfig({ role: req.user?.role });
const currentDate = new Date(); const currentDate = new Date();
const baseURL = getCodeBaseURL(); const baseURL = getCodeBaseURL();
const fileExt = path.extname(name); const fileExt = path.extname(name);
@ -77,10 +79,10 @@ const processCodeOutput = async ({
filename: name, filename: name,
conversationId, conversationId,
user: req.user.id, user: req.user.id,
type: `image/${req.app.locals.imageOutputType}`, type: `image/${appConfig.imageOutputType}`,
createdAt: formattedDate, createdAt: formattedDate,
updatedAt: formattedDate, updatedAt: formattedDate,
source: req.app.locals.fileStrategy, source: appConfig.fileStrategy,
context: FileContext.execute_code, context: FileContext.execute_code,
}; };
createFile(file, true); createFile(file, true);

View file

@ -2,6 +2,7 @@ const fs = require('fs');
const path = require('path'); const path = require('path');
const sharp = require('sharp'); const sharp = require('sharp');
const { logger } = require('@librechat/data-schemas'); const { logger } = require('@librechat/data-schemas');
const { getAppConfig } = require('~/server/services/Config');
const { resizeImageBuffer } = require('../images/resize'); const { resizeImageBuffer } = require('../images/resize');
const { updateUser, updateFile } = require('~/models'); const { updateUser, updateFile } = require('~/models');
const { saveBufferToFirebase } = require('./crud'); const { saveBufferToFirebase } = require('./crud');
@ -11,8 +12,7 @@ const { saveBufferToFirebase } = require('./crud');
* resolution. * resolution.
* *
* @param {Object} params - The params object. * @param {Object} params - The params object.
* @param {Express.Request} params.req - The request object from Express. It should have a `user` property with an `id` * @param {Express.Request} params.req - The request object from Express. It should have a `user` property with an `id` representing the user
* representing the user, and an `app.locals.paths` object with an `imageOutput` path.
* @param {Express.Multer.File} params.file - The file object, which is part of the request. The file object should * @param {Express.Multer.File} params.file - The file object, which is part of the request. The file object should
* have a `path` property that points to the location of the uploaded file. * have a `path` property that points to the location of the uploaded file.
* @param {EModelEndpoint} params.endpoint - The params object. * @param {EModelEndpoint} params.endpoint - The params object.
@ -26,6 +26,7 @@ const { saveBufferToFirebase } = require('./crud');
* - height: The height of the converted image. * - height: The height of the converted image.
*/ */
async function uploadImageToFirebase({ req, file, file_id, endpoint, resolution = 'high' }) { async function uploadImageToFirebase({ req, file, file_id, endpoint, resolution = 'high' }) {
const appConfig = await getAppConfig({ role: req.user?.role });
const inputFilePath = file.path; const inputFilePath = file.path;
const inputBuffer = await fs.promises.readFile(inputFilePath); const inputBuffer = await fs.promises.readFile(inputFilePath);
const { const {
@ -38,11 +39,11 @@ async function uploadImageToFirebase({ req, file, file_id, endpoint, resolution
let webPBuffer; let webPBuffer;
let fileName = `${file_id}__${path.basename(inputFilePath)}`; let fileName = `${file_id}__${path.basename(inputFilePath)}`;
const targetExtension = `.${req.app.locals.imageOutputType}`; const targetExtension = `.${appConfig.imageOutputType}`;
if (extension.toLowerCase() === targetExtension) { if (extension.toLowerCase() === targetExtension) {
webPBuffer = resizedBuffer; webPBuffer = resizedBuffer;
} else { } else {
webPBuffer = await sharp(resizedBuffer).toFormat(req.app.locals.imageOutputType).toBuffer(); webPBuffer = await sharp(resizedBuffer).toFormat(appConfig.imageOutputType).toBuffer();
// Replace or append the correct extension // Replace or append the correct extension
const extRegExp = new RegExp(path.extname(fileName) + '$'); const extRegExp = new RegExp(path.extname(fileName) + '$');
fileName = fileName.replace(extRegExp, targetExtension); fileName = fileName.replace(extRegExp, targetExtension);

View file

@ -4,6 +4,7 @@ const axios = require('axios');
const { logger } = require('@librechat/data-schemas'); const { logger } = require('@librechat/data-schemas');
const { EModelEndpoint } = require('librechat-data-provider'); const { EModelEndpoint } = require('librechat-data-provider');
const { generateShortLivedToken } = require('~/server/services/AuthService'); const { generateShortLivedToken } = require('~/server/services/AuthService');
const { getAppConfig } = require('~/server/services/Config');
const { getBufferMetadata } = require('~/server/utils'); const { getBufferMetadata } = require('~/server/utils');
const paths = require('~/config/paths'); const paths = require('~/config/paths');
@ -45,7 +46,8 @@ async function saveLocalFile(file, outputPath, outputFilename) {
* @throws Will throw an error if the image saving process fails. * @throws Will throw an error if the image saving process fails.
*/ */
const saveLocalImage = async (req, file, filename) => { const saveLocalImage = async (req, file, filename) => {
const imagePath = req.app.locals.paths.imageOutput; const appConfig = await getAppConfig({ role: req.user?.role });
const imagePath = appConfig.paths.imageOutput;
const outputPath = path.join(imagePath, req.user.id ?? ''); const outputPath = path.join(imagePath, req.user.id ?? '');
await saveLocalFile(file, outputPath, filename); await saveLocalFile(file, outputPath, filename);
}; };
@ -191,8 +193,7 @@ const unlinkFile = async (filepath) => {
* Deletes a file from the filesystem. This function takes a file object, constructs the full path, and * Deletes a file from the filesystem. This function takes a file object, constructs the full path, and
* verifies the path's validity before deleting the file. If the path is invalid, an error is thrown. * verifies the path's validity before deleting the file. If the path is invalid, an error is thrown.
* *
* @param {Express.Request} req - The request object from Express. It should have an `app.locals.paths` object with * @param {Express.Request} req - The request object from Express.
* a `publicPath` property.
* @param {MongoFile} file - The file object to be deleted. It should have a `filepath` property that is * @param {MongoFile} file - The file object to be deleted. It should have a `filepath` property that is
* a string representing the path of the file relative to the publicPath. * a string representing the path of the file relative to the publicPath.
* *
@ -201,7 +202,8 @@ const unlinkFile = async (filepath) => {
* file path is invalid or if there is an error in deletion. * file path is invalid or if there is an error in deletion.
*/ */
const deleteLocalFile = async (req, file) => { const deleteLocalFile = async (req, file) => {
const { publicPath, uploads } = req.app.locals.paths; const appConfig = await getAppConfig({ role: req.user?.role });
const { publicPath, uploads } = appConfig.paths;
/** Filepath stripped of query parameters (e.g., ?manual=true) */ /** Filepath stripped of query parameters (e.g., ?manual=true) */
const cleanFilepath = file.filepath.split('?')[0]; const cleanFilepath = file.filepath.split('?')[0];
@ -256,8 +258,7 @@ const deleteLocalFile = async (req, file) => {
* Uploads a file to the specified upload directory. * Uploads a file to the specified upload directory.
* *
* @param {Object} params - The params object. * @param {Object} params - The params object.
* @param {ServerRequest} params.req - The request object from Express. It should have a `user` property with an `id` * @param {ServerRequest} params.req - The request object from Express. It should have a `user` property with an `id` representing the user
* representing the user, and an `app.locals.paths` object with an `uploads` path.
* @param {Express.Multer.File} params.file - The file object, which is part of the request. The file object should * @param {Express.Multer.File} params.file - The file object, which is part of the request. The file object should
* have a `path` property that points to the location of the uploaded file. * have a `path` property that points to the location of the uploaded file.
* @param {string} params.file_id - The file ID. * @param {string} params.file_id - The file ID.
@ -268,11 +269,12 @@ const deleteLocalFile = async (req, file) => {
* - bytes: The size of the file in bytes. * - bytes: The size of the file in bytes.
*/ */
async function uploadLocalFile({ req, file, file_id }) { async function uploadLocalFile({ req, file, file_id }) {
const appConfig = await getAppConfig({ role: req.user?.role });
const inputFilePath = file.path; const inputFilePath = file.path;
const inputBuffer = await fs.promises.readFile(inputFilePath); const inputBuffer = await fs.promises.readFile(inputFilePath);
const bytes = Buffer.byteLength(inputBuffer); const bytes = Buffer.byteLength(inputBuffer);
const { uploads } = req.app.locals.paths; const { uploads } = appConfig.paths;
const userPath = path.join(uploads, req.user.id); const userPath = path.join(uploads, req.user.id);
if (!fs.existsSync(userPath)) { if (!fs.existsSync(userPath)) {
@ -295,8 +297,9 @@ async function uploadLocalFile({ req, file, file_id }) {
* @param {string} filepath - The filepath. * @param {string} filepath - The filepath.
* @returns {ReadableStream} A readable stream of the file. * @returns {ReadableStream} A readable stream of the file.
*/ */
function getLocalFileStream(req, filepath) { async function getLocalFileStream(req, filepath) {
try { try {
const appConfig = await getAppConfig({ role: req.user?.role });
if (filepath.includes('/uploads/')) { if (filepath.includes('/uploads/')) {
const basePath = filepath.split('/uploads/')[1]; const basePath = filepath.split('/uploads/')[1];
@ -305,8 +308,8 @@ function getLocalFileStream(req, filepath) {
throw new Error(`Invalid file path: ${filepath}`); throw new Error(`Invalid file path: ${filepath}`);
} }
const fullPath = path.join(req.app.locals.paths.uploads, basePath); const fullPath = path.join(appConfig.paths.uploads, basePath);
const uploadsDir = req.app.locals.paths.uploads; const uploadsDir = appConfig.paths.uploads;
const rel = path.relative(uploadsDir, fullPath); const rel = path.relative(uploadsDir, fullPath);
if (rel.startsWith('..') || path.isAbsolute(rel) || rel.includes(`..${path.sep}`)) { if (rel.startsWith('..') || path.isAbsolute(rel) || rel.includes(`..${path.sep}`)) {
@ -323,8 +326,8 @@ function getLocalFileStream(req, filepath) {
throw new Error(`Invalid file path: ${filepath}`); throw new Error(`Invalid file path: ${filepath}`);
} }
const fullPath = path.join(req.app.locals.paths.imageOutput, basePath); const fullPath = path.join(appConfig.paths.imageOutput, basePath);
const publicDir = req.app.locals.paths.imageOutput; const publicDir = appConfig.paths.imageOutput;
const rel = path.relative(publicDir, fullPath); const rel = path.relative(publicDir, fullPath);
if (rel.startsWith('..') || path.isAbsolute(rel) || rel.includes(`..${path.sep}`)) { if (rel.startsWith('..') || path.isAbsolute(rel) || rel.includes(`..${path.sep}`)) {

View file

@ -1,6 +1,7 @@
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const sharp = require('sharp'); const sharp = require('sharp');
const { getAppConfig } = require('~/server/services/Config');
const { resizeImageBuffer } = require('../images/resize'); const { resizeImageBuffer } = require('../images/resize');
const { updateUser, updateFile } = require('~/models'); const { updateUser, updateFile } = require('~/models');
@ -13,8 +14,7 @@ const { updateUser, updateFile } = require('~/models');
* *
* The original image is deleted after conversion. * The original image is deleted after conversion.
* @param {Object} params - The params object. * @param {Object} params - The params object.
* @param {Object} params.req - The request object from Express. It should have a `user` property with an `id` * @param {Object} params.req - The request object from Express. It should have a `user` property with an `id` representing the user
* representing the user, and an `app.locals.paths` object with an `imageOutput` path.
* @param {Express.Multer.File} params.file - The file object, which is part of the request. The file object should * @param {Express.Multer.File} params.file - The file object, which is part of the request. The file object should
* have a `path` property that points to the location of the uploaded file. * have a `path` property that points to the location of the uploaded file.
* @param {string} params.file_id - The file ID. * @param {string} params.file_id - The file ID.
@ -29,6 +29,7 @@ const { updateUser, updateFile } = require('~/models');
* - height: The height of the converted image. * - height: The height of the converted image.
*/ */
async function uploadLocalImage({ req, file, file_id, endpoint, resolution = 'high' }) { async function uploadLocalImage({ req, file, file_id, endpoint, resolution = 'high' }) {
const appConfig = await getAppConfig({ role: req.user?.role });
const inputFilePath = file.path; const inputFilePath = file.path;
const inputBuffer = await fs.promises.readFile(inputFilePath); const inputBuffer = await fs.promises.readFile(inputFilePath);
const { const {
@ -38,7 +39,7 @@ async function uploadLocalImage({ req, file, file_id, endpoint, resolution = 'hi
} = await resizeImageBuffer(inputBuffer, resolution, endpoint); } = await resizeImageBuffer(inputBuffer, resolution, endpoint);
const extension = path.extname(inputFilePath); const extension = path.extname(inputFilePath);
const { imageOutput } = req.app.locals.paths; const { imageOutput } = appConfig.paths;
const userPath = path.join(imageOutput, req.user.id); const userPath = path.join(imageOutput, req.user.id);
if (!fs.existsSync(userPath)) { if (!fs.existsSync(userPath)) {
@ -47,7 +48,7 @@ async function uploadLocalImage({ req, file, file_id, endpoint, resolution = 'hi
const fileName = `${file_id}__${path.basename(inputFilePath)}`; const fileName = `${file_id}__${path.basename(inputFilePath)}`;
const newPath = path.join(userPath, fileName); const newPath = path.join(userPath, fileName);
const targetExtension = `.${req.app.locals.imageOutputType}`; const targetExtension = `.${appConfig.imageOutputType}`;
if (extension.toLowerCase() === targetExtension) { if (extension.toLowerCase() === targetExtension) {
const bytes = Buffer.byteLength(resizedBuffer); const bytes = Buffer.byteLength(resizedBuffer);
@ -57,7 +58,7 @@ async function uploadLocalImage({ req, file, file_id, endpoint, resolution = 'hi
} }
const outputFilePath = newPath.replace(extension, targetExtension); const outputFilePath = newPath.replace(extension, targetExtension);
const data = await sharp(resizedBuffer).toFormat(req.app.locals.imageOutputType).toBuffer(); const data = await sharp(resizedBuffer).toFormat(appConfig.imageOutputType).toBuffer();
await fs.promises.writeFile(outputFilePath, data); await fs.promises.writeFile(outputFilePath, data);
const bytes = Buffer.byteLength(data); const bytes = Buffer.byteLength(data);
const filepath = path.posix.join('/', 'images', req.user.id, path.basename(outputFilePath)); const filepath = path.posix.join('/', 'images', req.user.id, path.basename(outputFilePath));
@ -90,7 +91,8 @@ function encodeImage(imagePath) {
* @returns {Promise<[MongoFile, string]>} - A promise that resolves to an array of results from updateFile and encodeImage. * @returns {Promise<[MongoFile, string]>} - A promise that resolves to an array of results from updateFile and encodeImage.
*/ */
async function prepareImagesLocal(req, file) { async function prepareImagesLocal(req, file) {
const { publicPath, imageOutput } = req.app.locals.paths; const appConfig = await getAppConfig({ role: req.user?.role });
const { publicPath, imageOutput } = appConfig.paths;
const userPath = path.join(imageOutput, req.user.id); const userPath = path.join(imageOutput, req.user.id);
if (!fs.existsSync(userPath)) { if (!fs.existsSync(userPath)) {

View file

@ -7,8 +7,7 @@ const { logger } = require('~/config');
* Uploads a file that can be used across various OpenAI services. * Uploads a file that can be used across various OpenAI services.
* *
* @param {Object} params - The params object. * @param {Object} params - The params object.
* @param {ServerRequest} params.req - The request object from Express. It should have a `user` property with an `id` * @param {ServerRequest} params.req - The request object from Express. It should have a `user` property with an `id` representing the user
* representing the user, and an `app.locals.paths` object with an `imageOutput` path.
* @param {Express.Multer.File} params.file - The file uploaded to the server via multer. * @param {Express.Multer.File} params.file - The file uploaded to the server via multer.
* @param {OpenAIClient} params.openai - The initialized OpenAI client. * @param {OpenAIClient} params.openai - The initialized OpenAI client.
* @returns {Promise<OpenAIFile>} * @returns {Promise<OpenAIFile>}

View file

@ -2,6 +2,7 @@ const fs = require('fs');
const path = require('path'); const path = require('path');
const sharp = require('sharp'); const sharp = require('sharp');
const { logger } = require('@librechat/data-schemas'); const { logger } = require('@librechat/data-schemas');
const { getAppConfig } = require('~/server/services/Config');
const { resizeImageBuffer } = require('../images/resize'); const { resizeImageBuffer } = require('../images/resize');
const { updateUser, updateFile } = require('~/models'); const { updateUser, updateFile } = require('~/models');
const { saveBufferToS3 } = require('./crud'); const { saveBufferToS3 } = require('./crud');
@ -12,7 +13,7 @@ const defaultBasePath = 'images';
* Resizes, converts, and uploads an image file to S3. * Resizes, converts, and uploads an image file to S3.
* *
* @param {Object} params * @param {Object} params
* @param {import('express').Request} params.req - Express request (expects user and app.locals.imageOutputType). * @param {import('express').Request} params.req - Express request (expects `user` and `appConfig.imageOutputType`).
* @param {Express.Multer.File} params.file - File object from Multer. * @param {Express.Multer.File} params.file - File object from Multer.
* @param {string} params.file_id - Unique file identifier. * @param {string} params.file_id - Unique file identifier.
* @param {any} params.endpoint - Endpoint identifier used in image processing. * @param {any} params.endpoint - Endpoint identifier used in image processing.
@ -29,6 +30,7 @@ async function uploadImageToS3({
basePath = defaultBasePath, basePath = defaultBasePath,
}) { }) {
try { try {
const appConfig = await getAppConfig({ role: req.user?.role });
const inputFilePath = file.path; const inputFilePath = file.path;
const inputBuffer = await fs.promises.readFile(inputFilePath); const inputBuffer = await fs.promises.readFile(inputFilePath);
const { const {
@ -41,14 +43,12 @@ async function uploadImageToS3({
let processedBuffer; let processedBuffer;
let fileName = `${file_id}__${path.basename(inputFilePath)}`; let fileName = `${file_id}__${path.basename(inputFilePath)}`;
const targetExtension = `.${req.app.locals.imageOutputType}`; const targetExtension = `.${appConfig.imageOutputType}`;
if (extension.toLowerCase() === targetExtension) { if (extension.toLowerCase() === targetExtension) {
processedBuffer = resizedBuffer; processedBuffer = resizedBuffer;
} else { } else {
processedBuffer = await sharp(resizedBuffer) processedBuffer = await sharp(resizedBuffer).toFormat(appConfig.imageOutputType).toBuffer();
.toFormat(req.app.locals.imageOutputType)
.toBuffer();
fileName = fileName.replace(new RegExp(path.extname(fileName) + '$'), targetExtension); fileName = fileName.replace(new RegExp(path.extname(fileName) + '$'), targetExtension);
if (!path.extname(fileName)) { if (!path.extname(fileName)) {
fileName += targetExtension; fileName += targetExtension;

View file

@ -10,8 +10,7 @@ const { generateShortLivedToken } = require('~/server/services/AuthService');
* Deletes a file from the vector database. This function takes a file object, constructs the full path, and * Deletes a file from the vector database. This function takes a file object, constructs the full path, and
* verifies the path's validity before deleting the file. If the path is invalid, an error is thrown. * verifies the path's validity before deleting the file. If the path is invalid, an error is thrown.
* *
* @param {ServerRequest} req - The request object from Express. It should have an `app.locals.paths` object with * @param {ServerRequest} req - The request object from Express.
* a `publicPath` property.
* @param {MongoFile} file - The file object to be deleted. It should have a `filepath` property that is * @param {MongoFile} file - The file object to be deleted. It should have a `filepath` property that is
* a string representing the path of the file relative to the publicPath. * a string representing the path of the file relative to the publicPath.
* *
@ -54,8 +53,7 @@ const deleteVectors = async (req, file) => {
* Uploads a file to the configured Vector database * Uploads a file to the configured Vector database
* *
* @param {Object} params - The params object. * @param {Object} params - The params object.
* @param {Object} params.req - The request object from Express. It should have a `user` property with an `id` * @param {Object} params.req - The request object from Express. It should have a `user` property with an `id` representing the user
* representing the user, and an `app.locals.paths` object with an `uploads` path.
* @param {Express.Multer.File} params.file - The file object, which is part of the request. The file object should * @param {Express.Multer.File} params.file - The file object, which is part of the request. The file object should
* have a `path` property that points to the location of the uploaded file. * have a `path` property that points to the location of the uploaded file.
* @param {string} params.file_id - The file ID. * @param {string} params.file_id - The file ID.

View file

@ -1,8 +1,9 @@
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const sharp = require('sharp'); const sharp = require('sharp');
const { resizeImageBuffer } = require('./resize'); const { getAppConfig } = require('~/server/services/Config');
const { getStrategyFunctions } = require('../strategies'); const { getStrategyFunctions } = require('../strategies');
const { resizeImageBuffer } = require('./resize');
const { logger } = require('~/config'); const { logger } = require('~/config');
/** /**
@ -17,6 +18,7 @@ const { logger } = require('~/config');
*/ */
async function convertImage(req, file, resolution = 'high', basename = '') { async function convertImage(req, file, resolution = 'high', basename = '') {
try { try {
const appConfig = await getAppConfig({ role: req.user?.role });
let inputBuffer; let inputBuffer;
let outputBuffer; let outputBuffer;
let extension = path.extname(file.path ?? basename).toLowerCase(); let extension = path.extname(file.path ?? basename).toLowerCase();
@ -39,11 +41,11 @@ async function convertImage(req, file, resolution = 'high', basename = '') {
} = await resizeImageBuffer(inputBuffer, resolution); } = await resizeImageBuffer(inputBuffer, resolution);
// Check if the file is already in target format; if it isn't, convert it: // Check if the file is already in target format; if it isn't, convert it:
const targetExtension = `.${req.app.locals.imageOutputType}`; const targetExtension = `.${appConfig.imageOutputType}`;
if (extension === targetExtension) { if (extension === targetExtension) {
outputBuffer = resizedBuffer; outputBuffer = resizedBuffer;
} else { } else {
outputBuffer = await sharp(resizedBuffer).toFormat(req.app.locals.imageOutputType).toBuffer(); outputBuffer = await sharp(resizedBuffer).toFormat(appConfig.imageOutputType).toBuffer();
extension = targetExtension; extension = targetExtension;
} }
@ -51,7 +53,7 @@ async function convertImage(req, file, resolution = 'high', basename = '') {
const newFileName = const newFileName =
path.basename(file.path ?? basename, path.extname(file.path ?? basename)) + extension; path.basename(file.path ?? basename, path.extname(file.path ?? basename)) + extension;
const { saveBuffer } = getStrategyFunctions(req.app.locals.fileStrategy); const { saveBuffer } = getStrategyFunctions(appConfig.fileStrategy);
const savedFilePath = await saveBuffer({ const savedFilePath = await saveBuffer({
userId: req.user.id, userId: req.user.id,

View file

@ -16,8 +16,8 @@ const {
removeNullishValues, removeNullishValues,
isAssistantsEndpoint, isAssistantsEndpoint,
} = require('librechat-data-provider'); } = require('librechat-data-provider');
const { sanitizeFilename } = require('@librechat/api');
const { EnvVar } = require('@librechat/agents'); const { EnvVar } = require('@librechat/agents');
const { sanitizeFilename } = require('@librechat/api');
const { const {
convertImage, convertImage,
resizeAndConvert, resizeAndConvert,
@ -27,11 +27,11 @@ const { addResourceFileId, deleteResourceFileId } = require('~/server/controller
const { addAgentResourceFile, removeAgentResourceFiles } = require('~/models/Agent'); const { addAgentResourceFile, removeAgentResourceFiles } = require('~/models/Agent');
const { getOpenAIClient } = require('~/server/controllers/assistants/helpers'); const { getOpenAIClient } = require('~/server/controllers/assistants/helpers');
const { createFile, updateFileUsage, deleteFiles } = require('~/models/File'); const { createFile, updateFileUsage, deleteFiles } = require('~/models/File');
const { checkCapability, getAppConfig } = require('~/server/services/Config');
const { loadAuthValues } = require('~/server/services/Tools/credentials'); const { loadAuthValues } = require('~/server/services/Tools/credentials');
const { checkCapability } = require('~/server/services/Config'); const { getFileStrategy } = require('~/server/utils/getFileStrategy');
const { LB_QueueAsyncCall } = require('~/server/utils/queue'); const { LB_QueueAsyncCall } = require('~/server/utils/queue');
const { getStrategyFunctions } = require('./strategies'); const { getStrategyFunctions } = require('./strategies');
const { getFileStrategy } = require('~/server/utils/getFileStrategy');
const { determineFileType } = require('~/server/utils'); const { determineFileType } = require('~/server/utils');
const { logger } = require('~/config'); const { logger } = require('~/config');
@ -157,6 +157,7 @@ function enqueueDeleteOperation({ req, file, deleteFile, promises, resolvedFileI
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
const processDeleteRequest = async ({ req, files }) => { const processDeleteRequest = async ({ req, files }) => {
const appConfig = await getAppConfig({ role: req.user?.role });
const resolvedFileIds = []; const resolvedFileIds = [];
const deletionMethods = {}; const deletionMethods = {};
const promises = []; const promises = [];
@ -164,7 +165,7 @@ const processDeleteRequest = async ({ req, files }) => {
/** @type {Record<string, OpenAI | undefined>} */ /** @type {Record<string, OpenAI | undefined>} */
const client = { [FileSources.openai]: undefined, [FileSources.azure]: undefined }; const client = { [FileSources.openai]: undefined, [FileSources.azure]: undefined };
const initializeClients = async () => { const initializeClients = async () => {
if (req.app.locals[EModelEndpoint.assistants]) { if (appConfig[EModelEndpoint.assistants]) {
const openAIClient = await getOpenAIClient({ const openAIClient = await getOpenAIClient({
req, req,
overrideEndpoint: EModelEndpoint.assistants, overrideEndpoint: EModelEndpoint.assistants,
@ -172,7 +173,7 @@ const processDeleteRequest = async ({ req, files }) => {
client[FileSources.openai] = openAIClient.openai; client[FileSources.openai] = openAIClient.openai;
} }
if (!req.app.locals[EModelEndpoint.azureOpenAI]?.assistants) { if (!appConfig[EModelEndpoint.azureOpenAI]?.assistants) {
return; return;
} }
@ -320,7 +321,8 @@ const processFileURL = async ({ fileStrategy, userId, URL, fileName, basePath, c
*/ */
const processImageFile = async ({ req, res, metadata, returnFile = false }) => { const processImageFile = async ({ req, res, metadata, returnFile = false }) => {
const { file } = req; const { file } = req;
const source = getFileStrategy(req.app.locals, { isImage: true }); const appConfig = await getAppConfig({ role: req.user?.role });
const source = getFileStrategy(appConfig, { isImage: true });
const { handleImageUpload } = getStrategyFunctions(source); const { handleImageUpload } = getStrategyFunctions(source);
const { file_id, temp_file_id, endpoint } = metadata; const { file_id, temp_file_id, endpoint } = metadata;
@ -341,7 +343,7 @@ const processImageFile = async ({ req, res, metadata, returnFile = false }) => {
filename: file.originalname, filename: file.originalname,
context: FileContext.message_attachment, context: FileContext.message_attachment,
source, source,
type: `image/${req.app.locals.imageOutputType}`, type: `image/${appConfig.imageOutputType}`,
width, width,
height, height,
}, },
@ -366,18 +368,19 @@ const processImageFile = async ({ req, res, metadata, returnFile = false }) => {
* @returns {Promise<{ filepath: string, filename: string, source: string, type: string}>} * @returns {Promise<{ filepath: string, filename: string, source: string, type: string}>}
*/ */
const uploadImageBuffer = async ({ req, context, metadata = {}, resize = true }) => { const uploadImageBuffer = async ({ req, context, metadata = {}, resize = true }) => {
const source = getFileStrategy(req.app.locals, { isImage: true }); const appConfig = await getAppConfig({ role: req.user?.role });
const source = getFileStrategy(appConfig, { isImage: true });
const { saveBuffer } = getStrategyFunctions(source); const { saveBuffer } = getStrategyFunctions(source);
let { buffer, width, height, bytes, filename, file_id, type } = metadata; let { buffer, width, height, bytes, filename, file_id, type } = metadata;
if (resize) { if (resize) {
file_id = v4(); file_id = v4();
type = `image/${req.app.locals.imageOutputType}`; type = `image/${appConfig.imageOutputType}`;
({ buffer, width, height, bytes } = await resizeAndConvert({ ({ buffer, width, height, bytes } = await resizeAndConvert({
inputBuffer: buffer, inputBuffer: buffer,
desiredFormat: req.app.locals.imageOutputType, desiredFormat: appConfig.imageOutputType,
})); }));
filename = `${path.basename(req.file.originalname, path.extname(req.file.originalname))}.${ filename = `${path.basename(req.file.originalname, path.extname(req.file.originalname))}.${
req.app.locals.imageOutputType appConfig.imageOutputType
}`; }`;
} }
const fileName = `${file_id}-${filename}`; const fileName = `${file_id}-${filename}`;
@ -411,11 +414,12 @@ const uploadImageBuffer = async ({ req, context, metadata = {}, resize = true })
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
const processFileUpload = async ({ req, res, metadata }) => { const processFileUpload = async ({ req, res, metadata }) => {
const appConfig = await getAppConfig({ role: req.user?.role });
const isAssistantUpload = isAssistantsEndpoint(metadata.endpoint); const isAssistantUpload = isAssistantsEndpoint(metadata.endpoint);
const assistantSource = const assistantSource =
metadata.endpoint === EModelEndpoint.azureAssistants ? FileSources.azure : FileSources.openai; metadata.endpoint === EModelEndpoint.azureAssistants ? FileSources.azure : FileSources.openai;
// Use the configured file strategy for regular file uploads (not vectordb) // Use the configured file strategy for regular file uploads (not vectordb)
const source = isAssistantUpload ? assistantSource : req.app.locals.fileStrategy; const source = isAssistantUpload ? assistantSource : appConfig.fileStrategy;
const { handleFileUpload } = getStrategyFunctions(source); const { handleFileUpload } = getStrategyFunctions(source);
const { file_id, temp_file_id = null } = metadata; const { file_id, temp_file_id = null } = metadata;
@ -501,6 +505,7 @@ const processFileUpload = async ({ req, res, metadata }) => {
*/ */
const processAgentFileUpload = async ({ req, res, metadata }) => { const processAgentFileUpload = async ({ req, res, metadata }) => {
const { file } = req; const { file } = req;
const appConfig = await getAppConfig({ role: req.user?.role });
const { agent_id, tool_resource, file_id, temp_file_id = null } = metadata; const { agent_id, tool_resource, file_id, temp_file_id = null } = metadata;
if (agent_id && !tool_resource) { if (agent_id && !tool_resource) {
throw new Error('No tool resource provided for agent file upload'); throw new Error('No tool resource provided for agent file upload');
@ -553,7 +558,7 @@ const processAgentFileUpload = async ({ req, res, metadata }) => {
} }
const { handleFileUpload: uploadOCR } = getStrategyFunctions( const { handleFileUpload: uploadOCR } = getStrategyFunctions(
req.app.locals?.ocr?.strategy ?? FileSources.mistral_ocr, appConfig?.ocr?.strategy ?? FileSources.mistral_ocr,
); );
const { file_id, temp_file_id = null } = metadata; const { file_id, temp_file_id = null } = metadata;
@ -564,7 +569,7 @@ const processAgentFileUpload = async ({ req, res, metadata }) => {
images: _i, images: _i,
filename, filename,
filepath: ocrFileURL, filepath: ocrFileURL,
} = await uploadOCR({ req, file, loadAuthValues }); } = await uploadOCR({ req, appConfig, file, loadAuthValues });
const fileInfo = removeNullishValues({ const fileInfo = removeNullishValues({
text, text,
@ -597,7 +602,7 @@ const processAgentFileUpload = async ({ req, res, metadata }) => {
// Dual storage pattern for RAG files: Storage + Vector DB // Dual storage pattern for RAG files: Storage + Vector DB
let storageResult, embeddingResult; let storageResult, embeddingResult;
const isImageFile = file.mimetype.startsWith('image'); const isImageFile = file.mimetype.startsWith('image');
const source = getFileStrategy(req.app.locals, { isImage: isImageFile }); const source = getFileStrategy(appConfig, { isImage: isImageFile });
if (tool_resource === EToolResources.file_search) { if (tool_resource === EToolResources.file_search) {
// FIRST: Upload to Storage for permanent backup (S3/local/etc.) // FIRST: Upload to Storage for permanent backup (S3/local/etc.)
@ -752,6 +757,7 @@ const processOpenAIFile = async ({
const processOpenAIImageOutput = async ({ req, buffer, file_id, filename, fileExt }) => { const processOpenAIImageOutput = async ({ req, buffer, file_id, filename, fileExt }) => {
const currentDate = new Date(); const currentDate = new Date();
const formattedDate = currentDate.toISOString(); const formattedDate = currentDate.toISOString();
const appConfig = await getAppConfig({ role: req.user?.role });
const _file = await convertImage(req, buffer, undefined, `${file_id}${fileExt}`); const _file = await convertImage(req, buffer, undefined, `${file_id}${fileExt}`);
// Create only one file record with the correct information // Create only one file record with the correct information
@ -762,7 +768,7 @@ const processOpenAIImageOutput = async ({ req, buffer, file_id, filename, fileEx
type: mime.getType(fileExt), type: mime.getType(fileExt),
createdAt: formattedDate, createdAt: formattedDate,
updatedAt: formattedDate, updatedAt: formattedDate,
source: getFileStrategy(req.app.locals, { isImage: true }), source: getFileStrategy(appConfig, { isImage: true }),
context: FileContext.assistants_output, context: FileContext.assistants_output,
file_id, file_id,
filename, filename,
@ -889,7 +895,7 @@ async function saveBase64Image(
url, url,
{ req, file_id: _file_id, filename: _filename, endpoint, context, resolution }, { req, file_id: _file_id, filename: _filename, endpoint, context, resolution },
) { ) {
const effectiveResolution = resolution ?? req.app.locals.fileConfig?.imageGeneration ?? 'high'; const effectiveResolution = resolution ?? appConfig.fileConfig?.imageGeneration ?? 'high';
const file_id = _file_id ?? v4(); const file_id = _file_id ?? v4();
let filename = `${file_id}-${_filename}`; let filename = `${file_id}-${_filename}`;
const { buffer: inputBuffer, type } = base64ToBuffer(url); const { buffer: inputBuffer, type } = base64ToBuffer(url);
@ -903,7 +909,8 @@ async function saveBase64Image(
} }
const image = await resizeImageBuffer(inputBuffer, effectiveResolution, endpoint); const image = await resizeImageBuffer(inputBuffer, effectiveResolution, endpoint);
const source = getFileStrategy(req.app.locals, { isImage: true }); const appConfig = await getAppConfig({ role: req.user?.role });
const source = getFileStrategy(appConfig, { isImage: true });
const { saveBuffer } = getStrategyFunctions(source); const { saveBuffer } = getStrategyFunctions(source);
const filepath = await saveBuffer({ const filepath = await saveBuffer({
userId: req.user.id, userId: req.user.id,
@ -964,7 +971,8 @@ function filterFile({ req, image, isAvatar }) {
throw new Error('No endpoint provided'); throw new Error('No endpoint provided');
} }
const fileConfig = mergeFileConfig(req.app.locals.fileConfig); const appConfig = getAppConfig({ role: req.user?.role });
const fileConfig = mergeFileConfig(appConfig.fileConfig);
const { fileSizeLimit: sizeLimit, supportedMimeTypes } = const { fileSizeLimit: sizeLimit, supportedMimeTypes } =
fileConfig.endpoints[endpoint] ?? fileConfig.endpoints.default; fileConfig.endpoints[endpoint] ?? fileConfig.endpoints.default;

View file

@ -1,6 +1,7 @@
const axios = require('axios'); const axios = require('axios');
const { logAxiosError } = require('@librechat/api'); const { logAxiosError } = require('@librechat/api');
const { EModelEndpoint } = require('librechat-data-provider'); const { EModelEndpoint } = require('librechat-data-provider');
const { getAppConfig } = require('~/server/services/Config');
/** /**
* @typedef {Object} RetrieveOptions * @typedef {Object} RetrieveOptions
@ -18,6 +19,7 @@ const { EModelEndpoint } = require('librechat-data-provider');
* @returns {Promise<Object>} The data retrieved from the API. * @returns {Promise<Object>} The data retrieved from the API.
*/ */
async function retrieveRun({ thread_id, run_id, timeout, openai }) { async function retrieveRun({ thread_id, run_id, timeout, openai }) {
const appConfig = await getAppConfig({ role: openai.req.user?.role });
const { apiKey, baseURL, httpAgent, organization } = openai; const { apiKey, baseURL, httpAgent, organization } = openai;
let url = `${baseURL}/threads/${thread_id}/runs/${run_id}`; let url = `${baseURL}/threads/${thread_id}/runs/${run_id}`;
@ -31,7 +33,7 @@ async function retrieveRun({ thread_id, run_id, timeout, openai }) {
} }
/** @type {TAzureConfig | undefined} */ /** @type {TAzureConfig | undefined} */
const azureConfig = openai.req.app.locals[EModelEndpoint.azureOpenAI]; const azureConfig = appConfig[EModelEndpoint.azureOpenAI];
if (azureConfig && azureConfig.assistants) { if (azureConfig && azureConfig.assistants) {
delete headers.Authorization; delete headers.Authorization;

View file

@ -32,8 +32,8 @@ const {
manifestToolMap, manifestToolMap,
toolkits, toolkits,
} = require('~/app/clients/tools'); } = require('~/app/clients/tools');
const { getEndpointsConfig, getCachedTools, getAppConfig } = require('~/server/services/Config');
const { processFileURL, uploadImageBuffer } = require('~/server/services/Files/process'); const { processFileURL, uploadImageBuffer } = require('~/server/services/Files/process');
const { getEndpointsConfig, getCachedTools } = require('~/server/services/Config');
const { createOnSearchResults } = require('~/server/services/Tools/search'); const { createOnSearchResults } = require('~/server/services/Tools/search');
const { isActionDomainAllowed } = require('~/server/services/domains'); const { isActionDomainAllowed } = require('~/server/services/domains');
const { recordUsage } = require('~/server/services/Threads'); const { recordUsage } = require('~/server/services/Threads');
@ -202,6 +202,7 @@ async function processRequiredActions(client, requiredActions) {
`[required actions] user: ${client.req.user.id} | thread_id: ${requiredActions[0].thread_id} | run_id: ${requiredActions[0].run_id}`, `[required actions] user: ${client.req.user.id} | thread_id: ${requiredActions[0].thread_id} | run_id: ${requiredActions[0].run_id}`,
requiredActions, requiredActions,
); );
const appConfig = await getAppConfig({ role: client.req.user?.role });
const toolDefinitions = await getCachedTools({ userId: client.req.user.id, includeGlobal: true }); const toolDefinitions = await getCachedTools({ userId: client.req.user.id, includeGlobal: true });
const seenToolkits = new Set(); const seenToolkits = new Set();
const tools = requiredActions const tools = requiredActions
@ -233,7 +234,7 @@ async function processRequiredActions(client, requiredActions) {
req: client.req, req: client.req,
uploadImageBuffer, uploadImageBuffer,
openAIApiKey: client.apiKey, openAIApiKey: client.apiKey,
fileStrategy: client.req.app.locals.fileStrategy, fileStrategy: appConfig.fileStrategy,
returnMetadata: true, returnMetadata: true,
}, },
}); });
@ -480,12 +481,13 @@ async function loadAgentTools({ req, res, agent, tool_resources, openAIApiKey })
return {}; return {};
} }
const appConfig = await getAppConfig({ role: req.user?.role });
const endpointsConfig = await getEndpointsConfig(req); const endpointsConfig = await getEndpointsConfig(req);
let enabledCapabilities = new Set(endpointsConfig?.[EModelEndpoint.agents]?.capabilities ?? []); let enabledCapabilities = new Set(endpointsConfig?.[EModelEndpoint.agents]?.capabilities ?? []);
/** Edge case: use defined/fallback capabilities when the "agents" endpoint is not enabled */ /** Edge case: use defined/fallback capabilities when the "agents" endpoint is not enabled */
if (enabledCapabilities.size === 0 && agent.id === Constants.EPHEMERAL_AGENT_ID) { if (enabledCapabilities.size === 0 && agent.id === Constants.EPHEMERAL_AGENT_ID) {
enabledCapabilities = new Set( enabledCapabilities = new Set(
req.app?.locals?.[EModelEndpoint.agents]?.capabilities ?? defaultAgentCapabilities, appConfig?.[EModelEndpoint.agents]?.capabilities ?? defaultAgentCapabilities,
); );
} }
const checkCapability = (capability) => { const checkCapability = (capability) => {
@ -536,7 +538,7 @@ async function loadAgentTools({ req, res, agent, tool_resources, openAIApiKey })
processFileURL, processFileURL,
uploadImageBuffer, uploadImageBuffer,
returnMetadata: true, returnMetadata: true,
fileStrategy: req.app.locals.fileStrategy, fileStrategy: appConfig.fileStrategy,
[Tools.web_search]: webSearchCallbacks, [Tools.web_search]: webSearchCallbacks,
}, },
}); });

View file

@ -1,15 +1,15 @@
const { logger } = require('@librechat/data-schemas'); const { logger } = require('@librechat/data-schemas');
const { getCachedTools, setCachedTools } = require('./Config'); const { getCachedTools, setCachedTools, getAppConfig } = require('./Config');
const { CacheKeys } = require('librechat-data-provider'); const { CacheKeys } = require('librechat-data-provider');
const { createMCPManager } = require('~/config'); const { createMCPManager } = require('~/config');
const { getLogStores } = require('~/cache'); const { getLogStores } = require('~/cache');
/** /**
* Initialize MCP servers * Initialize MCP servers
* @param {import('express').Application} app - Express app instance
*/ */
async function initializeMCPs(app) { async function initializeMCPs() {
const mcpServers = app.locals.mcpConfig; const appConfig = await getAppConfig();
const mcpServers = appConfig.mcpConfig;
if (!mcpServers) { if (!mcpServers) {
return; return;
} }
@ -17,7 +17,6 @@ async function initializeMCPs(app) {
const mcpManager = await createMCPManager(mcpServers); const mcpManager = await createMCPManager(mcpServers);
try { try {
delete app.locals.mcpConfig;
const cachedTools = await getCachedTools(); const cachedTools = await getCachedTools();
if (!cachedTools) { if (!cachedTools) {

View file

@ -24,11 +24,11 @@ const { FileContext } = require('librechat-data-provider');
* { isAvatar: true } * { isAvatar: true }
* ) // Returns 'local' * ) // Returns 'local'
*/ */
function getFileStrategy(appLocals, { isAvatar = false, isImage = false, context = null } = {}) { function getFileStrategy(appConfig, { isAvatar = false, isImage = false, context = null } = {}) {
// Handle both old (config object) and new (app.locals object) calling patterns // Handle both old (config object) and new (`appConfig` object) calling patterns
const isAppLocals = appLocals.fileStrategy !== undefined; const isAppConfig = appConfig.fileStrategy !== undefined;
const config = isAppLocals ? appLocals.config : appLocals; const config = isAppConfig ? appConfig.config : appConfig;
const fileStrategy = isAppLocals ? appLocals.fileStrategy : appLocals.fileStrategy; const fileStrategy = isAppConfig ? appConfig.fileStrategy : appConfig.fileStrategy;
// Fallback to legacy single strategy if no granular config // Fallback to legacy single strategy if no granular config
if (!config?.fileStrategies) { if (!config?.fileStrategies) {

View file

@ -1,9 +1,10 @@
import { primeResources } from './resources'; import { primeResources } from './resources';
import { logger } from '@librechat/data-schemas'; import { logger } from '@librechat/data-schemas';
import { EModelEndpoint, EToolResources, AgentCapabilities } from 'librechat-data-provider'; import { EModelEndpoint, EToolResources, AgentCapabilities } from 'librechat-data-provider';
import type { TAgentsEndpoint, TFile } from 'librechat-data-provider';
import type { Request as ServerRequest } from 'express'; import type { Request as ServerRequest } from 'express';
import type { TFile } from 'librechat-data-provider';
import type { TGetFiles } from './resources'; import type { TGetFiles } from './resources';
import type { AppConfig } from '~/types';
// Mock logger // Mock logger
jest.mock('@librechat/data-schemas', () => ({ jest.mock('@librechat/data-schemas', () => ({
@ -14,6 +15,7 @@ jest.mock('@librechat/data-schemas', () => ({
describe('primeResources', () => { describe('primeResources', () => {
let mockReq: ServerRequest; let mockReq: ServerRequest;
let mockAppConfig: AppConfig;
let mockGetFiles: jest.MockedFunction<TGetFiles>; let mockGetFiles: jest.MockedFunction<TGetFiles>;
let requestFileSet: Set<string>; let requestFileSet: Set<string>;
@ -32,6 +34,13 @@ describe('primeResources', () => {
}, },
} as unknown as ServerRequest; } as unknown as ServerRequest;
// Setup mock appConfig
mockAppConfig = {
[EModelEndpoint.agents]: {
capabilities: [AgentCapabilities.ocr],
} as TAgentsEndpoint,
} as AppConfig;
// Setup mock getFiles function // Setup mock getFiles function
mockGetFiles = jest.fn(); mockGetFiles = jest.fn();
@ -65,6 +74,7 @@ describe('primeResources', () => {
const result = await primeResources({ const result = await primeResources({
req: mockReq, req: mockReq,
appConfig: mockAppConfig,
getFiles: mockGetFiles, getFiles: mockGetFiles,
requestFileSet, requestFileSet,
attachments: undefined, attachments: undefined,
@ -85,6 +95,7 @@ describe('primeResources', () => {
describe('when OCR is disabled', () => { describe('when OCR is disabled', () => {
it('should not fetch OCR files even if tool_resources has OCR file_ids', async () => { it('should not fetch OCR files even if tool_resources has OCR file_ids', async () => {
(mockReq.app as ServerRequest['app']).locals[EModelEndpoint.agents].capabilities = []; (mockReq.app as ServerRequest['app']).locals[EModelEndpoint.agents].capabilities = [];
(mockAppConfig[EModelEndpoint.agents] as TAgentsEndpoint).capabilities = [];
const tool_resources = { const tool_resources = {
[EToolResources.ocr]: { [EToolResources.ocr]: {
@ -94,6 +105,7 @@ describe('primeResources', () => {
const result = await primeResources({ const result = await primeResources({
req: mockReq, req: mockReq,
appConfig: mockAppConfig,
getFiles: mockGetFiles, getFiles: mockGetFiles,
requestFileSet, requestFileSet,
attachments: undefined, attachments: undefined,
@ -129,6 +141,7 @@ describe('primeResources', () => {
const result = await primeResources({ const result = await primeResources({
req: mockReq, req: mockReq,
appConfig: mockAppConfig,
getFiles: mockGetFiles, getFiles: mockGetFiles,
requestFileSet, requestFileSet,
attachments, attachments,
@ -158,6 +171,7 @@ describe('primeResources', () => {
const result = await primeResources({ const result = await primeResources({
req: mockReq, req: mockReq,
appConfig: mockAppConfig,
getFiles: mockGetFiles, getFiles: mockGetFiles,
requestFileSet, requestFileSet,
attachments, attachments,
@ -189,6 +203,7 @@ describe('primeResources', () => {
const result = await primeResources({ const result = await primeResources({
req: mockReq, req: mockReq,
appConfig: mockAppConfig,
getFiles: mockGetFiles, getFiles: mockGetFiles,
requestFileSet, requestFileSet,
attachments, attachments,
@ -220,6 +235,7 @@ describe('primeResources', () => {
const result = await primeResources({ const result = await primeResources({
req: mockReq, req: mockReq,
appConfig: mockAppConfig,
getFiles: mockGetFiles, getFiles: mockGetFiles,
requestFileSet, requestFileSet,
attachments, attachments,
@ -250,6 +266,7 @@ describe('primeResources', () => {
const result = await primeResources({ const result = await primeResources({
req: mockReq, req: mockReq,
appConfig: mockAppConfig,
getFiles: mockGetFiles, getFiles: mockGetFiles,
requestFileSet, requestFileSet,
attachments, attachments,
@ -291,6 +308,7 @@ describe('primeResources', () => {
const result = await primeResources({ const result = await primeResources({
req: mockReq, req: mockReq,
appConfig: mockAppConfig,
getFiles: mockGetFiles, getFiles: mockGetFiles,
requestFileSet, requestFileSet,
attachments, attachments,
@ -342,6 +360,7 @@ describe('primeResources', () => {
const result = await primeResources({ const result = await primeResources({
req: mockReq, req: mockReq,
appConfig: mockAppConfig,
getFiles: mockGetFiles, getFiles: mockGetFiles,
requestFileSet, requestFileSet,
attachments, attachments,
@ -399,6 +418,7 @@ describe('primeResources', () => {
const result = await primeResources({ const result = await primeResources({
req: mockReq, req: mockReq,
appConfig: mockAppConfig,
getFiles: mockGetFiles, getFiles: mockGetFiles,
requestFileSet, requestFileSet,
attachments, attachments,
@ -450,6 +470,7 @@ describe('primeResources', () => {
const result = await primeResources({ const result = await primeResources({
req: mockReq, req: mockReq,
appConfig: mockAppConfig,
getFiles: mockGetFiles, getFiles: mockGetFiles,
requestFileSet, requestFileSet,
attachments, attachments,
@ -492,6 +513,7 @@ describe('primeResources', () => {
const result = await primeResources({ const result = await primeResources({
req: mockReq, req: mockReq,
appConfig: mockAppConfig,
getFiles: mockGetFiles, getFiles: mockGetFiles,
requestFileSet, requestFileSet,
attachments, attachments,
@ -560,6 +582,7 @@ describe('primeResources', () => {
const result = await primeResources({ const result = await primeResources({
req: mockReq, req: mockReq,
appConfig: mockAppConfig,
getFiles: mockGetFiles, getFiles: mockGetFiles,
requestFileSet, requestFileSet,
attachments, attachments,
@ -618,6 +641,7 @@ describe('primeResources', () => {
const result = await primeResources({ const result = await primeResources({
req: mockReq, req: mockReq,
appConfig: mockAppConfig,
getFiles: mockGetFiles, getFiles: mockGetFiles,
requestFileSet, requestFileSet,
attachments, attachments,
@ -671,6 +695,7 @@ describe('primeResources', () => {
const result = await primeResources({ const result = await primeResources({
req: mockReq, req: mockReq,
appConfig: mockAppConfig,
getFiles: mockGetFiles, getFiles: mockGetFiles,
requestFileSet, requestFileSet,
attachments, attachments,
@ -724,6 +749,7 @@ describe('primeResources', () => {
const result = await primeResources({ const result = await primeResources({
req: mockReq, req: mockReq,
appConfig: mockAppConfig,
getFiles: mockGetFiles, getFiles: mockGetFiles,
requestFileSet, requestFileSet,
attachments, attachments,
@ -764,6 +790,7 @@ describe('primeResources', () => {
const result = await primeResources({ const result = await primeResources({
req: mockReq, req: mockReq,
appConfig: mockAppConfig,
getFiles: mockGetFiles, getFiles: mockGetFiles,
requestFileSet, requestFileSet,
attachments, attachments,
@ -838,6 +865,7 @@ describe('primeResources', () => {
const result = await primeResources({ const result = await primeResources({
req: mockReq, req: mockReq,
appConfig: mockAppConfig,
getFiles: mockGetFiles, getFiles: mockGetFiles,
requestFileSet, requestFileSet,
attachments, attachments,
@ -888,6 +916,7 @@ describe('primeResources', () => {
const result = await primeResources({ const result = await primeResources({
req: mockReq, req: mockReq,
appConfig: mockAppConfig,
getFiles: mockGetFiles, getFiles: mockGetFiles,
requestFileSet, requestFileSet,
attachments, attachments,
@ -906,6 +935,7 @@ describe('primeResources', () => {
// The function should now handle rejected attachment promises gracefully // The function should now handle rejected attachment promises gracefully
const result = await primeResources({ const result = await primeResources({
req: mockReq, req: mockReq,
appConfig: mockAppConfig,
getFiles: mockGetFiles, getFiles: mockGetFiles,
requestFileSet, requestFileSet,
attachments, attachments,
@ -928,9 +958,11 @@ describe('primeResources', () => {
describe('edge cases', () => { describe('edge cases', () => {
it('should handle missing app.locals gracefully', async () => { it('should handle missing app.locals gracefully', async () => {
const reqWithoutLocals = {} as ServerRequest; const reqWithoutLocals = {} as ServerRequest;
const emptyAppConfig = {} as AppConfig;
const result = await primeResources({ const result = await primeResources({
req: reqWithoutLocals, req: reqWithoutLocals,
appConfig: emptyAppConfig,
getFiles: mockGetFiles, getFiles: mockGetFiles,
requestFileSet, requestFileSet,
attachments: undefined, attachments: undefined,
@ -950,6 +982,7 @@ describe('primeResources', () => {
it('should handle undefined tool_resources', async () => { it('should handle undefined tool_resources', async () => {
const result = await primeResources({ const result = await primeResources({
req: mockReq, req: mockReq,
appConfig: mockAppConfig,
getFiles: mockGetFiles, getFiles: mockGetFiles,
requestFileSet, requestFileSet,
attachments: undefined, attachments: undefined,
@ -982,6 +1015,7 @@ describe('primeResources', () => {
const result = await primeResources({ const result = await primeResources({
req: mockReq, req: mockReq,
appConfig: mockAppConfig,
getFiles: mockGetFiles, getFiles: mockGetFiles,
requestFileSet: emptyRequestFileSet, requestFileSet: emptyRequestFileSet,
attachments, attachments,

View file

@ -4,6 +4,7 @@ import type { AgentToolResources, TFile, AgentBaseResource } from 'librechat-dat
import type { FilterQuery, QueryOptions, ProjectionType } from 'mongoose'; import type { FilterQuery, QueryOptions, ProjectionType } from 'mongoose';
import type { IMongoFile, IUser } from '@librechat/data-schemas'; import type { IMongoFile, IUser } from '@librechat/data-schemas';
import type { Request as ServerRequest } from 'express'; import type { Request as ServerRequest } from 'express';
import type { AppConfig } from '~/types/';
/** /**
* Function type for retrieving files from the database * Function type for retrieving files from the database
@ -134,7 +135,8 @@ const categorizeFileForToolResources = ({
* 4. Prevents duplicate files across all sources * 4. Prevents duplicate files across all sources
* *
* @param params - Parameters object * @param params - Parameters object
* @param params.req - Express request object containing app configuration * @param params.req - Express request object
* @param params.appConfig - Application configuration object
* @param params.getFiles - Function to retrieve files from database * @param params.getFiles - Function to retrieve files from database
* @param params.requestFileSet - Set of file IDs from the current request * @param params.requestFileSet - Set of file IDs from the current request
* @param params.attachments - Promise resolving to array of attachment files * @param params.attachments - Promise resolving to array of attachment files
@ -143,6 +145,7 @@ const categorizeFileForToolResources = ({
*/ */
export const primeResources = async ({ export const primeResources = async ({
req, req,
appConfig,
getFiles, getFiles,
requestFileSet, requestFileSet,
attachments: _attachments, attachments: _attachments,
@ -150,6 +153,7 @@ export const primeResources = async ({
agentId, agentId,
}: { }: {
req: ServerRequest & { user?: IUser }; req: ServerRequest & { user?: IUser };
appConfig: AppConfig;
requestFileSet: Set<string>; requestFileSet: Set<string>;
attachments: Promise<Array<TFile | null>> | undefined; attachments: Promise<Array<TFile | null>> | undefined;
tool_resources: AgentToolResources | undefined; tool_resources: AgentToolResources | undefined;
@ -198,7 +202,7 @@ export const primeResources = async ({
} }
} }
const isOCREnabled = (req.app.locals?.[EModelEndpoint.agents]?.capabilities ?? []).includes( const isOCREnabled = (appConfig?.[EModelEndpoint.agents]?.capabilities ?? []).includes(
AgentCapabilities.ocr, AgentCapabilities.ocr,
); );

View file

@ -1,9 +1,9 @@
import { ErrorTypes, EModelEndpoint, mapModelToAzureConfig } from 'librechat-data-provider'; import { ErrorTypes, EModelEndpoint, mapModelToAzureConfig } from 'librechat-data-provider';
import type { import type {
UserKeyValues, InitializeOpenAIOptionsParams,
OpenAIOptionsResult, OpenAIOptionsResult,
OpenAIConfigOptions, OpenAIConfigOptions,
InitializeOpenAIOptionsParams, UserKeyValues,
} from '~/types'; } from '~/types';
import { createHandleLLMNewToken } from '~/utils/generators'; import { createHandleLLMNewToken } from '~/utils/generators';
import { getAzureCredentials } from '~/utils/azure'; import { getAzureCredentials } from '~/utils/azure';
@ -21,6 +21,7 @@ import { getOpenAIConfig } from './llm';
*/ */
export const initializeOpenAI = async ({ export const initializeOpenAI = async ({
req, req,
appConfig,
overrideModel, overrideModel,
endpointOption, endpointOption,
overrideEndpoint, overrideEndpoint,
@ -71,7 +72,7 @@ export const initializeOpenAI = async ({
}; };
const isAzureOpenAI = endpoint === EModelEndpoint.azureOpenAI; const isAzureOpenAI = endpoint === EModelEndpoint.azureOpenAI;
const azureConfig = isAzureOpenAI && req.app.locals[EModelEndpoint.azureOpenAI]; const azureConfig = isAzureOpenAI && appConfig[EModelEndpoint.azureOpenAI];
if (isAzureOpenAI && azureConfig) { if (isAzureOpenAI && azureConfig) {
const { modelGroupMap, groupMap } = azureConfig; const { modelGroupMap, groupMap } = azureConfig;
@ -142,8 +143,8 @@ export const initializeOpenAI = async ({
const options = getOpenAIConfig(apiKey, finalClientOptions, endpoint); const options = getOpenAIConfig(apiKey, finalClientOptions, endpoint);
const openAIConfig = req.app.locals[EModelEndpoint.openAI]; const openAIConfig = appConfig[EModelEndpoint.openAI];
const allConfig = req.app.locals.all; const allConfig = appConfig.all;
const azureRate = modelName?.includes('gpt-4') ? 30 : 17; const azureRate = modelName?.includes('gpt-4') ? 30 : 17;
let streamRate: number | undefined; let streamRate: number | undefined;

View file

@ -46,7 +46,12 @@ import * as fs from 'fs';
import axios from 'axios'; import axios from 'axios';
import type { Request as ExpressRequest } from 'express'; import type { Request as ExpressRequest } from 'express';
import type { Readable } from 'stream'; import type { Readable } from 'stream';
import type { MistralFileUploadResponse, MistralSignedUrlResponse, OCRResult } from '~/types'; import type {
MistralFileUploadResponse,
MistralSignedUrlResponse,
OCRResult,
AppConfig,
} from '~/types';
import { logger as mockLogger } from '@librechat/data-schemas'; import { logger as mockLogger } from '@librechat/data-schemas';
import { import {
uploadDocumentToMistral, uploadDocumentToMistral,
@ -497,17 +502,16 @@ describe('MistralOCR Service', () => {
const req = { const req = {
user: { id: 'user123' }, user: { id: 'user123' },
app: { } as unknown as ExpressRequest;
locals: {
const appConfig = {
ocr: { ocr: {
// Use environment variable syntax to ensure loadAuthValues is called // Use environment variable syntax to ensure loadAuthValues is called
apiKey: '${OCR_API_KEY}', apiKey: '${OCR_API_KEY}',
baseURL: '${OCR_BASEURL}', baseURL: '${OCR_BASEURL}',
mistralModel: 'mistral-medium', mistralModel: 'mistral-medium',
}, },
}, } as AppConfig;
},
} as unknown as ExpressRequest;
const file = { const file = {
path: '/tmp/upload/file.pdf', path: '/tmp/upload/file.pdf',
@ -517,6 +521,7 @@ describe('MistralOCR Service', () => {
const result = await uploadMistralOCR({ const result = await uploadMistralOCR({
req, req,
appConfig,
file, file,
loadAuthValues: mockLoadAuthValues, loadAuthValues: mockLoadAuthValues,
}); });
@ -599,16 +604,15 @@ describe('MistralOCR Service', () => {
const req = { const req = {
user: { id: 'user456' }, user: { id: 'user456' },
app: { } as unknown as ExpressRequest;
locals: {
const appConfig = {
ocr: { ocr: {
apiKey: '${OCR_API_KEY}', apiKey: '${OCR_API_KEY}',
baseURL: '${OCR_BASEURL}', baseURL: '${OCR_BASEURL}',
mistralModel: 'mistral-medium', mistralModel: 'mistral-medium',
}, },
}, } as AppConfig;
},
} as unknown as ExpressRequest;
const file = { const file = {
path: '/tmp/upload/image.png', path: '/tmp/upload/image.png',
@ -618,6 +622,7 @@ describe('MistralOCR Service', () => {
const result = await uploadMistralOCR({ const result = await uploadMistralOCR({
req, req,
appConfig,
file, file,
loadAuthValues: mockLoadAuthValues, loadAuthValues: mockLoadAuthValues,
}); });
@ -698,16 +703,15 @@ describe('MistralOCR Service', () => {
const req = { const req = {
user: { id: 'user123' }, user: { id: 'user123' },
app: { } as unknown as ExpressRequest;
locals: {
const appConfig = {
ocr: { ocr: {
apiKey: '${CUSTOM_API_KEY}', apiKey: '${CUSTOM_API_KEY}',
baseURL: '${CUSTOM_BASEURL}', baseURL: '${CUSTOM_BASEURL}',
mistralModel: '${CUSTOM_MODEL}', mistralModel: '${CUSTOM_MODEL}',
}, },
}, } as AppConfig;
},
} as unknown as ExpressRequest;
// Set environment variable for model // Set environment variable for model
process.env.CUSTOM_MODEL = 'mistral-large'; process.env.CUSTOM_MODEL = 'mistral-large';
@ -720,6 +724,7 @@ describe('MistralOCR Service', () => {
const result = await uploadMistralOCR({ const result = await uploadMistralOCR({
req, req,
appConfig,
file, file,
loadAuthValues: mockLoadAuthValues, loadAuthValues: mockLoadAuthValues,
}); });
@ -790,17 +795,16 @@ describe('MistralOCR Service', () => {
const req = { const req = {
user: { id: 'user123' }, user: { id: 'user123' },
app: { } as unknown as ExpressRequest;
locals: {
const appConfig = {
ocr: { ocr: {
// Use environment variable syntax to ensure loadAuthValues is called // Use environment variable syntax to ensure loadAuthValues is called
apiKey: '${INVALID_FORMAT}', // Using valid env var format but with an invalid name apiKey: '${INVALID_FORMAT}', // Using valid env var format but with an invalid name
baseURL: '${OCR_BASEURL}', // Using valid env var format baseURL: '${OCR_BASEURL}', // Using valid env var format
mistralModel: 'mistral-ocr-latest', // Plain string value mistralModel: 'mistral-ocr-latest', // Plain string value
}, },
}, } as AppConfig;
},
} as unknown as ExpressRequest;
const file = { const file = {
path: '/tmp/upload/file.pdf', path: '/tmp/upload/file.pdf',
@ -810,6 +814,7 @@ describe('MistralOCR Service', () => {
await uploadMistralOCR({ await uploadMistralOCR({
req, req,
appConfig,
file, file,
loadAuthValues: mockLoadAuthValues, loadAuthValues: mockLoadAuthValues,
}); });
@ -845,15 +850,14 @@ describe('MistralOCR Service', () => {
const req = { const req = {
user: { id: 'user123' }, user: { id: 'user123' },
app: { } as unknown as ExpressRequest;
locals: {
const appConfig = {
ocr: { ocr: {
apiKey: 'OCR_API_KEY', apiKey: 'OCR_API_KEY',
baseURL: 'OCR_BASEURL', baseURL: 'OCR_BASEURL',
}, },
}, } as AppConfig;
},
} as unknown as ExpressRequest;
const file = { const file = {
path: '/tmp/upload/file.pdf', path: '/tmp/upload/file.pdf',
@ -864,6 +868,7 @@ describe('MistralOCR Service', () => {
await expect( await expect(
uploadMistralOCR({ uploadMistralOCR({
req, req,
appConfig,
file, file,
loadAuthValues: mockLoadAuthValues, loadAuthValues: mockLoadAuthValues,
}), }),
@ -931,16 +936,15 @@ describe('MistralOCR Service', () => {
const req = { const req = {
user: { id: 'user123' }, user: { id: 'user123' },
app: { } as unknown as ExpressRequest;
locals: {
const appConfig = {
ocr: { ocr: {
apiKey: 'OCR_API_KEY', apiKey: 'OCR_API_KEY',
baseURL: 'OCR_BASEURL', baseURL: 'OCR_BASEURL',
mistralModel: 'mistral-ocr-latest', mistralModel: 'mistral-ocr-latest',
}, },
}, } as AppConfig;
},
} as unknown as ExpressRequest;
const file = { const file = {
path: '/tmp/upload/file.pdf', path: '/tmp/upload/file.pdf',
@ -950,6 +954,7 @@ describe('MistralOCR Service', () => {
const result = await uploadMistralOCR({ const result = await uploadMistralOCR({
req, req,
appConfig,
file, file,
loadAuthValues: mockLoadAuthValues, loadAuthValues: mockLoadAuthValues,
}); });
@ -1019,17 +1024,16 @@ describe('MistralOCR Service', () => {
const req = { const req = {
user: { id: 'user123' }, user: { id: 'user123' },
app: { } as unknown as ExpressRequest;
locals: {
const appConfig = {
ocr: { ocr: {
// Direct values that should be used as-is, without variable substitution // Direct values that should be used as-is, without variable substitution
apiKey: 'actual-api-key-value', apiKey: 'actual-api-key-value',
baseURL: 'https://direct-api-url.mistral.ai/v1', baseURL: 'https://direct-api-url.mistral.ai/v1',
mistralModel: 'mistral-direct-model', mistralModel: 'mistral-direct-model',
}, },
}, } as AppConfig;
},
} as unknown as ExpressRequest;
const file = { const file = {
path: '/tmp/upload/file.pdf', path: '/tmp/upload/file.pdf',
@ -1039,6 +1043,7 @@ describe('MistralOCR Service', () => {
const result = await uploadMistralOCR({ const result = await uploadMistralOCR({
req, req,
appConfig,
file, file,
loadAuthValues: mockLoadAuthValues, loadAuthValues: mockLoadAuthValues,
}); });
@ -1133,17 +1138,16 @@ describe('MistralOCR Service', () => {
const req = { const req = {
user: { id: 'user123' }, user: { id: 'user123' },
app: { } as unknown as ExpressRequest;
locals: {
const appConfig = {
ocr: { ocr: {
// Empty string values - should fall back to defaults // Empty string values - should fall back to defaults
apiKey: '', apiKey: '',
baseURL: '', baseURL: '',
mistralModel: '', mistralModel: '',
}, },
}, } as AppConfig;
},
} as unknown as ExpressRequest;
const file = { const file = {
path: '/tmp/upload/file.pdf', path: '/tmp/upload/file.pdf',
@ -1153,6 +1157,7 @@ describe('MistralOCR Service', () => {
const result = await uploadMistralOCR({ const result = await uploadMistralOCR({
req, req,
appConfig,
file, file,
loadAuthValues: mockLoadAuthValues, loadAuthValues: mockLoadAuthValues,
}); });
@ -1276,16 +1281,15 @@ describe('MistralOCR Service', () => {
const req = { const req = {
user: { id: 'user123' }, user: { id: 'user123' },
app: { } as unknown as ExpressRequest;
locals: {
const appConfig = {
ocr: { ocr: {
apiKey: '${AZURE_MISTRAL_OCR_API_KEY}', apiKey: '${AZURE_MISTRAL_OCR_API_KEY}',
baseURL: 'https://endpoint.models.ai.azure.com/v1', baseURL: 'https://endpoint.models.ai.azure.com/v1',
mistralModel: 'mistral-ocr-2503', mistralModel: 'mistral-ocr-2503',
}, },
}, } as AppConfig;
},
} as unknown as ExpressRequest;
const file = { const file = {
path: '/tmp/upload/file.pdf', path: '/tmp/upload/file.pdf',
@ -1295,6 +1299,7 @@ describe('MistralOCR Service', () => {
await uploadMistralOCR({ await uploadMistralOCR({
req, req,
appConfig,
file, file,
loadAuthValues: mockLoadAuthValues, loadAuthValues: mockLoadAuthValues,
}); });
@ -1360,16 +1365,15 @@ describe('MistralOCR Service', () => {
const req = { const req = {
user: { id: 'user456' }, user: { id: 'user456' },
app: { } as unknown as ExpressRequest;
locals: {
const appConfig = {
ocr: { ocr: {
apiKey: 'hardcoded-api-key-12345', apiKey: 'hardcoded-api-key-12345',
baseURL: '${CUSTOM_OCR_BASEURL}', baseURL: '${CUSTOM_OCR_BASEURL}',
mistralModel: 'mistral-ocr-latest', mistralModel: 'mistral-ocr-latest',
}, },
}, } as AppConfig;
},
} as unknown as ExpressRequest;
const file = { const file = {
path: '/tmp/upload/file.pdf', path: '/tmp/upload/file.pdf',
@ -1379,6 +1383,7 @@ describe('MistralOCR Service', () => {
await uploadMistralOCR({ await uploadMistralOCR({
req, req,
appConfig,
file, file,
loadAuthValues: mockLoadAuthValues, loadAuthValues: mockLoadAuthValues,
}); });
@ -1484,16 +1489,15 @@ describe('MistralOCR Service', () => {
const req = { const req = {
user: { id: 'user123' }, user: { id: 'user123' },
app: { } as unknown as ExpressRequest;
locals: {
const appConfig = {
ocr: { ocr: {
apiKey: '${OCR_API_KEY}', apiKey: '${OCR_API_KEY}',
baseURL: '${OCR_BASEURL}', baseURL: '${OCR_BASEURL}',
mistralModel: 'mistral-ocr-latest', mistralModel: 'mistral-ocr-latest',
}, },
}, } as AppConfig;
},
} as unknown as ExpressRequest;
const file = { const file = {
path: '/tmp/upload/file.pdf', path: '/tmp/upload/file.pdf',
@ -1503,6 +1507,7 @@ describe('MistralOCR Service', () => {
await uploadMistralOCR({ await uploadMistralOCR({
req, req,
appConfig,
file, file,
loadAuthValues: mockLoadAuthValues, loadAuthValues: mockLoadAuthValues,
}); });
@ -1553,16 +1558,15 @@ describe('MistralOCR Service', () => {
const req = { const req = {
user: { id: 'user123' }, user: { id: 'user123' },
app: { } as unknown as ExpressRequest;
locals: {
const appConfig = {
ocr: { ocr: {
apiKey: '${OCR_API_KEY}', apiKey: '${OCR_API_KEY}',
baseURL: '${OCR_BASEURL}', baseURL: '${OCR_BASEURL}',
mistralModel: 'mistral-ocr-latest', mistralModel: 'mistral-ocr-latest',
}, },
}, } as AppConfig;
},
} as unknown as ExpressRequest;
const file = { const file = {
path: '/tmp/upload/file.pdf', path: '/tmp/upload/file.pdf',
@ -1573,6 +1577,7 @@ describe('MistralOCR Service', () => {
await expect( await expect(
uploadMistralOCR({ uploadMistralOCR({
req, req,
appConfig,
file, file,
loadAuthValues: mockLoadAuthValues, loadAuthValues: mockLoadAuthValues,
}), }),
@ -1641,16 +1646,15 @@ describe('MistralOCR Service', () => {
const req = { const req = {
user: { id: 'user123' }, user: { id: 'user123' },
app: { } as unknown as ExpressRequest;
locals: {
const appConfig = {
ocr: { ocr: {
apiKey: '${OCR_API_KEY}', apiKey: '${OCR_API_KEY}',
baseURL: '${OCR_BASEURL}', baseURL: '${OCR_BASEURL}',
mistralModel: 'mistral-ocr-latest', mistralModel: 'mistral-ocr-latest',
}, },
}, } as AppConfig;
},
} as unknown as ExpressRequest;
const file = { const file = {
path: '/tmp/upload/file.pdf', path: '/tmp/upload/file.pdf',
@ -1661,6 +1665,7 @@ describe('MistralOCR Service', () => {
// Should not throw even if delete fails // Should not throw even if delete fails
const result = await uploadMistralOCR({ const result = await uploadMistralOCR({
req, req,
appConfig,
file, file,
loadAuthValues: mockLoadAuthValues, loadAuthValues: mockLoadAuthValues,
}); });
@ -1701,16 +1706,15 @@ describe('MistralOCR Service', () => {
const req = { const req = {
user: { id: 'user123' }, user: { id: 'user123' },
app: { } as unknown as ExpressRequest;
locals: {
const appConfig = {
ocr: { ocr: {
apiKey: '${OCR_API_KEY}', apiKey: '${OCR_API_KEY}',
baseURL: '${OCR_BASEURL}', baseURL: '${OCR_BASEURL}',
mistralModel: 'mistral-ocr-latest', mistralModel: 'mistral-ocr-latest',
}, },
}, } as AppConfig;
},
} as unknown as ExpressRequest;
const file = { const file = {
path: '/tmp/upload/file.pdf', path: '/tmp/upload/file.pdf',
@ -1721,6 +1725,7 @@ describe('MistralOCR Service', () => {
await expect( await expect(
uploadMistralOCR({ uploadMistralOCR({
req, req,
appConfig,
file, file,
loadAuthValues: mockLoadAuthValues, loadAuthValues: mockLoadAuthValues,
}), }),
@ -1775,16 +1780,15 @@ describe('MistralOCR Service', () => {
const req = { const req = {
user: { id: 'user123' }, user: { id: 'user123' },
app: { } as unknown as ExpressRequest;
locals: {
const appConfig = {
ocr: { ocr: {
apiKey: '${OCR_API_KEY}', apiKey: '${OCR_API_KEY}',
baseURL: '${OCR_BASEURL}', baseURL: '${OCR_BASEURL}',
mistralModel: 'mistral-ocr-latest', mistralModel: 'mistral-ocr-latest',
}, },
}, } as AppConfig;
},
} as unknown as ExpressRequest;
const file = { const file = {
path: '/tmp/upload/azure-file.pdf', path: '/tmp/upload/azure-file.pdf',
@ -1794,6 +1798,7 @@ describe('MistralOCR Service', () => {
const result = await uploadAzureMistralOCR({ const result = await uploadAzureMistralOCR({
req, req,
appConfig,
file, file,
loadAuthValues: mockLoadAuthValues, loadAuthValues: mockLoadAuthValues,
}); });
@ -1851,16 +1856,15 @@ describe('MistralOCR Service', () => {
const req = { const req = {
user: { id: 'user123' }, user: { id: 'user123' },
app: { } as unknown as ExpressRequest;
locals: {
const appConfig = {
ocr: { ocr: {
apiKey: '${AZURE_MISTRAL_OCR_API_KEY}', apiKey: '${AZURE_MISTRAL_OCR_API_KEY}',
baseURL: 'https://endpoint.models.ai.azure.com/v1', baseURL: 'https://endpoint.models.ai.azure.com/v1',
mistralModel: 'mistral-ocr-2503', mistralModel: 'mistral-ocr-2503',
}, },
}, } as AppConfig;
},
} as unknown as ExpressRequest;
const file = { const file = {
path: '/tmp/upload/file.pdf', path: '/tmp/upload/file.pdf',
@ -1870,6 +1874,7 @@ describe('MistralOCR Service', () => {
await uploadAzureMistralOCR({ await uploadAzureMistralOCR({
req, req,
appConfig,
file, file,
loadAuthValues: mockLoadAuthValues, loadAuthValues: mockLoadAuthValues,
}); });
@ -1915,16 +1920,15 @@ describe('MistralOCR Service', () => {
const req = { const req = {
user: { id: 'user456' }, user: { id: 'user456' },
app: { } as unknown as ExpressRequest;
locals: {
const appConfig = {
ocr: { ocr: {
apiKey: 'hardcoded-api-key-12345', apiKey: 'hardcoded-api-key-12345',
baseURL: '${CUSTOM_OCR_BASEURL}', baseURL: '${CUSTOM_OCR_BASEURL}',
mistralModel: 'mistral-ocr-latest', mistralModel: 'mistral-ocr-latest',
}, },
}, } as AppConfig;
},
} as unknown as ExpressRequest;
const file = { const file = {
path: '/tmp/upload/file.pdf', path: '/tmp/upload/file.pdf',
@ -1934,6 +1938,7 @@ describe('MistralOCR Service', () => {
await uploadAzureMistralOCR({ await uploadAzureMistralOCR({
req, req,
appConfig,
file, file,
loadAuthValues: mockLoadAuthValues, loadAuthValues: mockLoadAuthValues,
}); });

View file

@ -17,6 +17,7 @@ import type {
MistralOCRUploadResult, MistralOCRUploadResult,
MistralOCRError, MistralOCRError,
OCRResultPage, OCRResultPage,
AppConfig,
OCRResult, OCRResult,
OCRImage, OCRImage,
} from '~/types'; } from '~/types';
@ -42,14 +43,10 @@ interface GoogleServiceAccount {
/** Helper type for OCR request context */ /** Helper type for OCR request context */
interface OCRContext { interface OCRContext {
req: Pick<ServerRequest, 'user' | 'app'> & { req: Pick<ServerRequest, 'user'> & {
user?: { id: string }; user?: { id: string };
app: {
locals?: {
ocr?: TCustomConfig['ocr'];
};
};
}; };
appConfig: AppConfig;
file: Express.Multer.File; file: Express.Multer.File;
loadAuthValues: (params: { loadAuthValues: (params: {
userId: string; userId: string;
@ -241,7 +238,7 @@ async function resolveConfigValue(
* Loads authentication configuration from OCR config * Loads authentication configuration from OCR config
*/ */
async function loadAuthConfig(context: OCRContext): Promise<AuthConfig> { async function loadAuthConfig(context: OCRContext): Promise<AuthConfig> {
const ocrConfig = context.req.app.locals?.ocr; const ocrConfig = context.appConfig?.ocr;
const apiKeyConfig = ocrConfig?.apiKey || ''; const apiKeyConfig = ocrConfig?.apiKey || '';
const baseURLConfig = ocrConfig?.baseURL || ''; const baseURLConfig = ocrConfig?.baseURL || '';
@ -357,6 +354,7 @@ function createOCRError(error: unknown, baseMessage: string): Error {
* @param params - The params object. * @param params - The params object.
* @param params.req - The request object from Express. It should have a `user` property with an `id` * @param params.req - The request object from Express. It should have a `user` property with an `id`
* representing the user * representing the user
* @param params.appConfig - Application configuration object
* @param params.file - The file object, which is part of the request. The file object should * @param params.file - The file object, which is part of the request. The file object should
* have a `mimetype` property that tells us the file type * have a `mimetype` property that tells us the file type
* @param params.loadAuthValues - Function to load authentication values * @param params.loadAuthValues - Function to load authentication values
@ -372,7 +370,7 @@ export const uploadMistralOCR = async (context: OCRContext): Promise<MistralOCRU
const authConfig = await loadAuthConfig(context); const authConfig = await loadAuthConfig(context);
apiKey = authConfig.apiKey; apiKey = authConfig.apiKey;
baseURL = authConfig.baseURL; baseURL = authConfig.baseURL;
const model = getModelConfig(context.req.app.locals?.ocr); const model = getModelConfig(context.appConfig?.ocr);
const mistralFile = await uploadDocumentToMistral({ const mistralFile = await uploadDocumentToMistral({
filePath: context.file.path, filePath: context.file.path,
@ -430,6 +428,7 @@ export const uploadMistralOCR = async (context: OCRContext): Promise<MistralOCRU
* @param params - The params object. * @param params - The params object.
* @param params.req - The request object from Express. It should have a `user` property with an `id` * @param params.req - The request object from Express. It should have a `user` property with an `id`
* representing the user * representing the user
* @param params.appConfig - Application configuration object
* @param params.file - The file object, which is part of the request. The file object should * @param params.file - The file object, which is part of the request. The file object should
* have a `mimetype` property that tells us the file type * have a `mimetype` property that tells us the file type
* @param params.loadAuthValues - Function to load authentication values * @param params.loadAuthValues - Function to load authentication values
@ -441,7 +440,7 @@ export const uploadAzureMistralOCR = async (
): Promise<MistralOCRUploadResult> => { ): Promise<MistralOCRUploadResult> => {
try { try {
const { apiKey, baseURL } = await loadAuthConfig(context); const { apiKey, baseURL } = await loadAuthConfig(context);
const model = getModelConfig(context.req.app.locals?.ocr); const model = getModelConfig(context.appConfig?.ocr);
const buffer = fs.readFileSync(context.file.path); const buffer = fs.readFileSync(context.file.path);
const base64 = buffer.toString('base64'); const base64 = buffer.toString('base64');
@ -644,6 +643,7 @@ async function performGoogleVertexOCR({
* @param params - The params object. * @param params - The params object.
* @param params.req - The request object from Express. It should have a `user` property with an `id` * @param params.req - The request object from Express. It should have a `user` property with an `id`
* representing the user * representing the user
* @param params.appConfig - Application configuration object
* @param params.file - The file object, which is part of the request. The file object should * @param params.file - The file object, which is part of the request. The file object should
* have a `mimetype` property that tells us the file type * have a `mimetype` property that tells us the file type
* @param params.loadAuthValues - Function to load authentication values * @param params.loadAuthValues - Function to load authentication values
@ -655,7 +655,7 @@ export const uploadGoogleVertexMistralOCR = async (
): Promise<MistralOCRUploadResult> => { ): Promise<MistralOCRUploadResult> => {
try { try {
const { serviceAccount, accessToken } = await loadGoogleAuthConfig(); const { serviceAccount, accessToken } = await loadGoogleAuthConfig();
const model = getModelConfig(context.req.app.locals?.ocr); const model = getModelConfig(context.appConfig?.ocr);
const buffer = fs.readFileSync(context.file.path); const buffer = fs.readFileSync(context.file.path);
const base64 = buffer.toString('base64'); const base64 = buffer.toString('base64');

View file

@ -0,0 +1,78 @@
import type {
TEndpoint,
FileSources,
TAzureConfig,
TCustomConfig,
TMemoryConfig,
EModelEndpoint,
TAgentsEndpoint,
TAssistantEndpoint,
} from 'librechat-data-provider';
/**
* Application configuration object
* Based on the configuration defined in api/server/services/Config/getAppConfig.js
*/
export interface AppConfig {
/** The main custom configuration */
config: TCustomConfig;
/** OCR configuration */
ocr?: TCustomConfig['ocr'];
/** File paths configuration */
paths: {
uploads: string;
imageOutput: string;
publicPath: string;
[key: string]: string;
};
/** Memory configuration */
memory?: TMemoryConfig;
/** Web search configuration */
webSearch?: TCustomConfig['webSearch'];
/** File storage strategy ('local', 's3', 'firebase', 'azure_blob') */
fileStrategy: FileSources.local | FileSources.s3 | FileSources.firebase | FileSources.azure_blob;
/** Social login configurations */
socialLogins: Array<unknown>;
/** Admin-filtered tools */
filteredTools?: string[];
/** Admin-included tools */
includedTools?: string[];
/** Image output type configuration */
imageOutputType: string;
/** Interface configuration */
interfaceConfig?: TCustomConfig['interface'];
/** Turnstile configuration */
turnstileConfig?: TCustomConfig['registration'];
/** Balance configuration */
balance?: TCustomConfig['balance'];
/** MCP server configuration */
mcpConfig?: TCustomConfig['mcpServers'] | null;
/** File configuration */
fileConfig?: TCustomConfig['fileConfig'];
/** Secure image links configuration */
secureImageLinks?: TCustomConfig['secureImageLinks'];
/** Processed model specifications */
modelSpecs?: TCustomConfig['modelSpecs'];
/** OpenAI endpoint configuration */
openAI?: TEndpoint;
/** Google endpoint configuration */
google?: TEndpoint;
/** Bedrock endpoint configuration */
bedrock?: TEndpoint;
/** Anthropic endpoint configuration */
anthropic?: TEndpoint;
/** GPT plugins endpoint configuration */
gptPlugins?: TEndpoint;
/** Azure OpenAI endpoint configuration */
azureOpenAI?: TAzureConfig;
/** Assistants endpoint configuration */
assistants?: TAssistantEndpoint;
/** Azure assistants endpoint configuration */
azureAssistants?: TAssistantEndpoint;
/** Agents endpoint configuration */
[EModelEndpoint.agents]?: TAgentsEndpoint;
/** Global endpoint configuration */
all?: TEndpoint;
/** Any additional endpoint configurations */
[key: string]: unknown;
}

View file

@ -1,3 +1,4 @@
export * from './config';
export * from './azure'; export * from './azure';
export * from './balance'; export * from './balance';
export * from './events'; export * from './events';

View file

@ -4,6 +4,7 @@ import type { TEndpointOption, TAzureConfig, TEndpoint } from 'librechat-data-pr
import type { BindToolsInput } from '@langchain/core/language_models/chat_models'; import type { BindToolsInput } from '@langchain/core/language_models/chat_models';
import type { OpenAIClientOptions, Providers } from '@librechat/agents'; import type { OpenAIClientOptions, Providers } from '@librechat/agents';
import type { AzureOptions } from './azure'; import type { AzureOptions } from './azure';
import type { AppConfig } from './config';
export type OpenAIParameters = z.infer<typeof openAISchema>; export type OpenAIParameters = z.infer<typeof openAISchema>;
@ -85,6 +86,7 @@ export type CheckUserKeyExpiryFunction = (expiresAt: string, endpoint: string) =
*/ */
export interface InitializeOpenAIOptionsParams { export interface InitializeOpenAIOptionsParams {
req: RequestData; req: RequestData;
appConfig: AppConfig;
overrideModel?: string; overrideModel?: string;
overrideEndpoint?: string; overrideEndpoint?: string;
endpointOption: Partial<TEndpointOption>; endpointOption: Partial<TEndpointOption>;

View file

@ -1213,6 +1213,10 @@ export enum CacheKeys {
* Key for the static config namespace. * Key for the static config namespace.
*/ */
STATIC_CONFIG = 'STATIC_CONFIG', STATIC_CONFIG = 'STATIC_CONFIG',
/**
* Key for the app config namespace.
*/
APP_CONFIG = 'APP_CONFIG',
/** /**
* Key for accessing Abort Keys * Key for accessing Abort Keys
*/ */