diff --git a/api/server/controllers/PluginController.js b/api/server/controllers/PluginController.js index cac0b88bcf..5bb34671f8 100644 --- a/api/server/controllers/PluginController.js +++ b/api/server/controllers/PluginController.js @@ -55,24 +55,26 @@ const getAvailablePluginsController = async (req, res) => { return; } - /** @type {{ filteredTools: string[] }} */ - const { filteredTools = [] } = req.app.locals; - + /** @type {{ filteredTools: string[], includedTools: string[] }} */ + const { filteredTools = [], includedTools = [] } = req.app.locals; const pluginManifest = await fs.readFile(req.app.locals.paths.pluginManifest, 'utf8'); - const jsonData = JSON.parse(pluginManifest); - /** @type {TPlugin[]} */ + const uniquePlugins = filterUniquePlugins(jsonData); - const authenticatedPlugins = uniquePlugins.map((plugin) => { - if (isPluginAuthenticated(plugin)) { - return { ...plugin, authenticated: true }; - } else { - return plugin; - } - }); + let authenticatedPlugins = []; + for (const plugin of uniquePlugins) { + authenticatedPlugins.push( + isPluginAuthenticated(plugin) ? { ...plugin, authenticated: true } : plugin, + ); + } let plugins = await addOpenAPISpecs(authenticatedPlugins); - plugins = plugins.filter((plugin) => !filteredTools.includes(plugin.pluginKey)); + + if (includedTools.length > 0) { + plugins = plugins.filter((plugin) => includedTools.includes(plugin.pluginKey)); + } else { + plugins = plugins.filter((plugin) => !filteredTools.includes(plugin.pluginKey)); + } await cache.set(CacheKeys.PLUGINS, plugins); res.status(200).json(plugins); diff --git a/api/server/services/AppService.js b/api/server/services/AppService.js index bcdd089481..4163a3df87 100644 --- a/api/server/services/AppService.js +++ b/api/server/services/AppService.js @@ -21,6 +21,7 @@ const AppService = async (app) => { const configDefaults = getConfigDefaults(); const filteredTools = config.filteredTools; + const includedTools = config.includedTools; const fileStrategy = config.fileStrategy ?? configDefaults.fileStrategy; const imageOutputType = config?.imageOutputType ?? configDefaults.imageOutputType; @@ -37,23 +38,26 @@ const AppService = async (app) => { const availableTools = loadAndFormatTools({ directory: paths.structuredTools, adminFilter: filteredTools, + adminIncluded: includedTools, }); const socialLogins = config?.registration?.socialLogins ?? configDefaults?.registration?.socialLogins; const interfaceConfig = loadDefaultInterface(config, configDefaults); - if (!Object.keys(config).length) { - app.locals = { - paths, - fileStrategy, - socialLogins, - filteredTools, - availableTools, - imageOutputType, - interfaceConfig, - }; + const defaultLocals = { + paths, + fileStrategy, + socialLogins, + filteredTools, + includedTools, + availableTools, + imageOutputType, + interfaceConfig, + }; + if (!Object.keys(config).length) { + app.locals = defaultLocals; return; } @@ -79,13 +83,7 @@ const AppService = async (app) => { } app.locals = { - paths, - socialLogins, - fileStrategy, - filteredTools, - availableTools, - imageOutputType, - interfaceConfig, + ...defaultLocals, modelSpecs: config.modelSpecs, fileConfig: config?.fileConfig, secureImageLinks: config?.secureImageLinks, diff --git a/api/server/services/Config/loadCustomConfig.js b/api/server/services/Config/loadCustomConfig.js index dc3105c392..1b5b287066 100644 --- a/api/server/services/Config/loadCustomConfig.js +++ b/api/server/services/Config/loadCustomConfig.js @@ -37,7 +37,7 @@ async function loadCustomConfig() { if (!customConfig) { i === 0 && logger.info( - 'Custom config file missing or YAML format invalid.\n\nCheck out the latest config file guide for configurable options and features.\nhttps://docs.librechat.ai/install/configuration/custom_config.html\n\n', + 'Custom config file missing or YAML format invalid.\n\nCheck out the latest config file guide for configurable options and features.\nhttps://www.librechat.ai/docs/configuration/librechat_yaml\n\n', ); i === 0 && i++; return null; @@ -72,7 +72,7 @@ Please specify a correct \`imageOutputType\` value (case-sensitive). - ${EImageOutputType.WEBP} Refer to the latest config file guide for more information: - https://docs.librechat.ai/install/configuration/custom_config.html`, + https://www.librechat.ai/docs/configuration/librechat_yaml`, ); } if (!result.success) { diff --git a/api/server/services/ToolService.js b/api/server/services/ToolService.js index 0a10a9d394..149afc5df0 100644 --- a/api/server/services/ToolService.js +++ b/api/server/services/ToolService.js @@ -39,57 +39,65 @@ const filteredTools = new Set([ * @param {object} params - The parameters for the function. * @param {string} params.directory - The directory path where the tools are located. * @param {Array} [params.adminFilter=[]] - Array of admin-defined tool keys to exclude from loading. + * @param {Array} [params.adminIncluded=[]] - Array of admin-defined tool keys to include from loading. * @returns {Record} An object mapping each tool's plugin key to its instance. */ -function loadAndFormatTools({ directory, adminFilter = [] }) { +function loadAndFormatTools({ directory, adminFilter = [], adminIncluded = [] }) { const filter = new Set([...adminFilter, ...filteredTools]); + const included = new Set(adminIncluded); const tools = []; /* Structured Tools Directory */ const files = fs.readdirSync(directory); - for (const file of files) { - if (file.endsWith('.js') && !filter.has(file)) { - const filePath = path.join(directory, file); - let ToolClass = null; - try { - ToolClass = require(filePath); - } catch (error) { - logger.error(`[loadAndFormatTools] Error loading tool from ${filePath}:`, error); - continue; - } - - if (!ToolClass) { - continue; - } - - if (ToolClass.prototype instanceof StructuredTool) { - /** @type {StructuredTool | null} */ - let toolInstance = null; - try { - toolInstance = new ToolClass({ override: true }); - } catch (error) { - logger.error( - `[loadAndFormatTools] Error initializing \`${file}\` tool; if it requires authentication, is the \`override\` field configured?`, - error, - ); - continue; - } - - if (!toolInstance) { - continue; - } - - const formattedTool = formatToOpenAIAssistantTool(toolInstance); - tools.push(formattedTool); - } - } + if (included.size > 0 && adminFilter.length > 0) { + logger.warn( + 'Both `includedTools` and `filteredTools` are defined; `filteredTools` will be ignored.', + ); } - /** - * Basic Tools; schema: { input: string } - */ - const basicToolInstances = [new Calculator()]; + for (const file of files) { + const filePath = path.join(directory, file); + if (!file.endsWith('.js') || (filter.has(file) && included.size === 0)) { + continue; + } + let ToolClass = null; + try { + ToolClass = require(filePath); + } catch (error) { + logger.error(`[loadAndFormatTools] Error loading tool from ${filePath}:`, error); + continue; + } + + if (!ToolClass || !(ToolClass.prototype instanceof StructuredTool)) { + continue; + } + + if (included.size > 0 && !included.has(file)) { + continue; + } + + let toolInstance = null; + try { + toolInstance = new ToolClass({ override: true }); + } catch (error) { + logger.error( + `[loadAndFormatTools] Error initializing \`${file}\` tool; if it requires authentication, is the \`override\` field configured?`, + error, + ); + continue; + } + + if (!toolInstance) { + continue; + } + + const formattedTool = formatToOpenAIAssistantTool(toolInstance); + tools.push(formattedTool); + } + + /** Basic Tools; schema: { input: string } */ + const basicToolInstances = [new Calculator()]; for (const toolInstance of basicToolInstances) { const formattedTool = formatToOpenAIAssistantTool(toolInstance); tools.push(formattedTool); diff --git a/api/server/services/start/checks.js b/api/server/services/start/checks.js index 3593a5bafb..0dbbb23497 100644 --- a/api/server/services/start/checks.js +++ b/api/server/services/start/checks.js @@ -99,7 +99,12 @@ function checkAzureVariables() { function checkConfig(config) { if (config.version !== Constants.CONFIG_VERSION) { logger.info( - `\nOutdated Config version: ${config.version}. Current version: ${Constants.CONFIG_VERSION}\n\nCheck out the latest config file guide for new options and features.\nhttps://docs.librechat.ai/install/configuration/custom_config.html\n\n`, + `\nOutdated Config version: ${config.version} +Latest version: ${Constants.CONFIG_VERSION} + + Check out the Config changelogs for the latest options and features added. + + https://www.librechat.ai/changelog\n\n`, ); } } diff --git a/config/list-balances.js b/config/list-balances.js index 6021ed78bc..0878d19a57 100644 --- a/config/list-balances.js +++ b/config/list-balances.js @@ -19,9 +19,9 @@ const connect = require('./connect'); for (const user of users) { let balance = await Balance.findOne({ user: user._id }); if (balance !== null) { - console.green(`User ${user.name} has a balance of ${balance.tokenCredits}`); + console.green(`User ${user.name} (${user.email}) has a balance of ${balance.tokenCredits}`); } else { - console.yellow(`User ${user.name} has no balance`); + console.yellow(`User ${user.name} (${user.email}) has no balance`); } } diff --git a/config/user-stats.js b/config/user-stats.js index 9b8cdfb85b..efcf3b58b7 100644 --- a/config/user-stats.js +++ b/config/user-stats.js @@ -24,6 +24,7 @@ const connect = require('./connect'); userData.push({ User: user.name, + Email: user.email, Conversations: conversationsCount, Messages: messagesCount, }); diff --git a/librechat.example.yaml b/librechat.example.yaml index 0da47642e4..c83de80aea 100644 --- a/librechat.example.yaml +++ b/librechat.example.yaml @@ -1,5 +1,5 @@ # For more information, see the Configuration Guide: -# https://docs.librechat.ai/install/configuration/custom_config.html +# https://www.librechat.ai/docs/configuration/librechat_yaml # Configuration version (required) version: 1.0.9 @@ -148,4 +148,4 @@ endpoints: # serverFileSizeLimit: 100 # Global server file size limit in MB # avatarSizeLimit: 2 # Limit for user avatar image size in MB # See the Custom Configuration Guide for more information: -# https://docs.librechat.ai/install/configuration/custom_config.html +# https://www.librechat.ai/docs/configuration/librechat_yaml diff --git a/packages/data-provider/src/config.ts b/packages/data-provider/src/config.ts index a195febdda..3efd393ac0 100644 --- a/packages/data-provider/src/config.ts +++ b/packages/data-provider/src/config.ts @@ -245,6 +245,7 @@ export const configSchema = z.object({ cache: z.boolean().default(true), secureImageLinks: z.boolean().optional(), imageOutputType: z.nativeEnum(EImageOutputType).default(EImageOutputType.PNG), + includedTools: z.array(z.string()).optional(), filteredTools: z.array(z.string()).optional(), interface: z .object({ @@ -677,7 +678,7 @@ export enum Constants { /** Key for the app's version. */ VERSION = 'v0.7.2', /** Key for the Custom Config's version (librechat.yaml). */ - CONFIG_VERSION = '1.0.9', + CONFIG_VERSION = '1.1.0', /** Standard value for the first message's `parentMessageId` value, to indicate no parent exists. */ NO_PARENT = '00000000-0000-0000-0000-000000000000', /** Fixed, encoded domain length for Azure OpenAI Assistants Function name parsing. */