import { HumanMessage, AIMessage, SystemMessage, } from '@langchain/core/messages'; import { MULTI_DOCUMENT_THRESHOLD } from '@/common/constants'; import { detectDocuments, shouldInjectMultiDocHint, buildMultiDocHintContent, buildPostPruneNote, hasTaskTool, } from './contextPressure'; // ============================================================================ // detectDocuments // ============================================================================ describe('detectDocuments', () => { it('returns zero when no documents are present', () => { const messages = [ new HumanMessage('Hello, what can you do?'), new AIMessage('I can help with many things.'), ]; const result = detectDocuments(messages); expect(result.count).toBe(0); expect(result.names).toEqual([]); }); it('detects documents from # "filename" pattern', () => { const messages = [ new HumanMessage( 'Attached document(s):\n# "report.pdf"\nContent here\n# "analysis.docx"\nMore content' ), ]; const result = detectDocuments(messages); expect(result.count).toBe(2); expect(result.names).toEqual(['report.pdf', 'analysis.docx']); }); it('detects documents from "user has attached" pattern', () => { const messages = [ new HumanMessage( 'The user has attached: **budget.xlsx, summary.pdf, notes.txt**' ), ]; const result = detectDocuments(messages); expect(result.count).toBe(3); expect(result.names).toEqual(['budget.xlsx', 'summary.pdf', 'notes.txt']); }); it('deduplicates documents across messages', () => { const messages = [ new HumanMessage('# "report.pdf"\nContent'), new HumanMessage('# "report.pdf"\nSame doc again'), new HumanMessage('# "other.pdf"\nDifferent doc'), ]; const result = detectDocuments(messages); expect(result.count).toBe(2); expect(result.names).toEqual(['report.pdf', 'other.pdf']); }); it('deduplicates across both patterns', () => { const messages = [ new HumanMessage('# "report.pdf"\nContent'), new HumanMessage('The user has attached: **report.pdf, budget.xlsx**'), ]; const result = detectDocuments(messages); expect(result.count).toBe(2); expect(result.names).toEqual(['report.pdf', 'budget.xlsx']); }); it('handles array content (multi-part messages)', () => { const messages = [ new HumanMessage({ content: [ { type: 'text', text: '# "doc1.pdf"\nPart 1' }, { type: 'text', text: '# "doc2.pdf"\nPart 2' }, ], }), ]; const result = detectDocuments(messages); expect(result.count).toBe(2); expect(result.names).toEqual(['doc1.pdf', 'doc2.pdf']); }); it('ignores non-human messages with document patterns', () => { // detectDocuments scans ALL messages — AI messages with doc patterns // should still be detected (they may contain tool results with docs) const messages = [new AIMessage('Found: # "results.csv"\nData here')]; const result = detectDocuments(messages); expect(result.count).toBe(1); }); it('handles empty messages array', () => { const result = detectDocuments([]); expect(result.count).toBe(0); expect(result.names).toEqual([]); }); it('handles case-insensitive "user has attached" pattern', () => { const messages = [ new HumanMessage('The User Has Attached: **file1.pdf, file2.pdf**'), ]; const result = detectDocuments(messages); expect(result.count).toBe(2); }); }); // ============================================================================ // shouldInjectMultiDocHint // ============================================================================ describe('shouldInjectMultiDocHint', () => { it('returns true when document count meets threshold and no AI response', () => { expect(shouldInjectMultiDocHint(MULTI_DOCUMENT_THRESHOLD, false)).toBe( true ); }); it('returns true when document count exceeds threshold', () => { expect(shouldInjectMultiDocHint(MULTI_DOCUMENT_THRESHOLD + 5, false)).toBe( true ); }); it('returns false when document count is below threshold', () => { expect(shouldInjectMultiDocHint(MULTI_DOCUMENT_THRESHOLD - 1, false)).toBe( false ); }); it('returns false when AI has already responded', () => { expect(shouldInjectMultiDocHint(MULTI_DOCUMENT_THRESHOLD, true)).toBe( false ); }); it('returns false with zero documents', () => { expect(shouldInjectMultiDocHint(0, false)).toBe(false); }); it('uses MULTI_DOCUMENT_THRESHOLD constant (currently 3)', () => { expect(MULTI_DOCUMENT_THRESHOLD).toBe(3); expect(shouldInjectMultiDocHint(2, false)).toBe(false); expect(shouldInjectMultiDocHint(3, false)).toBe(true); }); }); // ============================================================================ // buildMultiDocHintContent // ============================================================================ describe('buildMultiDocHintContent', () => { it('includes document count in header', () => { const content = buildMultiDocHintContent(4, [ 'a.pdf', 'b.pdf', 'c.pdf', 'd.pdf', ]); expect(content).toContain('4 documents detected'); }); it('lists all document names', () => { const names = ['report.pdf', 'budget.xlsx', 'summary.docx']; const content = buildMultiDocHintContent(3, names); expect(content).toContain('report.pdf'); expect(content).toContain('budget.xlsx'); expect(content).toContain('summary.docx'); }); it('mentions the task tool for delegation', () => { const content = buildMultiDocHintContent(3, ['a', 'b', 'c']); expect(content).toContain('"task" tool'); expect(content).toContain('sub-agent'); }); it('does NOT contain any token numbers or budget percentages', () => { const content = buildMultiDocHintContent(5, ['a', 'b', 'c', 'd', 'e']); expect(content).not.toMatch(/\d+%/); expect(content).not.toMatch(/\d+ of \d+ tokens/); expect(content).not.toMatch(/tokens remaining/); expect(content).not.toMatch(/BUDGET/i); }); }); // ============================================================================ // buildPostPruneNote // ============================================================================ describe('buildPostPruneNote', () => { it('returns null when no messages were discarded', () => { expect(buildPostPruneNote(0, true)).toBeNull(); expect(buildPostPruneNote(0, false)).toBeNull(); }); it('returns null for negative discard count', () => { expect(buildPostPruneNote(-5, true)).toBeNull(); }); it('returns note with summary reference when summary exists', () => { const note = buildPostPruneNote(10, true); expect(note).not.toBeNull(); expect(note).toContain('summarized above'); expect(note).toContain('"task" tool'); }); it('returns note without summary reference when no summary', () => { const note = buildPostPruneNote(10, false); expect(note).not.toBeNull(); expect(note).toContain('removed to maintain context'); expect(note).toContain('"task" tool'); }); it('does NOT contain any token numbers or budget percentages', () => { const noteWithSummary = buildPostPruneNote(20, true)!; const noteWithout = buildPostPruneNote(20, false)!; for (const note of [noteWithSummary, noteWithout]) { expect(note).not.toMatch(/\d+%/); expect(note).not.toMatch(/\d+ of \d+ tokens/); expect(note).not.toMatch(/tokens remaining/); expect(note).not.toMatch(/BUDGET/i); expect(note).not.toMatch(/CRITICAL/i); expect(note).not.toMatch(/WARNING/i); } }); }); // ============================================================================ // hasTaskTool // ============================================================================ describe('hasTaskTool', () => { it('returns true when task tool exists', () => { const tools = [{ name: 'search' }, { name: 'task' }, { name: 'code' }]; expect(hasTaskTool(tools)).toBe(true); }); it('returns false when task tool does not exist', () => { const tools = [{ name: 'search' }, { name: 'code' }]; expect(hasTaskTool(tools)).toBe(false); }); it('returns false for undefined tools', () => { expect(hasTaskTool(undefined)).toBe(false); }); it('returns false for empty tools array', () => { expect(hasTaskTool([])).toBe(false); }); it('handles tools without name property', () => { const tools = [{}, { name: 'task' }, 'not-an-object'] as unknown[]; expect(hasTaskTool(tools)).toBe(true); }); it('returns false when tools have no name property', () => { const tools = [{}, {}, 42] as unknown[]; expect(hasTaskTool(tools)).toBe(false); }); });