| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357 |
- import { describe, expect, it } from 'vitest'
- import { zstdCompressSync } from 'node:zlib'
- import type { SessionEvent } from '@deepseek-ai/dsh-session'
- import { CallId, type StreamChunk } from '@deepseek-ai/dsh-llm'
- import {
- decodeStorageRecord,
- MAX_PACKED_DATA_BYTES,
- MAX_PACKED_ROW_MEMBERS,
- packChunkRuns,
- type StorageRecord,
- } from '../src/codec.ts'
- import {
- bindRecord,
- decodeRow,
- scanRows,
- ZSTD_DATA_THRESHOLD_BYTES,
- } from '../src/compression.ts'
- import type { EventRow } from '../src/schema.ts'
- function chunk(seq: number, text = `token-${seq}`): SessionEvent {
- return {
- type: 'assistant/chunk',
- seq,
- time: 1_000 + seq,
- data: {
- turn: 1,
- step: 1,
- chunk: { type: 'text-delta', index: 0, text },
- },
- }
- }
- function event(seq: number, time: number, value: StreamChunk, turn = 1, step = 1): SessionEvent {
- return { type: 'assistant/chunk', seq, time, data: { turn, step, chunk: value } }
- }
- function row(record: StorageRecord): EventRow {
- const bound = bindRecord(record)
- return {
- seq: bound.seq,
- type: bound.type,
- time: bound.time,
- data: bound.data,
- source_event_seqs: bound.sourceEventSeqs,
- surface_op: bound.surfaceOp,
- ignorable: bound.ignorable,
- }
- }
- describe('SQLite compression', () => {
- it('stores a 100-member run in one row and restores every logical event', () => {
- const events = Array.from({ length: 100 }, (_, index) => chunk(index))
- const records = packChunkRuns(events)
- expect(records).toHaveLength(1)
- expect(records[0]?.type).toBe('text-chunks')
- expect(scanRows(records.map(row)).preserved).toEqual(events)
- })
- it('partitions long and large runs within schema-owned row limits', () => {
- const long = Array.from({ length: MAX_PACKED_ROW_MEMBERS + 3 }, (_, index) => chunk(index))
- const longRecords = packChunkRuns(long)
- expect(longRecords).toHaveLength(2)
- expect(scanRows(longRecords.map(row)).preserved).toEqual(long)
- const large = Array.from({ length: 4 }, (_, index) => chunk(index, 'x'.repeat(300_000)))
- const largeRecords = packChunkRuns(large)
- expect(largeRecords).toHaveLength(2)
- for (const record of largeRecords) {
- if (record.type.endsWith('-chunks')) {
- expect(Buffer.byteLength(JSON.stringify(record.data))).toBeLessThanOrEqual(MAX_PACKED_DATA_BYTES)
- }
- }
- expect(scanRows(largeRecords.map(row)).preserved).toEqual(large)
- const individuallyLarge = Array.from({ length: 3 }, (_, index) => chunk(index, 'x'.repeat(400_000)))
- expect(packChunkRuns(individuallyLarge)).toEqual(individuallyLarge)
- const byteBound = Array.from({ length: 10 }, (_, index) => chunk(index, 'x'.repeat(150_000)))
- const byteBoundRecords = packChunkRuns(byteBound)
- expect(byteBoundRecords.length).toBeGreaterThan(1)
- expect(scanRows(byteBoundRecords.map(row)).preserved).toEqual(byteBound)
- })
- it('packs every owned kind and preserves optional tool-call names', () => {
- const events = [
- ...[0, 1, 2].map(seq => event(seq, seq, { type: 'reasoning-delta', index: 1, text: `${seq}` })),
- ...[3, 4, 5].map(seq => event(seq, seq, {
- type: 'tool-call-delta', index: 2, id: CallId('named'), name: 'write', argumentsDelta: `${seq}`,
- })),
- ...[6, 7, 8].map(seq => event(seq, seq, {
- type: 'tool-call-delta', index: 3, id: CallId('unnamed'), argumentsDelta: `${seq}`,
- })),
- ]
- const records = packChunkRuns(events)
- expect(records.map(record => record.type)).toEqual([
- 'reasoning-chunks', 'tool-call-chunks', 'tool-call-chunks',
- ])
- expect(records.flatMap(decodeStorageRecord)).toEqual(events)
- })
- it('keeps every off-format delta scalar and splits incompatible runs', () => {
- const malformed = (seq: number, data: unknown): SessionEvent => ({
- type: 'assistant/chunk', seq, time: 10 + seq, data,
- } as SessionEvent)
- const values: SessionEvent[] = [
- { type: 'turn/start', seq: 0, time: 1, data: { turn: 1 } },
- { ...chunk(1), extra: true } as unknown as SessionEvent,
- { ...chunk(-1), seq: -1 },
- { ...chunk(3), time: 1.5 },
- malformed(4, 'data'),
- malformed(5, { turn: 1, step: 1, chunk: { type: 'text-delta', index: 0, text: 'x' }, extra: 1 }),
- malformed(6, { turn: '1', step: 1, chunk: { type: 'text-delta', index: 0, text: 'x' } }),
- malformed(7, { turn: 1, step: 1, chunk: 'chunk' }),
- malformed(8, { turn: 1, step: 1, chunk: { type: 'text-delta', index: '0', text: 'x' } }),
- malformed(9, { turn: 1, step: 1, chunk: { type: 'text-delta', index: 0, text: 1 } }),
- malformed(10, { turn: 1, step: 1, chunk: { type: 'tool-call-delta', index: 0, id: 1, argumentsDelta: 'x' } }),
- malformed(11, { turn: 1, step: 1, chunk: { type: 'tool-call-delta', index: 0, id: 'id', name: 1, argumentsDelta: 'x' } }),
- malformed(12, { turn: 1, step: 1, chunk: { type: 'usage', index: 0, totalTokens: 1 } }),
- ]
- expect(packChunkRuns(values)).toEqual(values)
- const gap = [chunk(0), chunk(1), chunk(3)]
- const step = [chunk(0), chunk(1), event(2, 2, { type: 'text-delta', index: 0, text: 'x' }, 1, 2)]
- const block = [chunk(0), chunk(1), event(2, 2, { type: 'text-delta', index: 1, text: 'x' })]
- const unsafeTime = [
- event(0, Number.MIN_SAFE_INTEGER, { type: 'text-delta', index: 0, text: 'a' }),
- event(1, Number.MAX_SAFE_INTEGER, { type: 'text-delta', index: 0, text: 'b' }),
- event(2, Number.MAX_SAFE_INTEGER, { type: 'text-delta', index: 0, text: 'c' }),
- ]
- const toolName = [0, 1, 2].map(seq => event(seq, seq, {
- type: 'tool-call-delta', index: 0, id: CallId('id'),
- ...seq === 2 ? {} : { name: 'write' }, argumentsDelta: 'x',
- }))
- for (const events of [gap, step, block, unsafeTime, toolName]) {
- expect(packChunkRuns(events)).toEqual(events)
- }
- })
- it.each([
- ['extra envelope field', { type: 'text-chunks', seq0: 0, time0: 1, data: {}, extra: true }],
- ['negative sequence', { type: 'text-chunks', seq0: -1, time0: 1, data: {} }],
- ['fractional time', { type: 'text-chunks', seq0: 0, time0: 1.5, data: {} }],
- ['primitive data', { type: 'text-chunks', seq0: 0, time0: 1, data: 'bad' }],
- ['text fields', { type: 'text-chunks', seq0: 0, time0: 1, data: { turn: 1, step: 1, index: 0, dt: [], args: [] } }],
- ['non-numeric placement', { type: 'text-chunks', seq0: 0, time0: 1, data: { turn: '1', step: 1, index: 0, dt: [0, 0], texts: ['a', 'b', 'c'] } }],
- ['non-array members', { type: 'text-chunks', seq0: 0, time0: 1, data: { turn: 1, step: 1, index: 0, dt: [0, 0], texts: 'abc' } }],
- ['too few members', { type: 'text-chunks', seq0: 0, time0: 1, data: { turn: 1, step: 1, index: 0, dt: [0], texts: ['a', 'b'] } }],
- ['too many members', { type: 'text-chunks', seq0: 0, time0: 1, data: { turn: 1, step: 1, index: 0, dt: Array(1_024).fill(0), texts: Array(1_025).fill('a') } }],
- ['non-string member', { type: 'text-chunks', seq0: 0, time0: 1, data: { turn: 1, step: 1, index: 0, dt: [0, 0], texts: ['a', 1, 'c'] } }],
- ['invalid gaps', { type: 'text-chunks', seq0: 0, time0: 1, data: { turn: 1, step: 1, index: 0, dt: [0, 0.5], texts: ['a', 'b', 'c'] } }],
- ['non-array gaps', { type: 'text-chunks', seq0: 0, time0: 1, data: { turn: 1, step: 1, index: 0, dt: '00', texts: ['a', 'b', 'c'] } }],
- ['gap arity', { type: 'text-chunks', seq0: 0, time0: 1, data: { turn: 1, step: 1, index: 0, dt: [0], texts: ['a', 'b', 'c'] } }],
- ['oversized data', { type: 'text-chunks', seq0: 0, time0: 1, data: { turn: 1, step: 1, index: 0, dt: [0, 0], texts: ['x'.repeat(400_000), 'x'.repeat(400_000), 'x'.repeat(400_000)] } }],
- ['sequence overflow', { type: 'text-chunks', seq0: Number.MAX_SAFE_INTEGER, time0: 1, data: { turn: 1, step: 1, index: 0, dt: [0, 0], texts: ['a', 'b', 'c'] } }],
- ['time overflow', { type: 'text-chunks', seq0: 0, time0: Number.MAX_SAFE_INTEGER, data: { turn: 1, step: 1, index: 0, dt: [1, 0], texts: ['a', 'b', 'c'] } }],
- ['tool fields', { type: 'tool-call-chunks', seq0: 0, time0: 1, data: { turn: 1, step: 1, index: 0, dt: [0, 0], args: ['a', 'b', 'c'] } }],
- ['tool id', { type: 'tool-call-chunks', seq0: 0, time0: 1, data: { turn: 1, step: 1, index: 0, id: 1, dt: [0, 0], args: ['a', 'b', 'c'] } }],
- ['tool name', { type: 'tool-call-chunks', seq0: 0, time0: 1, data: { turn: 1, step: 1, index: 0, id: 'id', name: 1, dt: [0, 0], args: ['a', 'b', 'c'] } }],
- ])('rejects malformed packed data: %s', (_label, record) => {
- expect(() => decodeStorageRecord(record)).toThrow(/malformed .* storage row/)
- })
- it('decodes the schema-17 row vocabulary without another package codec', () => {
- const fixture: EventRow = {
- seq: 7,
- type: 'text-chunks',
- time: 90,
- data: JSON.stringify({ turn: 2, step: 3, index: 1, dt: [2, -1], texts: ['a', 'b', 'c'] }),
- source_event_seqs: null,
- surface_op: null,
- ignorable: 0,
- }
- expect(decodeRow(fixture)).toEqual([
- { ...chunk(7, 'a'), time: 90, data: { turn: 2, step: 3, chunk: { type: 'text-delta', index: 1, text: 'a' } } },
- { ...chunk(8, 'b'), time: 92, data: { turn: 2, step: 3, chunk: { type: 'text-delta', index: 1, text: 'b' } } },
- { ...chunk(9, 'c'), time: 91, data: { turn: 2, step: 3, chunk: { type: 'text-delta', index: 1, text: 'c' } } },
- ])
- expect(decodeStorageRecord('scalar')).toEqual(['scalar'])
- expect(decodeStorageRecord(chunk(0))).toEqual([chunk(0)])
- })
- it('rejects surface columns on packed rows', () => {
- const packed = row(packChunkRuns([chunk(0), chunk(1), chunk(2)])[0]!)
- const invalid: EventRow[] = [
- { ...packed, source_event_seqs: Buffer.alloc(0) },
- { ...packed, surface_op: '"append"' },
- ]
- for (const candidate of invalid) {
- expect(() => decodeRow(candidate)).toThrow(/surface fields must be null/)
- }
- })
- it('rejects the packed discriminator on a scalar event type', () => {
- const scalar = row({ type: 'turn/start', seq: 0, time: 1, data: { turn: 1 } })
- expect(() => decodeRow({ ...scalar, ignorable: 0 }))
- .toThrow(/packed discriminator requires a chunk tag/)
- })
- it.each(['text-chunks', 'reasoning-chunks', 'tool-call-chunks'])(
- 'preserves an ignorable logical event named %s as a scalar row',
- (type) => {
- const logical = {
- type,
- seq: 0,
- time: 1,
- data: { future: true },
- ignorable: true,
- } as unknown as SessionEvent
- const physical = row(logical)
- expect(physical.ignorable).toBe(1)
- expect(decodeRow(physical)).toEqual([logical])
- },
- )
- it('compresses large data and delta-encodes complete provenance arrays', () => {
- const sources = Array.from({ length: 2_000 }, (_, index) => index + 10)
- const event = {
- type: 'assistant/message',
- seq: sources.at(-1)! + 1,
- time: 1,
- data: { text: 'x'.repeat(ZSTD_DATA_THRESHOLD_BYTES * 2) },
- sourceEventSeqs: sources,
- surfaceOp: 'append',
- } as unknown as SessionEvent
- const bound = bindRecord(event)
- expect(bound.data).toBeInstanceOf(Uint8Array)
- expect(bound.sourceEventSeqs).toBeInstanceOf(Uint8Array)
- expect(bound.sourceEventSeqs?.byteLength).toBeLessThan(Buffer.byteLength(JSON.stringify(sources)))
- expect(decodeRow(row(event))).toEqual([event])
- const small = bindRecord({ type: 'turn/start', seq: 0, time: 1, data: { turn: 1 } })
- expect(typeof small.data).toBe('string')
- })
- it('round-trips empty, descending, and maximum-safe provenance deltas', () => {
- for (const sources of [
- [],
- [Number.MAX_SAFE_INTEGER - 1, 0, Number.MAX_SAFE_INTEGER - 2],
- ]) {
- const event = {
- type: 'assistant/message',
- seq: Number.MAX_SAFE_INTEGER,
- time: 1,
- data: {},
- sourceEventSeqs: sources,
- surfaceOp: 'append',
- } as unknown as SessionEvent
- expect(decodeRow(row(event))).toEqual([event])
- }
- })
- it.each([-1, 0.5])('rejects invalid provenance sequence %s before encoding', (sourceSeq) => {
- const event = {
- type: 'assistant/message',
- seq: 1,
- time: 1,
- data: {},
- sourceEventSeqs: [sourceSeq],
- surfaceOp: 'append',
- } as unknown as SessionEvent
- expect(() => bindRecord(event)).toThrow(/non-negative safe integers/)
- })
- it('rejects malformed compressed and delta-encoded values', () => {
- const scalar = row({ type: 'turn/start', seq: 0, time: 1, data: { turn: 1 } })
- expect(() => decodeRow({ ...scalar, data: Buffer.from('not zstd') })).toThrow()
- expect(() => decodeRow({ ...scalar, source_event_seqs: Buffer.from([0x80]) }))
- .toThrow(/truncated varint/)
- expect(() => decodeRow({ ...scalar, source_event_seqs: Buffer.from([0x80, 0x00]) }))
- .toThrow(/non-canonical varint/)
- expect(() => decodeRow({ ...scalar, source_event_seqs: Buffer.from([0x00, 0x01]) }))
- .toThrow(/decoded seq is out of range/)
- expect(() => decodeRow({ ...scalar, source_event_seqs: Buffer.from([
- 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x02,
- ]) })).toThrow(/decoded seq is out of range/)
- expect(() => decodeRow({ ...scalar, source_event_seqs: Buffer.from([
- 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x10,
- ]) })).toThrow(/varint is out of range/)
- expect(() => decodeRow({ ...scalar, source_event_seqs: Buffer.alloc(9, 0x80) }))
- .toThrow(/varint is out of range/)
- })
- it('rejects an oversized packed data column before JSON decoding', () => {
- const oversized: EventRow = {
- seq: 0,
- type: 'text-chunks',
- time: 1,
- data: ' '.repeat(MAX_PACKED_DATA_BYTES + 1),
- source_event_seqs: null,
- surface_op: null,
- ignorable: 0,
- }
- expect(() => decodeRow(oversized)).toThrow(/data exceeds/)
- })
- it('bounds packed data while decompressing', () => {
- const serialized = JSON.stringify({
- turn: 1,
- step: 1,
- index: 0,
- dt: [0, 0],
- texts: ['x'.repeat(MAX_PACKED_DATA_BYTES), 'b', 'c'],
- })
- const oversized: EventRow = {
- seq: 0,
- type: 'text-chunks',
- time: 1,
- data: zstdCompressSync(serialized),
- source_event_seqs: null,
- surface_op: null,
- ignorable: 0,
- }
- expect(() => decodeRow(oversized)).toThrow(/Buffer larger than/)
- })
- it('distinguishes removable and committed physical corruption', () => {
- const start = row({ type: 'turn/start', seq: 0, time: 1, data: { turn: 1 } })
- const skipped = row({ type: 'step/start', seq: 2, time: 2, data: { turn: 1, step: 1 } })
- expect(scanRows([start, skipped])).toEqual({ preserved: [
- { type: 'turn/start', seq: 0, time: 1, data: { turn: 1 } },
- ], tornFrom: 2 })
- const end = row({
- type: 'turn/end',
- seq: 3,
- time: 3,
- data: { turn: 1, reason: { kind: 'completed' } },
- })
- expect(() => scanRows([start, skipped, end])).toThrow(/invalid committed physical row at seq 2/)
- const malformed = {
- ...row(packChunkRuns([chunk(0), chunk(1), chunk(2)])[0]!),
- data: '{not json',
- }
- const committedEnd = row({
- type: 'turn/end',
- seq: 1,
- time: 4,
- data: { turn: 1, reason: { kind: 'completed' } },
- })
- expect(() => scanRows([malformed, committedEnd]))
- .toThrow(/invalid committed physical row at seq 0/)
- })
- it('treats a malformed packed tail as one removable physical row', () => {
- const malformed: EventRow = {
- seq: 0,
- type: 'text-chunks',
- time: 1,
- data: JSON.stringify({ turn: 1, step: 1, index: 0, dt: [], texts: ['a', 'b'] }),
- source_event_seqs: null,
- surface_op: null,
- ignorable: 0,
- }
- expect(scanRows([malformed])).toEqual({ preserved: [], tornFrom: 0 })
- })
- })
|