Files
makelore/tests/unit/opencode-session-state.test.ts

436 lines
14 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
completeCompaction,
createPendingCompaction,
getOrderedSessionParts,
getOrderedSessionCompactions,
hydrateOpenCodeSession,
reduceOpenCodeEvent,
removeCompaction,
} 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');
});
it('hydrates historical compaction Parts as completed timeline events in transcript order', () => {
const state = hydrateOpenCodeSession('ses_1', [{
info: { id: 'msg_1', role: 'assistant', sessionID: 'ses_1' },
parts: [
{ id: 'compact_1', type: 'compaction', auto: false },
{ id: 'compact_2', type: 'compaction', auto: true },
],
}]);
expect(getOrderedSessionCompactions(state)).toEqual([
expect.objectContaining({
id: 'compaction:ses_1:part:compact_1',
source: 'manual',
status: 'completed',
order: 0,
anchorMessageID: 'msg_1',
anchorPartID: 'compact_1',
nativePartID: 'compact_1',
}),
expect.objectContaining({
source: 'automatic',
status: 'completed',
order: 1,
nativePartID: 'compact_2',
}),
]);
});
it('allows only the latest compaction in an active snapshot to remain running', () => {
const state = hydrateOpenCodeSession('ses_1', [{
info: { id: 'msg_1', role: 'assistant', sessionID: 'ses_1' },
parts: [
{ id: 'compact_1', type: 'compaction', auto: true },
{ id: 'compact_2', type: 'compaction', auto: true },
],
}], 'busy');
expect(getOrderedSessionCompactions(state).map((event) => event.status))
.toEqual(['completed', 'running']);
});
it('does not downgrade a completed compaction during busy history hydration', () => {
const snapshot = [{
info: { id: 'msg_1', role: 'assistant', sessionID: 'ses_1' },
parts: [{ id: 'compact_1', type: 'compaction', auto: true }],
}];
const running = hydrateOpenCodeSession('ses_1', snapshot, 'busy');
const completed = reduceOpenCodeEvent(running, {
type: 'session.compacted',
payload: { sessionID: 'ses_1', eventID: 'evt_compacted_1' },
});
const rehydrated = hydrateOpenCodeSession('ses_1', snapshot, 'busy', completed);
expect(getOrderedSessionCompactions(rehydrated)).toEqual([
expect.objectContaining({
nativePartID: 'compact_1',
status: 'completed',
}),
]);
});
it('merges a manual pending event with its native Part without changing the UI id', () => {
const empty = hydrateOpenCodeSession('ses_1', [], 'busy');
const pending = createPendingCompaction(empty, {
id: 'ui_compact_1',
source: 'manual',
runID: 'run_1',
generation: 3,
startedAt: 1_700_000_000_000,
});
const withNativePart = reduceOpenCodeEvent(pending, {
type: 'message.part.updated',
payload: {
sessionID: 'ses_1',
eventID: 'evt_part_1',
messageID: 'msg_1',
part: {
id: 'native_compact_1',
type: 'compaction',
auto: false,
runID: 'run_1',
generation: 3,
},
},
});
expect(getOrderedSessionCompactions(withNativePart)).toEqual([
expect.objectContaining({
id: 'ui_compact_1',
nativePartID: 'native_compact_1',
nativeEventID: 'evt_part_1',
anchorMessageID: 'msg_1',
status: 'running',
}),
]);
const replayed = reduceOpenCodeEvent(withNativePart, {
type: 'message.part.updated',
payload: {
sessionID: 'ses_1',
eventID: 'evt_part_2',
messageID: 'msg_1',
part: { id: 'native_compact_1', type: 'compaction', auto: false },
},
});
expect(getOrderedSessionCompactions(replayed)).toHaveLength(1);
expect(getOrderedSessionCompactions(replayed)[0]?.id).toBe('ui_compact_1');
});
it('preserves unmatched running pending events across active hydration only', () => {
const current = createPendingCompaction(
hydrateOpenCodeSession('ses_1', [], 'busy'),
{ id: 'ui_pending', source: 'manual', runID: 'run_1', generation: 1 },
);
const active = hydrateOpenCodeSession('ses_1', [], 'busy', current);
expect(getOrderedSessionCompactions(active).map((event) => event.id)).toEqual(['ui_pending']);
const historical = hydrateOpenCodeSession('ses_1', [], 'idle', current);
expect(getOrderedSessionCompactions(historical)).toEqual([]);
});
it('merges an active snapshot Part into the current manual pending UI identity', () => {
const current = createPendingCompaction(
hydrateOpenCodeSession('ses_1', [], 'busy'),
{ id: 'ui_pending', source: 'manual', runID: 'run_1', generation: 1 },
);
const hydrated = hydrateOpenCodeSession('ses_1', [{
info: { id: 'msg_1', role: 'assistant', sessionID: 'ses_1' },
parts: [{ id: 'native_compact_1', type: 'compaction', auto: false }],
}], 'busy', current);
expect(getOrderedSessionCompactions(hydrated)).toEqual([
expect.objectContaining({
id: 'ui_pending',
nativePartID: 'native_compact_1',
status: 'running',
runID: 'run_1',
generation: 1,
}),
]);
});
it('closes an older running native compaction when a later one arrives', () => {
let state = hydrateOpenCodeSession('ses_1', [], 'busy');
for (const [messageID, partID] of [['msg_1', 'compact_1'], ['msg_2', 'compact_2']]) {
state = reduceOpenCodeEvent(state, {
type: 'message.part.updated',
payload: {
sessionID: 'ses_1',
messageID,
part: { id: partID, type: 'compaction', auto: true },
},
})!;
}
expect(getOrderedSessionCompactions(state).map((event) => event.status))
.toEqual(['completed', 'running']);
});
it('completes and removes only the compaction matching all supplied correlation keys', () => {
let state = createPendingCompaction(
hydrateOpenCodeSession('ses_1', [], 'busy'),
{ id: 'ui_1', source: 'manual', runID: 'run_1', generation: 1 },
);
state = createPendingCompaction(state, {
id: 'ui_2',
source: 'manual',
runID: 'run_2',
generation: 2,
nativePartID: 'native_2',
});
const wrongGeneration = completeCompaction(
state,
{ runID: 'run_2', generation: 1, nativePartID: 'native_2' },
);
expect(wrongGeneration).toBe(state);
state = completeCompaction(
state,
{ runID: 'run_2', generation: 2, nativePartID: 'native_2' },
{ nativeEventID: 'evt_complete_2', completedAt: 1_700_000_000_100 },
);
expect(state.compactionsById.ui_2).toEqual(expect.objectContaining({
status: 'completed',
nativeEventID: 'evt_complete_2',
completedAt: 1_700_000_000_100,
}));
expect(state.compactionsById.ui_1?.status).toBe('running');
const untouched = removeCompaction(state, { runID: 'run_1', generation: 2 });
expect(untouched).toBe(state);
state = removeCompaction(state, { runID: 'run_1', generation: 1 });
expect(getOrderedSessionCompactions(state).map((event) => event.id)).toEqual(['ui_2']);
});
it('completes compaction on session.compacted without marking the transcript idle', () => {
const busy = createPendingCompaction(
hydrateOpenCodeSession('ses_1', [], 'busy'),
{ id: 'ui_compact_1', source: 'automatic', runID: 'run_1', generation: 4 },
);
const compacted = reduceOpenCodeEvent(busy, {
type: 'session.compacted',
payload: {
sessionID: 'ses_1',
eventID: 'evt_complete_1',
runID: 'run_1',
generation: 4,
},
});
expect(compacted?.status).toBe('busy');
expect(compacted?.compactionsById.ui_compact_1).toEqual(expect.objectContaining({
status: 'completed',
nativeEventID: 'evt_complete_1',
}));
const idle = reduceOpenCodeEvent(compacted, {
type: 'session.idle',
payload: { sessionID: 'ses_1' },
});
expect(idle?.status).toBe('idle');
});
it('uses session.idle as a successful completion fallback', () => {
const busy = createPendingCompaction(
hydrateOpenCodeSession('ses_1', [], 'busy'),
{ id: 'ui_compact_idle', source: 'manual', runID: 'run_idle', generation: 5 },
);
const idle = reduceOpenCodeEvent(busy, {
type: 'session.idle',
payload: { sessionID: 'ses_1' },
});
expect(idle?.status).toBe('idle');
expect(idle?.compactionsById.ui_compact_idle?.status).toBe('completed');
});
it('removes unfinished compactions on a fatal session error', () => {
const busy = createPendingCompaction(
hydrateOpenCodeSession('ses_1', [], 'busy'),
{ id: 'ui_compact_error', source: 'automatic', runID: 'run_error', generation: 6 },
);
const failed = reduceOpenCodeEvent(busy, {
type: 'session.error',
payload: { sessionID: 'ses_1', error: 'provider failed' },
});
expect(failed?.status).toBe('idle');
expect(failed?.compactionOrder).toEqual([]);
expect(failed?.compactionsById.ui_compact_error).toBeUndefined();
});
});