mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-22 11:20:15 +01:00
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:
parent
5a14ee9c6a
commit
b992fed16c
66 changed files with 706 additions and 366 deletions
|
|
@ -11,6 +11,7 @@ const {
|
|||
Constants,
|
||||
} = require('librechat-data-provider');
|
||||
const { getMessages, saveMessage, updateMessage, saveConvo, getConvo } = require('~/models');
|
||||
const { getAppConfig } = require('~/server/services/Config');
|
||||
const { checkBalance } = require('~/models/balanceMethods');
|
||||
const { truncateToolCallOutputs } = require('./prompts');
|
||||
const { getFiles } = require('~/models/File');
|
||||
|
|
@ -567,6 +568,7 @@ class BaseClient {
|
|||
}
|
||||
|
||||
async sendMessage(message, opts = {}) {
|
||||
const appConfig = await getAppConfig({ role: this.options.req?.user?.role });
|
||||
/** @type {Promise<TMessage>} */
|
||||
let userMessagePromise;
|
||||
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 (
|
||||
balance?.enabled &&
|
||||
supportsBalanceCheck[this.options.endpointType ?? this.options.endpoint]
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ const {
|
|||
const { extractBaseURL, getModelMaxTokens, getModelMaxOutputTokens } = require('~/utils');
|
||||
const { encodeAndFormat } = require('~/server/services/Files/images/encode');
|
||||
const { addSpaceIfNeeded, sleep } = require('~/server/utils');
|
||||
const { getAppConfig } = require('~/server/services/Config');
|
||||
const { spendTokens } = require('~/models/spendTokens');
|
||||
const { handleOpenAIErrors } = require('./tools/util');
|
||||
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".
|
||||
*/
|
||||
async titleConvo({ text, conversationId, responseText = '' }) {
|
||||
const appConfig = await getAppConfig({ role: this.options.req?.user?.role });
|
||||
this.conversationId = conversationId;
|
||||
|
||||
if (this.options.attachments) {
|
||||
|
|
@ -731,7 +733,7 @@ class OpenAIClient extends BaseClient {
|
|||
};
|
||||
|
||||
/** @type {TAzureConfig | undefined} */
|
||||
const azureConfig = this.options?.req?.app?.locals?.[EModelEndpoint.azureOpenAI];
|
||||
const azureConfig = appConfig?.[EModelEndpoint.azureOpenAI];
|
||||
|
||||
const resetTitleOptions = !!(
|
||||
(this.azure && azureConfig) ||
|
||||
|
|
@ -1120,6 +1122,7 @@ ${convo}
|
|||
}
|
||||
|
||||
async chatCompletion({ payload, onProgress, abortController = null }) {
|
||||
const appConfig = await getAppConfig({ role: this.options.req?.user?.role });
|
||||
let error = null;
|
||||
let intermediateReply = [];
|
||||
const errorCallback = (err) => (error = err);
|
||||
|
|
@ -1166,7 +1169,7 @@ ${convo}
|
|||
}
|
||||
|
||||
/** @type {TAzureConfig | undefined} */
|
||||
const azureConfig = this.options?.req?.app?.locals?.[EModelEndpoint.azureOpenAI];
|
||||
const azureConfig = appConfig?.[EModelEndpoint.azureOpenAI];
|
||||
|
||||
if (
|
||||
(this.azure && this.isVisionModel && azureConfig) ||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ const { logAxiosError } = require('@librechat/api');
|
|||
const { logger } = require('@librechat/data-schemas');
|
||||
const { ContentTypes, EImageOutputType } = require('librechat-data-provider');
|
||||
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
||||
const { getAppConfig } = require('~/server/services/Config');
|
||||
const { extractBaseURL } = require('~/utils');
|
||||
const { getFiles } = require('~/models/File');
|
||||
|
||||
|
|
@ -123,7 +124,7 @@ function createAbortHandler() {
|
|||
* @param {MongoFile[]} [fields.imageFiles] - The images to be used for editing
|
||||
* @returns {Array} - Array of image tools
|
||||
*/
|
||||
function createOpenAIImageTools(fields = {}) {
|
||||
async function createOpenAIImageTools(fields = {}) {
|
||||
/** @type {boolean} Used to initialize the Tool without necessary variables. */
|
||||
const override = fields.override ?? false;
|
||||
/** @type {boolean} */
|
||||
|
|
@ -131,8 +132,9 @@ function createOpenAIImageTools(fields = {}) {
|
|||
throw new Error('This tool is only available for agents.');
|
||||
}
|
||||
const { req } = fields;
|
||||
const imageOutputType = req?.app.locals.imageOutputType || EImageOutputType.PNG;
|
||||
const appFileStrategy = req?.app.locals.fileStrategy;
|
||||
const appConfig = await getAppConfig({ role: req?.user?.role });
|
||||
const imageOutputType = appConfig?.imageOutputType || EImageOutputType.PNG;
|
||||
const appFileStrategy = appConfig?.fileStrategy;
|
||||
|
||||
const getApiKey = () => {
|
||||
const apiKey = process.env.IMAGE_GEN_OAI_API_KEY ?? '';
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ const {
|
|||
const { primeFiles: primeCodeFiles } = require('~/server/services/Files/Code/process');
|
||||
const { createFileSearchTool, primeFiles: primeSearchFiles } = require('./fileSearch');
|
||||
const { getUserPluginAuthValue } = require('~/server/services/PluginService');
|
||||
const { getCachedTools, getAppConfig } = require('~/server/services/Config');
|
||||
const { loadAuthValues } = require('~/server/services/Tools/credentials');
|
||||
const { getCachedTools } = require('~/server/services/Config');
|
||||
const { createMCPTool } = require('~/server/services/MCP');
|
||||
|
||||
/**
|
||||
|
|
@ -143,6 +143,7 @@ const loadTools = async ({
|
|||
functions = true,
|
||||
returnMap = false,
|
||||
}) => {
|
||||
const appConfig = await getAppConfig({ role: options?.req?.user?.role });
|
||||
const toolConstructors = {
|
||||
flux: FluxAPI,
|
||||
calculator: Calculator,
|
||||
|
|
@ -272,7 +273,7 @@ const loadTools = async ({
|
|||
};
|
||||
continue;
|
||||
} else if (tool === Tools.web_search) {
|
||||
const webSearchConfig = options?.req?.app?.locals?.webSearch;
|
||||
const webSearchConfig = appConfig?.webSearch;
|
||||
const result = await loadWebSearchAuth({
|
||||
userId: user,
|
||||
loadAuthValues,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ const { jwtLogin, ldapLogin, passportLogin } = require('~/strategies');
|
|||
const { checkMigrations } = require('./services/start/migration');
|
||||
const initializeMCPs = require('./services/initializeMCPs');
|
||||
const configureSocialLogins = require('./socialLogins');
|
||||
const { getAppConfig } = require('./services/Config');
|
||||
const AppService = require('./services/AppService');
|
||||
const staticCache = require('./utils/staticCache');
|
||||
const noIndex = require('./middleware/noIndex');
|
||||
|
|
@ -46,8 +47,8 @@ const startServer = async () => {
|
|||
app.set('trust proxy', trusted_proxy);
|
||||
|
||||
await AppService(app);
|
||||
|
||||
const indexPath = path.join(app.locals.paths.dist, 'index.html');
|
||||
const appConfig = await getAppConfig();
|
||||
const indexPath = path.join(appConfig.paths.dist, 'index.html');
|
||||
const indexHTML = fs.readFileSync(indexPath, 'utf8');
|
||||
|
||||
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.');
|
||||
}
|
||||
|
||||
// Serve static assets with aggressive caching
|
||||
app.use(staticCache(app.locals.paths.dist));
|
||||
app.use(staticCache(app.locals.paths.fonts));
|
||||
app.use(staticCache(app.locals.paths.assets));
|
||||
app.use(staticCache(appConfig.paths.dist));
|
||||
app.use(staticCache(appConfig.paths.fonts));
|
||||
app.use(staticCache(appConfig.paths.assets));
|
||||
|
||||
if (!ALLOW_SOCIAL_LOGIN) {
|
||||
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}`);
|
||||
}
|
||||
|
||||
initializeMCPs(app).then(() => checkMigrations());
|
||||
initializeMCPs().then(() => checkMigrations());
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
const { v4 } = require('uuid');
|
||||
const { handleAbortError } = require('~/server/middleware/abortMiddleware');
|
||||
const { getAppConfig } = require('~/server/services/Config/getAppConfig');
|
||||
|
||||
/**
|
||||
* 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 { endpoint, conversationId, assistant_id, messageId } = req.body;
|
||||
|
||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||
/** @type {Partial<TAssistantEndpoint>} */
|
||||
const assistantsConfig = req.app.locals?.[endpoint];
|
||||
const assistantsConfig = appConfig?.[endpoint];
|
||||
if (!assistantsConfig) {
|
||||
return next();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
const { SystemRoles } = require('librechat-data-provider');
|
||||
const { getAppConfig } = require('~/server/services/Config/getAppConfig');
|
||||
const { getAssistant } = require('~/models/Assistant');
|
||||
|
||||
/**
|
||||
|
|
@ -20,8 +21,9 @@ const validateAuthor = async ({ req, openai, overrideEndpoint, overrideAssistant
|
|||
const 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>} */
|
||||
const assistantsConfig = req.app.locals?.[endpoint];
|
||||
const assistantsConfig = appConfig?.[endpoint];
|
||||
if (!assistantsConfig) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ const {
|
|||
parseCompactConvo,
|
||||
} = require('librechat-data-provider');
|
||||
const azureAssistants = require('~/server/services/Endpoints/azureAssistants');
|
||||
const { getAppConfig } = require('~/server/services/Config/getAppConfig');
|
||||
const assistants = require('~/server/services/Endpoints/assistants');
|
||||
const { processFiles } = require('~/server/services/Files/process');
|
||||
const anthropic = require('~/server/services/Endpoints/anthropic');
|
||||
|
|
@ -40,9 +41,10 @@ async function buildEndpointOption(req, res, next) {
|
|||
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[] }}*/
|
||||
const { list } = req.app.locals.modelSpecs;
|
||||
const { list } = appConfig.modelSpecs;
|
||||
const { spec } = parsedBody;
|
||||
|
||||
if (!spec) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
const cookies = require('cookie');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { getAppConfig } = require('~/server/services/Config/getAppConfig');
|
||||
const { logger } = require('~/config');
|
||||
|
||||
const OBJECT_ID_LENGTH = 24;
|
||||
|
|
@ -24,8 +25,9 @@ function isValidObjectId(id) {
|
|||
* Middleware to validate image request.
|
||||
* Must be set by `secureImageLinks` via custom config file.
|
||||
*/
|
||||
function validateImageRequest(req, res, next) {
|
||||
if (!req.app.locals.secureImageLinks) {
|
||||
async function validateImageRequest(req, res, next) {
|
||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||
if (!appConfig.secureImageLinks) {
|
||||
return next();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ const { getOpenAIClient } = require('~/server/controllers/assistants/helpers');
|
|||
const { updateAction, getActions, deleteAction } = require('~/models/Action');
|
||||
const { updateAssistantDoc, getAssistant } = require('~/models/Assistant');
|
||||
const { isActionDomainAllowed } = require('~/server/services/domains');
|
||||
const { getAppConfig } = require('~/server/services/Config');
|
||||
const { logger } = require('~/config');
|
||||
|
||||
const router = express.Router();
|
||||
|
|
@ -20,6 +21,7 @@ const router = express.Router();
|
|||
* @returns {Object} 200 - success response - application/json
|
||||
*/
|
||||
router.post('/:assistant_id', async (req, res) => {
|
||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||
try {
|
||||
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 */
|
||||
if (req.app.locals[EModelEndpoint.azureOpenAI]?.assistants) {
|
||||
if (appConfig[EModelEndpoint.azureOpenAI]?.assistants) {
|
||||
updatedAssistant = {
|
||||
...updatedAssistant,
|
||||
model: req.body.model,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ const { isEnabled } = require('@librechat/api');
|
|||
const { logger } = require('@librechat/data-schemas');
|
||||
const { CacheKeys, defaultSocialLogins, Constants } = require('librechat-data-provider');
|
||||
const { getCustomConfig } = require('~/server/services/Config/getCustomConfig');
|
||||
const { getAppConfig } = require('~/server/services/Config/getAppConfig');
|
||||
const { getLdapConfig } = require('~/server/services/Config/ldap');
|
||||
const { getProjectByName } = require('~/models/Project');
|
||||
const { getMCPManager } = require('~/config');
|
||||
|
|
@ -43,6 +44,8 @@ router.get('/', async function (req, res) {
|
|||
const ldap = getLdapConfig();
|
||||
|
||||
try {
|
||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||
|
||||
const isOpenIdEnabled =
|
||||
!!process.env.OPENID_CLIENT_ID &&
|
||||
!!process.env.OPENID_CLIENT_SECRET &&
|
||||
|
|
@ -58,7 +61,7 @@ router.get('/', async function (req, res) {
|
|||
/** @type {TStartupConfig} */
|
||||
const payload = {
|
||||
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,
|
||||
facebookLoginEnabled:
|
||||
!!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) ||
|
||||
process.env.SHOW_BIRTHDAY_ICON === '',
|
||||
helpAndFaqURL: process.env.HELP_AND_FAQ_URL || 'https://librechat.ai',
|
||||
interface: req.app.locals.interfaceConfig,
|
||||
turnstile: req.app.locals.turnstileConfig,
|
||||
modelSpecs: req.app.locals.modelSpecs,
|
||||
balance: req.app.locals.balance,
|
||||
interface: appConfig.interfaceConfig,
|
||||
turnstile: appConfig.turnstileConfig,
|
||||
modelSpecs: appConfig.modelSpecs,
|
||||
balance: appConfig.balance,
|
||||
sharedLinksEnabled,
|
||||
publicSharedLinksEnabled,
|
||||
analyticsGtmId: process.env.ANALYTICS_GTM_ID,
|
||||
|
|
@ -128,8 +131,7 @@ router.get('/', async function (req, res) {
|
|||
}
|
||||
}
|
||||
|
||||
/** @type {TCustomConfig['webSearch']} */
|
||||
const webSearchConfig = req.app.locals.webSearch;
|
||||
const webSearchConfig = appConfig.webSearch;
|
||||
if (
|
||||
webSearchConfig != null &&
|
||||
(webSearchConfig.searchProvider ||
|
||||
|
|
|
|||
|
|
@ -2,14 +2,16 @@ const fs = require('fs').promises;
|
|||
const express = require('express');
|
||||
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
||||
const { resizeAvatar } = require('~/server/services/Files/images/avatar');
|
||||
const { filterFile } = require('~/server/services/Files/process');
|
||||
const { getFileStrategy } = require('~/server/utils/getFileStrategy');
|
||||
const { filterFile } = require('~/server/services/Files/process');
|
||||
const { getAppConfig } = require('~/server/services/Config');
|
||||
const { logger } = require('~/config');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.post('/', async (req, res) => {
|
||||
try {
|
||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||
filterFile({ req, file: req.file, image: true, isAvatar: true });
|
||||
const userId = req.user.id;
|
||||
const { manual } = req.body;
|
||||
|
|
@ -19,8 +21,8 @@ router.post('/', async (req, res) => {
|
|||
throw new Error('User ID is undefined');
|
||||
}
|
||||
|
||||
const fileStrategy = getFileStrategy(req.app.locals, { isAvatar: true });
|
||||
const desiredFormat = req.app.locals.imageOutputType;
|
||||
const fileStrategy = getFileStrategy(appConfig, { isAvatar: true });
|
||||
const desiredFormat = appConfig.imageOutputType;
|
||||
const resizedBuffer = await resizeAvatar({
|
||||
userId,
|
||||
input,
|
||||
|
|
@ -39,7 +41,7 @@ router.post('/', async (req, res) => {
|
|||
try {
|
||||
await fs.unlink(req.file.path);
|
||||
logger.debug('[/files/images/avatar] Temp. image upload file deleted');
|
||||
} catch (error) {
|
||||
} catch {
|
||||
logger.debug('[/files/images/avatar] Temp. image upload file already deleted');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ const { loadAuthValues } = require('~/server/services/Tools/credentials');
|
|||
const { refreshS3FileUrls } = require('~/server/services/Files/S3/crud');
|
||||
const { hasAccessToFilesViaAgent } = require('~/server/services/Files');
|
||||
const { getFiles, batchUpdateFiles } = require('~/models/File');
|
||||
const { getAppConfig } = require('~/server/services/Config');
|
||||
const { cleanFileName } = require('~/server/utils/files');
|
||||
const { getAssistant } = require('~/models/Assistant');
|
||||
const { getAgent } = require('~/models/Agent');
|
||||
|
|
@ -36,8 +37,9 @@ const router = express.Router();
|
|||
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||
const files = await getFiles({ user: req.user.id });
|
||||
if (req.app.locals.fileStrategy === FileSources.s3) {
|
||||
if (appConfig.fileStrategy === FileSources.s3) {
|
||||
try {
|
||||
const cache = getLogStores(CacheKeys.S3_EXPIRY_INTERVAL);
|
||||
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) => {
|
||||
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) {
|
||||
logger.error('[/files] Error getting fileConfig', error);
|
||||
res.status(400).json({ message: 'Error in request', error: error.message });
|
||||
|
|
|
|||
|
|
@ -7,11 +7,13 @@ const {
|
|||
processImageFile,
|
||||
processAgentFileUpload,
|
||||
} = require('~/server/services/Files/process');
|
||||
const { getAppConfig } = require('~/server/services/Config');
|
||||
const { logger } = require('~/config');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.post('/', async (req, res) => {
|
||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||
const metadata = req.body;
|
||||
|
||||
try {
|
||||
|
|
@ -30,7 +32,7 @@ router.post('/', async (req, res) => {
|
|||
logger.error('[/files/images] Error processing file:', error);
|
||||
try {
|
||||
const filepath = path.join(
|
||||
req.app.locals.paths.imageOutput,
|
||||
appConfig.paths.imageOutput,
|
||||
req.user.id,
|
||||
path.basename(req.file.filename),
|
||||
);
|
||||
|
|
@ -43,7 +45,7 @@ router.post('/', async (req, res) => {
|
|||
try {
|
||||
await fs.unlink(req.file.path);
|
||||
logger.debug('[/files/images] Temp. image upload file deleted');
|
||||
} catch (error) {
|
||||
} catch {
|
||||
logger.debug('[/files/images] Temp. image upload file already deleted');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,11 +4,12 @@ const crypto = require('crypto');
|
|||
const multer = require('multer');
|
||||
const { sanitizeFilename } = require('@librechat/api');
|
||||
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({
|
||||
destination: function (req, file, cb) {
|
||||
const outputPath = path.join(req.app.locals.paths.uploads, 'temp', req.user.id);
|
||||
destination: async function (req, file, cb) {
|
||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||
const outputPath = path.join(appConfig.paths.uploads, 'temp', req.user.id);
|
||||
if (!fs.existsSync(outputPath)) {
|
||||
fs.mkdirSync(outputPath, { recursive: true });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ const {
|
|||
deleteMemory,
|
||||
setMemory,
|
||||
} = require('~/models');
|
||||
const { getAppConfig } = require('~/server/services/Config');
|
||||
const { requireJwtAuth } = require('~/server/middleware');
|
||||
const { getRoleByName } = require('~/models/Role');
|
||||
|
||||
|
|
@ -60,7 +61,8 @@ router.get('/', checkMemoryRead, async (req, res) => {
|
|||
return sum + (memory.tokenCount || 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 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.' });
|
||||
}
|
||||
|
||||
const memoryConfig = req.app.locals?.memory;
|
||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||
const memoryConfig = appConfig?.memory;
|
||||
const charLimit = memoryConfig?.charLimit || 10000;
|
||||
|
||||
if (key.length > 1000) {
|
||||
|
|
@ -117,6 +120,9 @@ router.post('/', memoryPayloadLimit, checkMemoryCreate, async (req, res) => {
|
|||
const tokenCount = Tokenizer.getTokenCount(value, 'o200k_base');
|
||||
|
||||
const memories = await getAllUserMemories(req.user.id);
|
||||
|
||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||
const memoryConfig = appConfig?.memory;
|
||||
const tokenLimit = memoryConfig?.tokenLimit;
|
||||
|
||||
if (tokenLimit) {
|
||||
|
|
@ -200,8 +206,8 @@ router.patch('/:key', memoryPayloadLimit, checkMemoryUpdate, async (req, res) =>
|
|||
}
|
||||
|
||||
const newKey = bodyKey || urlKey;
|
||||
|
||||
const memoryConfig = req.app.locals?.memory;
|
||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||
const memoryConfig = appConfig?.memory;
|
||||
const charLimit = memoryConfig?.charLimit || 10000;
|
||||
|
||||
if (newKey.length > 1000) {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ const { ensureDefaultCategories, seedDefaultRoles, initializeRoles } = require('
|
|||
const { azureAssistantsDefaults, assistantsConfigSetup } = require('./start/assistants');
|
||||
const { initializeAzureBlobService } = require('./Files/Azure/initialize');
|
||||
const { initializeFirebase } = require('./Files/Firebase/initialize');
|
||||
const { initializeAppConfig } = require('./Config/getAppConfig');
|
||||
const loadCustomConfig = require('./Config/loadCustomConfig');
|
||||
const handleRateLimits = require('./Config/handleRateLimits');
|
||||
const { loadDefaultInterface } = require('./start/interface');
|
||||
|
|
@ -35,9 +36,8 @@ const paths = require('~/config/paths');
|
|||
/**
|
||||
* Loads custom config and initializes app-wide variables.
|
||||
* @function AppService
|
||||
* @param {Express.Application} app - The Express application object.
|
||||
*/
|
||||
const AppService = async (app) => {
|
||||
const AppService = async () => {
|
||||
await initializeRoles();
|
||||
await seedDefaultRoles();
|
||||
await ensureDefaultCategories();
|
||||
|
|
@ -109,10 +109,11 @@ const AppService = async (app) => {
|
|||
const agentsDefaults = agentsConfigSetup(config);
|
||||
|
||||
if (!Object.keys(config).length) {
|
||||
app.locals = {
|
||||
const appConfig = {
|
||||
...defaultLocals,
|
||||
[EModelEndpoint.agents]: agentsDefaults,
|
||||
};
|
||||
await initializeAppConfig(appConfig);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -167,13 +168,15 @@ const AppService = async (app) => {
|
|||
endpointLocals.all = endpoints.all;
|
||||
}
|
||||
|
||||
app.locals = {
|
||||
const appConfig = {
|
||||
...defaultLocals,
|
||||
fileConfig: config?.fileConfig,
|
||||
secureImageLinks: config?.secureImageLinks,
|
||||
modelSpecs: processModelSpecs(endpoints, config.modelSpecs, interfaceConfig),
|
||||
...endpointLocals,
|
||||
};
|
||||
|
||||
await initializeAppConfig(appConfig);
|
||||
};
|
||||
|
||||
module.exports = AppService;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ const { retrieveAndProcessFile } = require('~/server/services/Files/process');
|
|||
const { processRequiredActions } = require('~/server/services/ToolService');
|
||||
const { RunManager, waitForRun } = require('~/server/services/Runs');
|
||||
const { processMessages } = require('~/server/services/Threads');
|
||||
const { getAppConfig } = require('~/server/services/Config');
|
||||
const { createOnProgress } = require('~/server/utils');
|
||||
const { TextStream } = require('~/app/clients');
|
||||
|
||||
|
|
@ -350,6 +351,7 @@ async function runAssistant({
|
|||
accumulatedMessages = [],
|
||||
in_progress: inProgress,
|
||||
}) {
|
||||
const appConfig = await getAppConfig({ role: openai.req.user?.role });
|
||||
let steps = accumulatedSteps;
|
||||
let messages = accumulatedMessages;
|
||||
const in_progress = inProgress ?? createInProgressHandler(openai, thread_id, messages);
|
||||
|
|
@ -397,7 +399,7 @@ async function runAssistant({
|
|||
|
||||
const { endpoint = EModelEndpoint.azureAssistants } = openai.req.body;
|
||||
/** @type {TCustomConfig.endpoints.assistants} */
|
||||
const assistantsEndpointConfig = openai.req.app.locals?.[endpoint] ?? {};
|
||||
const assistantsEndpointConfig = appConfig?.[endpoint] ?? {};
|
||||
const { pollIntervalMs, timeoutMs } = assistantsEndpointConfig;
|
||||
|
||||
const run = await waitForRun({
|
||||
|
|
|
|||
110
api/server/services/Config/getAppConfig.js
Normal file
110
api/server/services/Config/getAppConfig.js
Normal 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,
|
||||
};
|
||||
|
|
@ -8,6 +8,7 @@ const {
|
|||
const loadDefaultEndpointsConfig = require('./loadDefaultEConfig');
|
||||
const loadConfigEndpoints = require('./loadConfigEndpoints');
|
||||
const getLogStores = require('~/cache/getLogStores');
|
||||
const { getAppConfig } = require('./getAppConfig');
|
||||
|
||||
/**
|
||||
*
|
||||
|
|
@ -23,12 +24,13 @@ async function getEndpointsConfig(req) {
|
|||
|
||||
const defaultEndpointsConfig = await loadDefaultEndpointsConfig(req);
|
||||
const customConfigEndpoints = await loadConfigEndpoints(req);
|
||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||
|
||||
/** @type {TEndpointsConfig} */
|
||||
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 } =
|
||||
req.app.locals[EModelEndpoint.assistants];
|
||||
appConfig[EModelEndpoint.assistants];
|
||||
|
||||
mergedConfig[EModelEndpoint.assistants] = {
|
||||
...mergedConfig[EModelEndpoint.assistants],
|
||||
|
|
@ -38,9 +40,9 @@ async function getEndpointsConfig(req) {
|
|||
capabilities,
|
||||
};
|
||||
}
|
||||
if (mergedConfig[EModelEndpoint.agents] && req.app.locals?.[EModelEndpoint.agents]) {
|
||||
if (mergedConfig[EModelEndpoint.agents] && appConfig?.[EModelEndpoint.agents]) {
|
||||
const { disableBuilder, capabilities, allowedProviders, ..._rest } =
|
||||
req.app.locals[EModelEndpoint.agents];
|
||||
appConfig[EModelEndpoint.agents];
|
||||
|
||||
mergedConfig[EModelEndpoint.agents] = {
|
||||
...mergedConfig[EModelEndpoint.agents],
|
||||
|
|
@ -50,12 +52,9 @@ async function getEndpointsConfig(req) {
|
|||
};
|
||||
}
|
||||
|
||||
if (
|
||||
mergedConfig[EModelEndpoint.azureAssistants] &&
|
||||
req.app.locals?.[EModelEndpoint.azureAssistants]
|
||||
) {
|
||||
if (mergedConfig[EModelEndpoint.azureAssistants] && appConfig?.[EModelEndpoint.azureAssistants]) {
|
||||
const { disableBuilder, retrievalModels, capabilities, version, ..._rest } =
|
||||
req.app.locals[EModelEndpoint.azureAssistants];
|
||||
appConfig[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]) {
|
||||
const { availableRegions } = req.app.locals[EModelEndpoint.bedrock];
|
||||
if (mergedConfig[EModelEndpoint.bedrock] && appConfig?.[EModelEndpoint.bedrock]) {
|
||||
const { availableRegions } = appConfig[EModelEndpoint.bedrock];
|
||||
mergedConfig[EModelEndpoint.bedrock] = {
|
||||
...mergedConfig[EModelEndpoint.bedrock],
|
||||
availableRegions,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
const getAppConfig = require('./getAppConfig');
|
||||
const { config } = require('./EndpointService');
|
||||
const getCachedTools = require('./getCachedTools');
|
||||
const getCustomConfig = require('./getCustomConfig');
|
||||
|
|
@ -15,6 +16,7 @@ module.exports = {
|
|||
loadDefaultModels,
|
||||
loadOverrideConfig,
|
||||
loadAsyncEndpoints,
|
||||
...getAppConfig,
|
||||
...getCachedTools,
|
||||
...getCustomConfig,
|
||||
...getEndpointsConfig,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ const path = require('path');
|
|||
const { logger } = require('@librechat/data-schemas');
|
||||
const { loadServiceKey, isUserProvided } = require('@librechat/api');
|
||||
const { EModelEndpoint } = require('librechat-data-provider');
|
||||
const { getAppConfig } = require('./getAppConfig');
|
||||
const { config } = require('./EndpointService');
|
||||
|
||||
const { openAIApiKey, azureOpenAIApiKey, useAzurePlugins, userProvidedOpenAI, googleKey } = config;
|
||||
|
|
@ -11,6 +12,7 @@ const { openAIApiKey, azureOpenAIApiKey, useAzurePlugins, userProvidedOpenAI, go
|
|||
* @param {Express.Request} req - The request object
|
||||
*/
|
||||
async function loadAsyncEndpoints(req) {
|
||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||
let serviceKey, googleUserProvides;
|
||||
|
||||
/** 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 useAzure = req.app.locals[EModelEndpoint.azureOpenAI]?.plugins;
|
||||
const useAzure = appConfig[EModelEndpoint.azureOpenAI]?.plugins;
|
||||
const gptPlugins =
|
||||
useAzure || openAIApiKey || azureOpenAIApiKey
|
||||
? {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
const { EModelEndpoint, extractEnvVariable } = require('librechat-data-provider');
|
||||
const { isUserProvided, normalizeEndpointName } = require('~/server/utils');
|
||||
const { getCustomConfig } = require('./getCustomConfig');
|
||||
const { getAppConfig } = require('./getAppConfig');
|
||||
|
||||
/**
|
||||
* Load config endpoints from the cached configuration object
|
||||
|
|
@ -14,6 +15,8 @@ async function loadConfigEndpoints(req) {
|
|||
return {};
|
||||
}
|
||||
|
||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||
|
||||
const { endpoints = {} } = customConfig ?? {};
|
||||
const endpointsConfig = {};
|
||||
|
||||
|
|
@ -53,14 +56,14 @@ async function loadConfigEndpoints(req) {
|
|||
}
|
||||
}
|
||||
|
||||
if (req.app.locals[EModelEndpoint.azureOpenAI]) {
|
||||
if (appConfig[EModelEndpoint.azureOpenAI]) {
|
||||
/** @type {Omit<TConfig, 'order'>} */
|
||||
endpointsConfig[EModelEndpoint.azureOpenAI] = {
|
||||
userProvide: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (req.app.locals[EModelEndpoint.azureOpenAI]?.assistants) {
|
||||
if (appConfig[EModelEndpoint.azureOpenAI]?.assistants) {
|
||||
/** @type {Omit<TConfig, 'order'>} */
|
||||
endpointsConfig[EModelEndpoint.azureAssistants] = {
|
||||
userProvide: false,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ const { EModelEndpoint, extractEnvVariable } = require('librechat-data-provider'
|
|||
const { isUserProvided, normalizeEndpointName } = require('~/server/utils');
|
||||
const { fetchModels } = require('~/server/services/ModelService');
|
||||
const { getCustomConfig } = require('./getCustomConfig');
|
||||
const { getAppConfig } = require('./getAppConfig');
|
||||
|
||||
/**
|
||||
* Load config endpoints from the cached configuration object
|
||||
|
|
@ -15,10 +16,11 @@ async function loadConfigModels(req) {
|
|||
return {};
|
||||
}
|
||||
|
||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||
const { endpoints = {} } = customConfig ?? {};
|
||||
const modelsConfig = {};
|
||||
const azureEndpoint = endpoints[EModelEndpoint.azureOpenAI];
|
||||
const azureConfig = req.app.locals[EModelEndpoint.azureOpenAI];
|
||||
const azureConfig = appConfig[EModelEndpoint.azureOpenAI];
|
||||
const { modelNames } = azureConfig ?? {};
|
||||
|
||||
if (modelNames && azureEndpoint) {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ const generateArtifactsPrompt = require('~/app/clients/prompts/artifacts');
|
|||
const { getProviderConfig } = require('~/server/services/Endpoints');
|
||||
const { processFiles } = require('~/server/services/Files/process');
|
||||
const { getFiles, getToolFilesByIds } = require('~/models/File');
|
||||
const { getAppConfig } = require('~/server/services/Config');
|
||||
const { getConvoFiles } = require('~/models/Conversation');
|
||||
const { getModelMaxTokens } = require('~/utils');
|
||||
|
||||
|
|
@ -43,6 +44,7 @@ const initializeAgent = async ({
|
|||
allowedProviders,
|
||||
isInitialAgent = false,
|
||||
}) => {
|
||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||
if (
|
||||
isAgentsEndpoint(endpointOption?.endpoint) &&
|
||||
allowedProviders.size > 0 &&
|
||||
|
|
@ -84,10 +86,11 @@ const initializeAgent = async ({
|
|||
const { attachments, tool_resources } = await primeResources({
|
||||
req,
|
||||
getFiles,
|
||||
appConfig,
|
||||
agentId: agent.id,
|
||||
attachments: currentFiles,
|
||||
tool_resources: agent.tool_resources,
|
||||
requestFileSet: new Set(requestFiles?.map((file) => file.file_id)),
|
||||
agentId: agent.id,
|
||||
});
|
||||
|
||||
const provider = agent.provider;
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ const {
|
|||
createToolEndCallback,
|
||||
getDefaultHandlers,
|
||||
} = require('~/server/controllers/agents/callbacks');
|
||||
const { getCustomEndpointConfig, getAppConfig } = require('~/server/services/Config');
|
||||
const { initializeAgent } = require('~/server/services/Endpoints/agents/agent');
|
||||
const { getModelsConfig } = require('~/server/controllers/ModelController');
|
||||
const { getCustomEndpointConfig } = require('~/server/services/Config');
|
||||
const { loadAgentTools } = require('~/server/services/ToolService');
|
||||
const AgentClient = require('~/server/controllers/agents/client');
|
||||
const { getAgent } = require('~/models/Agent');
|
||||
|
|
@ -50,6 +50,7 @@ const initializeClient = async ({ req, res, endpointOption }) => {
|
|||
if (!endpointOption) {
|
||||
throw new Error('Endpoint option not provided');
|
||||
}
|
||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||
|
||||
// TODO: use endpointOption to determine options/modelOptions
|
||||
/** @type {Array<UsageMetadata>} */
|
||||
|
|
@ -90,7 +91,7 @@ const initializeClient = async ({ req, res, endpointOption }) => {
|
|||
|
||||
const agentConfigs = new Map();
|
||||
/** @type {Set<string>} */
|
||||
const allowedProviders = new Set(req?.app?.locals?.[EModelEndpoint.agents]?.allowedProviders);
|
||||
const allowedProviders = new Set(appConfig?.[EModelEndpoint.agents]?.allowedProviders);
|
||||
|
||||
const loadTools = createToolLoader();
|
||||
/** @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) {
|
||||
try {
|
||||
endpointConfig = await getCustomEndpointConfig(primaryConfig.endpoint);
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@ const { EModelEndpoint } = require('librechat-data-provider');
|
|||
const { getUserKey, checkUserKeyExpiry } = require('~/server/services/UserService');
|
||||
const { getLLMConfig } = require('~/server/services/Endpoints/anthropic/llm');
|
||||
const AnthropicClient = require('~/app/clients/AnthropicClient');
|
||||
const { getAppConfig } = require('~/server/services/Config');
|
||||
|
||||
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 expiresAt = req.body.key;
|
||||
const isUserProvided = ANTHROPIC_API_KEY === 'user_provided';
|
||||
|
|
@ -23,7 +25,7 @@ const initializeClient = async ({ req, res, endpointOption, overrideModel, optio
|
|||
let clientOptions = {};
|
||||
|
||||
/** @type {undefined | TBaseEndpoint} */
|
||||
const anthropicConfig = req.app.locals[EModelEndpoint.anthropic];
|
||||
const anthropicConfig = appConfig[EModelEndpoint.anthropic];
|
||||
|
||||
if (anthropicConfig) {
|
||||
clientOptions.streamRate = anthropicConfig.streamRate;
|
||||
|
|
@ -31,7 +33,7 @@ const initializeClient = async ({ req, res, endpointOption, overrideModel, optio
|
|||
}
|
||||
|
||||
/** @type {undefined | TBaseEndpoint} */
|
||||
const allConfig = req.app.locals.all;
|
||||
const allConfig = appConfig.all;
|
||||
if (allConfig) {
|
||||
clientOptions.streamRate = allConfig.streamRate;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ const {
|
|||
getUserKeyValues,
|
||||
getUserKeyExpiry,
|
||||
} = require('~/server/services/UserService');
|
||||
const { getAppConfig } = require('~/server/services/Config');
|
||||
const OAIClient = require('~/app/clients/OpenAIClient');
|
||||
|
||||
class Files {
|
||||
|
|
@ -48,6 +49,7 @@ class Files {
|
|||
}
|
||||
|
||||
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 } =
|
||||
process.env;
|
||||
|
||||
|
|
@ -81,7 +83,7 @@ const initializeClient = async ({ req, res, version, endpointOption, initAppClie
|
|||
};
|
||||
|
||||
/** @type {TAzureConfig | undefined} */
|
||||
const azureConfig = req.app.locals[EModelEndpoint.azureOpenAI];
|
||||
const azureConfig = appConfig[EModelEndpoint.azureOpenAI];
|
||||
|
||||
/** @type {AzureOptions | undefined} */
|
||||
let azureOptions;
|
||||
|
|
|
|||
|
|
@ -9,8 +9,10 @@ const {
|
|||
removeNullishValues,
|
||||
} = require('librechat-data-provider');
|
||||
const { getUserKey, checkUserKeyExpiry } = require('~/server/services/UserService');
|
||||
const { getAppConfig } = require('~/server/services/Config');
|
||||
|
||||
const getOptions = async ({ req, overrideModel, endpointOption }) => {
|
||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||
const {
|
||||
BEDROCK_AWS_SECRET_ACCESS_KEY,
|
||||
BEDROCK_AWS_ACCESS_KEY_ID,
|
||||
|
|
@ -50,14 +52,14 @@ const getOptions = async ({ req, overrideModel, endpointOption }) => {
|
|||
let streamRate = Constants.DEFAULT_STREAM_RATE;
|
||||
|
||||
/** @type {undefined | TBaseEndpoint} */
|
||||
const bedrockConfig = req.app.locals[EModelEndpoint.bedrock];
|
||||
const bedrockConfig = appConfig[EModelEndpoint.bedrock];
|
||||
|
||||
if (bedrockConfig && bedrockConfig.streamRate) {
|
||||
streamRate = bedrockConfig.streamRate;
|
||||
}
|
||||
|
||||
/** @type {undefined | TBaseEndpoint} */
|
||||
const allConfig = req.app.locals.all;
|
||||
const allConfig = appConfig.all;
|
||||
if (allConfig && allConfig.streamRate) {
|
||||
streamRate = allConfig.streamRate;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ const {
|
|||
const { Providers } = require('@librechat/agents');
|
||||
const { getOpenAIConfig, createHandleLLMNewToken, resolveHeaders } = require('@librechat/api');
|
||||
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 OpenAIClient = require('~/app/clients/OpenAIClient');
|
||||
const { isUserProvided } = require('~/server/utils');
|
||||
|
|
@ -17,6 +17,7 @@ const getLogStores = require('~/cache/getLogStores');
|
|||
const { PROXY } = process.env;
|
||||
|
||||
const initializeClient = async ({ req, res, endpointOption, optionsOnly, overrideEndpoint }) => {
|
||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||
const { key: expiresAt } = req.body;
|
||||
const endpoint = overrideEndpoint ?? req.body.endpoint;
|
||||
|
||||
|
|
@ -118,7 +119,7 @@ const initializeClient = async ({ req, res, endpointOption, optionsOnly, overrid
|
|||
};
|
||||
|
||||
/** @type {undefined | TBaseEndpoint} */
|
||||
const allConfig = req.app.locals.all;
|
||||
const allConfig = appConfig.all;
|
||||
if (allConfig) {
|
||||
customOptions.streamRate = allConfig.streamRate;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ const path = require('path');
|
|||
const { EModelEndpoint, AuthKeys } = require('librechat-data-provider');
|
||||
const { getGoogleConfig, isEnabled, loadServiceKey } = require('@librechat/api');
|
||||
const { getUserKey, checkUserKeyExpiry } = require('~/server/services/UserService');
|
||||
const { getAppConfig } = require('~/server/services/Config');
|
||||
const { GoogleClient } = require('~/app');
|
||||
|
||||
const initializeClient = async ({ req, res, endpointOption, overrideModel, optionsOnly }) => {
|
||||
|
|
@ -46,10 +47,11 @@ const initializeClient = async ({ req, res, endpointOption, overrideModel, optio
|
|||
|
||||
let clientOptions = {};
|
||||
|
||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||
/** @type {undefined | TBaseEndpoint} */
|
||||
const allConfig = req.app.locals.all;
|
||||
const allConfig = appConfig.all;
|
||||
/** @type {undefined | TBaseEndpoint} */
|
||||
const googleConfig = req.app.locals[EModelEndpoint.google];
|
||||
const googleConfig = appConfig[EModelEndpoint.google];
|
||||
|
||||
if (googleConfig) {
|
||||
clientOptions.streamRate = googleConfig.streamRate;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
const { isEnabled } = require('@librechat/api');
|
||||
const { EModelEndpoint, CacheKeys, Constants, googleSettings } = require('librechat-data-provider');
|
||||
const { getAppConfig } = require('~/server/services/Config');
|
||||
const getLogStores = require('~/cache/getLogStores');
|
||||
const initializeClient = require('./initialize');
|
||||
const { isEnabled } = require('~/server/utils');
|
||||
const { saveConvo } = require('~/models');
|
||||
|
||||
const addTitle = async (req, { text, response, client }) => {
|
||||
|
|
@ -14,7 +15,8 @@ const addTitle = async (req, { text, response, client }) => {
|
|||
return;
|
||||
}
|
||||
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 =
|
||||
providerConfig?.titleModel ??
|
||||
GOOGLE_TITLE_MODEL ??
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ const {
|
|||
createHandleLLMNewToken,
|
||||
} = require('@librechat/api');
|
||||
const { getUserKeyValues, checkUserKeyExpiry } = require('~/server/services/UserService');
|
||||
const { getAppConfig } = require('~/server/services/Config');
|
||||
const OpenAIClient = require('~/app/clients/OpenAIClient');
|
||||
|
||||
const initializeClient = async ({
|
||||
|
|
@ -18,6 +19,7 @@ const initializeClient = async ({
|
|||
overrideEndpoint,
|
||||
overrideModel,
|
||||
}) => {
|
||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||
const {
|
||||
PROXY,
|
||||
OPENAI_API_KEY,
|
||||
|
|
@ -64,7 +66,7 @@ const initializeClient = async ({
|
|||
|
||||
const isAzureOpenAI = endpoint === EModelEndpoint.azureOpenAI;
|
||||
/** @type {false | TAzureConfig} */
|
||||
const azureConfig = isAzureOpenAI && req.app.locals[EModelEndpoint.azureOpenAI];
|
||||
const azureConfig = isAzureOpenAI && appConfig[EModelEndpoint.azureOpenAI];
|
||||
let serverless = false;
|
||||
if (isAzureOpenAI && azureConfig) {
|
||||
const { modelGroupMap, groupMap } = azureConfig;
|
||||
|
|
@ -113,7 +115,7 @@ const initializeClient = async ({
|
|||
}
|
||||
|
||||
/** @type {undefined | TBaseEndpoint} */
|
||||
const openAIConfig = req.app.locals[EModelEndpoint.openAI];
|
||||
const openAIConfig = appConfig[EModelEndpoint.openAI];
|
||||
|
||||
if (!isAzureOpenAI && openAIConfig) {
|
||||
clientOptions.streamRate = openAIConfig.streamRate;
|
||||
|
|
@ -121,7 +123,7 @@ const initializeClient = async ({
|
|||
}
|
||||
|
||||
/** @type {undefined | TBaseEndpoint} */
|
||||
const allConfig = req.app.locals.all;
|
||||
const allConfig = appConfig.all;
|
||||
if (allConfig) {
|
||||
clientOptions.streamRate = allConfig.streamRate;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ const path = require('path');
|
|||
const sharp = require('sharp');
|
||||
const { logger } = require('@librechat/data-schemas');
|
||||
const { resizeImageBuffer } = require('../images/resize');
|
||||
const { getAppConfig } = require('~/server/services/Config');
|
||||
const { updateUser, updateFile } = require('~/models');
|
||||
const { saveBufferToAzure } = require('./crud');
|
||||
|
||||
|
|
@ -30,6 +31,7 @@ async function uploadImageToAzure({
|
|||
containerName,
|
||||
}) {
|
||||
try {
|
||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||
const inputFilePath = file.path;
|
||||
const inputBuffer = await fs.promises.readFile(inputFilePath);
|
||||
const {
|
||||
|
|
@ -41,12 +43,12 @@ async function uploadImageToAzure({
|
|||
const userId = req.user.id;
|
||||
let webPBuffer;
|
||||
let fileName = `${file_id}__${path.basename(inputFilePath)}`;
|
||||
const targetExtension = `.${req.app.locals.imageOutputType}`;
|
||||
const targetExtension = `.${appConfig.imageOutputType}`;
|
||||
|
||||
if (extension.toLowerCase() === targetExtension) {
|
||||
webPBuffer = resizedBuffer;
|
||||
} 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) + '$');
|
||||
fileName = fileName.replace(extRegExp, targetExtension);
|
||||
if (!path.extname(fileName)) {
|
||||
|
|
|
|||
|
|
@ -43,8 +43,7 @@ async function getCodeOutputDownloadStream(fileIdentifier, apiKey) {
|
|||
/**
|
||||
* Uploads a file to the Code Environment server.
|
||||
* @param {Object} params - The params object.
|
||||
* @param {ServerRequest} params.req - The request object from Express. It should have a `user` property with an `id`
|
||||
* representing the user, and an `app.locals.paths` object with an `uploads` path.
|
||||
* @param {ServerRequest} params.req - The request object from Express. It should have a `user` property with an `id` representing the user
|
||||
* @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.apiKey - The API key for authentication.
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ const { filterFilesByAgentAccess } = require('~/server/services/Files/permission
|
|||
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
||||
const { convertImage } = require('~/server/services/Files/images/convert');
|
||||
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.
|
||||
|
|
@ -38,6 +39,7 @@ const processCodeOutput = async ({
|
|||
messageId,
|
||||
session_id,
|
||||
}) => {
|
||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||
const currentDate = new Date();
|
||||
const baseURL = getCodeBaseURL();
|
||||
const fileExt = path.extname(name);
|
||||
|
|
@ -77,10 +79,10 @@ const processCodeOutput = async ({
|
|||
filename: name,
|
||||
conversationId,
|
||||
user: req.user.id,
|
||||
type: `image/${req.app.locals.imageOutputType}`,
|
||||
type: `image/${appConfig.imageOutputType}`,
|
||||
createdAt: formattedDate,
|
||||
updatedAt: formattedDate,
|
||||
source: req.app.locals.fileStrategy,
|
||||
source: appConfig.fileStrategy,
|
||||
context: FileContext.execute_code,
|
||||
};
|
||||
createFile(file, true);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ const fs = require('fs');
|
|||
const path = require('path');
|
||||
const sharp = require('sharp');
|
||||
const { logger } = require('@librechat/data-schemas');
|
||||
const { getAppConfig } = require('~/server/services/Config');
|
||||
const { resizeImageBuffer } = require('../images/resize');
|
||||
const { updateUser, updateFile } = require('~/models');
|
||||
const { saveBufferToFirebase } = require('./crud');
|
||||
|
|
@ -11,8 +12,7 @@ const { saveBufferToFirebase } = require('./crud');
|
|||
* resolution.
|
||||
*
|
||||
* @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`
|
||||
* representing the user, and an `app.locals.paths` object with an `imageOutput` path.
|
||||
* @param {Express.Request} params.req - The request object from Express. It should have a `user` property with an `id` representing the user
|
||||
* @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.
|
||||
* @param {EModelEndpoint} params.endpoint - The params object.
|
||||
|
|
@ -26,6 +26,7 @@ const { saveBufferToFirebase } = require('./crud');
|
|||
* - height: The height of the converted image.
|
||||
*/
|
||||
async function uploadImageToFirebase({ req, file, file_id, endpoint, resolution = 'high' }) {
|
||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||
const inputFilePath = file.path;
|
||||
const inputBuffer = await fs.promises.readFile(inputFilePath);
|
||||
const {
|
||||
|
|
@ -38,11 +39,11 @@ async function uploadImageToFirebase({ req, file, file_id, endpoint, resolution
|
|||
|
||||
let webPBuffer;
|
||||
let fileName = `${file_id}__${path.basename(inputFilePath)}`;
|
||||
const targetExtension = `.${req.app.locals.imageOutputType}`;
|
||||
const targetExtension = `.${appConfig.imageOutputType}`;
|
||||
if (extension.toLowerCase() === targetExtension) {
|
||||
webPBuffer = resizedBuffer;
|
||||
} 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
|
||||
const extRegExp = new RegExp(path.extname(fileName) + '$');
|
||||
fileName = fileName.replace(extRegExp, targetExtension);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ const axios = require('axios');
|
|||
const { logger } = require('@librechat/data-schemas');
|
||||
const { EModelEndpoint } = require('librechat-data-provider');
|
||||
const { generateShortLivedToken } = require('~/server/services/AuthService');
|
||||
const { getAppConfig } = require('~/server/services/Config');
|
||||
const { getBufferMetadata } = require('~/server/utils');
|
||||
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.
|
||||
*/
|
||||
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 ?? '');
|
||||
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
|
||||
* 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
|
||||
* a `publicPath` property.
|
||||
* @param {Express.Request} req - The request object from Express.
|
||||
* @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.
|
||||
*
|
||||
|
|
@ -201,7 +202,8 @@ const unlinkFile = async (filepath) => {
|
|||
* file path is invalid or if there is an error in deletion.
|
||||
*/
|
||||
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) */
|
||||
const cleanFilepath = file.filepath.split('?')[0];
|
||||
|
|
@ -256,8 +258,7 @@ const deleteLocalFile = async (req, file) => {
|
|||
* Uploads a file to the specified upload directory.
|
||||
*
|
||||
* @param {Object} params - The params object.
|
||||
* @param {ServerRequest} params.req - The request object from Express. It should have a `user` property with an `id`
|
||||
* representing the user, and an `app.locals.paths` object with an `uploads` path.
|
||||
* @param {ServerRequest} params.req - The request object from Express. It should have a `user` property with an `id` representing the user
|
||||
* @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.
|
||||
* @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.
|
||||
*/
|
||||
async function uploadLocalFile({ req, file, file_id }) {
|
||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||
const inputFilePath = file.path;
|
||||
const inputBuffer = await fs.promises.readFile(inputFilePath);
|
||||
const bytes = Buffer.byteLength(inputBuffer);
|
||||
|
||||
const { uploads } = req.app.locals.paths;
|
||||
const { uploads } = appConfig.paths;
|
||||
const userPath = path.join(uploads, req.user.id);
|
||||
|
||||
if (!fs.existsSync(userPath)) {
|
||||
|
|
@ -295,8 +297,9 @@ async function uploadLocalFile({ req, file, file_id }) {
|
|||
* @param {string} filepath - The filepath.
|
||||
* @returns {ReadableStream} A readable stream of the file.
|
||||
*/
|
||||
function getLocalFileStream(req, filepath) {
|
||||
async function getLocalFileStream(req, filepath) {
|
||||
try {
|
||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||
if (filepath.includes('/uploads/')) {
|
||||
const basePath = filepath.split('/uploads/')[1];
|
||||
|
||||
|
|
@ -305,8 +308,8 @@ function getLocalFileStream(req, filepath) {
|
|||
throw new Error(`Invalid file path: ${filepath}`);
|
||||
}
|
||||
|
||||
const fullPath = path.join(req.app.locals.paths.uploads, basePath);
|
||||
const uploadsDir = req.app.locals.paths.uploads;
|
||||
const fullPath = path.join(appConfig.paths.uploads, basePath);
|
||||
const uploadsDir = appConfig.paths.uploads;
|
||||
|
||||
const rel = path.relative(uploadsDir, fullPath);
|
||||
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}`);
|
||||
}
|
||||
|
||||
const fullPath = path.join(req.app.locals.paths.imageOutput, basePath);
|
||||
const publicDir = req.app.locals.paths.imageOutput;
|
||||
const fullPath = path.join(appConfig.paths.imageOutput, basePath);
|
||||
const publicDir = appConfig.paths.imageOutput;
|
||||
|
||||
const rel = path.relative(publicDir, fullPath);
|
||||
if (rel.startsWith('..') || path.isAbsolute(rel) || rel.includes(`..${path.sep}`)) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const sharp = require('sharp');
|
||||
const { getAppConfig } = require('~/server/services/Config');
|
||||
const { resizeImageBuffer } = require('../images/resize');
|
||||
const { updateUser, updateFile } = require('~/models');
|
||||
|
||||
|
|
@ -13,8 +14,7 @@ const { updateUser, updateFile } = require('~/models');
|
|||
*
|
||||
* The original image is deleted after conversion.
|
||||
* @param {Object} params - The params object.
|
||||
* @param {Object} params.req - The request object from Express. It should have a `user` property with an `id`
|
||||
* representing the user, and an `app.locals.paths` object with an `imageOutput` path.
|
||||
* @param {Object} params.req - The request object from Express. It should have a `user` property with an `id` representing the user
|
||||
* @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.
|
||||
* @param {string} params.file_id - The file ID.
|
||||
|
|
@ -29,6 +29,7 @@ const { updateUser, updateFile } = require('~/models');
|
|||
* - height: The height of the converted image.
|
||||
*/
|
||||
async function uploadLocalImage({ req, file, file_id, endpoint, resolution = 'high' }) {
|
||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||
const inputFilePath = file.path;
|
||||
const inputBuffer = await fs.promises.readFile(inputFilePath);
|
||||
const {
|
||||
|
|
@ -38,7 +39,7 @@ async function uploadLocalImage({ req, file, file_id, endpoint, resolution = 'hi
|
|||
} = await resizeImageBuffer(inputBuffer, resolution, endpoint);
|
||||
const extension = path.extname(inputFilePath);
|
||||
|
||||
const { imageOutput } = req.app.locals.paths;
|
||||
const { imageOutput } = appConfig.paths;
|
||||
const userPath = path.join(imageOutput, req.user.id);
|
||||
|
||||
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 newPath = path.join(userPath, fileName);
|
||||
const targetExtension = `.${req.app.locals.imageOutputType}`;
|
||||
const targetExtension = `.${appConfig.imageOutputType}`;
|
||||
|
||||
if (extension.toLowerCase() === targetExtension) {
|
||||
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 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);
|
||||
const bytes = Buffer.byteLength(data);
|
||||
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.
|
||||
*/
|
||||
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);
|
||||
|
||||
if (!fs.existsSync(userPath)) {
|
||||
|
|
|
|||
|
|
@ -7,8 +7,7 @@ const { logger } = require('~/config');
|
|||
* Uploads a file that can be used across various OpenAI services.
|
||||
*
|
||||
* @param {Object} params - The params object.
|
||||
* @param {ServerRequest} params.req - The request object from Express. It should have a `user` property with an `id`
|
||||
* representing the user, and an `app.locals.paths` object with an `imageOutput` path.
|
||||
* @param {ServerRequest} params.req - The request object from Express. It should have a `user` property with an `id` representing the user
|
||||
* @param {Express.Multer.File} params.file - The file uploaded to the server via multer.
|
||||
* @param {OpenAIClient} params.openai - The initialized OpenAI client.
|
||||
* @returns {Promise<OpenAIFile>}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ const fs = require('fs');
|
|||
const path = require('path');
|
||||
const sharp = require('sharp');
|
||||
const { logger } = require('@librechat/data-schemas');
|
||||
const { getAppConfig } = require('~/server/services/Config');
|
||||
const { resizeImageBuffer } = require('../images/resize');
|
||||
const { updateUser, updateFile } = require('~/models');
|
||||
const { saveBufferToS3 } = require('./crud');
|
||||
|
|
@ -12,7 +13,7 @@ const defaultBasePath = 'images';
|
|||
* Resizes, converts, and uploads an image file to S3.
|
||||
*
|
||||
* @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 {string} params.file_id - Unique file identifier.
|
||||
* @param {any} params.endpoint - Endpoint identifier used in image processing.
|
||||
|
|
@ -29,6 +30,7 @@ async function uploadImageToS3({
|
|||
basePath = defaultBasePath,
|
||||
}) {
|
||||
try {
|
||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||
const inputFilePath = file.path;
|
||||
const inputBuffer = await fs.promises.readFile(inputFilePath);
|
||||
const {
|
||||
|
|
@ -41,14 +43,12 @@ async function uploadImageToS3({
|
|||
|
||||
let processedBuffer;
|
||||
let fileName = `${file_id}__${path.basename(inputFilePath)}`;
|
||||
const targetExtension = `.${req.app.locals.imageOutputType}`;
|
||||
const targetExtension = `.${appConfig.imageOutputType}`;
|
||||
|
||||
if (extension.toLowerCase() === targetExtension) {
|
||||
processedBuffer = resizedBuffer;
|
||||
} else {
|
||||
processedBuffer = await sharp(resizedBuffer)
|
||||
.toFormat(req.app.locals.imageOutputType)
|
||||
.toBuffer();
|
||||
processedBuffer = await sharp(resizedBuffer).toFormat(appConfig.imageOutputType).toBuffer();
|
||||
fileName = fileName.replace(new RegExp(path.extname(fileName) + '$'), targetExtension);
|
||||
if (!path.extname(fileName)) {
|
||||
fileName += targetExtension;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
* 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
|
||||
* a `publicPath` property.
|
||||
* @param {ServerRequest} req - The request object from Express.
|
||||
* @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.
|
||||
*
|
||||
|
|
@ -54,8 +53,7 @@ const deleteVectors = async (req, file) => {
|
|||
* Uploads a file to the configured Vector database
|
||||
*
|
||||
* @param {Object} params - The params object.
|
||||
* @param {Object} params.req - The request object from Express. It should have a `user` property with an `id`
|
||||
* representing the user, and an `app.locals.paths` object with an `uploads` path.
|
||||
* @param {Object} params.req - The request object from Express. It should have a `user` property with an `id` representing the user
|
||||
* @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.
|
||||
* @param {string} params.file_id - The file ID.
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const sharp = require('sharp');
|
||||
const { resizeImageBuffer } = require('./resize');
|
||||
const { getAppConfig } = require('~/server/services/Config');
|
||||
const { getStrategyFunctions } = require('../strategies');
|
||||
const { resizeImageBuffer } = require('./resize');
|
||||
const { logger } = require('~/config');
|
||||
|
||||
/**
|
||||
|
|
@ -17,6 +18,7 @@ const { logger } = require('~/config');
|
|||
*/
|
||||
async function convertImage(req, file, resolution = 'high', basename = '') {
|
||||
try {
|
||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||
let inputBuffer;
|
||||
let outputBuffer;
|
||||
let extension = path.extname(file.path ?? basename).toLowerCase();
|
||||
|
|
@ -39,11 +41,11 @@ async function convertImage(req, file, resolution = 'high', basename = '') {
|
|||
} = await resizeImageBuffer(inputBuffer, resolution);
|
||||
|
||||
// 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) {
|
||||
outputBuffer = resizedBuffer;
|
||||
} else {
|
||||
outputBuffer = await sharp(resizedBuffer).toFormat(req.app.locals.imageOutputType).toBuffer();
|
||||
outputBuffer = await sharp(resizedBuffer).toFormat(appConfig.imageOutputType).toBuffer();
|
||||
extension = targetExtension;
|
||||
}
|
||||
|
||||
|
|
@ -51,7 +53,7 @@ async function convertImage(req, file, resolution = 'high', basename = '') {
|
|||
const newFileName =
|
||||
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({
|
||||
userId: req.user.id,
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ const {
|
|||
removeNullishValues,
|
||||
isAssistantsEndpoint,
|
||||
} = require('librechat-data-provider');
|
||||
const { sanitizeFilename } = require('@librechat/api');
|
||||
const { EnvVar } = require('@librechat/agents');
|
||||
const { sanitizeFilename } = require('@librechat/api');
|
||||
const {
|
||||
convertImage,
|
||||
resizeAndConvert,
|
||||
|
|
@ -27,11 +27,11 @@ const { addResourceFileId, deleteResourceFileId } = require('~/server/controller
|
|||
const { addAgentResourceFile, removeAgentResourceFiles } = require('~/models/Agent');
|
||||
const { getOpenAIClient } = require('~/server/controllers/assistants/helpers');
|
||||
const { createFile, updateFileUsage, deleteFiles } = require('~/models/File');
|
||||
const { checkCapability, getAppConfig } = require('~/server/services/Config');
|
||||
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 { getStrategyFunctions } = require('./strategies');
|
||||
const { getFileStrategy } = require('~/server/utils/getFileStrategy');
|
||||
const { determineFileType } = require('~/server/utils');
|
||||
const { logger } = require('~/config');
|
||||
|
||||
|
|
@ -157,6 +157,7 @@ function enqueueDeleteOperation({ req, file, deleteFile, promises, resolvedFileI
|
|||
* @returns {Promise<void>}
|
||||
*/
|
||||
const processDeleteRequest = async ({ req, files }) => {
|
||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||
const resolvedFileIds = [];
|
||||
const deletionMethods = {};
|
||||
const promises = [];
|
||||
|
|
@ -164,7 +165,7 @@ const processDeleteRequest = async ({ req, files }) => {
|
|||
/** @type {Record<string, OpenAI | undefined>} */
|
||||
const client = { [FileSources.openai]: undefined, [FileSources.azure]: undefined };
|
||||
const initializeClients = async () => {
|
||||
if (req.app.locals[EModelEndpoint.assistants]) {
|
||||
if (appConfig[EModelEndpoint.assistants]) {
|
||||
const openAIClient = await getOpenAIClient({
|
||||
req,
|
||||
overrideEndpoint: EModelEndpoint.assistants,
|
||||
|
|
@ -172,7 +173,7 @@ const processDeleteRequest = async ({ req, files }) => {
|
|||
client[FileSources.openai] = openAIClient.openai;
|
||||
}
|
||||
|
||||
if (!req.app.locals[EModelEndpoint.azureOpenAI]?.assistants) {
|
||||
if (!appConfig[EModelEndpoint.azureOpenAI]?.assistants) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -320,7 +321,8 @@ const processFileURL = async ({ fileStrategy, userId, URL, fileName, basePath, c
|
|||
*/
|
||||
const processImageFile = async ({ req, res, metadata, returnFile = false }) => {
|
||||
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 { file_id, temp_file_id, endpoint } = metadata;
|
||||
|
||||
|
|
@ -341,7 +343,7 @@ const processImageFile = async ({ req, res, metadata, returnFile = false }) => {
|
|||
filename: file.originalname,
|
||||
context: FileContext.message_attachment,
|
||||
source,
|
||||
type: `image/${req.app.locals.imageOutputType}`,
|
||||
type: `image/${appConfig.imageOutputType}`,
|
||||
width,
|
||||
height,
|
||||
},
|
||||
|
|
@ -366,18 +368,19 @@ const processImageFile = async ({ req, res, metadata, returnFile = false }) => {
|
|||
* @returns {Promise<{ filepath: string, filename: string, source: string, type: string}>}
|
||||
*/
|
||||
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);
|
||||
let { buffer, width, height, bytes, filename, file_id, type } = metadata;
|
||||
if (resize) {
|
||||
file_id = v4();
|
||||
type = `image/${req.app.locals.imageOutputType}`;
|
||||
type = `image/${appConfig.imageOutputType}`;
|
||||
({ buffer, width, height, bytes } = await resizeAndConvert({
|
||||
inputBuffer: buffer,
|
||||
desiredFormat: req.app.locals.imageOutputType,
|
||||
desiredFormat: appConfig.imageOutputType,
|
||||
}));
|
||||
filename = `${path.basename(req.file.originalname, path.extname(req.file.originalname))}.${
|
||||
req.app.locals.imageOutputType
|
||||
appConfig.imageOutputType
|
||||
}`;
|
||||
}
|
||||
const fileName = `${file_id}-${filename}`;
|
||||
|
|
@ -411,11 +414,12 @@ const uploadImageBuffer = async ({ req, context, metadata = {}, resize = true })
|
|||
* @returns {Promise<void>}
|
||||
*/
|
||||
const processFileUpload = async ({ req, res, metadata }) => {
|
||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||
const isAssistantUpload = isAssistantsEndpoint(metadata.endpoint);
|
||||
const assistantSource =
|
||||
metadata.endpoint === EModelEndpoint.azureAssistants ? FileSources.azure : FileSources.openai;
|
||||
// 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 { file_id, temp_file_id = null } = metadata;
|
||||
|
||||
|
|
@ -501,6 +505,7 @@ const processFileUpload = async ({ req, res, metadata }) => {
|
|||
*/
|
||||
const processAgentFileUpload = async ({ req, res, metadata }) => {
|
||||
const { file } = req;
|
||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||
const { agent_id, tool_resource, file_id, temp_file_id = null } = metadata;
|
||||
if (agent_id && !tool_resource) {
|
||||
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(
|
||||
req.app.locals?.ocr?.strategy ?? FileSources.mistral_ocr,
|
||||
appConfig?.ocr?.strategy ?? FileSources.mistral_ocr,
|
||||
);
|
||||
const { file_id, temp_file_id = null } = metadata;
|
||||
|
||||
|
|
@ -564,7 +569,7 @@ const processAgentFileUpload = async ({ req, res, metadata }) => {
|
|||
images: _i,
|
||||
filename,
|
||||
filepath: ocrFileURL,
|
||||
} = await uploadOCR({ req, file, loadAuthValues });
|
||||
} = await uploadOCR({ req, appConfig, file, loadAuthValues });
|
||||
|
||||
const fileInfo = removeNullishValues({
|
||||
text,
|
||||
|
|
@ -597,7 +602,7 @@ const processAgentFileUpload = async ({ req, res, metadata }) => {
|
|||
// Dual storage pattern for RAG files: Storage + Vector DB
|
||||
let storageResult, embeddingResult;
|
||||
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) {
|
||||
// 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 currentDate = new Date();
|
||||
const formattedDate = currentDate.toISOString();
|
||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||
const _file = await convertImage(req, buffer, undefined, `${file_id}${fileExt}`);
|
||||
|
||||
// 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),
|
||||
createdAt: formattedDate,
|
||||
updatedAt: formattedDate,
|
||||
source: getFileStrategy(req.app.locals, { isImage: true }),
|
||||
source: getFileStrategy(appConfig, { isImage: true }),
|
||||
context: FileContext.assistants_output,
|
||||
file_id,
|
||||
filename,
|
||||
|
|
@ -889,7 +895,7 @@ async function saveBase64Image(
|
|||
url,
|
||||
{ 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();
|
||||
let filename = `${file_id}-${_filename}`;
|
||||
const { buffer: inputBuffer, type } = base64ToBuffer(url);
|
||||
|
|
@ -903,7 +909,8 @@ async function saveBase64Image(
|
|||
}
|
||||
|
||||
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 filepath = await saveBuffer({
|
||||
userId: req.user.id,
|
||||
|
|
@ -964,7 +971,8 @@ function filterFile({ req, image, isAvatar }) {
|
|||
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 } =
|
||||
fileConfig.endpoints[endpoint] ?? fileConfig.endpoints.default;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
const axios = require('axios');
|
||||
const { logAxiosError } = require('@librechat/api');
|
||||
const { EModelEndpoint } = require('librechat-data-provider');
|
||||
const { getAppConfig } = require('~/server/services/Config');
|
||||
|
||||
/**
|
||||
* @typedef {Object} RetrieveOptions
|
||||
|
|
@ -18,6 +19,7 @@ const { EModelEndpoint } = require('librechat-data-provider');
|
|||
* @returns {Promise<Object>} The data retrieved from the API.
|
||||
*/
|
||||
async function retrieveRun({ thread_id, run_id, timeout, openai }) {
|
||||
const appConfig = await getAppConfig({ role: openai.req.user?.role });
|
||||
const { apiKey, baseURL, httpAgent, organization } = openai;
|
||||
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} */
|
||||
const azureConfig = openai.req.app.locals[EModelEndpoint.azureOpenAI];
|
||||
const azureConfig = appConfig[EModelEndpoint.azureOpenAI];
|
||||
|
||||
if (azureConfig && azureConfig.assistants) {
|
||||
delete headers.Authorization;
|
||||
|
|
|
|||
|
|
@ -32,8 +32,8 @@ const {
|
|||
manifestToolMap,
|
||||
toolkits,
|
||||
} = require('~/app/clients/tools');
|
||||
const { getEndpointsConfig, getCachedTools, getAppConfig } = require('~/server/services/Config');
|
||||
const { processFileURL, uploadImageBuffer } = require('~/server/services/Files/process');
|
||||
const { getEndpointsConfig, getCachedTools } = require('~/server/services/Config');
|
||||
const { createOnSearchResults } = require('~/server/services/Tools/search');
|
||||
const { isActionDomainAllowed } = require('~/server/services/domains');
|
||||
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}`,
|
||||
requiredActions,
|
||||
);
|
||||
const appConfig = await getAppConfig({ role: client.req.user?.role });
|
||||
const toolDefinitions = await getCachedTools({ userId: client.req.user.id, includeGlobal: true });
|
||||
const seenToolkits = new Set();
|
||||
const tools = requiredActions
|
||||
|
|
@ -233,7 +234,7 @@ async function processRequiredActions(client, requiredActions) {
|
|||
req: client.req,
|
||||
uploadImageBuffer,
|
||||
openAIApiKey: client.apiKey,
|
||||
fileStrategy: client.req.app.locals.fileStrategy,
|
||||
fileStrategy: appConfig.fileStrategy,
|
||||
returnMetadata: true,
|
||||
},
|
||||
});
|
||||
|
|
@ -480,12 +481,13 @@ async function loadAgentTools({ req, res, agent, tool_resources, openAIApiKey })
|
|||
return {};
|
||||
}
|
||||
|
||||
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||
const endpointsConfig = await getEndpointsConfig(req);
|
||||
let enabledCapabilities = new Set(endpointsConfig?.[EModelEndpoint.agents]?.capabilities ?? []);
|
||||
/** Edge case: use defined/fallback capabilities when the "agents" endpoint is not enabled */
|
||||
if (enabledCapabilities.size === 0 && agent.id === Constants.EPHEMERAL_AGENT_ID) {
|
||||
enabledCapabilities = new Set(
|
||||
req.app?.locals?.[EModelEndpoint.agents]?.capabilities ?? defaultAgentCapabilities,
|
||||
appConfig?.[EModelEndpoint.agents]?.capabilities ?? defaultAgentCapabilities,
|
||||
);
|
||||
}
|
||||
const checkCapability = (capability) => {
|
||||
|
|
@ -536,7 +538,7 @@ async function loadAgentTools({ req, res, agent, tool_resources, openAIApiKey })
|
|||
processFileURL,
|
||||
uploadImageBuffer,
|
||||
returnMetadata: true,
|
||||
fileStrategy: req.app.locals.fileStrategy,
|
||||
fileStrategy: appConfig.fileStrategy,
|
||||
[Tools.web_search]: webSearchCallbacks,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
const { logger } = require('@librechat/data-schemas');
|
||||
const { getCachedTools, setCachedTools } = require('./Config');
|
||||
const { getCachedTools, setCachedTools, getAppConfig } = require('./Config');
|
||||
const { CacheKeys } = require('librechat-data-provider');
|
||||
const { createMCPManager } = require('~/config');
|
||||
const { getLogStores } = require('~/cache');
|
||||
|
||||
/**
|
||||
* Initialize MCP servers
|
||||
* @param {import('express').Application} app - Express app instance
|
||||
*/
|
||||
async function initializeMCPs(app) {
|
||||
const mcpServers = app.locals.mcpConfig;
|
||||
async function initializeMCPs() {
|
||||
const appConfig = await getAppConfig();
|
||||
const mcpServers = appConfig.mcpConfig;
|
||||
if (!mcpServers) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -17,7 +17,6 @@ async function initializeMCPs(app) {
|
|||
const mcpManager = await createMCPManager(mcpServers);
|
||||
|
||||
try {
|
||||
delete app.locals.mcpConfig;
|
||||
const cachedTools = await getCachedTools();
|
||||
|
||||
if (!cachedTools) {
|
||||
|
|
|
|||
|
|
@ -24,11 +24,11 @@ const { FileContext } = require('librechat-data-provider');
|
|||
* { isAvatar: true }
|
||||
* ) // Returns 'local'
|
||||
*/
|
||||
function getFileStrategy(appLocals, { isAvatar = false, isImage = false, context = null } = {}) {
|
||||
// Handle both old (config object) and new (app.locals object) calling patterns
|
||||
const isAppLocals = appLocals.fileStrategy !== undefined;
|
||||
const config = isAppLocals ? appLocals.config : appLocals;
|
||||
const fileStrategy = isAppLocals ? appLocals.fileStrategy : appLocals.fileStrategy;
|
||||
function getFileStrategy(appConfig, { isAvatar = false, isImage = false, context = null } = {}) {
|
||||
// Handle both old (config object) and new (`appConfig` object) calling patterns
|
||||
const isAppConfig = appConfig.fileStrategy !== undefined;
|
||||
const config = isAppConfig ? appConfig.config : appConfig;
|
||||
const fileStrategy = isAppConfig ? appConfig.fileStrategy : appConfig.fileStrategy;
|
||||
|
||||
// Fallback to legacy single strategy if no granular config
|
||||
if (!config?.fileStrategies) {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { primeResources } from './resources';
|
||||
import { logger } from '@librechat/data-schemas';
|
||||
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 { TFile } from 'librechat-data-provider';
|
||||
import type { TGetFiles } from './resources';
|
||||
import type { AppConfig } from '~/types';
|
||||
|
||||
// Mock logger
|
||||
jest.mock('@librechat/data-schemas', () => ({
|
||||
|
|
@ -14,6 +15,7 @@ jest.mock('@librechat/data-schemas', () => ({
|
|||
|
||||
describe('primeResources', () => {
|
||||
let mockReq: ServerRequest;
|
||||
let mockAppConfig: AppConfig;
|
||||
let mockGetFiles: jest.MockedFunction<TGetFiles>;
|
||||
let requestFileSet: Set<string>;
|
||||
|
||||
|
|
@ -32,6 +34,13 @@ describe('primeResources', () => {
|
|||
},
|
||||
} as unknown as ServerRequest;
|
||||
|
||||
// Setup mock appConfig
|
||||
mockAppConfig = {
|
||||
[EModelEndpoint.agents]: {
|
||||
capabilities: [AgentCapabilities.ocr],
|
||||
} as TAgentsEndpoint,
|
||||
} as AppConfig;
|
||||
|
||||
// Setup mock getFiles function
|
||||
mockGetFiles = jest.fn();
|
||||
|
||||
|
|
@ -65,6 +74,7 @@ describe('primeResources', () => {
|
|||
|
||||
const result = await primeResources({
|
||||
req: mockReq,
|
||||
appConfig: mockAppConfig,
|
||||
getFiles: mockGetFiles,
|
||||
requestFileSet,
|
||||
attachments: undefined,
|
||||
|
|
@ -85,6 +95,7 @@ describe('primeResources', () => {
|
|||
describe('when OCR is disabled', () => {
|
||||
it('should not fetch OCR files even if tool_resources has OCR file_ids', async () => {
|
||||
(mockReq.app as ServerRequest['app']).locals[EModelEndpoint.agents].capabilities = [];
|
||||
(mockAppConfig[EModelEndpoint.agents] as TAgentsEndpoint).capabilities = [];
|
||||
|
||||
const tool_resources = {
|
||||
[EToolResources.ocr]: {
|
||||
|
|
@ -94,6 +105,7 @@ describe('primeResources', () => {
|
|||
|
||||
const result = await primeResources({
|
||||
req: mockReq,
|
||||
appConfig: mockAppConfig,
|
||||
getFiles: mockGetFiles,
|
||||
requestFileSet,
|
||||
attachments: undefined,
|
||||
|
|
@ -129,6 +141,7 @@ describe('primeResources', () => {
|
|||
|
||||
const result = await primeResources({
|
||||
req: mockReq,
|
||||
appConfig: mockAppConfig,
|
||||
getFiles: mockGetFiles,
|
||||
requestFileSet,
|
||||
attachments,
|
||||
|
|
@ -158,6 +171,7 @@ describe('primeResources', () => {
|
|||
|
||||
const result = await primeResources({
|
||||
req: mockReq,
|
||||
appConfig: mockAppConfig,
|
||||
getFiles: mockGetFiles,
|
||||
requestFileSet,
|
||||
attachments,
|
||||
|
|
@ -189,6 +203,7 @@ describe('primeResources', () => {
|
|||
|
||||
const result = await primeResources({
|
||||
req: mockReq,
|
||||
appConfig: mockAppConfig,
|
||||
getFiles: mockGetFiles,
|
||||
requestFileSet,
|
||||
attachments,
|
||||
|
|
@ -220,6 +235,7 @@ describe('primeResources', () => {
|
|||
|
||||
const result = await primeResources({
|
||||
req: mockReq,
|
||||
appConfig: mockAppConfig,
|
||||
getFiles: mockGetFiles,
|
||||
requestFileSet,
|
||||
attachments,
|
||||
|
|
@ -250,6 +266,7 @@ describe('primeResources', () => {
|
|||
|
||||
const result = await primeResources({
|
||||
req: mockReq,
|
||||
appConfig: mockAppConfig,
|
||||
getFiles: mockGetFiles,
|
||||
requestFileSet,
|
||||
attachments,
|
||||
|
|
@ -291,6 +308,7 @@ describe('primeResources', () => {
|
|||
|
||||
const result = await primeResources({
|
||||
req: mockReq,
|
||||
appConfig: mockAppConfig,
|
||||
getFiles: mockGetFiles,
|
||||
requestFileSet,
|
||||
attachments,
|
||||
|
|
@ -342,6 +360,7 @@ describe('primeResources', () => {
|
|||
|
||||
const result = await primeResources({
|
||||
req: mockReq,
|
||||
appConfig: mockAppConfig,
|
||||
getFiles: mockGetFiles,
|
||||
requestFileSet,
|
||||
attachments,
|
||||
|
|
@ -399,6 +418,7 @@ describe('primeResources', () => {
|
|||
|
||||
const result = await primeResources({
|
||||
req: mockReq,
|
||||
appConfig: mockAppConfig,
|
||||
getFiles: mockGetFiles,
|
||||
requestFileSet,
|
||||
attachments,
|
||||
|
|
@ -450,6 +470,7 @@ describe('primeResources', () => {
|
|||
|
||||
const result = await primeResources({
|
||||
req: mockReq,
|
||||
appConfig: mockAppConfig,
|
||||
getFiles: mockGetFiles,
|
||||
requestFileSet,
|
||||
attachments,
|
||||
|
|
@ -492,6 +513,7 @@ describe('primeResources', () => {
|
|||
|
||||
const result = await primeResources({
|
||||
req: mockReq,
|
||||
appConfig: mockAppConfig,
|
||||
getFiles: mockGetFiles,
|
||||
requestFileSet,
|
||||
attachments,
|
||||
|
|
@ -560,6 +582,7 @@ describe('primeResources', () => {
|
|||
|
||||
const result = await primeResources({
|
||||
req: mockReq,
|
||||
appConfig: mockAppConfig,
|
||||
getFiles: mockGetFiles,
|
||||
requestFileSet,
|
||||
attachments,
|
||||
|
|
@ -618,6 +641,7 @@ describe('primeResources', () => {
|
|||
|
||||
const result = await primeResources({
|
||||
req: mockReq,
|
||||
appConfig: mockAppConfig,
|
||||
getFiles: mockGetFiles,
|
||||
requestFileSet,
|
||||
attachments,
|
||||
|
|
@ -671,6 +695,7 @@ describe('primeResources', () => {
|
|||
|
||||
const result = await primeResources({
|
||||
req: mockReq,
|
||||
appConfig: mockAppConfig,
|
||||
getFiles: mockGetFiles,
|
||||
requestFileSet,
|
||||
attachments,
|
||||
|
|
@ -724,6 +749,7 @@ describe('primeResources', () => {
|
|||
|
||||
const result = await primeResources({
|
||||
req: mockReq,
|
||||
appConfig: mockAppConfig,
|
||||
getFiles: mockGetFiles,
|
||||
requestFileSet,
|
||||
attachments,
|
||||
|
|
@ -764,6 +790,7 @@ describe('primeResources', () => {
|
|||
|
||||
const result = await primeResources({
|
||||
req: mockReq,
|
||||
appConfig: mockAppConfig,
|
||||
getFiles: mockGetFiles,
|
||||
requestFileSet,
|
||||
attachments,
|
||||
|
|
@ -838,6 +865,7 @@ describe('primeResources', () => {
|
|||
|
||||
const result = await primeResources({
|
||||
req: mockReq,
|
||||
appConfig: mockAppConfig,
|
||||
getFiles: mockGetFiles,
|
||||
requestFileSet,
|
||||
attachments,
|
||||
|
|
@ -888,6 +916,7 @@ describe('primeResources', () => {
|
|||
|
||||
const result = await primeResources({
|
||||
req: mockReq,
|
||||
appConfig: mockAppConfig,
|
||||
getFiles: mockGetFiles,
|
||||
requestFileSet,
|
||||
attachments,
|
||||
|
|
@ -906,6 +935,7 @@ describe('primeResources', () => {
|
|||
// The function should now handle rejected attachment promises gracefully
|
||||
const result = await primeResources({
|
||||
req: mockReq,
|
||||
appConfig: mockAppConfig,
|
||||
getFiles: mockGetFiles,
|
||||
requestFileSet,
|
||||
attachments,
|
||||
|
|
@ -928,9 +958,11 @@ describe('primeResources', () => {
|
|||
describe('edge cases', () => {
|
||||
it('should handle missing app.locals gracefully', async () => {
|
||||
const reqWithoutLocals = {} as ServerRequest;
|
||||
const emptyAppConfig = {} as AppConfig;
|
||||
|
||||
const result = await primeResources({
|
||||
req: reqWithoutLocals,
|
||||
appConfig: emptyAppConfig,
|
||||
getFiles: mockGetFiles,
|
||||
requestFileSet,
|
||||
attachments: undefined,
|
||||
|
|
@ -950,6 +982,7 @@ describe('primeResources', () => {
|
|||
it('should handle undefined tool_resources', async () => {
|
||||
const result = await primeResources({
|
||||
req: mockReq,
|
||||
appConfig: mockAppConfig,
|
||||
getFiles: mockGetFiles,
|
||||
requestFileSet,
|
||||
attachments: undefined,
|
||||
|
|
@ -982,6 +1015,7 @@ describe('primeResources', () => {
|
|||
|
||||
const result = await primeResources({
|
||||
req: mockReq,
|
||||
appConfig: mockAppConfig,
|
||||
getFiles: mockGetFiles,
|
||||
requestFileSet: emptyRequestFileSet,
|
||||
attachments,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import type { AgentToolResources, TFile, AgentBaseResource } from 'librechat-dat
|
|||
import type { FilterQuery, QueryOptions, ProjectionType } from 'mongoose';
|
||||
import type { IMongoFile, IUser } from '@librechat/data-schemas';
|
||||
import type { Request as ServerRequest } from 'express';
|
||||
import type { AppConfig } from '~/types/';
|
||||
|
||||
/**
|
||||
* Function type for retrieving files from the database
|
||||
|
|
@ -134,7 +135,8 @@ const categorizeFileForToolResources = ({
|
|||
* 4. Prevents duplicate files across all sources
|
||||
*
|
||||
* @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.requestFileSet - Set of file IDs from the current request
|
||||
* @param params.attachments - Promise resolving to array of attachment files
|
||||
|
|
@ -143,6 +145,7 @@ const categorizeFileForToolResources = ({
|
|||
*/
|
||||
export const primeResources = async ({
|
||||
req,
|
||||
appConfig,
|
||||
getFiles,
|
||||
requestFileSet,
|
||||
attachments: _attachments,
|
||||
|
|
@ -150,6 +153,7 @@ export const primeResources = async ({
|
|||
agentId,
|
||||
}: {
|
||||
req: ServerRequest & { user?: IUser };
|
||||
appConfig: AppConfig;
|
||||
requestFileSet: Set<string>;
|
||||
attachments: Promise<Array<TFile | null>> | 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,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { ErrorTypes, EModelEndpoint, mapModelToAzureConfig } from 'librechat-data-provider';
|
||||
import type {
|
||||
UserKeyValues,
|
||||
InitializeOpenAIOptionsParams,
|
||||
OpenAIOptionsResult,
|
||||
OpenAIConfigOptions,
|
||||
InitializeOpenAIOptionsParams,
|
||||
UserKeyValues,
|
||||
} from '~/types';
|
||||
import { createHandleLLMNewToken } from '~/utils/generators';
|
||||
import { getAzureCredentials } from '~/utils/azure';
|
||||
|
|
@ -21,6 +21,7 @@ import { getOpenAIConfig } from './llm';
|
|||
*/
|
||||
export const initializeOpenAI = async ({
|
||||
req,
|
||||
appConfig,
|
||||
overrideModel,
|
||||
endpointOption,
|
||||
overrideEndpoint,
|
||||
|
|
@ -71,7 +72,7 @@ export const initializeOpenAI = async ({
|
|||
};
|
||||
|
||||
const isAzureOpenAI = endpoint === EModelEndpoint.azureOpenAI;
|
||||
const azureConfig = isAzureOpenAI && req.app.locals[EModelEndpoint.azureOpenAI];
|
||||
const azureConfig = isAzureOpenAI && appConfig[EModelEndpoint.azureOpenAI];
|
||||
|
||||
if (isAzureOpenAI && azureConfig) {
|
||||
const { modelGroupMap, groupMap } = azureConfig;
|
||||
|
|
@ -142,8 +143,8 @@ export const initializeOpenAI = async ({
|
|||
|
||||
const options = getOpenAIConfig(apiKey, finalClientOptions, endpoint);
|
||||
|
||||
const openAIConfig = req.app.locals[EModelEndpoint.openAI];
|
||||
const allConfig = req.app.locals.all;
|
||||
const openAIConfig = appConfig[EModelEndpoint.openAI];
|
||||
const allConfig = appConfig.all;
|
||||
const azureRate = modelName?.includes('gpt-4') ? 30 : 17;
|
||||
|
||||
let streamRate: number | undefined;
|
||||
|
|
|
|||
|
|
@ -46,7 +46,12 @@ import * as fs from 'fs';
|
|||
import axios from 'axios';
|
||||
import type { Request as ExpressRequest } from 'express';
|
||||
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 {
|
||||
uploadDocumentToMistral,
|
||||
|
|
@ -497,17 +502,16 @@ describe('MistralOCR Service', () => {
|
|||
|
||||
const req = {
|
||||
user: { id: 'user123' },
|
||||
app: {
|
||||
locals: {
|
||||
} as unknown as ExpressRequest;
|
||||
|
||||
const appConfig = {
|
||||
ocr: {
|
||||
// Use environment variable syntax to ensure loadAuthValues is called
|
||||
apiKey: '${OCR_API_KEY}',
|
||||
baseURL: '${OCR_BASEURL}',
|
||||
mistralModel: 'mistral-medium',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as ExpressRequest;
|
||||
} as AppConfig;
|
||||
|
||||
const file = {
|
||||
path: '/tmp/upload/file.pdf',
|
||||
|
|
@ -517,6 +521,7 @@ describe('MistralOCR Service', () => {
|
|||
|
||||
const result = await uploadMistralOCR({
|
||||
req,
|
||||
appConfig,
|
||||
file,
|
||||
loadAuthValues: mockLoadAuthValues,
|
||||
});
|
||||
|
|
@ -599,16 +604,15 @@ describe('MistralOCR Service', () => {
|
|||
|
||||
const req = {
|
||||
user: { id: 'user456' },
|
||||
app: {
|
||||
locals: {
|
||||
} as unknown as ExpressRequest;
|
||||
|
||||
const appConfig = {
|
||||
ocr: {
|
||||
apiKey: '${OCR_API_KEY}',
|
||||
baseURL: '${OCR_BASEURL}',
|
||||
mistralModel: 'mistral-medium',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as ExpressRequest;
|
||||
} as AppConfig;
|
||||
|
||||
const file = {
|
||||
path: '/tmp/upload/image.png',
|
||||
|
|
@ -618,6 +622,7 @@ describe('MistralOCR Service', () => {
|
|||
|
||||
const result = await uploadMistralOCR({
|
||||
req,
|
||||
appConfig,
|
||||
file,
|
||||
loadAuthValues: mockLoadAuthValues,
|
||||
});
|
||||
|
|
@ -698,16 +703,15 @@ describe('MistralOCR Service', () => {
|
|||
|
||||
const req = {
|
||||
user: { id: 'user123' },
|
||||
app: {
|
||||
locals: {
|
||||
} as unknown as ExpressRequest;
|
||||
|
||||
const appConfig = {
|
||||
ocr: {
|
||||
apiKey: '${CUSTOM_API_KEY}',
|
||||
baseURL: '${CUSTOM_BASEURL}',
|
||||
mistralModel: '${CUSTOM_MODEL}',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as ExpressRequest;
|
||||
} as AppConfig;
|
||||
|
||||
// Set environment variable for model
|
||||
process.env.CUSTOM_MODEL = 'mistral-large';
|
||||
|
|
@ -720,6 +724,7 @@ describe('MistralOCR Service', () => {
|
|||
|
||||
const result = await uploadMistralOCR({
|
||||
req,
|
||||
appConfig,
|
||||
file,
|
||||
loadAuthValues: mockLoadAuthValues,
|
||||
});
|
||||
|
|
@ -790,17 +795,16 @@ describe('MistralOCR Service', () => {
|
|||
|
||||
const req = {
|
||||
user: { id: 'user123' },
|
||||
app: {
|
||||
locals: {
|
||||
} as unknown as ExpressRequest;
|
||||
|
||||
const appConfig = {
|
||||
ocr: {
|
||||
// Use environment variable syntax to ensure loadAuthValues is called
|
||||
apiKey: '${INVALID_FORMAT}', // Using valid env var format but with an invalid name
|
||||
baseURL: '${OCR_BASEURL}', // Using valid env var format
|
||||
mistralModel: 'mistral-ocr-latest', // Plain string value
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as ExpressRequest;
|
||||
} as AppConfig;
|
||||
|
||||
const file = {
|
||||
path: '/tmp/upload/file.pdf',
|
||||
|
|
@ -810,6 +814,7 @@ describe('MistralOCR Service', () => {
|
|||
|
||||
await uploadMistralOCR({
|
||||
req,
|
||||
appConfig,
|
||||
file,
|
||||
loadAuthValues: mockLoadAuthValues,
|
||||
});
|
||||
|
|
@ -845,15 +850,14 @@ describe('MistralOCR Service', () => {
|
|||
|
||||
const req = {
|
||||
user: { id: 'user123' },
|
||||
app: {
|
||||
locals: {
|
||||
} as unknown as ExpressRequest;
|
||||
|
||||
const appConfig = {
|
||||
ocr: {
|
||||
apiKey: 'OCR_API_KEY',
|
||||
baseURL: 'OCR_BASEURL',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as ExpressRequest;
|
||||
} as AppConfig;
|
||||
|
||||
const file = {
|
||||
path: '/tmp/upload/file.pdf',
|
||||
|
|
@ -864,6 +868,7 @@ describe('MistralOCR Service', () => {
|
|||
await expect(
|
||||
uploadMistralOCR({
|
||||
req,
|
||||
appConfig,
|
||||
file,
|
||||
loadAuthValues: mockLoadAuthValues,
|
||||
}),
|
||||
|
|
@ -931,16 +936,15 @@ describe('MistralOCR Service', () => {
|
|||
|
||||
const req = {
|
||||
user: { id: 'user123' },
|
||||
app: {
|
||||
locals: {
|
||||
} as unknown as ExpressRequest;
|
||||
|
||||
const appConfig = {
|
||||
ocr: {
|
||||
apiKey: 'OCR_API_KEY',
|
||||
baseURL: 'OCR_BASEURL',
|
||||
mistralModel: 'mistral-ocr-latest',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as ExpressRequest;
|
||||
} as AppConfig;
|
||||
|
||||
const file = {
|
||||
path: '/tmp/upload/file.pdf',
|
||||
|
|
@ -950,6 +954,7 @@ describe('MistralOCR Service', () => {
|
|||
|
||||
const result = await uploadMistralOCR({
|
||||
req,
|
||||
appConfig,
|
||||
file,
|
||||
loadAuthValues: mockLoadAuthValues,
|
||||
});
|
||||
|
|
@ -1019,17 +1024,16 @@ describe('MistralOCR Service', () => {
|
|||
|
||||
const req = {
|
||||
user: { id: 'user123' },
|
||||
app: {
|
||||
locals: {
|
||||
} as unknown as ExpressRequest;
|
||||
|
||||
const appConfig = {
|
||||
ocr: {
|
||||
// Direct values that should be used as-is, without variable substitution
|
||||
apiKey: 'actual-api-key-value',
|
||||
baseURL: 'https://direct-api-url.mistral.ai/v1',
|
||||
mistralModel: 'mistral-direct-model',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as ExpressRequest;
|
||||
} as AppConfig;
|
||||
|
||||
const file = {
|
||||
path: '/tmp/upload/file.pdf',
|
||||
|
|
@ -1039,6 +1043,7 @@ describe('MistralOCR Service', () => {
|
|||
|
||||
const result = await uploadMistralOCR({
|
||||
req,
|
||||
appConfig,
|
||||
file,
|
||||
loadAuthValues: mockLoadAuthValues,
|
||||
});
|
||||
|
|
@ -1133,17 +1138,16 @@ describe('MistralOCR Service', () => {
|
|||
|
||||
const req = {
|
||||
user: { id: 'user123' },
|
||||
app: {
|
||||
locals: {
|
||||
} as unknown as ExpressRequest;
|
||||
|
||||
const appConfig = {
|
||||
ocr: {
|
||||
// Empty string values - should fall back to defaults
|
||||
apiKey: '',
|
||||
baseURL: '',
|
||||
mistralModel: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as ExpressRequest;
|
||||
} as AppConfig;
|
||||
|
||||
const file = {
|
||||
path: '/tmp/upload/file.pdf',
|
||||
|
|
@ -1153,6 +1157,7 @@ describe('MistralOCR Service', () => {
|
|||
|
||||
const result = await uploadMistralOCR({
|
||||
req,
|
||||
appConfig,
|
||||
file,
|
||||
loadAuthValues: mockLoadAuthValues,
|
||||
});
|
||||
|
|
@ -1276,16 +1281,15 @@ describe('MistralOCR Service', () => {
|
|||
|
||||
const req = {
|
||||
user: { id: 'user123' },
|
||||
app: {
|
||||
locals: {
|
||||
} as unknown as ExpressRequest;
|
||||
|
||||
const appConfig = {
|
||||
ocr: {
|
||||
apiKey: '${AZURE_MISTRAL_OCR_API_KEY}',
|
||||
baseURL: 'https://endpoint.models.ai.azure.com/v1',
|
||||
mistralModel: 'mistral-ocr-2503',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as ExpressRequest;
|
||||
} as AppConfig;
|
||||
|
||||
const file = {
|
||||
path: '/tmp/upload/file.pdf',
|
||||
|
|
@ -1295,6 +1299,7 @@ describe('MistralOCR Service', () => {
|
|||
|
||||
await uploadMistralOCR({
|
||||
req,
|
||||
appConfig,
|
||||
file,
|
||||
loadAuthValues: mockLoadAuthValues,
|
||||
});
|
||||
|
|
@ -1360,16 +1365,15 @@ describe('MistralOCR Service', () => {
|
|||
|
||||
const req = {
|
||||
user: { id: 'user456' },
|
||||
app: {
|
||||
locals: {
|
||||
} as unknown as ExpressRequest;
|
||||
|
||||
const appConfig = {
|
||||
ocr: {
|
||||
apiKey: 'hardcoded-api-key-12345',
|
||||
baseURL: '${CUSTOM_OCR_BASEURL}',
|
||||
mistralModel: 'mistral-ocr-latest',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as ExpressRequest;
|
||||
} as AppConfig;
|
||||
|
||||
const file = {
|
||||
path: '/tmp/upload/file.pdf',
|
||||
|
|
@ -1379,6 +1383,7 @@ describe('MistralOCR Service', () => {
|
|||
|
||||
await uploadMistralOCR({
|
||||
req,
|
||||
appConfig,
|
||||
file,
|
||||
loadAuthValues: mockLoadAuthValues,
|
||||
});
|
||||
|
|
@ -1484,16 +1489,15 @@ describe('MistralOCR Service', () => {
|
|||
|
||||
const req = {
|
||||
user: { id: 'user123' },
|
||||
app: {
|
||||
locals: {
|
||||
} as unknown as ExpressRequest;
|
||||
|
||||
const appConfig = {
|
||||
ocr: {
|
||||
apiKey: '${OCR_API_KEY}',
|
||||
baseURL: '${OCR_BASEURL}',
|
||||
mistralModel: 'mistral-ocr-latest',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as ExpressRequest;
|
||||
} as AppConfig;
|
||||
|
||||
const file = {
|
||||
path: '/tmp/upload/file.pdf',
|
||||
|
|
@ -1503,6 +1507,7 @@ describe('MistralOCR Service', () => {
|
|||
|
||||
await uploadMistralOCR({
|
||||
req,
|
||||
appConfig,
|
||||
file,
|
||||
loadAuthValues: mockLoadAuthValues,
|
||||
});
|
||||
|
|
@ -1553,16 +1558,15 @@ describe('MistralOCR Service', () => {
|
|||
|
||||
const req = {
|
||||
user: { id: 'user123' },
|
||||
app: {
|
||||
locals: {
|
||||
} as unknown as ExpressRequest;
|
||||
|
||||
const appConfig = {
|
||||
ocr: {
|
||||
apiKey: '${OCR_API_KEY}',
|
||||
baseURL: '${OCR_BASEURL}',
|
||||
mistralModel: 'mistral-ocr-latest',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as ExpressRequest;
|
||||
} as AppConfig;
|
||||
|
||||
const file = {
|
||||
path: '/tmp/upload/file.pdf',
|
||||
|
|
@ -1573,6 +1577,7 @@ describe('MistralOCR Service', () => {
|
|||
await expect(
|
||||
uploadMistralOCR({
|
||||
req,
|
||||
appConfig,
|
||||
file,
|
||||
loadAuthValues: mockLoadAuthValues,
|
||||
}),
|
||||
|
|
@ -1641,16 +1646,15 @@ describe('MistralOCR Service', () => {
|
|||
|
||||
const req = {
|
||||
user: { id: 'user123' },
|
||||
app: {
|
||||
locals: {
|
||||
} as unknown as ExpressRequest;
|
||||
|
||||
const appConfig = {
|
||||
ocr: {
|
||||
apiKey: '${OCR_API_KEY}',
|
||||
baseURL: '${OCR_BASEURL}',
|
||||
mistralModel: 'mistral-ocr-latest',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as ExpressRequest;
|
||||
} as AppConfig;
|
||||
|
||||
const file = {
|
||||
path: '/tmp/upload/file.pdf',
|
||||
|
|
@ -1661,6 +1665,7 @@ describe('MistralOCR Service', () => {
|
|||
// Should not throw even if delete fails
|
||||
const result = await uploadMistralOCR({
|
||||
req,
|
||||
appConfig,
|
||||
file,
|
||||
loadAuthValues: mockLoadAuthValues,
|
||||
});
|
||||
|
|
@ -1701,16 +1706,15 @@ describe('MistralOCR Service', () => {
|
|||
|
||||
const req = {
|
||||
user: { id: 'user123' },
|
||||
app: {
|
||||
locals: {
|
||||
} as unknown as ExpressRequest;
|
||||
|
||||
const appConfig = {
|
||||
ocr: {
|
||||
apiKey: '${OCR_API_KEY}',
|
||||
baseURL: '${OCR_BASEURL}',
|
||||
mistralModel: 'mistral-ocr-latest',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as ExpressRequest;
|
||||
} as AppConfig;
|
||||
|
||||
const file = {
|
||||
path: '/tmp/upload/file.pdf',
|
||||
|
|
@ -1721,6 +1725,7 @@ describe('MistralOCR Service', () => {
|
|||
await expect(
|
||||
uploadMistralOCR({
|
||||
req,
|
||||
appConfig,
|
||||
file,
|
||||
loadAuthValues: mockLoadAuthValues,
|
||||
}),
|
||||
|
|
@ -1775,16 +1780,15 @@ describe('MistralOCR Service', () => {
|
|||
|
||||
const req = {
|
||||
user: { id: 'user123' },
|
||||
app: {
|
||||
locals: {
|
||||
} as unknown as ExpressRequest;
|
||||
|
||||
const appConfig = {
|
||||
ocr: {
|
||||
apiKey: '${OCR_API_KEY}',
|
||||
baseURL: '${OCR_BASEURL}',
|
||||
mistralModel: 'mistral-ocr-latest',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as ExpressRequest;
|
||||
} as AppConfig;
|
||||
|
||||
const file = {
|
||||
path: '/tmp/upload/azure-file.pdf',
|
||||
|
|
@ -1794,6 +1798,7 @@ describe('MistralOCR Service', () => {
|
|||
|
||||
const result = await uploadAzureMistralOCR({
|
||||
req,
|
||||
appConfig,
|
||||
file,
|
||||
loadAuthValues: mockLoadAuthValues,
|
||||
});
|
||||
|
|
@ -1851,16 +1856,15 @@ describe('MistralOCR Service', () => {
|
|||
|
||||
const req = {
|
||||
user: { id: 'user123' },
|
||||
app: {
|
||||
locals: {
|
||||
} as unknown as ExpressRequest;
|
||||
|
||||
const appConfig = {
|
||||
ocr: {
|
||||
apiKey: '${AZURE_MISTRAL_OCR_API_KEY}',
|
||||
baseURL: 'https://endpoint.models.ai.azure.com/v1',
|
||||
mistralModel: 'mistral-ocr-2503',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as ExpressRequest;
|
||||
} as AppConfig;
|
||||
|
||||
const file = {
|
||||
path: '/tmp/upload/file.pdf',
|
||||
|
|
@ -1870,6 +1874,7 @@ describe('MistralOCR Service', () => {
|
|||
|
||||
await uploadAzureMistralOCR({
|
||||
req,
|
||||
appConfig,
|
||||
file,
|
||||
loadAuthValues: mockLoadAuthValues,
|
||||
});
|
||||
|
|
@ -1915,16 +1920,15 @@ describe('MistralOCR Service', () => {
|
|||
|
||||
const req = {
|
||||
user: { id: 'user456' },
|
||||
app: {
|
||||
locals: {
|
||||
} as unknown as ExpressRequest;
|
||||
|
||||
const appConfig = {
|
||||
ocr: {
|
||||
apiKey: 'hardcoded-api-key-12345',
|
||||
baseURL: '${CUSTOM_OCR_BASEURL}',
|
||||
mistralModel: 'mistral-ocr-latest',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as ExpressRequest;
|
||||
} as AppConfig;
|
||||
|
||||
const file = {
|
||||
path: '/tmp/upload/file.pdf',
|
||||
|
|
@ -1934,6 +1938,7 @@ describe('MistralOCR Service', () => {
|
|||
|
||||
await uploadAzureMistralOCR({
|
||||
req,
|
||||
appConfig,
|
||||
file,
|
||||
loadAuthValues: mockLoadAuthValues,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import type {
|
|||
MistralOCRUploadResult,
|
||||
MistralOCRError,
|
||||
OCRResultPage,
|
||||
AppConfig,
|
||||
OCRResult,
|
||||
OCRImage,
|
||||
} from '~/types';
|
||||
|
|
@ -42,14 +43,10 @@ interface GoogleServiceAccount {
|
|||
|
||||
/** Helper type for OCR request context */
|
||||
interface OCRContext {
|
||||
req: Pick<ServerRequest, 'user' | 'app'> & {
|
||||
req: Pick<ServerRequest, 'user'> & {
|
||||
user?: { id: string };
|
||||
app: {
|
||||
locals?: {
|
||||
ocr?: TCustomConfig['ocr'];
|
||||
};
|
||||
};
|
||||
};
|
||||
appConfig: AppConfig;
|
||||
file: Express.Multer.File;
|
||||
loadAuthValues: (params: {
|
||||
userId: string;
|
||||
|
|
@ -241,7 +238,7 @@ async function resolveConfigValue(
|
|||
* Loads authentication configuration from OCR config
|
||||
*/
|
||||
async function loadAuthConfig(context: OCRContext): Promise<AuthConfig> {
|
||||
const ocrConfig = context.req.app.locals?.ocr;
|
||||
const ocrConfig = context.appConfig?.ocr;
|
||||
const apiKeyConfig = ocrConfig?.apiKey || '';
|
||||
const baseURLConfig = ocrConfig?.baseURL || '';
|
||||
|
||||
|
|
@ -357,6 +354,7 @@ function createOCRError(error: unknown, baseMessage: string): Error {
|
|||
* @param params - The params object.
|
||||
* @param params.req - The request object from Express. It should have a `user` property with an `id`
|
||||
* 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
|
||||
* have a `mimetype` property that tells us the file type
|
||||
* @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);
|
||||
apiKey = authConfig.apiKey;
|
||||
baseURL = authConfig.baseURL;
|
||||
const model = getModelConfig(context.req.app.locals?.ocr);
|
||||
const model = getModelConfig(context.appConfig?.ocr);
|
||||
|
||||
const mistralFile = await uploadDocumentToMistral({
|
||||
filePath: context.file.path,
|
||||
|
|
@ -430,6 +428,7 @@ export const uploadMistralOCR = async (context: OCRContext): Promise<MistralOCRU
|
|||
* @param params - The params object.
|
||||
* @param params.req - The request object from Express. It should have a `user` property with an `id`
|
||||
* 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
|
||||
* have a `mimetype` property that tells us the file type
|
||||
* @param params.loadAuthValues - Function to load authentication values
|
||||
|
|
@ -441,7 +440,7 @@ export const uploadAzureMistralOCR = async (
|
|||
): Promise<MistralOCRUploadResult> => {
|
||||
try {
|
||||
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 base64 = buffer.toString('base64');
|
||||
|
|
@ -644,6 +643,7 @@ async function performGoogleVertexOCR({
|
|||
* @param params - The params object.
|
||||
* @param params.req - The request object from Express. It should have a `user` property with an `id`
|
||||
* 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
|
||||
* have a `mimetype` property that tells us the file type
|
||||
* @param params.loadAuthValues - Function to load authentication values
|
||||
|
|
@ -655,7 +655,7 @@ export const uploadGoogleVertexMistralOCR = async (
|
|||
): Promise<MistralOCRUploadResult> => {
|
||||
try {
|
||||
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 base64 = buffer.toString('base64');
|
||||
|
|
|
|||
78
packages/api/src/types/config.ts
Normal file
78
packages/api/src/types/config.ts
Normal 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;
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
export * from './config';
|
||||
export * from './azure';
|
||||
export * from './balance';
|
||||
export * from './events';
|
||||
|
|
|
|||
|
|
@ -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 { OpenAIClientOptions, Providers } from '@librechat/agents';
|
||||
import type { AzureOptions } from './azure';
|
||||
import type { AppConfig } from './config';
|
||||
|
||||
export type OpenAIParameters = z.infer<typeof openAISchema>;
|
||||
|
||||
|
|
@ -85,6 +86,7 @@ export type CheckUserKeyExpiryFunction = (expiresAt: string, endpoint: string) =
|
|||
*/
|
||||
export interface InitializeOpenAIOptionsParams {
|
||||
req: RequestData;
|
||||
appConfig: AppConfig;
|
||||
overrideModel?: string;
|
||||
overrideEndpoint?: string;
|
||||
endpointOption: Partial<TEndpointOption>;
|
||||
|
|
|
|||
|
|
@ -1213,6 +1213,10 @@ export enum CacheKeys {
|
|||
* Key for the static config namespace.
|
||||
*/
|
||||
STATIC_CONFIG = 'STATIC_CONFIG',
|
||||
/**
|
||||
* Key for the app config namespace.
|
||||
*/
|
||||
APP_CONFIG = 'APP_CONFIG',
|
||||
/**
|
||||
* Key for accessing Abort Keys
|
||||
*/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue