prompt-agent.test.js•15.4 kB
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import {
handlePromptAgent,
promptAgentToolDefinition,
} from '../../../tools/agents/prompt-agent.js';
import { createMockLettaServer } from '../../utils/mock-server.js';
import { fixtures } from '../../utils/test-fixtures.js';
import { expectValidToolResponse } from '../../utils/test-helpers.js';
describe('Prompt Agent', () => {
let mockServer;
beforeEach(() => {
mockServer = createMockLettaServer();
});
afterEach(() => {
vi.restoreAllMocks();
});
describe('Tool Definition', () => {
it('should have correct tool definition', () => {
expect(promptAgentToolDefinition.name).toBe('prompt_agent');
expect(promptAgentToolDefinition.description).toContain('Send a message to an agent');
expect(promptAgentToolDefinition.inputSchema.required).toEqual(['agent_id', 'message']);
expect(promptAgentToolDefinition.inputSchema.properties).toHaveProperty('agent_id');
expect(promptAgentToolDefinition.inputSchema.properties).toHaveProperty('message');
});
it('should have proper parameter descriptions', () => {
const agentIdProp = promptAgentToolDefinition.inputSchema.properties.agent_id;
const messageProp = promptAgentToolDefinition.inputSchema.properties.message;
expect(agentIdProp.type).toBe('string');
expect(agentIdProp.description).toContain('ID of the agent');
expect(messageProp.type).toBe('string');
expect(messageProp.description).toContain('Message to send');
});
});
describe('Functionality Tests', () => {
it('should prompt agent successfully with assistant message', async () => {
const agentData = { ...fixtures.agent.basic, name: 'Test Assistant' };
mockServer.api.get.mockResolvedValueOnce({ data: agentData });
// Mock SSE response with assistant message
const sseResponse = [
'data: {"message_type": "assistant_message", "content": "Hello! How can I help you today?"}',
'',
'data: [DONE]',
'',
].join('\n');
mockServer.api.post.mockResolvedValueOnce({
data: sseResponse,
headers: { 'content-type': 'text/event-stream' },
});
const result = await handlePromptAgent(mockServer, {
agent_id: 'agent-123',
message: 'Hello, how are you?',
});
// Verify API calls
expect(mockServer.api.get).toHaveBeenCalledWith(
'/agents/agent-123',
expect.objectContaining({ headers: expect.any(Object) }),
);
expect(mockServer.api.post).toHaveBeenCalledWith(
'/agents/agent-123/messages/stream',
{
messages: [{ role: 'user', content: 'Hello, how are you?' }],
stream_steps: false,
stream_tokens: false,
},
expect.objectContaining({
headers: expect.any(Object),
responseType: 'text',
}),
);
// Verify response
const data = expectValidToolResponse(result);
expect(data.agent_id).toBe('agent-123');
expect(data.agent_name).toBe('Test Assistant');
expect(data.message).toBe('Hello, how are you?');
expect(data.response).toBe('Hello! How can I help you today?');
});
it('should handle reasoning messages', async () => {
const agentData = fixtures.agent.basic;
mockServer.api.get.mockResolvedValueOnce({ data: agentData });
const sseResponse = [
'data: {"message_type": "reasoning_message", "reasoning": "The user is asking about the weather. I should provide helpful information."}',
'data: {"message_type": "assistant_message", "content": "I\'d be happy to help with weather information! Could you tell me which location you\'re interested in?"}',
'data: [DONE]',
].join('\n');
mockServer.api.post.mockResolvedValueOnce({ data: sseResponse });
const result = await handlePromptAgent(mockServer, {
agent_id: 'agent-123',
message: "What's the weather like?",
});
const data = expectValidToolResponse(result);
expect(data.response).toBe(
"I'd be happy to help with weather information! Could you tell me which location you're interested in?",
);
});
it('should handle streaming delta updates', async () => {
const agentData = fixtures.agent.basic;
mockServer.api.get.mockResolvedValueOnce({ data: agentData });
const sseResponse = [
'data: {"delta": {"content": "I am "}}',
'data: {"delta": {"content": "processing "}}',
'data: {"delta": {"content": "your request."}}',
'data: [DONE]',
].join('\n');
mockServer.api.post.mockResolvedValueOnce({ data: sseResponse });
const result = await handlePromptAgent(mockServer, {
agent_id: 'agent-123',
message: 'Process this request',
});
const data = expectValidToolResponse(result);
// The response includes [DONE] because it's parsed as a delta
expect(data.response).toBe('I am \nprocessing \nyour request.\n[DONE]');
});
it('should handle mixed message types', async () => {
const agentData = fixtures.agent.basic;
mockServer.api.get.mockResolvedValueOnce({ data: agentData });
const sseResponse = [
'data: {"message_type": "reasoning_message", "reasoning": "Analyzing the question..."}',
'data: {"delta": {"content": "Based on "}}',
'data: {"delta": {"content": "my analysis..."}}',
'data: {"message_type": "assistant_message", "content": "Based on my analysis, here is the answer."}',
'data: [DONE]',
].join('\n');
mockServer.api.post.mockResolvedValueOnce({ data: sseResponse });
const result = await handlePromptAgent(mockServer, {
agent_id: 'agent-123',
message: 'Analyze this',
});
const data = expectValidToolResponse(result);
// Should use the assistant_message when available
expect(data.response).toBe('Based on my analysis, here is the answer.');
});
it('should handle empty SSE response', async () => {
const agentData = fixtures.agent.basic;
mockServer.api.get.mockResolvedValueOnce({ data: agentData });
mockServer.api.post.mockResolvedValueOnce({ data: '' });
const result = await handlePromptAgent(mockServer, {
agent_id: 'agent-123',
message: 'Hello',
});
const data = expectValidToolResponse(result);
expect(data.response).toBe("Received response but couldn't extract message content");
});
it('should handle non-string response data', async () => {
const agentData = fixtures.agent.basic;
mockServer.api.get.mockResolvedValueOnce({ data: agentData });
// Non-SSE response (unexpected)
mockServer.api.post.mockResolvedValueOnce({
data: { message: 'Direct response object' },
});
const result = await handlePromptAgent(mockServer, {
agent_id: 'agent-123',
message: 'Hello',
});
const data = expectValidToolResponse(result);
expect(data.response).toBe('{"message":"Direct response object"}');
});
});
describe('Error Handling', () => {
it('should throw error for missing agent_id', async () => {
await expect(
handlePromptAgent(mockServer, {
message: 'Hello',
}),
).rejects.toThrow('Missing required arguments: agent_id and message');
});
it('should throw error for missing message', async () => {
await expect(
handlePromptAgent(mockServer, {
agent_id: 'agent-123',
}),
).rejects.toThrow('Missing required arguments: agent_id and message');
});
it('should throw error for missing both arguments', async () => {
await expect(handlePromptAgent(mockServer, {})).rejects.toThrow(
'Missing required arguments: agent_id and message',
);
});
it('should handle agent not found', async () => {
const error = new Error('Not found');
error.response = { status: 404 };
mockServer.api.get.mockRejectedValueOnce(error);
await expect(
handlePromptAgent(mockServer, {
agent_id: 'nonexistent',
message: 'Hello',
}),
).rejects.toThrow('Not found');
});
it('should handle API error during message sending', async () => {
const agentData = fixtures.agent.basic;
mockServer.api.get.mockResolvedValueOnce({ data: agentData });
const error = new Error('Server error');
error.response = { status: 500 };
mockServer.api.post.mockRejectedValueOnce(error);
await expect(
handlePromptAgent(mockServer, {
agent_id: 'agent-123',
message: 'Hello',
}),
).rejects.toThrow('Server error');
});
it('should handle malformed SSE data', async () => {
const agentData = fixtures.agent.basic;
mockServer.api.get.mockResolvedValueOnce({ data: agentData });
const sseResponse = [
'data: {invalid json',
'data: {"valid": "json", "but": "no message content"}',
'data: null',
'not-data-line: {"ignored": true}',
].join('\n');
mockServer.api.post.mockResolvedValueOnce({ data: sseResponse });
const result = await handlePromptAgent(mockServer, {
agent_id: 'agent-123',
message: 'Hello',
});
const data = expectValidToolResponse(result);
// Should handle parse errors gracefully
expect(data.response).toContain('{invalid json');
});
});
describe('Edge Cases', () => {
it('should handle very long messages', async () => {
const agentData = fixtures.agent.basic;
mockServer.api.get.mockResolvedValueOnce({ data: agentData });
const longMessage = 'A'.repeat(10000);
const sseResponse =
'data: {"message_type": "assistant_message", "content": "Received long message"}\n';
mockServer.api.post.mockResolvedValueOnce({ data: sseResponse });
const result = await handlePromptAgent(mockServer, {
agent_id: 'agent-123',
message: longMessage,
});
expect(mockServer.api.post).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
messages: [{ role: 'user', content: longMessage }],
}),
expect.any(Object),
);
const data = expectValidToolResponse(result);
expect(data.message).toBe(longMessage);
expect(data.response).toBe('Received long message');
});
it('should handle unicode in messages', async () => {
const agentData = fixtures.agent.basic;
mockServer.api.get.mockResolvedValueOnce({ data: agentData });
const unicodeMessage = 'Hello 🤖 世界 🌍';
const sseResponse =
'data: {"message_type": "assistant_message", "content": "Hello! I see emojis and Chinese characters: 🤖 世界 🌍"}\n';
mockServer.api.post.mockResolvedValueOnce({ data: sseResponse });
const result = await handlePromptAgent(mockServer, {
agent_id: 'agent-123',
message: unicodeMessage,
});
const data = expectValidToolResponse(result);
expect(data.message).toBe(unicodeMessage);
expect(data.response).toContain('🤖 世界 🌍');
});
it('should handle multiple data lines without proper message', async () => {
const agentData = fixtures.agent.basic;
mockServer.api.get.mockResolvedValueOnce({ data: agentData });
const sseResponse = [
'data: {"type": "start"}',
'data: {"type": "thinking"}',
'data: {"type": "end"}',
'',
].join('\n');
mockServer.api.post.mockResolvedValueOnce({ data: sseResponse });
const result = await handlePromptAgent(mockServer, {
agent_id: 'agent-123',
message: 'Hello',
});
const data = expectValidToolResponse(result);
expect(data.response).toBe("Received response but couldn't extract message content");
});
it('should preserve agent name from initial lookup', async () => {
const agentData = {
...fixtures.agent.basic,
name: 'Custom Agent Name 🤖',
};
mockServer.api.get.mockResolvedValueOnce({ data: agentData });
const sseResponse =
'data: {"message_type": "assistant_message", "content": "Hello!"}\n';
mockServer.api.post.mockResolvedValueOnce({ data: sseResponse });
const result = await handlePromptAgent(mockServer, {
agent_id: 'agent-123',
message: 'Hi',
});
const data = expectValidToolResponse(result);
expect(data.agent_name).toBe('Custom Agent Name 🤖');
});
it('should handle SSE with extra whitespace', async () => {
const agentData = fixtures.agent.basic;
mockServer.api.get.mockResolvedValueOnce({ data: agentData });
const sseResponse = [
' data: {"message_type": "assistant_message", "content": "Response with spaces"} ',
'',
' data: [DONE] ',
].join('\n');
mockServer.api.post.mockResolvedValueOnce({ data: sseResponse });
const result = await handlePromptAgent(mockServer, {
agent_id: 'agent-123',
message: 'Test',
});
const data = expectValidToolResponse(result);
// The parsing fails due to leading spaces, so raw lines are added
expect(data.response).toContain('Response with spaces');
});
});
});