mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-22 19:30: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,
|
Constants,
|
||||||
} = require('librechat-data-provider');
|
} = require('librechat-data-provider');
|
||||||
const { getMessages, saveMessage, updateMessage, saveConvo, getConvo } = require('~/models');
|
const { getMessages, saveMessage, updateMessage, saveConvo, getConvo } = require('~/models');
|
||||||
|
const { getAppConfig } = require('~/server/services/Config');
|
||||||
const { checkBalance } = require('~/models/balanceMethods');
|
const { checkBalance } = require('~/models/balanceMethods');
|
||||||
const { truncateToolCallOutputs } = require('./prompts');
|
const { truncateToolCallOutputs } = require('./prompts');
|
||||||
const { getFiles } = require('~/models/File');
|
const { getFiles } = require('~/models/File');
|
||||||
|
|
@ -567,6 +568,7 @@ class BaseClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendMessage(message, opts = {}) {
|
async sendMessage(message, opts = {}) {
|
||||||
|
const appConfig = await getAppConfig({ role: this.options.req?.user?.role });
|
||||||
/** @type {Promise<TMessage>} */
|
/** @type {Promise<TMessage>} */
|
||||||
let userMessagePromise;
|
let userMessagePromise;
|
||||||
const { user, head, isEdited, conversationId, responseMessageId, saveOptions, userMessage } =
|
const { user, head, isEdited, conversationId, responseMessageId, saveOptions, userMessage } =
|
||||||
|
|
@ -653,7 +655,7 @@ class BaseClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const balance = this.options.req?.app?.locals?.balance;
|
const balance = appConfig?.balance;
|
||||||
if (
|
if (
|
||||||
balance?.enabled &&
|
balance?.enabled &&
|
||||||
supportsBalanceCheck[this.options.endpointType ?? this.options.endpoint]
|
supportsBalanceCheck[this.options.endpointType ?? this.options.endpoint]
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ const {
|
||||||
const { extractBaseURL, getModelMaxTokens, getModelMaxOutputTokens } = require('~/utils');
|
const { extractBaseURL, getModelMaxTokens, getModelMaxOutputTokens } = require('~/utils');
|
||||||
const { encodeAndFormat } = require('~/server/services/Files/images/encode');
|
const { encodeAndFormat } = require('~/server/services/Files/images/encode');
|
||||||
const { addSpaceIfNeeded, sleep } = require('~/server/utils');
|
const { addSpaceIfNeeded, sleep } = require('~/server/utils');
|
||||||
|
const { getAppConfig } = require('~/server/services/Config');
|
||||||
const { spendTokens } = require('~/models/spendTokens');
|
const { spendTokens } = require('~/models/spendTokens');
|
||||||
const { handleOpenAIErrors } = require('./tools/util');
|
const { handleOpenAIErrors } = require('./tools/util');
|
||||||
const { createLLM, RunManager } = require('./llm');
|
const { createLLM, RunManager } = require('./llm');
|
||||||
|
|
@ -702,6 +703,7 @@ class OpenAIClient extends BaseClient {
|
||||||
* In case of failure, it will return the default title, "New Chat".
|
* In case of failure, it will return the default title, "New Chat".
|
||||||
*/
|
*/
|
||||||
async titleConvo({ text, conversationId, responseText = '' }) {
|
async titleConvo({ text, conversationId, responseText = '' }) {
|
||||||
|
const appConfig = await getAppConfig({ role: this.options.req?.user?.role });
|
||||||
this.conversationId = conversationId;
|
this.conversationId = conversationId;
|
||||||
|
|
||||||
if (this.options.attachments) {
|
if (this.options.attachments) {
|
||||||
|
|
@ -731,7 +733,7 @@ class OpenAIClient extends BaseClient {
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @type {TAzureConfig | undefined} */
|
/** @type {TAzureConfig | undefined} */
|
||||||
const azureConfig = this.options?.req?.app?.locals?.[EModelEndpoint.azureOpenAI];
|
const azureConfig = appConfig?.[EModelEndpoint.azureOpenAI];
|
||||||
|
|
||||||
const resetTitleOptions = !!(
|
const resetTitleOptions = !!(
|
||||||
(this.azure && azureConfig) ||
|
(this.azure && azureConfig) ||
|
||||||
|
|
@ -1120,6 +1122,7 @@ ${convo}
|
||||||
}
|
}
|
||||||
|
|
||||||
async chatCompletion({ payload, onProgress, abortController = null }) {
|
async chatCompletion({ payload, onProgress, abortController = null }) {
|
||||||
|
const appConfig = await getAppConfig({ role: this.options.req?.user?.role });
|
||||||
let error = null;
|
let error = null;
|
||||||
let intermediateReply = [];
|
let intermediateReply = [];
|
||||||
const errorCallback = (err) => (error = err);
|
const errorCallback = (err) => (error = err);
|
||||||
|
|
@ -1166,7 +1169,7 @@ ${convo}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @type {TAzureConfig | undefined} */
|
/** @type {TAzureConfig | undefined} */
|
||||||
const azureConfig = this.options?.req?.app?.locals?.[EModelEndpoint.azureOpenAI];
|
const azureConfig = appConfig?.[EModelEndpoint.azureOpenAI];
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(this.azure && this.isVisionModel && azureConfig) ||
|
(this.azure && this.isVisionModel && azureConfig) ||
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ const { logAxiosError } = require('@librechat/api');
|
||||||
const { logger } = require('@librechat/data-schemas');
|
const { logger } = require('@librechat/data-schemas');
|
||||||
const { ContentTypes, EImageOutputType } = require('librechat-data-provider');
|
const { ContentTypes, EImageOutputType } = require('librechat-data-provider');
|
||||||
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
||||||
|
const { getAppConfig } = require('~/server/services/Config');
|
||||||
const { extractBaseURL } = require('~/utils');
|
const { extractBaseURL } = require('~/utils');
|
||||||
const { getFiles } = require('~/models/File');
|
const { getFiles } = require('~/models/File');
|
||||||
|
|
||||||
|
|
@ -123,7 +124,7 @@ function createAbortHandler() {
|
||||||
* @param {MongoFile[]} [fields.imageFiles] - The images to be used for editing
|
* @param {MongoFile[]} [fields.imageFiles] - The images to be used for editing
|
||||||
* @returns {Array} - Array of image tools
|
* @returns {Array} - Array of image tools
|
||||||
*/
|
*/
|
||||||
function createOpenAIImageTools(fields = {}) {
|
async function createOpenAIImageTools(fields = {}) {
|
||||||
/** @type {boolean} Used to initialize the Tool without necessary variables. */
|
/** @type {boolean} Used to initialize the Tool without necessary variables. */
|
||||||
const override = fields.override ?? false;
|
const override = fields.override ?? false;
|
||||||
/** @type {boolean} */
|
/** @type {boolean} */
|
||||||
|
|
@ -131,8 +132,9 @@ function createOpenAIImageTools(fields = {}) {
|
||||||
throw new Error('This tool is only available for agents.');
|
throw new Error('This tool is only available for agents.');
|
||||||
}
|
}
|
||||||
const { req } = fields;
|
const { req } = fields;
|
||||||
const imageOutputType = req?.app.locals.imageOutputType || EImageOutputType.PNG;
|
const appConfig = await getAppConfig({ role: req?.user?.role });
|
||||||
const appFileStrategy = req?.app.locals.fileStrategy;
|
const imageOutputType = appConfig?.imageOutputType || EImageOutputType.PNG;
|
||||||
|
const appFileStrategy = appConfig?.fileStrategy;
|
||||||
|
|
||||||
const getApiKey = () => {
|
const getApiKey = () => {
|
||||||
const apiKey = process.env.IMAGE_GEN_OAI_API_KEY ?? '';
|
const apiKey = process.env.IMAGE_GEN_OAI_API_KEY ?? '';
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,8 @@ const {
|
||||||
const { primeFiles: primeCodeFiles } = require('~/server/services/Files/Code/process');
|
const { primeFiles: primeCodeFiles } = require('~/server/services/Files/Code/process');
|
||||||
const { createFileSearchTool, primeFiles: primeSearchFiles } = require('./fileSearch');
|
const { createFileSearchTool, primeFiles: primeSearchFiles } = require('./fileSearch');
|
||||||
const { getUserPluginAuthValue } = require('~/server/services/PluginService');
|
const { getUserPluginAuthValue } = require('~/server/services/PluginService');
|
||||||
|
const { getCachedTools, getAppConfig } = require('~/server/services/Config');
|
||||||
const { loadAuthValues } = require('~/server/services/Tools/credentials');
|
const { loadAuthValues } = require('~/server/services/Tools/credentials');
|
||||||
const { getCachedTools } = require('~/server/services/Config');
|
|
||||||
const { createMCPTool } = require('~/server/services/MCP');
|
const { createMCPTool } = require('~/server/services/MCP');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -143,6 +143,7 @@ const loadTools = async ({
|
||||||
functions = true,
|
functions = true,
|
||||||
returnMap = false,
|
returnMap = false,
|
||||||
}) => {
|
}) => {
|
||||||
|
const appConfig = await getAppConfig({ role: options?.req?.user?.role });
|
||||||
const toolConstructors = {
|
const toolConstructors = {
|
||||||
flux: FluxAPI,
|
flux: FluxAPI,
|
||||||
calculator: Calculator,
|
calculator: Calculator,
|
||||||
|
|
@ -272,7 +273,7 @@ const loadTools = async ({
|
||||||
};
|
};
|
||||||
continue;
|
continue;
|
||||||
} else if (tool === Tools.web_search) {
|
} else if (tool === Tools.web_search) {
|
||||||
const webSearchConfig = options?.req?.app?.locals?.webSearch;
|
const webSearchConfig = appConfig?.webSearch;
|
||||||
const result = await loadWebSearchAuth({
|
const result = await loadWebSearchAuth({
|
||||||
userId: user,
|
userId: user,
|
||||||
loadAuthValues,
|
loadAuthValues,
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ const {
|
||||||
filterUniquePlugins,
|
filterUniquePlugins,
|
||||||
convertMCPToolsToPlugins,
|
convertMCPToolsToPlugins,
|
||||||
} = require('@librechat/api');
|
} = require('@librechat/api');
|
||||||
const { getCustomConfig, getCachedTools } = require('~/server/services/Config');
|
const { getCustomConfig, getCachedTools, getAppConfig } = require('~/server/services/Config');
|
||||||
const { availableTools, toolkits } = require('~/app/clients/tools');
|
const { availableTools, toolkits } = require('~/app/clients/tools');
|
||||||
const { getMCPManager, getFlowStateManager } = require('~/config');
|
const { getMCPManager, getFlowStateManager } = require('~/config');
|
||||||
const { getLogStores } = require('~/cache');
|
const { getLogStores } = require('~/cache');
|
||||||
|
|
@ -20,8 +20,9 @@ const getAvailablePluginsController = async (req, res) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
/** @type {{ filteredTools: string[], includedTools: string[] }} */
|
/** @type {{ filteredTools: string[], includedTools: string[] }} */
|
||||||
const { filteredTools = [], includedTools = [] } = req.app.locals;
|
const { filteredTools = [], includedTools = [] } = appConfig;
|
||||||
const pluginManifest = availableTools;
|
const pluginManifest = availableTools;
|
||||||
|
|
||||||
const uniquePlugins = filterUniquePlugins(pluginManifest);
|
const uniquePlugins = filterUniquePlugins(pluginManifest);
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,13 @@ const { needsRefresh, getNewS3URL } = require('~/server/services/Files/S3/crud')
|
||||||
const { Tools, Constants, FileSources } = require('librechat-data-provider');
|
const { Tools, Constants, FileSources } = require('librechat-data-provider');
|
||||||
const { processDeleteRequest } = require('~/server/services/Files/process');
|
const { processDeleteRequest } = require('~/server/services/Files/process');
|
||||||
const { Transaction, Balance, User } = require('~/db/models');
|
const { Transaction, Balance, User } = require('~/db/models');
|
||||||
|
const { getAppConfig } = require('~/server/services/Config');
|
||||||
const { deleteToolCalls } = require('~/models/ToolCall');
|
const { deleteToolCalls } = require('~/models/ToolCall');
|
||||||
const { deleteAllSharedLinks } = require('~/models');
|
const { deleteAllSharedLinks } = require('~/models');
|
||||||
const { getMCPManager } = require('~/config');
|
const { getMCPManager } = require('~/config');
|
||||||
|
|
||||||
const getUserController = async (req, res) => {
|
const getUserController = async (req, res) => {
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
/** @type {MongoUser} */
|
/** @type {MongoUser} */
|
||||||
const userData = req.user.toObject != null ? req.user.toObject() : { ...req.user };
|
const userData = req.user.toObject != null ? req.user.toObject() : { ...req.user };
|
||||||
/**
|
/**
|
||||||
|
|
@ -31,7 +33,7 @@ const getUserController = async (req, res) => {
|
||||||
delete userData.password;
|
delete userData.password;
|
||||||
delete userData.totpSecret;
|
delete userData.totpSecret;
|
||||||
delete userData.backupCodes;
|
delete userData.backupCodes;
|
||||||
if (req.app.locals.fileStrategy === FileSources.s3 && userData.avatar) {
|
if (appConfig.fileStrategy === FileSources.s3 && userData.avatar) {
|
||||||
const avatarNeedsRefresh = needsRefresh(userData.avatar, 3600);
|
const avatarNeedsRefresh = needsRefresh(userData.avatar, 3600);
|
||||||
if (!avatarNeedsRefresh) {
|
if (!avatarNeedsRefresh) {
|
||||||
return res.status(200).send(userData);
|
return res.status(200).send(userData);
|
||||||
|
|
@ -87,6 +89,7 @@ const deleteUserFiles = async (req) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateUserPluginsController = async (req, res) => {
|
const updateUserPluginsController = async (req, res) => {
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
const { user } = req;
|
const { user } = req;
|
||||||
const { pluginKey, action, auth, isEntityTool } = req.body;
|
const { pluginKey, action, auth, isEntityTool } = req.body;
|
||||||
try {
|
try {
|
||||||
|
|
@ -131,7 +134,7 @@ const updateUserPluginsController = async (req, res) => {
|
||||||
|
|
||||||
if (pluginKey === Tools.web_search) {
|
if (pluginKey === Tools.web_search) {
|
||||||
/** @type {TCustomConfig['webSearch']} */
|
/** @type {TCustomConfig['webSearch']} */
|
||||||
const webSearchConfig = req.app.locals?.webSearch;
|
const webSearchConfig = appConfig?.webSearch;
|
||||||
keys = extractWebSearchEnvVars({
|
keys = extractWebSearchEnvVars({
|
||||||
keys: action === 'install' ? keys : webSearchKeys,
|
keys: action === 'install' ? keys : webSearchKeys,
|
||||||
config: webSearchConfig,
|
config: webSearchConfig,
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,12 @@ const {
|
||||||
deleteMemory,
|
deleteMemory,
|
||||||
setMemory,
|
setMemory,
|
||||||
} = require('~/models');
|
} = require('~/models');
|
||||||
const { getMCPAuthMap, checkCapability, hasCustomUserVars } = require('~/server/services/Config');
|
const {
|
||||||
|
hasCustomUserVars,
|
||||||
|
checkCapability,
|
||||||
|
getMCPAuthMap,
|
||||||
|
getAppConfig,
|
||||||
|
} = require('~/server/services/Config');
|
||||||
const { addCacheControl, createContextHandlers } = require('~/app/clients/prompts');
|
const { addCacheControl, createContextHandlers } = require('~/app/clients/prompts');
|
||||||
const { initializeAgent } = require('~/server/services/Endpoints/agents/agent');
|
const { initializeAgent } = require('~/server/services/Endpoints/agents/agent');
|
||||||
const { spendTokens, spendStructuredTokens } = require('~/models/spendTokens');
|
const { spendTokens, spendStructuredTokens } = require('~/models/spendTokens');
|
||||||
|
|
@ -451,17 +456,15 @@ class AgentClient extends BaseClient {
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/** @type {TCustomConfig['memory']} */
|
const appConfig = await getAppConfig({ role: user.role });
|
||||||
const memoryConfig = this.options.req?.app?.locals?.memory;
|
const memoryConfig = appConfig.memory;
|
||||||
if (!memoryConfig || memoryConfig.disabled === true) {
|
if (!memoryConfig || memoryConfig.disabled === true) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @type {Agent} */
|
/** @type {Agent} */
|
||||||
let prelimAgent;
|
let prelimAgent;
|
||||||
const allowedProviders = new Set(
|
const allowedProviders = new Set(appConfig?.[EModelEndpoint.agents]?.allowedProviders);
|
||||||
this.options.req?.app?.locals?.[EModelEndpoint.agents]?.allowedProviders,
|
|
||||||
);
|
|
||||||
try {
|
try {
|
||||||
if (memoryConfig.agent?.id != null && memoryConfig.agent.id !== this.options.agent.id) {
|
if (memoryConfig.agent?.id != null && memoryConfig.agent.id !== this.options.agent.id) {
|
||||||
prelimAgent = await loadAgent({
|
prelimAgent = await loadAgent({
|
||||||
|
|
@ -582,8 +585,8 @@ class AgentClient extends BaseClient {
|
||||||
if (this.processMemory == null) {
|
if (this.processMemory == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/** @type {TCustomConfig['memory']} */
|
const appConfig = await getAppConfig({ role: this.options.req.user?.role });
|
||||||
const memoryConfig = this.options.req?.app?.locals?.memory;
|
const memoryConfig = appConfig.memory;
|
||||||
const messageWindowSize = memoryConfig?.messageWindowSize ?? 5;
|
const messageWindowSize = memoryConfig?.messageWindowSize ?? 5;
|
||||||
|
|
||||||
let messagesToProcess = [...messages];
|
let messagesToProcess = [...messages];
|
||||||
|
|
@ -759,8 +762,9 @@ class AgentClient extends BaseClient {
|
||||||
abortController = new AbortController();
|
abortController = new AbortController();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const appConfig = await getAppConfig({ role: this.options.req.user?.role });
|
||||||
/** @type {TCustomConfig['endpoints']['agents']} */
|
/** @type {TCustomConfig['endpoints']['agents']} */
|
||||||
const agentsEConfig = this.options.req.app.locals[EModelEndpoint.agents];
|
const agentsEConfig = appConfig[EModelEndpoint.agents];
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
configurable: {
|
configurable: {
|
||||||
|
|
@ -1081,6 +1085,7 @@ class AgentClient extends BaseClient {
|
||||||
}
|
}
|
||||||
const { handleLLMEnd, collected: collectedMetadata } = createMetadataAggregator();
|
const { handleLLMEnd, collected: collectedMetadata } = createMetadataAggregator();
|
||||||
const { req, res, agent } = this.options;
|
const { req, res, agent } = this.options;
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
let endpoint = agent.endpoint;
|
let endpoint = agent.endpoint;
|
||||||
|
|
||||||
/** @type {import('@librechat/agents').ClientOptions} */
|
/** @type {import('@librechat/agents').ClientOptions} */
|
||||||
|
|
@ -1092,7 +1097,7 @@ class AgentClient extends BaseClient {
|
||||||
|
|
||||||
/** @type {TEndpoint | undefined} */
|
/** @type {TEndpoint | undefined} */
|
||||||
const endpointConfig =
|
const endpointConfig =
|
||||||
req.app.locals.all ?? req.app.locals[endpoint] ?? titleProviderConfig.customEndpointConfig;
|
appConfig.all ?? appConfig[endpoint] ?? titleProviderConfig.customEndpointConfig;
|
||||||
if (!endpointConfig) {
|
if (!endpointConfig) {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
'[api/server/controllers/agents/client.js #titleConvo] Error getting endpoint config',
|
'[api/server/controllers/agents/client.js #titleConvo] Error getting endpoint config',
|
||||||
|
|
|
||||||
|
|
@ -31,12 +31,12 @@ const {
|
||||||
grantPermission,
|
grantPermission,
|
||||||
} = require('~/server/services/PermissionService');
|
} = require('~/server/services/PermissionService');
|
||||||
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
||||||
|
const { getCachedTools, getAppConfig } = require('~/server/services/Config');
|
||||||
const { resizeAvatar } = require('~/server/services/Files/images/avatar');
|
const { resizeAvatar } = require('~/server/services/Files/images/avatar');
|
||||||
const { getFileStrategy } = require('~/server/utils/getFileStrategy');
|
const { getFileStrategy } = require('~/server/utils/getFileStrategy');
|
||||||
const { refreshS3Url } = require('~/server/services/Files/S3/crud');
|
const { refreshS3Url } = require('~/server/services/Files/S3/crud');
|
||||||
const { filterFile } = require('~/server/services/Files/process');
|
const { filterFile } = require('~/server/services/Files/process');
|
||||||
const { updateAction, getActions } = require('~/models/Action');
|
const { updateAction, getActions } = require('~/models/Action');
|
||||||
const { getCachedTools } = require('~/server/services/Config');
|
|
||||||
const { deleteFileByFilter } = require('~/models/File');
|
const { deleteFileByFilter } = require('~/models/File');
|
||||||
const { getCategoriesWithCounts } = require('~/models');
|
const { getCategoriesWithCounts } = require('~/models');
|
||||||
|
|
||||||
|
|
@ -487,6 +487,7 @@ const getListAgentsHandler = async (req, res) => {
|
||||||
*/
|
*/
|
||||||
const uploadAgentAvatarHandler = async (req, res) => {
|
const uploadAgentAvatarHandler = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
filterFile({ req, file: req.file, image: true, isAvatar: true });
|
filterFile({ req, file: req.file, image: true, isAvatar: true });
|
||||||
const { agent_id } = req.params;
|
const { agent_id } = req.params;
|
||||||
if (!agent_id) {
|
if (!agent_id) {
|
||||||
|
|
@ -510,9 +511,7 @@ const uploadAgentAvatarHandler = async (req, res) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const buffer = await fs.readFile(req.file.path);
|
const buffer = await fs.readFile(req.file.path);
|
||||||
|
const fileStrategy = getFileStrategy(appConfig, { isAvatar: true });
|
||||||
const fileStrategy = getFileStrategy(req.app.locals, { isAvatar: true });
|
|
||||||
|
|
||||||
const resizedBuffer = await resizeAvatar({
|
const resizedBuffer = await resizeAvatar({
|
||||||
userId: req.user.id,
|
userId: req.user.id,
|
||||||
input: buffer,
|
input: buffer,
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ const { createRun, StreamRunManager } = require('~/server/services/Runs');
|
||||||
const { addTitle } = require('~/server/services/Endpoints/assistants');
|
const { addTitle } = require('~/server/services/Endpoints/assistants');
|
||||||
const { createRunBody } = require('~/server/services/createRunBody');
|
const { createRunBody } = require('~/server/services/createRunBody');
|
||||||
const { sendResponse } = require('~/server/middleware/error');
|
const { sendResponse } = require('~/server/middleware/error');
|
||||||
|
const { getAppConfig } = require('~/server/services/Config');
|
||||||
const { getTransactions } = require('~/models/Transaction');
|
const { getTransactions } = require('~/models/Transaction');
|
||||||
const { checkBalance } = require('~/models/balanceMethods');
|
const { checkBalance } = require('~/models/balanceMethods');
|
||||||
const { getConvo } = require('~/models/Conversation');
|
const { getConvo } = require('~/models/Conversation');
|
||||||
|
|
@ -47,6 +48,7 @@ const { getOpenAIClient } = require('./helpers');
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
const chatV1 = async (req, res) => {
|
const chatV1 = async (req, res) => {
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
logger.debug('[/assistants/chat/] req.body', req.body);
|
logger.debug('[/assistants/chat/] req.body', req.body);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|
@ -251,7 +253,7 @@ const chatV1 = async (req, res) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkBalanceBeforeRun = async () => {
|
const checkBalanceBeforeRun = async () => {
|
||||||
const balance = req.app?.locals?.balance;
|
const balance = appConfig?.balance;
|
||||||
if (!balance?.enabled) {
|
if (!balance?.enabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ const validateAuthor = require('~/server/middleware/assistants/validateAuthor');
|
||||||
const { createRun, StreamRunManager } = require('~/server/services/Runs');
|
const { createRun, StreamRunManager } = require('~/server/services/Runs');
|
||||||
const { addTitle } = require('~/server/services/Endpoints/assistants');
|
const { addTitle } = require('~/server/services/Endpoints/assistants');
|
||||||
const { createRunBody } = require('~/server/services/createRunBody');
|
const { createRunBody } = require('~/server/services/createRunBody');
|
||||||
|
const { getAppConfig } = require('~/server/services/Config');
|
||||||
const { getTransactions } = require('~/models/Transaction');
|
const { getTransactions } = require('~/models/Transaction');
|
||||||
const { checkBalance } = require('~/models/balanceMethods');
|
const { checkBalance } = require('~/models/balanceMethods');
|
||||||
const { getConvo } = require('~/models/Conversation');
|
const { getConvo } = require('~/models/Conversation');
|
||||||
|
|
@ -44,6 +45,7 @@ const { getOpenAIClient } = require('./helpers');
|
||||||
*/
|
*/
|
||||||
const chatV2 = async (req, res) => {
|
const chatV2 = async (req, res) => {
|
||||||
logger.debug('[/assistants/chat/] req.body', req.body);
|
logger.debug('[/assistants/chat/] req.body', req.body);
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
|
|
||||||
/** @type {{files: MongoFile[]}} */
|
/** @type {{files: MongoFile[]}} */
|
||||||
const {
|
const {
|
||||||
|
|
@ -126,7 +128,7 @@ const chatV2 = async (req, res) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkBalanceBeforeRun = async () => {
|
const checkBalanceBeforeRun = async () => {
|
||||||
const balance = req.app?.locals?.balance;
|
const balance = appConfig?.balance;
|
||||||
if (!balance?.enabled) {
|
if (!balance?.enabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -374,9 +376,9 @@ const chatV2 = async (req, res) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @type {undefined | TAssistantEndpoint} */
|
/** @type {undefined | TAssistantEndpoint} */
|
||||||
const config = req.app.locals[endpoint] ?? {};
|
const config = appConfig[endpoint] ?? {};
|
||||||
/** @type {undefined | TBaseEndpoint} */
|
/** @type {undefined | TBaseEndpoint} */
|
||||||
const allConfig = req.app.locals.all;
|
const allConfig = appConfig.all;
|
||||||
|
|
||||||
const streamRunManager = new StreamRunManager({
|
const streamRunManager = new StreamRunManager({
|
||||||
req,
|
req,
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ const {
|
||||||
const {
|
const {
|
||||||
initializeClient: initAzureClient,
|
initializeClient: initAzureClient,
|
||||||
} = require('~/server/services/Endpoints/azureAssistants');
|
} = require('~/server/services/Endpoints/azureAssistants');
|
||||||
|
const { getEndpointsConfig, getAppConfig } = require('~/server/services/Config');
|
||||||
const { initializeClient } = require('~/server/services/Endpoints/assistants');
|
const { initializeClient } = require('~/server/services/Endpoints/assistants');
|
||||||
const { getEndpointsConfig } = require('~/server/services/Config');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Express.Request} req
|
* @param {Express.Request} req
|
||||||
|
|
@ -210,6 +210,7 @@ async function getOpenAIClient({ req, res, endpointOption, initAppClient, overri
|
||||||
* @returns {Promise<AssistantListResponse>} 200 - success response - application/json
|
* @returns {Promise<AssistantListResponse>} 200 - success response - application/json
|
||||||
*/
|
*/
|
||||||
const fetchAssistants = async ({ req, res, overrideEndpoint }) => {
|
const fetchAssistants = async ({ req, res, overrideEndpoint }) => {
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
const {
|
const {
|
||||||
limit = 100,
|
limit = 100,
|
||||||
order = 'desc',
|
order = 'desc',
|
||||||
|
|
@ -230,20 +231,20 @@ const fetchAssistants = async ({ req, res, overrideEndpoint }) => {
|
||||||
if (endpoint === EModelEndpoint.assistants) {
|
if (endpoint === EModelEndpoint.assistants) {
|
||||||
({ body } = await listAllAssistants({ req, res, version, query }));
|
({ body } = await listAllAssistants({ req, res, version, query }));
|
||||||
} else if (endpoint === EModelEndpoint.azureAssistants) {
|
} else if (endpoint === EModelEndpoint.azureAssistants) {
|
||||||
const azureConfig = req.app.locals[EModelEndpoint.azureOpenAI];
|
const azureConfig = appConfig[EModelEndpoint.azureOpenAI];
|
||||||
body = await listAssistantsForAzure({ req, res, version, azureConfig, query });
|
body = await listAssistantsForAzure({ req, res, version, azureConfig, query });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.user.role === SystemRoles.ADMIN) {
|
if (req.user.role === SystemRoles.ADMIN) {
|
||||||
return body;
|
return body;
|
||||||
} else if (!req.app.locals[endpoint]) {
|
} else if (!appConfig[endpoint]) {
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.data = filterAssistants({
|
body.data = filterAssistants({
|
||||||
userId: req.user.id,
|
userId: req.user.id,
|
||||||
assistants: body.data,
|
assistants: body.data,
|
||||||
assistantsConfig: req.app.locals[endpoint],
|
assistantsConfig: appConfig[endpoint],
|
||||||
});
|
});
|
||||||
return body;
|
return body;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@ const { uploadImageBuffer, filterFile } = require('~/server/services/Files/proce
|
||||||
const validateAuthor = require('~/server/middleware/assistants/validateAuthor');
|
const validateAuthor = require('~/server/middleware/assistants/validateAuthor');
|
||||||
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
||||||
const { deleteAssistantActions } = require('~/server/services/ActionService');
|
const { deleteAssistantActions } = require('~/server/services/ActionService');
|
||||||
|
const { getCachedTools, getAppConfig } = require('~/server/services/Config');
|
||||||
const { updateAssistantDoc, getAssistants } = require('~/models/Assistant');
|
const { updateAssistantDoc, getAssistants } = require('~/models/Assistant');
|
||||||
const { getOpenAIClient, fetchAssistants } = require('./helpers');
|
const { getOpenAIClient, fetchAssistants } = require('./helpers');
|
||||||
const { getCachedTools } = require('~/server/services/Config');
|
|
||||||
const { manifestToolMap } = require('~/app/clients/tools');
|
const { manifestToolMap } = require('~/app/clients/tools');
|
||||||
const { deleteFileByFilter } = require('~/models/File');
|
const { deleteFileByFilter } = require('~/models/File');
|
||||||
|
|
||||||
|
|
@ -258,8 +258,9 @@ function filterAssistantDocs({ documents, userId, assistantsConfig = {} }) {
|
||||||
*/
|
*/
|
||||||
const getAssistantDocuments = async (req, res) => {
|
const getAssistantDocuments = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
const endpoint = req.query;
|
const endpoint = req.query;
|
||||||
const assistantsConfig = req.app.locals[endpoint];
|
const assistantsConfig = appConfig[endpoint];
|
||||||
const documents = await getAssistants(
|
const documents = await getAssistants(
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
|
|
@ -296,6 +297,7 @@ const getAssistantDocuments = async (req, res) => {
|
||||||
*/
|
*/
|
||||||
const uploadAssistantAvatar = async (req, res) => {
|
const uploadAssistantAvatar = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
filterFile({ req, file: req.file, image: true, isAvatar: true });
|
filterFile({ req, file: req.file, image: true, isAvatar: true });
|
||||||
const { assistant_id } = req.params;
|
const { assistant_id } = req.params;
|
||||||
if (!assistant_id) {
|
if (!assistant_id) {
|
||||||
|
|
@ -337,7 +339,7 @@ const uploadAssistantAvatar = async (req, res) => {
|
||||||
const metadata = {
|
const metadata = {
|
||||||
..._metadata,
|
..._metadata,
|
||||||
avatar: image.filepath,
|
avatar: image.filepath,
|
||||||
avatar_source: req.app.locals.fileStrategy,
|
avatar_source: appConfig.fileStrategy,
|
||||||
};
|
};
|
||||||
|
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
|
@ -347,7 +349,7 @@ const uploadAssistantAvatar = async (req, res) => {
|
||||||
{
|
{
|
||||||
avatar: {
|
avatar: {
|
||||||
filepath: image.filepath,
|
filepath: image.filepath,
|
||||||
source: req.app.locals.fileStrategy,
|
source: appConfig.fileStrategy,
|
||||||
},
|
},
|
||||||
user: req.user.id,
|
user: req.user.id,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ const { processFileURL, uploadImageBuffer } = require('~/server/services/Files/p
|
||||||
const { processCodeOutput } = require('~/server/services/Files/Code/process');
|
const { processCodeOutput } = require('~/server/services/Files/Code/process');
|
||||||
const { createToolCall, getToolCallsByConvo } = require('~/models/ToolCall');
|
const { createToolCall, getToolCallsByConvo } = require('~/models/ToolCall');
|
||||||
const { loadAuthValues } = require('~/server/services/Tools/credentials');
|
const { loadAuthValues } = require('~/server/services/Tools/credentials');
|
||||||
|
const { getAppConfig } = require('~/server/services/Config');
|
||||||
const { loadTools } = require('~/app/clients/tools/util');
|
const { loadTools } = require('~/app/clients/tools/util');
|
||||||
const { getRoleByName } = require('~/models/Role');
|
const { getRoleByName } = require('~/models/Role');
|
||||||
const { getMessage } = require('~/models/Message');
|
const { getMessage } = require('~/models/Message');
|
||||||
|
|
@ -35,9 +36,10 @@ const toolAccessPermType = {
|
||||||
*/
|
*/
|
||||||
const verifyWebSearchAuth = async (req, res) => {
|
const verifyWebSearchAuth = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
const userId = req.user.id;
|
const userId = req.user.id;
|
||||||
/** @type {TCustomConfig['webSearch']} */
|
/** @type {TCustomConfig['webSearch']} */
|
||||||
const webSearchConfig = req.app.locals?.webSearch || {};
|
const webSearchConfig = appConfig?.webSearch || {};
|
||||||
const result = await loadWebSearchAuth({
|
const result = await loadWebSearchAuth({
|
||||||
userId,
|
userId,
|
||||||
loadAuthValues,
|
loadAuthValues,
|
||||||
|
|
@ -110,6 +112,7 @@ const verifyToolAuth = async (req, res) => {
|
||||||
*/
|
*/
|
||||||
const callTool = async (req, res) => {
|
const callTool = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
const { toolId = '' } = req.params;
|
const { toolId = '' } = req.params;
|
||||||
if (!fieldsMap[toolId]) {
|
if (!fieldsMap[toolId]) {
|
||||||
logger.warn(`[${toolId}/call] User ${req.user.id} attempted call to invalid tool`);
|
logger.warn(`[${toolId}/call] User ${req.user.id} attempted call to invalid tool`);
|
||||||
|
|
@ -155,7 +158,7 @@ const callTool = async (req, res) => {
|
||||||
returnMetadata: true,
|
returnMetadata: true,
|
||||||
processFileURL,
|
processFileURL,
|
||||||
uploadImageBuffer,
|
uploadImageBuffer,
|
||||||
fileStrategy: req.app.locals.fileStrategy,
|
fileStrategy: appConfig.fileStrategy,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ const { jwtLogin, ldapLogin, passportLogin } = require('~/strategies');
|
||||||
const { checkMigrations } = require('./services/start/migration');
|
const { checkMigrations } = require('./services/start/migration');
|
||||||
const initializeMCPs = require('./services/initializeMCPs');
|
const initializeMCPs = require('./services/initializeMCPs');
|
||||||
const configureSocialLogins = require('./socialLogins');
|
const configureSocialLogins = require('./socialLogins');
|
||||||
|
const { getAppConfig } = require('./services/Config');
|
||||||
const AppService = require('./services/AppService');
|
const AppService = require('./services/AppService');
|
||||||
const staticCache = require('./utils/staticCache');
|
const staticCache = require('./utils/staticCache');
|
||||||
const noIndex = require('./middleware/noIndex');
|
const noIndex = require('./middleware/noIndex');
|
||||||
|
|
@ -46,8 +47,8 @@ const startServer = async () => {
|
||||||
app.set('trust proxy', trusted_proxy);
|
app.set('trust proxy', trusted_proxy);
|
||||||
|
|
||||||
await AppService(app);
|
await AppService(app);
|
||||||
|
const appConfig = await getAppConfig();
|
||||||
const indexPath = path.join(app.locals.paths.dist, 'index.html');
|
const indexPath = path.join(appConfig.paths.dist, 'index.html');
|
||||||
const indexHTML = fs.readFileSync(indexPath, 'utf8');
|
const indexHTML = fs.readFileSync(indexPath, 'utf8');
|
||||||
|
|
||||||
app.get('/health', (_req, res) => res.status(200).send('OK'));
|
app.get('/health', (_req, res) => res.status(200).send('OK'));
|
||||||
|
|
@ -66,10 +67,9 @@ const startServer = async () => {
|
||||||
console.warn('Response compression has been disabled via DISABLE_COMPRESSION.');
|
console.warn('Response compression has been disabled via DISABLE_COMPRESSION.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serve static assets with aggressive caching
|
app.use(staticCache(appConfig.paths.dist));
|
||||||
app.use(staticCache(app.locals.paths.dist));
|
app.use(staticCache(appConfig.paths.fonts));
|
||||||
app.use(staticCache(app.locals.paths.fonts));
|
app.use(staticCache(appConfig.paths.assets));
|
||||||
app.use(staticCache(app.locals.paths.assets));
|
|
||||||
|
|
||||||
if (!ALLOW_SOCIAL_LOGIN) {
|
if (!ALLOW_SOCIAL_LOGIN) {
|
||||||
console.warn('Social logins are disabled. Set ALLOW_SOCIAL_LOGIN=true to enable them.');
|
console.warn('Social logins are disabled. Set ALLOW_SOCIAL_LOGIN=true to enable them.');
|
||||||
|
|
@ -146,7 +146,7 @@ const startServer = async () => {
|
||||||
logger.info(`Server listening at http://${host == '0.0.0.0' ? 'localhost' : host}:${port}`);
|
logger.info(`Server listening at http://${host == '0.0.0.0' ? 'localhost' : host}:${port}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeMCPs(app).then(() => checkMigrations());
|
initializeMCPs().then(() => checkMigrations());
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
const { v4 } = require('uuid');
|
const { v4 } = require('uuid');
|
||||||
const { handleAbortError } = require('~/server/middleware/abortMiddleware');
|
const { handleAbortError } = require('~/server/middleware/abortMiddleware');
|
||||||
|
const { getAppConfig } = require('~/server/services/Config/getAppConfig');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the assistant is supported or excluded
|
* Checks if the assistant is supported or excluded
|
||||||
|
|
@ -12,8 +13,9 @@ const { handleAbortError } = require('~/server/middleware/abortMiddleware');
|
||||||
const validateAssistant = async (req, res, next) => {
|
const validateAssistant = async (req, res, next) => {
|
||||||
const { endpoint, conversationId, assistant_id, messageId } = req.body;
|
const { endpoint, conversationId, assistant_id, messageId } = req.body;
|
||||||
|
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
/** @type {Partial<TAssistantEndpoint>} */
|
/** @type {Partial<TAssistantEndpoint>} */
|
||||||
const assistantsConfig = req.app.locals?.[endpoint];
|
const assistantsConfig = appConfig?.[endpoint];
|
||||||
if (!assistantsConfig) {
|
if (!assistantsConfig) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
const { SystemRoles } = require('librechat-data-provider');
|
const { SystemRoles } = require('librechat-data-provider');
|
||||||
|
const { getAppConfig } = require('~/server/services/Config/getAppConfig');
|
||||||
const { getAssistant } = require('~/models/Assistant');
|
const { getAssistant } = require('~/models/Assistant');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -20,8 +21,9 @@ const validateAuthor = async ({ req, openai, overrideEndpoint, overrideAssistant
|
||||||
const assistant_id =
|
const assistant_id =
|
||||||
overrideAssistantId ?? req.params.id ?? req.body.assistant_id ?? req.query.assistant_id;
|
overrideAssistantId ?? req.params.id ?? req.body.assistant_id ?? req.query.assistant_id;
|
||||||
|
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
/** @type {Partial<TAssistantEndpoint>} */
|
/** @type {Partial<TAssistantEndpoint>} */
|
||||||
const assistantsConfig = req.app.locals?.[endpoint];
|
const assistantsConfig = appConfig?.[endpoint];
|
||||||
if (!assistantsConfig) {
|
if (!assistantsConfig) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ const {
|
||||||
parseCompactConvo,
|
parseCompactConvo,
|
||||||
} = require('librechat-data-provider');
|
} = require('librechat-data-provider');
|
||||||
const azureAssistants = require('~/server/services/Endpoints/azureAssistants');
|
const azureAssistants = require('~/server/services/Endpoints/azureAssistants');
|
||||||
|
const { getAppConfig } = require('~/server/services/Config/getAppConfig');
|
||||||
const assistants = require('~/server/services/Endpoints/assistants');
|
const assistants = require('~/server/services/Endpoints/assistants');
|
||||||
const { processFiles } = require('~/server/services/Files/process');
|
const { processFiles } = require('~/server/services/Files/process');
|
||||||
const anthropic = require('~/server/services/Endpoints/anthropic');
|
const anthropic = require('~/server/services/Endpoints/anthropic');
|
||||||
|
|
@ -40,9 +41,10 @@ async function buildEndpointOption(req, res, next) {
|
||||||
return handleError(res, { text: 'Error parsing conversation' });
|
return handleError(res, { text: 'Error parsing conversation' });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.app.locals.modelSpecs?.list && req.app.locals.modelSpecs?.enforce) {
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
|
if (appConfig.modelSpecs?.list && appConfig.modelSpecs?.enforce) {
|
||||||
/** @type {{ list: TModelSpec[] }}*/
|
/** @type {{ list: TModelSpec[] }}*/
|
||||||
const { list } = req.app.locals.modelSpecs;
|
const { list } = appConfig.modelSpecs;
|
||||||
const { spec } = parsedBody;
|
const { spec } = parsedBody;
|
||||||
|
|
||||||
if (!spec) {
|
if (!spec) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
const cookies = require('cookie');
|
const cookies = require('cookie');
|
||||||
const jwt = require('jsonwebtoken');
|
const jwt = require('jsonwebtoken');
|
||||||
|
const { getAppConfig } = require('~/server/services/Config/getAppConfig');
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
|
||||||
const OBJECT_ID_LENGTH = 24;
|
const OBJECT_ID_LENGTH = 24;
|
||||||
|
|
@ -24,8 +25,9 @@ function isValidObjectId(id) {
|
||||||
* Middleware to validate image request.
|
* Middleware to validate image request.
|
||||||
* Must be set by `secureImageLinks` via custom config file.
|
* Must be set by `secureImageLinks` via custom config file.
|
||||||
*/
|
*/
|
||||||
function validateImageRequest(req, res, next) {
|
async function validateImageRequest(req, res, next) {
|
||||||
if (!req.app.locals.secureImageLinks) {
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
|
if (!appConfig.secureImageLinks) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ const { getOpenAIClient } = require('~/server/controllers/assistants/helpers');
|
||||||
const { updateAction, getActions, deleteAction } = require('~/models/Action');
|
const { updateAction, getActions, deleteAction } = require('~/models/Action');
|
||||||
const { updateAssistantDoc, getAssistant } = require('~/models/Assistant');
|
const { updateAssistantDoc, getAssistant } = require('~/models/Assistant');
|
||||||
const { isActionDomainAllowed } = require('~/server/services/domains');
|
const { isActionDomainAllowed } = require('~/server/services/domains');
|
||||||
|
const { getAppConfig } = require('~/server/services/Config');
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
@ -20,6 +21,7 @@ const router = express.Router();
|
||||||
* @returns {Object} 200 - success response - application/json
|
* @returns {Object} 200 - success response - application/json
|
||||||
*/
|
*/
|
||||||
router.post('/:assistant_id', async (req, res) => {
|
router.post('/:assistant_id', async (req, res) => {
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
try {
|
try {
|
||||||
const { assistant_id } = req.params;
|
const { assistant_id } = req.params;
|
||||||
|
|
||||||
|
|
@ -125,7 +127,7 @@ router.post('/:assistant_id', async (req, res) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Map Azure OpenAI model to the assistant as defined by config */
|
/* Map Azure OpenAI model to the assistant as defined by config */
|
||||||
if (req.app.locals[EModelEndpoint.azureOpenAI]?.assistants) {
|
if (appConfig[EModelEndpoint.azureOpenAI]?.assistants) {
|
||||||
updatedAssistant = {
|
updatedAssistant = {
|
||||||
...updatedAssistant,
|
...updatedAssistant,
|
||||||
model: req.body.model,
|
model: req.body.model,
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ const { isEnabled } = require('@librechat/api');
|
||||||
const { logger } = require('@librechat/data-schemas');
|
const { logger } = require('@librechat/data-schemas');
|
||||||
const { CacheKeys, defaultSocialLogins, Constants } = require('librechat-data-provider');
|
const { CacheKeys, defaultSocialLogins, Constants } = require('librechat-data-provider');
|
||||||
const { getCustomConfig } = require('~/server/services/Config/getCustomConfig');
|
const { getCustomConfig } = require('~/server/services/Config/getCustomConfig');
|
||||||
|
const { getAppConfig } = require('~/server/services/Config/getAppConfig');
|
||||||
const { getLdapConfig } = require('~/server/services/Config/ldap');
|
const { getLdapConfig } = require('~/server/services/Config/ldap');
|
||||||
const { getProjectByName } = require('~/models/Project');
|
const { getProjectByName } = require('~/models/Project');
|
||||||
const { getMCPManager } = require('~/config');
|
const { getMCPManager } = require('~/config');
|
||||||
|
|
@ -43,6 +44,8 @@ router.get('/', async function (req, res) {
|
||||||
const ldap = getLdapConfig();
|
const ldap = getLdapConfig();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
|
|
||||||
const isOpenIdEnabled =
|
const isOpenIdEnabled =
|
||||||
!!process.env.OPENID_CLIENT_ID &&
|
!!process.env.OPENID_CLIENT_ID &&
|
||||||
!!process.env.OPENID_CLIENT_SECRET &&
|
!!process.env.OPENID_CLIENT_SECRET &&
|
||||||
|
|
@ -58,7 +61,7 @@ router.get('/', async function (req, res) {
|
||||||
/** @type {TStartupConfig} */
|
/** @type {TStartupConfig} */
|
||||||
const payload = {
|
const payload = {
|
||||||
appTitle: process.env.APP_TITLE || 'LibreChat',
|
appTitle: process.env.APP_TITLE || 'LibreChat',
|
||||||
socialLogins: req.app.locals.socialLogins ?? defaultSocialLogins,
|
socialLogins: appConfig.socialLogins ?? defaultSocialLogins,
|
||||||
discordLoginEnabled: !!process.env.DISCORD_CLIENT_ID && !!process.env.DISCORD_CLIENT_SECRET,
|
discordLoginEnabled: !!process.env.DISCORD_CLIENT_ID && !!process.env.DISCORD_CLIENT_SECRET,
|
||||||
facebookLoginEnabled:
|
facebookLoginEnabled:
|
||||||
!!process.env.FACEBOOK_CLIENT_ID && !!process.env.FACEBOOK_CLIENT_SECRET,
|
!!process.env.FACEBOOK_CLIENT_ID && !!process.env.FACEBOOK_CLIENT_SECRET,
|
||||||
|
|
@ -91,10 +94,10 @@ router.get('/', async function (req, res) {
|
||||||
isEnabled(process.env.SHOW_BIRTHDAY_ICON) ||
|
isEnabled(process.env.SHOW_BIRTHDAY_ICON) ||
|
||||||
process.env.SHOW_BIRTHDAY_ICON === '',
|
process.env.SHOW_BIRTHDAY_ICON === '',
|
||||||
helpAndFaqURL: process.env.HELP_AND_FAQ_URL || 'https://librechat.ai',
|
helpAndFaqURL: process.env.HELP_AND_FAQ_URL || 'https://librechat.ai',
|
||||||
interface: req.app.locals.interfaceConfig,
|
interface: appConfig.interfaceConfig,
|
||||||
turnstile: req.app.locals.turnstileConfig,
|
turnstile: appConfig.turnstileConfig,
|
||||||
modelSpecs: req.app.locals.modelSpecs,
|
modelSpecs: appConfig.modelSpecs,
|
||||||
balance: req.app.locals.balance,
|
balance: appConfig.balance,
|
||||||
sharedLinksEnabled,
|
sharedLinksEnabled,
|
||||||
publicSharedLinksEnabled,
|
publicSharedLinksEnabled,
|
||||||
analyticsGtmId: process.env.ANALYTICS_GTM_ID,
|
analyticsGtmId: process.env.ANALYTICS_GTM_ID,
|
||||||
|
|
@ -128,8 +131,7 @@ router.get('/', async function (req, res) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @type {TCustomConfig['webSearch']} */
|
const webSearchConfig = appConfig.webSearch;
|
||||||
const webSearchConfig = req.app.locals.webSearch;
|
|
||||||
if (
|
if (
|
||||||
webSearchConfig != null &&
|
webSearchConfig != null &&
|
||||||
(webSearchConfig.searchProvider ||
|
(webSearchConfig.searchProvider ||
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,16 @@ const fs = require('fs').promises;
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
||||||
const { resizeAvatar } = require('~/server/services/Files/images/avatar');
|
const { resizeAvatar } = require('~/server/services/Files/images/avatar');
|
||||||
const { filterFile } = require('~/server/services/Files/process');
|
|
||||||
const { getFileStrategy } = require('~/server/utils/getFileStrategy');
|
const { getFileStrategy } = require('~/server/utils/getFileStrategy');
|
||||||
|
const { filterFile } = require('~/server/services/Files/process');
|
||||||
|
const { getAppConfig } = require('~/server/services/Config');
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.post('/', async (req, res) => {
|
router.post('/', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
filterFile({ req, file: req.file, image: true, isAvatar: true });
|
filterFile({ req, file: req.file, image: true, isAvatar: true });
|
||||||
const userId = req.user.id;
|
const userId = req.user.id;
|
||||||
const { manual } = req.body;
|
const { manual } = req.body;
|
||||||
|
|
@ -19,8 +21,8 @@ router.post('/', async (req, res) => {
|
||||||
throw new Error('User ID is undefined');
|
throw new Error('User ID is undefined');
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileStrategy = getFileStrategy(req.app.locals, { isAvatar: true });
|
const fileStrategy = getFileStrategy(appConfig, { isAvatar: true });
|
||||||
const desiredFormat = req.app.locals.imageOutputType;
|
const desiredFormat = appConfig.imageOutputType;
|
||||||
const resizedBuffer = await resizeAvatar({
|
const resizedBuffer = await resizeAvatar({
|
||||||
userId,
|
userId,
|
||||||
input,
|
input,
|
||||||
|
|
@ -39,7 +41,7 @@ router.post('/', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
await fs.unlink(req.file.path);
|
await fs.unlink(req.file.path);
|
||||||
logger.debug('[/files/images/avatar] Temp. image upload file deleted');
|
logger.debug('[/files/images/avatar] Temp. image upload file deleted');
|
||||||
} catch (error) {
|
} catch {
|
||||||
logger.debug('[/files/images/avatar] Temp. image upload file already deleted');
|
logger.debug('[/files/images/avatar] Temp. image upload file already deleted');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ const { loadAuthValues } = require('~/server/services/Tools/credentials');
|
||||||
const { refreshS3FileUrls } = require('~/server/services/Files/S3/crud');
|
const { refreshS3FileUrls } = require('~/server/services/Files/S3/crud');
|
||||||
const { hasAccessToFilesViaAgent } = require('~/server/services/Files');
|
const { hasAccessToFilesViaAgent } = require('~/server/services/Files');
|
||||||
const { getFiles, batchUpdateFiles } = require('~/models/File');
|
const { getFiles, batchUpdateFiles } = require('~/models/File');
|
||||||
|
const { getAppConfig } = require('~/server/services/Config');
|
||||||
const { cleanFileName } = require('~/server/utils/files');
|
const { cleanFileName } = require('~/server/utils/files');
|
||||||
const { getAssistant } = require('~/models/Assistant');
|
const { getAssistant } = require('~/models/Assistant');
|
||||||
const { getAgent } = require('~/models/Agent');
|
const { getAgent } = require('~/models/Agent');
|
||||||
|
|
@ -36,8 +37,9 @@ const router = express.Router();
|
||||||
|
|
||||||
router.get('/', async (req, res) => {
|
router.get('/', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
const files = await getFiles({ user: req.user.id });
|
const files = await getFiles({ user: req.user.id });
|
||||||
if (req.app.locals.fileStrategy === FileSources.s3) {
|
if (appConfig.fileStrategy === FileSources.s3) {
|
||||||
try {
|
try {
|
||||||
const cache = getLogStores(CacheKeys.S3_EXPIRY_INTERVAL);
|
const cache = getLogStores(CacheKeys.S3_EXPIRY_INTERVAL);
|
||||||
const alreadyChecked = await cache.get(req.user.id);
|
const alreadyChecked = await cache.get(req.user.id);
|
||||||
|
|
@ -114,7 +116,8 @@ router.get('/agent/:agent_id', async (req, res) => {
|
||||||
|
|
||||||
router.get('/config', async (req, res) => {
|
router.get('/config', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
res.status(200).json(req.app.locals.fileConfig);
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
|
res.status(200).json(appConfig.fileConfig);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[/files] Error getting fileConfig', error);
|
logger.error('[/files] Error getting fileConfig', error);
|
||||||
res.status(400).json({ message: 'Error in request', error: error.message });
|
res.status(400).json({ message: 'Error in request', error: error.message });
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,13 @@ const {
|
||||||
processImageFile,
|
processImageFile,
|
||||||
processAgentFileUpload,
|
processAgentFileUpload,
|
||||||
} = require('~/server/services/Files/process');
|
} = require('~/server/services/Files/process');
|
||||||
|
const { getAppConfig } = require('~/server/services/Config');
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.post('/', async (req, res) => {
|
router.post('/', async (req, res) => {
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
const metadata = req.body;
|
const metadata = req.body;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -30,7 +32,7 @@ router.post('/', async (req, res) => {
|
||||||
logger.error('[/files/images] Error processing file:', error);
|
logger.error('[/files/images] Error processing file:', error);
|
||||||
try {
|
try {
|
||||||
const filepath = path.join(
|
const filepath = path.join(
|
||||||
req.app.locals.paths.imageOutput,
|
appConfig.paths.imageOutput,
|
||||||
req.user.id,
|
req.user.id,
|
||||||
path.basename(req.file.filename),
|
path.basename(req.file.filename),
|
||||||
);
|
);
|
||||||
|
|
@ -43,7 +45,7 @@ router.post('/', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
await fs.unlink(req.file.path);
|
await fs.unlink(req.file.path);
|
||||||
logger.debug('[/files/images] Temp. image upload file deleted');
|
logger.debug('[/files/images] Temp. image upload file deleted');
|
||||||
} catch (error) {
|
} catch {
|
||||||
logger.debug('[/files/images] Temp. image upload file already deleted');
|
logger.debug('[/files/images] Temp. image upload file already deleted');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,12 @@ const crypto = require('crypto');
|
||||||
const multer = require('multer');
|
const multer = require('multer');
|
||||||
const { sanitizeFilename } = require('@librechat/api');
|
const { sanitizeFilename } = require('@librechat/api');
|
||||||
const { fileConfig: defaultFileConfig, mergeFileConfig } = require('librechat-data-provider');
|
const { fileConfig: defaultFileConfig, mergeFileConfig } = require('librechat-data-provider');
|
||||||
const { getCustomConfig } = require('~/server/services/Config');
|
const { getCustomConfig, getAppConfig } = require('~/server/services/Config');
|
||||||
|
|
||||||
const storage = multer.diskStorage({
|
const storage = multer.diskStorage({
|
||||||
destination: function (req, file, cb) {
|
destination: async function (req, file, cb) {
|
||||||
const outputPath = path.join(req.app.locals.paths.uploads, 'temp', req.user.id);
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
|
const outputPath = path.join(appConfig.paths.uploads, 'temp', req.user.id);
|
||||||
if (!fs.existsSync(outputPath)) {
|
if (!fs.existsSync(outputPath)) {
|
||||||
fs.mkdirSync(outputPath, { recursive: true });
|
fs.mkdirSync(outputPath, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ const {
|
||||||
deleteMemory,
|
deleteMemory,
|
||||||
setMemory,
|
setMemory,
|
||||||
} = require('~/models');
|
} = require('~/models');
|
||||||
|
const { getAppConfig } = require('~/server/services/Config');
|
||||||
const { requireJwtAuth } = require('~/server/middleware');
|
const { requireJwtAuth } = require('~/server/middleware');
|
||||||
const { getRoleByName } = require('~/models/Role');
|
const { getRoleByName } = require('~/models/Role');
|
||||||
|
|
||||||
|
|
@ -60,7 +61,8 @@ router.get('/', checkMemoryRead, async (req, res) => {
|
||||||
return sum + (memory.tokenCount || 0);
|
return sum + (memory.tokenCount || 0);
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
const memoryConfig = req.app.locals?.memory;
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
|
const memoryConfig = appConfig?.memory;
|
||||||
const tokenLimit = memoryConfig?.tokenLimit;
|
const tokenLimit = memoryConfig?.tokenLimit;
|
||||||
const charLimit = memoryConfig?.charLimit || 10000;
|
const charLimit = memoryConfig?.charLimit || 10000;
|
||||||
|
|
||||||
|
|
@ -98,7 +100,8 @@ router.post('/', memoryPayloadLimit, checkMemoryCreate, async (req, res) => {
|
||||||
return res.status(400).json({ error: 'Value is required and must be a non-empty string.' });
|
return res.status(400).json({ error: 'Value is required and must be a non-empty string.' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const memoryConfig = req.app.locals?.memory;
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
|
const memoryConfig = appConfig?.memory;
|
||||||
const charLimit = memoryConfig?.charLimit || 10000;
|
const charLimit = memoryConfig?.charLimit || 10000;
|
||||||
|
|
||||||
if (key.length > 1000) {
|
if (key.length > 1000) {
|
||||||
|
|
@ -117,6 +120,9 @@ router.post('/', memoryPayloadLimit, checkMemoryCreate, async (req, res) => {
|
||||||
const tokenCount = Tokenizer.getTokenCount(value, 'o200k_base');
|
const tokenCount = Tokenizer.getTokenCount(value, 'o200k_base');
|
||||||
|
|
||||||
const memories = await getAllUserMemories(req.user.id);
|
const memories = await getAllUserMemories(req.user.id);
|
||||||
|
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
|
const memoryConfig = appConfig?.memory;
|
||||||
const tokenLimit = memoryConfig?.tokenLimit;
|
const tokenLimit = memoryConfig?.tokenLimit;
|
||||||
|
|
||||||
if (tokenLimit) {
|
if (tokenLimit) {
|
||||||
|
|
@ -200,8 +206,8 @@ router.patch('/:key', memoryPayloadLimit, checkMemoryUpdate, async (req, res) =>
|
||||||
}
|
}
|
||||||
|
|
||||||
const newKey = bodyKey || urlKey;
|
const newKey = bodyKey || urlKey;
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
const memoryConfig = req.app.locals?.memory;
|
const memoryConfig = appConfig?.memory;
|
||||||
const charLimit = memoryConfig?.charLimit || 10000;
|
const charLimit = memoryConfig?.charLimit || 10000;
|
||||||
|
|
||||||
if (newKey.length > 1000) {
|
if (newKey.length > 1000) {
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ const { ensureDefaultCategories, seedDefaultRoles, initializeRoles } = require('
|
||||||
const { azureAssistantsDefaults, assistantsConfigSetup } = require('./start/assistants');
|
const { azureAssistantsDefaults, assistantsConfigSetup } = require('./start/assistants');
|
||||||
const { initializeAzureBlobService } = require('./Files/Azure/initialize');
|
const { initializeAzureBlobService } = require('./Files/Azure/initialize');
|
||||||
const { initializeFirebase } = require('./Files/Firebase/initialize');
|
const { initializeFirebase } = require('./Files/Firebase/initialize');
|
||||||
|
const { initializeAppConfig } = require('./Config/getAppConfig');
|
||||||
const loadCustomConfig = require('./Config/loadCustomConfig');
|
const loadCustomConfig = require('./Config/loadCustomConfig');
|
||||||
const handleRateLimits = require('./Config/handleRateLimits');
|
const handleRateLimits = require('./Config/handleRateLimits');
|
||||||
const { loadDefaultInterface } = require('./start/interface');
|
const { loadDefaultInterface } = require('./start/interface');
|
||||||
|
|
@ -35,9 +36,8 @@ const paths = require('~/config/paths');
|
||||||
/**
|
/**
|
||||||
* Loads custom config and initializes app-wide variables.
|
* Loads custom config and initializes app-wide variables.
|
||||||
* @function AppService
|
* @function AppService
|
||||||
* @param {Express.Application} app - The Express application object.
|
|
||||||
*/
|
*/
|
||||||
const AppService = async (app) => {
|
const AppService = async () => {
|
||||||
await initializeRoles();
|
await initializeRoles();
|
||||||
await seedDefaultRoles();
|
await seedDefaultRoles();
|
||||||
await ensureDefaultCategories();
|
await ensureDefaultCategories();
|
||||||
|
|
@ -109,10 +109,11 @@ const AppService = async (app) => {
|
||||||
const agentsDefaults = agentsConfigSetup(config);
|
const agentsDefaults = agentsConfigSetup(config);
|
||||||
|
|
||||||
if (!Object.keys(config).length) {
|
if (!Object.keys(config).length) {
|
||||||
app.locals = {
|
const appConfig = {
|
||||||
...defaultLocals,
|
...defaultLocals,
|
||||||
[EModelEndpoint.agents]: agentsDefaults,
|
[EModelEndpoint.agents]: agentsDefaults,
|
||||||
};
|
};
|
||||||
|
await initializeAppConfig(appConfig);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -167,13 +168,15 @@ const AppService = async (app) => {
|
||||||
endpointLocals.all = endpoints.all;
|
endpointLocals.all = endpoints.all;
|
||||||
}
|
}
|
||||||
|
|
||||||
app.locals = {
|
const appConfig = {
|
||||||
...defaultLocals,
|
...defaultLocals,
|
||||||
fileConfig: config?.fileConfig,
|
fileConfig: config?.fileConfig,
|
||||||
secureImageLinks: config?.secureImageLinks,
|
secureImageLinks: config?.secureImageLinks,
|
||||||
modelSpecs: processModelSpecs(endpoints, config.modelSpecs, interfaceConfig),
|
modelSpecs: processModelSpecs(endpoints, config.modelSpecs, interfaceConfig),
|
||||||
...endpointLocals,
|
...endpointLocals,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
await initializeAppConfig(appConfig);
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = AppService;
|
module.exports = AppService;
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ const { retrieveAndProcessFile } = require('~/server/services/Files/process');
|
||||||
const { processRequiredActions } = require('~/server/services/ToolService');
|
const { processRequiredActions } = require('~/server/services/ToolService');
|
||||||
const { RunManager, waitForRun } = require('~/server/services/Runs');
|
const { RunManager, waitForRun } = require('~/server/services/Runs');
|
||||||
const { processMessages } = require('~/server/services/Threads');
|
const { processMessages } = require('~/server/services/Threads');
|
||||||
|
const { getAppConfig } = require('~/server/services/Config');
|
||||||
const { createOnProgress } = require('~/server/utils');
|
const { createOnProgress } = require('~/server/utils');
|
||||||
const { TextStream } = require('~/app/clients');
|
const { TextStream } = require('~/app/clients');
|
||||||
|
|
||||||
|
|
@ -350,6 +351,7 @@ async function runAssistant({
|
||||||
accumulatedMessages = [],
|
accumulatedMessages = [],
|
||||||
in_progress: inProgress,
|
in_progress: inProgress,
|
||||||
}) {
|
}) {
|
||||||
|
const appConfig = await getAppConfig({ role: openai.req.user?.role });
|
||||||
let steps = accumulatedSteps;
|
let steps = accumulatedSteps;
|
||||||
let messages = accumulatedMessages;
|
let messages = accumulatedMessages;
|
||||||
const in_progress = inProgress ?? createInProgressHandler(openai, thread_id, messages);
|
const in_progress = inProgress ?? createInProgressHandler(openai, thread_id, messages);
|
||||||
|
|
@ -397,7 +399,7 @@ async function runAssistant({
|
||||||
|
|
||||||
const { endpoint = EModelEndpoint.azureAssistants } = openai.req.body;
|
const { endpoint = EModelEndpoint.azureAssistants } = openai.req.body;
|
||||||
/** @type {TCustomConfig.endpoints.assistants} */
|
/** @type {TCustomConfig.endpoints.assistants} */
|
||||||
const assistantsEndpointConfig = openai.req.app.locals?.[endpoint] ?? {};
|
const assistantsEndpointConfig = appConfig?.[endpoint] ?? {};
|
||||||
const { pollIntervalMs, timeoutMs } = assistantsEndpointConfig;
|
const { pollIntervalMs, timeoutMs } = assistantsEndpointConfig;
|
||||||
|
|
||||||
const run = await waitForRun({
|
const run = await waitForRun({
|
||||||
|
|
|
||||||
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 loadDefaultEndpointsConfig = require('./loadDefaultEConfig');
|
||||||
const loadConfigEndpoints = require('./loadConfigEndpoints');
|
const loadConfigEndpoints = require('./loadConfigEndpoints');
|
||||||
const getLogStores = require('~/cache/getLogStores');
|
const getLogStores = require('~/cache/getLogStores');
|
||||||
|
const { getAppConfig } = require('./getAppConfig');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|
@ -23,12 +24,13 @@ async function getEndpointsConfig(req) {
|
||||||
|
|
||||||
const defaultEndpointsConfig = await loadDefaultEndpointsConfig(req);
|
const defaultEndpointsConfig = await loadDefaultEndpointsConfig(req);
|
||||||
const customConfigEndpoints = await loadConfigEndpoints(req);
|
const customConfigEndpoints = await loadConfigEndpoints(req);
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
|
|
||||||
/** @type {TEndpointsConfig} */
|
/** @type {TEndpointsConfig} */
|
||||||
const mergedConfig = { ...defaultEndpointsConfig, ...customConfigEndpoints };
|
const mergedConfig = { ...defaultEndpointsConfig, ...customConfigEndpoints };
|
||||||
if (mergedConfig[EModelEndpoint.assistants] && req.app.locals?.[EModelEndpoint.assistants]) {
|
if (mergedConfig[EModelEndpoint.assistants] && appConfig?.[EModelEndpoint.assistants]) {
|
||||||
const { disableBuilder, retrievalModels, capabilities, version, ..._rest } =
|
const { disableBuilder, retrievalModels, capabilities, version, ..._rest } =
|
||||||
req.app.locals[EModelEndpoint.assistants];
|
appConfig[EModelEndpoint.assistants];
|
||||||
|
|
||||||
mergedConfig[EModelEndpoint.assistants] = {
|
mergedConfig[EModelEndpoint.assistants] = {
|
||||||
...mergedConfig[EModelEndpoint.assistants],
|
...mergedConfig[EModelEndpoint.assistants],
|
||||||
|
|
@ -38,9 +40,9 @@ async function getEndpointsConfig(req) {
|
||||||
capabilities,
|
capabilities,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (mergedConfig[EModelEndpoint.agents] && req.app.locals?.[EModelEndpoint.agents]) {
|
if (mergedConfig[EModelEndpoint.agents] && appConfig?.[EModelEndpoint.agents]) {
|
||||||
const { disableBuilder, capabilities, allowedProviders, ..._rest } =
|
const { disableBuilder, capabilities, allowedProviders, ..._rest } =
|
||||||
req.app.locals[EModelEndpoint.agents];
|
appConfig[EModelEndpoint.agents];
|
||||||
|
|
||||||
mergedConfig[EModelEndpoint.agents] = {
|
mergedConfig[EModelEndpoint.agents] = {
|
||||||
...mergedConfig[EModelEndpoint.agents],
|
...mergedConfig[EModelEndpoint.agents],
|
||||||
|
|
@ -50,12 +52,9 @@ async function getEndpointsConfig(req) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (mergedConfig[EModelEndpoint.azureAssistants] && appConfig?.[EModelEndpoint.azureAssistants]) {
|
||||||
mergedConfig[EModelEndpoint.azureAssistants] &&
|
|
||||||
req.app.locals?.[EModelEndpoint.azureAssistants]
|
|
||||||
) {
|
|
||||||
const { disableBuilder, retrievalModels, capabilities, version, ..._rest } =
|
const { disableBuilder, retrievalModels, capabilities, version, ..._rest } =
|
||||||
req.app.locals[EModelEndpoint.azureAssistants];
|
appConfig[EModelEndpoint.azureAssistants];
|
||||||
|
|
||||||
mergedConfig[EModelEndpoint.azureAssistants] = {
|
mergedConfig[EModelEndpoint.azureAssistants] = {
|
||||||
...mergedConfig[EModelEndpoint.azureAssistants],
|
...mergedConfig[EModelEndpoint.azureAssistants],
|
||||||
|
|
@ -66,8 +65,8 @@ async function getEndpointsConfig(req) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mergedConfig[EModelEndpoint.bedrock] && req.app.locals?.[EModelEndpoint.bedrock]) {
|
if (mergedConfig[EModelEndpoint.bedrock] && appConfig?.[EModelEndpoint.bedrock]) {
|
||||||
const { availableRegions } = req.app.locals[EModelEndpoint.bedrock];
|
const { availableRegions } = appConfig[EModelEndpoint.bedrock];
|
||||||
mergedConfig[EModelEndpoint.bedrock] = {
|
mergedConfig[EModelEndpoint.bedrock] = {
|
||||||
...mergedConfig[EModelEndpoint.bedrock],
|
...mergedConfig[EModelEndpoint.bedrock],
|
||||||
availableRegions,
|
availableRegions,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
const getAppConfig = require('./getAppConfig');
|
||||||
const { config } = require('./EndpointService');
|
const { config } = require('./EndpointService');
|
||||||
const getCachedTools = require('./getCachedTools');
|
const getCachedTools = require('./getCachedTools');
|
||||||
const getCustomConfig = require('./getCustomConfig');
|
const getCustomConfig = require('./getCustomConfig');
|
||||||
|
|
@ -15,6 +16,7 @@ module.exports = {
|
||||||
loadDefaultModels,
|
loadDefaultModels,
|
||||||
loadOverrideConfig,
|
loadOverrideConfig,
|
||||||
loadAsyncEndpoints,
|
loadAsyncEndpoints,
|
||||||
|
...getAppConfig,
|
||||||
...getCachedTools,
|
...getCachedTools,
|
||||||
...getCustomConfig,
|
...getCustomConfig,
|
||||||
...getEndpointsConfig,
|
...getEndpointsConfig,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ const path = require('path');
|
||||||
const { logger } = require('@librechat/data-schemas');
|
const { logger } = require('@librechat/data-schemas');
|
||||||
const { loadServiceKey, isUserProvided } = require('@librechat/api');
|
const { loadServiceKey, isUserProvided } = require('@librechat/api');
|
||||||
const { EModelEndpoint } = require('librechat-data-provider');
|
const { EModelEndpoint } = require('librechat-data-provider');
|
||||||
|
const { getAppConfig } = require('./getAppConfig');
|
||||||
const { config } = require('./EndpointService');
|
const { config } = require('./EndpointService');
|
||||||
|
|
||||||
const { openAIApiKey, azureOpenAIApiKey, useAzurePlugins, userProvidedOpenAI, googleKey } = config;
|
const { openAIApiKey, azureOpenAIApiKey, useAzurePlugins, userProvidedOpenAI, googleKey } = config;
|
||||||
|
|
@ -11,6 +12,7 @@ const { openAIApiKey, azureOpenAIApiKey, useAzurePlugins, userProvidedOpenAI, go
|
||||||
* @param {Express.Request} req - The request object
|
* @param {Express.Request} req - The request object
|
||||||
*/
|
*/
|
||||||
async function loadAsyncEndpoints(req) {
|
async function loadAsyncEndpoints(req) {
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
let serviceKey, googleUserProvides;
|
let serviceKey, googleUserProvides;
|
||||||
|
|
||||||
/** Check if GOOGLE_KEY is provided at all(including 'user_provided') */
|
/** Check if GOOGLE_KEY is provided at all(including 'user_provided') */
|
||||||
|
|
@ -34,7 +36,7 @@ async function loadAsyncEndpoints(req) {
|
||||||
|
|
||||||
const google = serviceKey || isGoogleKeyProvided ? { userProvide: googleUserProvides } : false;
|
const google = serviceKey || isGoogleKeyProvided ? { userProvide: googleUserProvides } : false;
|
||||||
|
|
||||||
const useAzure = req.app.locals[EModelEndpoint.azureOpenAI]?.plugins;
|
const useAzure = appConfig[EModelEndpoint.azureOpenAI]?.plugins;
|
||||||
const gptPlugins =
|
const gptPlugins =
|
||||||
useAzure || openAIApiKey || azureOpenAIApiKey
|
useAzure || openAIApiKey || azureOpenAIApiKey
|
||||||
? {
|
? {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
const { EModelEndpoint, extractEnvVariable } = require('librechat-data-provider');
|
const { EModelEndpoint, extractEnvVariable } = require('librechat-data-provider');
|
||||||
const { isUserProvided, normalizeEndpointName } = require('~/server/utils');
|
const { isUserProvided, normalizeEndpointName } = require('~/server/utils');
|
||||||
const { getCustomConfig } = require('./getCustomConfig');
|
const { getCustomConfig } = require('./getCustomConfig');
|
||||||
|
const { getAppConfig } = require('./getAppConfig');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load config endpoints from the cached configuration object
|
* Load config endpoints from the cached configuration object
|
||||||
|
|
@ -14,6 +15,8 @@ async function loadConfigEndpoints(req) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
|
|
||||||
const { endpoints = {} } = customConfig ?? {};
|
const { endpoints = {} } = customConfig ?? {};
|
||||||
const endpointsConfig = {};
|
const endpointsConfig = {};
|
||||||
|
|
||||||
|
|
@ -53,14 +56,14 @@ async function loadConfigEndpoints(req) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.app.locals[EModelEndpoint.azureOpenAI]) {
|
if (appConfig[EModelEndpoint.azureOpenAI]) {
|
||||||
/** @type {Omit<TConfig, 'order'>} */
|
/** @type {Omit<TConfig, 'order'>} */
|
||||||
endpointsConfig[EModelEndpoint.azureOpenAI] = {
|
endpointsConfig[EModelEndpoint.azureOpenAI] = {
|
||||||
userProvide: false,
|
userProvide: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.app.locals[EModelEndpoint.azureOpenAI]?.assistants) {
|
if (appConfig[EModelEndpoint.azureOpenAI]?.assistants) {
|
||||||
/** @type {Omit<TConfig, 'order'>} */
|
/** @type {Omit<TConfig, 'order'>} */
|
||||||
endpointsConfig[EModelEndpoint.azureAssistants] = {
|
endpointsConfig[EModelEndpoint.azureAssistants] = {
|
||||||
userProvide: false,
|
userProvide: false,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ const { EModelEndpoint, extractEnvVariable } = require('librechat-data-provider'
|
||||||
const { isUserProvided, normalizeEndpointName } = require('~/server/utils');
|
const { isUserProvided, normalizeEndpointName } = require('~/server/utils');
|
||||||
const { fetchModels } = require('~/server/services/ModelService');
|
const { fetchModels } = require('~/server/services/ModelService');
|
||||||
const { getCustomConfig } = require('./getCustomConfig');
|
const { getCustomConfig } = require('./getCustomConfig');
|
||||||
|
const { getAppConfig } = require('./getAppConfig');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load config endpoints from the cached configuration object
|
* Load config endpoints from the cached configuration object
|
||||||
|
|
@ -15,10 +16,11 @@ async function loadConfigModels(req) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
const { endpoints = {} } = customConfig ?? {};
|
const { endpoints = {} } = customConfig ?? {};
|
||||||
const modelsConfig = {};
|
const modelsConfig = {};
|
||||||
const azureEndpoint = endpoints[EModelEndpoint.azureOpenAI];
|
const azureEndpoint = endpoints[EModelEndpoint.azureOpenAI];
|
||||||
const azureConfig = req.app.locals[EModelEndpoint.azureOpenAI];
|
const azureConfig = appConfig[EModelEndpoint.azureOpenAI];
|
||||||
const { modelNames } = azureConfig ?? {};
|
const { modelNames } = azureConfig ?? {};
|
||||||
|
|
||||||
if (modelNames && azureEndpoint) {
|
if (modelNames && azureEndpoint) {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ const generateArtifactsPrompt = require('~/app/clients/prompts/artifacts');
|
||||||
const { getProviderConfig } = require('~/server/services/Endpoints');
|
const { getProviderConfig } = require('~/server/services/Endpoints');
|
||||||
const { processFiles } = require('~/server/services/Files/process');
|
const { processFiles } = require('~/server/services/Files/process');
|
||||||
const { getFiles, getToolFilesByIds } = require('~/models/File');
|
const { getFiles, getToolFilesByIds } = require('~/models/File');
|
||||||
|
const { getAppConfig } = require('~/server/services/Config');
|
||||||
const { getConvoFiles } = require('~/models/Conversation');
|
const { getConvoFiles } = require('~/models/Conversation');
|
||||||
const { getModelMaxTokens } = require('~/utils');
|
const { getModelMaxTokens } = require('~/utils');
|
||||||
|
|
||||||
|
|
@ -43,6 +44,7 @@ const initializeAgent = async ({
|
||||||
allowedProviders,
|
allowedProviders,
|
||||||
isInitialAgent = false,
|
isInitialAgent = false,
|
||||||
}) => {
|
}) => {
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
if (
|
if (
|
||||||
isAgentsEndpoint(endpointOption?.endpoint) &&
|
isAgentsEndpoint(endpointOption?.endpoint) &&
|
||||||
allowedProviders.size > 0 &&
|
allowedProviders.size > 0 &&
|
||||||
|
|
@ -84,10 +86,11 @@ const initializeAgent = async ({
|
||||||
const { attachments, tool_resources } = await primeResources({
|
const { attachments, tool_resources } = await primeResources({
|
||||||
req,
|
req,
|
||||||
getFiles,
|
getFiles,
|
||||||
|
appConfig,
|
||||||
|
agentId: agent.id,
|
||||||
attachments: currentFiles,
|
attachments: currentFiles,
|
||||||
tool_resources: agent.tool_resources,
|
tool_resources: agent.tool_resources,
|
||||||
requestFileSet: new Set(requestFiles?.map((file) => file.file_id)),
|
requestFileSet: new Set(requestFiles?.map((file) => file.file_id)),
|
||||||
agentId: agent.id,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const provider = agent.provider;
|
const provider = agent.provider;
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,9 @@ const {
|
||||||
createToolEndCallback,
|
createToolEndCallback,
|
||||||
getDefaultHandlers,
|
getDefaultHandlers,
|
||||||
} = require('~/server/controllers/agents/callbacks');
|
} = require('~/server/controllers/agents/callbacks');
|
||||||
|
const { getCustomEndpointConfig, getAppConfig } = require('~/server/services/Config');
|
||||||
const { initializeAgent } = require('~/server/services/Endpoints/agents/agent');
|
const { initializeAgent } = require('~/server/services/Endpoints/agents/agent');
|
||||||
const { getModelsConfig } = require('~/server/controllers/ModelController');
|
const { getModelsConfig } = require('~/server/controllers/ModelController');
|
||||||
const { getCustomEndpointConfig } = require('~/server/services/Config');
|
|
||||||
const { loadAgentTools } = require('~/server/services/ToolService');
|
const { loadAgentTools } = require('~/server/services/ToolService');
|
||||||
const AgentClient = require('~/server/controllers/agents/client');
|
const AgentClient = require('~/server/controllers/agents/client');
|
||||||
const { getAgent } = require('~/models/Agent');
|
const { getAgent } = require('~/models/Agent');
|
||||||
|
|
@ -50,6 +50,7 @@ const initializeClient = async ({ req, res, endpointOption }) => {
|
||||||
if (!endpointOption) {
|
if (!endpointOption) {
|
||||||
throw new Error('Endpoint option not provided');
|
throw new Error('Endpoint option not provided');
|
||||||
}
|
}
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
|
|
||||||
// TODO: use endpointOption to determine options/modelOptions
|
// TODO: use endpointOption to determine options/modelOptions
|
||||||
/** @type {Array<UsageMetadata>} */
|
/** @type {Array<UsageMetadata>} */
|
||||||
|
|
@ -90,7 +91,7 @@ const initializeClient = async ({ req, res, endpointOption }) => {
|
||||||
|
|
||||||
const agentConfigs = new Map();
|
const agentConfigs = new Map();
|
||||||
/** @type {Set<string>} */
|
/** @type {Set<string>} */
|
||||||
const allowedProviders = new Set(req?.app?.locals?.[EModelEndpoint.agents]?.allowedProviders);
|
const allowedProviders = new Set(appConfig?.[EModelEndpoint.agents]?.allowedProviders);
|
||||||
|
|
||||||
const loadTools = createToolLoader();
|
const loadTools = createToolLoader();
|
||||||
/** @type {Array<MongoFile>} */
|
/** @type {Array<MongoFile>} */
|
||||||
|
|
@ -144,7 +145,7 @@ const initializeClient = async ({ req, res, endpointOption }) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let endpointConfig = req.app.locals[primaryConfig.endpoint];
|
let endpointConfig = appConfig[primaryConfig.endpoint];
|
||||||
if (!isAgentsEndpoint(primaryConfig.endpoint) && !endpointConfig) {
|
if (!isAgentsEndpoint(primaryConfig.endpoint) && !endpointConfig) {
|
||||||
try {
|
try {
|
||||||
endpointConfig = await getCustomEndpointConfig(primaryConfig.endpoint);
|
endpointConfig = await getCustomEndpointConfig(primaryConfig.endpoint);
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,10 @@ const { EModelEndpoint } = require('librechat-data-provider');
|
||||||
const { getUserKey, checkUserKeyExpiry } = require('~/server/services/UserService');
|
const { getUserKey, checkUserKeyExpiry } = require('~/server/services/UserService');
|
||||||
const { getLLMConfig } = require('~/server/services/Endpoints/anthropic/llm');
|
const { getLLMConfig } = require('~/server/services/Endpoints/anthropic/llm');
|
||||||
const AnthropicClient = require('~/app/clients/AnthropicClient');
|
const AnthropicClient = require('~/app/clients/AnthropicClient');
|
||||||
|
const { getAppConfig } = require('~/server/services/Config');
|
||||||
|
|
||||||
const initializeClient = async ({ req, res, endpointOption, overrideModel, optionsOnly }) => {
|
const initializeClient = async ({ req, res, endpointOption, overrideModel, optionsOnly }) => {
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
const { ANTHROPIC_API_KEY, ANTHROPIC_REVERSE_PROXY, PROXY } = process.env;
|
const { ANTHROPIC_API_KEY, ANTHROPIC_REVERSE_PROXY, PROXY } = process.env;
|
||||||
const expiresAt = req.body.key;
|
const expiresAt = req.body.key;
|
||||||
const isUserProvided = ANTHROPIC_API_KEY === 'user_provided';
|
const isUserProvided = ANTHROPIC_API_KEY === 'user_provided';
|
||||||
|
|
@ -23,7 +25,7 @@ const initializeClient = async ({ req, res, endpointOption, overrideModel, optio
|
||||||
let clientOptions = {};
|
let clientOptions = {};
|
||||||
|
|
||||||
/** @type {undefined | TBaseEndpoint} */
|
/** @type {undefined | TBaseEndpoint} */
|
||||||
const anthropicConfig = req.app.locals[EModelEndpoint.anthropic];
|
const anthropicConfig = appConfig[EModelEndpoint.anthropic];
|
||||||
|
|
||||||
if (anthropicConfig) {
|
if (anthropicConfig) {
|
||||||
clientOptions.streamRate = anthropicConfig.streamRate;
|
clientOptions.streamRate = anthropicConfig.streamRate;
|
||||||
|
|
@ -31,7 +33,7 @@ const initializeClient = async ({ req, res, endpointOption, overrideModel, optio
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @type {undefined | TBaseEndpoint} */
|
/** @type {undefined | TBaseEndpoint} */
|
||||||
const allConfig = req.app.locals.all;
|
const allConfig = appConfig.all;
|
||||||
if (allConfig) {
|
if (allConfig) {
|
||||||
clientOptions.streamRate = allConfig.streamRate;
|
clientOptions.streamRate = allConfig.streamRate;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ const {
|
||||||
getUserKeyValues,
|
getUserKeyValues,
|
||||||
getUserKeyExpiry,
|
getUserKeyExpiry,
|
||||||
} = require('~/server/services/UserService');
|
} = require('~/server/services/UserService');
|
||||||
|
const { getAppConfig } = require('~/server/services/Config');
|
||||||
const OAIClient = require('~/app/clients/OpenAIClient');
|
const OAIClient = require('~/app/clients/OpenAIClient');
|
||||||
|
|
||||||
class Files {
|
class Files {
|
||||||
|
|
@ -48,6 +49,7 @@ class Files {
|
||||||
}
|
}
|
||||||
|
|
||||||
const initializeClient = async ({ req, res, version, endpointOption, initAppClient = false }) => {
|
const initializeClient = async ({ req, res, version, endpointOption, initAppClient = false }) => {
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
const { PROXY, OPENAI_ORGANIZATION, AZURE_ASSISTANTS_API_KEY, AZURE_ASSISTANTS_BASE_URL } =
|
const { PROXY, OPENAI_ORGANIZATION, AZURE_ASSISTANTS_API_KEY, AZURE_ASSISTANTS_BASE_URL } =
|
||||||
process.env;
|
process.env;
|
||||||
|
|
||||||
|
|
@ -81,7 +83,7 @@ const initializeClient = async ({ req, res, version, endpointOption, initAppClie
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @type {TAzureConfig | undefined} */
|
/** @type {TAzureConfig | undefined} */
|
||||||
const azureConfig = req.app.locals[EModelEndpoint.azureOpenAI];
|
const azureConfig = appConfig[EModelEndpoint.azureOpenAI];
|
||||||
|
|
||||||
/** @type {AzureOptions | undefined} */
|
/** @type {AzureOptions | undefined} */
|
||||||
let azureOptions;
|
let azureOptions;
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,10 @@ const {
|
||||||
removeNullishValues,
|
removeNullishValues,
|
||||||
} = require('librechat-data-provider');
|
} = require('librechat-data-provider');
|
||||||
const { getUserKey, checkUserKeyExpiry } = require('~/server/services/UserService');
|
const { getUserKey, checkUserKeyExpiry } = require('~/server/services/UserService');
|
||||||
|
const { getAppConfig } = require('~/server/services/Config');
|
||||||
|
|
||||||
const getOptions = async ({ req, overrideModel, endpointOption }) => {
|
const getOptions = async ({ req, overrideModel, endpointOption }) => {
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
const {
|
const {
|
||||||
BEDROCK_AWS_SECRET_ACCESS_KEY,
|
BEDROCK_AWS_SECRET_ACCESS_KEY,
|
||||||
BEDROCK_AWS_ACCESS_KEY_ID,
|
BEDROCK_AWS_ACCESS_KEY_ID,
|
||||||
|
|
@ -50,14 +52,14 @@ const getOptions = async ({ req, overrideModel, endpointOption }) => {
|
||||||
let streamRate = Constants.DEFAULT_STREAM_RATE;
|
let streamRate = Constants.DEFAULT_STREAM_RATE;
|
||||||
|
|
||||||
/** @type {undefined | TBaseEndpoint} */
|
/** @type {undefined | TBaseEndpoint} */
|
||||||
const bedrockConfig = req.app.locals[EModelEndpoint.bedrock];
|
const bedrockConfig = appConfig[EModelEndpoint.bedrock];
|
||||||
|
|
||||||
if (bedrockConfig && bedrockConfig.streamRate) {
|
if (bedrockConfig && bedrockConfig.streamRate) {
|
||||||
streamRate = bedrockConfig.streamRate;
|
streamRate = bedrockConfig.streamRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @type {undefined | TBaseEndpoint} */
|
/** @type {undefined | TBaseEndpoint} */
|
||||||
const allConfig = req.app.locals.all;
|
const allConfig = appConfig.all;
|
||||||
if (allConfig && allConfig.streamRate) {
|
if (allConfig && allConfig.streamRate) {
|
||||||
streamRate = allConfig.streamRate;
|
streamRate = allConfig.streamRate;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ const {
|
||||||
const { Providers } = require('@librechat/agents');
|
const { Providers } = require('@librechat/agents');
|
||||||
const { getOpenAIConfig, createHandleLLMNewToken, resolveHeaders } = require('@librechat/api');
|
const { getOpenAIConfig, createHandleLLMNewToken, resolveHeaders } = require('@librechat/api');
|
||||||
const { getUserKeyValues, checkUserKeyExpiry } = require('~/server/services/UserService');
|
const { getUserKeyValues, checkUserKeyExpiry } = require('~/server/services/UserService');
|
||||||
const { getCustomEndpointConfig } = require('~/server/services/Config');
|
const { getCustomEndpointConfig, getAppConfig } = require('~/server/services/Config');
|
||||||
const { fetchModels } = require('~/server/services/ModelService');
|
const { fetchModels } = require('~/server/services/ModelService');
|
||||||
const OpenAIClient = require('~/app/clients/OpenAIClient');
|
const OpenAIClient = require('~/app/clients/OpenAIClient');
|
||||||
const { isUserProvided } = require('~/server/utils');
|
const { isUserProvided } = require('~/server/utils');
|
||||||
|
|
@ -17,6 +17,7 @@ const getLogStores = require('~/cache/getLogStores');
|
||||||
const { PROXY } = process.env;
|
const { PROXY } = process.env;
|
||||||
|
|
||||||
const initializeClient = async ({ req, res, endpointOption, optionsOnly, overrideEndpoint }) => {
|
const initializeClient = async ({ req, res, endpointOption, optionsOnly, overrideEndpoint }) => {
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
const { key: expiresAt } = req.body;
|
const { key: expiresAt } = req.body;
|
||||||
const endpoint = overrideEndpoint ?? req.body.endpoint;
|
const endpoint = overrideEndpoint ?? req.body.endpoint;
|
||||||
|
|
||||||
|
|
@ -118,7 +119,7 @@ const initializeClient = async ({ req, res, endpointOption, optionsOnly, overrid
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @type {undefined | TBaseEndpoint} */
|
/** @type {undefined | TBaseEndpoint} */
|
||||||
const allConfig = req.app.locals.all;
|
const allConfig = appConfig.all;
|
||||||
if (allConfig) {
|
if (allConfig) {
|
||||||
customOptions.streamRate = allConfig.streamRate;
|
customOptions.streamRate = allConfig.streamRate;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ const path = require('path');
|
||||||
const { EModelEndpoint, AuthKeys } = require('librechat-data-provider');
|
const { EModelEndpoint, AuthKeys } = require('librechat-data-provider');
|
||||||
const { getGoogleConfig, isEnabled, loadServiceKey } = require('@librechat/api');
|
const { getGoogleConfig, isEnabled, loadServiceKey } = require('@librechat/api');
|
||||||
const { getUserKey, checkUserKeyExpiry } = require('~/server/services/UserService');
|
const { getUserKey, checkUserKeyExpiry } = require('~/server/services/UserService');
|
||||||
|
const { getAppConfig } = require('~/server/services/Config');
|
||||||
const { GoogleClient } = require('~/app');
|
const { GoogleClient } = require('~/app');
|
||||||
|
|
||||||
const initializeClient = async ({ req, res, endpointOption, overrideModel, optionsOnly }) => {
|
const initializeClient = async ({ req, res, endpointOption, overrideModel, optionsOnly }) => {
|
||||||
|
|
@ -46,10 +47,11 @@ const initializeClient = async ({ req, res, endpointOption, overrideModel, optio
|
||||||
|
|
||||||
let clientOptions = {};
|
let clientOptions = {};
|
||||||
|
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
/** @type {undefined | TBaseEndpoint} */
|
/** @type {undefined | TBaseEndpoint} */
|
||||||
const allConfig = req.app.locals.all;
|
const allConfig = appConfig.all;
|
||||||
/** @type {undefined | TBaseEndpoint} */
|
/** @type {undefined | TBaseEndpoint} */
|
||||||
const googleConfig = req.app.locals[EModelEndpoint.google];
|
const googleConfig = appConfig[EModelEndpoint.google];
|
||||||
|
|
||||||
if (googleConfig) {
|
if (googleConfig) {
|
||||||
clientOptions.streamRate = googleConfig.streamRate;
|
clientOptions.streamRate = googleConfig.streamRate;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
|
const { isEnabled } = require('@librechat/api');
|
||||||
const { EModelEndpoint, CacheKeys, Constants, googleSettings } = require('librechat-data-provider');
|
const { EModelEndpoint, CacheKeys, Constants, googleSettings } = require('librechat-data-provider');
|
||||||
|
const { getAppConfig } = require('~/server/services/Config');
|
||||||
const getLogStores = require('~/cache/getLogStores');
|
const getLogStores = require('~/cache/getLogStores');
|
||||||
const initializeClient = require('./initialize');
|
const initializeClient = require('./initialize');
|
||||||
const { isEnabled } = require('~/server/utils');
|
|
||||||
const { saveConvo } = require('~/models');
|
const { saveConvo } = require('~/models');
|
||||||
|
|
||||||
const addTitle = async (req, { text, response, client }) => {
|
const addTitle = async (req, { text, response, client }) => {
|
||||||
|
|
@ -14,7 +15,8 @@ const addTitle = async (req, { text, response, client }) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { GOOGLE_TITLE_MODEL } = process.env ?? {};
|
const { GOOGLE_TITLE_MODEL } = process.env ?? {};
|
||||||
const providerConfig = req.app.locals[EModelEndpoint.google];
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
|
const providerConfig = appConfig[EModelEndpoint.google];
|
||||||
let model =
|
let model =
|
||||||
providerConfig?.titleModel ??
|
providerConfig?.titleModel ??
|
||||||
GOOGLE_TITLE_MODEL ??
|
GOOGLE_TITLE_MODEL ??
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ const {
|
||||||
createHandleLLMNewToken,
|
createHandleLLMNewToken,
|
||||||
} = require('@librechat/api');
|
} = require('@librechat/api');
|
||||||
const { getUserKeyValues, checkUserKeyExpiry } = require('~/server/services/UserService');
|
const { getUserKeyValues, checkUserKeyExpiry } = require('~/server/services/UserService');
|
||||||
|
const { getAppConfig } = require('~/server/services/Config');
|
||||||
const OpenAIClient = require('~/app/clients/OpenAIClient');
|
const OpenAIClient = require('~/app/clients/OpenAIClient');
|
||||||
|
|
||||||
const initializeClient = async ({
|
const initializeClient = async ({
|
||||||
|
|
@ -18,6 +19,7 @@ const initializeClient = async ({
|
||||||
overrideEndpoint,
|
overrideEndpoint,
|
||||||
overrideModel,
|
overrideModel,
|
||||||
}) => {
|
}) => {
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
const {
|
const {
|
||||||
PROXY,
|
PROXY,
|
||||||
OPENAI_API_KEY,
|
OPENAI_API_KEY,
|
||||||
|
|
@ -64,7 +66,7 @@ const initializeClient = async ({
|
||||||
|
|
||||||
const isAzureOpenAI = endpoint === EModelEndpoint.azureOpenAI;
|
const isAzureOpenAI = endpoint === EModelEndpoint.azureOpenAI;
|
||||||
/** @type {false | TAzureConfig} */
|
/** @type {false | TAzureConfig} */
|
||||||
const azureConfig = isAzureOpenAI && req.app.locals[EModelEndpoint.azureOpenAI];
|
const azureConfig = isAzureOpenAI && appConfig[EModelEndpoint.azureOpenAI];
|
||||||
let serverless = false;
|
let serverless = false;
|
||||||
if (isAzureOpenAI && azureConfig) {
|
if (isAzureOpenAI && azureConfig) {
|
||||||
const { modelGroupMap, groupMap } = azureConfig;
|
const { modelGroupMap, groupMap } = azureConfig;
|
||||||
|
|
@ -113,7 +115,7 @@ const initializeClient = async ({
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @type {undefined | TBaseEndpoint} */
|
/** @type {undefined | TBaseEndpoint} */
|
||||||
const openAIConfig = req.app.locals[EModelEndpoint.openAI];
|
const openAIConfig = appConfig[EModelEndpoint.openAI];
|
||||||
|
|
||||||
if (!isAzureOpenAI && openAIConfig) {
|
if (!isAzureOpenAI && openAIConfig) {
|
||||||
clientOptions.streamRate = openAIConfig.streamRate;
|
clientOptions.streamRate = openAIConfig.streamRate;
|
||||||
|
|
@ -121,7 +123,7 @@ const initializeClient = async ({
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @type {undefined | TBaseEndpoint} */
|
/** @type {undefined | TBaseEndpoint} */
|
||||||
const allConfig = req.app.locals.all;
|
const allConfig = appConfig.all;
|
||||||
if (allConfig) {
|
if (allConfig) {
|
||||||
clientOptions.streamRate = allConfig.streamRate;
|
clientOptions.streamRate = allConfig.streamRate;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ const path = require('path');
|
||||||
const sharp = require('sharp');
|
const sharp = require('sharp');
|
||||||
const { logger } = require('@librechat/data-schemas');
|
const { logger } = require('@librechat/data-schemas');
|
||||||
const { resizeImageBuffer } = require('../images/resize');
|
const { resizeImageBuffer } = require('../images/resize');
|
||||||
|
const { getAppConfig } = require('~/server/services/Config');
|
||||||
const { updateUser, updateFile } = require('~/models');
|
const { updateUser, updateFile } = require('~/models');
|
||||||
const { saveBufferToAzure } = require('./crud');
|
const { saveBufferToAzure } = require('./crud');
|
||||||
|
|
||||||
|
|
@ -30,6 +31,7 @@ async function uploadImageToAzure({
|
||||||
containerName,
|
containerName,
|
||||||
}) {
|
}) {
|
||||||
try {
|
try {
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
const inputFilePath = file.path;
|
const inputFilePath = file.path;
|
||||||
const inputBuffer = await fs.promises.readFile(inputFilePath);
|
const inputBuffer = await fs.promises.readFile(inputFilePath);
|
||||||
const {
|
const {
|
||||||
|
|
@ -41,12 +43,12 @@ async function uploadImageToAzure({
|
||||||
const userId = req.user.id;
|
const userId = req.user.id;
|
||||||
let webPBuffer;
|
let webPBuffer;
|
||||||
let fileName = `${file_id}__${path.basename(inputFilePath)}`;
|
let fileName = `${file_id}__${path.basename(inputFilePath)}`;
|
||||||
const targetExtension = `.${req.app.locals.imageOutputType}`;
|
const targetExtension = `.${appConfig.imageOutputType}`;
|
||||||
|
|
||||||
if (extension.toLowerCase() === targetExtension) {
|
if (extension.toLowerCase() === targetExtension) {
|
||||||
webPBuffer = resizedBuffer;
|
webPBuffer = resizedBuffer;
|
||||||
} else {
|
} else {
|
||||||
webPBuffer = await sharp(resizedBuffer).toFormat(req.app.locals.imageOutputType).toBuffer();
|
webPBuffer = await sharp(resizedBuffer).toFormat(appConfig.imageOutputType).toBuffer();
|
||||||
const extRegExp = new RegExp(path.extname(fileName) + '$');
|
const extRegExp = new RegExp(path.extname(fileName) + '$');
|
||||||
fileName = fileName.replace(extRegExp, targetExtension);
|
fileName = fileName.replace(extRegExp, targetExtension);
|
||||||
if (!path.extname(fileName)) {
|
if (!path.extname(fileName)) {
|
||||||
|
|
|
||||||
|
|
@ -43,8 +43,7 @@ async function getCodeOutputDownloadStream(fileIdentifier, apiKey) {
|
||||||
/**
|
/**
|
||||||
* Uploads a file to the Code Environment server.
|
* Uploads a file to the Code Environment server.
|
||||||
* @param {Object} params - The params object.
|
* @param {Object} params - The params object.
|
||||||
* @param {ServerRequest} params.req - The request object from Express. It should have a `user` property with an `id`
|
* @param {ServerRequest} params.req - The request object from Express. It should have a `user` property with an `id` representing the user
|
||||||
* representing the user, and an `app.locals.paths` object with an `uploads` path.
|
|
||||||
* @param {import('fs').ReadStream | import('stream').Readable} params.stream - The read stream for the file.
|
* @param {import('fs').ReadStream | import('stream').Readable} params.stream - The read stream for the file.
|
||||||
* @param {string} params.filename - The name of the file.
|
* @param {string} params.filename - The name of the file.
|
||||||
* @param {string} params.apiKey - The API key for authentication.
|
* @param {string} params.apiKey - The API key for authentication.
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ const { filterFilesByAgentAccess } = require('~/server/services/Files/permission
|
||||||
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
||||||
const { convertImage } = require('~/server/services/Files/images/convert');
|
const { convertImage } = require('~/server/services/Files/images/convert');
|
||||||
const { createFile, getFiles, updateFile } = require('~/models/File');
|
const { createFile, getFiles, updateFile } = require('~/models/File');
|
||||||
|
const { getAppConfig } = require('~/server/services/Config');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process OpenAI image files, convert to target format, save and return file metadata.
|
* Process OpenAI image files, convert to target format, save and return file metadata.
|
||||||
|
|
@ -38,6 +39,7 @@ const processCodeOutput = async ({
|
||||||
messageId,
|
messageId,
|
||||||
session_id,
|
session_id,
|
||||||
}) => {
|
}) => {
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
const currentDate = new Date();
|
const currentDate = new Date();
|
||||||
const baseURL = getCodeBaseURL();
|
const baseURL = getCodeBaseURL();
|
||||||
const fileExt = path.extname(name);
|
const fileExt = path.extname(name);
|
||||||
|
|
@ -77,10 +79,10 @@ const processCodeOutput = async ({
|
||||||
filename: name,
|
filename: name,
|
||||||
conversationId,
|
conversationId,
|
||||||
user: req.user.id,
|
user: req.user.id,
|
||||||
type: `image/${req.app.locals.imageOutputType}`,
|
type: `image/${appConfig.imageOutputType}`,
|
||||||
createdAt: formattedDate,
|
createdAt: formattedDate,
|
||||||
updatedAt: formattedDate,
|
updatedAt: formattedDate,
|
||||||
source: req.app.locals.fileStrategy,
|
source: appConfig.fileStrategy,
|
||||||
context: FileContext.execute_code,
|
context: FileContext.execute_code,
|
||||||
};
|
};
|
||||||
createFile(file, true);
|
createFile(file, true);
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const sharp = require('sharp');
|
const sharp = require('sharp');
|
||||||
const { logger } = require('@librechat/data-schemas');
|
const { logger } = require('@librechat/data-schemas');
|
||||||
|
const { getAppConfig } = require('~/server/services/Config');
|
||||||
const { resizeImageBuffer } = require('../images/resize');
|
const { resizeImageBuffer } = require('../images/resize');
|
||||||
const { updateUser, updateFile } = require('~/models');
|
const { updateUser, updateFile } = require('~/models');
|
||||||
const { saveBufferToFirebase } = require('./crud');
|
const { saveBufferToFirebase } = require('./crud');
|
||||||
|
|
@ -11,8 +12,7 @@ const { saveBufferToFirebase } = require('./crud');
|
||||||
* resolution.
|
* resolution.
|
||||||
*
|
*
|
||||||
* @param {Object} params - The params object.
|
* @param {Object} params - The params object.
|
||||||
* @param {Express.Request} params.req - The request object from Express. It should have a `user` property with an `id`
|
* @param {Express.Request} params.req - The request object from Express. It should have a `user` property with an `id` representing the user
|
||||||
* representing the user, and an `app.locals.paths` object with an `imageOutput` path.
|
|
||||||
* @param {Express.Multer.File} params.file - The file object, which is part of the request. The file object should
|
* @param {Express.Multer.File} params.file - The file object, which is part of the request. The file object should
|
||||||
* have a `path` property that points to the location of the uploaded file.
|
* have a `path` property that points to the location of the uploaded file.
|
||||||
* @param {EModelEndpoint} params.endpoint - The params object.
|
* @param {EModelEndpoint} params.endpoint - The params object.
|
||||||
|
|
@ -26,6 +26,7 @@ const { saveBufferToFirebase } = require('./crud');
|
||||||
* - height: The height of the converted image.
|
* - height: The height of the converted image.
|
||||||
*/
|
*/
|
||||||
async function uploadImageToFirebase({ req, file, file_id, endpoint, resolution = 'high' }) {
|
async function uploadImageToFirebase({ req, file, file_id, endpoint, resolution = 'high' }) {
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
const inputFilePath = file.path;
|
const inputFilePath = file.path;
|
||||||
const inputBuffer = await fs.promises.readFile(inputFilePath);
|
const inputBuffer = await fs.promises.readFile(inputFilePath);
|
||||||
const {
|
const {
|
||||||
|
|
@ -38,11 +39,11 @@ async function uploadImageToFirebase({ req, file, file_id, endpoint, resolution
|
||||||
|
|
||||||
let webPBuffer;
|
let webPBuffer;
|
||||||
let fileName = `${file_id}__${path.basename(inputFilePath)}`;
|
let fileName = `${file_id}__${path.basename(inputFilePath)}`;
|
||||||
const targetExtension = `.${req.app.locals.imageOutputType}`;
|
const targetExtension = `.${appConfig.imageOutputType}`;
|
||||||
if (extension.toLowerCase() === targetExtension) {
|
if (extension.toLowerCase() === targetExtension) {
|
||||||
webPBuffer = resizedBuffer;
|
webPBuffer = resizedBuffer;
|
||||||
} else {
|
} else {
|
||||||
webPBuffer = await sharp(resizedBuffer).toFormat(req.app.locals.imageOutputType).toBuffer();
|
webPBuffer = await sharp(resizedBuffer).toFormat(appConfig.imageOutputType).toBuffer();
|
||||||
// Replace or append the correct extension
|
// Replace or append the correct extension
|
||||||
const extRegExp = new RegExp(path.extname(fileName) + '$');
|
const extRegExp = new RegExp(path.extname(fileName) + '$');
|
||||||
fileName = fileName.replace(extRegExp, targetExtension);
|
fileName = fileName.replace(extRegExp, targetExtension);
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ const axios = require('axios');
|
||||||
const { logger } = require('@librechat/data-schemas');
|
const { logger } = require('@librechat/data-schemas');
|
||||||
const { EModelEndpoint } = require('librechat-data-provider');
|
const { EModelEndpoint } = require('librechat-data-provider');
|
||||||
const { generateShortLivedToken } = require('~/server/services/AuthService');
|
const { generateShortLivedToken } = require('~/server/services/AuthService');
|
||||||
|
const { getAppConfig } = require('~/server/services/Config');
|
||||||
const { getBufferMetadata } = require('~/server/utils');
|
const { getBufferMetadata } = require('~/server/utils');
|
||||||
const paths = require('~/config/paths');
|
const paths = require('~/config/paths');
|
||||||
|
|
||||||
|
|
@ -45,7 +46,8 @@ async function saveLocalFile(file, outputPath, outputFilename) {
|
||||||
* @throws Will throw an error if the image saving process fails.
|
* @throws Will throw an error if the image saving process fails.
|
||||||
*/
|
*/
|
||||||
const saveLocalImage = async (req, file, filename) => {
|
const saveLocalImage = async (req, file, filename) => {
|
||||||
const imagePath = req.app.locals.paths.imageOutput;
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
|
const imagePath = appConfig.paths.imageOutput;
|
||||||
const outputPath = path.join(imagePath, req.user.id ?? '');
|
const outputPath = path.join(imagePath, req.user.id ?? '');
|
||||||
await saveLocalFile(file, outputPath, filename);
|
await saveLocalFile(file, outputPath, filename);
|
||||||
};
|
};
|
||||||
|
|
@ -191,8 +193,7 @@ const unlinkFile = async (filepath) => {
|
||||||
* Deletes a file from the filesystem. This function takes a file object, constructs the full path, and
|
* Deletes a file from the filesystem. This function takes a file object, constructs the full path, and
|
||||||
* verifies the path's validity before deleting the file. If the path is invalid, an error is thrown.
|
* verifies the path's validity before deleting the file. If the path is invalid, an error is thrown.
|
||||||
*
|
*
|
||||||
* @param {Express.Request} req - The request object from Express. It should have an `app.locals.paths` object with
|
* @param {Express.Request} req - The request object from Express.
|
||||||
* a `publicPath` property.
|
|
||||||
* @param {MongoFile} file - The file object to be deleted. It should have a `filepath` property that is
|
* @param {MongoFile} file - The file object to be deleted. It should have a `filepath` property that is
|
||||||
* a string representing the path of the file relative to the publicPath.
|
* a string representing the path of the file relative to the publicPath.
|
||||||
*
|
*
|
||||||
|
|
@ -201,7 +202,8 @@ const unlinkFile = async (filepath) => {
|
||||||
* file path is invalid or if there is an error in deletion.
|
* file path is invalid or if there is an error in deletion.
|
||||||
*/
|
*/
|
||||||
const deleteLocalFile = async (req, file) => {
|
const deleteLocalFile = async (req, file) => {
|
||||||
const { publicPath, uploads } = req.app.locals.paths;
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
|
const { publicPath, uploads } = appConfig.paths;
|
||||||
|
|
||||||
/** Filepath stripped of query parameters (e.g., ?manual=true) */
|
/** Filepath stripped of query parameters (e.g., ?manual=true) */
|
||||||
const cleanFilepath = file.filepath.split('?')[0];
|
const cleanFilepath = file.filepath.split('?')[0];
|
||||||
|
|
@ -256,8 +258,7 @@ const deleteLocalFile = async (req, file) => {
|
||||||
* Uploads a file to the specified upload directory.
|
* Uploads a file to the specified upload directory.
|
||||||
*
|
*
|
||||||
* @param {Object} params - The params object.
|
* @param {Object} params - The params object.
|
||||||
* @param {ServerRequest} params.req - The request object from Express. It should have a `user` property with an `id`
|
* @param {ServerRequest} params.req - The request object from Express. It should have a `user` property with an `id` representing the user
|
||||||
* representing the user, and an `app.locals.paths` object with an `uploads` path.
|
|
||||||
* @param {Express.Multer.File} params.file - The file object, which is part of the request. The file object should
|
* @param {Express.Multer.File} params.file - The file object, which is part of the request. The file object should
|
||||||
* have a `path` property that points to the location of the uploaded file.
|
* have a `path` property that points to the location of the uploaded file.
|
||||||
* @param {string} params.file_id - The file ID.
|
* @param {string} params.file_id - The file ID.
|
||||||
|
|
@ -268,11 +269,12 @@ const deleteLocalFile = async (req, file) => {
|
||||||
* - bytes: The size of the file in bytes.
|
* - bytes: The size of the file in bytes.
|
||||||
*/
|
*/
|
||||||
async function uploadLocalFile({ req, file, file_id }) {
|
async function uploadLocalFile({ req, file, file_id }) {
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
const inputFilePath = file.path;
|
const inputFilePath = file.path;
|
||||||
const inputBuffer = await fs.promises.readFile(inputFilePath);
|
const inputBuffer = await fs.promises.readFile(inputFilePath);
|
||||||
const bytes = Buffer.byteLength(inputBuffer);
|
const bytes = Buffer.byteLength(inputBuffer);
|
||||||
|
|
||||||
const { uploads } = req.app.locals.paths;
|
const { uploads } = appConfig.paths;
|
||||||
const userPath = path.join(uploads, req.user.id);
|
const userPath = path.join(uploads, req.user.id);
|
||||||
|
|
||||||
if (!fs.existsSync(userPath)) {
|
if (!fs.existsSync(userPath)) {
|
||||||
|
|
@ -295,8 +297,9 @@ async function uploadLocalFile({ req, file, file_id }) {
|
||||||
* @param {string} filepath - The filepath.
|
* @param {string} filepath - The filepath.
|
||||||
* @returns {ReadableStream} A readable stream of the file.
|
* @returns {ReadableStream} A readable stream of the file.
|
||||||
*/
|
*/
|
||||||
function getLocalFileStream(req, filepath) {
|
async function getLocalFileStream(req, filepath) {
|
||||||
try {
|
try {
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
if (filepath.includes('/uploads/')) {
|
if (filepath.includes('/uploads/')) {
|
||||||
const basePath = filepath.split('/uploads/')[1];
|
const basePath = filepath.split('/uploads/')[1];
|
||||||
|
|
||||||
|
|
@ -305,8 +308,8 @@ function getLocalFileStream(req, filepath) {
|
||||||
throw new Error(`Invalid file path: ${filepath}`);
|
throw new Error(`Invalid file path: ${filepath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const fullPath = path.join(req.app.locals.paths.uploads, basePath);
|
const fullPath = path.join(appConfig.paths.uploads, basePath);
|
||||||
const uploadsDir = req.app.locals.paths.uploads;
|
const uploadsDir = appConfig.paths.uploads;
|
||||||
|
|
||||||
const rel = path.relative(uploadsDir, fullPath);
|
const rel = path.relative(uploadsDir, fullPath);
|
||||||
if (rel.startsWith('..') || path.isAbsolute(rel) || rel.includes(`..${path.sep}`)) {
|
if (rel.startsWith('..') || path.isAbsolute(rel) || rel.includes(`..${path.sep}`)) {
|
||||||
|
|
@ -323,8 +326,8 @@ function getLocalFileStream(req, filepath) {
|
||||||
throw new Error(`Invalid file path: ${filepath}`);
|
throw new Error(`Invalid file path: ${filepath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const fullPath = path.join(req.app.locals.paths.imageOutput, basePath);
|
const fullPath = path.join(appConfig.paths.imageOutput, basePath);
|
||||||
const publicDir = req.app.locals.paths.imageOutput;
|
const publicDir = appConfig.paths.imageOutput;
|
||||||
|
|
||||||
const rel = path.relative(publicDir, fullPath);
|
const rel = path.relative(publicDir, fullPath);
|
||||||
if (rel.startsWith('..') || path.isAbsolute(rel) || rel.includes(`..${path.sep}`)) {
|
if (rel.startsWith('..') || path.isAbsolute(rel) || rel.includes(`..${path.sep}`)) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const sharp = require('sharp');
|
const sharp = require('sharp');
|
||||||
|
const { getAppConfig } = require('~/server/services/Config');
|
||||||
const { resizeImageBuffer } = require('../images/resize');
|
const { resizeImageBuffer } = require('../images/resize');
|
||||||
const { updateUser, updateFile } = require('~/models');
|
const { updateUser, updateFile } = require('~/models');
|
||||||
|
|
||||||
|
|
@ -13,8 +14,7 @@ const { updateUser, updateFile } = require('~/models');
|
||||||
*
|
*
|
||||||
* The original image is deleted after conversion.
|
* The original image is deleted after conversion.
|
||||||
* @param {Object} params - The params object.
|
* @param {Object} params - The params object.
|
||||||
* @param {Object} params.req - The request object from Express. It should have a `user` property with an `id`
|
* @param {Object} params.req - The request object from Express. It should have a `user` property with an `id` representing the user
|
||||||
* representing the user, and an `app.locals.paths` object with an `imageOutput` path.
|
|
||||||
* @param {Express.Multer.File} params.file - The file object, which is part of the request. The file object should
|
* @param {Express.Multer.File} params.file - The file object, which is part of the request. The file object should
|
||||||
* have a `path` property that points to the location of the uploaded file.
|
* have a `path` property that points to the location of the uploaded file.
|
||||||
* @param {string} params.file_id - The file ID.
|
* @param {string} params.file_id - The file ID.
|
||||||
|
|
@ -29,6 +29,7 @@ const { updateUser, updateFile } = require('~/models');
|
||||||
* - height: The height of the converted image.
|
* - height: The height of the converted image.
|
||||||
*/
|
*/
|
||||||
async function uploadLocalImage({ req, file, file_id, endpoint, resolution = 'high' }) {
|
async function uploadLocalImage({ req, file, file_id, endpoint, resolution = 'high' }) {
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
const inputFilePath = file.path;
|
const inputFilePath = file.path;
|
||||||
const inputBuffer = await fs.promises.readFile(inputFilePath);
|
const inputBuffer = await fs.promises.readFile(inputFilePath);
|
||||||
const {
|
const {
|
||||||
|
|
@ -38,7 +39,7 @@ async function uploadLocalImage({ req, file, file_id, endpoint, resolution = 'hi
|
||||||
} = await resizeImageBuffer(inputBuffer, resolution, endpoint);
|
} = await resizeImageBuffer(inputBuffer, resolution, endpoint);
|
||||||
const extension = path.extname(inputFilePath);
|
const extension = path.extname(inputFilePath);
|
||||||
|
|
||||||
const { imageOutput } = req.app.locals.paths;
|
const { imageOutput } = appConfig.paths;
|
||||||
const userPath = path.join(imageOutput, req.user.id);
|
const userPath = path.join(imageOutput, req.user.id);
|
||||||
|
|
||||||
if (!fs.existsSync(userPath)) {
|
if (!fs.existsSync(userPath)) {
|
||||||
|
|
@ -47,7 +48,7 @@ async function uploadLocalImage({ req, file, file_id, endpoint, resolution = 'hi
|
||||||
|
|
||||||
const fileName = `${file_id}__${path.basename(inputFilePath)}`;
|
const fileName = `${file_id}__${path.basename(inputFilePath)}`;
|
||||||
const newPath = path.join(userPath, fileName);
|
const newPath = path.join(userPath, fileName);
|
||||||
const targetExtension = `.${req.app.locals.imageOutputType}`;
|
const targetExtension = `.${appConfig.imageOutputType}`;
|
||||||
|
|
||||||
if (extension.toLowerCase() === targetExtension) {
|
if (extension.toLowerCase() === targetExtension) {
|
||||||
const bytes = Buffer.byteLength(resizedBuffer);
|
const bytes = Buffer.byteLength(resizedBuffer);
|
||||||
|
|
@ -57,7 +58,7 @@ async function uploadLocalImage({ req, file, file_id, endpoint, resolution = 'hi
|
||||||
}
|
}
|
||||||
|
|
||||||
const outputFilePath = newPath.replace(extension, targetExtension);
|
const outputFilePath = newPath.replace(extension, targetExtension);
|
||||||
const data = await sharp(resizedBuffer).toFormat(req.app.locals.imageOutputType).toBuffer();
|
const data = await sharp(resizedBuffer).toFormat(appConfig.imageOutputType).toBuffer();
|
||||||
await fs.promises.writeFile(outputFilePath, data);
|
await fs.promises.writeFile(outputFilePath, data);
|
||||||
const bytes = Buffer.byteLength(data);
|
const bytes = Buffer.byteLength(data);
|
||||||
const filepath = path.posix.join('/', 'images', req.user.id, path.basename(outputFilePath));
|
const filepath = path.posix.join('/', 'images', req.user.id, path.basename(outputFilePath));
|
||||||
|
|
@ -90,7 +91,8 @@ function encodeImage(imagePath) {
|
||||||
* @returns {Promise<[MongoFile, string]>} - A promise that resolves to an array of results from updateFile and encodeImage.
|
* @returns {Promise<[MongoFile, string]>} - A promise that resolves to an array of results from updateFile and encodeImage.
|
||||||
*/
|
*/
|
||||||
async function prepareImagesLocal(req, file) {
|
async function prepareImagesLocal(req, file) {
|
||||||
const { publicPath, imageOutput } = req.app.locals.paths;
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
|
const { publicPath, imageOutput } = appConfig.paths;
|
||||||
const userPath = path.join(imageOutput, req.user.id);
|
const userPath = path.join(imageOutput, req.user.id);
|
||||||
|
|
||||||
if (!fs.existsSync(userPath)) {
|
if (!fs.existsSync(userPath)) {
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,7 @@ const { logger } = require('~/config');
|
||||||
* Uploads a file that can be used across various OpenAI services.
|
* Uploads a file that can be used across various OpenAI services.
|
||||||
*
|
*
|
||||||
* @param {Object} params - The params object.
|
* @param {Object} params - The params object.
|
||||||
* @param {ServerRequest} params.req - The request object from Express. It should have a `user` property with an `id`
|
* @param {ServerRequest} params.req - The request object from Express. It should have a `user` property with an `id` representing the user
|
||||||
* representing the user, and an `app.locals.paths` object with an `imageOutput` path.
|
|
||||||
* @param {Express.Multer.File} params.file - The file uploaded to the server via multer.
|
* @param {Express.Multer.File} params.file - The file uploaded to the server via multer.
|
||||||
* @param {OpenAIClient} params.openai - The initialized OpenAI client.
|
* @param {OpenAIClient} params.openai - The initialized OpenAI client.
|
||||||
* @returns {Promise<OpenAIFile>}
|
* @returns {Promise<OpenAIFile>}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const sharp = require('sharp');
|
const sharp = require('sharp');
|
||||||
const { logger } = require('@librechat/data-schemas');
|
const { logger } = require('@librechat/data-schemas');
|
||||||
|
const { getAppConfig } = require('~/server/services/Config');
|
||||||
const { resizeImageBuffer } = require('../images/resize');
|
const { resizeImageBuffer } = require('../images/resize');
|
||||||
const { updateUser, updateFile } = require('~/models');
|
const { updateUser, updateFile } = require('~/models');
|
||||||
const { saveBufferToS3 } = require('./crud');
|
const { saveBufferToS3 } = require('./crud');
|
||||||
|
|
@ -12,7 +13,7 @@ const defaultBasePath = 'images';
|
||||||
* Resizes, converts, and uploads an image file to S3.
|
* Resizes, converts, and uploads an image file to S3.
|
||||||
*
|
*
|
||||||
* @param {Object} params
|
* @param {Object} params
|
||||||
* @param {import('express').Request} params.req - Express request (expects user and app.locals.imageOutputType).
|
* @param {import('express').Request} params.req - Express request (expects `user` and `appConfig.imageOutputType`).
|
||||||
* @param {Express.Multer.File} params.file - File object from Multer.
|
* @param {Express.Multer.File} params.file - File object from Multer.
|
||||||
* @param {string} params.file_id - Unique file identifier.
|
* @param {string} params.file_id - Unique file identifier.
|
||||||
* @param {any} params.endpoint - Endpoint identifier used in image processing.
|
* @param {any} params.endpoint - Endpoint identifier used in image processing.
|
||||||
|
|
@ -29,6 +30,7 @@ async function uploadImageToS3({
|
||||||
basePath = defaultBasePath,
|
basePath = defaultBasePath,
|
||||||
}) {
|
}) {
|
||||||
try {
|
try {
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
const inputFilePath = file.path;
|
const inputFilePath = file.path;
|
||||||
const inputBuffer = await fs.promises.readFile(inputFilePath);
|
const inputBuffer = await fs.promises.readFile(inputFilePath);
|
||||||
const {
|
const {
|
||||||
|
|
@ -41,14 +43,12 @@ async function uploadImageToS3({
|
||||||
|
|
||||||
let processedBuffer;
|
let processedBuffer;
|
||||||
let fileName = `${file_id}__${path.basename(inputFilePath)}`;
|
let fileName = `${file_id}__${path.basename(inputFilePath)}`;
|
||||||
const targetExtension = `.${req.app.locals.imageOutputType}`;
|
const targetExtension = `.${appConfig.imageOutputType}`;
|
||||||
|
|
||||||
if (extension.toLowerCase() === targetExtension) {
|
if (extension.toLowerCase() === targetExtension) {
|
||||||
processedBuffer = resizedBuffer;
|
processedBuffer = resizedBuffer;
|
||||||
} else {
|
} else {
|
||||||
processedBuffer = await sharp(resizedBuffer)
|
processedBuffer = await sharp(resizedBuffer).toFormat(appConfig.imageOutputType).toBuffer();
|
||||||
.toFormat(req.app.locals.imageOutputType)
|
|
||||||
.toBuffer();
|
|
||||||
fileName = fileName.replace(new RegExp(path.extname(fileName) + '$'), targetExtension);
|
fileName = fileName.replace(new RegExp(path.extname(fileName) + '$'), targetExtension);
|
||||||
if (!path.extname(fileName)) {
|
if (!path.extname(fileName)) {
|
||||||
fileName += targetExtension;
|
fileName += targetExtension;
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,7 @@ const { generateShortLivedToken } = require('~/server/services/AuthService');
|
||||||
* Deletes a file from the vector database. This function takes a file object, constructs the full path, and
|
* Deletes a file from the vector database. This function takes a file object, constructs the full path, and
|
||||||
* verifies the path's validity before deleting the file. If the path is invalid, an error is thrown.
|
* verifies the path's validity before deleting the file. If the path is invalid, an error is thrown.
|
||||||
*
|
*
|
||||||
* @param {ServerRequest} req - The request object from Express. It should have an `app.locals.paths` object with
|
* @param {ServerRequest} req - The request object from Express.
|
||||||
* a `publicPath` property.
|
|
||||||
* @param {MongoFile} file - The file object to be deleted. It should have a `filepath` property that is
|
* @param {MongoFile} file - The file object to be deleted. It should have a `filepath` property that is
|
||||||
* a string representing the path of the file relative to the publicPath.
|
* a string representing the path of the file relative to the publicPath.
|
||||||
*
|
*
|
||||||
|
|
@ -54,8 +53,7 @@ const deleteVectors = async (req, file) => {
|
||||||
* Uploads a file to the configured Vector database
|
* Uploads a file to the configured Vector database
|
||||||
*
|
*
|
||||||
* @param {Object} params - The params object.
|
* @param {Object} params - The params object.
|
||||||
* @param {Object} params.req - The request object from Express. It should have a `user` property with an `id`
|
* @param {Object} params.req - The request object from Express. It should have a `user` property with an `id` representing the user
|
||||||
* representing the user, and an `app.locals.paths` object with an `uploads` path.
|
|
||||||
* @param {Express.Multer.File} params.file - The file object, which is part of the request. The file object should
|
* @param {Express.Multer.File} params.file - The file object, which is part of the request. The file object should
|
||||||
* have a `path` property that points to the location of the uploaded file.
|
* have a `path` property that points to the location of the uploaded file.
|
||||||
* @param {string} params.file_id - The file ID.
|
* @param {string} params.file_id - The file ID.
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const sharp = require('sharp');
|
const sharp = require('sharp');
|
||||||
const { resizeImageBuffer } = require('./resize');
|
const { getAppConfig } = require('~/server/services/Config');
|
||||||
const { getStrategyFunctions } = require('../strategies');
|
const { getStrategyFunctions } = require('../strategies');
|
||||||
|
const { resizeImageBuffer } = require('./resize');
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -17,6 +18,7 @@ const { logger } = require('~/config');
|
||||||
*/
|
*/
|
||||||
async function convertImage(req, file, resolution = 'high', basename = '') {
|
async function convertImage(req, file, resolution = 'high', basename = '') {
|
||||||
try {
|
try {
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
let inputBuffer;
|
let inputBuffer;
|
||||||
let outputBuffer;
|
let outputBuffer;
|
||||||
let extension = path.extname(file.path ?? basename).toLowerCase();
|
let extension = path.extname(file.path ?? basename).toLowerCase();
|
||||||
|
|
@ -39,11 +41,11 @@ async function convertImage(req, file, resolution = 'high', basename = '') {
|
||||||
} = await resizeImageBuffer(inputBuffer, resolution);
|
} = await resizeImageBuffer(inputBuffer, resolution);
|
||||||
|
|
||||||
// Check if the file is already in target format; if it isn't, convert it:
|
// Check if the file is already in target format; if it isn't, convert it:
|
||||||
const targetExtension = `.${req.app.locals.imageOutputType}`;
|
const targetExtension = `.${appConfig.imageOutputType}`;
|
||||||
if (extension === targetExtension) {
|
if (extension === targetExtension) {
|
||||||
outputBuffer = resizedBuffer;
|
outputBuffer = resizedBuffer;
|
||||||
} else {
|
} else {
|
||||||
outputBuffer = await sharp(resizedBuffer).toFormat(req.app.locals.imageOutputType).toBuffer();
|
outputBuffer = await sharp(resizedBuffer).toFormat(appConfig.imageOutputType).toBuffer();
|
||||||
extension = targetExtension;
|
extension = targetExtension;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,7 +53,7 @@ async function convertImage(req, file, resolution = 'high', basename = '') {
|
||||||
const newFileName =
|
const newFileName =
|
||||||
path.basename(file.path ?? basename, path.extname(file.path ?? basename)) + extension;
|
path.basename(file.path ?? basename, path.extname(file.path ?? basename)) + extension;
|
||||||
|
|
||||||
const { saveBuffer } = getStrategyFunctions(req.app.locals.fileStrategy);
|
const { saveBuffer } = getStrategyFunctions(appConfig.fileStrategy);
|
||||||
|
|
||||||
const savedFilePath = await saveBuffer({
|
const savedFilePath = await saveBuffer({
|
||||||
userId: req.user.id,
|
userId: req.user.id,
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,8 @@ const {
|
||||||
removeNullishValues,
|
removeNullishValues,
|
||||||
isAssistantsEndpoint,
|
isAssistantsEndpoint,
|
||||||
} = require('librechat-data-provider');
|
} = require('librechat-data-provider');
|
||||||
const { sanitizeFilename } = require('@librechat/api');
|
|
||||||
const { EnvVar } = require('@librechat/agents');
|
const { EnvVar } = require('@librechat/agents');
|
||||||
|
const { sanitizeFilename } = require('@librechat/api');
|
||||||
const {
|
const {
|
||||||
convertImage,
|
convertImage,
|
||||||
resizeAndConvert,
|
resizeAndConvert,
|
||||||
|
|
@ -27,11 +27,11 @@ const { addResourceFileId, deleteResourceFileId } = require('~/server/controller
|
||||||
const { addAgentResourceFile, removeAgentResourceFiles } = require('~/models/Agent');
|
const { addAgentResourceFile, removeAgentResourceFiles } = require('~/models/Agent');
|
||||||
const { getOpenAIClient } = require('~/server/controllers/assistants/helpers');
|
const { getOpenAIClient } = require('~/server/controllers/assistants/helpers');
|
||||||
const { createFile, updateFileUsage, deleteFiles } = require('~/models/File');
|
const { createFile, updateFileUsage, deleteFiles } = require('~/models/File');
|
||||||
|
const { checkCapability, getAppConfig } = require('~/server/services/Config');
|
||||||
const { loadAuthValues } = require('~/server/services/Tools/credentials');
|
const { loadAuthValues } = require('~/server/services/Tools/credentials');
|
||||||
const { checkCapability } = require('~/server/services/Config');
|
const { getFileStrategy } = require('~/server/utils/getFileStrategy');
|
||||||
const { LB_QueueAsyncCall } = require('~/server/utils/queue');
|
const { LB_QueueAsyncCall } = require('~/server/utils/queue');
|
||||||
const { getStrategyFunctions } = require('./strategies');
|
const { getStrategyFunctions } = require('./strategies');
|
||||||
const { getFileStrategy } = require('~/server/utils/getFileStrategy');
|
|
||||||
const { determineFileType } = require('~/server/utils');
|
const { determineFileType } = require('~/server/utils');
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
|
||||||
|
|
@ -157,6 +157,7 @@ function enqueueDeleteOperation({ req, file, deleteFile, promises, resolvedFileI
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
const processDeleteRequest = async ({ req, files }) => {
|
const processDeleteRequest = async ({ req, files }) => {
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
const resolvedFileIds = [];
|
const resolvedFileIds = [];
|
||||||
const deletionMethods = {};
|
const deletionMethods = {};
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
|
@ -164,7 +165,7 @@ const processDeleteRequest = async ({ req, files }) => {
|
||||||
/** @type {Record<string, OpenAI | undefined>} */
|
/** @type {Record<string, OpenAI | undefined>} */
|
||||||
const client = { [FileSources.openai]: undefined, [FileSources.azure]: undefined };
|
const client = { [FileSources.openai]: undefined, [FileSources.azure]: undefined };
|
||||||
const initializeClients = async () => {
|
const initializeClients = async () => {
|
||||||
if (req.app.locals[EModelEndpoint.assistants]) {
|
if (appConfig[EModelEndpoint.assistants]) {
|
||||||
const openAIClient = await getOpenAIClient({
|
const openAIClient = await getOpenAIClient({
|
||||||
req,
|
req,
|
||||||
overrideEndpoint: EModelEndpoint.assistants,
|
overrideEndpoint: EModelEndpoint.assistants,
|
||||||
|
|
@ -172,7 +173,7 @@ const processDeleteRequest = async ({ req, files }) => {
|
||||||
client[FileSources.openai] = openAIClient.openai;
|
client[FileSources.openai] = openAIClient.openai;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!req.app.locals[EModelEndpoint.azureOpenAI]?.assistants) {
|
if (!appConfig[EModelEndpoint.azureOpenAI]?.assistants) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -320,7 +321,8 @@ const processFileURL = async ({ fileStrategy, userId, URL, fileName, basePath, c
|
||||||
*/
|
*/
|
||||||
const processImageFile = async ({ req, res, metadata, returnFile = false }) => {
|
const processImageFile = async ({ req, res, metadata, returnFile = false }) => {
|
||||||
const { file } = req;
|
const { file } = req;
|
||||||
const source = getFileStrategy(req.app.locals, { isImage: true });
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
|
const source = getFileStrategy(appConfig, { isImage: true });
|
||||||
const { handleImageUpload } = getStrategyFunctions(source);
|
const { handleImageUpload } = getStrategyFunctions(source);
|
||||||
const { file_id, temp_file_id, endpoint } = metadata;
|
const { file_id, temp_file_id, endpoint } = metadata;
|
||||||
|
|
||||||
|
|
@ -341,7 +343,7 @@ const processImageFile = async ({ req, res, metadata, returnFile = false }) => {
|
||||||
filename: file.originalname,
|
filename: file.originalname,
|
||||||
context: FileContext.message_attachment,
|
context: FileContext.message_attachment,
|
||||||
source,
|
source,
|
||||||
type: `image/${req.app.locals.imageOutputType}`,
|
type: `image/${appConfig.imageOutputType}`,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
},
|
},
|
||||||
|
|
@ -366,18 +368,19 @@ const processImageFile = async ({ req, res, metadata, returnFile = false }) => {
|
||||||
* @returns {Promise<{ filepath: string, filename: string, source: string, type: string}>}
|
* @returns {Promise<{ filepath: string, filename: string, source: string, type: string}>}
|
||||||
*/
|
*/
|
||||||
const uploadImageBuffer = async ({ req, context, metadata = {}, resize = true }) => {
|
const uploadImageBuffer = async ({ req, context, metadata = {}, resize = true }) => {
|
||||||
const source = getFileStrategy(req.app.locals, { isImage: true });
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
|
const source = getFileStrategy(appConfig, { isImage: true });
|
||||||
const { saveBuffer } = getStrategyFunctions(source);
|
const { saveBuffer } = getStrategyFunctions(source);
|
||||||
let { buffer, width, height, bytes, filename, file_id, type } = metadata;
|
let { buffer, width, height, bytes, filename, file_id, type } = metadata;
|
||||||
if (resize) {
|
if (resize) {
|
||||||
file_id = v4();
|
file_id = v4();
|
||||||
type = `image/${req.app.locals.imageOutputType}`;
|
type = `image/${appConfig.imageOutputType}`;
|
||||||
({ buffer, width, height, bytes } = await resizeAndConvert({
|
({ buffer, width, height, bytes } = await resizeAndConvert({
|
||||||
inputBuffer: buffer,
|
inputBuffer: buffer,
|
||||||
desiredFormat: req.app.locals.imageOutputType,
|
desiredFormat: appConfig.imageOutputType,
|
||||||
}));
|
}));
|
||||||
filename = `${path.basename(req.file.originalname, path.extname(req.file.originalname))}.${
|
filename = `${path.basename(req.file.originalname, path.extname(req.file.originalname))}.${
|
||||||
req.app.locals.imageOutputType
|
appConfig.imageOutputType
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
const fileName = `${file_id}-${filename}`;
|
const fileName = `${file_id}-${filename}`;
|
||||||
|
|
@ -411,11 +414,12 @@ const uploadImageBuffer = async ({ req, context, metadata = {}, resize = true })
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
const processFileUpload = async ({ req, res, metadata }) => {
|
const processFileUpload = async ({ req, res, metadata }) => {
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
const isAssistantUpload = isAssistantsEndpoint(metadata.endpoint);
|
const isAssistantUpload = isAssistantsEndpoint(metadata.endpoint);
|
||||||
const assistantSource =
|
const assistantSource =
|
||||||
metadata.endpoint === EModelEndpoint.azureAssistants ? FileSources.azure : FileSources.openai;
|
metadata.endpoint === EModelEndpoint.azureAssistants ? FileSources.azure : FileSources.openai;
|
||||||
// Use the configured file strategy for regular file uploads (not vectordb)
|
// Use the configured file strategy for regular file uploads (not vectordb)
|
||||||
const source = isAssistantUpload ? assistantSource : req.app.locals.fileStrategy;
|
const source = isAssistantUpload ? assistantSource : appConfig.fileStrategy;
|
||||||
const { handleFileUpload } = getStrategyFunctions(source);
|
const { handleFileUpload } = getStrategyFunctions(source);
|
||||||
const { file_id, temp_file_id = null } = metadata;
|
const { file_id, temp_file_id = null } = metadata;
|
||||||
|
|
||||||
|
|
@ -501,6 +505,7 @@ const processFileUpload = async ({ req, res, metadata }) => {
|
||||||
*/
|
*/
|
||||||
const processAgentFileUpload = async ({ req, res, metadata }) => {
|
const processAgentFileUpload = async ({ req, res, metadata }) => {
|
||||||
const { file } = req;
|
const { file } = req;
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
const { agent_id, tool_resource, file_id, temp_file_id = null } = metadata;
|
const { agent_id, tool_resource, file_id, temp_file_id = null } = metadata;
|
||||||
if (agent_id && !tool_resource) {
|
if (agent_id && !tool_resource) {
|
||||||
throw new Error('No tool resource provided for agent file upload');
|
throw new Error('No tool resource provided for agent file upload');
|
||||||
|
|
@ -553,7 +558,7 @@ const processAgentFileUpload = async ({ req, res, metadata }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const { handleFileUpload: uploadOCR } = getStrategyFunctions(
|
const { handleFileUpload: uploadOCR } = getStrategyFunctions(
|
||||||
req.app.locals?.ocr?.strategy ?? FileSources.mistral_ocr,
|
appConfig?.ocr?.strategy ?? FileSources.mistral_ocr,
|
||||||
);
|
);
|
||||||
const { file_id, temp_file_id = null } = metadata;
|
const { file_id, temp_file_id = null } = metadata;
|
||||||
|
|
||||||
|
|
@ -564,7 +569,7 @@ const processAgentFileUpload = async ({ req, res, metadata }) => {
|
||||||
images: _i,
|
images: _i,
|
||||||
filename,
|
filename,
|
||||||
filepath: ocrFileURL,
|
filepath: ocrFileURL,
|
||||||
} = await uploadOCR({ req, file, loadAuthValues });
|
} = await uploadOCR({ req, appConfig, file, loadAuthValues });
|
||||||
|
|
||||||
const fileInfo = removeNullishValues({
|
const fileInfo = removeNullishValues({
|
||||||
text,
|
text,
|
||||||
|
|
@ -597,7 +602,7 @@ const processAgentFileUpload = async ({ req, res, metadata }) => {
|
||||||
// Dual storage pattern for RAG files: Storage + Vector DB
|
// Dual storage pattern for RAG files: Storage + Vector DB
|
||||||
let storageResult, embeddingResult;
|
let storageResult, embeddingResult;
|
||||||
const isImageFile = file.mimetype.startsWith('image');
|
const isImageFile = file.mimetype.startsWith('image');
|
||||||
const source = getFileStrategy(req.app.locals, { isImage: isImageFile });
|
const source = getFileStrategy(appConfig, { isImage: isImageFile });
|
||||||
|
|
||||||
if (tool_resource === EToolResources.file_search) {
|
if (tool_resource === EToolResources.file_search) {
|
||||||
// FIRST: Upload to Storage for permanent backup (S3/local/etc.)
|
// FIRST: Upload to Storage for permanent backup (S3/local/etc.)
|
||||||
|
|
@ -752,6 +757,7 @@ const processOpenAIFile = async ({
|
||||||
const processOpenAIImageOutput = async ({ req, buffer, file_id, filename, fileExt }) => {
|
const processOpenAIImageOutput = async ({ req, buffer, file_id, filename, fileExt }) => {
|
||||||
const currentDate = new Date();
|
const currentDate = new Date();
|
||||||
const formattedDate = currentDate.toISOString();
|
const formattedDate = currentDate.toISOString();
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
const _file = await convertImage(req, buffer, undefined, `${file_id}${fileExt}`);
|
const _file = await convertImage(req, buffer, undefined, `${file_id}${fileExt}`);
|
||||||
|
|
||||||
// Create only one file record with the correct information
|
// Create only one file record with the correct information
|
||||||
|
|
@ -762,7 +768,7 @@ const processOpenAIImageOutput = async ({ req, buffer, file_id, filename, fileEx
|
||||||
type: mime.getType(fileExt),
|
type: mime.getType(fileExt),
|
||||||
createdAt: formattedDate,
|
createdAt: formattedDate,
|
||||||
updatedAt: formattedDate,
|
updatedAt: formattedDate,
|
||||||
source: getFileStrategy(req.app.locals, { isImage: true }),
|
source: getFileStrategy(appConfig, { isImage: true }),
|
||||||
context: FileContext.assistants_output,
|
context: FileContext.assistants_output,
|
||||||
file_id,
|
file_id,
|
||||||
filename,
|
filename,
|
||||||
|
|
@ -889,7 +895,7 @@ async function saveBase64Image(
|
||||||
url,
|
url,
|
||||||
{ req, file_id: _file_id, filename: _filename, endpoint, context, resolution },
|
{ req, file_id: _file_id, filename: _filename, endpoint, context, resolution },
|
||||||
) {
|
) {
|
||||||
const effectiveResolution = resolution ?? req.app.locals.fileConfig?.imageGeneration ?? 'high';
|
const effectiveResolution = resolution ?? appConfig.fileConfig?.imageGeneration ?? 'high';
|
||||||
const file_id = _file_id ?? v4();
|
const file_id = _file_id ?? v4();
|
||||||
let filename = `${file_id}-${_filename}`;
|
let filename = `${file_id}-${_filename}`;
|
||||||
const { buffer: inputBuffer, type } = base64ToBuffer(url);
|
const { buffer: inputBuffer, type } = base64ToBuffer(url);
|
||||||
|
|
@ -903,7 +909,8 @@ async function saveBase64Image(
|
||||||
}
|
}
|
||||||
|
|
||||||
const image = await resizeImageBuffer(inputBuffer, effectiveResolution, endpoint);
|
const image = await resizeImageBuffer(inputBuffer, effectiveResolution, endpoint);
|
||||||
const source = getFileStrategy(req.app.locals, { isImage: true });
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
|
const source = getFileStrategy(appConfig, { isImage: true });
|
||||||
const { saveBuffer } = getStrategyFunctions(source);
|
const { saveBuffer } = getStrategyFunctions(source);
|
||||||
const filepath = await saveBuffer({
|
const filepath = await saveBuffer({
|
||||||
userId: req.user.id,
|
userId: req.user.id,
|
||||||
|
|
@ -964,7 +971,8 @@ function filterFile({ req, image, isAvatar }) {
|
||||||
throw new Error('No endpoint provided');
|
throw new Error('No endpoint provided');
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileConfig = mergeFileConfig(req.app.locals.fileConfig);
|
const appConfig = getAppConfig({ role: req.user?.role });
|
||||||
|
const fileConfig = mergeFileConfig(appConfig.fileConfig);
|
||||||
|
|
||||||
const { fileSizeLimit: sizeLimit, supportedMimeTypes } =
|
const { fileSizeLimit: sizeLimit, supportedMimeTypes } =
|
||||||
fileConfig.endpoints[endpoint] ?? fileConfig.endpoints.default;
|
fileConfig.endpoints[endpoint] ?? fileConfig.endpoints.default;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
const { logAxiosError } = require('@librechat/api');
|
const { logAxiosError } = require('@librechat/api');
|
||||||
const { EModelEndpoint } = require('librechat-data-provider');
|
const { EModelEndpoint } = require('librechat-data-provider');
|
||||||
|
const { getAppConfig } = require('~/server/services/Config');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} RetrieveOptions
|
* @typedef {Object} RetrieveOptions
|
||||||
|
|
@ -18,6 +19,7 @@ const { EModelEndpoint } = require('librechat-data-provider');
|
||||||
* @returns {Promise<Object>} The data retrieved from the API.
|
* @returns {Promise<Object>} The data retrieved from the API.
|
||||||
*/
|
*/
|
||||||
async function retrieveRun({ thread_id, run_id, timeout, openai }) {
|
async function retrieveRun({ thread_id, run_id, timeout, openai }) {
|
||||||
|
const appConfig = await getAppConfig({ role: openai.req.user?.role });
|
||||||
const { apiKey, baseURL, httpAgent, organization } = openai;
|
const { apiKey, baseURL, httpAgent, organization } = openai;
|
||||||
let url = `${baseURL}/threads/${thread_id}/runs/${run_id}`;
|
let url = `${baseURL}/threads/${thread_id}/runs/${run_id}`;
|
||||||
|
|
||||||
|
|
@ -31,7 +33,7 @@ async function retrieveRun({ thread_id, run_id, timeout, openai }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @type {TAzureConfig | undefined} */
|
/** @type {TAzureConfig | undefined} */
|
||||||
const azureConfig = openai.req.app.locals[EModelEndpoint.azureOpenAI];
|
const azureConfig = appConfig[EModelEndpoint.azureOpenAI];
|
||||||
|
|
||||||
if (azureConfig && azureConfig.assistants) {
|
if (azureConfig && azureConfig.assistants) {
|
||||||
delete headers.Authorization;
|
delete headers.Authorization;
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,8 @@ const {
|
||||||
manifestToolMap,
|
manifestToolMap,
|
||||||
toolkits,
|
toolkits,
|
||||||
} = require('~/app/clients/tools');
|
} = require('~/app/clients/tools');
|
||||||
|
const { getEndpointsConfig, getCachedTools, getAppConfig } = require('~/server/services/Config');
|
||||||
const { processFileURL, uploadImageBuffer } = require('~/server/services/Files/process');
|
const { processFileURL, uploadImageBuffer } = require('~/server/services/Files/process');
|
||||||
const { getEndpointsConfig, getCachedTools } = require('~/server/services/Config');
|
|
||||||
const { createOnSearchResults } = require('~/server/services/Tools/search');
|
const { createOnSearchResults } = require('~/server/services/Tools/search');
|
||||||
const { isActionDomainAllowed } = require('~/server/services/domains');
|
const { isActionDomainAllowed } = require('~/server/services/domains');
|
||||||
const { recordUsage } = require('~/server/services/Threads');
|
const { recordUsage } = require('~/server/services/Threads');
|
||||||
|
|
@ -202,6 +202,7 @@ async function processRequiredActions(client, requiredActions) {
|
||||||
`[required actions] user: ${client.req.user.id} | thread_id: ${requiredActions[0].thread_id} | run_id: ${requiredActions[0].run_id}`,
|
`[required actions] user: ${client.req.user.id} | thread_id: ${requiredActions[0].thread_id} | run_id: ${requiredActions[0].run_id}`,
|
||||||
requiredActions,
|
requiredActions,
|
||||||
);
|
);
|
||||||
|
const appConfig = await getAppConfig({ role: client.req.user?.role });
|
||||||
const toolDefinitions = await getCachedTools({ userId: client.req.user.id, includeGlobal: true });
|
const toolDefinitions = await getCachedTools({ userId: client.req.user.id, includeGlobal: true });
|
||||||
const seenToolkits = new Set();
|
const seenToolkits = new Set();
|
||||||
const tools = requiredActions
|
const tools = requiredActions
|
||||||
|
|
@ -233,7 +234,7 @@ async function processRequiredActions(client, requiredActions) {
|
||||||
req: client.req,
|
req: client.req,
|
||||||
uploadImageBuffer,
|
uploadImageBuffer,
|
||||||
openAIApiKey: client.apiKey,
|
openAIApiKey: client.apiKey,
|
||||||
fileStrategy: client.req.app.locals.fileStrategy,
|
fileStrategy: appConfig.fileStrategy,
|
||||||
returnMetadata: true,
|
returnMetadata: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -480,12 +481,13 @@ async function loadAgentTools({ req, res, agent, tool_resources, openAIApiKey })
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const appConfig = await getAppConfig({ role: req.user?.role });
|
||||||
const endpointsConfig = await getEndpointsConfig(req);
|
const endpointsConfig = await getEndpointsConfig(req);
|
||||||
let enabledCapabilities = new Set(endpointsConfig?.[EModelEndpoint.agents]?.capabilities ?? []);
|
let enabledCapabilities = new Set(endpointsConfig?.[EModelEndpoint.agents]?.capabilities ?? []);
|
||||||
/** Edge case: use defined/fallback capabilities when the "agents" endpoint is not enabled */
|
/** Edge case: use defined/fallback capabilities when the "agents" endpoint is not enabled */
|
||||||
if (enabledCapabilities.size === 0 && agent.id === Constants.EPHEMERAL_AGENT_ID) {
|
if (enabledCapabilities.size === 0 && agent.id === Constants.EPHEMERAL_AGENT_ID) {
|
||||||
enabledCapabilities = new Set(
|
enabledCapabilities = new Set(
|
||||||
req.app?.locals?.[EModelEndpoint.agents]?.capabilities ?? defaultAgentCapabilities,
|
appConfig?.[EModelEndpoint.agents]?.capabilities ?? defaultAgentCapabilities,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const checkCapability = (capability) => {
|
const checkCapability = (capability) => {
|
||||||
|
|
@ -536,7 +538,7 @@ async function loadAgentTools({ req, res, agent, tool_resources, openAIApiKey })
|
||||||
processFileURL,
|
processFileURL,
|
||||||
uploadImageBuffer,
|
uploadImageBuffer,
|
||||||
returnMetadata: true,
|
returnMetadata: true,
|
||||||
fileStrategy: req.app.locals.fileStrategy,
|
fileStrategy: appConfig.fileStrategy,
|
||||||
[Tools.web_search]: webSearchCallbacks,
|
[Tools.web_search]: webSearchCallbacks,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
const { logger } = require('@librechat/data-schemas');
|
const { logger } = require('@librechat/data-schemas');
|
||||||
const { getCachedTools, setCachedTools } = require('./Config');
|
const { getCachedTools, setCachedTools, getAppConfig } = require('./Config');
|
||||||
const { CacheKeys } = require('librechat-data-provider');
|
const { CacheKeys } = require('librechat-data-provider');
|
||||||
const { createMCPManager } = require('~/config');
|
const { createMCPManager } = require('~/config');
|
||||||
const { getLogStores } = require('~/cache');
|
const { getLogStores } = require('~/cache');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize MCP servers
|
* Initialize MCP servers
|
||||||
* @param {import('express').Application} app - Express app instance
|
|
||||||
*/
|
*/
|
||||||
async function initializeMCPs(app) {
|
async function initializeMCPs() {
|
||||||
const mcpServers = app.locals.mcpConfig;
|
const appConfig = await getAppConfig();
|
||||||
|
const mcpServers = appConfig.mcpConfig;
|
||||||
if (!mcpServers) {
|
if (!mcpServers) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -17,7 +17,6 @@ async function initializeMCPs(app) {
|
||||||
const mcpManager = await createMCPManager(mcpServers);
|
const mcpManager = await createMCPManager(mcpServers);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
delete app.locals.mcpConfig;
|
|
||||||
const cachedTools = await getCachedTools();
|
const cachedTools = await getCachedTools();
|
||||||
|
|
||||||
if (!cachedTools) {
|
if (!cachedTools) {
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,11 @@ const { FileContext } = require('librechat-data-provider');
|
||||||
* { isAvatar: true }
|
* { isAvatar: true }
|
||||||
* ) // Returns 'local'
|
* ) // Returns 'local'
|
||||||
*/
|
*/
|
||||||
function getFileStrategy(appLocals, { isAvatar = false, isImage = false, context = null } = {}) {
|
function getFileStrategy(appConfig, { isAvatar = false, isImage = false, context = null } = {}) {
|
||||||
// Handle both old (config object) and new (app.locals object) calling patterns
|
// Handle both old (config object) and new (`appConfig` object) calling patterns
|
||||||
const isAppLocals = appLocals.fileStrategy !== undefined;
|
const isAppConfig = appConfig.fileStrategy !== undefined;
|
||||||
const config = isAppLocals ? appLocals.config : appLocals;
|
const config = isAppConfig ? appConfig.config : appConfig;
|
||||||
const fileStrategy = isAppLocals ? appLocals.fileStrategy : appLocals.fileStrategy;
|
const fileStrategy = isAppConfig ? appConfig.fileStrategy : appConfig.fileStrategy;
|
||||||
|
|
||||||
// Fallback to legacy single strategy if no granular config
|
// Fallback to legacy single strategy if no granular config
|
||||||
if (!config?.fileStrategies) {
|
if (!config?.fileStrategies) {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { primeResources } from './resources';
|
import { primeResources } from './resources';
|
||||||
import { logger } from '@librechat/data-schemas';
|
import { logger } from '@librechat/data-schemas';
|
||||||
import { EModelEndpoint, EToolResources, AgentCapabilities } from 'librechat-data-provider';
|
import { EModelEndpoint, EToolResources, AgentCapabilities } from 'librechat-data-provider';
|
||||||
|
import type { TAgentsEndpoint, TFile } from 'librechat-data-provider';
|
||||||
import type { Request as ServerRequest } from 'express';
|
import type { Request as ServerRequest } from 'express';
|
||||||
import type { TFile } from 'librechat-data-provider';
|
|
||||||
import type { TGetFiles } from './resources';
|
import type { TGetFiles } from './resources';
|
||||||
|
import type { AppConfig } from '~/types';
|
||||||
|
|
||||||
// Mock logger
|
// Mock logger
|
||||||
jest.mock('@librechat/data-schemas', () => ({
|
jest.mock('@librechat/data-schemas', () => ({
|
||||||
|
|
@ -14,6 +15,7 @@ jest.mock('@librechat/data-schemas', () => ({
|
||||||
|
|
||||||
describe('primeResources', () => {
|
describe('primeResources', () => {
|
||||||
let mockReq: ServerRequest;
|
let mockReq: ServerRequest;
|
||||||
|
let mockAppConfig: AppConfig;
|
||||||
let mockGetFiles: jest.MockedFunction<TGetFiles>;
|
let mockGetFiles: jest.MockedFunction<TGetFiles>;
|
||||||
let requestFileSet: Set<string>;
|
let requestFileSet: Set<string>;
|
||||||
|
|
||||||
|
|
@ -32,6 +34,13 @@ describe('primeResources', () => {
|
||||||
},
|
},
|
||||||
} as unknown as ServerRequest;
|
} as unknown as ServerRequest;
|
||||||
|
|
||||||
|
// Setup mock appConfig
|
||||||
|
mockAppConfig = {
|
||||||
|
[EModelEndpoint.agents]: {
|
||||||
|
capabilities: [AgentCapabilities.ocr],
|
||||||
|
} as TAgentsEndpoint,
|
||||||
|
} as AppConfig;
|
||||||
|
|
||||||
// Setup mock getFiles function
|
// Setup mock getFiles function
|
||||||
mockGetFiles = jest.fn();
|
mockGetFiles = jest.fn();
|
||||||
|
|
||||||
|
|
@ -65,6 +74,7 @@ describe('primeResources', () => {
|
||||||
|
|
||||||
const result = await primeResources({
|
const result = await primeResources({
|
||||||
req: mockReq,
|
req: mockReq,
|
||||||
|
appConfig: mockAppConfig,
|
||||||
getFiles: mockGetFiles,
|
getFiles: mockGetFiles,
|
||||||
requestFileSet,
|
requestFileSet,
|
||||||
attachments: undefined,
|
attachments: undefined,
|
||||||
|
|
@ -85,6 +95,7 @@ describe('primeResources', () => {
|
||||||
describe('when OCR is disabled', () => {
|
describe('when OCR is disabled', () => {
|
||||||
it('should not fetch OCR files even if tool_resources has OCR file_ids', async () => {
|
it('should not fetch OCR files even if tool_resources has OCR file_ids', async () => {
|
||||||
(mockReq.app as ServerRequest['app']).locals[EModelEndpoint.agents].capabilities = [];
|
(mockReq.app as ServerRequest['app']).locals[EModelEndpoint.agents].capabilities = [];
|
||||||
|
(mockAppConfig[EModelEndpoint.agents] as TAgentsEndpoint).capabilities = [];
|
||||||
|
|
||||||
const tool_resources = {
|
const tool_resources = {
|
||||||
[EToolResources.ocr]: {
|
[EToolResources.ocr]: {
|
||||||
|
|
@ -94,6 +105,7 @@ describe('primeResources', () => {
|
||||||
|
|
||||||
const result = await primeResources({
|
const result = await primeResources({
|
||||||
req: mockReq,
|
req: mockReq,
|
||||||
|
appConfig: mockAppConfig,
|
||||||
getFiles: mockGetFiles,
|
getFiles: mockGetFiles,
|
||||||
requestFileSet,
|
requestFileSet,
|
||||||
attachments: undefined,
|
attachments: undefined,
|
||||||
|
|
@ -129,6 +141,7 @@ describe('primeResources', () => {
|
||||||
|
|
||||||
const result = await primeResources({
|
const result = await primeResources({
|
||||||
req: mockReq,
|
req: mockReq,
|
||||||
|
appConfig: mockAppConfig,
|
||||||
getFiles: mockGetFiles,
|
getFiles: mockGetFiles,
|
||||||
requestFileSet,
|
requestFileSet,
|
||||||
attachments,
|
attachments,
|
||||||
|
|
@ -158,6 +171,7 @@ describe('primeResources', () => {
|
||||||
|
|
||||||
const result = await primeResources({
|
const result = await primeResources({
|
||||||
req: mockReq,
|
req: mockReq,
|
||||||
|
appConfig: mockAppConfig,
|
||||||
getFiles: mockGetFiles,
|
getFiles: mockGetFiles,
|
||||||
requestFileSet,
|
requestFileSet,
|
||||||
attachments,
|
attachments,
|
||||||
|
|
@ -189,6 +203,7 @@ describe('primeResources', () => {
|
||||||
|
|
||||||
const result = await primeResources({
|
const result = await primeResources({
|
||||||
req: mockReq,
|
req: mockReq,
|
||||||
|
appConfig: mockAppConfig,
|
||||||
getFiles: mockGetFiles,
|
getFiles: mockGetFiles,
|
||||||
requestFileSet,
|
requestFileSet,
|
||||||
attachments,
|
attachments,
|
||||||
|
|
@ -220,6 +235,7 @@ describe('primeResources', () => {
|
||||||
|
|
||||||
const result = await primeResources({
|
const result = await primeResources({
|
||||||
req: mockReq,
|
req: mockReq,
|
||||||
|
appConfig: mockAppConfig,
|
||||||
getFiles: mockGetFiles,
|
getFiles: mockGetFiles,
|
||||||
requestFileSet,
|
requestFileSet,
|
||||||
attachments,
|
attachments,
|
||||||
|
|
@ -250,6 +266,7 @@ describe('primeResources', () => {
|
||||||
|
|
||||||
const result = await primeResources({
|
const result = await primeResources({
|
||||||
req: mockReq,
|
req: mockReq,
|
||||||
|
appConfig: mockAppConfig,
|
||||||
getFiles: mockGetFiles,
|
getFiles: mockGetFiles,
|
||||||
requestFileSet,
|
requestFileSet,
|
||||||
attachments,
|
attachments,
|
||||||
|
|
@ -291,6 +308,7 @@ describe('primeResources', () => {
|
||||||
|
|
||||||
const result = await primeResources({
|
const result = await primeResources({
|
||||||
req: mockReq,
|
req: mockReq,
|
||||||
|
appConfig: mockAppConfig,
|
||||||
getFiles: mockGetFiles,
|
getFiles: mockGetFiles,
|
||||||
requestFileSet,
|
requestFileSet,
|
||||||
attachments,
|
attachments,
|
||||||
|
|
@ -342,6 +360,7 @@ describe('primeResources', () => {
|
||||||
|
|
||||||
const result = await primeResources({
|
const result = await primeResources({
|
||||||
req: mockReq,
|
req: mockReq,
|
||||||
|
appConfig: mockAppConfig,
|
||||||
getFiles: mockGetFiles,
|
getFiles: mockGetFiles,
|
||||||
requestFileSet,
|
requestFileSet,
|
||||||
attachments,
|
attachments,
|
||||||
|
|
@ -399,6 +418,7 @@ describe('primeResources', () => {
|
||||||
|
|
||||||
const result = await primeResources({
|
const result = await primeResources({
|
||||||
req: mockReq,
|
req: mockReq,
|
||||||
|
appConfig: mockAppConfig,
|
||||||
getFiles: mockGetFiles,
|
getFiles: mockGetFiles,
|
||||||
requestFileSet,
|
requestFileSet,
|
||||||
attachments,
|
attachments,
|
||||||
|
|
@ -450,6 +470,7 @@ describe('primeResources', () => {
|
||||||
|
|
||||||
const result = await primeResources({
|
const result = await primeResources({
|
||||||
req: mockReq,
|
req: mockReq,
|
||||||
|
appConfig: mockAppConfig,
|
||||||
getFiles: mockGetFiles,
|
getFiles: mockGetFiles,
|
||||||
requestFileSet,
|
requestFileSet,
|
||||||
attachments,
|
attachments,
|
||||||
|
|
@ -492,6 +513,7 @@ describe('primeResources', () => {
|
||||||
|
|
||||||
const result = await primeResources({
|
const result = await primeResources({
|
||||||
req: mockReq,
|
req: mockReq,
|
||||||
|
appConfig: mockAppConfig,
|
||||||
getFiles: mockGetFiles,
|
getFiles: mockGetFiles,
|
||||||
requestFileSet,
|
requestFileSet,
|
||||||
attachments,
|
attachments,
|
||||||
|
|
@ -560,6 +582,7 @@ describe('primeResources', () => {
|
||||||
|
|
||||||
const result = await primeResources({
|
const result = await primeResources({
|
||||||
req: mockReq,
|
req: mockReq,
|
||||||
|
appConfig: mockAppConfig,
|
||||||
getFiles: mockGetFiles,
|
getFiles: mockGetFiles,
|
||||||
requestFileSet,
|
requestFileSet,
|
||||||
attachments,
|
attachments,
|
||||||
|
|
@ -618,6 +641,7 @@ describe('primeResources', () => {
|
||||||
|
|
||||||
const result = await primeResources({
|
const result = await primeResources({
|
||||||
req: mockReq,
|
req: mockReq,
|
||||||
|
appConfig: mockAppConfig,
|
||||||
getFiles: mockGetFiles,
|
getFiles: mockGetFiles,
|
||||||
requestFileSet,
|
requestFileSet,
|
||||||
attachments,
|
attachments,
|
||||||
|
|
@ -671,6 +695,7 @@ describe('primeResources', () => {
|
||||||
|
|
||||||
const result = await primeResources({
|
const result = await primeResources({
|
||||||
req: mockReq,
|
req: mockReq,
|
||||||
|
appConfig: mockAppConfig,
|
||||||
getFiles: mockGetFiles,
|
getFiles: mockGetFiles,
|
||||||
requestFileSet,
|
requestFileSet,
|
||||||
attachments,
|
attachments,
|
||||||
|
|
@ -724,6 +749,7 @@ describe('primeResources', () => {
|
||||||
|
|
||||||
const result = await primeResources({
|
const result = await primeResources({
|
||||||
req: mockReq,
|
req: mockReq,
|
||||||
|
appConfig: mockAppConfig,
|
||||||
getFiles: mockGetFiles,
|
getFiles: mockGetFiles,
|
||||||
requestFileSet,
|
requestFileSet,
|
||||||
attachments,
|
attachments,
|
||||||
|
|
@ -764,6 +790,7 @@ describe('primeResources', () => {
|
||||||
|
|
||||||
const result = await primeResources({
|
const result = await primeResources({
|
||||||
req: mockReq,
|
req: mockReq,
|
||||||
|
appConfig: mockAppConfig,
|
||||||
getFiles: mockGetFiles,
|
getFiles: mockGetFiles,
|
||||||
requestFileSet,
|
requestFileSet,
|
||||||
attachments,
|
attachments,
|
||||||
|
|
@ -838,6 +865,7 @@ describe('primeResources', () => {
|
||||||
|
|
||||||
const result = await primeResources({
|
const result = await primeResources({
|
||||||
req: mockReq,
|
req: mockReq,
|
||||||
|
appConfig: mockAppConfig,
|
||||||
getFiles: mockGetFiles,
|
getFiles: mockGetFiles,
|
||||||
requestFileSet,
|
requestFileSet,
|
||||||
attachments,
|
attachments,
|
||||||
|
|
@ -888,6 +916,7 @@ describe('primeResources', () => {
|
||||||
|
|
||||||
const result = await primeResources({
|
const result = await primeResources({
|
||||||
req: mockReq,
|
req: mockReq,
|
||||||
|
appConfig: mockAppConfig,
|
||||||
getFiles: mockGetFiles,
|
getFiles: mockGetFiles,
|
||||||
requestFileSet,
|
requestFileSet,
|
||||||
attachments,
|
attachments,
|
||||||
|
|
@ -906,6 +935,7 @@ describe('primeResources', () => {
|
||||||
// The function should now handle rejected attachment promises gracefully
|
// The function should now handle rejected attachment promises gracefully
|
||||||
const result = await primeResources({
|
const result = await primeResources({
|
||||||
req: mockReq,
|
req: mockReq,
|
||||||
|
appConfig: mockAppConfig,
|
||||||
getFiles: mockGetFiles,
|
getFiles: mockGetFiles,
|
||||||
requestFileSet,
|
requestFileSet,
|
||||||
attachments,
|
attachments,
|
||||||
|
|
@ -928,9 +958,11 @@ describe('primeResources', () => {
|
||||||
describe('edge cases', () => {
|
describe('edge cases', () => {
|
||||||
it('should handle missing app.locals gracefully', async () => {
|
it('should handle missing app.locals gracefully', async () => {
|
||||||
const reqWithoutLocals = {} as ServerRequest;
|
const reqWithoutLocals = {} as ServerRequest;
|
||||||
|
const emptyAppConfig = {} as AppConfig;
|
||||||
|
|
||||||
const result = await primeResources({
|
const result = await primeResources({
|
||||||
req: reqWithoutLocals,
|
req: reqWithoutLocals,
|
||||||
|
appConfig: emptyAppConfig,
|
||||||
getFiles: mockGetFiles,
|
getFiles: mockGetFiles,
|
||||||
requestFileSet,
|
requestFileSet,
|
||||||
attachments: undefined,
|
attachments: undefined,
|
||||||
|
|
@ -950,6 +982,7 @@ describe('primeResources', () => {
|
||||||
it('should handle undefined tool_resources', async () => {
|
it('should handle undefined tool_resources', async () => {
|
||||||
const result = await primeResources({
|
const result = await primeResources({
|
||||||
req: mockReq,
|
req: mockReq,
|
||||||
|
appConfig: mockAppConfig,
|
||||||
getFiles: mockGetFiles,
|
getFiles: mockGetFiles,
|
||||||
requestFileSet,
|
requestFileSet,
|
||||||
attachments: undefined,
|
attachments: undefined,
|
||||||
|
|
@ -982,6 +1015,7 @@ describe('primeResources', () => {
|
||||||
|
|
||||||
const result = await primeResources({
|
const result = await primeResources({
|
||||||
req: mockReq,
|
req: mockReq,
|
||||||
|
appConfig: mockAppConfig,
|
||||||
getFiles: mockGetFiles,
|
getFiles: mockGetFiles,
|
||||||
requestFileSet: emptyRequestFileSet,
|
requestFileSet: emptyRequestFileSet,
|
||||||
attachments,
|
attachments,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import type { AgentToolResources, TFile, AgentBaseResource } from 'librechat-dat
|
||||||
import type { FilterQuery, QueryOptions, ProjectionType } from 'mongoose';
|
import type { FilterQuery, QueryOptions, ProjectionType } from 'mongoose';
|
||||||
import type { IMongoFile, IUser } from '@librechat/data-schemas';
|
import type { IMongoFile, IUser } from '@librechat/data-schemas';
|
||||||
import type { Request as ServerRequest } from 'express';
|
import type { Request as ServerRequest } from 'express';
|
||||||
|
import type { AppConfig } from '~/types/';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function type for retrieving files from the database
|
* Function type for retrieving files from the database
|
||||||
|
|
@ -134,7 +135,8 @@ const categorizeFileForToolResources = ({
|
||||||
* 4. Prevents duplicate files across all sources
|
* 4. Prevents duplicate files across all sources
|
||||||
*
|
*
|
||||||
* @param params - Parameters object
|
* @param params - Parameters object
|
||||||
* @param params.req - Express request object containing app configuration
|
* @param params.req - Express request object
|
||||||
|
* @param params.appConfig - Application configuration object
|
||||||
* @param params.getFiles - Function to retrieve files from database
|
* @param params.getFiles - Function to retrieve files from database
|
||||||
* @param params.requestFileSet - Set of file IDs from the current request
|
* @param params.requestFileSet - Set of file IDs from the current request
|
||||||
* @param params.attachments - Promise resolving to array of attachment files
|
* @param params.attachments - Promise resolving to array of attachment files
|
||||||
|
|
@ -143,6 +145,7 @@ const categorizeFileForToolResources = ({
|
||||||
*/
|
*/
|
||||||
export const primeResources = async ({
|
export const primeResources = async ({
|
||||||
req,
|
req,
|
||||||
|
appConfig,
|
||||||
getFiles,
|
getFiles,
|
||||||
requestFileSet,
|
requestFileSet,
|
||||||
attachments: _attachments,
|
attachments: _attachments,
|
||||||
|
|
@ -150,6 +153,7 @@ export const primeResources = async ({
|
||||||
agentId,
|
agentId,
|
||||||
}: {
|
}: {
|
||||||
req: ServerRequest & { user?: IUser };
|
req: ServerRequest & { user?: IUser };
|
||||||
|
appConfig: AppConfig;
|
||||||
requestFileSet: Set<string>;
|
requestFileSet: Set<string>;
|
||||||
attachments: Promise<Array<TFile | null>> | undefined;
|
attachments: Promise<Array<TFile | null>> | undefined;
|
||||||
tool_resources: AgentToolResources | undefined;
|
tool_resources: AgentToolResources | undefined;
|
||||||
|
|
@ -198,7 +202,7 @@ export const primeResources = async ({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const isOCREnabled = (req.app.locals?.[EModelEndpoint.agents]?.capabilities ?? []).includes(
|
const isOCREnabled = (appConfig?.[EModelEndpoint.agents]?.capabilities ?? []).includes(
|
||||||
AgentCapabilities.ocr,
|
AgentCapabilities.ocr,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { ErrorTypes, EModelEndpoint, mapModelToAzureConfig } from 'librechat-data-provider';
|
import { ErrorTypes, EModelEndpoint, mapModelToAzureConfig } from 'librechat-data-provider';
|
||||||
import type {
|
import type {
|
||||||
UserKeyValues,
|
InitializeOpenAIOptionsParams,
|
||||||
OpenAIOptionsResult,
|
OpenAIOptionsResult,
|
||||||
OpenAIConfigOptions,
|
OpenAIConfigOptions,
|
||||||
InitializeOpenAIOptionsParams,
|
UserKeyValues,
|
||||||
} from '~/types';
|
} from '~/types';
|
||||||
import { createHandleLLMNewToken } from '~/utils/generators';
|
import { createHandleLLMNewToken } from '~/utils/generators';
|
||||||
import { getAzureCredentials } from '~/utils/azure';
|
import { getAzureCredentials } from '~/utils/azure';
|
||||||
|
|
@ -21,6 +21,7 @@ import { getOpenAIConfig } from './llm';
|
||||||
*/
|
*/
|
||||||
export const initializeOpenAI = async ({
|
export const initializeOpenAI = async ({
|
||||||
req,
|
req,
|
||||||
|
appConfig,
|
||||||
overrideModel,
|
overrideModel,
|
||||||
endpointOption,
|
endpointOption,
|
||||||
overrideEndpoint,
|
overrideEndpoint,
|
||||||
|
|
@ -71,7 +72,7 @@ export const initializeOpenAI = async ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const isAzureOpenAI = endpoint === EModelEndpoint.azureOpenAI;
|
const isAzureOpenAI = endpoint === EModelEndpoint.azureOpenAI;
|
||||||
const azureConfig = isAzureOpenAI && req.app.locals[EModelEndpoint.azureOpenAI];
|
const azureConfig = isAzureOpenAI && appConfig[EModelEndpoint.azureOpenAI];
|
||||||
|
|
||||||
if (isAzureOpenAI && azureConfig) {
|
if (isAzureOpenAI && azureConfig) {
|
||||||
const { modelGroupMap, groupMap } = azureConfig;
|
const { modelGroupMap, groupMap } = azureConfig;
|
||||||
|
|
@ -142,8 +143,8 @@ export const initializeOpenAI = async ({
|
||||||
|
|
||||||
const options = getOpenAIConfig(apiKey, finalClientOptions, endpoint);
|
const options = getOpenAIConfig(apiKey, finalClientOptions, endpoint);
|
||||||
|
|
||||||
const openAIConfig = req.app.locals[EModelEndpoint.openAI];
|
const openAIConfig = appConfig[EModelEndpoint.openAI];
|
||||||
const allConfig = req.app.locals.all;
|
const allConfig = appConfig.all;
|
||||||
const azureRate = modelName?.includes('gpt-4') ? 30 : 17;
|
const azureRate = modelName?.includes('gpt-4') ? 30 : 17;
|
||||||
|
|
||||||
let streamRate: number | undefined;
|
let streamRate: number | undefined;
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,12 @@ import * as fs from 'fs';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import type { Request as ExpressRequest } from 'express';
|
import type { Request as ExpressRequest } from 'express';
|
||||||
import type { Readable } from 'stream';
|
import type { Readable } from 'stream';
|
||||||
import type { MistralFileUploadResponse, MistralSignedUrlResponse, OCRResult } from '~/types';
|
import type {
|
||||||
|
MistralFileUploadResponse,
|
||||||
|
MistralSignedUrlResponse,
|
||||||
|
OCRResult,
|
||||||
|
AppConfig,
|
||||||
|
} from '~/types';
|
||||||
import { logger as mockLogger } from '@librechat/data-schemas';
|
import { logger as mockLogger } from '@librechat/data-schemas';
|
||||||
import {
|
import {
|
||||||
uploadDocumentToMistral,
|
uploadDocumentToMistral,
|
||||||
|
|
@ -497,17 +502,16 @@ describe('MistralOCR Service', () => {
|
||||||
|
|
||||||
const req = {
|
const req = {
|
||||||
user: { id: 'user123' },
|
user: { id: 'user123' },
|
||||||
app: {
|
} as unknown as ExpressRequest;
|
||||||
locals: {
|
|
||||||
|
const appConfig = {
|
||||||
ocr: {
|
ocr: {
|
||||||
// Use environment variable syntax to ensure loadAuthValues is called
|
// Use environment variable syntax to ensure loadAuthValues is called
|
||||||
apiKey: '${OCR_API_KEY}',
|
apiKey: '${OCR_API_KEY}',
|
||||||
baseURL: '${OCR_BASEURL}',
|
baseURL: '${OCR_BASEURL}',
|
||||||
mistralModel: 'mistral-medium',
|
mistralModel: 'mistral-medium',
|
||||||
},
|
},
|
||||||
},
|
} as AppConfig;
|
||||||
},
|
|
||||||
} as unknown as ExpressRequest;
|
|
||||||
|
|
||||||
const file = {
|
const file = {
|
||||||
path: '/tmp/upload/file.pdf',
|
path: '/tmp/upload/file.pdf',
|
||||||
|
|
@ -517,6 +521,7 @@ describe('MistralOCR Service', () => {
|
||||||
|
|
||||||
const result = await uploadMistralOCR({
|
const result = await uploadMistralOCR({
|
||||||
req,
|
req,
|
||||||
|
appConfig,
|
||||||
file,
|
file,
|
||||||
loadAuthValues: mockLoadAuthValues,
|
loadAuthValues: mockLoadAuthValues,
|
||||||
});
|
});
|
||||||
|
|
@ -599,16 +604,15 @@ describe('MistralOCR Service', () => {
|
||||||
|
|
||||||
const req = {
|
const req = {
|
||||||
user: { id: 'user456' },
|
user: { id: 'user456' },
|
||||||
app: {
|
} as unknown as ExpressRequest;
|
||||||
locals: {
|
|
||||||
|
const appConfig = {
|
||||||
ocr: {
|
ocr: {
|
||||||
apiKey: '${OCR_API_KEY}',
|
apiKey: '${OCR_API_KEY}',
|
||||||
baseURL: '${OCR_BASEURL}',
|
baseURL: '${OCR_BASEURL}',
|
||||||
mistralModel: 'mistral-medium',
|
mistralModel: 'mistral-medium',
|
||||||
},
|
},
|
||||||
},
|
} as AppConfig;
|
||||||
},
|
|
||||||
} as unknown as ExpressRequest;
|
|
||||||
|
|
||||||
const file = {
|
const file = {
|
||||||
path: '/tmp/upload/image.png',
|
path: '/tmp/upload/image.png',
|
||||||
|
|
@ -618,6 +622,7 @@ describe('MistralOCR Service', () => {
|
||||||
|
|
||||||
const result = await uploadMistralOCR({
|
const result = await uploadMistralOCR({
|
||||||
req,
|
req,
|
||||||
|
appConfig,
|
||||||
file,
|
file,
|
||||||
loadAuthValues: mockLoadAuthValues,
|
loadAuthValues: mockLoadAuthValues,
|
||||||
});
|
});
|
||||||
|
|
@ -698,16 +703,15 @@ describe('MistralOCR Service', () => {
|
||||||
|
|
||||||
const req = {
|
const req = {
|
||||||
user: { id: 'user123' },
|
user: { id: 'user123' },
|
||||||
app: {
|
} as unknown as ExpressRequest;
|
||||||
locals: {
|
|
||||||
|
const appConfig = {
|
||||||
ocr: {
|
ocr: {
|
||||||
apiKey: '${CUSTOM_API_KEY}',
|
apiKey: '${CUSTOM_API_KEY}',
|
||||||
baseURL: '${CUSTOM_BASEURL}',
|
baseURL: '${CUSTOM_BASEURL}',
|
||||||
mistralModel: '${CUSTOM_MODEL}',
|
mistralModel: '${CUSTOM_MODEL}',
|
||||||
},
|
},
|
||||||
},
|
} as AppConfig;
|
||||||
},
|
|
||||||
} as unknown as ExpressRequest;
|
|
||||||
|
|
||||||
// Set environment variable for model
|
// Set environment variable for model
|
||||||
process.env.CUSTOM_MODEL = 'mistral-large';
|
process.env.CUSTOM_MODEL = 'mistral-large';
|
||||||
|
|
@ -720,6 +724,7 @@ describe('MistralOCR Service', () => {
|
||||||
|
|
||||||
const result = await uploadMistralOCR({
|
const result = await uploadMistralOCR({
|
||||||
req,
|
req,
|
||||||
|
appConfig,
|
||||||
file,
|
file,
|
||||||
loadAuthValues: mockLoadAuthValues,
|
loadAuthValues: mockLoadAuthValues,
|
||||||
});
|
});
|
||||||
|
|
@ -790,17 +795,16 @@ describe('MistralOCR Service', () => {
|
||||||
|
|
||||||
const req = {
|
const req = {
|
||||||
user: { id: 'user123' },
|
user: { id: 'user123' },
|
||||||
app: {
|
} as unknown as ExpressRequest;
|
||||||
locals: {
|
|
||||||
|
const appConfig = {
|
||||||
ocr: {
|
ocr: {
|
||||||
// Use environment variable syntax to ensure loadAuthValues is called
|
// Use environment variable syntax to ensure loadAuthValues is called
|
||||||
apiKey: '${INVALID_FORMAT}', // Using valid env var format but with an invalid name
|
apiKey: '${INVALID_FORMAT}', // Using valid env var format but with an invalid name
|
||||||
baseURL: '${OCR_BASEURL}', // Using valid env var format
|
baseURL: '${OCR_BASEURL}', // Using valid env var format
|
||||||
mistralModel: 'mistral-ocr-latest', // Plain string value
|
mistralModel: 'mistral-ocr-latest', // Plain string value
|
||||||
},
|
},
|
||||||
},
|
} as AppConfig;
|
||||||
},
|
|
||||||
} as unknown as ExpressRequest;
|
|
||||||
|
|
||||||
const file = {
|
const file = {
|
||||||
path: '/tmp/upload/file.pdf',
|
path: '/tmp/upload/file.pdf',
|
||||||
|
|
@ -810,6 +814,7 @@ describe('MistralOCR Service', () => {
|
||||||
|
|
||||||
await uploadMistralOCR({
|
await uploadMistralOCR({
|
||||||
req,
|
req,
|
||||||
|
appConfig,
|
||||||
file,
|
file,
|
||||||
loadAuthValues: mockLoadAuthValues,
|
loadAuthValues: mockLoadAuthValues,
|
||||||
});
|
});
|
||||||
|
|
@ -845,15 +850,14 @@ describe('MistralOCR Service', () => {
|
||||||
|
|
||||||
const req = {
|
const req = {
|
||||||
user: { id: 'user123' },
|
user: { id: 'user123' },
|
||||||
app: {
|
} as unknown as ExpressRequest;
|
||||||
locals: {
|
|
||||||
|
const appConfig = {
|
||||||
ocr: {
|
ocr: {
|
||||||
apiKey: 'OCR_API_KEY',
|
apiKey: 'OCR_API_KEY',
|
||||||
baseURL: 'OCR_BASEURL',
|
baseURL: 'OCR_BASEURL',
|
||||||
},
|
},
|
||||||
},
|
} as AppConfig;
|
||||||
},
|
|
||||||
} as unknown as ExpressRequest;
|
|
||||||
|
|
||||||
const file = {
|
const file = {
|
||||||
path: '/tmp/upload/file.pdf',
|
path: '/tmp/upload/file.pdf',
|
||||||
|
|
@ -864,6 +868,7 @@ describe('MistralOCR Service', () => {
|
||||||
await expect(
|
await expect(
|
||||||
uploadMistralOCR({
|
uploadMistralOCR({
|
||||||
req,
|
req,
|
||||||
|
appConfig,
|
||||||
file,
|
file,
|
||||||
loadAuthValues: mockLoadAuthValues,
|
loadAuthValues: mockLoadAuthValues,
|
||||||
}),
|
}),
|
||||||
|
|
@ -931,16 +936,15 @@ describe('MistralOCR Service', () => {
|
||||||
|
|
||||||
const req = {
|
const req = {
|
||||||
user: { id: 'user123' },
|
user: { id: 'user123' },
|
||||||
app: {
|
} as unknown as ExpressRequest;
|
||||||
locals: {
|
|
||||||
|
const appConfig = {
|
||||||
ocr: {
|
ocr: {
|
||||||
apiKey: 'OCR_API_KEY',
|
apiKey: 'OCR_API_KEY',
|
||||||
baseURL: 'OCR_BASEURL',
|
baseURL: 'OCR_BASEURL',
|
||||||
mistralModel: 'mistral-ocr-latest',
|
mistralModel: 'mistral-ocr-latest',
|
||||||
},
|
},
|
||||||
},
|
} as AppConfig;
|
||||||
},
|
|
||||||
} as unknown as ExpressRequest;
|
|
||||||
|
|
||||||
const file = {
|
const file = {
|
||||||
path: '/tmp/upload/file.pdf',
|
path: '/tmp/upload/file.pdf',
|
||||||
|
|
@ -950,6 +954,7 @@ describe('MistralOCR Service', () => {
|
||||||
|
|
||||||
const result = await uploadMistralOCR({
|
const result = await uploadMistralOCR({
|
||||||
req,
|
req,
|
||||||
|
appConfig,
|
||||||
file,
|
file,
|
||||||
loadAuthValues: mockLoadAuthValues,
|
loadAuthValues: mockLoadAuthValues,
|
||||||
});
|
});
|
||||||
|
|
@ -1019,17 +1024,16 @@ describe('MistralOCR Service', () => {
|
||||||
|
|
||||||
const req = {
|
const req = {
|
||||||
user: { id: 'user123' },
|
user: { id: 'user123' },
|
||||||
app: {
|
} as unknown as ExpressRequest;
|
||||||
locals: {
|
|
||||||
|
const appConfig = {
|
||||||
ocr: {
|
ocr: {
|
||||||
// Direct values that should be used as-is, without variable substitution
|
// Direct values that should be used as-is, without variable substitution
|
||||||
apiKey: 'actual-api-key-value',
|
apiKey: 'actual-api-key-value',
|
||||||
baseURL: 'https://direct-api-url.mistral.ai/v1',
|
baseURL: 'https://direct-api-url.mistral.ai/v1',
|
||||||
mistralModel: 'mistral-direct-model',
|
mistralModel: 'mistral-direct-model',
|
||||||
},
|
},
|
||||||
},
|
} as AppConfig;
|
||||||
},
|
|
||||||
} as unknown as ExpressRequest;
|
|
||||||
|
|
||||||
const file = {
|
const file = {
|
||||||
path: '/tmp/upload/file.pdf',
|
path: '/tmp/upload/file.pdf',
|
||||||
|
|
@ -1039,6 +1043,7 @@ describe('MistralOCR Service', () => {
|
||||||
|
|
||||||
const result = await uploadMistralOCR({
|
const result = await uploadMistralOCR({
|
||||||
req,
|
req,
|
||||||
|
appConfig,
|
||||||
file,
|
file,
|
||||||
loadAuthValues: mockLoadAuthValues,
|
loadAuthValues: mockLoadAuthValues,
|
||||||
});
|
});
|
||||||
|
|
@ -1133,17 +1138,16 @@ describe('MistralOCR Service', () => {
|
||||||
|
|
||||||
const req = {
|
const req = {
|
||||||
user: { id: 'user123' },
|
user: { id: 'user123' },
|
||||||
app: {
|
} as unknown as ExpressRequest;
|
||||||
locals: {
|
|
||||||
|
const appConfig = {
|
||||||
ocr: {
|
ocr: {
|
||||||
// Empty string values - should fall back to defaults
|
// Empty string values - should fall back to defaults
|
||||||
apiKey: '',
|
apiKey: '',
|
||||||
baseURL: '',
|
baseURL: '',
|
||||||
mistralModel: '',
|
mistralModel: '',
|
||||||
},
|
},
|
||||||
},
|
} as AppConfig;
|
||||||
},
|
|
||||||
} as unknown as ExpressRequest;
|
|
||||||
|
|
||||||
const file = {
|
const file = {
|
||||||
path: '/tmp/upload/file.pdf',
|
path: '/tmp/upload/file.pdf',
|
||||||
|
|
@ -1153,6 +1157,7 @@ describe('MistralOCR Service', () => {
|
||||||
|
|
||||||
const result = await uploadMistralOCR({
|
const result = await uploadMistralOCR({
|
||||||
req,
|
req,
|
||||||
|
appConfig,
|
||||||
file,
|
file,
|
||||||
loadAuthValues: mockLoadAuthValues,
|
loadAuthValues: mockLoadAuthValues,
|
||||||
});
|
});
|
||||||
|
|
@ -1276,16 +1281,15 @@ describe('MistralOCR Service', () => {
|
||||||
|
|
||||||
const req = {
|
const req = {
|
||||||
user: { id: 'user123' },
|
user: { id: 'user123' },
|
||||||
app: {
|
} as unknown as ExpressRequest;
|
||||||
locals: {
|
|
||||||
|
const appConfig = {
|
||||||
ocr: {
|
ocr: {
|
||||||
apiKey: '${AZURE_MISTRAL_OCR_API_KEY}',
|
apiKey: '${AZURE_MISTRAL_OCR_API_KEY}',
|
||||||
baseURL: 'https://endpoint.models.ai.azure.com/v1',
|
baseURL: 'https://endpoint.models.ai.azure.com/v1',
|
||||||
mistralModel: 'mistral-ocr-2503',
|
mistralModel: 'mistral-ocr-2503',
|
||||||
},
|
},
|
||||||
},
|
} as AppConfig;
|
||||||
},
|
|
||||||
} as unknown as ExpressRequest;
|
|
||||||
|
|
||||||
const file = {
|
const file = {
|
||||||
path: '/tmp/upload/file.pdf',
|
path: '/tmp/upload/file.pdf',
|
||||||
|
|
@ -1295,6 +1299,7 @@ describe('MistralOCR Service', () => {
|
||||||
|
|
||||||
await uploadMistralOCR({
|
await uploadMistralOCR({
|
||||||
req,
|
req,
|
||||||
|
appConfig,
|
||||||
file,
|
file,
|
||||||
loadAuthValues: mockLoadAuthValues,
|
loadAuthValues: mockLoadAuthValues,
|
||||||
});
|
});
|
||||||
|
|
@ -1360,16 +1365,15 @@ describe('MistralOCR Service', () => {
|
||||||
|
|
||||||
const req = {
|
const req = {
|
||||||
user: { id: 'user456' },
|
user: { id: 'user456' },
|
||||||
app: {
|
} as unknown as ExpressRequest;
|
||||||
locals: {
|
|
||||||
|
const appConfig = {
|
||||||
ocr: {
|
ocr: {
|
||||||
apiKey: 'hardcoded-api-key-12345',
|
apiKey: 'hardcoded-api-key-12345',
|
||||||
baseURL: '${CUSTOM_OCR_BASEURL}',
|
baseURL: '${CUSTOM_OCR_BASEURL}',
|
||||||
mistralModel: 'mistral-ocr-latest',
|
mistralModel: 'mistral-ocr-latest',
|
||||||
},
|
},
|
||||||
},
|
} as AppConfig;
|
||||||
},
|
|
||||||
} as unknown as ExpressRequest;
|
|
||||||
|
|
||||||
const file = {
|
const file = {
|
||||||
path: '/tmp/upload/file.pdf',
|
path: '/tmp/upload/file.pdf',
|
||||||
|
|
@ -1379,6 +1383,7 @@ describe('MistralOCR Service', () => {
|
||||||
|
|
||||||
await uploadMistralOCR({
|
await uploadMistralOCR({
|
||||||
req,
|
req,
|
||||||
|
appConfig,
|
||||||
file,
|
file,
|
||||||
loadAuthValues: mockLoadAuthValues,
|
loadAuthValues: mockLoadAuthValues,
|
||||||
});
|
});
|
||||||
|
|
@ -1484,16 +1489,15 @@ describe('MistralOCR Service', () => {
|
||||||
|
|
||||||
const req = {
|
const req = {
|
||||||
user: { id: 'user123' },
|
user: { id: 'user123' },
|
||||||
app: {
|
} as unknown as ExpressRequest;
|
||||||
locals: {
|
|
||||||
|
const appConfig = {
|
||||||
ocr: {
|
ocr: {
|
||||||
apiKey: '${OCR_API_KEY}',
|
apiKey: '${OCR_API_KEY}',
|
||||||
baseURL: '${OCR_BASEURL}',
|
baseURL: '${OCR_BASEURL}',
|
||||||
mistralModel: 'mistral-ocr-latest',
|
mistralModel: 'mistral-ocr-latest',
|
||||||
},
|
},
|
||||||
},
|
} as AppConfig;
|
||||||
},
|
|
||||||
} as unknown as ExpressRequest;
|
|
||||||
|
|
||||||
const file = {
|
const file = {
|
||||||
path: '/tmp/upload/file.pdf',
|
path: '/tmp/upload/file.pdf',
|
||||||
|
|
@ -1503,6 +1507,7 @@ describe('MistralOCR Service', () => {
|
||||||
|
|
||||||
await uploadMistralOCR({
|
await uploadMistralOCR({
|
||||||
req,
|
req,
|
||||||
|
appConfig,
|
||||||
file,
|
file,
|
||||||
loadAuthValues: mockLoadAuthValues,
|
loadAuthValues: mockLoadAuthValues,
|
||||||
});
|
});
|
||||||
|
|
@ -1553,16 +1558,15 @@ describe('MistralOCR Service', () => {
|
||||||
|
|
||||||
const req = {
|
const req = {
|
||||||
user: { id: 'user123' },
|
user: { id: 'user123' },
|
||||||
app: {
|
} as unknown as ExpressRequest;
|
||||||
locals: {
|
|
||||||
|
const appConfig = {
|
||||||
ocr: {
|
ocr: {
|
||||||
apiKey: '${OCR_API_KEY}',
|
apiKey: '${OCR_API_KEY}',
|
||||||
baseURL: '${OCR_BASEURL}',
|
baseURL: '${OCR_BASEURL}',
|
||||||
mistralModel: 'mistral-ocr-latest',
|
mistralModel: 'mistral-ocr-latest',
|
||||||
},
|
},
|
||||||
},
|
} as AppConfig;
|
||||||
},
|
|
||||||
} as unknown as ExpressRequest;
|
|
||||||
|
|
||||||
const file = {
|
const file = {
|
||||||
path: '/tmp/upload/file.pdf',
|
path: '/tmp/upload/file.pdf',
|
||||||
|
|
@ -1573,6 +1577,7 @@ describe('MistralOCR Service', () => {
|
||||||
await expect(
|
await expect(
|
||||||
uploadMistralOCR({
|
uploadMistralOCR({
|
||||||
req,
|
req,
|
||||||
|
appConfig,
|
||||||
file,
|
file,
|
||||||
loadAuthValues: mockLoadAuthValues,
|
loadAuthValues: mockLoadAuthValues,
|
||||||
}),
|
}),
|
||||||
|
|
@ -1641,16 +1646,15 @@ describe('MistralOCR Service', () => {
|
||||||
|
|
||||||
const req = {
|
const req = {
|
||||||
user: { id: 'user123' },
|
user: { id: 'user123' },
|
||||||
app: {
|
} as unknown as ExpressRequest;
|
||||||
locals: {
|
|
||||||
|
const appConfig = {
|
||||||
ocr: {
|
ocr: {
|
||||||
apiKey: '${OCR_API_KEY}',
|
apiKey: '${OCR_API_KEY}',
|
||||||
baseURL: '${OCR_BASEURL}',
|
baseURL: '${OCR_BASEURL}',
|
||||||
mistralModel: 'mistral-ocr-latest',
|
mistralModel: 'mistral-ocr-latest',
|
||||||
},
|
},
|
||||||
},
|
} as AppConfig;
|
||||||
},
|
|
||||||
} as unknown as ExpressRequest;
|
|
||||||
|
|
||||||
const file = {
|
const file = {
|
||||||
path: '/tmp/upload/file.pdf',
|
path: '/tmp/upload/file.pdf',
|
||||||
|
|
@ -1661,6 +1665,7 @@ describe('MistralOCR Service', () => {
|
||||||
// Should not throw even if delete fails
|
// Should not throw even if delete fails
|
||||||
const result = await uploadMistralOCR({
|
const result = await uploadMistralOCR({
|
||||||
req,
|
req,
|
||||||
|
appConfig,
|
||||||
file,
|
file,
|
||||||
loadAuthValues: mockLoadAuthValues,
|
loadAuthValues: mockLoadAuthValues,
|
||||||
});
|
});
|
||||||
|
|
@ -1701,16 +1706,15 @@ describe('MistralOCR Service', () => {
|
||||||
|
|
||||||
const req = {
|
const req = {
|
||||||
user: { id: 'user123' },
|
user: { id: 'user123' },
|
||||||
app: {
|
} as unknown as ExpressRequest;
|
||||||
locals: {
|
|
||||||
|
const appConfig = {
|
||||||
ocr: {
|
ocr: {
|
||||||
apiKey: '${OCR_API_KEY}',
|
apiKey: '${OCR_API_KEY}',
|
||||||
baseURL: '${OCR_BASEURL}',
|
baseURL: '${OCR_BASEURL}',
|
||||||
mistralModel: 'mistral-ocr-latest',
|
mistralModel: 'mistral-ocr-latest',
|
||||||
},
|
},
|
||||||
},
|
} as AppConfig;
|
||||||
},
|
|
||||||
} as unknown as ExpressRequest;
|
|
||||||
|
|
||||||
const file = {
|
const file = {
|
||||||
path: '/tmp/upload/file.pdf',
|
path: '/tmp/upload/file.pdf',
|
||||||
|
|
@ -1721,6 +1725,7 @@ describe('MistralOCR Service', () => {
|
||||||
await expect(
|
await expect(
|
||||||
uploadMistralOCR({
|
uploadMistralOCR({
|
||||||
req,
|
req,
|
||||||
|
appConfig,
|
||||||
file,
|
file,
|
||||||
loadAuthValues: mockLoadAuthValues,
|
loadAuthValues: mockLoadAuthValues,
|
||||||
}),
|
}),
|
||||||
|
|
@ -1775,16 +1780,15 @@ describe('MistralOCR Service', () => {
|
||||||
|
|
||||||
const req = {
|
const req = {
|
||||||
user: { id: 'user123' },
|
user: { id: 'user123' },
|
||||||
app: {
|
} as unknown as ExpressRequest;
|
||||||
locals: {
|
|
||||||
|
const appConfig = {
|
||||||
ocr: {
|
ocr: {
|
||||||
apiKey: '${OCR_API_KEY}',
|
apiKey: '${OCR_API_KEY}',
|
||||||
baseURL: '${OCR_BASEURL}',
|
baseURL: '${OCR_BASEURL}',
|
||||||
mistralModel: 'mistral-ocr-latest',
|
mistralModel: 'mistral-ocr-latest',
|
||||||
},
|
},
|
||||||
},
|
} as AppConfig;
|
||||||
},
|
|
||||||
} as unknown as ExpressRequest;
|
|
||||||
|
|
||||||
const file = {
|
const file = {
|
||||||
path: '/tmp/upload/azure-file.pdf',
|
path: '/tmp/upload/azure-file.pdf',
|
||||||
|
|
@ -1794,6 +1798,7 @@ describe('MistralOCR Service', () => {
|
||||||
|
|
||||||
const result = await uploadAzureMistralOCR({
|
const result = await uploadAzureMistralOCR({
|
||||||
req,
|
req,
|
||||||
|
appConfig,
|
||||||
file,
|
file,
|
||||||
loadAuthValues: mockLoadAuthValues,
|
loadAuthValues: mockLoadAuthValues,
|
||||||
});
|
});
|
||||||
|
|
@ -1851,16 +1856,15 @@ describe('MistralOCR Service', () => {
|
||||||
|
|
||||||
const req = {
|
const req = {
|
||||||
user: { id: 'user123' },
|
user: { id: 'user123' },
|
||||||
app: {
|
} as unknown as ExpressRequest;
|
||||||
locals: {
|
|
||||||
|
const appConfig = {
|
||||||
ocr: {
|
ocr: {
|
||||||
apiKey: '${AZURE_MISTRAL_OCR_API_KEY}',
|
apiKey: '${AZURE_MISTRAL_OCR_API_KEY}',
|
||||||
baseURL: 'https://endpoint.models.ai.azure.com/v1',
|
baseURL: 'https://endpoint.models.ai.azure.com/v1',
|
||||||
mistralModel: 'mistral-ocr-2503',
|
mistralModel: 'mistral-ocr-2503',
|
||||||
},
|
},
|
||||||
},
|
} as AppConfig;
|
||||||
},
|
|
||||||
} as unknown as ExpressRequest;
|
|
||||||
|
|
||||||
const file = {
|
const file = {
|
||||||
path: '/tmp/upload/file.pdf',
|
path: '/tmp/upload/file.pdf',
|
||||||
|
|
@ -1870,6 +1874,7 @@ describe('MistralOCR Service', () => {
|
||||||
|
|
||||||
await uploadAzureMistralOCR({
|
await uploadAzureMistralOCR({
|
||||||
req,
|
req,
|
||||||
|
appConfig,
|
||||||
file,
|
file,
|
||||||
loadAuthValues: mockLoadAuthValues,
|
loadAuthValues: mockLoadAuthValues,
|
||||||
});
|
});
|
||||||
|
|
@ -1915,16 +1920,15 @@ describe('MistralOCR Service', () => {
|
||||||
|
|
||||||
const req = {
|
const req = {
|
||||||
user: { id: 'user456' },
|
user: { id: 'user456' },
|
||||||
app: {
|
} as unknown as ExpressRequest;
|
||||||
locals: {
|
|
||||||
|
const appConfig = {
|
||||||
ocr: {
|
ocr: {
|
||||||
apiKey: 'hardcoded-api-key-12345',
|
apiKey: 'hardcoded-api-key-12345',
|
||||||
baseURL: '${CUSTOM_OCR_BASEURL}',
|
baseURL: '${CUSTOM_OCR_BASEURL}',
|
||||||
mistralModel: 'mistral-ocr-latest',
|
mistralModel: 'mistral-ocr-latest',
|
||||||
},
|
},
|
||||||
},
|
} as AppConfig;
|
||||||
},
|
|
||||||
} as unknown as ExpressRequest;
|
|
||||||
|
|
||||||
const file = {
|
const file = {
|
||||||
path: '/tmp/upload/file.pdf',
|
path: '/tmp/upload/file.pdf',
|
||||||
|
|
@ -1934,6 +1938,7 @@ describe('MistralOCR Service', () => {
|
||||||
|
|
||||||
await uploadAzureMistralOCR({
|
await uploadAzureMistralOCR({
|
||||||
req,
|
req,
|
||||||
|
appConfig,
|
||||||
file,
|
file,
|
||||||
loadAuthValues: mockLoadAuthValues,
|
loadAuthValues: mockLoadAuthValues,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import type {
|
||||||
MistralOCRUploadResult,
|
MistralOCRUploadResult,
|
||||||
MistralOCRError,
|
MistralOCRError,
|
||||||
OCRResultPage,
|
OCRResultPage,
|
||||||
|
AppConfig,
|
||||||
OCRResult,
|
OCRResult,
|
||||||
OCRImage,
|
OCRImage,
|
||||||
} from '~/types';
|
} from '~/types';
|
||||||
|
|
@ -42,14 +43,10 @@ interface GoogleServiceAccount {
|
||||||
|
|
||||||
/** Helper type for OCR request context */
|
/** Helper type for OCR request context */
|
||||||
interface OCRContext {
|
interface OCRContext {
|
||||||
req: Pick<ServerRequest, 'user' | 'app'> & {
|
req: Pick<ServerRequest, 'user'> & {
|
||||||
user?: { id: string };
|
user?: { id: string };
|
||||||
app: {
|
|
||||||
locals?: {
|
|
||||||
ocr?: TCustomConfig['ocr'];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
appConfig: AppConfig;
|
||||||
file: Express.Multer.File;
|
file: Express.Multer.File;
|
||||||
loadAuthValues: (params: {
|
loadAuthValues: (params: {
|
||||||
userId: string;
|
userId: string;
|
||||||
|
|
@ -241,7 +238,7 @@ async function resolveConfigValue(
|
||||||
* Loads authentication configuration from OCR config
|
* Loads authentication configuration from OCR config
|
||||||
*/
|
*/
|
||||||
async function loadAuthConfig(context: OCRContext): Promise<AuthConfig> {
|
async function loadAuthConfig(context: OCRContext): Promise<AuthConfig> {
|
||||||
const ocrConfig = context.req.app.locals?.ocr;
|
const ocrConfig = context.appConfig?.ocr;
|
||||||
const apiKeyConfig = ocrConfig?.apiKey || '';
|
const apiKeyConfig = ocrConfig?.apiKey || '';
|
||||||
const baseURLConfig = ocrConfig?.baseURL || '';
|
const baseURLConfig = ocrConfig?.baseURL || '';
|
||||||
|
|
||||||
|
|
@ -357,6 +354,7 @@ function createOCRError(error: unknown, baseMessage: string): Error {
|
||||||
* @param params - The params object.
|
* @param params - The params object.
|
||||||
* @param params.req - The request object from Express. It should have a `user` property with an `id`
|
* @param params.req - The request object from Express. It should have a `user` property with an `id`
|
||||||
* representing the user
|
* representing the user
|
||||||
|
* @param params.appConfig - Application configuration object
|
||||||
* @param params.file - The file object, which is part of the request. The file object should
|
* @param params.file - The file object, which is part of the request. The file object should
|
||||||
* have a `mimetype` property that tells us the file type
|
* have a `mimetype` property that tells us the file type
|
||||||
* @param params.loadAuthValues - Function to load authentication values
|
* @param params.loadAuthValues - Function to load authentication values
|
||||||
|
|
@ -372,7 +370,7 @@ export const uploadMistralOCR = async (context: OCRContext): Promise<MistralOCRU
|
||||||
const authConfig = await loadAuthConfig(context);
|
const authConfig = await loadAuthConfig(context);
|
||||||
apiKey = authConfig.apiKey;
|
apiKey = authConfig.apiKey;
|
||||||
baseURL = authConfig.baseURL;
|
baseURL = authConfig.baseURL;
|
||||||
const model = getModelConfig(context.req.app.locals?.ocr);
|
const model = getModelConfig(context.appConfig?.ocr);
|
||||||
|
|
||||||
const mistralFile = await uploadDocumentToMistral({
|
const mistralFile = await uploadDocumentToMistral({
|
||||||
filePath: context.file.path,
|
filePath: context.file.path,
|
||||||
|
|
@ -430,6 +428,7 @@ export const uploadMistralOCR = async (context: OCRContext): Promise<MistralOCRU
|
||||||
* @param params - The params object.
|
* @param params - The params object.
|
||||||
* @param params.req - The request object from Express. It should have a `user` property with an `id`
|
* @param params.req - The request object from Express. It should have a `user` property with an `id`
|
||||||
* representing the user
|
* representing the user
|
||||||
|
* @param params.appConfig - Application configuration object
|
||||||
* @param params.file - The file object, which is part of the request. The file object should
|
* @param params.file - The file object, which is part of the request. The file object should
|
||||||
* have a `mimetype` property that tells us the file type
|
* have a `mimetype` property that tells us the file type
|
||||||
* @param params.loadAuthValues - Function to load authentication values
|
* @param params.loadAuthValues - Function to load authentication values
|
||||||
|
|
@ -441,7 +440,7 @@ export const uploadAzureMistralOCR = async (
|
||||||
): Promise<MistralOCRUploadResult> => {
|
): Promise<MistralOCRUploadResult> => {
|
||||||
try {
|
try {
|
||||||
const { apiKey, baseURL } = await loadAuthConfig(context);
|
const { apiKey, baseURL } = await loadAuthConfig(context);
|
||||||
const model = getModelConfig(context.req.app.locals?.ocr);
|
const model = getModelConfig(context.appConfig?.ocr);
|
||||||
|
|
||||||
const buffer = fs.readFileSync(context.file.path);
|
const buffer = fs.readFileSync(context.file.path);
|
||||||
const base64 = buffer.toString('base64');
|
const base64 = buffer.toString('base64');
|
||||||
|
|
@ -644,6 +643,7 @@ async function performGoogleVertexOCR({
|
||||||
* @param params - The params object.
|
* @param params - The params object.
|
||||||
* @param params.req - The request object from Express. It should have a `user` property with an `id`
|
* @param params.req - The request object from Express. It should have a `user` property with an `id`
|
||||||
* representing the user
|
* representing the user
|
||||||
|
* @param params.appConfig - Application configuration object
|
||||||
* @param params.file - The file object, which is part of the request. The file object should
|
* @param params.file - The file object, which is part of the request. The file object should
|
||||||
* have a `mimetype` property that tells us the file type
|
* have a `mimetype` property that tells us the file type
|
||||||
* @param params.loadAuthValues - Function to load authentication values
|
* @param params.loadAuthValues - Function to load authentication values
|
||||||
|
|
@ -655,7 +655,7 @@ export const uploadGoogleVertexMistralOCR = async (
|
||||||
): Promise<MistralOCRUploadResult> => {
|
): Promise<MistralOCRUploadResult> => {
|
||||||
try {
|
try {
|
||||||
const { serviceAccount, accessToken } = await loadGoogleAuthConfig();
|
const { serviceAccount, accessToken } = await loadGoogleAuthConfig();
|
||||||
const model = getModelConfig(context.req.app.locals?.ocr);
|
const model = getModelConfig(context.appConfig?.ocr);
|
||||||
|
|
||||||
const buffer = fs.readFileSync(context.file.path);
|
const buffer = fs.readFileSync(context.file.path);
|
||||||
const base64 = buffer.toString('base64');
|
const base64 = buffer.toString('base64');
|
||||||
|
|
|
||||||
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 './azure';
|
||||||
export * from './balance';
|
export * from './balance';
|
||||||
export * from './events';
|
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 { BindToolsInput } from '@langchain/core/language_models/chat_models';
|
||||||
import type { OpenAIClientOptions, Providers } from '@librechat/agents';
|
import type { OpenAIClientOptions, Providers } from '@librechat/agents';
|
||||||
import type { AzureOptions } from './azure';
|
import type { AzureOptions } from './azure';
|
||||||
|
import type { AppConfig } from './config';
|
||||||
|
|
||||||
export type OpenAIParameters = z.infer<typeof openAISchema>;
|
export type OpenAIParameters = z.infer<typeof openAISchema>;
|
||||||
|
|
||||||
|
|
@ -85,6 +86,7 @@ export type CheckUserKeyExpiryFunction = (expiresAt: string, endpoint: string) =
|
||||||
*/
|
*/
|
||||||
export interface InitializeOpenAIOptionsParams {
|
export interface InitializeOpenAIOptionsParams {
|
||||||
req: RequestData;
|
req: RequestData;
|
||||||
|
appConfig: AppConfig;
|
||||||
overrideModel?: string;
|
overrideModel?: string;
|
||||||
overrideEndpoint?: string;
|
overrideEndpoint?: string;
|
||||||
endpointOption: Partial<TEndpointOption>;
|
endpointOption: Partial<TEndpointOption>;
|
||||||
|
|
|
||||||
|
|
@ -1213,6 +1213,10 @@ export enum CacheKeys {
|
||||||
* Key for the static config namespace.
|
* Key for the static config namespace.
|
||||||
*/
|
*/
|
||||||
STATIC_CONFIG = 'STATIC_CONFIG',
|
STATIC_CONFIG = 'STATIC_CONFIG',
|
||||||
|
/**
|
||||||
|
* Key for the app config namespace.
|
||||||
|
*/
|
||||||
|
APP_CONFIG = 'APP_CONFIG',
|
||||||
/**
|
/**
|
||||||
* Key for accessing Abort Keys
|
* Key for accessing Abort Keys
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue