feat: update Makelore modules and conversations
This commit is contained in:
174
tests/unit/opencode-session-state.test.ts
Normal file
174
tests/unit/opencode-session-state.test.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
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');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user