mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 00:40:14 +01:00
🧑💻 fix: Agents Config Defaults and Avatar Uploads Across File Strategies (#7814)
* fix: avatar processing across storage services, uniqueness by agent ID, prevent overwriting user avatar * fix: sanitize file paths in deleteLocalFile function to prevent invalid path errors * fix: correct spelling of 'agentsEndpointSchema' in agents.js and config.ts * fix: default app.locals agents configuration setup and add agent endpoint schema default
This commit is contained in:
parent
118ad943c9
commit
a57224c1d5
13 changed files with 192 additions and 55 deletions
|
|
@ -387,6 +387,7 @@ const uploadAgentAvatarHandler = async (req, res) => {
|
||||||
buffer: resizedBuffer,
|
buffer: resizedBuffer,
|
||||||
userId: req.user.id,
|
userId: req.user.id,
|
||||||
manual: 'false',
|
manual: 'false',
|
||||||
|
agentId: agent_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const image = {
|
const image = {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ const {
|
||||||
getConfigDefaults,
|
getConfigDefaults,
|
||||||
loadWebSearchConfig,
|
loadWebSearchConfig,
|
||||||
} = require('librechat-data-provider');
|
} = require('librechat-data-provider');
|
||||||
|
const { agentsConfigSetup } = require('@librechat/api');
|
||||||
const {
|
const {
|
||||||
checkHealth,
|
checkHealth,
|
||||||
checkConfig,
|
checkConfig,
|
||||||
|
|
@ -25,7 +26,6 @@ const { azureConfigSetup } = require('./start/azureOpenAI');
|
||||||
const { processModelSpecs } = require('./start/modelSpecs');
|
const { processModelSpecs } = require('./start/modelSpecs');
|
||||||
const { initializeS3 } = require('./Files/S3/initialize');
|
const { initializeS3 } = require('./Files/S3/initialize');
|
||||||
const { loadAndFormatTools } = require('./ToolService');
|
const { loadAndFormatTools } = require('./ToolService');
|
||||||
const { agentsConfigSetup } = require('./start/agents');
|
|
||||||
const { isEnabled } = require('~/server/utils');
|
const { isEnabled } = require('~/server/utils');
|
||||||
const { initializeRoles } = require('~/models');
|
const { initializeRoles } = require('~/models');
|
||||||
const { getMCPManager } = require('~/config');
|
const { getMCPManager } = require('~/config');
|
||||||
|
|
@ -103,8 +103,13 @@ const AppService = async (app) => {
|
||||||
balance,
|
balance,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const agentsDefaults = agentsConfigSetup(config);
|
||||||
|
|
||||||
if (!Object.keys(config).length) {
|
if (!Object.keys(config).length) {
|
||||||
app.locals = defaultLocals;
|
app.locals = {
|
||||||
|
...defaultLocals,
|
||||||
|
[EModelEndpoint.agents]: agentsDefaults,
|
||||||
|
};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,9 +144,7 @@ const AppService = async (app) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (endpoints?.[EModelEndpoint.agents]) {
|
endpointLocals[EModelEndpoint.agents] = agentsConfigSetup(config, agentsDefaults);
|
||||||
endpointLocals[EModelEndpoint.agents] = agentsConfigSetup(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
const endpointKeys = [
|
const endpointKeys = [
|
||||||
EModelEndpoint.openAI,
|
EModelEndpoint.openAI,
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,10 @@ const {
|
||||||
FileSources,
|
FileSources,
|
||||||
EModelEndpoint,
|
EModelEndpoint,
|
||||||
EImageOutputType,
|
EImageOutputType,
|
||||||
|
AgentCapabilities,
|
||||||
defaultSocialLogins,
|
defaultSocialLogins,
|
||||||
validateAzureGroups,
|
validateAzureGroups,
|
||||||
|
defaultAgentCapabilities,
|
||||||
deprecatedAzureVariables,
|
deprecatedAzureVariables,
|
||||||
conflictingAzureVariables,
|
conflictingAzureVariables,
|
||||||
} = require('librechat-data-provider');
|
} = require('librechat-data-provider');
|
||||||
|
|
@ -151,6 +153,11 @@ describe('AppService', () => {
|
||||||
safeSearch: 1,
|
safeSearch: 1,
|
||||||
serperApiKey: '${SERPER_API_KEY}',
|
serperApiKey: '${SERPER_API_KEY}',
|
||||||
},
|
},
|
||||||
|
memory: undefined,
|
||||||
|
agents: {
|
||||||
|
disableBuilder: false,
|
||||||
|
capabilities: expect.arrayContaining([...defaultAgentCapabilities]),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -268,6 +275,71 @@ describe('AppService', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should correctly configure Agents endpoint based on custom config', async () => {
|
||||||
|
require('./Config/loadCustomConfig').mockImplementationOnce(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
endpoints: {
|
||||||
|
[EModelEndpoint.agents]: {
|
||||||
|
disableBuilder: true,
|
||||||
|
recursionLimit: 10,
|
||||||
|
maxRecursionLimit: 20,
|
||||||
|
allowedProviders: ['openai', 'anthropic'],
|
||||||
|
capabilities: [AgentCapabilities.tools, AgentCapabilities.actions],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await AppService(app);
|
||||||
|
|
||||||
|
expect(app.locals).toHaveProperty(EModelEndpoint.agents);
|
||||||
|
expect(app.locals[EModelEndpoint.agents]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
disableBuilder: true,
|
||||||
|
recursionLimit: 10,
|
||||||
|
maxRecursionLimit: 20,
|
||||||
|
allowedProviders: expect.arrayContaining(['openai', 'anthropic']),
|
||||||
|
capabilities: expect.arrayContaining([AgentCapabilities.tools, AgentCapabilities.actions]),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should configure Agents endpoint with defaults when no config is provided', async () => {
|
||||||
|
require('./Config/loadCustomConfig').mockImplementationOnce(() => Promise.resolve({}));
|
||||||
|
|
||||||
|
await AppService(app);
|
||||||
|
|
||||||
|
expect(app.locals).toHaveProperty(EModelEndpoint.agents);
|
||||||
|
expect(app.locals[EModelEndpoint.agents]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
disableBuilder: false,
|
||||||
|
capabilities: expect.arrayContaining([...defaultAgentCapabilities]),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should configure Agents endpoint with defaults when endpoints exist but agents is not defined', async () => {
|
||||||
|
require('./Config/loadCustomConfig').mockImplementationOnce(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
endpoints: {
|
||||||
|
[EModelEndpoint.openAI]: {
|
||||||
|
titleConvo: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await AppService(app);
|
||||||
|
|
||||||
|
expect(app.locals).toHaveProperty(EModelEndpoint.agents);
|
||||||
|
expect(app.locals[EModelEndpoint.agents]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
disableBuilder: false,
|
||||||
|
capabilities: expect.arrayContaining([...defaultAgentCapabilities]),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('should correctly configure minimum Azure OpenAI Assistant values', async () => {
|
it('should correctly configure minimum Azure OpenAI Assistant values', async () => {
|
||||||
const assistantGroups = [azureGroups[0], { ...azureGroups[1], assistants: true }];
|
const assistantGroups = [azureGroups[0], { ...azureGroups[1], assistants: true }];
|
||||||
require('./Config/loadCustomConfig').mockImplementationOnce(() =>
|
require('./Config/loadCustomConfig').mockImplementationOnce(() =>
|
||||||
|
|
|
||||||
|
|
@ -91,15 +91,28 @@ async function prepareAzureImageURL(req, file) {
|
||||||
* @param {Buffer} params.buffer - The avatar image buffer.
|
* @param {Buffer} params.buffer - The avatar image buffer.
|
||||||
* @param {string} params.userId - The user's id.
|
* @param {string} params.userId - The user's id.
|
||||||
* @param {string} params.manual - Flag to indicate manual update.
|
* @param {string} params.manual - Flag to indicate manual update.
|
||||||
|
* @param {string} [params.agentId] - Optional agent ID if this is an agent avatar.
|
||||||
* @param {string} [params.basePath='images'] - The base folder within the container.
|
* @param {string} [params.basePath='images'] - The base folder within the container.
|
||||||
* @param {string} [params.containerName] - The Azure Blob container name.
|
* @param {string} [params.containerName] - The Azure Blob container name.
|
||||||
* @returns {Promise<string>} The URL of the avatar.
|
* @returns {Promise<string>} The URL of the avatar.
|
||||||
*/
|
*/
|
||||||
async function processAzureAvatar({ buffer, userId, manual, basePath = 'images', containerName }) {
|
async function processAzureAvatar({
|
||||||
|
buffer,
|
||||||
|
userId,
|
||||||
|
manual,
|
||||||
|
agentId,
|
||||||
|
basePath = 'images',
|
||||||
|
containerName,
|
||||||
|
}) {
|
||||||
try {
|
try {
|
||||||
const metadata = await sharp(buffer).metadata();
|
const metadata = await sharp(buffer).metadata();
|
||||||
const extension = metadata.format === 'gif' ? 'gif' : 'png';
|
const extension = metadata.format === 'gif' ? 'gif' : 'png';
|
||||||
const fileName = `avatar.${extension}`;
|
const timestamp = new Date().getTime();
|
||||||
|
|
||||||
|
/** Unique filename with timestamp and optional agent ID */
|
||||||
|
const fileName = agentId
|
||||||
|
? `agent-${agentId}-avatar-${timestamp}.${extension}`
|
||||||
|
: `avatar-${timestamp}.${extension}`;
|
||||||
|
|
||||||
const downloadURL = await saveBufferToAzure({
|
const downloadURL = await saveBufferToAzure({
|
||||||
userId,
|
userId,
|
||||||
|
|
@ -110,9 +123,12 @@ async function processAzureAvatar({ buffer, userId, manual, basePath = 'images',
|
||||||
});
|
});
|
||||||
const isManual = manual === 'true';
|
const isManual = manual === 'true';
|
||||||
const url = `${downloadURL}?manual=${isManual}`;
|
const url = `${downloadURL}?manual=${isManual}`;
|
||||||
if (isManual) {
|
|
||||||
|
// Only update user record if this is a user avatar (manual === 'true')
|
||||||
|
if (isManual && !agentId) {
|
||||||
await updateUser(userId, { avatar: url });
|
await updateUser(userId, { avatar: url });
|
||||||
}
|
}
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[processAzureAvatar] Error uploading profile picture to Azure:', error);
|
logger.error('[processAzureAvatar] Error uploading profile picture to Azure:', error);
|
||||||
|
|
|
||||||
|
|
@ -82,14 +82,20 @@ async function prepareImageURL(req, file) {
|
||||||
* @param {Buffer} params.buffer - The Buffer containing the avatar image.
|
* @param {Buffer} params.buffer - The Buffer containing the avatar image.
|
||||||
* @param {string} params.userId - The user ID.
|
* @param {string} params.userId - The user ID.
|
||||||
* @param {string} params.manual - A string flag indicating whether the update is manual ('true' or 'false').
|
* @param {string} params.manual - A string flag indicating whether the update is manual ('true' or 'false').
|
||||||
|
* @param {string} [params.agentId] - Optional agent ID if this is an agent avatar.
|
||||||
* @returns {Promise<string>} - A promise that resolves with the URL of the uploaded avatar.
|
* @returns {Promise<string>} - A promise that resolves with the URL of the uploaded avatar.
|
||||||
* @throws {Error} - Throws an error if Firebase is not initialized or if there is an error in uploading.
|
* @throws {Error} - Throws an error if Firebase is not initialized or if there is an error in uploading.
|
||||||
*/
|
*/
|
||||||
async function processFirebaseAvatar({ buffer, userId, manual }) {
|
async function processFirebaseAvatar({ buffer, userId, manual, agentId }) {
|
||||||
try {
|
try {
|
||||||
const metadata = await sharp(buffer).metadata();
|
const metadata = await sharp(buffer).metadata();
|
||||||
const extension = metadata.format === 'gif' ? 'gif' : 'png';
|
const extension = metadata.format === 'gif' ? 'gif' : 'png';
|
||||||
const fileName = `avatar.${extension}`;
|
const timestamp = new Date().getTime();
|
||||||
|
|
||||||
|
/** Unique filename with timestamp and optional agent ID */
|
||||||
|
const fileName = agentId
|
||||||
|
? `agent-${agentId}-avatar-${timestamp}.${extension}`
|
||||||
|
: `avatar-${timestamp}.${extension}`;
|
||||||
|
|
||||||
const downloadURL = await saveBufferToFirebase({
|
const downloadURL = await saveBufferToFirebase({
|
||||||
userId,
|
userId,
|
||||||
|
|
@ -98,10 +104,10 @@ async function processFirebaseAvatar({ buffer, userId, manual }) {
|
||||||
});
|
});
|
||||||
|
|
||||||
const isManual = manual === 'true';
|
const isManual = manual === 'true';
|
||||||
|
|
||||||
const url = `${downloadURL}?manual=${isManual}`;
|
const url = `${downloadURL}?manual=${isManual}`;
|
||||||
|
|
||||||
if (isManual) {
|
// Only update user record if this is a user avatar (manual === 'true')
|
||||||
|
if (isManual && !agentId) {
|
||||||
await updateUser(userId, { avatar: url });
|
await updateUser(userId, { avatar: url });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -201,6 +201,10 @@ const unlinkFile = async (filepath) => {
|
||||||
*/
|
*/
|
||||||
const deleteLocalFile = async (req, file) => {
|
const deleteLocalFile = async (req, file) => {
|
||||||
const { publicPath, uploads } = req.app.locals.paths;
|
const { publicPath, uploads } = req.app.locals.paths;
|
||||||
|
|
||||||
|
/** Filepath stripped of query parameters (e.g., ?manual=true) */
|
||||||
|
const cleanFilepath = file.filepath.split('?')[0];
|
||||||
|
|
||||||
if (file.embedded && process.env.RAG_API_URL) {
|
if (file.embedded && process.env.RAG_API_URL) {
|
||||||
const jwtToken = req.headers.authorization.split(' ')[1];
|
const jwtToken = req.headers.authorization.split(' ')[1];
|
||||||
axios.delete(`${process.env.RAG_API_URL}/documents`, {
|
axios.delete(`${process.env.RAG_API_URL}/documents`, {
|
||||||
|
|
@ -213,32 +217,32 @@ const deleteLocalFile = async (req, file) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file.filepath.startsWith(`/uploads/${req.user.id}`)) {
|
if (cleanFilepath.startsWith(`/uploads/${req.user.id}`)) {
|
||||||
const userUploadDir = path.join(uploads, req.user.id);
|
const userUploadDir = path.join(uploads, req.user.id);
|
||||||
const basePath = file.filepath.split(`/uploads/${req.user.id}/`)[1];
|
const basePath = cleanFilepath.split(`/uploads/${req.user.id}/`)[1];
|
||||||
|
|
||||||
if (!basePath) {
|
if (!basePath) {
|
||||||
throw new Error(`Invalid file path: ${file.filepath}`);
|
throw new Error(`Invalid file path: ${cleanFilepath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const filepath = path.join(userUploadDir, basePath);
|
const filepath = path.join(userUploadDir, basePath);
|
||||||
|
|
||||||
const rel = path.relative(userUploadDir, filepath);
|
const rel = path.relative(userUploadDir, filepath);
|
||||||
if (rel.startsWith('..') || path.isAbsolute(rel) || rel.includes(`..${path.sep}`)) {
|
if (rel.startsWith('..') || path.isAbsolute(rel) || rel.includes(`..${path.sep}`)) {
|
||||||
throw new Error(`Invalid file path: ${file.filepath}`);
|
throw new Error(`Invalid file path: ${cleanFilepath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
await unlinkFile(filepath);
|
await unlinkFile(filepath);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parts = file.filepath.split(path.sep);
|
const parts = cleanFilepath.split(path.sep);
|
||||||
const subfolder = parts[1];
|
const subfolder = parts[1];
|
||||||
if (!subfolder && parts[0] === EModelEndpoint.agents) {
|
if (!subfolder && parts[0] === EModelEndpoint.agents) {
|
||||||
logger.warn(`Agent File ${file.file_id} is missing filepath, may have been deleted already`);
|
logger.warn(`Agent File ${file.file_id} is missing filepath, may have been deleted already`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const filepath = path.join(publicPath, file.filepath);
|
const filepath = path.join(publicPath, cleanFilepath);
|
||||||
|
|
||||||
if (!isValidPath(req, publicPath, subfolder, filepath)) {
|
if (!isValidPath(req, publicPath, subfolder, filepath)) {
|
||||||
throw new Error('Invalid file path');
|
throw new Error('Invalid file path');
|
||||||
|
|
|
||||||
|
|
@ -112,10 +112,11 @@ async function prepareImagesLocal(req, file) {
|
||||||
* @param {Buffer} params.buffer - The Buffer containing the avatar image.
|
* @param {Buffer} params.buffer - The Buffer containing the avatar image.
|
||||||
* @param {string} params.userId - The user ID.
|
* @param {string} params.userId - The user ID.
|
||||||
* @param {string} params.manual - A string flag indicating whether the update is manual ('true' or 'false').
|
* @param {string} params.manual - A string flag indicating whether the update is manual ('true' or 'false').
|
||||||
|
* @param {string} [params.agentId] - Optional agent ID if this is an agent avatar.
|
||||||
* @returns {Promise<string>} - A promise that resolves with the URL of the uploaded avatar.
|
* @returns {Promise<string>} - A promise that resolves with the URL of the uploaded avatar.
|
||||||
* @throws {Error} - Throws an error if Firebase is not initialized or if there is an error in uploading.
|
* @throws {Error} - Throws an error if Firebase is not initialized or if there is an error in uploading.
|
||||||
*/
|
*/
|
||||||
async function processLocalAvatar({ buffer, userId, manual }) {
|
async function processLocalAvatar({ buffer, userId, manual, agentId }) {
|
||||||
const userDir = path.resolve(
|
const userDir = path.resolve(
|
||||||
__dirname,
|
__dirname,
|
||||||
'..',
|
'..',
|
||||||
|
|
@ -132,7 +133,11 @@ async function processLocalAvatar({ buffer, userId, manual }) {
|
||||||
const metadata = await sharp(buffer).metadata();
|
const metadata = await sharp(buffer).metadata();
|
||||||
const extension = metadata.format === 'gif' ? 'gif' : 'png';
|
const extension = metadata.format === 'gif' ? 'gif' : 'png';
|
||||||
|
|
||||||
const fileName = `avatar-${new Date().getTime()}.${extension}`;
|
const timestamp = new Date().getTime();
|
||||||
|
/** Unique filename with timestamp and optional agent ID */
|
||||||
|
const fileName = agentId
|
||||||
|
? `agent-${agentId}-avatar-${timestamp}.${extension}`
|
||||||
|
: `avatar-${timestamp}.${extension}`;
|
||||||
const urlRoute = `/images/${userId}/${fileName}`;
|
const urlRoute = `/images/${userId}/${fileName}`;
|
||||||
const avatarPath = path.join(userDir, fileName);
|
const avatarPath = path.join(userDir, fileName);
|
||||||
|
|
||||||
|
|
@ -142,7 +147,8 @@ async function processLocalAvatar({ buffer, userId, manual }) {
|
||||||
const isManual = manual === 'true';
|
const isManual = manual === 'true';
|
||||||
let url = `${urlRoute}?manual=${isManual}`;
|
let url = `${urlRoute}?manual=${isManual}`;
|
||||||
|
|
||||||
if (isManual) {
|
// Only update user record if this is a user avatar (manual === 'true')
|
||||||
|
if (isManual && !agentId) {
|
||||||
await updateUser(userId, { avatar: url });
|
await updateUser(userId, { avatar: url });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -94,19 +94,28 @@ async function prepareImageURLS3(req, file) {
|
||||||
* @param {Buffer} params.buffer - Avatar image buffer.
|
* @param {Buffer} params.buffer - Avatar image buffer.
|
||||||
* @param {string} params.userId - User's unique identifier.
|
* @param {string} params.userId - User's unique identifier.
|
||||||
* @param {string} params.manual - 'true' or 'false' flag for manual update.
|
* @param {string} params.manual - 'true' or 'false' flag for manual update.
|
||||||
|
* @param {string} [params.agentId] - Optional agent ID if this is an agent avatar.
|
||||||
* @param {string} [params.basePath='images'] - Base path in the bucket.
|
* @param {string} [params.basePath='images'] - Base path in the bucket.
|
||||||
* @returns {Promise<string>} Signed URL of the uploaded avatar.
|
* @returns {Promise<string>} Signed URL of the uploaded avatar.
|
||||||
*/
|
*/
|
||||||
async function processS3Avatar({ buffer, userId, manual, basePath = defaultBasePath }) {
|
async function processS3Avatar({ buffer, userId, manual, agentId, basePath = defaultBasePath }) {
|
||||||
try {
|
try {
|
||||||
const metadata = await sharp(buffer).metadata();
|
const metadata = await sharp(buffer).metadata();
|
||||||
const extension = metadata.format === 'gif' ? 'gif' : 'png';
|
const extension = metadata.format === 'gif' ? 'gif' : 'png';
|
||||||
const fileName = `avatar.${extension}`;
|
const timestamp = new Date().getTime();
|
||||||
|
|
||||||
|
/** Unique filename with timestamp and optional agent ID */
|
||||||
|
const fileName = agentId
|
||||||
|
? `agent-${agentId}-avatar-${timestamp}.${extension}`
|
||||||
|
: `avatar-${timestamp}.${extension}`;
|
||||||
|
|
||||||
const downloadURL = await saveBufferToS3({ userId, buffer, fileName, basePath });
|
const downloadURL = await saveBufferToS3({ userId, buffer, fileName, basePath });
|
||||||
if (manual === 'true') {
|
|
||||||
|
// Only update user record if this is a user avatar (manual === 'true')
|
||||||
|
if (manual === 'true' && !agentId) {
|
||||||
await updateUser(userId, { avatar: downloadURL });
|
await updateUser(userId, { avatar: downloadURL });
|
||||||
}
|
}
|
||||||
|
|
||||||
return downloadURL;
|
return downloadURL;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[processS3Avatar] Error processing S3 avatar:', error.message);
|
logger.error('[processS3Avatar] Error processing S3 avatar:', error.message);
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
const { EModelEndpoint, agentsEndpointSChema } = require('librechat-data-provider');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the Agents configuration from the config (`librechat.yaml`) file.
|
|
||||||
* @param {TCustomConfig} config - The loaded custom configuration.
|
|
||||||
* @returns {Partial<TAgentsEndpoint>} The Agents endpoint configuration.
|
|
||||||
*/
|
|
||||||
function agentsConfigSetup(config) {
|
|
||||||
const agentsConfig = config.endpoints[EModelEndpoint.agents];
|
|
||||||
const parsedConfig = agentsEndpointSChema.parse(agentsConfig);
|
|
||||||
return parsedConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { agentsConfigSetup };
|
|
||||||
|
|
@ -31,7 +31,7 @@ const handleExistingUser = async (oldUser, avatarUrl) => {
|
||||||
input: avatarUrl,
|
input: avatarUrl,
|
||||||
});
|
});
|
||||||
const { processAvatar } = getStrategyFunctions(fileStrategy);
|
const { processAvatar } = getStrategyFunctions(fileStrategy);
|
||||||
updatedAvatar = await processAvatar({ buffer: resizedBuffer, userId });
|
updatedAvatar = await processAvatar({ buffer: resizedBuffer, userId, manual: 'false' });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updatedAvatar) {
|
if (updatedAvatar) {
|
||||||
|
|
@ -90,7 +90,11 @@ const createSocialUser = async ({
|
||||||
input: avatarUrl,
|
input: avatarUrl,
|
||||||
});
|
});
|
||||||
const { processAvatar } = getStrategyFunctions(fileStrategy);
|
const { processAvatar } = getStrategyFunctions(fileStrategy);
|
||||||
const avatar = await processAvatar({ buffer: resizedBuffer, userId: newUserId });
|
const avatar = await processAvatar({
|
||||||
|
buffer: resizedBuffer,
|
||||||
|
userId: newUserId,
|
||||||
|
manual: 'false',
|
||||||
|
});
|
||||||
await updateUser(newUserId, { avatar });
|
await updateUser(newUserId, { avatar });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
24
packages/api/src/agents/config.ts
Normal file
24
packages/api/src/agents/config.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { EModelEndpoint, agentsEndpointSchema } from 'librechat-data-provider';
|
||||||
|
import type { TCustomConfig, TAgentsEndpoint } from 'librechat-data-provider';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the Agents configuration from the config (`librechat.yaml`) file.
|
||||||
|
* If no agents config is defined, uses the provided defaults or parses empty object.
|
||||||
|
*
|
||||||
|
* @param config - The loaded custom configuration.
|
||||||
|
* @param [defaultConfig] - Default configuration from getConfigDefaults.
|
||||||
|
* @returns The Agents endpoint configuration.
|
||||||
|
*/
|
||||||
|
export function agentsConfigSetup(
|
||||||
|
config: TCustomConfig,
|
||||||
|
defaultConfig: Partial<TAgentsEndpoint>,
|
||||||
|
): Partial<TAgentsEndpoint> {
|
||||||
|
const agentsConfig = config?.endpoints?.[EModelEndpoint.agents];
|
||||||
|
|
||||||
|
if (!agentsConfig) {
|
||||||
|
return defaultConfig || agentsEndpointSchema.parse({});
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedConfig = agentsEndpointSchema.parse(agentsConfig);
|
||||||
|
return parsedConfig;
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
export * from './config';
|
||||||
export * from './memory';
|
export * from './memory';
|
||||||
export * from './resources';
|
export * from './resources';
|
||||||
export * from './run';
|
export * from './run';
|
||||||
|
|
|
||||||
|
|
@ -244,21 +244,26 @@ export const defaultAgentCapabilities = [
|
||||||
AgentCapabilities.ocr,
|
AgentCapabilities.ocr,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const agentsEndpointSChema = baseEndpointSchema.merge(
|
export const agentsEndpointSchema = baseEndpointSchema
|
||||||
z.object({
|
.merge(
|
||||||
/* agents specific */
|
z.object({
|
||||||
recursionLimit: z.number().optional(),
|
/* agents specific */
|
||||||
disableBuilder: z.boolean().optional(),
|
recursionLimit: z.number().optional(),
|
||||||
maxRecursionLimit: z.number().optional(),
|
disableBuilder: z.boolean().optional().default(false),
|
||||||
allowedProviders: z.array(z.union([z.string(), eModelEndpointSchema])).optional(),
|
maxRecursionLimit: z.number().optional(),
|
||||||
capabilities: z
|
allowedProviders: z.array(z.union([z.string(), eModelEndpointSchema])).optional(),
|
||||||
.array(z.nativeEnum(AgentCapabilities))
|
capabilities: z
|
||||||
.optional()
|
.array(z.nativeEnum(AgentCapabilities))
|
||||||
.default(defaultAgentCapabilities),
|
.optional()
|
||||||
}),
|
.default(defaultAgentCapabilities),
|
||||||
);
|
}),
|
||||||
|
)
|
||||||
|
.default({
|
||||||
|
disableBuilder: false,
|
||||||
|
capabilities: defaultAgentCapabilities,
|
||||||
|
});
|
||||||
|
|
||||||
export type TAgentsEndpoint = z.infer<typeof agentsEndpointSChema>;
|
export type TAgentsEndpoint = z.infer<typeof agentsEndpointSchema>;
|
||||||
|
|
||||||
export const endpointSchema = baseEndpointSchema.merge(
|
export const endpointSchema = baseEndpointSchema.merge(
|
||||||
z.object({
|
z.object({
|
||||||
|
|
@ -720,7 +725,7 @@ export const configSchema = z.object({
|
||||||
[EModelEndpoint.azureOpenAI]: azureEndpointSchema.optional(),
|
[EModelEndpoint.azureOpenAI]: azureEndpointSchema.optional(),
|
||||||
[EModelEndpoint.azureAssistants]: assistantEndpointSchema.optional(),
|
[EModelEndpoint.azureAssistants]: assistantEndpointSchema.optional(),
|
||||||
[EModelEndpoint.assistants]: assistantEndpointSchema.optional(),
|
[EModelEndpoint.assistants]: assistantEndpointSchema.optional(),
|
||||||
[EModelEndpoint.agents]: agentsEndpointSChema.optional(),
|
[EModelEndpoint.agents]: agentsEndpointSchema.optional(),
|
||||||
[EModelEndpoint.custom]: z.array(endpointSchema.partial()).optional(),
|
[EModelEndpoint.custom]: z.array(endpointSchema.partial()).optional(),
|
||||||
[EModelEndpoint.bedrock]: baseEndpointSchema.optional(),
|
[EModelEndpoint.bedrock]: baseEndpointSchema.optional(),
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue