feat: add agent roles/permissions

This commit is contained in:
Danny Avila 2024-09-03 22:03:59 -04:00
parent e54352e4c7
commit 2931d1d9cb
No known key found for this signature in database
GPG key ID: 2DD9CC89B9B50364
5 changed files with 84 additions and 13 deletions

View file

@ -4,6 +4,7 @@ const {
roleDefaults, roleDefaults,
PermissionTypes, PermissionTypes,
removeNullishValues, removeNullishValues,
agentPermissionsSchema,
promptPermissionsSchema, promptPermissionsSchema,
bookmarkPermissionsSchema, bookmarkPermissionsSchema,
} = require('librechat-data-provider'); } = require('librechat-data-provider');
@ -71,6 +72,7 @@ const updateRoleByName = async function (roleName, updates) {
}; };
const permissionSchemas = { const permissionSchemas = {
[PermissionTypes.AGENTS]: agentPermissionsSchema,
[PermissionTypes.PROMPTS]: promptPermissionsSchema, [PermissionTypes.PROMPTS]: promptPermissionsSchema,
[PermissionTypes.BOOKMARKS]: bookmarkPermissionsSchema, [PermissionTypes.BOOKMARKS]: bookmarkPermissionsSchema,
}; };

View file

@ -28,6 +28,20 @@ const roleSchema = new mongoose.Schema({
default: true, default: true,
}, },
}, },
[PermissionTypes.AGENTS]: {
[Permissions.SHARED_GLOBAL]: {
type: Boolean,
default: false,
},
[Permissions.USE]: {
type: Boolean,
default: true,
},
[Permissions.CREATE]: {
type: Boolean,
default: true,
},
},
}); });
const Role = mongoose.model('Role', roleSchema); const Role = mongoose.model('Role', roleSchema);

View file

