import { describe, expect, it } from 'vitest'; import { ChatTimeHistoryMapper, ChatTimeTimelineBuilder, } from '../chat-time'; describe('chat-time timeline builder', () => { it('builds assistant thinking and message in the same assistant item', () => { const items: any[] = []; ChatTimeTimelineBuilder.appendUserMessage(items, { content: '你好', created: 1, id: 'user-1', }); ChatTimeTimelineBuilder.appendThinkingDelta(items, '先想一下', 2); ChatTimeTimelineBuilder.appendMessageDelta(items, '最终回答', 3); ChatTimeTimelineBuilder.finalize(items); expect(items).toHaveLength(2); expect(items[1]).toMatchObject({ content: '最终回答', role: 'assistant', }); expect(items[1].segments).toMatchObject([ { content: '先想一下', status: 'end', type: 'thinking' }, { content: '最终回答', type: 'text' }, ]); }); it('creates a new assistant item after tool result', () => { const items: any[] = []; ChatTimeTimelineBuilder.appendMessageDelta(items, '第一段回答', 1); ChatTimeTimelineBuilder.upsertToolCall(items, { name: 'search_docs', toolCallId: 'tool-1', value: '{"query":"java"}', }); ChatTimeTimelineBuilder.upsertToolResult(items, { result: '{"hits":1}', toolCallId: 'tool-1', }); ChatTimeTimelineBuilder.appendThinkingDelta(items, '继续思考', 2); ChatTimeTimelineBuilder.appendMessageDelta(items, '第二段回答', 3); ChatTimeTimelineBuilder.finalize(items); expect(items).toHaveLength(3); expect(items[0]).toMatchObject({ content: '第一段回答', role: 'assistant' }); expect(items[1]).toMatchObject({ name: 'search_docs', result: '{"hits":1}', role: 'tool', status: 'TOOL_RESULT', toolCallId: 'tool-1', }); expect(items[2]).toMatchObject({ content: '第二段回答', role: 'assistant' }); expect(items[2].segments).toMatchObject([ { content: '继续思考', status: 'end', type: 'thinking' }, { content: '第二段回答', type: 'text' }, ]); }); }); describe('chat-time history mapper', () => { it('expands structured messageChain into assistant and tool timeline items', () => { const items = ChatTimeHistoryMapper.fromHistoryRecords([ { contentPayload: { messageChain: [ { content: '先回答一点', reasoningContent: '先思考', role: 'assistant', toolCalls: [ { arguments: '{"query":"java"}', id: 'tool-1', name: 'search_docs', }, ], }, { content: '{"hits":1}', role: 'tool', toolCallId: 'tool-1', }, { content: '最后总结', reasoningContent: '继续思考', role: 'assistant', }, ], }, created: 100, id: 'assistant-record', senderRole: 'assistant', }, ]); expect(items).toHaveLength(3); expect(items[0]).toMatchObject({ content: '先回答一点', role: 'assistant', }); expect(items[1]).toMatchObject({ arguments: '{"query":"java"}', name: 'search_docs', result: '{"hits":1}', role: 'tool', toolCallId: 'tool-1', }); expect(items[2]).toMatchObject({ content: '最后总结', role: 'assistant', }); expect(items[0]?.id).not.toBe(items[2]?.id); }); it('falls back to legacy chains when messageChain is unavailable', () => { const items = ChatTimeHistoryMapper.fromLegacyMessages([ { chains: [ { reasoning_content: '旧思考', thinkingStatus: 'end', }, { id: 'tool-2', name: 'legacy_tool', result: '{"ok":true}', status: 'TOOL_RESULT', }, ], content: '旧回答', created: '2026-05-11 10:00:00', id: 'legacy-1', role: 'assistant', }, ]); expect(items).toHaveLength(2); expect(items[0]).toMatchObject({ content: '旧回答', role: 'assistant', }); expect(items[0].segments).toMatchObject([ { content: '旧思考', status: 'end', type: 'thinking' }, { content: '旧回答', type: 'text' }, ]); expect(items[1]).toMatchObject({ name: 'legacy_tool', result: '{"ok":true}', role: 'tool', toolCallId: 'tool-2', }); }); });