From 84c6721d9120fa59730c8e586b830d800b99a4b0 Mon Sep 17 00:00:00 2001 From: Educg550 Date: Tue, 16 Sep 2025 13:57:02 -0300 Subject: [PATCH 1/2] feat: agent marketplace categories synced through librechat.yaml --- .../services/Config/loadCustomConfig.js | 19 +++++ api/server/utils/agentCategory.js | 82 +++++++++++++++++++ librechat.example.yaml | 10 ++- 3 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 api/server/utils/agentCategory.js diff --git a/api/server/services/Config/loadCustomConfig.js b/api/server/services/Config/loadCustomConfig.js index 479c4bada0..5ff15715e8 100644 --- a/api/server/services/Config/loadCustomConfig.js +++ b/api/server/services/Config/loadCustomConfig.js @@ -13,6 +13,7 @@ const { validateSettingDefinitions, } = require('librechat-data-provider'); const getLogStores = require('~/cache/getLogStores'); +const { syncCategories } = require('~/server/utils/agentCategory'); const projectRoot = path.resolve(__dirname, '..', '..', '..', '..'); const defaultConfigPath = path.resolve(projectRoot, 'librechat.yaml'); @@ -115,6 +116,24 @@ https://www.librechat.ai/docs/configuration/stt_tts`); } } + // Injecting custom marketplace categories + logger.info('Checking for custom marketplace categories to sync.'); + if (customConfig.interface?.marketplace?.use) { + logger.info('Marketplace is enabled in config.'); + const marketplaceConfig = customConfig.interface.marketplace; + if (marketplaceConfig.categories) { + logger.info('Marketplace categories configuration found.'); + // enableDefaultCategories should be set as a boolean, defaulting to true if not specified + const enableDefaultCategories = + typeof marketplaceConfig.categories.enableDefaultCategories === 'boolean' + ? marketplaceConfig.categories.enableDefaultCategories + : true; + const customCategoriesList = marketplaceConfig.categories.list || []; + logger.info(`Found ${customCategoriesList.length} custom categories to sync.`); + await syncCategories(customCategoriesList, enableDefaultCategories); + } + } + (customConfig.endpoints?.custom ?? []) .filter((endpoint) => endpoint.customParams) .forEach((endpoint) => parseCustomParams(endpoint.name, endpoint.customParams)); diff --git a/api/server/utils/agentCategory.js b/api/server/utils/agentCategory.js new file mode 100644 index 0000000000..b9375fa673 --- /dev/null +++ b/api/server/utils/agentCategory.js @@ -0,0 +1,82 @@ +const { logger } = require('@librechat/data-schemas'); +const { getAllCategories, createCategory, updateCategory, deleteCategory } = require('~/models'); + +/** + * Synchronizes custom agent categories with the database. + * @param {Array} customCategoriesList - List of custom categories to be synchronized. + * @param {boolean} enableDefaultCategories - Flag indicating whether to enable default categories. + */ +async function syncCategories(customCategoriesList, enableDefaultCategories) { + logger.info('Syncing custom agent categories...'); + + // Get all categories from DB + const dbCategories = await getAllCategories(); + logger.info(`Found ${dbCategories.length} categories in the database.`); + + logger.info( + `${enableDefaultCategories ? 'Enabling' : 'Disabling'} default agent categories as per configuration.`, + ); + for (const cat of dbCategories.filter((c) => !c.custom)) { + logger.info(`${enableDefaultCategories ? 'Enabling' : 'Disabling'} category: ${cat.value}`); + await updateCategory(cat.value, { isActive: enableDefaultCategories }); + } + + // Initial order takes the max value of existing default categories + 1 + const defaultCategoriesInDb = dbCategories.filter((cat) => !cat.custom); + const initialOrder = + defaultCategoriesInDb.reduce((max, cat) => Math.max(max, cat.order || 0), 0) + 1; + logger.info(`Current order for new custom categories starts at ${initialOrder}.`); + + // Inject order to custom categories + logger.info('Assigning order to custom categories.'); + customCategoriesList = customCategoriesList.map((cat, index) => ({ + ...cat, + order: initialOrder + index, + })); + + // Delete custom categories not in the config file + const customCategoryValues = new Set(customCategoriesList.map((cat) => cat.value.toLowerCase())); + const categoriesToDelete = dbCategories.filter( + (cat) => cat.custom && !customCategoryValues.has(cat.value.toLowerCase()), + ); + for (const cat of categoriesToDelete) { + logger.info(`Deleting custom category not in config: ${cat.value}`); + await deleteCategory(cat.value); + } + + // Sync custom categories + for (const category of customCategoriesList) { + let formattedCategory; + try { + formattedCategory = { + value: category.value.toLowerCase() || 'unnamed', + label: category.value || 'Unnamed', + description: category.description || '', + isActive: true, + custom: true, + order: category.order, + }; + } catch (error) { + logger.error('Error formatting category: ', category, error); + continue; + } + + // Check if category exists by value + const existing = dbCategories.find( + (cat) => cat.value.toLowerCase() === formattedCategory.value, + ); + if (existing) { + logger.info(`Updating category: ${formattedCategory.value}`); + await updateCategory(existing.value, formattedCategory); + } else { + logger.info(`Creating category: ${formattedCategory.value}`); + await createCategory(formattedCategory); + } + } + + logger.info('Custom categories synchronized successfully.'); +} + +module.exports = { + syncCategories, +}; diff --git a/librechat.example.yaml b/librechat.example.yaml index 6f034910dc..490cf5fd31 100644 --- a/librechat.example.yaml +++ b/librechat.example.yaml @@ -94,7 +94,15 @@ interface: groups: true roles: true marketplace: - use: false + use: false + categories: + enableDefaultCategories: false + # list: + # - value: "Education" + # description: "Educational agents." + # - value: "Productivity" + # description: "Productivity agents." + fileCitations: true # Temporary chat retention period in hours (default: 720, min: 1, max: 8760) # temporaryChatRetention: 1 From b48debc3f97756d14bfa3405998112f6bee99e9f Mon Sep 17 00:00:00 2001 From: Educg550 Date: Wed, 22 Oct 2025 16:51:12 -0300 Subject: [PATCH 2/2] fix: update librechat.example.yaml, internal imports --- api/server/utils/agentCategory.js | 2 +- librechat.example.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/server/utils/agentCategory.js b/api/server/utils/agentCategory.js index b9375fa673..1d40193a57 100644 --- a/api/server/utils/agentCategory.js +++ b/api/server/utils/agentCategory.js @@ -1,5 +1,4 @@ const { logger } = require('@librechat/data-schemas'); -const { getAllCategories, createCategory, updateCategory, deleteCategory } = require('~/models'); /** * Synchronizes custom agent categories with the database. @@ -7,6 +6,7 @@ const { getAllCategories, createCategory, updateCategory, deleteCategory } = req * @param {boolean} enableDefaultCategories - Flag indicating whether to enable default categories. */ async function syncCategories(customCategoriesList, enableDefaultCategories) { + const { getAllCategories, createCategory, updateCategory, deleteCategory } = require('~/models'); logger.info('Syncing custom agent categories...'); // Get all categories from DB diff --git a/librechat.example.yaml b/librechat.example.yaml index 490cf5fd31..810f1ac376 100644 --- a/librechat.example.yaml +++ b/librechat.example.yaml @@ -96,7 +96,7 @@ interface: marketplace: use: false categories: - enableDefaultCategories: false + enableDefaultCategories: true # list: # - value: "Education" # description: "Educational agents."