@ -1,5 +1,5 @@
const { nanoid } = require('nanoid'); const { nanoid } = require('nanoid');
const { FileContext } = require('librechat-data-provider'); const { FileContext, Constants } = require('librechat-data-provider');
const { const {
getAgent, getAgent,
createAgent, createAgent,
@ -9,6 +9,7 @@ const {
} = require('~/models/Agent'); } = require('~/models/Agent');
const { getStrategyFunctions } = require('~/server/services/Files/strategies'); const { getStrategyFunctions } = require('~/server/services/Files/strategies');
const { uploadImageBuffer } = require('~/server/services/Files/process'); const { uploadImageBuffer } = require('~/server/services/Files/process');
const { getProjectByName } = require('~/models/Project');
const { updateAgentProjects } = require('~/models/Agent'); const { updateAgentProjects } = require('~/models/Agent');
const { deleteFileByFilter } = require('~/models/File'); const { deleteFileByFilter } = require('~/models/File');
const { logger } = require('~/config'); const { logger } = require('~/config');
@ -54,16 +55,31 @@ const createAgentHandler = async (req, res) => {
* @param {object} req - Express Request * @param {object} req - Express Request
* @param {object} req.params - Request params * @param {object} req.params - Request params
* @param {string} req.params.id - Agent identifier. * @param {string} req.params.id - Agent identifier.
* @returns {Agent} 200 - success response - application/json * @param {object} req.user - Authenticated user information
* @param {string} req.user.id - User ID
* @returns {Promise<Agent>} 200 - success response - application/json
* @returns {Error} 404 - Agent not found * @returns {Error} 404 - Agent not found
*/ */
const getAgentHandler = async (req, res) => { const getAgentHandler = async (req, res) => {
try { try {
const id = req.params.id; const id = req.params.id;
const agent = await getAgent({ id }); const author = req.user.id;
let query = { id, author };
const globalProject = await getProjectByName(Constants.GLOBAL_PROJECT_NAME, ['agentIds']);
if (globalProject && (globalProject.agentIds?.length ?? 0) > 0) {
query = {
$or: [{ id, $in: globalProject.agentIds }, query],
};
}
const agent = await getAgent(query);
if (!agent) { if (!agent) {
return res.status(404).json({ error: 'Agent not found' }); return res.status(404).json({ error: 'Agent not found' });
} }
return res.status(200).json(agent); return res.status(200).json(agent);
} catch (error) { } catch (error) {
logger.error('[/Agents/:id] Error retrieving agent', error); logger.error('[/Agents/:id] Error retrieving agent', error);
@ -127,7 +143,7 @@ const deleteAgentHandler = async (req, res) => {
* @param {object} req - Express Request * @param {object} req - Express Request
* @param {object} req.query - Request query * @param {object} req.query - Request query
* @param {string} [req.query.user] - The user ID of the agent's author. * @param {string} [req.query.user] - The user ID of the agent's author.
* @returns {AgentListResponse} 200 - success response - application/json * @returns {Promise<AgentListResponse>} 200 - success response - application/json
*/ */
const getListAgentsHandler = async (req, res) => { const getListAgentsHandler = async (req, res) => {
try { try {

View file

@ -1,11 +1,30 @@
const multer = require('multer'); const multer = require('multer');
const express = require('express'); const express = require('express');
const { PermissionTypes, Permissions } = require('librechat-data-provider');
const { requireJwtAuth, generateCheckAccess } = require('~/server/middleware');
const v1 = require('~/server/controllers/agents/v1'); const v1 = require('~/server/controllers/agents/v1');
const actions = require('./actions'); const actions = require('./actions');
const upload = multer(); const upload = multer();
const router = express.Router(); const router = express.Router();
const checkAgentAccess = generateCheckAccess(PermissionTypes.AGENTS, [Permissions.USE]);
const checkAgentCreate = generateCheckAccess(PermissionTypes.AGENTS, [
Permissions.USE,
Permissions.CREATE,
]);
const checkGlobalAgentShare = generateCheckAccess(
PermissionTypes.AGENTS,
[Permissions.USE, Permissions.CREATE],
{
[Permissions.SHARED_GLOBAL]: ['projectIds', 'removeProjectIds'],
},
);
router.use(requireJwtAuth);
router.use(checkAgentAccess);
/** /**
* Agent actions route. * Agent actions route.
* @route GET|POST /agents/actions * @route GET|POST /agents/actions
@ -27,7 +46,7 @@ router.use('/tools', (req, res) => {
* @param {AgentCreateParams} req.body - The agent creation parameters. * @param {AgentCreateParams} req.body - The agent creation parameters.
* @returns {Agent} 201 - Success response - application/json * @returns {Agent} 201 - Success response - application/json
*/ */
router.post('/', v1.createAgent); router.post('/', checkAgentCreate, v1.createAgent);
/** /**
* Retrieves an agent. * Retrieves an agent.
@ -35,7 +54,7 @@ router.post('/', v1.createAgent);
* @param {string} req.params.id - Agent identifier. * @param {string} req.params.id - Agent identifier.
* @returns {Agent} 200 - Success response - application/json * @returns {Agent} 200 - Success response - application/json
*/ */
router.get('/:id', v1.getAgent); router.get('/:id', checkAgentAccess, v1.getAgent);
/** /**
* Updates an agent. * Updates an agent.
@ -44,7 +63,7 @@ router.get('/:id', v1.getAgent);
* @param {AgentUpdateParams} req.body - The agent update parameters. * @param {AgentUpdateParams} req.body - The agent update parameters.
* @returns {Agent} 200 - Success response - application/json * @returns {Agent} 200 - Success response - application/json
*/ */
router.patch('/:id', v1.updateAgent); router.patch('/:id', checkGlobalAgentShare, v1.updateAgent);
/** /**
* Deletes an agent. * Deletes an agent.
@ -52,7 +71,7 @@ router.patch('/:id', v1.updateAgent);
* @param {string} req.params.id - Agent identifier. * @param {string} req.params.id - Agent identifier.
* @returns {Agent} 200 - success response - application/json * @returns {Agent} 200 - success response - application/json
*/ */
router.delete('/:id', v1.deleteAgent); router.delete('/:id', checkAgentCreate, v1.deleteAgent);
/** /**
* Returns a list of agents. * Returns a list of agents.
@ -60,9 +79,7 @@ router.delete('/:id', v1.deleteAgent);
* @param {AgentListParams} req.query - The agent list parameters for pagination and sorting. * @param {AgentListParams} req.query - The agent list parameters for pagination and sorting.
* @returns {AgentListResponse} 200 - success response - application/json * @returns {AgentListResponse} 200 - success response - application/json
*/ */
router.get('/', v1.getListAgents); router.get('/', checkAgentAccess, v1.getListAgents);
// TODO: handle private agents
/** /**
* Uploads and updates an avatar for a specific agent. * Uploads and updates an avatar for a specific agent.
@ -72,6 +89,6 @@ router.get('/', v1.getListAgents);
* @param {string} [req.body.metadata] - Optional metadata for the agent's avatar. * @param {string} [req.body.metadata] - Optional metadata for the agent's avatar.
* @returns {Object} 200 - success response - application/json * @returns {Object} 200 - success response - application/json
*/ */
router.post('/avatar/:agent_id', upload.single('file'), v1.uploadAgentAvatar); router.post('/avatar/:agent_id', checkAgentAccess, upload.single('file'), v1.uploadAgentAvatar);
module.exports = router; module.exports = router;

View file

@ -23,9 +23,13 @@ export enum PermissionTypes {
*/ */
PROMPTS = 'PROMPTS', PROMPTS = 'PROMPTS',
/** /**
* Type for Bookmarks Permissions * Type for Bookmark Permissions
*/ */
BOOKMARKS = 'BOOKMARKS', BOOKMARKS = 'BOOKMARKS',
/**
* Type for Agent Permissions
*/
AGENTS = 'AGENTS',
} }
/** /**
@ -49,13 +53,22 @@ export const bookmarkPermissionsSchema = z.object({
[Permissions.USE]: z.boolean().default(true), [Permissions.USE]: z.boolean().default(true),
}); });
export const agentPermissionsSchema = z.object({
[Permissions.SHARED_GLOBAL]: z.boolean().default(false),
[Permissions.USE]: z.boolean().default(true),
[Permissions.CREATE]: z.boolean().default(true),
[Permissions.SHARE]: z.boolean().default(false),
});
export const roleSchema = z.object({ export const roleSchema = z.object({
name: z.string(), name: z.string(),
[PermissionTypes.PROMPTS]: promptPermissionsSchema, [PermissionTypes.PROMPTS]: promptPermissionsSchema,
[PermissionTypes.BOOKMARKS]: bookmarkPermissionsSchema, [PermissionTypes.BOOKMARKS]: bookmarkPermissionsSchema,
[PermissionTypes.AGENTS]: agentPermissionsSchema,
}); });
export type TRole = z.infer<typeof roleSchema>; export type TRole = z.infer<typeof roleSchema>;
export type TAgentPermissions = z.infer<typeof agentPermissionsSchema>;
export type TPromptPermissions = z.infer<typeof promptPermissionsSchema>; export type TPromptPermissions = z.infer<typeof promptPermissionsSchema>;
export type TBookmarkPermissions = z.infer<typeof bookmarkPermissionsSchema>; export type TBookmarkPermissions = z.infer<typeof bookmarkPermissionsSchema>;
@ -71,11 +84,18 @@ const defaultRolesSchema = z.object({
[PermissionTypes.BOOKMARKS]: bookmarkPermissionsSchema.extend({ [PermissionTypes.BOOKMARKS]: bookmarkPermissionsSchema.extend({
[Permissions.USE]: z.boolean().default(true), [Permissions.USE]: z.boolean().default(true),
}), }),
[PermissionTypes.AGENTS]: agentPermissionsSchema.extend({
[Permissions.SHARED_GLOBAL]: z.boolean().default(true),
[Permissions.USE]: z.boolean().default(true),
[Permissions.CREATE]: z.boolean().default(true),
[Permissions.SHARE]: z.boolean().default(true),
}),
}), }),
[SystemRoles.USER]: roleSchema.extend({ [SystemRoles.USER]: roleSchema.extend({
name: z.literal(SystemRoles.USER), name: z.literal(SystemRoles.USER),
[PermissionTypes.PROMPTS]: promptPermissionsSchema, [PermissionTypes.PROMPTS]: promptPermissionsSchema,
[PermissionTypes.BOOKMARKS]: bookmarkPermissionsSchema, [PermissionTypes.BOOKMARKS]: bookmarkPermissionsSchema,
[PermissionTypes.AGENTS]: agentPermissionsSchema,
}), }),
}); });
@ -84,10 +104,12 @@ export const roleDefaults = defaultRolesSchema.parse({
name: SystemRoles.ADMIN, name: SystemRoles.ADMIN,
[PermissionTypes.PROMPTS]: {}, [PermissionTypes.PROMPTS]: {},
[PermissionTypes.BOOKMARKS]: {}, [PermissionTypes.BOOKMARKS]: {},
[PermissionTypes.AGENTS]: {},
}, },
[SystemRoles.USER]: { [SystemRoles.USER]: {
name: SystemRoles.USER, name: SystemRoles.USER,
[PermissionTypes.PROMPTS]: {}, [PermissionTypes.PROMPTS]: {},
[PermissionTypes.BOOKMARKS]: {}, [PermissionTypes.BOOKMARKS]: {},
[PermissionTypes.AGENTS]: {},
}, },
}); });