// src/messages/__tests__/dedup.test.ts import { SystemMessage, HumanMessage, AIMessage, ToolMessage, } from '@langchain/core/messages'; import type { BaseMessage } from '@langchain/core/messages'; import { deduplicateSystemMessages } from '../dedup'; describe('deduplicateSystemMessages', () => { it('returns empty array unchanged', () => { const result = deduplicateSystemMessages([]); expect(result.messages).toEqual([]); expect(result.removedCount).toBe(0); }); it('returns single message unchanged', () => { const msgs = [new SystemMessage('Hello')]; const result = deduplicateSystemMessages(msgs); expect(result.messages).toHaveLength(1); expect(result.removedCount).toBe(0); }); it('preserves conversation with no duplicates', () => { const msgs: BaseMessage[] = [ new SystemMessage('You are helpful'), new HumanMessage('Hello'), new AIMessage('Hi there'), new SystemMessage('Summary of prior context'), new HumanMessage('What about X?'), ]; const result = deduplicateSystemMessages(msgs); expect(result.messages).toHaveLength(5); expect(result.removedCount).toBe(0); }); it('removes duplicate system messages', () => { const msgs: BaseMessage[] = [ new SystemMessage('You are helpful'), new SystemMessage('[Conversation Summary]\nUser discussed X'), new HumanMessage('Hello'), new AIMessage('Hi'), new SystemMessage('[Conversation Summary]\nUser discussed X'), // duplicate new HumanMessage('What?'), ]; const result = deduplicateSystemMessages(msgs); expect(result.messages).toHaveLength(5); expect(result.removedCount).toBe(1); // Verify the duplicate at index 4 was removed expect(result.messages.map((m) => m.getType())).toEqual([ 'system', 'system', 'human', 'ai', 'human', ]); }); it('keeps the first occurrence of duplicate system messages', () => { const duplicateContent = 'Context was compressed. Summary follows.'; const msgs: BaseMessage[] = [ new SystemMessage('Main prompt'), new SystemMessage(duplicateContent), // first occurrence new HumanMessage('Q1'), new AIMessage('A1'), new SystemMessage(duplicateContent), // duplicate new HumanMessage('Q2'), new AIMessage('A2'), new SystemMessage(duplicateContent), // another duplicate ]; const result = deduplicateSystemMessages(msgs); expect(result.messages).toHaveLength(6); expect(result.removedCount).toBe(2); }); it('never removes non-system messages', () => { const msgs: BaseMessage[] = [ new SystemMessage('Prompt'), new HumanMessage('Same content'), new AIMessage('Same content'), new HumanMessage('Same content'), // duplicate content but not system new AIMessage('Same content'), // duplicate content but not system ]; const result = deduplicateSystemMessages(msgs); expect(result.messages).toHaveLength(5); expect(result.removedCount).toBe(0); }); it('always preserves the first system message (main prompt)', () => { const msgs: BaseMessage[] = [ new SystemMessage('Main system prompt'), new HumanMessage('Hello'), new SystemMessage('Main system prompt'), // duplicate of first ]; const result = deduplicateSystemMessages(msgs); expect(result.messages).toHaveLength(2); expect(result.removedCount).toBe(1); // First system message is preserved expect(result.messages[0].content as string).toBe('Main system prompt'); }); it('handles tool messages correctly (never deduped)', () => { const msgs: BaseMessage[] = [ new SystemMessage('Prompt'), new HumanMessage('Q'), new AIMessage({ content: 'Using tool', tool_calls: [{ id: 'tc1', name: 'test', args: {} }], }), new ToolMessage({ content: 'result', tool_call_id: 'tc1' }), new SystemMessage('Post-prune note'), new AIMessage({ content: 'Using tool', tool_calls: [{ id: 'tc2', name: 'test', args: {} }], }), new ToolMessage({ content: 'result', tool_call_id: 'tc2' }), new SystemMessage('Post-prune note'), // duplicate system msg ]; const result = deduplicateSystemMessages(msgs); expect(result.removedCount).toBe(1); // Tool messages are all preserved const toolMsgs = result.messages.filter((m) => m.getType() === 'tool'); expect(toolMsgs).toHaveLength(2); }); it('does not mutate input array', () => { const msgs: BaseMessage[] = [ new SystemMessage('Prompt'), new SystemMessage('Dup'), new SystemMessage('Dup'), ]; const originalLength = msgs.length; deduplicateSystemMessages(msgs); expect(msgs.length).toBe(originalLength); }); it('handles mixed duplicate patterns', () => { const msgs: BaseMessage[] = [ new SystemMessage('A'), new SystemMessage('B'), new SystemMessage('A'), // dup of first new HumanMessage('Q'), new SystemMessage('B'), // dup new SystemMessage('C'), new SystemMessage('C'), // dup ]; const result = deduplicateSystemMessages(msgs); expect(result.removedCount).toBe(3); expect(result.messages).toHaveLength(4); expect( result.messages.map((m) => { if (typeof m.content === 'string') return m.content; return ''; }) ).toEqual(['A', 'B', 'Q', 'C']); }); });