| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252 |
- import {
- SessionFormatError,
- sessionFormatCount,
- sessionFormatSafeInteger,
- snapshotSessionFormatJson,
- } from '@deepseek-ai/dsh-session-format'
- import type {
- SessionFormatArtifactDecoder,
- SessionFormatCodec,
- SessionFormatCurrentEncoder,
- SessionFormatEvent,
- SessionFormatHeader,
- SessionFormatJsonObject,
- SessionFormatJsonValue,
- SessionFormatRecovery,
- } from '@deepseek-ai/dsh-session-format'
- import { assertReleasedV2Header } from './validation.ts'
- const HEADER_REQUIRED = ['type', 'version', 'id', 'createdAt', 'isSeeded', 'delegationDepth'] as const
- const HEADER_OPTIONAL = ['cwd', 'parentSession', 'origin', 'agentPreset'] as const
- const EVENT_REQUIRED = ['type', 'seq', 'time', 'data'] as const
- const EVENT_OPTIONAL = ['ignorable', 'sourceEventSeqs', 'surfaceOp'] as const
- const EVENT_KEYS: ReadonlySet<string> = new Set([...EVENT_REQUIRED, ...EVENT_OPTIONAL])
- /** Frozen physical JSON codec for released v2. */
- export const releasedV2SessionFormatCodec = Object.freeze({
- version: 2,
- decodeHeader(value: unknown) {
- return decodePhysicalHeader(value)
- },
- createDecoder(headerValue: unknown, recovery: SessionFormatRecovery) {
- return createDecoder(headerValue, recovery)
- },
- encodeHeader(header: SessionFormatHeader, inheritedEventCount: number) {
- return encodeHeader(header, inheritedEventCount)
- },
- encodeEvent(event: SessionFormatEvent) {
- return encodeProvenance(event)
- },
- } satisfies SessionFormatCodec & SessionFormatCurrentEncoder)
- function decodePhysicalHeader(value: unknown): SessionFormatHeader {
- const snapshot = snapshotSessionFormatJson(value, 'released v2 physical header')
- const record = jsonRecord(snapshot, 'released v2 physical header')
- exactKeys(record, HEADER_REQUIRED, HEADER_OPTIONAL, 'released v2 physical header')
- if (record['type'] !== 'session' || record['version'] !== 2) {
- throw new SessionFormatError('expected released v2 physical Session header')
- }
- if (typeof record['id'] !== 'string') throw new SessionFormatError('released v2 header id must be a string')
- const createdAt = sessionFormatCount(record['createdAt'], 'released v2 header createdAt')
- const delegationDepth = sessionFormatCount(record['delegationDepth'], 'released v2 header delegationDepth')
- if (typeof record['isSeeded'] !== 'boolean') throw new SessionFormatError('released v2 header isSeeded must be boolean')
- for (const key of ['cwd', 'parentSession', 'agentPreset'] as const) {
- if (record[key] !== undefined && typeof record[key] !== 'string') {
- throw new SessionFormatError(`released v2 header ${key} must be a string`)
- }
- }
- if (record['origin'] !== undefined && record['origin'] !== 'subagent') {
- throw new SessionFormatError('released v2 header origin must be "subagent"')
- }
- const header = snapshotSessionFormatJson({
- version: 2,
- id: record['id'],
- createdAt,
- ...(record['cwd'] === undefined ? {} : { cwd: record['cwd'] }),
- ...(record['parentSession'] === undefined ? {} : { parentSession: record['parentSession'] }),
- isSeeded: record['isSeeded'],
- ...(record['origin'] === undefined ? {} : { origin: record['origin'] }),
- delegationDepth,
- ...(record['agentPreset'] === undefined ? {} : { agentPreset: record['agentPreset'] }),
- }, 'released v2 logical header') as SessionFormatHeader
- assertReleasedV2Header(header)
- return header
- }
- function createDecoder(
- headerValue: unknown,
- recovery: SessionFormatRecovery,
- ): SessionFormatArtifactDecoder {
- const header = decodePhysicalHeader(headerValue)
- let rowIndex = 0
- let eventCount = 0
- let inheritedEventCount: number | undefined
- let issue: SessionFormatError | undefined
- return {
- header,
- decodeRow(value, context) {
- const currentRow = rowIndex
- rowIndex += 1
- let event: SessionFormatEvent
- try {
- event = decodeEvent(value, currentRow)
- } catch (error: unknown) {
- const current = error instanceof SessionFormatError
- ? error
- : new SessionFormatError(`released v2 row ${currentRow} is malformed`, { cause: error })
- if (recovery === 'strict') throw current
- issue ??= current
- return
- }
- if (issue !== undefined) {
- if (event.type === 'turn/end') throw issue
- return
- }
- if (event.seq !== eventCount) {
- const gap = new SessionFormatError(
- `released v2 row ${currentRow} has seq gap (expected ${eventCount}, got ${event.seq})`,
- )
- if (recovery === 'strict') throw gap
- issue = gap
- if (event.type === 'turn/end') throw issue
- return
- }
- eventCount += 1
- if (event.type === 'session/end-seed') {
- const data = jsonRecord(event.data, `session/end-seed ${event.seq} data`)
- if (data['inherited'] === true) inheritedEventCount = event.seq
- }
- context.emitEvent(event)
- },
- finish(_context) {
- if (header.isSeeded && inheritedEventCount === undefined) {
- throw new SessionFormatError('released v2 seeded Session lacks an inherited end-seed marker')
- }
- if (!header.isSeeded && inheritedEventCount !== undefined) {
- throw new SessionFormatError('released v2 unseeded Session contains an inherited end-seed marker')
- }
- return inheritedEventCount ?? 0
- },
- }
- }
- function decodeEvent(value: unknown, rowIndex: number): SessionFormatEvent {
- const record = jsonRecord(value as SessionFormatJsonValue, `released v2 row ${rowIndex}`)
- const missing = EVENT_REQUIRED.find(key => !Object.hasOwn(record, key))
- if (missing !== undefined) throw new SessionFormatError(`released v2 row ${rowIndex} lacks required field ${missing}`)
- const unexpected = Object.keys(record).find(key => !EVENT_KEYS.has(key))
- if (unexpected !== undefined) {
- throw new SessionFormatError(`released v2 row ${rowIndex} has unexpected field ${unexpected}`)
- }
- if (typeof record['type'] !== 'string') {
- throw new SessionFormatError(`released v2 row ${rowIndex} type must be a string`)
- }
- sessionFormatSafeInteger(record['time'], `released v2 row ${rowIndex} time`)
- if (record['ignorable'] !== undefined && record['ignorable'] !== true) {
- throw new SessionFormatError(`released v2 row ${rowIndex} ignorable must be true when present`)
- }
- if (record['sourceEventSeqs'] === undefined) return record as unknown as SessionFormatEvent
- const seq = sessionFormatCount(record['seq'], `released v2 row ${rowIndex} seq`)
- return {
- ...record,
- sourceEventSeqs: decodeSeqRanges(record['sourceEventSeqs'], seq),
- } as unknown as SessionFormatEvent
- }
- function encodeHeader(
- header: SessionFormatHeader,
- inheritedEventCount: number,
- ): SessionFormatJsonObject {
- assertReleasedV2Header(header)
- const cut = sessionFormatCount(inheritedEventCount, 'format v2 inherited event count')
- if (!header.isSeeded && cut !== 0) {
- throw new SessionFormatError('unseeded format v2 Session has inherited events')
- }
- return {
- type: 'session',
- version: 2,
- id: header.id,
- createdAt: header.createdAt,
- ...(header.cwd === undefined ? {} : { cwd: header.cwd }),
- ...(header.parentSession === undefined ? {} : { parentSession: header.parentSession }),
- isSeeded: header.isSeeded,
- ...(header.origin === undefined ? {} : { origin: header.origin }),
- delegationDepth: header.delegationDepth,
- ...(header.agentPreset === undefined ? {} : { agentPreset: header.agentPreset }),
- }
- }
- function encodeProvenance(event: SessionFormatEvent): SessionFormatJsonObject {
- if (event.sourceEventSeqs === undefined) return event
- return {
- ...event,
- sourceEventSeqs: encodeSeqRanges(event.sourceEventSeqs as readonly number[]),
- }
- }
- function decodeSeqRanges(value: SessionFormatJsonValue, maxEntries: number): readonly number[] {
- if (!Array.isArray(value)) throw new SessionFormatError('sourceEventSeqs must be an array')
- const output: number[] = []
- let hasRange = false
- for (const entry of value) {
- if (!Array.isArray(entry)) {
- output.push(sessionFormatCount(entry, 'sourceEventSeqs member'))
- continue
- }
- if (entry.length !== 2) throw new SessionFormatError('sourceEventSeqs range must be a [start, end] pair')
- const start = sessionFormatCount(entry[0], 'sourceEventSeqs range start')
- const end = sessionFormatCount(entry[1], 'sourceEventSeqs range end')
- if (start > end || end >= maxEntries || end - start + 1 > maxEntries - output.length) {
- throw new SessionFormatError('sourceEventSeqs range exceeds its event seq')
- }
- for (let current = start; current <= end; current += 1) output.push(current)
- hasRange = true
- }
- const seen = new Set<number>()
- for (const source of output) {
- if (source >= maxEntries || seen.has(source)) {
- throw new SessionFormatError('sourceEventSeqs ranges must contain unique earlier seqs')
- }
- seen.add(source)
- }
- if (hasRange && output.some((source, index) => index > 0 && source <= (output[index - 1] as number))) {
- throw new SessionFormatError('sourceEventSeqs ranges must be strictly increasing')
- }
- return output
- }
- function encodeSeqRanges(values: readonly number[]): readonly SessionFormatJsonValue[] {
- if (values.some((value, index) => index > 0 && value <= (values[index - 1] as number))) return [...values]
- const output: SessionFormatJsonValue[] = []
- for (let index = 0; index < values.length;) {
- const start = values[index] as number
- let end = start
- while (index + 1 < values.length && values[index + 1] === end + 1) {
- index += 1
- end += 1
- }
- output.push(end - start >= 2 ? [start, end] : start)
- if (end - start === 1) output.push(end)
- index += 1
- }
- return output
- }
- function jsonRecord(value: SessionFormatJsonValue | undefined, label: string): SessionFormatJsonObject {
- if (typeof value !== 'object' || value === null || Array.isArray(value)) {
- throw new SessionFormatError(`${label} must be an object`)
- }
- return value as SessionFormatJsonObject
- }
- function exactKeys(
- record: SessionFormatJsonObject,
- required: readonly string[],
- optional: readonly string[],
- label: string,
- ): void {
- const allowed = new Set([...required, ...optional])
- const missing = required.find(key => !Object.hasOwn(record, key))
- if (missing !== undefined) throw new SessionFormatError(`${label} lacks ${missing}`)
- const unexpected = Object.keys(record).find(key => !allowed.has(key))
- if (unexpected !== undefined) throw new SessionFormatError(`${label} has unexpected field ${unexpected}`)
- }
|