refactor: consolidate agent marketplace endpoints into main agents API and improve data management consistency

- Remove dedicated marketplace controller and routes, merging functionality into main agents v1 API
  - Add countPromotedAgents function to Agent model for promoted agents count
  - Enhance getListAgents handler with marketplace filtering (category, search, promoted status)
  - Move getAgentCategories from marketplace to v1 controller with same functionality
  - Update agent mutations to invalidate marketplace queries and handle multiple permission levels
  - Improve cache management by updating all agent query variants (VIEW/EDIT permissions)
  - Consolidate agent data access patterns for better maintainability and consistency
  - Remove duplicate marketplace route definitions and middleware
This commit is contained in:
Atef Bellaaj 2025-06-23 10:47:37 +02:00 committed by Danny Avila
parent d549a64317
commit 722e40f276
No known key found for this signature in database
GPG key ID: BF31EEB2C5CA0956
14 changed files with 291 additions and 412 deletions

View file

@ -751,6 +751,14 @@ const generateActionMetadataHash = async (actionIds, actions) => {
return hashHex; return hashHex;
}; };
/**
* Counts the number of promoted agents.
* @returns {Promise<number>} - The count of promoted agents
*/
const countPromotedAgents = async () => {
const count = await Agent.countDocuments({ is_promoted: true });
return count;
};
/** /**
* Load a default agent based on the endpoint * Load a default agent based on the endpoint
@ -771,4 +779,5 @@ module.exports = {
getListAgentsByAccess, getListAgentsByAccess,
removeAgentResourceFiles, removeAgentResourceFiles,
generateActionMetadataHash, generateActionMetadataHash,
countPromotedAgents,
}; };

View file

@ -1,195 +0,0 @@
const mongoose = require('mongoose');
const { logger } = require('~/config');
const { findCategoryByValue, getCategoriesWithCounts } = require('~/models');
const { getListAgentsByAccess } = require('~/models/Agent');
const {
findAccessibleResources,
findPubliclyAccessibleResources,
} = require('~/server/services/PermissionService');
// Get the Agent model
const Agent = mongoose.model('Agent');
// Default page size for agent browsing
const DEFAULT_PAGE_SIZE = 6;
const getAgentsPagedByAccess = async (
userId,
requiredPermission,
filter,
limit = DEFAULT_PAGE_SIZE,
cursor,
) => {
const accessibleIds = await findAccessibleResources({
userId,
resourceType: 'agent',
requiredPermissions: requiredPermission,
});
const publiclyAccessibleIds = await findPubliclyAccessibleResources({
resourceType: 'agent',
requiredPermissions: requiredPermission,
});
// Use the new ACL-aware function
const data = await getListAgentsByAccess({
accessibleIds,
otherParams: filter,
limit,
after: cursor,
});
if (data?.data?.length) {
data.data = data.data.map((agent) => {
if (publiclyAccessibleIds.some((id) => id.equals(agent._id))) {
agent.isPublic = true;
}
return agent;
});
}
return data;
};
/**
* Unified marketplace agents endpoint with query string controls
* Query parameters:
* - category: string (filter by specific category - if undefined, no category filter applied)
* - search: string (search term for name/description)
* - limit: number (page size, default 6)
* - cursor: base64 string (for cursor-based pagination)
* - promoted: 0|1 (filter promoted agents, 1=promoted only, 0=exclude promoted)
* - requiredPermission: number (permission level required to access agents, default 1)
*
* @param {Object} req - Express request object
* @param {Object} res - Express response object
*/
const getMarketplaceAgents = async (req, res) => {
try {
const {
category,
search,
limit = DEFAULT_PAGE_SIZE,
cursor,
promoted,
requiredPermission = 1,
} = req.query;
const parsedLimit = parseInt(limit) || DEFAULT_PAGE_SIZE;
const parsedRequiredPermission = parseInt(requiredPermission) || 1;
// Base filter
const filter = {};
// Handle category filter - only apply if category is defined
if (category !== undefined && category.trim() !== '') {
filter.category = category;
}
// Handle promoted filter - only from query param
if (promoted === '1') {
filter.is_promoted = true;
} else if (promoted === '0') {
filter.is_promoted = { $ne: true };
}
// Handle search filter
if (search && search.trim() !== '') {
filter.$or = [
{ name: { $regex: search.trim(), $options: 'i' } },
{ description: { $regex: search.trim(), $options: 'i' } },
];
}
// Use ACL-aware function for proper permission handling
const result = await getAgentsPagedByAccess(
req.user.id,
parsedRequiredPermission, // Required permission as number
filter,
parsedLimit,
cursor,
);
// Add category info if category was specified
if (category !== undefined && category.trim() !== '') {
const categoryDoc = await findCategoryByValue(category);
result.category = {
name: category,
description: categoryDoc?.description || '',
total: result.pagination?.total || result.data?.length || 0,
};
}
// Add search info if search was performed
if (search && search.trim() !== '') {
result.query = search.trim();
}
res.status(200).json(result);
} catch (error) {
logger.error('[/Agents/Marketplace] Error fetching marketplace agents:', error);
res.status(500).json({
error: 'Failed to fetch marketplace agents',
userMessage: 'Unable to load agents. Please try refreshing the page.',
suggestion: 'Try refreshing the page or check your network connection',
});
}
};
/**
* Get all agent categories with counts
*
* @param {Object} _req - Express request object (unused)
* @param {Object} res - Express response object
*/
const getAgentCategories = async (_req, res) => {
try {
// Get categories with agent counts from database
const categories = await getCategoriesWithCounts();
// Get count of promoted agents for Top Picks
const promotedCount = await Agent.countDocuments({
is_promoted: true,
});
// Convert to marketplace format (TCategory structure)
const formattedCategories = categories.map((category) => ({
value: category.value,
label: category.label,
count: category.agentCount,
description: category.description,
}));
// Add promoted category if agents exist
if (promotedCount > 0) {
formattedCategories.unshift({
value: 'promoted',
label: 'Promoted',
count: promotedCount,
description: 'Our recommended agents',
});
}
// Get total count of all shared agents for "All" category
const totalAgents = await Agent.countDocuments({
projectIds: { $exists: true, $ne: [] },
});
// Add "All" category at the end
formattedCategories.push({
value: 'all',
label: 'All',
count: totalAgents,
description: 'All available agents',
});
res.status(200).json(formattedCategories);
} catch (error) {
logger.error('[/Agents/Marketplace] Error fetching agent categories:', error);
res.status(500).json({
error: 'Failed to fetch agent categories',
userMessage: 'Unable to load categories. Please refresh the page.',
suggestion: 'Try refreshing the page or check your network connection',
});
}
};
module.exports = {
getMarketplaceAgents,
getAgentCategories,
};

