mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-22 08:12:00 +02:00
🔼 feat: "Run Code" Button Toggle (#5988)
* feat: Add 'Run Code' and 'Temporary Chat' permissions to role management * feat: Add NextFunction typedef to api/typedefs.js * feat: Add temporary chat and run code permissions to role schema * refactor: Enhance access check middleware with logging for permission errors and better typing * refactor: Set default value of USE permission to true in multiConvoPermissionsSchema * refactor: Implement checkAccess function for separation of permission validation logic from middleware * feat: Integrate permission checks for tool execution and enhance Markdown code block with execution capability * fix: Convert REDIS_MAX_LISTENERS to a number, closes #5979
This commit is contained in:
parent
2a74ceb630
commit
0e719592c6
14 changed files with 198 additions and 34 deletions
2
api/cache/keyvRedis.js
vendored
2
api/cache/keyvRedis.js
vendored
|
@ -9,7 +9,7 @@ const { REDIS_URI, USE_REDIS, USE_REDIS_CLUSTER, REDIS_CA, REDIS_KEY_PREFIX, RED
|
||||||
|
|
||||||
let keyvRedis;
|
let keyvRedis;
|
||||||
const redis_prefix = REDIS_KEY_PREFIX || '';
|
const redis_prefix = REDIS_KEY_PREFIX || '';
|
||||||
const redis_max_listeners = REDIS_MAX_LISTENERS || 10;
|
const redis_max_listeners = Number(REDIS_MAX_LISTENERS) || 10;
|
||||||
|
|
||||||
function mapURI(uri) {
|
function mapURI(uri) {
|
||||||
const regex =
|
const regex =
|
||||||
|
|
|
@ -6,8 +6,10 @@ const {
|
||||||
removeNullishValues,
|
removeNullishValues,
|
||||||
agentPermissionsSchema,
|
agentPermissionsSchema,
|
||||||
promptPermissionsSchema,
|
promptPermissionsSchema,
|
||||||
|
runCodePermissionsSchema,
|
||||||
bookmarkPermissionsSchema,
|
bookmarkPermissionsSchema,
|
||||||
multiConvoPermissionsSchema,
|
multiConvoPermissionsSchema,
|
||||||
|
temporaryChatPermissionsSchema,
|
||||||
} = require('librechat-data-provider');
|
} = require('librechat-data-provider');
|
||||||
const getLogStores = require('~/cache/getLogStores');
|
const getLogStores = require('~/cache/getLogStores');
|
||||||
const Role = require('~/models/schema/roleSchema');
|
const Role = require('~/models/schema/roleSchema');
|
||||||
|
@ -77,6 +79,8 @@ const permissionSchemas = {
|
||||||
[PermissionTypes.PROMPTS]: promptPermissionsSchema,
|
[PermissionTypes.PROMPTS]: promptPermissionsSchema,
|
||||||
[PermissionTypes.BOOKMARKS]: bookmarkPermissionsSchema,
|
[PermissionTypes.BOOKMARKS]: bookmarkPermissionsSchema,
|
||||||
[PermissionTypes.MULTI_CONVO]: multiConvoPermissionsSchema,
|
[PermissionTypes.MULTI_CONVO]: multiConvoPermissionsSchema,
|
||||||
|
[PermissionTypes.TEMPORARY_CHAT]: temporaryChatPermissionsSchema,
|
||||||
|
[PermissionTypes.RUN_CODE]: runCodePermissionsSchema,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -48,6 +48,18 @@ const roleSchema = new mongoose.Schema({
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
[PermissionTypes.TEMPORARY_CHAT]: {
|
||||||
|
[Permissions.USE]: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[PermissionTypes.RUN_CODE]: {
|
||||||
|
[Permissions.USE]: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const Role = mongoose.model('Role', roleSchema);
|
const Role = mongoose.model('Role', roleSchema);
|
||||||
|
|
|
@ -1,10 +1,17 @@
|
||||||
const { nanoid } = require('nanoid');
|
const { nanoid } = require('nanoid');
|
||||||
const { EnvVar } = require('@librechat/agents');
|
const { EnvVar } = require('@librechat/agents');
|
||||||
const { Tools, AuthType, ToolCallTypes } = require('librechat-data-provider');
|
const {
|
||||||
|
Tools,
|
||||||
|
AuthType,
|
||||||
|
Permissions,
|
||||||
|
ToolCallTypes,
|
||||||
|
PermissionTypes,
|
||||||
|
} = require('librechat-data-provider');
|
||||||
const { processFileURL, uploadImageBuffer } = require('~/server/services/Files/process');
|
const { processFileURL, uploadImageBuffer } = require('~/server/services/Files/process');
|
||||||
const { processCodeOutput } = require('~/server/services/Files/Code/process');
|
const { processCodeOutput } = require('~/server/services/Files/Code/process');
|
||||||
const { loadAuthValues, loadTools } = require('~/app/clients/tools/util');
|
|
||||||
const { createToolCall, getToolCallsByConvo } = require('~/models/ToolCall');
|
const { createToolCall, getToolCallsByConvo } = require('~/models/ToolCall');
|
||||||
|
const { loadAuthValues, loadTools } = require('~/app/clients/tools/util');
|
||||||
|
const { checkAccess } = require('~/server/middleware');
|
||||||
const { getMessage } = require('~/models/Message');
|
const { getMessage } = require('~/models/Message');
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
|
||||||
|
@ -12,6 +19,10 @@ const fieldsMap = {
|
||||||
[Tools.execute_code]: [EnvVar.CODE_API_KEY],
|
[Tools.execute_code]: [EnvVar.CODE_API_KEY],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toolAccessPermType = {
|
||||||
|
[Tools.execute_code]: PermissionTypes.RUN_CODE,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {ServerRequest} req - The request object, containing information about the HTTP request.
|
* @param {ServerRequest} req - The request object, containing information about the HTTP request.
|
||||||
* @param {ServerResponse} res - The response object, used to send back the desired HTTP response.
|
* @param {ServerResponse} res - The response object, used to send back the desired HTTP response.
|
||||||
|
@ -58,6 +69,7 @@ const verifyToolAuth = async (req, res) => {
|
||||||
/**
|
/**
|
||||||
* @param {ServerRequest} req - The request object, containing information about the HTTP request.
|
* @param {ServerRequest} req - The request object, containing information about the HTTP request.
|
||||||
* @param {ServerResponse} res - The response object, used to send back the desired HTTP response.
|
* @param {ServerResponse} res - The response object, used to send back the desired HTTP response.
|
||||||
|
* @param {NextFunction} next - The next middleware function to call.
|
||||||
* @returns {Promise<void>} A promise that resolves when the function has completed.
|
* @returns {Promise<void>} A promise that resolves when the function has completed.
|
||||||
*/
|
*/
|
||||||
const callTool = async (req, res) => {
|
const callTool = async (req, res) => {
|
||||||
|
@ -83,6 +95,16 @@ const callTool = async (req, res) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
logger.debug(`[${toolId}/call] User: ${req.user.id}`);
|
logger.debug(`[${toolId}/call] User: ${req.user.id}`);
|
||||||
|
let hasAccess = true;
|
||||||
|
if (toolAccessPermType[toolId]) {
|
||||||
|
hasAccess = await checkAccess(req.user, toolAccessPermType[toolId], [Permissions.USE]);
|
||||||
|
}
|
||||||
|
if (!hasAccess) {
|
||||||
|
logger.warn(
|
||||||
|
`[${toolAccessPermType[toolId]}] Forbidden: Insufficient permissions for User ${req.user.id}: ${Permissions.USE}`,
|
||||||
|
);
|
||||||
|
return res.status(403).json({ message: 'Forbidden: Insufficient permissions' });
|
||||||
|
}
|
||||||
const { loadedTools } = await loadTools({
|
const { loadedTools } = await loadTools({
|
||||||
user: req.user.id,
|
user: req.user.id,
|
||||||
tools: [toolId],
|
tools: [toolId],
|
||||||
|
|
|
@ -1,4 +1,42 @@
|
||||||
const { getRoleByName } = require('~/models/Role');
|
const { getRoleByName } = require('~/models/Role');
|
||||||
|
const { logger } = require('~/config');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Core function to check if a user has one or more required permissions
|
||||||
|
*
|
||||||
|
* @param {object} user - The user object
|
||||||
|
* @param {PermissionTypes} permissionType - The type of permission to check
|
||||||
|
* @param {Permissions[]} permissions - The list of specific permissions to check
|
||||||
|
* @param {Record<Permissions, string[]>} [bodyProps] - An optional object where keys are permissions and values are arrays of properties to check
|
||||||
|
* @param {object} [checkObject] - The object to check properties against
|
||||||
|
* @returns {Promise<boolean>} Whether the user has the required permissions
|
||||||
|
*/
|
||||||
|
const checkAccess = async (user, permissionType, permissions, bodyProps = {}, checkObject = {}) => {
|
||||||
|
if (!user) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const role = await getRoleByName(user.role);
|
||||||
|
if (role && role[permissionType]) {
|
||||||
|
const hasAnyPermission = permissions.some((permission) => {
|
||||||
|
if (role[permissionType][permission]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bodyProps[permission] && checkObject) {
|
||||||
|
return bodyProps[permission].some((prop) =>
|
||||||
|
Object.prototype.hasOwnProperty.call(checkObject, prop),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
return hasAnyPermission;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Middleware to check if a user has one or more required permissions, optionally based on `req.body` properties.
|
* Middleware to check if a user has one or more required permissions, optionally based on `req.body` properties.
|
||||||
|
@ -6,42 +44,35 @@ const { getRoleByName } = require('~/models/Role');
|
||||||
* @param {PermissionTypes} permissionType - The type of permission to check.
|
* @param {PermissionTypes} permissionType - The type of permission to check.
|
||||||
* @param {Permissions[]} permissions - The list of specific permissions to check.
|
* @param {Permissions[]} permissions - The list of specific permissions to check.
|
||||||
* @param {Record<Permissions, string[]>} [bodyProps] - An optional object where keys are permissions and values are arrays of `req.body` properties to check.
|
* @param {Record<Permissions, string[]>} [bodyProps] - An optional object where keys are permissions and values are arrays of `req.body` properties to check.
|
||||||
* @returns {Function} Express middleware function.
|
* @returns {(req: ServerRequest, res: ServerResponse, next: NextFunction) => Promise<void>} Express middleware function.
|
||||||
*/
|
*/
|
||||||
const generateCheckAccess = (permissionType, permissions, bodyProps = {}) => {
|
const generateCheckAccess = (permissionType, permissions, bodyProps = {}) => {
|
||||||
return async (req, res, next) => {
|
return async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const { user } = req;
|
const hasAccess = await checkAccess(
|
||||||
if (!user) {
|
req.user,
|
||||||
return res.status(401).json({ message: 'Authorization required' });
|
permissionType,
|
||||||
}
|
permissions,
|
||||||
|
bodyProps,
|
||||||
const role = await getRoleByName(user.role);
|
req.body,
|
||||||
if (role && role[permissionType]) {
|
);
|
||||||
const hasAnyPermission = permissions.some((permission) => {
|
|
||||||
if (role[permissionType][permission]) {
|
if (hasAccess) {
|
||||||
return true;
|
return next();
|
||||||
}
|
|
||||||
|
|
||||||
if (bodyProps[permission] && req.body) {
|
|
||||||
return bodyProps[permission].some((prop) =>
|
|
||||||
Object.prototype.hasOwnProperty.call(req.body, prop),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (hasAnyPermission) {
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.warn(
|
||||||
|
`[${permissionType}] Forbidden: Insufficient permissions for User ${req.user.id}: ${permissions.join(', ')}`,
|
||||||
|
);
|
||||||
return res.status(403).json({ message: 'Forbidden: Insufficient permissions' });
|
return res.status(403).json({ message: 'Forbidden: Insufficient permissions' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
return res.status(500).json({ message: `Server error: ${error.message}` });
|
return res.status(500).json({ message: `Server error: ${error.message}` });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = generateCheckAccess;
|
module.exports = {
|
||||||
|
checkAccess,
|
||||||
|
generateCheckAccess,
|
||||||
|
};
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
const checkAdmin = require('./checkAdmin');
|
const checkAdmin = require('./checkAdmin');
|
||||||
const generateCheckAccess = require('./generateCheckAccess');
|
const { checkAccess, generateCheckAccess } = require('./generateCheckAccess');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
checkAdmin,
|
checkAdmin,
|
||||||
|
checkAccess,
|
||||||
generateCheckAccess,
|
generateCheckAccess,
|
||||||
};
|
};
|
||||||
|
|
|
@ -34,6 +34,7 @@ async function loadDefaultInterface(config, configDefaults, roleName = SystemRol
|
||||||
multiConvo: interfaceConfig?.multiConvo ?? defaults.multiConvo,
|
multiConvo: interfaceConfig?.multiConvo ?? defaults.multiConvo,
|
||||||
agents: interfaceConfig?.agents ?? defaults.agents,
|
agents: interfaceConfig?.agents ?? defaults.agents,
|
||||||
temporaryChat: interfaceConfig?.temporaryChat ?? defaults.temporaryChat,
|
temporaryChat: interfaceConfig?.temporaryChat ?? defaults.temporaryChat,
|
||||||
|
runCode: interfaceConfig?.runCode ?? defaults.runCode,
|
||||||
customWelcome: interfaceConfig?.customWelcome ?? defaults.customWelcome,
|
customWelcome: interfaceConfig?.customWelcome ?? defaults.customWelcome,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -42,12 +43,16 @@ async function loadDefaultInterface(config, configDefaults, roleName = SystemRol
|
||||||
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: loadedInterface.bookmarks },
|
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: loadedInterface.bookmarks },
|
||||||
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: loadedInterface.multiConvo },
|
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: loadedInterface.multiConvo },
|
||||||
[PermissionTypes.AGENTS]: { [Permissions.USE]: loadedInterface.agents },
|
[PermissionTypes.AGENTS]: { [Permissions.USE]: loadedInterface.agents },
|
||||||
|
[PermissionTypes.TEMPORARY_CHAT]: { [Permissions.USE]: loadedInterface.temporaryChat },
|
||||||
|
[PermissionTypes.RUN_CODE]: { [Permissions.USE]: loadedInterface.runCode },
|
||||||
});
|
});
|
||||||
await updateAccessPermissions(SystemRoles.ADMIN, {
|
await updateAccessPermissions(SystemRoles.ADMIN, {
|
||||||
[PermissionTypes.PROMPTS]: { [Permissions.USE]: loadedInterface.prompts },
|
[PermissionTypes.PROMPTS]: { [Permissions.USE]: loadedInterface.prompts },
|
||||||
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: loadedInterface.bookmarks },
|
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: loadedInterface.bookmarks },
|
||||||
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: loadedInterface.multiConvo },
|
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: loadedInterface.multiConvo },
|
||||||
[PermissionTypes.AGENTS]: { [Permissions.USE]: loadedInterface.agents },
|
[PermissionTypes.AGENTS]: { [Permissions.USE]: loadedInterface.agents },
|
||||||
|
[PermissionTypes.TEMPORARY_CHAT]: { [Permissions.USE]: loadedInterface.temporaryChat },
|
||||||
|
[PermissionTypes.RUN_CODE]: { [Permissions.USE]: loadedInterface.runCode },
|
||||||
});
|
});
|
||||||
|
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
|
|
@ -14,6 +14,8 @@ describe('loadDefaultInterface', () => {
|
||||||
bookmarks: true,
|
bookmarks: true,
|
||||||
multiConvo: true,
|
multiConvo: true,
|
||||||
agents: true,
|
agents: true,
|
||||||
|
temporaryChat: true,
|
||||||
|
runCode: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const configDefaults = { interface: {} };
|
const configDefaults = { interface: {} };
|
||||||
|
@ -25,6 +27,8 @@ describe('loadDefaultInterface', () => {
|
||||||
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: true },
|
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: true },
|
||||||
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: true },
|
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: true },
|
||||||
[PermissionTypes.AGENTS]: { [Permissions.USE]: true },
|
[PermissionTypes.AGENTS]: { [Permissions.USE]: true },
|
||||||
|
[PermissionTypes.TEMPORARY_CHAT]: { [Permissions.USE]: true },
|
||||||
|
[PermissionTypes.RUN_CODE]: { [Permissions.USE]: true },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -35,6 +39,8 @@ describe('loadDefaultInterface', () => {
|
||||||
bookmarks: false,
|
bookmarks: false,
|
||||||
multiConvo: false,
|
multiConvo: false,
|
||||||
agents: false,
|
agents: false,
|
||||||
|
temporaryChat: false,
|
||||||
|
runCode: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const configDefaults = { interface: {} };
|
const configDefaults = { interface: {} };
|
||||||
|
@ -46,6 +52,8 @@ describe('loadDefaultInterface', () => {
|
||||||
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: false },
|
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: false },
|
||||||
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: false },
|
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: false },
|
||||||
[PermissionTypes.AGENTS]: { [Permissions.USE]: false },
|
[PermissionTypes.AGENTS]: { [Permissions.USE]: false },
|
||||||
|
[PermissionTypes.TEMPORARY_CHAT]: { [Permissions.USE]: false },
|
||||||
|
[PermissionTypes.RUN_CODE]: { [Permissions.USE]: false },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -60,6 +68,8 @@ describe('loadDefaultInterface', () => {
|
||||||
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: undefined },
|
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: undefined },
|
||||||
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: undefined },
|
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: undefined },
|
||||||
[PermissionTypes.AGENTS]: { [Permissions.USE]: undefined },
|
[PermissionTypes.AGENTS]: { [Permissions.USE]: undefined },
|
||||||
|
[PermissionTypes.TEMPORARY_CHAT]: { [Permissions.USE]: undefined },
|
||||||
|
[PermissionTypes.RUN_CODE]: { [Permissions.USE]: undefined },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -70,6 +80,8 @@ describe('loadDefaultInterface', () => {
|
||||||
bookmarks: undefined,
|
bookmarks: undefined,
|
||||||
multiConvo: undefined,
|
multiConvo: undefined,
|
||||||
agents: undefined,
|
agents: undefined,
|
||||||
|
temporaryChat: undefined,
|
||||||
|
runCode: undefined,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const configDefaults = { interface: {} };
|
const configDefaults = { interface: {} };
|
||||||
|
@ -81,6 +93,8 @@ describe('loadDefaultInterface', () => {
|
||||||
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: undefined },
|
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: undefined },
|
||||||
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: undefined },
|
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: undefined },
|
||||||
[PermissionTypes.AGENTS]: { [Permissions.USE]: undefined },
|
[PermissionTypes.AGENTS]: { [Permissions.USE]: undefined },
|
||||||
|
[PermissionTypes.TEMPORARY_CHAT]: { [Permissions.USE]: undefined },
|
||||||
|
[PermissionTypes.RUN_CODE]: { [Permissions.USE]: undefined },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -91,6 +105,8 @@ describe('loadDefaultInterface', () => {
|
||||||
bookmarks: false,
|
bookmarks: false,
|
||||||
multiConvo: undefined,
|
multiConvo: undefined,
|
||||||
agents: true,
|
agents: true,
|
||||||
|
temporaryChat: undefined,
|
||||||
|
runCode: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const configDefaults = { interface: {} };
|
const configDefaults = { interface: {} };
|
||||||
|
@ -102,6 +118,8 @@ describe('loadDefaultInterface', () => {
|
||||||
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: false },
|
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: false },
|
||||||
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: undefined },
|
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: undefined },
|
||||||
[PermissionTypes.AGENTS]: { [Permissions.USE]: true },
|
[PermissionTypes.AGENTS]: { [Permissions.USE]: true },
|
||||||
|
[PermissionTypes.TEMPORARY_CHAT]: { [Permissions.USE]: undefined },
|
||||||
|
[PermissionTypes.RUN_CODE]: { [Permissions.USE]: false },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -113,6 +131,8 @@ describe('loadDefaultInterface', () => {
|
||||||
bookmarks: true,
|
bookmarks: true,
|
||||||
multiConvo: true,
|
multiConvo: true,
|
||||||
agents: true,
|
agents: true,
|
||||||
|
temporaryChat: true,
|
||||||
|
runCode: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -123,6 +143,8 @@ describe('loadDefaultInterface', () => {
|
||||||
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: true },
|
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: true },
|
||||||
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: true },
|
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: true },
|
||||||
[PermissionTypes.AGENTS]: { [Permissions.USE]: true },
|
[PermissionTypes.AGENTS]: { [Permissions.USE]: true },
|
||||||
|
[PermissionTypes.TEMPORARY_CHAT]: { [Permissions.USE]: true },
|
||||||
|
[PermissionTypes.RUN_CODE]: { [Permissions.USE]: true },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -137,6 +159,8 @@ describe('loadDefaultInterface', () => {
|
||||||
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: undefined },
|
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: undefined },
|
||||||
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: true },
|
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: true },
|
||||||
[PermissionTypes.AGENTS]: { [Permissions.USE]: undefined },
|
[PermissionTypes.AGENTS]: { [Permissions.USE]: undefined },
|
||||||
|
[PermissionTypes.TEMPORARY_CHAT]: { [Permissions.USE]: undefined },
|
||||||
|
[PermissionTypes.RUN_CODE]: { [Permissions.USE]: undefined },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -151,6 +175,8 @@ describe('loadDefaultInterface', () => {
|
||||||
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: undefined },
|
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: undefined },
|
||||||
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: false },
|
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: false },
|
||||||
[PermissionTypes.AGENTS]: { [Permissions.USE]: undefined },
|
[PermissionTypes.AGENTS]: { [Permissions.USE]: undefined },
|
||||||
|
[PermissionTypes.TEMPORARY_CHAT]: { [Permissions.USE]: undefined },
|
||||||
|
[PermissionTypes.RUN_CODE]: { [Permissions.USE]: undefined },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -165,6 +191,8 @@ describe('loadDefaultInterface', () => {
|
||||||
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: undefined },
|
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: undefined },
|
||||||
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: undefined },
|
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: undefined },
|
||||||
[PermissionTypes.AGENTS]: { [Permissions.USE]: undefined },
|
[PermissionTypes.AGENTS]: { [Permissions.USE]: undefined },
|
||||||
|
[PermissionTypes.TEMPORARY_CHAT]: { [Permissions.USE]: undefined },
|
||||||
|
[PermissionTypes.RUN_CODE]: { [Permissions.USE]: undefined },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -175,6 +203,8 @@ describe('loadDefaultInterface', () => {
|
||||||
bookmarks: false,
|
bookmarks: false,
|
||||||
multiConvo: true,
|
multiConvo: true,
|
||||||
agents: false,
|
agents: false,
|
||||||
|
temporaryChat: true,
|
||||||
|
runCode: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const configDefaults = { interface: {} };
|
const configDefaults = { interface: {} };
|
||||||
|
@ -186,6 +216,8 @@ describe('loadDefaultInterface', () => {
|
||||||
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: false },
|
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: false },
|
||||||
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: true },
|
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: true },
|
||||||
[PermissionTypes.AGENTS]: { [Permissions.USE]: false },
|
[PermissionTypes.AGENTS]: { [Permissions.USE]: false },
|
||||||
|
[PermissionTypes.TEMPORARY_CHAT]: { [Permissions.USE]: true },
|
||||||
|
[PermissionTypes.RUN_CODE]: { [Permissions.USE]: false },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -197,6 +229,8 @@ describe('loadDefaultInterface', () => {
|
||||||
bookmarks: true,
|
bookmarks: true,
|
||||||
multiConvo: false,
|
multiConvo: false,
|
||||||
agents: undefined,
|
agents: undefined,
|
||||||
|
temporaryChat: undefined,
|
||||||
|
runCode: undefined,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -207,6 +241,8 @@ describe('loadDefaultInterface', () => {
|
||||||
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: true },
|
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: true },
|
||||||
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: false },
|
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: false },
|
||||||
[PermissionTypes.AGENTS]: { [Permissions.USE]: undefined },
|
[PermissionTypes.AGENTS]: { [Permissions.USE]: undefined },
|
||||||
|
[PermissionTypes.TEMPORARY_CHAT]: { [Permissions.USE]: undefined },
|
||||||
|
[PermissionTypes.RUN_CODE]: { [Permissions.USE]: undefined },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -20,6 +20,12 @@
|
||||||
* @memberof typedefs
|
* @memberof typedefs
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports NextFunction
|
||||||
|
* @typedef {import('express').NextFunction} NextFunction
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @exports AgentRun
|
* @exports AgentRun
|
||||||
* @typedef {import('@librechat/agents').Run} AgentRun
|
* @typedef {import('@librechat/agents').Run} AgentRun
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { useRecoilValue } from 'recoil';
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
import rehypeHighlight from 'rehype-highlight';
|
import rehypeHighlight from 'rehype-highlight';
|
||||||
import remarkDirective from 'remark-directive';
|
import remarkDirective from 'remark-directive';
|
||||||
|
import { PermissionTypes, Permissions } from 'librechat-data-provider';
|
||||||
import type { Pluggable } from 'unified';
|
import type { Pluggable } from 'unified';
|
||||||
import {
|
import {
|
||||||
useToastContext,
|
useToastContext,
|
||||||
|
@ -17,6 +18,7 @@ import {
|
||||||
import { Artifact, artifactPlugin } from '~/components/Artifacts/Artifact';
|
import { Artifact, artifactPlugin } from '~/components/Artifacts/Artifact';
|
||||||
import { langSubset, preprocessLaTeX, handleDoubleClick } from '~/utils';
|
import { langSubset, preprocessLaTeX, handleDoubleClick } from '~/utils';
|
||||||
import CodeBlock from '~/components/Messages/Content/CodeBlock';
|
import CodeBlock from '~/components/Messages/Content/CodeBlock';
|
||||||
|
import useHasAccess from '~/hooks/Roles/useHasAccess';
|
||||||
import { useFileDownload } from '~/data-provider';
|
import { useFileDownload } from '~/data-provider';
|
||||||
import useLocalize from '~/hooks/useLocalize';
|
import useLocalize from '~/hooks/useLocalize';
|
||||||
import store from '~/store';
|
import store from '~/store';
|
||||||
|
@ -28,6 +30,10 @@ type TCodeProps = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const code: React.ElementType = memo(({ className, children }: TCodeProps) => {
|
export const code: React.ElementType = memo(({ className, children }: TCodeProps) => {
|
||||||
|
const canRunCode = useHasAccess({
|
||||||
|
permissionType: PermissionTypes.RUN_CODE,
|
||||||
|
permission: Permissions.USE,
|
||||||
|
});
|
||||||
const match = /language-(\w+)/.exec(className ?? '');
|
const match = /language-(\w+)/.exec(className ?? '');
|
||||||
const lang = match && match[1];
|
const lang = match && match[1];
|
||||||
const isMath = lang === 'math';
|
const isMath = lang === 'math';
|
||||||
|
@ -49,7 +55,14 @@ export const code: React.ElementType = memo(({ className, children }: TCodeProps
|
||||||
</code>
|
</code>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return <CodeBlock lang={lang ?? 'text'} codeChildren={children} blockIndex={blockIndex} />;
|
return (
|
||||||
|
<CodeBlock
|
||||||
|
lang={lang ?? 'text'}
|
||||||
|
codeChildren={children}
|
||||||
|
blockIndex={blockIndex}
|
||||||
|
allowExecution={canRunCode}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
2
package-lock.json
generated
2
package-lock.json
generated
|
@ -38885,7 +38885,7 @@
|
||||||
},
|
},
|
||||||
"packages/data-provider": {
|
"packages/data-provider": {
|
||||||
"name": "librechat-data-provider",
|
"name": "librechat-data-provider",
|
||||||
"version": "0.7.6991",
|
"version": "0.7.6992",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "librechat-data-provider",
|
"name": "librechat-data-provider",
|
||||||
"version": "0.7.6991",
|
"version": "0.7.6992",
|
||||||
"description": "data services for librechat apps",
|
"description": "data services for librechat apps",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"module": "dist/index.es.js",
|
"module": "dist/index.es.js",
|
||||||
|
|
|
@ -457,6 +457,7 @@ export const intefaceSchema = z
|
||||||
prompts: z.boolean().optional(),
|
prompts: z.boolean().optional(),
|
||||||
agents: z.boolean().optional(),
|
agents: z.boolean().optional(),
|
||||||
temporaryChat: z.boolean().optional(),
|
temporaryChat: z.boolean().optional(),
|
||||||
|
runCode: z.boolean().optional(),
|
||||||
})
|
})
|
||||||
.default({
|
.default({
|
||||||
endpointsMenu: true,
|
endpointsMenu: true,
|
||||||
|
@ -469,6 +470,7 @@ export const intefaceSchema = z
|
||||||
prompts: true,
|
prompts: true,
|
||||||
agents: true,
|
agents: true,
|
||||||
temporaryChat: true,
|
temporaryChat: true,
|
||||||
|
runCode: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TInterfaceConfig = z.infer<typeof intefaceSchema>;
|
export type TInterfaceConfig = z.infer<typeof intefaceSchema>;
|
||||||
|
|
|
@ -34,6 +34,14 @@ export enum PermissionTypes {
|
||||||
* Type for Multi-Conversation Permissions
|
* Type for Multi-Conversation Permissions
|
||||||
*/
|
*/
|
||||||
MULTI_CONVO = 'MULTI_CONVO',
|
MULTI_CONVO = 'MULTI_CONVO',
|
||||||
|
/**
|
||||||
|
* Type for Temporary Chat
|
||||||
|
*/
|
||||||
|
TEMPORARY_CHAT = 'TEMPORARY_CHAT',
|
||||||
|
/**
|
||||||
|
* Type for using the "Run Code" LC Code Interpreter API feature
|
||||||
|
*/
|
||||||
|
RUN_CODE = 'RUN_CODE',
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -68,7 +76,15 @@ export const agentPermissionsSchema = z.object({
|
||||||
});
|
});
|
||||||
|
|
||||||
export const multiConvoPermissionsSchema = z.object({
|
export const multiConvoPermissionsSchema = z.object({
|
||||||
[Permissions.USE]: z.boolean().default(false),
|
[Permissions.USE]: z.boolean().default(true),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const temporaryChatPermissionsSchema = z.object({
|
||||||
|
[Permissions.USE]: z.boolean().default(true),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const runCodePermissionsSchema = z.object({
|
||||||
|
[Permissions.USE]: z.boolean().default(true),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const roleSchema = z.object({
|
export const roleSchema = z.object({
|
||||||
|
@ -77,6 +93,8 @@ export const roleSchema = z.object({
|
||||||
[PermissionTypes.BOOKMARKS]: bookmarkPermissionsSchema,
|
[PermissionTypes.BOOKMARKS]: bookmarkPermissionsSchema,
|
||||||
[PermissionTypes.AGENTS]: agentPermissionsSchema,
|
[PermissionTypes.AGENTS]: agentPermissionsSchema,
|
||||||
[PermissionTypes.MULTI_CONVO]: multiConvoPermissionsSchema,
|
[PermissionTypes.MULTI_CONVO]: multiConvoPermissionsSchema,
|
||||||
|
[PermissionTypes.TEMPORARY_CHAT]: temporaryChatPermissionsSchema,
|
||||||
|
[PermissionTypes.RUN_CODE]: runCodePermissionsSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TRole = z.infer<typeof roleSchema>;
|
export type TRole = z.infer<typeof roleSchema>;
|
||||||
|
@ -84,6 +102,8 @@ 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>;
|
||||||
export type TMultiConvoPermissions = z.infer<typeof multiConvoPermissionsSchema>;
|
export type TMultiConvoPermissions = z.infer<typeof multiConvoPermissionsSchema>;
|
||||||
|
export type TTemporaryChatPermissions = z.infer<typeof temporaryChatPermissionsSchema>;
|
||||||
|
export type TRunCodePermissions = z.infer<typeof runCodePermissionsSchema>;
|
||||||
|
|
||||||
const defaultRolesSchema = z.object({
|
const defaultRolesSchema = z.object({
|
||||||
[SystemRoles.ADMIN]: roleSchema.extend({
|
[SystemRoles.ADMIN]: roleSchema.extend({
|
||||||
|
@ -106,6 +126,12 @@ const defaultRolesSchema = z.object({
|
||||||
[PermissionTypes.MULTI_CONVO]: multiConvoPermissionsSchema.extend({
|
[PermissionTypes.MULTI_CONVO]: multiConvoPermissionsSchema.extend({
|
||||||
[Permissions.USE]: z.boolean().default(true),
|
[Permissions.USE]: z.boolean().default(true),
|
||||||
}),
|
}),
|
||||||
|
[PermissionTypes.TEMPORARY_CHAT]: temporaryChatPermissionsSchema.extend({
|
||||||
|
[Permissions.USE]: z.boolean().default(true),
|
||||||
|
}),
|
||||||
|
[PermissionTypes.RUN_CODE]: runCodePermissionsSchema.extend({
|
||||||
|
[Permissions.USE]: z.boolean().default(true),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
[SystemRoles.USER]: roleSchema.extend({
|
[SystemRoles.USER]: roleSchema.extend({
|
||||||
name: z.literal(SystemRoles.USER),
|
name: z.literal(SystemRoles.USER),
|
||||||
|
@ -113,6 +139,8 @@ const defaultRolesSchema = z.object({
|
||||||
[PermissionTypes.BOOKMARKS]: bookmarkPermissionsSchema,
|
[PermissionTypes.BOOKMARKS]: bookmarkPermissionsSchema,
|
||||||
[PermissionTypes.AGENTS]: agentPermissionsSchema,
|
[PermissionTypes.AGENTS]: agentPermissionsSchema,
|
||||||
[PermissionTypes.MULTI_CONVO]: multiConvoPermissionsSchema,
|
[PermissionTypes.MULTI_CONVO]: multiConvoPermissionsSchema,
|
||||||
|
[PermissionTypes.TEMPORARY_CHAT]: temporaryChatPermissionsSchema,
|
||||||
|
[PermissionTypes.RUN_CODE]: runCodePermissionsSchema,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -123,6 +151,8 @@ export const roleDefaults = defaultRolesSchema.parse({
|
||||||
[PermissionTypes.BOOKMARKS]: {},
|
[PermissionTypes.BOOKMARKS]: {},
|
||||||
[PermissionTypes.AGENTS]: {},
|
[PermissionTypes.AGENTS]: {},
|
||||||
[PermissionTypes.MULTI_CONVO]: {},
|
[PermissionTypes.MULTI_CONVO]: {},
|
||||||
|
[PermissionTypes.TEMPORARY_CHAT]: {},
|
||||||
|
[PermissionTypes.RUN_CODE]: {},
|
||||||
},
|
},
|
||||||
[SystemRoles.USER]: {
|
[SystemRoles.USER]: {
|
||||||
name: SystemRoles.USER,
|
name: SystemRoles.USER,
|
||||||
|
@ -130,5 +160,7 @@ export const roleDefaults = defaultRolesSchema.parse({
|
||||||
[PermissionTypes.BOOKMARKS]: {},
|
[PermissionTypes.BOOKMARKS]: {},
|
||||||
[PermissionTypes.AGENTS]: {},
|
[PermissionTypes.AGENTS]: {},
|
||||||
[PermissionTypes.MULTI_CONVO]: {},
|
[PermissionTypes.MULTI_CONVO]: {},
|
||||||
|
[PermissionTypes.TEMPORARY_CHAT]: {},
|
||||||
|
[PermissionTypes.RUN_CODE]: {},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue