diff --git a/api/models/Agent.js b/api/models/Agent.js index cfc010f886..b4979051b1 100644 --- a/api/models/Agent.js +++ b/api/models/Agent.js @@ -751,6 +751,14 @@ const generateActionMetadataHash = async (actionIds, actions) => { return hashHex; }; +/** + * Counts the number of promoted agents. + * @returns {Promise} - 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 @@ -771,4 +779,5 @@ module.exports = { getListAgentsByAccess, removeAgentResourceFiles, generateActionMetadataHash, + countPromotedAgents, }; diff --git a/api/server/controllers/agents/marketplace.js b/api/server/controllers/agents/marketplace.js deleted file mode 100644 index ce348ee4cd..0000000000 --- a/api/server/controllers/agents/marketplace.js +++ /dev/null @@ -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, -}; diff --git a/api/server/controllers/agents/v1.js b/api/server/controllers/agents/v1.js index b7540a5da2..ddd51f9c40 100644 --- a/api/server/controllers/agents/v1.js +++ b/api/server/controllers/agents/v1.js @@ -14,6 +14,9 @@ const { updateAgent, deleteAgent, getListAgentsByAccess, + countPromotedAgents, + updateAgentProjects, + revertAgentVersion, } = require('~/models/Agent'); const { grantPermission, @@ -29,6 +32,7 @@ const { updateAction, getActions } = require('~/models/Action'); const { getCachedTools } = require('~/server/services/Config'); const { revertAgentVersion } = require('~/models/Agent'); const { deleteFileByFilter } = require('~/models/File'); +const { getCategoriesWithCounts } = require('~/models'); const systemTools = { [Tools.execute_code]: true, @@ -389,17 +393,38 @@ const deleteAgentHandler = async (req, res) => { const getListAgentsHandler = async (req, res) => { try { const userId = req.user.id; - if (!req.query.requiredPermission) { - req.query.requiredPermission = PermissionBits.VIEW; - } else if (typeof req.query.requiredPermission === 'string') { - req.query.requiredPermission = parseInt(req.query.requiredPermission, 10); - if (isNaN(req.query.requiredPermission)) { - req.query.requiredPermission = PermissionBits.VIEW; + const { category, search, limit, cursor, promoted } = req.query; + let requiredPermission = req.query.requiredPermission; + if (typeof requiredPermission === 'string') { + requiredPermission = parseInt(requiredPermission, 10); + if (isNaN(requiredPermission)) { + requiredPermission = PermissionBits.VIEW; } - } else if (typeof req.query.requiredPermission !== 'number') { - req.query.requiredPermission = PermissionBits.VIEW; + } else if (typeof requiredPermission !== 'number') { + 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 const accessibleIds = await findAccessibleResources({ userId, @@ -413,7 +438,9 @@ const getListAgentsHandler = async (req, res) => { // Use the new ACL-aware function const data = await getListAgentsByAccess({ accessibleIds, - otherParams: {}, // Can add query params here if needed + otherParams: filter, + limit, + after: cursor, }); if (data?.data?.length) { data.data = data.data.map((agent) => { @@ -580,7 +607,48 @@ const revertAgentVersionHandler = async (req, res) => { 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 = { createAgent: createAgentHandler, getAgent: getAgentHandler, @@ -590,4 +658,5 @@ module.exports = { getListAgents: getListAgentsHandler, uploadAgentAvatar: uploadAgentAvatarHandler, revertAgentVersion: revertAgentVersionHandler, + getAgentCategories, }; diff --git a/api/server/routes/agents/index.js b/api/server/routes/agents/index.js index 5427128a38..1c4f69d9ac 100644 --- a/api/server/routes/agents/index.js +++ b/api/server/routes/agents/index.js @@ -10,7 +10,6 @@ const { const { isEnabled } = require('~/server/utils'); const { v1 } = require('./v1'); const chat = require('./chat'); -const marketplace = require('./marketplace'); const { LIMIT_CONCURRENT_MESSAGES, LIMIT_MESSAGE_IP, LIMIT_MESSAGE_USER } = process.env ?? {}; @@ -20,8 +19,6 @@ router.use(requireJwtAuth); router.use(checkBan); router.use(uaParser); -router.use('/marketplace', marketplace); - router.use('/', v1); const chatRouter = express.Router(); diff --git a/api/server/routes/agents/marketplace.js b/api/server/routes/agents/marketplace.js deleted file mode 100644 index 0f563b7add..0000000000 --- a/api/server/routes/agents/marketplace.js +++ /dev/null @@ -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; diff --git a/api/server/routes/agents/v1.js b/api/server/routes/agents/v1.js index 783f36a8fb..b304537e48 100644 --- a/api/server/routes/agents/v1.js +++ b/api/server/routes/agents/v1.js @@ -42,6 +42,11 @@ router.use('/actions', actions); */ router.use('/tools', tools); +/** + * Get all agent categories with counts + * @route GET /agents/marketplace/categories + */ +router.get('/categories', v1.getAgentCategories); /** * Creates an agent. * @route POST /agents diff --git a/client/src/components/SidePanel/Agents/AgentAvatar.tsx b/client/src/components/SidePanel/Agents/AgentAvatar.tsx index 80b58e787b..2c85d4853c 100644 --- a/client/src/components/SidePanel/Agents/AgentAvatar.tsx +++ b/client/src/components/SidePanel/Agents/AgentAvatar.tsx @@ -14,7 +14,12 @@ import type { AgentCreateParams, AgentListResponse, } 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 { useToastContext } from '~/Providers'; import { useLocalize } from '~/hooks'; @@ -57,30 +62,31 @@ function Avatar({ const newUrl = data.avatar?.filepath ?? ''; setPreviewUrl(newUrl); - const res = queryClient.getQueryData([ - QueryKeys.agents, - defaultOrderQuery, - ]); + ((keys) => { + keys.forEach((key) => { + const res = queryClient.getQueryData([QueryKeys.agents, key]); - if (!res?.data) { - return; - } + if (!res?.data) { + return; + } - const agents = res.data.map((agent) => { - if (agent.id === agent_id) { - return { - ...agent, - ...data, - }; - } - return agent; - }); - - queryClient.setQueryData([QueryKeys.agents, defaultOrderQuery], { - ...res, - data: agents, - }); + const agents = res.data.map((agent) => { + if (agent.id === agent_id) { + return { + ...agent, + ...data, + }; + } + return agent; + }); + queryClient.setQueryData([QueryKeys.agents, key], { + ...res, + data: agents, + }); + }); + })(allAgentViewAndEditQueryKeys); + invalidateAgentMarketplaceQueries(queryClient); setProgress(1); }, onError: (error) => { diff --git a/client/src/data-provider/Agents/mutations.ts b/client/src/data-provider/Agents/mutations.ts index 9956f40e42..b1c3cb191c 100644 --- a/client/src/data-provider/Agents/mutations.ts +++ b/client/src/data-provider/Agents/mutations.ts @@ -1,12 +1,15 @@ 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 { UseMutationResult } from '@tanstack/react-query'; +import type { QueryClient, UseMutationResult } from '@tanstack/react-query'; /** * AGENTS */ - +export const allAgentViewAndEditQueryKeys: t.AgentListParams[] = [ + { requiredPermission: PERMISSION_BITS.VIEW }, + { requiredPermission: PERMISSION_BITS.EDIT }, +]; /** * Create a new agent */ @@ -18,21 +21,22 @@ export const useCreateAgentMutation = ( onMutate: (variables) => options?.onMutate?.(variables), onError: (error, variables, context) => options?.onError?.(error, variables, context), onSuccess: (newAgent, variables, context) => { - const listRes = queryClient.getQueryData([ - QueryKeys.agents, - defaultOrderQuery, - ]); + ((keys: t.AgentListParams[]) => { + keys.forEach((key) => { + const listRes = queryClient.getQueryData([QueryKeys.agents, key]); + if (!listRes) { + return options?.onSuccess?.(newAgent, variables, context); + } + const currentAgents = [newAgent, ...JSON.parse(JSON.stringify(listRes.data))]; - if (!listRes) { - return options?.onSuccess?.(newAgent, variables, context); - } + queryClient.setQueryData([QueryKeys.agents, key], { + ...listRes, + data: currentAgents, + }); + }); + })(allAgentViewAndEditQueryKeys); + invalidateAgentMarketplaceQueries(queryClient); - const currentAgents = [newAgent, ...JSON.parse(JSON.stringify(listRes.data))]; - - queryClient.setQueryData([QueryKeys.agents, defaultOrderQuery], { - ...listRes, - data: currentAgents, - }); return options?.onSuccess?.(newAgent, variables, context); }, }); @@ -63,30 +67,33 @@ export const useUpdateAgentMutation = ( return options?.onError?.(typedError, variables, context); }, onSuccess: (updatedAgent, variables, context) => { - const listRes = queryClient.getQueryData([ - QueryKeys.agents, - defaultOrderQuery, - ]); + ((keys: t.AgentListParams[]) => { + keys.forEach((key) => { + const listRes = queryClient.getQueryData([QueryKeys.agents, key]); - if (!listRes) { - return options?.onSuccess?.(updatedAgent, variables, context); - } - - queryClient.setQueryData([QueryKeys.agents, defaultOrderQuery], { - ...listRes, - data: listRes.data.map((agent) => { - if (agent.id === variables.agent_id) { - return updatedAgent; + if (!listRes) { + return options?.onSuccess?.(updatedAgent, variables, context); } - return agent; - }), - }); + + queryClient.setQueryData([QueryKeys.agents, key], { + ...listRes, + data: listRes.data.map((agent) => { + if (agent.id === variables.agent_id) { + return updatedAgent; + } + return agent; + }), + }); + }); + })(allAgentViewAndEditQueryKeys); queryClient.setQueryData([QueryKeys.agent, variables.agent_id], updatedAgent); queryClient.setQueryData( [QueryKeys.agent, variables.agent_id, 'expanded'], updatedAgent, ); + invalidateAgentMarketplaceQueries(queryClient); + return options?.onSuccess?.(updatedAgent, variables, context); }, }, @@ -108,24 +115,28 @@ export const useDeleteAgentMutation = ( onMutate: (variables) => options?.onMutate?.(variables), onError: (error, variables, context) => options?.onError?.(error, variables, context), onSuccess: (_data, variables, context) => { - const listRes = queryClient.getQueryData([ - QueryKeys.agents, - defaultOrderQuery, - ]); + const data = ((keys: t.AgentListParams[]) => { + let data: t.Agent[] = []; + keys.forEach((key) => { + const listRes = queryClient.getQueryData([QueryKeys.agents, key]); - if (!listRes) { - return options?.onSuccess?.(_data, variables, context); - } + if (!listRes) { + 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([QueryKeys.agents, defaultOrderQuery], { - ...listRes, - data, - }); + queryClient.setQueryData([QueryKeys.agents, key], { + ...listRes, + data, + }); + }); + return data; + })(allAgentViewAndEditQueryKeys); queryClient.removeQueries([QueryKeys.agent, variables.agent_id]); queryClient.removeQueries([QueryKeys.agent, variables.agent_id, 'expanded']); + invalidateAgentMarketplaceQueries(queryClient); return options?.onSuccess?.(_data, variables, data); }, @@ -147,22 +158,23 @@ export const useDuplicateAgentMutation = ( onMutate: options?.onMutate, onError: options?.onError, onSuccess: ({ agent, actions }, variables, context) => { - const listRes = queryClient.getQueryData([ - QueryKeys.agents, - defaultOrderQuery, - ]); - - if (listRes) { - const currentAgents = [agent, ...listRes.data]; - queryClient.setQueryData([QueryKeys.agents, defaultOrderQuery], { - ...listRes, - data: currentAgents, + ((keys: t.AgentListParams[]) => { + keys.forEach((key) => { + const listRes = queryClient.getQueryData([QueryKeys.agents, key]); + if (listRes) { + const currentAgents = [agent, ...listRes.data]; + queryClient.setQueryData([QueryKeys.agents, key], { + ...listRes, + data: currentAgents, + }); + } }); - } + })(allAgentViewAndEditQueryKeys); const existingActions = queryClient.getQueryData([QueryKeys.actions]) || []; queryClient.setQueryData([QueryKeys.actions], existingActions.concat(actions)); + invalidateAgentMarketplaceQueries(queryClient); return options?.onSuccess?.({ agent, actions }, variables, context); }, @@ -181,6 +193,8 @@ export const useUploadAgentAvatarMutation = ( t.AgentAvatarVariables, // request unknown // context > => { + const queryClient = useQueryClient(); + return useMutation([MutationKeys.agentAvatarUpload], { mutationFn: ({ postCreation, ...variables }: t.AgentAvatarVariables) => dataService.uploadAgentAvatar(variables), @@ -207,26 +221,25 @@ export const useUpdateAgentAction = ( onMutate: (variables) => options?.onMutate?.(variables), onError: (error, variables, context) => options?.onError?.(error, variables, context), onSuccess: (updateAgentActionResponse, variables, context) => { - const listRes = queryClient.getQueryData([ - QueryKeys.agents, - defaultOrderQuery, - ]); - - if (!listRes) { - return options?.onSuccess?.(updateAgentActionResponse, variables, context); - } - const updatedAgent = updateAgentActionResponse[0]; + ((keys: t.AgentListParams[]) => { + keys.forEach((key) => { + const listRes = queryClient.getQueryData([QueryKeys.agents, key]); - queryClient.setQueryData([QueryKeys.agents, defaultOrderQuery], { - ...listRes, - data: listRes.data.map((agent) => { - if (agent.id === variables.agent_id) { - return updatedAgent; + if (!listRes) { + return options?.onSuccess?.(updateAgentActionResponse, variables, context); } - return agent; - }), - }); + queryClient.setQueryData([QueryKeys.agents, key], { + ...listRes, + data: listRes.data.map((agent) => { + if (agent.id === variables.agent_id) { + return updatedAgent; + } + return agent; + }), + }); + }); + })(allAgentViewAndEditQueryKeys); queryClient.setQueryData([QueryKeys.actions], (prev) => { if (!prev) { @@ -280,28 +293,28 @@ export const useDeleteAgentAction = ( return action.action_id !== variables.action_id; }); }); + ((keys: t.AgentListParams[]) => { + keys.forEach((key) => { + queryClient.setQueryData([QueryKeys.agents, key], (prev) => { + if (!prev) { + return prev; + } - queryClient.setQueryData( - [QueryKeys.agents, defaultOrderQuery], - (prev) => { - if (!prev) { - return prev; - } - - return { - ...prev, - data: prev.data.map((agent) => { - if (agent.id === variables.agent_id) { - return { - ...agent, - tools: agent.tools?.filter((tool) => !tool.includes(domain ?? '')), - }; - } - return agent; - }), - }; - }, - ); + return { + ...prev, + data: prev.data.map((agent) => { + if (agent.id === variables.agent_id) { + return { + ...agent, + tools: agent.tools?.filter((tool) => !tool.includes(domain ?? '')), + }; + } + return agent; + }), + }; + }); + }); + })(allAgentViewAndEditQueryKeys); const updaterFn = (prev) => { if (!prev) { return prev; @@ -342,25 +355,30 @@ export const useRevertAgentVersionMutation = ( onSuccess: (revertedAgent, variables, context) => { queryClient.setQueryData([QueryKeys.agent, variables.agent_id], revertedAgent); - const listRes = queryClient.getQueryData([ - QueryKeys.agents, - defaultOrderQuery, - ]); + ((keys: t.AgentListParams[]) => { + keys.forEach((key) => { + const listRes = queryClient.getQueryData([QueryKeys.agents, key]); - if (listRes) { - queryClient.setQueryData([QueryKeys.agents, defaultOrderQuery], { - ...listRes, - data: listRes.data.map((agent) => { - if (agent.id === variables.agent_id) { - return revertedAgent; - } - return agent; - }), + if (listRes) { + queryClient.setQueryData([QueryKeys.agents, key], { + ...listRes, + data: listRes.data.map((agent) => { + if (agent.id === variables.agent_id) { + return revertedAgent; + } + return agent; + }), + }); + } }); - } + })(allAgentViewAndEditQueryKeys); return options?.onSuccess?.(revertedAgent, variables, context); }, }, ); }; + +export const invalidateAgentMarketplaceQueries = (queryClient: QueryClient) => { + queryClient.invalidateQueries([QueryKeys.marketplaceAgents]); +}; diff --git a/client/src/data-provider/Agents/queries.ts b/client/src/data-provider/Agents/queries.ts index cec72c03a8..57cd797861 100644 --- a/client/src/data-provider/Agents/queries.ts +++ b/client/src/data-provider/Agents/queries.ts @@ -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 type { QueryObserverResult, @@ -11,7 +11,10 @@ import type t from 'librechat-data-provider'; /** * AGENTS */ - +export const defaultAgentParams: t.AgentListParams = { + limit: 10, + requiredPermission: PERMISSION_BITS.EDIT, +}; /** * Hook for getting all available tools for A */ @@ -32,7 +35,7 @@ export const useAvailableAgentToolsQuery = (): QueryObserverResult * Hook for listing all Agents, with optional parameters provided for pagination and sorting */ export const useListAgentsQuery = ( - params: t.AgentListParams = defaultOrderQuery, + params: t.AgentListParams = defaultAgentParams, config?: UseQueryOptions, ): QueryObserverResult => { const queryClient = useQueryClient(); diff --git a/client/src/hooks/Agents/useAgentsMap.ts b/client/src/hooks/Agents/useAgentsMap.ts index 8b594d15ef..d872e6f34d 100644 --- a/client/src/hooks/Agents/useAgentsMap.ts +++ b/client/src/hooks/Agents/useAgentsMap.ts @@ -9,9 +9,7 @@ export default function useAgentsMap({ isAuthenticated: boolean; }): TAgentsMap | undefined { const { data: agentsList = null } = useListAgentsQuery( - { - requiredPermission: PERMISSION_BITS.EDIT, - }, + { requiredPermission: PERMISSION_BITS.EDIT }, { select: (res) => mapAgents(res.data), enabled: isAuthenticated, diff --git a/client/src/hooks/Input/useMentions.ts b/client/src/hooks/Input/useMentions.ts index 46f438ba91..fa27f183f0 100644 --- a/client/src/hooks/Input/useMentions.ts +++ b/client/src/hooks/Input/useMentions.ts @@ -8,6 +8,7 @@ import { isAgentsEndpoint, getConfigDefaults, isAssistantsEndpoint, + PERMISSION_BITS, } from 'librechat-data-provider'; import type { TAssistantsMap, TEndpointsConfig } from 'librechat-data-provider'; import type { MentionOption } from '~/common'; @@ -79,28 +80,31 @@ export default function useMentions({ () => startupConfig?.interface ?? defaultInterface, [startupConfig?.interface], ); - const { data: agentsList = null } = useListAgentsQuery(undefined, { - enabled: hasAgentAccess && interfaceConfig.modelSelect === true, - select: (res) => { - const { data } = res; - return data.map(({ id, name, avatar }) => ({ - value: id, - label: name ?? '', - type: EModelEndpoint.agents, - icon: EndpointIcon({ - conversation: { - agent_id: id, - endpoint: EModelEndpoint.agents, - iconURL: avatar?.filepath, - }, - containerClassName: 'shadow-stroke overflow-hidden rounded-full', - endpointsConfig: endpointsConfig, - context: 'menu-item', - size: 20, - }), - })); + const { data: agentsList = null } = useListAgentsQuery( + { requiredPermission: PERMISSION_BITS.VIEW }, + { + enabled: hasAgentAccess && interfaceConfig.modelSelect === true, + select: (res) => { + const { data } = res; + return data.map(({ id, name, avatar }) => ({ + value: id, + label: name ?? '', + type: EModelEndpoint.agents, + icon: EndpointIcon({ + conversation: { + agent_id: id, + endpoint: EModelEndpoint.agents, + iconURL: avatar?.filepath, + }, + containerClassName: 'shadow-stroke overflow-hidden rounded-full', + endpointsConfig: endpointsConfig, + context: 'menu-item', + size: 20, + }), + })); + }, }, - }); + ); const assistantListMap = useMemo( () => ({ [EModelEndpoint.assistants]: listMap[EModelEndpoint.assistants] diff --git a/packages/data-provider/src/data-service.ts b/packages/data-provider/src/data-service.ts index 3c538b69bc..4fc3a432f1 100644 --- a/packages/data-provider/src/data-service.ts +++ b/packages/data-provider/src/data-service.ts @@ -451,7 +451,7 @@ export const revertAgentVersion = ({ * Get agent categories with counts for marketplace tabs */ export const getAgentCategories = (): Promise => { - return request.get(endpoints.agents({ path: 'marketplace/categories' })); + return request.get(endpoints.agents({ path: 'categories' })); }; /** @@ -467,7 +467,7 @@ export const getMarketplaceAgents = (params: { }): Promise => { return request.get( endpoints.agents({ - path: 'marketplace', + // path: 'marketplace', options: params, }), ); diff --git a/packages/data-provider/src/types/assistants.ts b/packages/data-provider/src/types/assistants.ts index 63239c92b3..df90097282 100644 --- a/packages/data-provider/src/types/assistants.ts +++ b/packages/data-provider/src/types/assistants.ts @@ -286,11 +286,11 @@ export type AgentUpdateParams = { export type AgentListParams = { limit?: number; - before?: string | null; - after?: string | null; - order?: 'asc' | 'desc'; - provider?: AgentProvider; - requiredPermission?: number; + requiredPermission: number; + category?: string; + search?: string; + cursor?: string; + promoted?: 0 | 1; }; export type AgentListResponse = { diff --git a/packages/data-schemas/src/methods/agentCategory.ts b/packages/data-schemas/src/methods/agentCategory.ts index ae375811b6..fb1b1aaa14 100644 --- a/packages/data-schemas/src/methods/agentCategory.ts +++ b/packages/data-schemas/src/methods/agentCategory.ts @@ -150,11 +150,11 @@ export function createAgentCategoryMethods(mongoose: typeof import('mongoose')) */ async function ensureDefaultCategories(): Promise { const existingCategories = await getAllCategories(); - + if (existingCategories.length > 0) { return false; // Categories already exist } - + const defaultCategories = [ { value: 'general', @@ -182,14 +182,14 @@ export function createAgentCategoryMethods(mongoose: typeof import('mongoose')) }, { value: 'it', - label: 'Information Technology', + label: 'IT', description: 'Agents for IT support, technical troubleshooting, and system administration', order: 4, }, { value: 'sales', - label: 'Sales & Marketing', - description: 'Agents focused on sales processes, customer relations, and marketing', + label: 'Sales', + description: 'Agents focused on sales processes, customer relations.', order: 5, }, { @@ -199,7 +199,7 @@ export function createAgentCategoryMethods(mongoose: typeof import('mongoose')) order: 6, }, ]; - + await seedCategories(defaultCategories); return true; // Categories were seeded }