import assert from 'node:assert/strict' import { applyAssistantEvent, getMessageParts } from '../src/shared/message-parts' import type { ChatEvent, ChatMessage, ToolCallRecord } from '../src/shared/types' function tool(overrides: Partial = {}): ToolCallRecord { return { id: 'tool-1', name: 'read_file', args: { path: 'notes.txt' }, argsRaw: '{"path":"notes.txt"}', status: 'running', ...overrides } } function reduce(message: ChatMessage, event: ChatEvent): ChatMessage { return applyAssistantEvent(message, event) } function testOrderedPartsAndImmutableUpdates(): void { const initial: ChatMessage = { id: 'assistant-1', role: 'assistant', content: '', tools: [], parts: [], createdAt: 1, status: 'streaming' } const afterFirstText = reduce(initial, { runId: 'run-1', type: 'text', delta: 'before' }) const afterToolCall = reduce(afterFirstText, { runId: 'run-1', type: 'tool_call', call: tool() }) const afterSecondText = reduce(afterToolCall, { runId: 'run-1', type: 'text', delta: 'after' }) const afterToolResult = reduce(afterSecondText, { runId: 'run-1', type: 'tool_result', call: tool({ status: 'ok', output: 'file contents', summary: 'file contents', durationMs: 12 }) }) const final = reduce(afterToolResult, { runId: 'run-1', type: 'done', turnDurationMs: 22000, content: 'beforeafter', tools: [tool({ status: 'ok', output: 'file contents', summary: 'file contents', durationMs: 12 })] }) assert.deepEqual(final.parts, [ { type: 'text', text: 'before' }, { type: 'tool', toolId: 'tool-1' }, { type: 'text', text: 'after' } ]) assert.equal(final.content, 'beforeafter') assert.equal(final.status, 'done') assert.equal(final.turnDurationMs, 22000, 'terminal duration survives the shared live/persistence projection') assert.equal(final.tools?.length, 1, 'tool updates must not create duplicate tool records') assert.equal(final.parts?.filter((part) => part.type === 'tool' && part.toolId === 'tool-1').length, 1, 'tool updates must not create duplicate ordered references') assert.equal(final.tools?.[0].status, 'ok') assert.equal(final.tools?.[0].output, 'file contents') assert.equal(initial.content, '', 'reducing events must not mutate the original message') assert.deepEqual(initial.parts, [], 'reducing events must not mutate the original parts array') assert.deepEqual(afterFirstText.parts, [{ type: 'text', text: 'before' }], 'later text and tool updates must not mutate prior message snapshots') assert.equal(afterFirstText.content, 'before') assert.deepEqual(afterToolResult.parts, [ { type: 'text', text: 'before' }, { type: 'tool', toolId: 'tool-1' }, { type: 'text', text: 'after' } ]) } function testLegacyFallback(): void { const legacy: ChatMessage = { id: 'legacy-1', role: 'assistant', content: 'legacy response', tools: [tool({ id: 'legacy-tool', status: 'ok' })], createdAt: 2 } assert.deepEqual(getMessageParts(legacy), [ { type: 'text', text: 'legacy response' }, { type: 'tool', toolId: 'legacy-tool' } ]) } function testTerminalToolFinalization(): void { const initial: ChatMessage = { id: 'assistant-terminal', role: 'assistant', content: 'partial answer', tools: [tool({ output: 'retained output' })], parts: [ { type: 'text', text: 'partial answer' }, { type: 'tool', toolId: 'tool-1' } ], createdAt: 3, status: 'streaming' } const cancelled = reduce(initial, { runId: 'run-cancel', type: 'cancelled' }) assert.equal(cancelled.status, 'cancelled') assert.equal(cancelled.tools?.[0].status, 'cancelled') assert.equal(cancelled.tools?.[0].output, 'retained output') const errored = reduce(initial, { runId: 'run-error', type: 'error', message: 'connection lost' }) assert.equal(errored.status, 'error') assert.equal(errored.error, 'connection lost') assert.equal(errored.tools?.[0].status, 'error') assert.equal(errored.tools?.[0].output, 'retained output') assert.equal(initial.status, 'streaming', 'terminal updates must leave the prior message unchanged') assert.equal(initial.tools?.[0].status, 'running', 'terminal updates must leave prior tool state unchanged') assert.equal(initial.tools?.[0].output, 'retained output') } testOrderedPartsAndImmutableUpdates() testLegacyFallback() testTerminalToolFinalization() console.log('message-order-regression: all checks passed')