🔼 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:
Danny Avila 2025-02-23 14:01:36 -05:00 committed by GitHub
parent 2a74ceb630
commit 0e719592c6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 198 additions and 34 deletions

View file

@ -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 =

View file

@ -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,
}; };
/** /**

View file

@ -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);

View file

@ -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],

View file

@ -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,
};

View file

@ -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,
}; };

View file

@ -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;

View file

@ -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 },
}); });
}); });
}); });

View file

@ -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

View file

@ -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
View file

@ -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",

View file

@ -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",

View file

@ -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>;

View file

@ -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]: {},
}, },
}); });