This commit is contained in:
133
packages/hive-mind-core/tests/harvest/caption-parity.test.ts
Normal file
133
packages/hive-mind-core/tests/harvest/caption-parity.test.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
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]');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user