synthetic-released-v0-session.ts 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. /** Deterministic released-v0 Zstandard Session input for opening benchmarks. */
  2. import { mkdir, writeFile } from 'node:fs/promises'
  3. import { join } from 'node:path'
  4. import { SESSION_FORMAT_VERSION } from '@deepseek-ai/dsh-session'
  5. import { generationLogFilename } from '../../packages/session/session-persistence-jsonl/src/format.ts'
  6. import { compressZstdFrame } from '../../packages/session/session-persistence-jsonl/src/zstd.ts'
  7. import {
  8. SYNTHETIC_SESSION_CWD,
  9. SYNTHETIC_SESSION_DIRECTORY,
  10. SYNTHETIC_SESSION_ID,
  11. SYNTHETIC_V0_FILENAME,
  12. } from './session-open.constants.ts'
  13. export {
  14. SYNTHETIC_SESSION_CWD,
  15. SYNTHETIC_SESSION_DIRECTORY,
  16. SYNTHETIC_SESSION_ID,
  17. SYNTHETIC_V0_FILENAME,
  18. } from './session-open.constants.ts'
  19. /** Fixed workload parameters used by every Session-opening scenario. */
  20. export interface SyntheticV0SessionShape {
  21. /** Completed turns, each with one user prompt and one streamed assistant reply. */
  22. readonly turns: number
  23. /** Text deltas per reply; each reply also contains one quarter as many reasoning deltas. */
  24. readonly textDeltas: number
  25. }
  26. /** Canonical current-generation filename produced by the runtime under test. */
  27. export const SYNTHETIC_CURRENT_FILENAME = generationLogFilename(SESSION_FORMAT_VERSION, 'zstd')
  28. /** Report label for a fresh-process reopen of the runtime's current generation. */
  29. export const SYNTHETIC_CURRENT_GENERATION = `current-v${String(SESSION_FORMAT_VERSION)}`
  30. const TIME_ZERO = 1_700_000_000_000
  31. /** One body frame per row preserves the historical many-frame workload deterministically. */
  32. const ROWS_PER_FRAME = 1
  33. type SyntheticPhysicalRow = Readonly<Record<string, unknown>>
  34. /** Complete metadata returned after writing one synthetic source generation. */
  35. export interface SyntheticV0SessionWrite {
  36. readonly path: string
  37. readonly compressedBytes: number
  38. readonly logicalBytes: number
  39. readonly events: number
  40. readonly rows: number
  41. readonly frames: number
  42. }
  43. /** Owns the immutable released-v0 physical layout used only as benchmark input. */
  44. class ReleasedV0FixtureBuilder {
  45. readonly rows: SyntheticPhysicalRow[] = []
  46. private seq = 0
  47. private time = TIME_ZERO
  48. get eventCount(): number {
  49. return this.seq
  50. }
  51. appendTurns(shape: SyntheticV0SessionShape): void {
  52. const reasoningDeltas = Math.floor(shape.textDeltas / 4)
  53. for (let turn = 1; turn <= shape.turns; turn += 1) {
  54. this.appendTurn(turn, reasoningDeltas, shape.textDeltas)
  55. }
  56. }
  57. private appendTurn(turn: number, reasoningDeltaCount: number, textDeltaCount: number): void {
  58. this.appendEvent('turn/start', { turn })
  59. this.appendEvent('step/start', { turn, step: 1 })
  60. this.appendEvent('user/message', {
  61. id: `user-${String(turn)}`,
  62. role: 'user',
  63. content: [{ type: 'text', text: `prompt ${String(turn)}` }],
  64. source: { kind: 'user' },
  65. }, { surfaceOp: 'append' })
  66. const firstChunkSeq = this.appendChunk(turn, { type: 'block-start', index: 0, blockType: 'reasoning' })
  67. const reasoningDeltas = Array.from(
  68. { length: reasoningDeltaCount },
  69. (_, index) => `r${String(index)} `,
  70. )
  71. this.appendDeltas('reasoning', turn, 0, reasoningDeltas)
  72. const reasoning = reasoningDeltas.join('')
  73. this.appendChunk(turn, { type: 'block-end', index: 0, block: { type: 'reasoning', text: reasoning } })
  74. this.appendChunk(turn, { type: 'block-start', index: 1, blockType: 'text' })
  75. const textDeltas = Array.from(
  76. { length: textDeltaCount },
  77. (_, index) => `w${String(index)} `,
  78. )
  79. this.appendDeltas('text', turn, 1, textDeltas)
  80. const text = textDeltas.join('')
  81. this.appendChunk(turn, { type: 'block-end', index: 1, block: { type: 'text', text } })
  82. const usage = { inputTokens: 100, outputTokens: textDeltaCount }
  83. this.appendChunk(turn, { type: 'usage', usage })
  84. const lastChunkSeq = this.appendChunk(turn, { type: 'finish', reason: { kind: 'stop' } })
  85. this.appendEvent('assistant/message', {
  86. turn,
  87. step: 1,
  88. message: {
  89. id: `assistant-${String(turn)}`,
  90. role: 'assistant',
  91. content: [{ type: 'reasoning', text: reasoning }, { type: 'text', text }],
  92. source: { kind: 'model', provider: 'bench', model: 'bench' },
  93. },
  94. usage,
  95. }, { sourceEventSeqs: [[firstChunkSeq, lastChunkSeq]], surfaceOp: 'append' })
  96. this.appendEvent('step/end', { turn, step: 1 })
  97. this.appendEvent('turn/end', { turn, reason: { kind: 'completed' } })
  98. }
  99. private appendChunk(turn: number, chunk: unknown): number {
  100. return this.appendEvent('assistant/chunk', { turn, step: 1, chunk })
  101. }
  102. private appendDeltas(
  103. kind: 'reasoning' | 'text',
  104. turn: number,
  105. index: number,
  106. deltas: readonly string[],
  107. ): void {
  108. if (deltas.length < 3) {
  109. for (const delta of deltas) {
  110. this.appendChunk(turn, {
  111. type: `${kind}-delta`,
  112. index,
  113. text: delta,
  114. })
  115. }
  116. return
  117. }
  118. this.rows.push({
  119. type: `${kind}-chunks`,
  120. seq0: this.seq,
  121. time0: this.time,
  122. data: {
  123. turn,
  124. step: 1,
  125. index,
  126. dt: Array.from({ length: deltas.length - 1 }, () => 1),
  127. texts: deltas,
  128. },
  129. })
  130. this.seq += deltas.length
  131. this.time += deltas.length
  132. }
  133. private appendEvent(
  134. type: string,
  135. data: unknown,
  136. extra: Readonly<Record<string, unknown>> = {},
  137. ): number {
  138. const seq = this.seq
  139. this.rows.push({ type, seq, time: this.time, data, ...extra })
  140. this.seq += 1
  141. this.time += 1
  142. return seq
  143. }
  144. }
  145. /**
  146. * Write one deterministic released-v0 Zstandard generation.
  147. * @param root - JSONL persistence root.
  148. * @param shape - workload size.
  149. * @returns physical and logical workload facts.
  150. */
  151. export async function writeSyntheticReleasedV0Session(
  152. root: string,
  153. shape: SyntheticV0SessionShape,
  154. ): Promise<SyntheticV0SessionWrite> {
  155. const fixture = new ReleasedV0FixtureBuilder()
  156. fixture.appendTurns(shape)
  157. const header = {
  158. type: 'session',
  159. version: 0,
  160. id: SYNTHETIC_SESSION_ID,
  161. createdAt: TIME_ZERO,
  162. cwd: SYNTHETIC_SESSION_CWD,
  163. delegationDepth: 0,
  164. }
  165. const headerLine = `${JSON.stringify(header)}\n`
  166. const bodyFrames: Buffer[] = []
  167. let logicalBytes = Buffer.byteLength(headerLine)
  168. for (let index = 0; index < fixture.rows.length; index += ROWS_PER_FRAME) {
  169. const rows = fixture.rows.slice(index, index + ROWS_PER_FRAME)
  170. const text = `${rows.map(row => JSON.stringify(row)).join('\n')}\n`
  171. logicalBytes += Buffer.byteLength(text)
  172. bodyFrames.push(await compressZstdFrame(text))
  173. }
  174. const physical = Buffer.concat([
  175. await compressZstdFrame(headerLine),
  176. ...bodyFrames,
  177. ])
  178. const directory = join(root, SYNTHETIC_SESSION_DIRECTORY)
  179. await mkdir(directory, { recursive: true })
  180. const path = join(directory, SYNTHETIC_V0_FILENAME)
  181. await writeFile(path, physical)
  182. return {
  183. path,
  184. compressedBytes: physical.byteLength,
  185. logicalBytes,
  186. events: fixture.eventCount,
  187. rows: fixture.rows.length,
  188. frames: fixture.rows.length + 1,
  189. }
  190. }