modify-agent.test.js•14.4 kB
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { handleModifyAgent, modifyAgentDefinition } from '../../../tools/agents/modify-agent.js';
import { createMockLettaServer } from '../../utils/mock-server.js';
import { fixtures } from '../../utils/test-fixtures.js';
import { expectValidToolResponse } from '../../utils/test-helpers.js';
describe('Modify Agent', () => {
    let mockServer;
    beforeEach(() => {
        mockServer = createMockLettaServer();
    });
    afterEach(() => {
        vi.restoreAllMocks();
    });
    describe('Tool Definition', () => {
        it('should have correct tool definition', () => {
            expect(modifyAgentDefinition.name).toBe('modify_agent');
            expect(modifyAgentDefinition.description).toContain('Update an existing agent');
            expect(modifyAgentDefinition.inputSchema.required).toEqual(['agent_id', 'update_data']);
            expect(modifyAgentDefinition.inputSchema.properties).toHaveProperty('agent_id');
            expect(modifyAgentDefinition.inputSchema.properties).toHaveProperty('update_data');
        });
        it('should have proper update_data schema', () => {
            const updateDataProp = modifyAgentDefinition.inputSchema.properties.update_data;
            expect(updateDataProp.type).toBe('object');
            expect(updateDataProp.properties).toHaveProperty('name');
            expect(updateDataProp.properties).toHaveProperty('system');
            expect(updateDataProp.properties).toHaveProperty('description');
            expect(updateDataProp.additionalProperties).toBe(true);
        });
    });
    describe('Functionality Tests', () => {
        it('should modify agent name successfully', async () => {
            const updatedAgent = {
                ...fixtures.agent.basic,
                name: 'Updated Agent Name',
            };
            mockServer.api.patch.mockResolvedValueOnce({ data: updatedAgent });
            const result = await handleModifyAgent(mockServer, {
                agent_id: 'agent-123',
                update_data: {
                    name: 'Updated Agent Name',
                },
            });
            // Verify API call
            expect(mockServer.api.patch).toHaveBeenCalledWith(
                '/agents/agent-123',
                { name: 'Updated Agent Name' },
                expect.objectContaining({ headers: expect.any(Object) }),
            );
            // Verify response
            const data = expectValidToolResponse(result);
            expect(data.agent).toEqual(updatedAgent);
            expect(data.agent.name).toBe('Updated Agent Name');
        });
        it('should modify multiple fields at once', async () => {
            const updatedAgent = {
                ...fixtures.agent.basic,
                name: 'New Name',
                description: 'New Description',
                system: 'You are an updated assistant',
                tags: ['updated', 'modified'],
            };
            mockServer.api.patch.mockResolvedValueOnce({ data: updatedAgent });
            const result = await handleModifyAgent(mockServer, {
                agent_id: 'agent-123',
                update_data: {
                    name: 'New Name',
                    description: 'New Description',
                    system: 'You are an updated assistant',
                    tags: ['updated', 'modified'],
                },
            });
            expect(mockServer.api.patch).toHaveBeenCalledWith(
                '/agents/agent-123',
                {
                    name: 'New Name',
                    description: 'New Description',
                    system: 'You are an updated assistant',
                    tags: ['updated', 'modified'],
                },
                expect.any(Object),
            );
            const data = expectValidToolResponse(result);
            expect(data.agent.name).toBe('New Name');
            expect(data.agent.description).toBe('New Description');
            expect(data.agent.system).toBe('You are an updated assistant');
            expect(data.agent.tags).toEqual(['updated', 'modified']);
        });
        it('should handle special characters in agent_id', async () => {
            const updatedAgent = fixtures.agent.basic;
            mockServer.api.patch.mockResolvedValueOnce({ data: updatedAgent });
            const result = await handleModifyAgent(mockServer, {
                agent_id: 'agent@special#123',
                update_data: { name: 'Updated' },
            });
            // Verify URL encoding
            expect(mockServer.api.patch).toHaveBeenCalledWith(
                '/agents/agent%40special%23123',
                expect.any(Object),
                expect.any(Object),
            );
            const data = expectValidToolResponse(result);
            expect(data.agent).toEqual(updatedAgent);
        });
        it('should modify agent configuration fields', async () => {
            const updatedAgent = {
                ...fixtures.agent.basic,
                llm_config: {
                    model: 'gpt-4-turbo',
                    temperature: 0.9,
                    max_tokens: 3000,
                },
                embedding_config: {
                    model: 'text-embedding-3-large',
                },
            };
            mockServer.api.patch.mockResolvedValueOnce({ data: updatedAgent });
            const result = await handleModifyAgent(mockServer, {
                agent_id: 'agent-123',
                update_data: {
                    llm_config: {
                        model: 'gpt-4-turbo',
                        temperature: 0.9,
                        max_tokens: 3000,
                    },
                    embedding_config: {
                        model: 'text-embedding-3-large',
                    },
                },
            });
            const data = expectValidToolResponse(result);
            expect(data.agent.llm_config.model).toBe('gpt-4-turbo');
            expect(data.agent.llm_config.temperature).toBe(0.9);
            expect(data.agent.embedding_config.model).toBe('text-embedding-3-large');
        });
        it('should handle empty update_data gracefully', async () => {
            const unchangedAgent = fixtures.agent.basic;
            mockServer.api.patch.mockResolvedValueOnce({ data: unchangedAgent });
            const result = await handleModifyAgent(mockServer, {
                agent_id: 'agent-123',
                update_data: {},
            });
            expect(mockServer.api.patch).toHaveBeenCalledWith(
                '/agents/agent-123',
                {},
                expect.any(Object),
            );
            const data = expectValidToolResponse(result);
            expect(data.agent).toEqual(unchangedAgent);
        });
        it('should update tool_ids array', async () => {
            const updatedAgent = {
                ...fixtures.agent.basic,
                tool_ids: ['new-tool-1', 'new-tool-2', 'new-tool-3'],
            };
            mockServer.api.patch.mockResolvedValueOnce({ data: updatedAgent });
            const result = await handleModifyAgent(mockServer, {
                agent_id: 'agent-123',
                update_data: {
                    tool_ids: ['new-tool-1', 'new-tool-2', 'new-tool-3'],
                },
            });
            const data = expectValidToolResponse(result);
            expect(data.agent.tool_ids).toEqual(['new-tool-1', 'new-tool-2', 'new-tool-3']);
        });
    });
    describe('Error Handling', () => {
        it('should throw error for missing agent_id', async () => {
            await expect(
                handleModifyAgent(mockServer, {
                    update_data: { name: 'New Name' },
                }),
            ).rejects.toThrow('Missing required argument: agent_id');
        });
        it('should throw error for missing update_data', async () => {
            await expect(
                handleModifyAgent(mockServer, {
                    agent_id: 'agent-123',
                }),
            ).rejects.toThrow('Missing required argument: update_data');
        });
        it('should throw error for null agent_id', async () => {
            await expect(
                handleModifyAgent(mockServer, {
                    agent_id: null,
                    update_data: { name: 'New Name' },
                }),
            ).rejects.toThrow('Missing required argument: agent_id');
        });
        it('should throw error for null update_data', async () => {
            await expect(
                handleModifyAgent(mockServer, {
                    agent_id: 'agent-123',
                    update_data: null,
                }),
            ).rejects.toThrow('Missing required argument: update_data');
        });
        it('should handle agent not found (404)', async () => {
            const error = new Error('Not found');
            error.response = { status: 404 };
            mockServer.api.patch.mockRejectedValueOnce(error);
            await expect(
                handleModifyAgent(mockServer, {
                    agent_id: 'nonexistent',
                    update_data: { name: 'New Name' },
                }),
            ).rejects.toThrow('Agent not found: nonexistent');
        });
        it('should handle validation errors (422)', async () => {
            const error = new Error('Validation failed');
            error.response = {
                status: 422,
                data: {
                    detail: [
                        { loc: ['body', 'name'], msg: 'Name too long' },
                        { loc: ['body', 'temperature'], msg: 'Must be between 0 and 1' },
                    ],
                },
            };
            mockServer.api.patch.mockRejectedValueOnce(error);
            await expect(
                handleModifyAgent(mockServer, {
                    agent_id: 'agent-123',
                    update_data: {
                        name: 'A'.repeat(1000),
                        temperature: 2.0,
                    },
                }),
            ).rejects.toThrow(/Validation error updating agent agent-123/);
        });
        it('should handle server errors (500)', async () => {
            const error = new Error('Internal server error');
            error.response = { status: 500, data: { error: 'Database error' } };
            mockServer.api.patch.mockRejectedValueOnce(error);
            await expect(
                handleModifyAgent(mockServer, {
                    agent_id: 'agent-123',
                    update_data: { name: 'New Name' },
                }),
            ).rejects.toThrow('Internal server error');
        });
        it('should handle network errors', async () => {
            const error = new Error('Network error');
            // No response property for network errors
            mockServer.api.patch.mockRejectedValueOnce(error);
            await expect(
                handleModifyAgent(mockServer, {
                    agent_id: 'agent-123',
                    update_data: { name: 'New Name' },
                }),
            ).rejects.toThrow('Network error');
        });
    });
    describe('Edge Cases', () => {
        it('should handle empty agent_id string', async () => {
            await expect(
                handleModifyAgent(mockServer, {
                    agent_id: '',
                    update_data: { name: 'New Name' },
                }),
            ).rejects.toThrow('Missing required argument: agent_id');
        });
        it('should handle updating with null values', async () => {
            const updatedAgent = {
                ...fixtures.agent.basic,
                description: null,
                tags: null,
            };
            mockServer.api.patch.mockResolvedValueOnce({ data: updatedAgent });
            const result = await handleModifyAgent(mockServer, {
                agent_id: 'agent-123',
                update_data: {
                    description: null,
                    tags: null,
                },
            });
            const data = expectValidToolResponse(result);
            expect(data.agent.description).toBeNull();
            expect(data.agent.tags).toBeNull();
        });
        it('should handle deeply nested update data', async () => {
            const updatedAgent = {
                ...fixtures.agent.basic,
                metadata: {
                    custom: {
                        nested: {
                            value: 'deep update',
                        },
                    },
                },
            };
            mockServer.api.patch.mockResolvedValueOnce({ data: updatedAgent });
            const result = await handleModifyAgent(mockServer, {
                agent_id: 'agent-123',
                update_data: {
                    metadata: {
                        custom: {
                            nested: {
                                value: 'deep update',
                            },
                        },
                    },
                },
            });
            const data = expectValidToolResponse(result);
            expect(data.agent.metadata.custom.nested.value).toBe('deep update');
        });
        it('should preserve fields not included in update_data', async () => {
            const originalAgent = {
                ...fixtures.agent.basic,
                field1: 'original1',
                field2: 'original2',
                field3: 'original3',
            };
            const updatedAgent = {
                ...originalAgent,
                field2: 'updated2',
            };
            mockServer.api.patch.mockResolvedValueOnce({ data: updatedAgent });
            const result = await handleModifyAgent(mockServer, {
                agent_id: 'agent-123',
                update_data: {
                    field2: 'updated2',
                },
            });
            const data = expectValidToolResponse(result);
            expect(data.agent.field1).toBe('original1');
            expect(data.agent.field2).toBe('updated2');
            expect(data.agent.field3).toBe('original3');
        });
    });
});