import { describe, expect, it } from 'vitest' import type { ConversationContext, ConversationNode, RequestView, } from '@deepseek-ai/dsh-client-runtime/client' import { deriveTrajectoryContextBranches, trajectoryBranchContainsRequest, } from '../src/client/context-branches.ts' const checkpoint = { kind: 'context', seq: 100, time: 100, content: [], source: { kind: 'plugin', plugin: 'compact' }, provenance: { role: 'inject', label: 'compact' }, form: null, } as ConversationNode const abandoned = { kind: 'assistant', seq: 20, time: 20, turn: 1, step: 1, blocks: [{ kind: 'text', text: 'abandoned' }], } as ConversationNode const current = { kind: 'user', seq: 110, time: 110, content: [{ type: 'text', text: 'rewound' }], source: { kind: 'plugin', plugin: 'rewind' }, } as ConversationNode function request( purpose: RequestView['purpose'], startSeq: number, resultSeq?: number, replacementSeq?: number, ): RequestView { const base = { startSeq, startedAt: startSeq, completedAt: startSeq + 1, status: 'complete' as const, ...(resultSeq === undefined ? {} : { resultSeq }), } return purpose === 'assistant' ? { ...base, purpose, turn: 1, step: 1 } : { ...base, purpose, turn: 1, step: 0, ...(replacementSeq === undefined ? {} : { replacementSeq }), } } describe('trajectory context branches', () => { it('inherits nodes and requests by retained surface position rather than seq cutoff', () => { const contexts: ConversationContext[] = [ { id: 0, nodes: [checkpoint, abandoned] }, { id: 1, parentId: 0, origin: 'rewind', originSeq: 110, nodes: [checkpoint, current], }, ] const branches = deriveTrajectoryContextBranches(contexts) const successor = branches[1]! expect(successor.key).toBe('rewind:110') expect(successor.nodes.map(node => node.seq)).toEqual([110]) expect(trajectoryBranchContainsRequest( successor, request('assistant', 10, 20), )).toBe(false) expect(trajectoryBranchContainsRequest( successor, request('compaction', 90, 95, 100), )).toBe(true) expect(trajectoryBranchContainsRequest( successor, request('assistant', 111), )).toBe(true) }) it('keeps branch identity when prepended generations shift local ids', () => { const branch = (id: number) => deriveTrajectoryContextBranches([{ id, origin: 'rewind', originSeq: 110, nodes: [current], }])[0] expect(branch(1)?.key).toBe(branch(9)?.key) }) })