import test from 'node:test'; import assert from 'node:assert/strict'; import { mergeConversationSnapshot, mergeSessionMessagesAfterFinish, } from './chat-finish-sync.mjs'; const msg = (id, role, text) => ({ id, role, metadata: { userVisible: true }, content: [{ type: 'text', text }], }); test('mergeConversationSnapshot keeps local-only messages when server snapshot is shorter', () => { const local = [ msg('u1', 'user', '生成一个页面'), msg('a1', 'assistant', '正在写入 public/demo.html…'), msg('a2', 'assistant', '页面已生成'), ]; const server = [msg('u1', 'user', '生成一个页面')]; const merged = mergeConversationSnapshot(local, server); assert.equal(merged.length, 3); assert.deepEqual( merged.map((message) => message.id), ['u1', 'a1', 'a2'], ); }); test('mergeConversationSnapshot prefers server content for shared message ids', () => { const local = [msg('u1', 'user', '旧文案')]; const server = [msg('u1', 'user', '新文案')]; const merged = mergeConversationSnapshot(local, server); assert.equal(merged.length, 1); assert.equal(merged[0].content[0].text, '新文案'); }); test('mergeConversationSnapshot returns current when incoming is empty', () => { const local = [msg('u1', 'user', 'hello')]; assert.deepEqual(mergeConversationSnapshot(local, []), local); }); test('mergeSessionMessagesAfterFinish matches Finish sync merge semantics', () => { const local = [ msg('u1', 'user', '根据图片做主题页'), msg('a1', 'assistant', '已完成'), ]; const server = [msg('u1', 'user', '根据图片做主题页')]; assert.deepEqual( mergeSessionMessagesAfterFinish(local, server).map((message) => message.id), ['u1', 'a1'], ); }); test('mergeConversationSnapshot keeps an omitted middle message between its anchors', () => { const local = [ msg('u1', 'user', '第一轮'), msg('a1', 'assistant', '第一轮回复'), msg('u2', 'user', '第二轮'), msg('a2', 'assistant', '第二轮回复'), ]; const server = [local[0], local[1], local[3]]; assert.deepEqual( mergeConversationSnapshot(local, server).map((message) => message.id), ['u1', 'a1', 'u2', 'a2'], ); }); test('mergeConversationSnapshot keeps local messages before the first and after the last server anchor', () => { const local = [ msg('u0', 'user', '本地前置'), msg('u1', 'user', '服务端消息'), msg('a1', 'assistant', '服务端回复'), msg('a2', 'assistant', '本地尾部'), ]; const server = [local[1], local[2]]; assert.deepEqual( mergeConversationSnapshot(local, server).map((message) => message.id), ['u0', 'u1', 'a1', 'a2'], ); }); test('mergeConversationSnapshot keeps local-only messages at their streamed position', () => { const local = [ msg('u1', 'user', '生成页面'), msg('a1', 'assistant', '开始分析'), msg('a2', 'assistant', '调用工具'), msg('a3', 'assistant', '页面完成'), ]; const server = [ msg('u1', 'user', '生成页面'), msg('a3', 'assistant', '页面完成(服务端)'), ]; const merged = mergeConversationSnapshot(local, server); assert.deepEqual(merged.map((message) => message.id), ['u1', 'a1', 'a2', 'a3']); assert.equal(merged[3].content[0].text, '页面完成(服务端)'); }); test('mergeConversationSnapshot preserves multiple local messages before the next server anchor', () => { const local = [ msg('u1', 'user', '任务'), msg('a1', 'assistant', '步骤一'), msg('a2', 'assistant', '步骤二'), msg('a3', 'assistant', '完成'), ]; const server = [msg('u1', 'user', '任务'), msg('a3', 'assistant', '完成')]; assert.deepEqual( mergeConversationSnapshot(local, server).map((message) => message.id), ['u1', 'a1', 'a2', 'a3'], ); });