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

@ -6,7 +6,7 @@ const {
filterUniquePlugins,
convertMCPToolsToPlugins,
} = 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 { getMCPManager, getFlowStateManager } = require('~/config');
const { getLogStores } = require('~/cache');
@ -20,8 +20,9 @@ const getAvailablePluginsController = async (req, res) => {
return;
}
const appConfig = await getAppConfig({ role: req.user?.role });
/** @type {{ filteredTools: string[], includedTools: string[] }} */
const { filteredTools = [], includedTools = [] } = req.app.locals;
const { filteredTools = [], includedTools = [] } = appConfig;
const pluginManifest = availableTools;
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 { processDeleteRequest } = require('~/server/services/Files/process');
const { Transaction, Balance, User } = require('~/db/models');
const { getAppConfig } = require('~/server/services/Config');
const { deleteToolCalls } = require('~/models/ToolCall');
const { deleteAllSharedLinks } = require('~/models');
const { getMCPManager } = require('~/config');
const getUserController = async (req, res) => {
const appConfig = await getAppConfig({ role: req.user?.role });
/** @type {MongoUser} */
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.totpSecret;
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);
if (!avatarNeedsRefresh) {
return res.status(200).send(userData);
@ -87,6 +89,7 @@ const deleteUserFiles = async (req) => {
};
const updateUserPluginsController = async (req, res) => {
const appConfig = await getAppConfig({ role: req.user?.role });
const { user } = req;
const { pluginKey, action, auth, isEntityTool } = req.body;
try {
@ -131,7 +134,7 @@ const updateUserPluginsController = async (req, res) => {
if (pluginKey === Tools.web_search) {
/** @type {TCustomConfig['webSearch']} */
const webSearchConfig = req.app.locals?.webSearch;
const webSearchConfig = appConfig?.webSearch;
keys = extractWebSearchEnvVars({
keys: action === 'install' ? keys : webSearchKeys,
config: webSearchConfig,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,9 +5,9 @@ const { uploadImageBuffer, filterFile } = require('~/server/services/Files/proce
const validateAuthor = require('~/server/middleware/assistants/validateAuthor');
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
const { deleteAssistantActions } = require('~/server/services/ActionService');
const { getCachedTools, getAppConfig } = require('~/server/services/Config');
const { updateAssistantDoc, getAssistants } = require('~/models/Assistant');
const { getOpenAIClient, fetchAssistants } = require('./helpers');
const { getCachedTools } = require('~/server/services/Config');
const { manifestToolMap } = require('~/app/clients/tools');
const { deleteFileByFilter } = require('~/models/File');
@ -258,8 +258,9 @@ function filterAssistantDocs({ documents, userId, assistantsConfig = {} }) {
*/
const getAssistantDocuments = async (req, res) => {
try {
const appConfig = await getAppConfig({ role: req.user?.role });
const endpoint = req.query;
const assistantsConfig = req.app.locals[endpoint];
const assistantsConfig = appConfig[endpoint];
const documents = await getAssistants(
{},
{
@ -296,6 +297,7 @@ const getAssistantDocuments = async (req, res) => {
*/
const uploadAssistantAvatar = async (req, res) => {
try {
const appConfig = await getAppConfig({ role: req.user?.role });
filterFile({ req, file: req.file, image: true, isAvatar: true });
const { assistant_id } = req.params;
if (!assistant_id) {
@ -337,7 +339,7 @@ const uploadAssistantAvatar = async (req, res) => {
const metadata = {
..._metadata,
avatar: image.filepath,
avatar_source: req.app.locals.fileStrategy,
avatar_source: appConfig.fileStrategy,
};
const promises = [];
@ -347,7 +349,7 @@ const uploadAssistantAvatar = async (req, res) => {
{
avatar: {
filepath: image.filepath,
source: req.app.locals.fileStrategy,
source: appConfig.fileStrategy,
},
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 { createToolCall, getToolCallsByConvo } = require('~/models/ToolCall');
const { loadAuthValues } = require('~/server/services/Tools/credentials');
const { getAppConfig } = require('~/server/services/Config');
const { loadTools } = require('~/app/clients/tools/util');
const { getRoleByName } = require('~/models/Role');
const { getMessage } = require('~/models/Message');
@ -35,9 +36,10 @@ const toolAccessPermType = {
*/
const verifyWebSearchAuth = async (req, res) => {
try {
const appConfig = await getAppConfig({ role: req.user?.role });
const userId = req.user.id;
/** @type {TCustomConfig['webSearch']} */
const webSearchConfig = req.app.locals?.webSearch || {};
const webSearchConfig = appConfig?.webSearch || {};
const result = await loadWebSearchAuth({
userId,
loadAuthValues,
@ -110,6 +112,7 @@ const verifyToolAuth = async (req, res) => {
*/
const callTool = async (req, res) => {
try {
const appConfig = await getAppConfig({ role: req.user?.role });
const { toolId = '' } = req.params;
if (!fieldsMap[toolId]) {
logger.warn(`[${toolId}/call] User ${req.user.id} attempted call to invalid tool`);
@ -155,7 +158,7 @@ const callTool = async (req, res) => {
returnMetadata: true,
processFileURL,
uploadImageBuffer,
fileStrategy: req.app.locals.fileStrategy,
fileStrategy: appConfig.fileStrategy,
},
});