159 lines
5.4 KiB
TypeScript
159 lines
5.4 KiB
TypeScript
import { createMessage } from '@deepseek-ai/dsh-llm'
|
|
import type { SessionEvent } from '@deepseek-ai/dsh-session/types'
|
|
import { describe, expect, it } from 'vitest'
|
|
import { projectConversationHistory } from '../src/client/session-history/history-fold.ts'
|
|
import { compactHistoryInspectionEntries } from '../src/client/sessions/history.ts'
|
|
import { inspectRequests } from '../src/client/sessions/request-inspection.ts'
|
|
import { ev } from './event-script.ts'
|
|
|
|
const at = (seq: number, event: Record<string, unknown>): SessionEvent =>
|
|
({ seq, time: 1_700_000_000_000 + seq, ...event }) as unknown as SessionEvent
|
|
|
|
describe('projectConversationHistory', () => {
|
|
it('projects a high-sequence history window without synthesizing its unloaded prefix', () => {
|
|
const baseSeq = 400_000
|
|
const events = [
|
|
ev.user(baseSeq, 'loaded tail'),
|
|
at(baseSeq + 1, {
|
|
type: 'assistant/message',
|
|
surfaceOp: { op: 'replace', start: baseSeq, end: baseSeq },
|
|
sourceEventSeqs: [baseSeq],
|
|
data: {
|
|
turn: 80,
|
|
step: 1,
|
|
message: createMessage({
|
|
role: 'assistant',
|
|
content: [{ type: 'text', text: 'tail summary' }],
|
|
source: { kind: 'model', provider: 'fake', model: 'fake' },
|
|
}),
|
|
},
|
|
}),
|
|
]
|
|
|
|
const projection = projectConversationHistory(events.map(event => ({ event })))
|
|
expect(projection.eventNodes.map(node => node.seq)).toEqual([baseSeq, baseSeq + 1])
|
|
expect(projection.contexts.map(context => ({
|
|
originSeq: context.originSeq,
|
|
nodes: context.nodes.map(node => node.seq),
|
|
}))).toEqual([
|
|
{ originSeq: undefined, nodes: [baseSeq] },
|
|
{ originSeq: baseSeq + 1, nodes: [baseSeq + 1] },
|
|
])
|
|
})
|
|
|
|
it('projects frozen surface generations without widening the core live surface', () => {
|
|
const events = [
|
|
ev.user(0, 'a'),
|
|
ev.user(1, 'b'),
|
|
at(2, {
|
|
type: 'assistant/message',
|
|
surfaceOp: { op: 'replace', start: 0, end: 0 },
|
|
sourceEventSeqs: [0],
|
|
data: {
|
|
turn: 1,
|
|
step: 1,
|
|
message: createMessage({
|
|
role: 'assistant',
|
|
content: [{ type: 'text', text: 'summary' }],
|
|
source: { kind: 'model', provider: 'fake', model: 'fake' },
|
|
}),
|
|
},
|
|
}),
|
|
at(3, {
|
|
type: 'assistant/message',
|
|
surfaceOp: { op: 'replace', start: 2, end: 1 },
|
|
sourceEventSeqs: [2, 1],
|
|
data: {
|
|
turn: 1,
|
|
step: 2,
|
|
message: createMessage({
|
|
role: 'assistant',
|
|
content: [{ type: 'text', text: 'summary 2' }],
|
|
source: { kind: 'model', provider: 'fake', model: 'fake' },
|
|
}),
|
|
},
|
|
}),
|
|
]
|
|
|
|
expect(projectConversationHistory(events.map(event => ({ event }))).contexts.map(context => ({
|
|
id: context.id,
|
|
parentId: context.parentId,
|
|
originSeq: context.originSeq,
|
|
nodes: context.nodes.map(node => node.seq),
|
|
}))).toEqual([
|
|
{ id: 0, parentId: undefined, originSeq: undefined, nodes: [0, 1] },
|
|
{ id: 1, parentId: 0, originSeq: 2, nodes: [2, 1] },
|
|
{ id: 2, parentId: 1, originSeq: 3, nodes: [3] },
|
|
])
|
|
})
|
|
|
|
it('projects assistant timing and the active request header from history', () => {
|
|
const projection = projectConversationHistory([
|
|
ev.stepStart(0, 1, 2),
|
|
at(1, { type: 'request/header', data: {
|
|
reason: 'initial',
|
|
header: {
|
|
config: { provider: 'fake', model: 'first' },
|
|
tools: [],
|
|
},
|
|
} }),
|
|
ev.chunkStart(2, 1, 2),
|
|
ev.chunkText(3, 1, 'token', 2),
|
|
ev.assistant(4, 1, 'done', 2),
|
|
ev.stepStart(5, 2, 1),
|
|
ev.chunkText(6, 2, 'next', 1),
|
|
ev.assistant(7, 2, 'next done', 1),
|
|
].map(event => ({ event })))
|
|
|
|
expect(projection.eventNodes[0]).toMatchObject({
|
|
kind: 'assistant',
|
|
timing: {
|
|
stepStartTime: 1_700_000_000_000,
|
|
firstTokenTime: 1_700_000_000_003,
|
|
completedTime: 1_700_000_000_004,
|
|
},
|
|
requestConfig: { provider: 'fake', model: 'first' },
|
|
})
|
|
|
|
expect(projection.eventNodes.at(-1)).toMatchObject({
|
|
timing: {
|
|
stepStartTime: 1_700_000_000_005,
|
|
firstTokenTime: 1_700_000_000_006,
|
|
completedTime: 1_700_000_000_007,
|
|
},
|
|
requestConfig: { provider: 'fake', model: 'first' },
|
|
})
|
|
})
|
|
|
|
it('drops completed token payloads without changing inspection projections', () => {
|
|
const events = [
|
|
ev.user(0, 'before'),
|
|
ev.stepStart(1, 1, 0),
|
|
ev.chunkStart(2, 1),
|
|
ev.chunkText(3, 1, ''),
|
|
ev.chunkText(4, 1, 'first'),
|
|
ev.chunkText(5, 1, ' discarded'),
|
|
at(6, { type: 'assistant/chunk', data: {
|
|
turn: 1,
|
|
step: 0,
|
|
chunk: { type: 'usage', usage: { inputTokens: 4, outputTokens: 2 } },
|
|
} }),
|
|
ev.assistant(7, 1, 'first discarded'),
|
|
ev.compactSummary(8, 'summary', 0, 7),
|
|
ev.compactCheckpoint(9, 8, 0, 7),
|
|
ev.stepStart(10, 2, 0),
|
|
ev.chunkStart(11, 2),
|
|
ev.chunkText(12, 2, 'interrupted'),
|
|
ev.turnEnd(13, 2, 'aborted'),
|
|
]
|
|
const raw = events.map(event => ({ event }))
|
|
const compacted = compactHistoryInspectionEntries(raw)
|
|
|
|
expect(compacted.map(entry => entry.event.seq)).toEqual([
|
|
0, 1, 4, 6, 7, 8, 9, 10, 11, 12, 13,
|
|
])
|
|
expect(projectConversationHistory(compacted)).toEqual(projectConversationHistory(raw))
|
|
expect(inspectRequests(compacted)).toEqual(inspectRequests(raw))
|
|
})
|
|
})
|