mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-21 21:50:49 +02:00

* 🔧 feat: Enhance logging configuration for production and debug environments * 🔒 feat: Implement encryption and decryption functions for sensitive values in ActionService with URL encoding/decoding * refactor: optimize action service for agent tools * refactor: optimize action processing for Assistants API * fix: handle case where agent is not found in loadAgent function * refactor: improve error handling in API calls by throwing new Error with logAxiosError output * chore: bump @librechat/agents to 2.3.95, fixes "Invalid tool call structure: No preceding AIMessage with tool_call_ids" * refactor: enhance error logging in logAxiosError function to include response status * refactor: remove unused useModelSelection hook from Endpoint * refactor: add support for assistants in useSelectorEffects hook * refactor: replace string easing with imported easings in Landing component * chore: remove duplicate translation * refactor: update model selection logic and improve localization for UI elements * refactor: replace endpoint value checks with helper functions for agents and assistants * refactor: optimize display value logic and utilize useMemo for performance improvements * refactor: clean up imports and optimize display/icon value logic in endpoint components, fix spec selection * refactor: enhance error logging in axios utility to include stack traces for better debugging * refactor: update logging configuration to use DEBUG_LOGGING and streamline log level handling * refactor: adjust className for export menu button to improve layout consistency and remove unused title prop from ShareButton * refactor: update import path for logAxiosError utility to improve module organization and clarity * refactor: implement debounced search value setter in ModelSelectorContext for improved performance
318 lines
9.8 KiB
JavaScript
318 lines
9.8 KiB
JavaScript
const mongoose = require('mongoose');
|
|
const { SystemRoles } = require('librechat-data-provider');
|
|
const { GLOBAL_PROJECT_NAME } = require('librechat-data-provider').Constants;
|
|
const { CONFIG_STORE, STARTUP_CONFIG } = require('librechat-data-provider').CacheKeys;
|
|
const {
|
|
getProjectByName,
|
|
addAgentIdsToProject,
|
|
removeAgentIdsFromProject,
|
|
removeAgentFromAllProjects,
|
|
} = require('./Project');
|
|
const getLogStores = require('~/cache/getLogStores');
|
|
const { agentSchema } = require('@librechat/data-schemas');
|
|
|
|
const Agent = mongoose.model('agent', agentSchema);
|
|
|
|
/**
|
|
* Create an agent with the provided data.
|
|
* @param {Object} agentData - The agent data to create.
|
|
* @returns {Promise<Agent>} The created agent document as a plain object.
|
|
* @throws {Error} If the agent creation fails.
|
|
*/
|
|
const createAgent = async (agentData) => {
|
|
return (await Agent.create(agentData)).toObject();
|
|
};
|
|
|
|
/**
|
|
* Get an agent document based on the provided ID.
|
|
*
|
|
* @param {Object} searchParameter - The search parameters to find the agent to update.
|
|
* @param {string} searchParameter.id - The ID of the agent to update.
|
|
* @param {string} searchParameter.author - The user ID of the agent's author.
|
|
* @returns {Promise<Agent|null>} The agent document as a plain object, or null if not found.
|
|
*/
|
|
const getAgent = async (searchParameter) => await Agent.findOne(searchParameter).lean();
|
|
|
|
/**
|
|
* Load an agent based on the provided ID
|
|
*
|
|
* @param {Object} params
|
|
* @param {ServerRequest} params.req
|
|
* @param {string} params.agent_id
|
|
* @returns {Promise<Agent|null>} The agent document as a plain object, or null if not found.
|
|
*/
|
|
const loadAgent = async ({ req, agent_id }) => {
|
|
const agent = await getAgent({
|
|
id: agent_id,
|
|
});
|
|
|
|
if (!agent) {
|
|
return null;
|
|
}
|
|
|
|
if (agent.author.toString() === req.user.id) {
|
|
return agent;
|
|
}
|
|
|
|
if (!agent.projectIds) {
|
|
return null;
|
|
}
|
|
|
|
const cache = getLogStores(CONFIG_STORE);
|
|
/** @type {TStartupConfig} */
|
|
const cachedStartupConfig = await cache.get(STARTUP_CONFIG);
|
|
let { instanceProjectId } = cachedStartupConfig ?? {};
|
|
if (!instanceProjectId) {
|
|
instanceProjectId = (await getProjectByName(GLOBAL_PROJECT_NAME, '_id'))._id.toString();
|
|
}
|
|
|
|
for (const projectObjectId of agent.projectIds) {
|
|
const projectId = projectObjectId.toString();
|
|
if (projectId === instanceProjectId) {
|
|
return agent;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Update an agent with new data without overwriting existing
|
|
* properties, or create a new agent if it doesn't exist.
|
|
*
|
|
* @param {Object} searchParameter - The search parameters to find the agent to update.
|
|
* @param {string} searchParameter.id - The ID of the agent to update.
|
|
* @param {string} [searchParameter.author] - The user ID of the agent's author.
|
|
* @param {Object} updateData - An object containing the properties to update.
|
|
* @returns {Promise<Agent>} The updated or newly created agent document as a plain object.
|
|
*/
|
|
const updateAgent = async (searchParameter, updateData) => {
|
|
const options = { new: true, upsert: false };
|
|
return Agent.findOneAndUpdate(searchParameter, updateData, options).lean();
|
|
};
|
|
|
|
/**
|
|
* Modifies an agent with the resource file id.
|
|
* @param {object} params
|
|
* @param {ServerRequest} params.req
|
|
* @param {string} params.agent_id
|
|
* @param {string} params.tool_resource
|
|
* @param {string} params.file_id
|
|
* @returns {Promise<Agent>} The updated agent.
|
|
*/
|
|
const addAgentResourceFile = async ({ agent_id, tool_resource, file_id }) => {
|
|
const searchParameter = { id: agent_id };
|
|
|
|
const fileIdsPath = `tool_resources.${tool_resource}.file_ids`;
|
|
|
|
await Agent.updateOne(
|
|
{
|
|
id: agent_id,
|
|
[`${fileIdsPath}`]: { $exists: false },
|
|
},
|
|
{
|
|
$set: {
|
|
[`${fileIdsPath}`]: [],
|
|
},
|
|
},
|
|
);
|
|
|
|
const updateData = { $addToSet: { [fileIdsPath]: file_id } };
|
|
|
|
const updatedAgent = await updateAgent(searchParameter, updateData);
|
|
if (updatedAgent) {
|
|
return updatedAgent;
|
|
} else {
|
|
throw new Error('Agent not found for adding resource file');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Removes multiple resource files from an agent in a single update.
|
|
* @param {object} params
|
|
* @param {string} params.agent_id
|
|
* @param {Array<{tool_resource: string, file_id: string}>} params.files
|
|
* @returns {Promise<Agent>} The updated agent.
|
|
*/
|
|
const removeAgentResourceFiles = async ({ agent_id, files }) => {
|
|
const searchParameter = { id: agent_id };
|
|
|
|
// associate each tool resource with the respective file ids array
|
|
const filesByResource = files.reduce((acc, { tool_resource, file_id }) => {
|
|
if (!acc[tool_resource]) {
|
|
acc[tool_resource] = [];
|
|
}
|
|
acc[tool_resource].push(file_id);
|
|
return acc;
|
|
}, {});
|
|
|
|
// build the update aggregation pipeline wich removes file ids from tool resources array
|
|
// and eventually deletes empty tool resources
|
|
const updateData = [];
|
|
Object.entries(filesByResource).forEach(([resource, fileIds]) => {
|
|
const toolResourcePath = `tool_resources.${resource}`;
|
|
const fileIdsPath = `${toolResourcePath}.file_ids`;
|
|
|
|
// file ids removal stage
|
|
updateData.push({
|
|
$set: {
|
|
[fileIdsPath]: {
|
|
$filter: {
|
|
input: `$${fileIdsPath}`,
|
|
cond: { $not: [{ $in: ['$$this', fileIds] }] },
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
// empty tool resource deletion stage
|
|
updateData.push({
|
|
$set: {
|
|
[toolResourcePath]: {
|
|
$cond: [{ $eq: [`$${fileIdsPath}`, []] }, '$$REMOVE', `$${toolResourcePath}`],
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
// return the updated agent or throw if no agent matches
|
|
const updatedAgent = await updateAgent(searchParameter, updateData);
|
|
if (updatedAgent) {
|
|
return updatedAgent;
|
|
} else {
|
|
throw new Error('Agent not found for removing resource files');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Deletes an agent based on the provided ID.
|
|
*
|
|
* @param {Object} searchParameter - The search parameters to find the agent to delete.
|
|
* @param {string} searchParameter.id - The ID of the agent to delete.
|
|
* @param {string} [searchParameter.author] - The user ID of the agent's author.
|
|
* @returns {Promise<void>} Resolves when the agent has been successfully deleted.
|
|
*/
|
|
const deleteAgent = async (searchParameter) => {
|
|
const agent = await Agent.findOneAndDelete(searchParameter);
|
|
if (agent) {
|
|
await removeAgentFromAllProjects(agent.id);
|
|
}
|
|
return agent;
|
|
};
|
|
|
|
/**
|
|
* Get all agents.
|
|
* @param {Object} searchParameter - The search parameters to find matching agents.
|
|
* @param {string} searchParameter.author - The user ID of the agent's author.
|
|
* @returns {Promise<Object>} A promise that resolves to an object containing the agents data and pagination info.
|
|
*/
|
|
const getListAgents = async (searchParameter) => {
|
|
const { author, ...otherParams } = searchParameter;
|
|
|
|
let query = Object.assign({ author }, otherParams);
|
|
|
|
const globalProject = await getProjectByName(GLOBAL_PROJECT_NAME, ['agentIds']);
|
|
if (globalProject && (globalProject.agentIds?.length ?? 0) > 0) {
|
|
const globalQuery = { id: { $in: globalProject.agentIds }, ...otherParams };
|
|
delete globalQuery.author;
|
|
query = { $or: [globalQuery, query] };
|
|
}
|
|
|
|
const agents = (
|
|
await Agent.find(query, {
|
|
id: 1,
|
|
_id: 0,
|
|
name: 1,
|
|
avatar: 1,
|
|
author: 1,
|
|
projectIds: 1,
|
|
description: 1,
|
|
isCollaborative: 1,
|
|
}).lean()
|
|
).map((agent) => {
|
|
if (agent.author?.toString() !== author) {
|
|
delete agent.author;
|
|
}
|
|
if (agent.author) {
|
|
agent.author = agent.author.toString();
|
|
}
|
|
return agent;
|
|
});
|
|
|
|
const hasMore = agents.length > 0;
|
|
const firstId = agents.length > 0 ? agents[0].id : null;
|
|
const lastId = agents.length > 0 ? agents[agents.length - 1].id : null;
|
|
|
|
return {
|
|
data: agents,
|
|
has_more: hasMore,
|
|
first_id: firstId,
|
|
last_id: lastId,
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Updates the projects associated with an agent, adding and removing project IDs as specified.
|
|
* This function also updates the corresponding projects to include or exclude the agent ID.
|
|
*
|
|
* @param {Object} params - Parameters for updating the agent's projects.
|
|
* @param {import('librechat-data-provider').TUser} params.user - Parameters for updating the agent's projects.
|
|
* @param {string} params.agentId - The ID of the agent to update.
|
|
* @param {string[]} [params.projectIds] - Array of project IDs to add to the agent.
|
|
* @param {string[]} [params.removeProjectIds] - Array of project IDs to remove from the agent.
|
|
* @returns {Promise<MongoAgent>} The updated agent document.
|
|
* @throws {Error} If there's an error updating the agent or projects.
|
|
*/
|
|
const updateAgentProjects = async ({ user, agentId, projectIds, removeProjectIds }) => {
|
|
const updateOps = {};
|
|
|
|
if (removeProjectIds && removeProjectIds.length > 0) {
|
|
for (const projectId of removeProjectIds) {
|
|
await removeAgentIdsFromProject(projectId, [agentId]);
|
|
}
|
|
updateOps.$pull = { projectIds: { $in: removeProjectIds } };
|
|
}
|
|
|
|
if (projectIds && projectIds.length > 0) {
|
|
for (const projectId of projectIds) {
|
|
await addAgentIdsToProject(projectId, [agentId]);
|
|
}
|
|
updateOps.$addToSet = { projectIds: { $each: projectIds } };
|
|
}
|
|
|
|
if (Object.keys(updateOps).length === 0) {
|
|
return await getAgent({ id: agentId });
|
|
}
|
|
|
|
const updateQuery = { id: agentId, author: user.id };
|
|
if (user.role === SystemRoles.ADMIN) {
|
|
delete updateQuery.author;
|
|
}
|
|
|
|
const updatedAgent = await updateAgent(updateQuery, updateOps);
|
|
if (updatedAgent) {
|
|
return updatedAgent;
|
|
}
|
|
if (updateOps.$addToSet) {
|
|
for (const projectId of projectIds) {
|
|
await removeAgentIdsFromProject(projectId, [agentId]);
|
|
}
|
|
} else if (updateOps.$pull) {
|
|
for (const projectId of removeProjectIds) {
|
|
await addAgentIdsToProject(projectId, [agentId]);
|
|
}
|
|
}
|
|
|
|
return await getAgent({ id: agentId });
|
|
};
|
|
|
|
module.exports = {
|
|
Agent,
|
|
getAgent,
|
|
loadAgent,
|
|
createAgent,
|
|
updateAgent,
|
|
deleteAgent,
|
|
getListAgents,
|
|
updateAgentProjects,
|
|
addAgentResourceFile,
|
|
removeAgentResourceFiles,
|
|
};
|