View file

@ -14,6 +14,9 @@ const {
updateAgent, updateAgent,
deleteAgent, deleteAgent,
getListAgentsByAccess, getListAgentsByAccess,
countPromotedAgents,
updateAgentProjects,
revertAgentVersion,
} = require('~/models/Agent'); } = require('~/models/Agent');
const { const {
grantPermission, grantPermission,
@ -29,6 +32,7 @@ const { updateAction, getActions } = require('~/models/Action');
const { getCachedTools } = require('~/server/services/Config'); const { getCachedTools } = require('~/server/services/Config');
const { revertAgentVersion } = require('~/models/Agent'); const { revertAgentVersion } = require('~/models/Agent');
const { deleteFileByFilter } = require('~/models/File'); const { deleteFileByFilter } = require('~/models/File');
const { getCategoriesWithCounts } = require('~/models');
const systemTools = { const systemTools = {
[Tools.execute_code]: true, [Tools.execute_code]: true,
@ -389,17 +393,38 @@ const deleteAgentHandler = async (req, res) => {
const getListAgentsHandler = async (req, res) => { const getListAgentsHandler = async (req, res) => {
try { try {
const userId = req.user.id; const userId = req.user.id;
if (!req.query.requiredPermission) { const { category, search, limit, cursor, promoted } = req.query;
req.query.requiredPermission = PermissionBits.VIEW; let requiredPermission = req.query.requiredPermission;
} else if (typeof req.query.requiredPermission === 'string') { if (typeof requiredPermission === 'string') {
req.query.requiredPermission = parseInt(req.query.requiredPermission, 10); requiredPermission = parseInt(requiredPermission, 10);
if (isNaN(req.query.requiredPermission)) { if (isNaN(requiredPermission)) {
req.query.requiredPermission = PermissionBits.VIEW; requiredPermission = PermissionBits.VIEW;
} }
} else if (typeof req.query.requiredPermission !== 'number') { } else if (typeof requiredPermission !== 'number') {
req.query.requiredPermission = PermissionBits.VIEW; requiredPermission = PermissionBits.VIEW;
}
// Base filter
const filter = {};
// Handle category filter - only apply if category is defined
if (category !== undefined && category.trim() !== '') {
filter.category = category;
}
// Handle promoted filter - only from query param
if (promoted === '1') {
filter.is_promoted = true;
} else if (promoted === '0') {
filter.is_promoted = { $ne: true };
}
// Handle search filter
if (search && search.trim() !== '') {
filter.$or = [
{ name: { $regex: search.trim(), $options: 'i' } },
{ description: { $regex: search.trim(), $options: 'i' } },
];
} }
const requiredPermission = req.query.requiredPermission || PermissionBits.VIEW;
// Get agent IDs the user has VIEW access to via ACL // Get agent IDs the user has VIEW access to via ACL
const accessibleIds = await findAccessibleResources({ const accessibleIds = await findAccessibleResources({
userId, userId,
@ -413,7 +438,9 @@ const getListAgentsHandler = async (req, res) => {
// Use the new ACL-aware function // Use the new ACL-aware function
const data = await getListAgentsByAccess({ const data = await getListAgentsByAccess({
accessibleIds, accessibleIds,
otherParams: {}, // Can add query params here if needed otherParams: filter,
limit,
after: cursor,
}); });
if (data?.data?.length) { if (data?.data?.length) {
data.data = data.data.map((agent) => { data.data = data.data.map((agent) => {
@ -580,7 +607,48 @@ const revertAgentVersionHandler = async (req, res) => {
res.status(500).json({ error: error.message }); res.status(500).json({ error: error.message });
} }
}; };
/**
* Get all agent categories with counts
*
* @param {Object} _req - Express request object (unused)
* @param {Object} res - Express response object
*/
const getAgentCategories = async (_req, res) => {
try {
const categories = await getCategoriesWithCounts();
const promotedCount = await countPromotedAgents();
const formattedCategories = categories.map((category) => ({
value: category.value,
label: category.label,
count: category.agentCount,
description: category.description,
}));
if (promotedCount > 0) {
formattedCategories.unshift({
value: 'promoted',
label: 'Promoted',
count: promotedCount,
description: 'Our recommended agents',
});
}
formattedCategories.push({
value: 'all',
label: 'All',
description: 'All available agents',
});
res.status(200).json(formattedCategories);
} catch (error) {
logger.error('[/Agents/Marketplace] Error fetching agent categories:', error);
res.status(500).json({
error: 'Failed to fetch agent categories',
userMessage: 'Unable to load categories. Please refresh the page.',
suggestion: 'Try refreshing the page or check your network connection',
});
}
};
module.exports = { module.exports = {
createAgent: createAgentHandler, createAgent: createAgentHandler,
getAgent: getAgentHandler, getAgent: getAgentHandler,
@ -590,4 +658,5 @@ module.exports = {
getListAgents: getListAgentsHandler, getListAgents: getListAgentsHandler,
uploadAgentAvatar: uploadAgentAvatarHandler, uploadAgentAvatar: uploadAgentAvatarHandler,
revertAgentVersion: revertAgentVersionHandler, revertAgentVersion: revertAgentVersionHandler,
getAgentCategories,
}; };

View file

@ -10,7 +10,6 @@ const {
const { isEnabled } = require('~/server/utils'); const { isEnabled } = require('~/server/utils');
const { v1 } = require('./v1'); const { v1 } = require('./v1');
const chat = require('./chat'); const chat = require('./chat');
const marketplace = require('./marketplace');
const { LIMIT_CONCURRENT_MESSAGES, LIMIT_MESSAGE_IP, LIMIT_MESSAGE_USER } = process.env ?? {}; const { LIMIT_CONCURRENT_MESSAGES, LIMIT_MESSAGE_IP, LIMIT_MESSAGE_USER } = process.env ?? {};
@ -20,8 +19,6 @@ router.use(requireJwtAuth);
router.use(checkBan); router.use(checkBan);
router.use(uaParser); router.use(uaParser);
router.use('/marketplace', marketplace);
router.use('/', v1); router.use('/', v1);
const chatRouter = express.Router(); const chatRouter = express.Router();

View file

@ -1,35 +0,0 @@
const express = require('express');
const { requireJwtAuth, checkBan, generateCheckAccess } = require('~/server/middleware');
const { PermissionTypes, Permissions } = require('librechat-data-provider');
const marketplace = require('~/server/controllers/agents/marketplace');
const router = express.Router();
// Apply middleware for authentication and authorization
router.use(requireJwtAuth);
router.use(checkBan);
// Check if user has permission to use agents
const checkAgentAccess = generateCheckAccess(PermissionTypes.AGENTS, [Permissions.USE]);
router.use(checkAgentAccess);
/**
* Unified marketplace agents endpoint with query string controls
* Query parameters:
* - category: string (filter by category, or 'all' for all agents, 'promoted' for promoted)
* - search: string (search term for name/description)
* - limit: number (page size, default 6)
* - cursor: base64 string (for cursor-based pagination)
* - promoted: 0|1 (filter promoted agents, 1=promoted only, 0=exclude promoted)
* - requiredPermission: number (permission level required to access agents, default 1)
* @route GET /agents/marketplace
*/
router.get('/', marketplace.getMarketplaceAgents);
/**
* Get all agent categories with counts
* @route GET /agents/marketplace/categories
*/
router.get('/categories', marketplace.getAgentCategories);
module.exports = router;

View file

@ -42,6 +42,11 @@ router.use('/actions', actions);
*/ */
router.use('/tools', tools); router.use('/tools', tools);
/**
* Get all agent categories with counts
* @route GET /agents/marketplace/categories
*/
router.get('/categories', v1.getAgentCategories);
/** /**
* Creates an agent. * Creates an agent.
* @route POST /agents * @route POST /agents

View file

@ -14,7 +14,12 @@ import type {
AgentCreateParams, AgentCreateParams,
AgentListResponse, AgentListResponse,
} from 'librechat-data-provider'; } from 'librechat-data-provider';
import { useUploadAgentAvatarMutation, useGetFileConfig } from '~/data-provider'; import {
useUploadAgentAvatarMutation,
useGetFileConfig,
allAgentViewAndEditQueryKeys,
invalidateAgentMarketplaceQueries,
} from '~/data-provider';
import { AgentAvatarRender, NoImage, AvatarMenu } from './Images'; import { AgentAvatarRender, NoImage, AvatarMenu } from './Images';
import { useToastContext } from '~/Providers'; import { useToastContext } from '~/Providers';
import { useLocalize } from '~/hooks'; import { useLocalize } from '~/hooks';
@ -57,10 +62,9 @@ function Avatar({
const newUrl = data.avatar?.filepath ?? ''; const newUrl = data.avatar?.filepath ?? '';
setPreviewUrl(newUrl); setPreviewUrl(newUrl);
const res = queryClient.getQueryData<AgentListResponse>([ ((keys) => {
QueryKeys.agents, keys.forEach((key) => {
defaultOrderQuery, const res = queryClient.getQueryData<AgentListResponse>([QueryKeys.agents, key]);
]);
if (!res?.data) { if (!res?.data) {
return; return;
@ -76,11 +80,13 @@ function Avatar({
return agent; return agent;
}); });
queryClient.setQueryData<AgentListResponse>([QueryKeys.agents, defaultOrderQuery], { queryClient.setQueryData<AgentListResponse>([QueryKeys.agents, key], {
...res, ...res,
data: agents, data: agents,
}); });
});
})(allAgentViewAndEditQueryKeys);
invalidateAgentMarketplaceQueries(queryClient);
setProgress(1); setProgress(1);
}, },
onError: (error) => { onError: (error) => {

View file

@ -1,12 +1,15 @@
import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQueryClient } from '@tanstack/react-query';
import { dataService, MutationKeys, QueryKeys, defaultOrderQuery } from 'librechat-data-provider'; import { dataService, MutationKeys, PERMISSION_BITS, QueryKeys } from 'librechat-data-provider';
import type * as t from 'librechat-data-provider'; import type * as t from 'librechat-data-provider';
import type { UseMutationResult } from '@tanstack/react-query'; import type { QueryClient, UseMutationResult } from '@tanstack/react-query';
/** /**
* AGENTS * AGENTS
*/ */
export const allAgentViewAndEditQueryKeys: t.AgentListParams[] = [
{ requiredPermission: PERMISSION_BITS.VIEW },
{ requiredPermission: PERMISSION_BITS.EDIT },
];
/** /**
* Create a new agent * Create a new agent
*/ */
@ -18,21 +21,22 @@ export const useCreateAgentMutation = (
onMutate: (variables) => options?.onMutate?.(variables), onMutate: (variables) => options?.onMutate?.(variables),
onError: (error, variables, context) => options?.onError?.(error, variables, context), onError: (error, variables, context) => options?.onError?.(error, variables, context),
onSuccess: (newAgent, variables, context) => { onSuccess: (newAgent, variables, context) => {
const listRes = queryClient.getQueryData<t.AgentListResponse>([ ((keys: t.AgentListParams[]) => {
QueryKeys.agents, keys.forEach((key) => {
defaultOrderQuery, const listRes = queryClient.getQueryData<t.AgentListResponse>([QueryKeys.agents, key]);
]);
if (!listRes) { if (!listRes) {
return options?.onSuccess?.(newAgent, variables, context); return options?.onSuccess?.(newAgent, variables, context);
} }
const currentAgents = [newAgent, ...JSON.parse(JSON.stringify(listRes.data))]; const currentAgents = [newAgent, ...JSON.parse(JSON.stringify(listRes.data))];
queryClient.setQueryData<t.AgentListResponse>([QueryKeys.agents, defaultOrderQuery], { queryClient.setQueryData<t.AgentListResponse>([QueryKeys.agents, key], {
...listRes, ...listRes,
data: currentAgents, data: currentAgents,
}); });
});
})(allAgentViewAndEditQueryKeys);
invalidateAgentMarketplaceQueries(queryClient);
return options?.onSuccess?.(newAgent, variables, context); return options?.onSuccess?.(newAgent, variables, context);
}, },
}); });
@ -63,16 +67,15 @@ export const useUpdateAgentMutation = (
return options?.onError?.(typedError, variables, context); return options?.onError?.(typedError, variables, context);
}, },
onSuccess: (updatedAgent, variables, context) => { onSuccess: (updatedAgent, variables, context) => {
const listRes = queryClient.getQueryData<t.AgentListResponse>([ ((keys: t.AgentListParams[]) => {
QueryKeys.agents, keys.forEach((key) => {
defaultOrderQuery, const listRes = queryClient.getQueryData<t.AgentListResponse>([QueryKeys.agents, key]);
]);
if (!listRes) { if (!listRes) {
return options?.onSuccess?.(updatedAgent, variables, context); return options?.onSuccess?.(updatedAgent, variables, context);
} }
queryClient.setQueryData<t.AgentListResponse>([QueryKeys.agents, defaultOrderQuery], { queryClient.setQueryData<t.AgentListResponse>([QueryKeys.agents, key], {
...listRes, ...listRes,
data: listRes.data.map((agent) => { data: listRes.data.map((agent) => {
if (agent.id === variables.agent_id) { if (agent.id === variables.agent_id) {
@ -81,12 +84,16 @@ export const useUpdateAgentMutation = (
return agent; return agent;
}), }),
}); });
});
})(allAgentViewAndEditQueryKeys);
queryClient.setQueryData<t.Agent>([QueryKeys.agent, variables.agent_id], updatedAgent); queryClient.setQueryData<t.Agent>([QueryKeys.agent, variables.agent_id], updatedAgent);
queryClient.setQueryData<t.Agent>( queryClient.setQueryData<t.Agent>(
[QueryKeys.agent, variables.agent_id, 'expanded'], [QueryKeys.agent, variables.agent_id, 'expanded'],
updatedAgent, updatedAgent,
); );
invalidateAgentMarketplaceQueries(queryClient);
return options?.onSuccess?.(updatedAgent, variables, context); return options?.onSuccess?.(updatedAgent, variables, context);
}, },
}, },
@ -108,24 +115,28 @@ export const useDeleteAgentMutation = (
onMutate: (variables) => options?.onMutate?.(variables), onMutate: (variables) => options?.onMutate?.(variables),
onError: (error, variables, context) => options?.onError?.(error, variables, context), onError: (error, variables, context) => options?.onError?.(error, variables, context),
onSuccess: (_data, variables, context) => { onSuccess: (_data, variables, context) => {
const listRes = queryClient.getQueryData<t.AgentListResponse>([ const data = ((keys: t.AgentListParams[]) => {
QueryKeys.agents, let data: t.Agent[] = [];
defaultOrderQuery, keys.forEach((key) => {
]); const listRes = queryClient.getQueryData<t.AgentListResponse>([QueryKeys.agents, key]);
if (!listRes) { if (!listRes) {
return options?.onSuccess?.(_data, variables, context); return options?.onSuccess?.(_data, variables, context);
} }
const data = listRes.data.filter((agent) => agent.id !== variables.agent_id); data = listRes.data.filter((agent) => agent.id !== variables.agent_id);
queryClient.setQueryData<t.AgentListResponse>([QueryKeys.agents, defaultOrderQuery], { queryClient.setQueryData<t.AgentListResponse>([QueryKeys.agents, key], {
...listRes, ...listRes,
data, data,
}); });
});
return data;
})(allAgentViewAndEditQueryKeys);
queryClient.removeQueries([QueryKeys.agent, variables.agent_id]); queryClient.removeQueries([QueryKeys.agent, variables.agent_id]);
queryClient.removeQueries([QueryKeys.agent, variables.agent_id, 'expanded']); queryClient.removeQueries([QueryKeys.agent, variables.agent_id, 'expanded']);
invalidateAgentMarketplaceQueries(queryClient);
return options?.onSuccess?.(_data, variables, data); return options?.onSuccess?.(_data, variables, data);
}, },
@ -147,22 +158,23 @@ export const useDuplicateAgentMutation = (
onMutate: options?.onMutate, onMutate: options?.onMutate,
onError: options?.onError, onError: options?.onError,
onSuccess: ({ agent, actions }, variables, context) => { onSuccess: ({ agent, actions }, variables, context) => {
const listRes = queryClient.getQueryData<t.AgentListResponse>([ ((keys: t.AgentListParams[]) => {
QueryKeys.agents, keys.forEach((key) => {
defaultOrderQuery, const listRes = queryClient.getQueryData<t.AgentListResponse>([QueryKeys.agents, key]);
]);
if (listRes) { if (listRes) {
const currentAgents = [agent, ...listRes.data]; const currentAgents = [agent, ...listRes.data];
queryClient.setQueryData<t.AgentListResponse>([QueryKeys.agents, defaultOrderQuery], { queryClient.setQueryData<t.AgentListResponse>([QueryKeys.agents, key], {
...listRes, ...listRes,
data: currentAgents, data: currentAgents,
}); });
} }
});
})(allAgentViewAndEditQueryKeys);
const existingActions = queryClient.getQueryData<t.Action[]>([QueryKeys.actions]) || []; const existingActions = queryClient.getQueryData<t.Action[]>([QueryKeys.actions]) || [];
queryClient.setQueryData<t.Action[]>([QueryKeys.actions], existingActions.concat(actions)); queryClient.setQueryData<t.Action[]>([QueryKeys.actions], existingActions.concat(actions));
invalidateAgentMarketplaceQueries(queryClient);
return options?.onSuccess?.({ agent, actions }, variables, context); return options?.onSuccess?.({ agent, actions }, variables, context);
}, },
@ -181,6 +193,8 @@ export const useUploadAgentAvatarMutation = (
t.AgentAvatarVariables, // request t.AgentAvatarVariables, // request
unknown // context unknown // context
> => { > => {
const queryClient = useQueryClient();
return useMutation([MutationKeys.agentAvatarUpload], { return useMutation([MutationKeys.agentAvatarUpload], {
mutationFn: ({ postCreation, ...variables }: t.AgentAvatarVariables) => mutationFn: ({ postCreation, ...variables }: t.AgentAvatarVariables) =>
dataService.uploadAgentAvatar(variables), dataService.uploadAgentAvatar(variables),
@ -207,18 +221,15 @@ export const useUpdateAgentAction = (
onMutate: (variables) => options?.onMutate?.(variables), onMutate: (variables) => options?.onMutate?.(variables),
onError: (error, variables, context) => options?.onError?.(error, variables, context), onError: (error, variables, context) => options?.onError?.(error, variables, context),
onSuccess: (updateAgentActionResponse, variables, context) => { onSuccess: (updateAgentActionResponse, variables, context) => {
const listRes = queryClient.getQueryData<t.AgentListResponse>([ const updatedAgent = updateAgentActionResponse[0];
QueryKeys.agents, ((keys: t.AgentListParams[]) => {
defaultOrderQuery, keys.forEach((key) => {
]); const listRes = queryClient.getQueryData<t.AgentListResponse>([QueryKeys.agents, key]);
if (!listRes) { if (!listRes) {
return options?.onSuccess?.(updateAgentActionResponse, variables, context); return options?.onSuccess?.(updateAgentActionResponse, variables, context);
} }
queryClient.setQueryData<t.AgentListResponse>([QueryKeys.agents, key], {
const updatedAgent = updateAgentActionResponse[0];
queryClient.setQueryData<t.AgentListResponse>([QueryKeys.agents, defaultOrderQuery], {
...listRes, ...listRes,
data: listRes.data.map((agent) => { data: listRes.data.map((agent) => {
if (agent.id === variables.agent_id) { if (agent.id === variables.agent_id) {
@ -227,6 +238,8 @@ export const useUpdateAgentAction = (
return agent; return agent;
}), }),
}); });
});
})(allAgentViewAndEditQueryKeys);
queryClient.setQueryData<t.Action[]>([QueryKeys.actions], (prev) => { queryClient.setQueryData<t.Action[]>([QueryKeys.actions], (prev) => {
if (!prev) { if (!prev) {
@ -280,10 +293,9 @@ export const useDeleteAgentAction = (
return action.action_id !== variables.action_id; return action.action_id !== variables.action_id;
}); });
}); });
((keys: t.AgentListParams[]) => {
queryClient.setQueryData<t.AgentListResponse>( keys.forEach((key) => {
[QueryKeys.agents, defaultOrderQuery], queryClient.setQueryData<t.AgentListResponse>([QueryKeys.agents, key], (prev) => {
(prev) => {
if (!prev) { if (!prev) {
return prev; return prev;
} }
@ -300,8 +312,9 @@ export const useDeleteAgentAction = (
return agent; return agent;
}), }),
}; };
}, });
); });
})(allAgentViewAndEditQueryKeys);
const updaterFn = (prev) => { const updaterFn = (prev) => {
if (!prev) { if (!prev) {
return prev; return prev;
@ -342,13 +355,12 @@ export const useRevertAgentVersionMutation = (
onSuccess: (revertedAgent, variables, context) => { onSuccess: (revertedAgent, variables, context) => {
queryClient.setQueryData<t.Agent>([QueryKeys.agent, variables.agent_id], revertedAgent); queryClient.setQueryData<t.Agent>([QueryKeys.agent, variables.agent_id], revertedAgent);
const listRes = queryClient.getQueryData<t.AgentListResponse>([ ((keys: t.AgentListParams[]) => {
QueryKeys.agents, keys.forEach((key) => {
defaultOrderQuery, const listRes = queryClient.getQueryData<t.AgentListResponse>([QueryKeys.agents, key]);
]);
if (listRes) { if (listRes) {
queryClient.setQueryData<t.AgentListResponse>([QueryKeys.agents, defaultOrderQuery], { queryClient.setQueryData<t.AgentListResponse>([QueryKeys.agents, key], {
...listRes, ...listRes,
data: listRes.data.map((agent) => { data: listRes.data.map((agent) => {
if (agent.id === variables.agent_id) { if (agent.id === variables.agent_id) {
@ -358,9 +370,15 @@ export const useRevertAgentVersionMutation = (
}), }),
}); });
} }
});
})(allAgentViewAndEditQueryKeys);
return options?.onSuccess?.(revertedAgent, variables, context); return options?.onSuccess?.(revertedAgent, variables, context);
}, },
}, },
); );
}; };
export const invalidateAgentMarketplaceQueries = (queryClient: QueryClient) => {
queryClient.invalidateQueries([QueryKeys.marketplaceAgents]);
};

View file

@ -1,4 +1,4 @@
import { QueryKeys, dataService, EModelEndpoint, defaultOrderQuery } from 'librechat-data-provider'; import { QueryKeys, dataService, EModelEndpoint, PERMISSION_BITS } from 'librechat-data-provider';
import { useQuery, useInfiniteQuery, useQueryClient } from '@tanstack/react-query'; import { useQuery, useInfiniteQuery, useQueryClient } from '@tanstack/react-query';
import type { import type {
QueryObserverResult, QueryObserverResult,
@ -11,7 +11,10 @@ import type t from 'librechat-data-provider';
/** /**
* AGENTS * AGENTS
*/ */
export const defaultAgentParams: t.AgentListParams = {
limit: 10,
requiredPermission: PERMISSION_BITS.EDIT,
};
/** /**
* Hook for getting all available tools for A * Hook for getting all available tools for A
*/ */
@ -32,7 +35,7 @@ export const useAvailableAgentToolsQuery = (): QueryObserverResult<t.TPlugin[]>
* Hook for listing all Agents, with optional parameters provided for pagination and sorting * Hook for listing all Agents, with optional parameters provided for pagination and sorting
*/ */
export const useListAgentsQuery = <TData = t.AgentListResponse>( export const useListAgentsQuery = <TData = t.AgentListResponse>(
params: t.AgentListParams = defaultOrderQuery, params: t.AgentListParams = defaultAgentParams,
config?: UseQueryOptions<t.AgentListResponse, unknown, TData>, config?: UseQueryOptions<t.AgentListResponse, unknown, TData>,
): QueryObserverResult<TData> => { ): QueryObserverResult<TData> => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();

View file

@ -9,9 +9,7 @@ export default function useAgentsMap({
isAuthenticated: boolean; isAuthenticated: boolean;
}): TAgentsMap | undefined { }): TAgentsMap | undefined {
const { data: agentsList = null } = useListAgentsQuery( const { data: agentsList = null } = useListAgentsQuery(
{ { requiredPermission: PERMISSION_BITS.EDIT },
requiredPermission: PERMISSION_BITS.EDIT,
},
{ {
select: (res) => mapAgents(res.data), select: (res) => mapAgents(res.data),
enabled: isAuthenticated, enabled: isAuthenticated,

View file

@ -8,6 +8,7 @@ import {
isAgentsEndpoint, isAgentsEndpoint,
getConfigDefaults, getConfigDefaults,
isAssistantsEndpoint, isAssistantsEndpoint,
PERMISSION_BITS,
} from 'librechat-data-provider'; } from 'librechat-data-provider';
import type { TAssistantsMap, TEndpointsConfig } from 'librechat-data-provider'; import type { TAssistantsMap, TEndpointsConfig } from 'librechat-data-provider';
import type { MentionOption } from '~/common'; import type { MentionOption } from '~/common';
@ -79,7 +80,9 @@ export default function useMentions({
() => startupConfig?.interface ?? defaultInterface, () => startupConfig?.interface ?? defaultInterface,
[startupConfig?.interface], [startupConfig?.interface],
); );
const { data: agentsList = null } = useListAgentsQuery(undefined, { const { data: agentsList = null } = useListAgentsQuery(
{ requiredPermission: PERMISSION_BITS.VIEW },
{
enabled: hasAgentAccess && interfaceConfig.modelSelect === true, enabled: hasAgentAccess && interfaceConfig.modelSelect === true,
select: (res) => { select: (res) => {
const { data } = res; const { data } = res;
@ -100,7 +103,8 @@ export default function useMentions({
}), }),
})); }));
}, },
}); },
);
const assistantListMap = useMemo( const assistantListMap = useMemo(
() => ({ () => ({
[EModelEndpoint.assistants]: listMap[EModelEndpoint.assistants] [EModelEndpoint.assistants]: listMap[EModelEndpoint.assistants]

View file

@ -451,7 +451,7 @@ export const revertAgentVersion = ({
* Get agent categories with counts for marketplace tabs * Get agent categories with counts for marketplace tabs
*/ */
export const getAgentCategories = (): Promise<t.TMarketplaceCategory[]> => { export const getAgentCategories = (): Promise<t.TMarketplaceCategory[]> => {
return request.get(endpoints.agents({ path: 'marketplace/categories' })); return request.get(endpoints.agents({ path: 'categories' }));
}; };
/** /**
@ -467,7 +467,7 @@ export const getMarketplaceAgents = (params: {
}): Promise<a.AgentListResponse> => { }): Promise<a.AgentListResponse> => {
return request.get( return request.get(
endpoints.agents({ endpoints.agents({
path: 'marketplace', // path: 'marketplace',
options: params, options: params,
}), }),
); );

View file

@ -286,11 +286,11 @@ export type AgentUpdateParams = {
export type AgentListParams = { export type AgentListParams = {
limit?: number; limit?: number;
before?: string | null; requiredPermission: number;
after?: string | null; category?: string;
order?: 'asc' | 'desc'; search?: string;
provider?: AgentProvider; cursor?: string;
requiredPermission?: number; promoted?: 0 | 1;
}; };
export type AgentListResponse = { export type AgentListResponse = {

View file

@ -182,14 +182,14 @@ export function createAgentCategoryMethods(mongoose: typeof import('mongoose'))
}, },
{ {
value: 'it', value: 'it',
label: 'Information Technology', label: 'IT',
description: 'Agents for IT support, technical troubleshooting, and system administration', description: 'Agents for IT support, technical troubleshooting, and system administration',
order: 4, order: 4,
}, },
{ {
value: 'sales', value: 'sales',
label: 'Sales & Marketing', label: 'Sales',
description: 'Agents focused on sales processes, customer relations, and marketing', description: 'Agents focused on sales processes, customer relations.',
order: 5, order: 5,
}, },
{ {