mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-07-01 20:01:35 +00:00
🦥 perf: Lazy-Load Agent Version History in Editor (#13977)
Opening the agent editor fetched the full `versions` array (each a complete config snapshot) alongside the agent, so agents with large histories were slow to open. Version history is now loaded only when the user opens it. - Add `getAgentWithVersionCount` (aggregation: version count, no versions array) and `getAgentVersions` data-schemas methods. - `getAgentHandler` returns the version count without the heavy array; add `GET /agents/:id/versions` (EDIT-gated) for lazy retrieval. - Add `useGetAgentVersionsQuery`; VersionPanel reads current config from the cached expanded query and fetches versions on open. Revert keeps the expanded cache and versions query in sync.
This commit is contained in:
parent
b15d40e3e4
commit
12fea693bb
10 changed files with 301 additions and 45 deletions
|
|
@ -487,16 +487,15 @@ const getAgentHandler = async (req, res, expandProperties = false) => {
|
|||
const id = req.params.id;
|
||||
const author = req.user.id;
|
||||
|
||||
// Permissions are validated by middleware before calling this function
|
||||
// Simply load the agent by ID
|
||||
const agent = await db.getAgent({ id });
|
||||
// Permissions are validated by middleware before calling this function.
|
||||
// Load the agent with a `version` count but without the heavy `versions`
|
||||
// array; version history is fetched lazily via GET /agents/:id/versions.
|
||||
const agent = await db.getAgentWithVersionCount({ id });
|
||||
|
||||
if (!agent) {
|
||||
return res.status(404).json({ error: 'Agent not found' });
|
||||
}
|
||||
|
||||
agent.version = agent.versions ? agent.versions.length : 0;
|
||||
|
||||
if (agent.avatar && agent.avatar?.source === FileSources.s3) {
|
||||
try {
|
||||
agent.avatar = {
|
||||
|
|
@ -561,6 +560,32 @@ const getAgentHandler = async (req, res, expandProperties = false) => {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves an agent's version history.
|
||||
* Loaded lazily so the editor doesn't transfer large histories up front.
|
||||
* @route GET /agents/:id/versions
|
||||
* @param {object} req - Express Request
|
||||
* @param {object} req.params - Request params
|
||||
* @param {string} req.params.id - Agent identifier.
|
||||
* @returns {Promise<Agent[]>} 200 - The agent's version history - application/json
|
||||
* @returns {Error} 404 - Agent not found
|
||||
*/
|
||||
const getAgentVersionsHandler = async (req, res) => {
|
||||
try {
|
||||
const id = req.params.id;
|
||||
const versions = await db.getAgentVersions({ id });
|
||||
|
||||
if (versions == null) {
|
||||
return res.status(404).json({ error: 'Agent not found' });
|
||||
}
|
||||
|
||||
return res.status(200).json(versions);
|
||||
} catch (error) {
|
||||
logger.error('[/Agents/:id/versions] Error retrieving agent versions', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates an Agent.
|
||||
* @route PATCH /Agents/:id
|
||||
|
|
@ -1330,6 +1355,7 @@ const getAgentCategories = async (_req, res) => {
|
|||
module.exports = {
|
||||
createAgent: createAgentHandler,
|
||||
getAgent: getAgentHandler,
|
||||
getAgentVersions: getAgentVersionsHandler,
|
||||
updateAgent: updateAgentHandler,
|
||||
duplicateAgent: duplicateAgentHandler,
|
||||
deleteAgent: deleteAgentHandler,
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ jest.mock('~/cache', () => ({
|
|||
const {
|
||||
createAgent: createAgentHandler,
|
||||
getAgent: getAgentHandler,
|
||||
getAgentVersions: getAgentVersionsHandler,
|
||||
duplicateAgent: duplicateAgentHandler,
|
||||
revertAgentVersion: revertAgentVersionHandler,
|
||||
updateAgent: updateAgentHandler,
|
||||
|
|
@ -603,6 +604,45 @@ describe('Agent Controllers - Mass Assignment Protection', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('getAgentVersionsHandler', () => {
|
||||
test('returns the version history and excludes it from the basic VIEW response', async () => {
|
||||
const agent = await Agent.create({
|
||||
id: `agent_${uuidv4()}`,
|
||||
name: 'Versioned Agent',
|
||||
provider: 'openai',
|
||||
model: 'gpt-4',
|
||||
author: mockReq.user.id,
|
||||
versions: [
|
||||
{ name: 'V1', provider: 'openai', model: 'gpt-4', updatedAt: new Date() },
|
||||
{ name: 'V2', provider: 'openai', model: 'gpt-4', updatedAt: new Date() },
|
||||
],
|
||||
});
|
||||
mockReq.params = { id: agent.id };
|
||||
|
||||
await getAgentHandler(mockReq, mockRes);
|
||||
const basicResponse = mockRes.json.mock.calls[0][0];
|
||||
expect(basicResponse.versions).toBeUndefined();
|
||||
expect(basicResponse.version).toBe(2);
|
||||
|
||||
mockRes.json.mockClear();
|
||||
await getAgentVersionsHandler(mockReq, mockRes);
|
||||
|
||||
expect(mockRes.status).toHaveBeenCalledWith(200);
|
||||
const versions = mockRes.json.mock.calls[0][0];
|
||||
expect(Array.isArray(versions)).toBe(true);
|
||||
expect(versions).toHaveLength(2);
|
||||
expect(versions.map((v) => v.name)).toEqual(['V1', 'V2']);
|
||||
});
|
||||
|
||||
test('returns 404 when the agent does not exist', async () => {
|
||||
mockReq.params = { id: `agent_${uuidv4()}` };
|
||||
|
||||
await getAgentVersionsHandler(mockReq, mockRes);
|
||||
|
||||
expect(mockRes.status).toHaveBeenCalledWith(404);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateAgentHandler', () => {
|
||||
let existingAgentId;
|
||||
let existingAgentAuthorId;
|
||||
|
|
|
|||
|
|
@ -81,6 +81,23 @@ router.get(
|
|||
}),
|
||||
(req, res) => v1.getAgent(req, res, true), // Expanded version
|
||||
);
|
||||
|
||||
/**
|
||||
* Retrieves an agent's version history (EDIT permission required).
|
||||
* Loaded lazily so the editor doesn't transfer large histories up front.
|
||||
* @route GET /agents/:id/versions
|
||||
* @param {string} req.params.id - Agent identifier.
|
||||
* @returns {Agent[]} 200 - Agent version history - application/json
|
||||
*/
|
||||
router.get(
|
||||
'/:id/versions',
|
||||
checkAgentAccess,
|
||||
canAccessAgentResource({
|
||||
requiredPermission: PermissionBits.EDIT,
|
||||
resourceIdParam: 'id',
|
||||
}),
|
||||
v1.getAgentVersions,
|
||||
);
|
||||
/**
|
||||
* Updates an agent.
|
||||
* @route PATCH /agents/:id
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
import { ChevronLeft } from 'lucide-react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { ChevronLeft } from 'lucide-react';
|
||||
import { useToastContext } from '@librechat/client';
|
||||
import { useGetAgentByIdQuery, useRevertAgentVersionMutation } from '~/data-provider';
|
||||
import type { AgentWithVersions, VersionContext } from './types';
|
||||
import type { AgentWithVersions, VersionContext, VersionRecord } from './types';
|
||||
import {
|
||||
useGetAgentVersionsQuery,
|
||||
useRevertAgentVersionMutation,
|
||||
useGetExpandedAgentByIdQuery,
|
||||
} from '~/data-provider';
|
||||
import { isActiveVersion } from './isActiveVersion';
|
||||
import { useAgentPanelContext } from '~/Providers';
|
||||
import VersionContent from './VersionContent';
|
||||
|
|
@ -16,7 +20,15 @@ export default function VersionPanel() {
|
|||
|
||||
const selectedAgentId = agent_id ?? '';
|
||||
|
||||
const { data: agent, isLoading, error, refetch } = useGetAgentByIdQuery(selectedAgentId);
|
||||
const { data: agent } = useGetExpandedAgentByIdQuery(selectedAgentId, {
|
||||
enabled: !!selectedAgentId,
|
||||
});
|
||||
const {
|
||||
data: versionsData,
|
||||
isLoading,
|
||||
error,
|
||||
refetch,
|
||||
} = useGetAgentVersionsQuery(selectedAgentId);
|
||||
|
||||
const revertAgentVersion = useRevertAgentVersionMutation({
|
||||
onSuccess: () => {
|
||||
|
|
@ -34,7 +46,7 @@ export default function VersionPanel() {
|
|||
},
|
||||
});
|
||||
|
||||
const agentWithVersions = agent as AgentWithVersions;
|
||||
const agentWithVersions = agent as AgentWithVersions | undefined;
|
||||
|
||||
const currentAgent = useMemo(() => {
|
||||
if (!agentWithVersions) return null;
|
||||
|
|
@ -48,14 +60,15 @@ export default function VersionPanel() {
|
|||
};
|
||||
}, [agentWithVersions]);
|
||||
|
||||
const versionRecords = useMemo<VersionRecord[]>(() => versionsData ?? [], [versionsData]);
|
||||
|
||||
const versions = useMemo(() => {
|
||||
const versionsCopy = [...(agentWithVersions?.versions || [])];
|
||||
return versionsCopy.sort((a, b) => {
|
||||
return [...versionRecords].sort((a, b) => {
|
||||
const aTime = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
|
||||
const bTime = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
|
||||
return bTime - aTime;
|
||||
});
|
||||
}, [agentWithVersions?.versions]);
|
||||
}, [versionRecords]);
|
||||
|
||||
const activeVersion = useMemo(() => {
|
||||
return versions.length > 0
|
||||
|
|
@ -73,7 +86,7 @@ export default function VersionPanel() {
|
|||
|
||||
return versions.map((version, displayIndex) => {
|
||||
const originalIndex =
|
||||
agentWithVersions?.versions?.findIndex(
|
||||
versionRecords.findIndex(
|
||||
(v) =>
|
||||
v.updatedAt === version.updatedAt &&
|
||||
v.createdAt === version.createdAt &&
|
||||
|
|
@ -87,7 +100,7 @@ export default function VersionPanel() {
|
|||
isActive: displayIndex === activeVersionId,
|
||||
};
|
||||
});
|
||||
}, [versions, currentAgent, agentWithVersions?.versions]);
|
||||
}, [versions, currentAgent, versionRecords]);
|
||||
|
||||
const versionContext: VersionContext = useMemo(
|
||||
() => ({
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import '@testing-library/jest-dom/extend-expect';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { Panel } from '~/common/types';
|
||||
import VersionContent from '../VersionContent';
|
||||
import VersionPanel from '../VersionPanel';
|
||||
import { Panel } from '~/common/types';
|
||||
|
||||
const mockAgentData = {
|
||||
name: 'Test Agent',
|
||||
|
|
@ -10,35 +10,42 @@ const mockAgentData = {
|
|||
instructions: 'Test Instructions',
|
||||
tools: ['tool1', 'tool2'],
|
||||
capabilities: ['capability1', 'capability2'],
|
||||
versions: [
|
||||
{
|
||||
name: 'Version 1',
|
||||
description: 'Description 1',
|
||||
instructions: 'Instructions 1',
|
||||
tools: ['tool1'],
|
||||
capabilities: ['capability1'],
|
||||
createdAt: '2023-01-01T00:00:00Z',
|
||||
updatedAt: '2023-01-01T00:00:00Z',
|
||||
},
|
||||
{
|
||||
name: 'Version 2',
|
||||
description: 'Description 2',
|
||||
instructions: 'Instructions 2',
|
||||
tools: ['tool1', 'tool2'],
|
||||
capabilities: ['capability1', 'capability2'],
|
||||
createdAt: '2023-01-02T00:00:00Z',
|
||||
updatedAt: '2023-01-02T00:00:00Z',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const mockVersions = [
|
||||
{
|
||||
name: 'Version 1',
|
||||
description: 'Description 1',
|
||||
instructions: 'Instructions 1',
|
||||
tools: ['tool1'],
|
||||
capabilities: ['capability1'],
|
||||
createdAt: '2023-01-01T00:00:00Z',
|
||||
updatedAt: '2023-01-01T00:00:00Z',
|
||||
},
|
||||
{
|
||||
name: 'Version 2',
|
||||
description: 'Description 2',
|
||||
instructions: 'Instructions 2',
|
||||
tools: ['tool1', 'tool2'],
|
||||
capabilities: ['capability1', 'capability2'],
|
||||
createdAt: '2023-01-02T00:00:00Z',
|
||||
updatedAt: '2023-01-02T00:00:00Z',
|
||||
},
|
||||
];
|
||||
|
||||
jest.mock('~/data-provider', () => ({
|
||||
useGetAgentByIdQuery: jest.fn(() => ({
|
||||
useGetExpandedAgentByIdQuery: jest.fn(() => ({
|
||||
data: mockAgentData,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
refetch: jest.fn(),
|
||||
})),
|
||||
useGetAgentVersionsQuery: jest.fn(() => ({
|
||||
data: mockVersions,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
refetch: jest.fn(),
|
||||
})),
|
||||
useRevertAgentVersionMutation: jest.fn(() => ({
|
||||
mutate: jest.fn(),
|
||||
isLoading: false,
|
||||
|
|
@ -67,16 +74,24 @@ describe('VersionPanel', () => {
|
|||
'~/Providers/AgentPanelContext',
|
||||
).useAgentPanelContext;
|
||||
|
||||
const mockUseGetAgentByIdQuery = jest.requireMock('~/data-provider').useGetAgentByIdQuery;
|
||||
const mockUseGetExpandedAgentByIdQuery =
|
||||
jest.requireMock('~/data-provider').useGetExpandedAgentByIdQuery;
|
||||
const mockUseGetAgentVersionsQuery = jest.requireMock('~/data-provider').useGetAgentVersionsQuery;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockUseGetAgentByIdQuery.mockReturnValue({
|
||||
mockUseGetExpandedAgentByIdQuery.mockReturnValue({
|
||||
data: mockAgentData,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
refetch: jest.fn(),
|
||||
});
|
||||
mockUseGetAgentVersionsQuery.mockReturnValue({
|
||||
data: mockVersions,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
refetch: jest.fn(),
|
||||
});
|
||||
|
||||
// Set up the default context mock
|
||||
mockUseAgentPanelContext.mockReturnValue({
|
||||
|
|
@ -126,7 +141,13 @@ describe('VersionPanel', () => {
|
|||
);
|
||||
|
||||
// Test with null data
|
||||
mockUseGetAgentByIdQuery.mockReturnValueOnce({
|
||||
mockUseGetExpandedAgentByIdQuery.mockReturnValueOnce({
|
||||
data: null,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
refetch: jest.fn(),
|
||||
});
|
||||
mockUseGetAgentVersionsQuery.mockReturnValueOnce({
|
||||
data: null,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
|
|
@ -150,8 +171,8 @@ describe('VersionPanel', () => {
|
|||
);
|
||||
|
||||
// 3. versions is undefined
|
||||
mockUseGetAgentByIdQuery.mockReturnValueOnce({
|
||||
data: { ...mockAgentData, versions: undefined },
|
||||
mockUseGetAgentVersionsQuery.mockReturnValueOnce({
|
||||
data: undefined,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
refetch: jest.fn(),
|
||||
|
|
@ -165,7 +186,7 @@ describe('VersionPanel', () => {
|
|||
);
|
||||
|
||||
// 4. loading state
|
||||
mockUseGetAgentByIdQuery.mockReturnValueOnce({
|
||||
mockUseGetAgentVersionsQuery.mockReturnValueOnce({
|
||||
data: null,
|
||||
isLoading: true,
|
||||
error: null,
|
||||
|
|
@ -179,7 +200,7 @@ describe('VersionPanel', () => {
|
|||
|
||||
// 5. error state
|
||||
const testError = new Error('Test error');
|
||||
mockUseGetAgentByIdQuery.mockReturnValueOnce({
|
||||
mockUseGetAgentVersionsQuery.mockReturnValueOnce({
|
||||
data: null,
|
||||
isLoading: false,
|
||||
error: testError,
|
||||
|
|
@ -193,12 +214,18 @@ describe('VersionPanel', () => {
|
|||
});
|
||||
|
||||
test('memoizes agent data correctly', () => {
|
||||
mockUseGetAgentByIdQuery.mockReturnValueOnce({
|
||||
mockUseGetExpandedAgentByIdQuery.mockReturnValueOnce({
|
||||
data: mockAgentData,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
refetch: jest.fn(),
|
||||
});
|
||||
mockUseGetAgentVersionsQuery.mockReturnValueOnce({
|
||||
data: mockVersions,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
refetch: jest.fn(),
|
||||
});
|
||||
|
||||
render(<VersionPanel />);
|
||||
expect(VersionContent).toHaveBeenCalledWith(
|
||||
|
|
|
|||
|
|
@ -379,6 +379,11 @@ export const useRevertAgentVersionMutation = (
|
|||
onError: (error, variables, context) => options?.onError?.(error, variables, context),
|
||||
onSuccess: (revertedAgent, variables, context) => {
|
||||
queryClient.setQueryData<t.Agent>([QueryKeys.agent, variables.agent_id], revertedAgent);
|
||||
queryClient.setQueryData<t.Agent>(
|
||||
[QueryKeys.agent, variables.agent_id, 'expanded'],
|
||||
revertedAgent,
|
||||
);
|
||||
queryClient.invalidateQueries([QueryKeys.agent, variables.agent_id, 'versions']);
|
||||
|
||||
((keys: t.AgentListParams[]) => {
|
||||
keys.forEach((key) => {
|
||||
|
|
|
|||
|
|
@ -133,6 +133,33 @@ export const useGetExpandedAgentByIdQuery = (
|
|||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook for lazily retrieving an agent's version history (EDIT permission).
|
||||
* Only fetched when the user opens version history, so editors with large
|
||||
* histories don't pay the cost on every open.
|
||||
*/
|
||||
export const useGetAgentVersionsQuery = (
|
||||
agent_id: string | null | undefined,
|
||||
config?: UseQueryOptions<t.Agent[]>,
|
||||
): QueryObserverResult<t.Agent[]> => {
|
||||
const isValidAgentId = !!agent_id && !isEphemeralAgent(agent_id);
|
||||
|
||||
return useQuery<t.Agent[]>(
|
||||
[QueryKeys.agent, agent_id, 'versions'],
|
||||
() =>
|
||||
dataService.getAgentVersions({
|
||||
agent_id: agent_id as string,
|
||||
}),
|
||||
{
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
retry: false,
|
||||
...config,
|
||||
enabled: isValidAgentId && (config?.enabled ?? true),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* MARKETPLACE
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -525,6 +525,14 @@ export const getExpandedAgentById = ({ agent_id }: { agent_id: string }): Promis
|
|||
);
|
||||
};
|
||||
|
||||
export const getAgentVersions = ({ agent_id }: { agent_id: string }): Promise<a.Agent[]> => {
|
||||
return request.get(
|
||||
endpoints.agents({
|
||||
path: `${agent_id}/versions`,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
export const updateAgent = ({
|
||||
agent_id,
|
||||
data,
|
||||
|
|
|
|||
|
|
@ -47,6 +47,8 @@ let methods: ReturnType<typeof createAgentMethods>;
|
|||
|
||||
let createAgent: AgentMethods['createAgent'];
|
||||
let getAgent: AgentMethods['getAgent'];
|
||||
let getAgentVersions: AgentMethods['getAgentVersions'];
|
||||
let getAgentWithVersionCount: AgentMethods['getAgentWithVersionCount'];
|
||||
let updateAgent: AgentMethods['updateAgent'];
|
||||
let deleteAgent: AgentMethods['deleteAgent'];
|
||||
let deleteUserAgents: AgentMethods['deleteUserAgents'];
|
||||
|
|
@ -90,6 +92,8 @@ beforeAll(async () => {
|
|||
});
|
||||
createAgent = methods.createAgent;
|
||||
getAgent = methods.getAgent;
|
||||
getAgentVersions = methods.getAgentVersions;
|
||||
getAgentWithVersionCount = methods.getAgentWithVersionCount;
|
||||
updateAgent = methods.updateAgent;
|
||||
deleteAgent = methods.deleteAgent;
|
||||
deleteUserAgents = methods.deleteUserAgents;
|
||||
|
|
@ -1476,6 +1480,55 @@ describe('Agent Methods', () => {
|
|||
expect(agent!.versions![0].model).toBe('test-model');
|
||||
});
|
||||
|
||||
test('getAgentVersions returns only the versions array', async () => {
|
||||
const agentId = `agent_${uuidv4()}`;
|
||||
await createAgent({
|
||||
id: agentId,
|
||||
name: 'First Name',
|
||||
provider: 'test',
|
||||
model: 'test-model',
|
||||
author: new mongoose.Types.ObjectId(),
|
||||
});
|
||||
await updateAgent({ id: agentId }, { name: 'Second Name' });
|
||||
|
||||
const versions = await getAgentVersions({ id: agentId });
|
||||
|
||||
expect(Array.isArray(versions)).toBe(true);
|
||||
expect(versions).toHaveLength(2);
|
||||
expect(versions![0].name).toBe('First Name');
|
||||
expect(versions![1].name).toBe('Second Name');
|
||||
});
|
||||
|
||||
test('getAgentVersions returns null for a non-existent agent', async () => {
|
||||
const versions = await getAgentVersions({ id: `agent_${uuidv4()}` });
|
||||
expect(versions).toBeNull();
|
||||
});
|
||||
|
||||
test('getAgentWithVersionCount returns the count without the versions array', async () => {
|
||||
const agentId = `agent_${uuidv4()}`;
|
||||
await createAgent({
|
||||
id: agentId,
|
||||
name: 'First Name',
|
||||
provider: 'test',
|
||||
model: 'test-model',
|
||||
author: new mongoose.Types.ObjectId(),
|
||||
});
|
||||
await updateAgent({ id: agentId }, { name: 'Second Name' });
|
||||
await updateAgent({ id: agentId }, { name: 'Third Name' });
|
||||
|
||||
const agent = await getAgentWithVersionCount({ id: agentId });
|
||||
|
||||
expect(agent).not.toBeNull();
|
||||
expect(agent!.name).toBe('Third Name');
|
||||
expect(agent!.version).toBe(3);
|
||||
expect(agent!.versions).toBeUndefined();
|
||||
});
|
||||
|
||||
test('getAgentWithVersionCount returns null for a non-existent agent', async () => {
|
||||
const agent = await getAgentWithVersionCount({ id: `agent_${uuidv4()}` });
|
||||
expect(agent).toBeNull();
|
||||
});
|
||||
|
||||
test('should accumulate version history across multiple updates', async () => {
|
||||
const agentId = `agent_${uuidv4()}`;
|
||||
const author = new mongoose.Types.ObjectId();
|
||||
|
|
|
|||
|
|
@ -252,6 +252,10 @@ export function createAgentMethods(
|
|||
deps: AgentDeps,
|
||||
): {
|
||||
getAgent: (searchParameter: FilterQuery<IAgent>) => Promise<IAgent | null>;
|
||||
getAgentVersions: (searchParameter: FilterQuery<IAgent>) => Promise<IAgent['versions'] | null>;
|
||||
getAgentWithVersionCount: (
|
||||
searchParameter: FilterQuery<IAgent>,
|
||||
) => Promise<(IAgent & { version: number }) | null>;
|
||||
getAgents: (searchParameter: FilterQuery<IAgent>) => Promise<IAgent[]>;
|
||||
createAgent: (agentData: Record<string, unknown>) => Promise<IAgent>;
|
||||
hasAgentWithMCPServerName: ({
|
||||
|
|
@ -366,6 +370,40 @@ export function createAgentMethods(
|
|||
return await Agent.findOne(searchParameter).lean<IAgent>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an agent's version history only, without the rest of the document.
|
||||
* Returns an empty array when the agent exists but has no versions, or `null`
|
||||
* when no agent matches the search parameter.
|
||||
*/
|
||||
async function getAgentVersions(
|
||||
searchParameter: FilterQuery<IAgent>,
|
||||
): Promise<IAgent['versions'] | null> {
|
||||
const Agent = mongoose.models.Agent as Model<IAgent>;
|
||||
const result = await Agent.findOne(searchParameter, { versions: 1, _id: 0 }).lean<
|
||||
Pick<IAgent, 'versions'>
|
||||
>();
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
return result.versions ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an agent document with a `version` count, excluding the heavy `versions` array.
|
||||
* Used when loading the editor so large version histories aren't transferred eagerly.
|
||||
*/
|
||||
async function getAgentWithVersionCount(
|
||||
searchParameter: FilterQuery<IAgent>,
|
||||
): Promise<(IAgent & { version: number }) | null> {
|
||||
const Agent = mongoose.models.Agent as Model<IAgent>;
|
||||
const [agent] = await Agent.aggregate<IAgent & { version: number }>([
|
||||
{ $match: searchParameter },
|
||||
{ $addFields: { version: { $size: { $ifNull: ['$versions', []] } } } },
|
||||
{ $project: { versions: 0 } },
|
||||
]);
|
||||
return agent ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get multiple agent documents based on the provided search parameters.
|
||||
*/
|
||||
|
|
@ -994,6 +1032,8 @@ export function createAgentMethods(
|
|||
|
||||
return {
|
||||
getAgent,
|
||||
getAgentVersions,
|
||||
getAgentWithVersionCount,
|
||||
getAgents,
|
||||
createAgent,
|
||||
hasAgentWithMCPServerName,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue