import { describe, expect, it } from 'vitest'; import { getOrderedSessionParts, hydrateOpenCodeSession, reduceOpenCodeEvent, } from '@/lib/opencode-session-state'; describe('OpenCode native session state', () => { it('hydrates native parts without dropping execution metadata', () => { const state = hydrateOpenCodeSession('ses_1', [{ info: { id: 'msg_1', role: 'assistant', sessionID: 'ses_1', time: { created: 1_700_000_000_000 }, }, parts: [ { id: 'text_1', type: 'text', text: 'Final answer' }, { id: 'reason_1', type: 'reasoning', text: 'Checking the repository' }, { id: 'tool_1', type: 'tool', tool: 'read', callID: 'call_1', state: { status: 'completed', input: { path: 'README.md' }, output: 'ok', time: { start: 1_700_000_000_010, end: 1_700_000_000_110 }, }, }, { id: 'step_1', type: 'step-finish', reason: 'stop', cost: 0.01 }, { id: 'future_1', type: 'future-native-part', detail: 'preserve me' }, ], }]); const parts = getOrderedSessionParts(state, 'msg_1'); expect(parts.map((part) => part.type)).toEqual([ 'text', 'reasoning', 'tool', 'step-finish', 'unknown', ]); expect(parts[2]).toEqual(expect.objectContaining({ tool: 'read', callID: 'call_1', state: expect.objectContaining({ status: 'completed' }), })); expect(parts.at(-1)).toEqual(expect.objectContaining({ rawType: 'future-native-part', detail: expect.stringContaining('preserve me'), })); }); it('applies full updates, deltas, and removals by stable part id', () => { const base = hydrateOpenCodeSession('ses_1', []); const updated = reduceOpenCodeEvent(base, { type: 'message.part.updated', payload: { sessionID: 'ses_1', messageID: 'msg_1', part: { id: 'part_1', type: 'text', text: 'Hello' }, }, }); const withDelta = reduceOpenCodeEvent(updated, { type: 'message.part.delta', payload: { sessionID: 'ses_1', messageID: 'msg_1', partID: 'part_1', field: 'text', delta: ' world', }, }); const duplicateDelta = reduceOpenCodeEvent(withDelta, { type: 'message.part.delta', payload: { sessionID: 'ses_1', messageID: 'msg_1', partID: 'part_1', field: 'text', delta: ' world', }, }); expect(getOrderedSessionParts(duplicateDelta, 'msg_1')[0]?.text).toBe('Hello world'); const removed = reduceOpenCodeEvent(duplicateDelta, { type: 'message.part.removed', payload: { sessionID: 'ses_1', messageID: 'msg_1', partID: 'part_1', }, }); expect(getOrderedSessionParts(removed, 'msg_1')).toEqual([]); }); it('ignores events from another session and tracks native lifecycle status', () => { const state = hydrateOpenCodeSession('ses_1', []); const untouched = reduceOpenCodeEvent(state, { type: 'message.part.delta', payload: { sessionID: 'ses_2', messageID: 'msg_2', partID: 'part_2', delta: 'wrong session', }, }); expect(untouched).toBe(state); const busy = reduceOpenCodeEvent(state, { type: 'session.status', payload: { sessionID: 'ses_1', status: { type: 'retry' } }, }); expect(busy?.status).toBe('retry'); const idle = reduceOpenCodeEvent(busy, { type: 'session.idle', payload: { sessionID: 'ses_1' }, }); expect(idle?.status).toBe('idle'); }); it('deduplicates replayed delta events by event identity without dropping equal real chunks', () => { let state = hydrateOpenCodeSession('ses_1', []); state = reduceOpenCodeEvent(state, { type: 'message.part.updated', payload: { sessionID: 'ses_1', messageID: 'msg_1', part: { id: 'part_1', type: 'text', text: '' }, }, }); state = reduceOpenCodeEvent(state, { type: 'message.part.delta', payload: { sessionID: 'ses_1', messageID: 'msg_1', partID: 'part_1', delta: 'x', eventID: 'evt_1', }, }); state = reduceOpenCodeEvent(state, { type: 'session.status', payload: { sessionID: 'ses_1', status: { type: 'busy' } }, }); state = reduceOpenCodeEvent(state, { type: 'message.part.delta', payload: { sessionID: 'ses_1', messageID: 'msg_1', partID: 'part_1', delta: 'x', eventID: 'evt_1', }, }); expect(getOrderedSessionParts(state, 'msg_1')[0]?.text).toBe('x'); const secondRealChunk = reduceOpenCodeEvent(state, { type: 'message.part.delta', payload: { sessionID: 'ses_1', messageID: 'msg_1', partID: 'part_1', delta: 'x', eventID: 'evt_2', }, }); expect(getOrderedSessionParts(secondRealChunk, 'msg_1')[0]?.text).toBe('xx'); }); });