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

* ✨ feat: Enhance agent update functionality to save current state in versions array - Updated the `updateAgent` function to push the current agent's state into a new `versions` array when an agent is updated. - Modified the agent schema to include a `versions` field for storing historical states of agents. * ✨ feat: Add comprehensive CRUD operations for agents in tests - Introduced a new test suite for CRUD operations on agents, including create, read, update, and delete functionalities. - Implemented tests for listing agents by author and updating agent projects. - Enhanced the agent model to support version history tracking during updates. - Ensured proper environment variable management during tests. * ✨ feat: Introduce version tracking for agents and enhance UI components - Added a `version` property to the agent model to track the number of versions. - Updated the `getAgentHandler` to include the agent's version in the response. - Introduced a new `VersionButton` component for navigating to the version panel. - Created a `VersionPanel` component for displaying version-related information. - Updated the UI to conditionally render the version button and panel based on the active state. - Added localization for the new version-related UI elements. * ✨ i18n: Add "version" translation key across multiple languages - Introduced the "com_ui_agent_version" translation key in various language files to support version tracking for agents. - Updated Arabic, Czech, German, English, Spanish, Estonian, Persian, Finnish, French, Hebrew, Hungarian, Indonesian, Italian, Japanese, Korean, Dutch, Polish, Portuguese (Brazil and Portugal), Russian, Swedish, Thai, Turkish, Vietnamese, and Chinese (Simplified and Traditional) translations. * ✨ feat: Update AgentFooter to conditionally render AdminSettings - Modified the logic for displaying buttons in the AgentFooter component to only show them when the active panel is the builder. - Ensured that AdminSettings is displayed only when the user has an admin role and the buttons are visible. * ✨ feat: Enhance AgentPanelSwitch and VersionPanel for improved agent capabilities - Updated AgentPanelSwitch to include a new VersionPanel for displaying version-related information. - Enhanced agentsConfig logic to properly handle agent capabilities. - Modified VersionPanel to improve structure and localization support. - Integrated createAgent mutation for future agent creation functionality. * ✨ feat: Enhance VersionPanel to display agent version history and loading states - Integrated version fetching logic in VersionPanel to retrieve and display agent version history. - Added loading and error handling states to improve user experience. - Updated agent schema to use mixed types for versions, allowing for more flexible version data structures. - Introduced localization support for version-related UI elements. * ✨ feat: Update VersionPanel and AgentPanelSwitch to enhance agent selection and version display - Modified AgentPanelSwitch to pass selectedAgentId to VersionPanel for improved agent context. - Enhanced VersionPanel to handle multiple timestamp formats and display appropriate messages when no agent is selected. - Improved structure and readability of the VersionPanel component by adding a helper function for timestamp retrieval. * ✨ feat: Refactor VersionPanel to utilize localization and improve timestamp handling - Replaced hardcoded text constants with localization support for various UI elements in VersionPanel. - Enhanced the timestamp retrieval function to handle errors gracefully and utilize localized messages for unknown dates. - Improved user feedback by displaying localized messages for agent selection, version errors, and empty states. * ✨ refactor: Clean up VersionPanel by removing unused code and improving timestamp handling * ✨ feat: Implement agent version reverting functionality - Added `revertAgentVersion` method in the Agent model to allow reverting to a previous version of an agent. - Introduced `revertAgentVersionHandler` in the agents controller to handle requests for reverting agent versions. - Updated API routes to include a new endpoint for reverting agent versions. - Enhanced the VersionPanel component to support version restoration with user confirmation and feedback. - Added localization support for success and error messages related to version restoration. * ✨ i18n: Add localization for agent version restoration messages * Simplify VersionPanel by removing unused parameters and enhancing agent ID handling * Refactor Agent model and VersionPanel component to streamline version data handling * Update version handling in Agent model and VersionPanel - Enhanced the Agent model to include an `updatedAt` timestamp when pushing new versions. - Improved the VersionPanel component to sort versions by the `updatedAt` timestamp for better display order. - Added a new localization entry for indicating the active version of an agent. * ✨ i18n: Add localization for active agent version across multiple languages * ✨ feat: Introduce version management components for agent history - Added `isActiveVersion` utility to determine the active version of an agent based on various criteria. - Implemented `VersionContent` and `VersionItem` components to display agent version history, including loading and error states. - Enhanced `VersionPanel` to integrate new components and manage version context effectively. - Added comprehensive tests for version management functionalities to ensure reliability and correctness. * Add unit tests for AgentFooter component * cleanup * Enhance agent version update handling and add unit tests for update operators - Updated the `updateAgent` function to properly handle various update operators ($push, $pull, $addToSet) while maintaining version history. - Modified unit tests to validate the correct behavior of agent updates, including versioning and tool management. * Enhance version comparison logic and update tests for artifacts handling - Modified the `isActiveVersion` utility to include artifacts in the version comparison criteria. - Updated the `VersionPanel` component to support artifacts in the agent state. - Added new unit tests to validate artifacts matching scenarios and edge cases in the `isActiveVersion` function. * Implement duplicate version detection in agent updates and enhance error handling - Added `isDuplicateVersion` function to check for identical versions during agent updates, excluding certain fields. - Updated `updateAgent` function to throw an error if a duplicate version is detected, with detailed error information. - Enhanced the `updateAgentHandler` to return appropriate responses for duplicate version errors. - Modified client-side error handling to display user-friendly messages for duplicate version scenarios. - Added comprehensive unit tests to validate duplicate version detection and error handling across various update scenarios. * Update version title localization to include version number across multiple languages - Modified the `com_ui_agent_version_title` translation key to include a placeholder for the version number in various language files. - Enhanced the `VersionItem` component to utilize the updated localization for displaying version titles dynamically. * Enhance agent version handling and add revert functionality - Updated the `isDuplicateVersion` function to improve version comparison logic, including special handling for `projectIds` and arrays of objects. - Modified the `updateAgent` function to streamline version updates and removed unnecessary checks for test environments. - Introduced a new `revertAgentVersion` function to allow reverting agents to specific versions, with detailed documentation. - Enhanced unit tests to validate duplicate version detection and revert functionality, ensuring robust error handling and version management. * fix CI issues * cleanup * Revert all non-English translations * clean up tests
547 lines
17 KiB
JavaScript
547 lines
17 KiB
JavaScript
const mongoose = require('mongoose');
|
|
const { agentSchema } = require('@librechat/data-schemas');
|
|
const { SystemRoles, Tools } = require('librechat-data-provider');
|
|
const { GLOBAL_PROJECT_NAME, EPHEMERAL_AGENT_ID, mcp_delimiter } =
|
|
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 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) => {
|
|
const { versions, ...versionData } = agentData;
|
|
const timestamp = new Date();
|
|
const initialAgentData = {
|
|
...agentData,
|
|
versions: [
|
|
{
|
|
...versionData,
|
|
createdAt: timestamp,
|
|
updatedAt: timestamp,
|
|
},
|
|
],
|
|
};
|
|
return (await Agent.create(initialAgentData)).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
|
|
* @param {string} params.endpoint
|
|
* @param {import('@librechat/agents').ClientOptions} [params.model_parameters]
|
|
* @returns {Agent|null} The agent document as a plain object, or null if not found.
|
|
*/
|
|
const loadEphemeralAgent = ({ req, agent_id, endpoint, model_parameters: _m }) => {
|
|
const { model, ...model_parameters } = _m;
|
|
/** @type {Record<string, FunctionTool>} */
|
|
const availableTools = req.app.locals.availableTools;
|
|
const mcpServers = new Set(req.body.ephemeralAgent?.mcp);
|
|
/** @type {string[]} */
|
|
const tools = [];
|
|
if (req.body.ephemeralAgent?.execute_code === true) {
|
|
tools.push(Tools.execute_code);
|
|
}
|
|
|
|
if (mcpServers.size > 0) {
|
|
for (const toolName of Object.keys(availableTools)) {
|
|
if (!toolName.includes(mcp_delimiter)) {
|
|
continue;
|
|
}
|
|
const mcpServer = toolName.split(mcp_delimiter)?.[1];
|
|
if (mcpServer && mcpServers.has(mcpServer)) {
|
|
tools.push(toolName);
|
|
}
|
|
}
|
|
}
|
|
|
|
const instructions = req.body.promptPrefix;
|
|
return {
|
|
id: agent_id,
|
|
instructions,
|
|
provider: endpoint,
|
|
model_parameters,
|
|
model,
|
|
tools,
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Load an agent based on the provided ID
|
|
*
|
|
* @param {Object} params
|
|
* @param {ServerRequest} params.req
|
|
* @param {string} params.agent_id
|
|
* @param {string} params.endpoint
|
|
* @param {import('@librechat/agents').ClientOptions} [params.model_parameters]
|
|
* @returns {Promise<Agent|null>} The agent document as a plain object, or null if not found.
|
|
*/
|
|
const loadAgent = async ({ req, agent_id, endpoint, model_parameters }) => {
|
|
if (!agent_id) {
|
|
return null;
|
|
}
|
|
if (agent_id === EPHEMERAL_AGENT_ID) {
|
|
return loadEphemeralAgent({ req, agent_id, endpoint, model_parameters });
|
|
}
|
|
const agent = await getAgent({
|
|
id: agent_id,
|
|
});
|
|
|
|
if (!agent) {
|
|
return null;
|
|
}
|
|
|
|
agent.version = agent.versions ? agent.versions.length : 0;
|
|
|
|
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;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Check if a version already exists in the versions array, excluding timestamp and author fields
|
|
* @param {Object} updateData - The update data to compare
|
|
* @param {Array} versions - The existing versions array
|
|
* @returns {Object|null} - The matching version if found, null otherwise
|
|
*/
|
|
const isDuplicateVersion = (updateData, currentData, versions) => {
|
|
if (!versions || versions.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
const excludeFields = [
|
|
'_id',
|
|
'id',
|
|
'createdAt',
|
|
'updatedAt',
|
|
'author',
|
|
'created_at',
|
|
'updated_at',
|
|
'__v',
|
|
'agent_ids',
|
|
'versions',
|
|
];
|
|
|
|
const { $push, $pull, $addToSet, ...directUpdates } = updateData;
|
|
|
|
if (Object.keys(directUpdates).length === 0) {
|
|
return null;
|
|
}
|
|
|
|
const wouldBeVersion = { ...currentData, ...directUpdates };
|
|
const lastVersion = versions[versions.length - 1];
|
|
|
|
const allFields = new Set([...Object.keys(wouldBeVersion), ...Object.keys(lastVersion)]);
|
|
|
|
const importantFields = Array.from(allFields).filter((field) => !excludeFields.includes(field));
|
|
|
|
let isMatch = true;
|
|
for (const field of importantFields) {
|
|
if (!wouldBeVersion[field] && !lastVersion[field]) {
|
|
continue;
|
|
}
|
|
|
|
if (Array.isArray(wouldBeVersion[field]) && Array.isArray(lastVersion[field])) {
|
|
if (wouldBeVersion[field].length !== lastVersion[field].length) {
|
|
isMatch = false;
|
|
break;
|
|
}
|
|
|
|
// Special handling for projectIds (MongoDB ObjectIds)
|
|
if (field === 'projectIds') {
|
|
const wouldBeIds = wouldBeVersion[field].map((id) => id.toString()).sort();
|
|
const versionIds = lastVersion[field].map((id) => id.toString()).sort();
|
|
|
|
if (!wouldBeIds.every((id, i) => id === versionIds[i])) {
|
|
isMatch = false;
|
|
break;
|
|
}
|
|
}
|
|
// Handle arrays of objects like tool_kwargs
|
|
else if (typeof wouldBeVersion[field][0] === 'object' && wouldBeVersion[field][0] !== null) {
|
|
const sortedWouldBe = [...wouldBeVersion[field]].map((item) => JSON.stringify(item)).sort();
|
|
const sortedVersion = [...lastVersion[field]].map((item) => JSON.stringify(item)).sort();
|
|
|
|
if (!sortedWouldBe.every((item, i) => item === sortedVersion[i])) {
|
|
isMatch = false;
|
|
break;
|
|
}
|
|
} else {
|
|
const sortedWouldBe = [...wouldBeVersion[field]].sort();
|
|
const sortedVersion = [...lastVersion[field]].sort();
|
|
|
|
if (!sortedWouldBe.every((item, i) => item === sortedVersion[i])) {
|
|
isMatch = false;
|
|
break;
|
|
}
|
|
}
|
|
} else if (field === 'model_parameters') {
|
|
const wouldBeParams = wouldBeVersion[field] || {};
|
|
const lastVersionParams = lastVersion[field] || {};
|
|
if (JSON.stringify(wouldBeParams) !== JSON.stringify(lastVersionParams)) {
|
|
isMatch = false;
|
|
break;
|
|
}
|
|
} else if (wouldBeVersion[field] !== lastVersion[field]) {
|
|
isMatch = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return isMatch ? lastVersion : null;
|
|
};
|
|
|
|
/**
|
|
* Update an agent with new data without overwriting existing
|
|
* properties, or create a new agent if it doesn't exist.
|
|
* When an agent is updated, a copy of the current state will be saved to the versions array.
|
|
*
|
|
* @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.
|
|
* @throws {Error} If the update would create a duplicate version
|
|
*/
|
|
const updateAgent = async (searchParameter, updateData) => {
|
|
const options = { new: true, upsert: false };
|
|
|
|
const currentAgent = await Agent.findOne(searchParameter);
|
|
if (currentAgent) {
|
|
const { __v, _id, id, versions, ...versionData } = currentAgent.toObject();
|
|
const { $push, $pull, $addToSet, ...directUpdates } = updateData;
|
|
|
|
if (Object.keys(directUpdates).length > 0 && versions && versions.length > 0) {
|
|
const duplicateVersion = isDuplicateVersion(updateData, versionData, versions);
|
|
if (duplicateVersion) {
|
|
const error = new Error(
|
|
'Duplicate version: This would create a version identical to an existing one',
|
|
);
|
|
error.statusCode = 409;
|
|
error.details = {
|
|
duplicateVersion,
|
|
versionIndex: versions.findIndex(
|
|
(v) => JSON.stringify(duplicateVersion) === JSON.stringify(v),
|
|
),
|
|
};
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
updateData.$push = {
|
|
...($push || {}),
|
|
versions: {
|
|
...versionData,
|
|
...directUpdates,
|
|
updatedAt: new Date(),
|
|
},
|
|
};
|
|
}
|
|
|
|
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 };
|
|
let agent = await getAgent(searchParameter);
|
|
if (!agent) {
|
|
throw new Error('Agent not found for adding resource file');
|
|
}
|
|
const fileIdsPath = `tool_resources.${tool_resource}.file_ids`;
|
|
await Agent.updateOne(
|
|
{
|
|
id: agent_id,
|
|
[`${fileIdsPath}`]: { $exists: false },
|
|
},
|
|
{
|
|
$set: {
|
|
[`${fileIdsPath}`]: [],
|
|
},
|
|
},
|
|
);
|
|
|
|
const updateData = {
|
|
$addToSet: {
|
|
tools: tool_resource,
|
|
[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 using atomic operations.
|
|
* @param {object} params
|
|
* @param {string} params.agent_id
|
|
* @param {Array<{tool_resource: string, file_id: string}>} params.files
|
|
* @returns {Promise<Agent>} The updated agent.
|
|
* @throws {Error} If the agent is not found or update fails.
|
|
*/
|
|
const removeAgentResourceFiles = async ({ agent_id, files }) => {
|
|
const searchParameter = { id: agent_id };
|
|
|
|
// Group files to remove by resource
|
|
const filesByResource = files.reduce((acc, { tool_resource, file_id }) => {
|
|
if (!acc[tool_resource]) {
|
|
acc[tool_resource] = [];
|
|
}
|
|
acc[tool_resource].push(file_id);
|
|
return acc;
|
|
}, {});
|
|
|
|
// Step 1: Atomically remove file IDs using $pull
|
|
const pullOps = {};
|
|
const resourcesToCheck = new Set();
|
|
for (const [resource, fileIds] of Object.entries(filesByResource)) {
|
|
const fileIdsPath = `tool_resources.${resource}.file_ids`;
|
|
pullOps[fileIdsPath] = { $in: fileIds };
|
|
resourcesToCheck.add(resource);
|
|
}
|
|
|
|
const updatePullData = { $pull: pullOps };
|
|
const agentAfterPull = await Agent.findOneAndUpdate(searchParameter, updatePullData, {
|
|
new: true,
|
|
}).lean();
|
|
|
|
if (!agentAfterPull) {
|
|
// Agent might have been deleted concurrently, or never existed.
|
|
// Check if it existed before trying to throw.
|
|
const agentExists = await getAgent(searchParameter);
|
|
if (!agentExists) {
|
|
throw new Error('Agent not found for removing resource files');
|
|
}
|
|
// If it existed but findOneAndUpdate returned null, something else went wrong.
|
|
throw new Error('Failed to update agent during file removal (pull step)');
|
|
}
|
|
|
|
// Return the agent state directly after the $pull operation.
|
|
// Skipping the $unset step for now to simplify and test core $pull atomicity.
|
|
// Empty arrays might remain, but the removal itself should be correct.
|
|
return agentAfterPull;
|
|
};
|
|
|
|
/**
|
|
* 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 {MongoUser} 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 });
|
|
};
|
|
|
|
/**
|
|
* Reverts an agent to a specific version in its version history.
|
|
* @param {Object} searchParameter - The search parameters to find the agent to revert.
|
|
* @param {string} searchParameter.id - The ID of the agent to revert.
|
|
* @param {string} [searchParameter.author] - The user ID of the agent's author.
|
|
* @param {number} versionIndex - The index of the version to revert to in the versions array.
|
|
* @returns {Promise<MongoAgent>} The updated agent document after reverting.
|
|
* @throws {Error} If the agent is not found or the specified version does not exist.
|
|
*/
|
|
const revertAgentVersion = async (searchParameter, versionIndex) => {
|
|
const agent = await Agent.findOne(searchParameter);
|
|
if (!agent) {
|
|
throw new Error('Agent not found');
|
|
}
|
|
|
|
if (!agent.versions || !agent.versions[versionIndex]) {
|
|
throw new Error(`Version ${versionIndex} not found`);
|
|
}
|
|
|
|
const revertToVersion = agent.versions[versionIndex];
|
|
|
|
const updateData = {
|
|
...revertToVersion,
|
|
};
|
|
|
|
delete updateData._id;
|
|
delete updateData.id;
|
|
delete updateData.versions;
|
|
|
|
return Agent.findOneAndUpdate(searchParameter, updateData, { new: true }).lean();
|
|
};
|
|
|
|
module.exports = {
|
|
Agent,
|
|
getAgent,
|
|
loadAgent,
|
|
createAgent,
|
|
updateAgent,
|
|
deleteAgent,
|
|
getListAgents,
|
|
updateAgentProjects,
|
|
addAgentResourceFile,
|
|
removeAgentResourceFiles,
|
|
revertAgentVersion,
|
|
};
|