import { describe, it, expect } from 'vitest'; import { ChatGPTAdapter } from '../../src/harvest/chatgpt-adapter.js'; import { ClaudeAdapter } from '../../src/harvest/claude-adapter.js'; import { GeminiAdapter } from '../../src/harvest/gemini-adapter.js'; import { UniversalAdapter } from '../../src/harvest/universal-adapter.js'; /** * W4.4 — caption-aware harvest adapters (plan component #7). Exact production * counterpart of the W3.3 benchmark data-parity fix: image content the * exports ALREADY carry as text was silently dropped by every adapter * (measured cost on LoCoMo: 4.5pp single-hop). No vision model — these * tests assert the text-bearing fields now surface in parsed item content. */ describe('W4.4 — caption parity across adapters', () => { it('ChatGPT: DALL-E image parts surface their generation prompt', () => { const fixture = [{ title: 'Image chat', create_time: 1700000000, mapping: { n1: { message: { author: { role: 'user' }, content: { parts: ['Here is my painting:'] }, create_time: 1700000001, }, }, n2: { message: { author: { role: 'assistant' }, content: { parts: [ { content_type: 'image_asset_pointer', asset_pointer: 'file://x', metadata: { dalle: { prompt: 'a painting of a sunset with a pink sky' } } }, 'Here you go!', ], }, create_time: 1700000002, }, }, }, }]; const items = new ChatGPTAdapter().parse(fixture); expect(items).toHaveLength(1); expect(items[0].content).toContain('[Shared image: a painting of a sunset with a pink sky]'); expect(items[0].content).toContain('Here you go!'); }); it('ChatGPT: message-level attachments surface as presence signals', () => { const fixture = [{ title: 'Attachment chat', create_time: 1700000000, mapping: { n1: { message: { author: { role: 'user' }, content: { parts: ['Review this please'] }, metadata: { attachments: [{ name: 'Q3-roadmap.pdf' }] }, create_time: 1700000001, }, }, }, }]; const items = new ChatGPTAdapter().parse(fixture); expect(items[0].content).toContain('[Attached: Q3-roadmap.pdf]'); }); it('Claude: attachment extracted_content surfaces (text already extracted)', () => { const fixture = [{ uuid: 'c1', name: 'Doc chat', created_at: '2026-05-01T10:00:00Z', chat_messages: [ { sender: 'human', text: 'Summarize the attached notes', created_at: '2026-05-01T10:00:00Z', attachments: [{ file_name: 'meeting-notes.txt', extracted_content: 'Launch locked for June 20. Marketing owns the landing page.' }], }, ], }]; const items = new ClaudeAdapter().parse(fixture); expect(items).toHaveLength(1); expect(items[0].content).toContain('[Attached: meeting-notes.txt] Launch locked for June 20'); }); it('Claude: an attachment-only message survives (was dropped as empty)', () => { const fixture = [{ uuid: 'c2', name: 'Image only', created_at: '2026-05-01T10:00:00Z', chat_messages: [ { sender: 'human', text: '', files: [{ file_name: 'whiteboard.png' }] }, { sender: 'assistant', text: 'Nice whiteboard sketch!' }, ], }]; const items = new ClaudeAdapter().parse(fixture); expect(items[0].content).toContain('[Shared file: whiteboard.png]'); }); it('Gemini: fileData/inlineData parts surface as text signals', () => { const fixture = { conversations: [{ id: 'g1', title: 'Media chat', create_time: '2026-05-01T10:00:00Z', messages: [ { role: 'user', parts: [{ text: 'Look at this:' }, { inlineData: { mimeType: 'image/png', data: 'AAAA' } }] }, { role: 'model', parts: [{ fileData: { fileUri: 'gs://bucket/diagram.svg', mimeType: 'image/svg+xml' } }, { text: 'Interesting diagram.' }] }, ], }], }; const items = new GeminiAdapter().parse(fixture); expect(items.length).toBeGreaterThan(0); const all = items.map(i => i.content).join('\n'); expect(all).toContain('[Shared media: image/png]'); expect(all).toContain('[Shared file: gs://bucket/diagram.svg]'); }); it('Universal: caption/alt/description fields on object blocks surface', () => { const fixture = { conversations: [{ id: 'u1', title: 'Generic chat', messages: [ { role: 'user', content: [{ type: 'image', caption: 'a sunset with a palm tree' }, { type: 'text', text: 'What do you think?' }] }, ], }], }; const items = new UniversalAdapter().parse(fixture); expect(items.length).toBeGreaterThan(0); expect(items[0].content).toContain('[Shared image: a sunset with a palm tree]'); }); });