event-script.client.ts 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. import {
  2. ToolCallId, createMessage, createToolResultMessage, createUserMessage,
  3. } from '@deepseek-ai/dsh-llm'
  4. // Minimal SessionEvent builders for orchestration tests (shape mirrors what the
  5. // host emits; only the fields the object layer reads).
  6. import type { ContentBlock } from '@deepseek-ai/dsh-llm/types'
  7. import type { SessionEvent } from '@deepseek-ai/dsh-session/types'
  8. import type {
  9. SessionEventEntry,
  10. SessionPage,
  11. SessionWireEvent,
  12. } from '../src/types.ts'
  13. /** One text content block (local helper). */
  14. const text = (t: string): ContentBlock[] => [{ type: 'text', text: t }]
  15. const at = (seq: number, e: Record<string, unknown>): SessionEvent =>
  16. ({ seq, time: 1_700_000_000_000 + seq, ...e }) as unknown as SessionEvent
  17. export const ev = {
  18. turnStart: (seq: number, turn: number): SessionEvent =>
  19. at(seq, { type: 'turn/start', data: { turn } }),
  20. user: (seq: number, body: string): SessionEvent =>
  21. at(seq, { type: 'user/message', surfaceOp: 'append', data: createUserMessage({
  22. content: text(body), source: { kind: 'user' },
  23. }) }),
  24. stepStart: (seq: number, turn: number, step = 0): SessionEvent =>
  25. at(seq, { type: 'step/start', data: { turn, step } }),
  26. chunkStart: (seq: number, turn: number, step = 0, index = 0): SessionEvent =>
  27. at(seq, { type: 'assistant/chunk', data: { turn, step, chunk: { type: 'block-start', index, blockType: 'text' } } }),
  28. chunkText: (seq: number, turn: number, piece: string, step = 0, index = 0): SessionEvent =>
  29. at(seq, { type: 'assistant/chunk', data: { turn, step, chunk: { type: 'text-delta', index, text: piece } } }),
  30. assistant: (seq: number, turn: number, body: string, step = 0): SessionEvent =>
  31. at(seq, { type: 'assistant/message', surfaceOp: 'append', data: {
  32. turn, step,
  33. message: createMessage({
  34. role: 'assistant',
  35. content: text(body),
  36. source: {
  37. kind: 'model',
  38. ...{ provider: 'fake', model: 'fk-1' },
  39. },
  40. }),
  41. } }),
  42. toolCall: (seq: number, turn: number, callId: string, name: string, args: string, step = 0): SessionEvent =>
  43. at(seq, { type: 'tool/call', data: { turn, step, callId, name, arguments: args } }),
  44. toolResult: (seq: number, turn: number, callId: string, body: string, step = 0): SessionEvent =>
  45. at(seq, {
  46. type: 'tool/result',
  47. surfaceOp: 'append',
  48. data: {
  49. turn,
  50. step,
  51. message: createToolResultMessage({
  52. callId: ToolCallId(callId),
  53. content: text(body),
  54. isError: false,
  55. }),
  56. },
  57. }),
  58. codeDispatchStart: (seq: number, parentCallId: string, n: number, name: string, args: unknown): SessionEvent =>
  59. at(seq, {
  60. type: 'tool/code-dispatch-start',
  61. data: { rootCallId: parentCallId, parentCallId, subCallId: `${parentCallId}:code:${n}`, name, arguments: args },
  62. }),
  63. codeDispatch: (seq: number, parentCallId: string, n: number, name: string, args: unknown, body: string, isError = false): SessionEvent =>
  64. at(seq, {
  65. type: 'tool/code-dispatch',
  66. data: { rootCallId: parentCallId, parentCallId, subCallId: `${parentCallId}:code:${n}`, name, arguments: args, isError, content: text(body) },
  67. }),
  68. stepEnd: (seq: number, turn: number, step = 0): SessionEvent =>
  69. at(seq, { type: 'step/end', data: { turn, step } }),
  70. retry: (
  71. seq: number,
  72. turn: number,
  73. step = 0,
  74. retry = 1,
  75. maxRetries = 2,
  76. delayMs = 500,
  77. message = 'temporary transport failure',
  78. ): SessionEvent =>
  79. at(seq, {
  80. type: 'llm/retry',
  81. data: {
  82. turn, step,
  83. provider: 'fake', mode: 'normal', policyKey: 'fake-normal',
  84. retry, maxRetries, delayMs,
  85. failure: { code: 'TRANSPORT', message },
  86. },
  87. }),
  88. turnEnd: (seq: number, turn: number, reason: 'completed' | 'aborted' | 'disposed' = 'completed'): SessionEvent =>
  89. at(seq, { type: 'turn/end', data: {
  90. turn,
  91. reason: reason === 'completed'
  92. ? { kind: 'completed' }
  93. : { kind: 'aborted', reason: { kind: reason === 'disposed' ? 'disposed' : 'user' } },
  94. } }),
  95. commandRun: (seq: number, commandId: string, name: string, args = ''): SessionEvent =>
  96. at(seq, { type: 'command/run', data: { commandId, name, args, source: { kind: 'user' } } }),
  97. commandRunWithoutInput: (seq: number, commandId: string, name: string): SessionEvent =>
  98. at(seq, { type: 'command/run', data: { commandId, name, source: { kind: 'user' } } }),
  99. commandDone: (
  100. seq: number,
  101. commandId: string,
  102. kind: 'success' | 'error' = 'success',
  103. text?: string,
  104. sourceEventSeq?: number,
  105. ): SessionEvent =>
  106. at(seq, { type: 'command/done', data: {
  107. commandId,
  108. kind,
  109. ...text === undefined ? {} : { text },
  110. ...sourceEventSeq === undefined ? {} : { sourceEventSeq },
  111. } }),
  112. /** A compaction's log-only `compaction/summary` record. */
  113. compactSummary: (seq: number, summary: string, start: number, end: number): SessionEvent =>
  114. at(seq, { type: 'compaction/summary', data: {
  115. summary: text(summary),
  116. shadowedRange: { start, end },
  117. shadowedSeqs: [start, end],
  118. shadowedTokenCount: 100,
  119. provider: 'fake',
  120. model: 'compact-1',
  121. } }),
  122. /** The replacement user message a compaction backend lands (the checkpoint). */
  123. compactCheckpoint: (seq: number, summarySeq: number, start: number, end: number): SessionEvent =>
  124. at(seq, {
  125. type: 'user/message',
  126. surfaceOp: { op: 'replace', start, end },
  127. sourceEventSeqs: [summarySeq, start, end],
  128. data: createUserMessage({
  129. content: text('<context_checkpoint>model only</context_checkpoint>'),
  130. source: { kind: 'plugin', plugin: 'compact' },
  131. }),
  132. }),
  133. }
  134. /** One complete plain turn (turn/start → user → step → assistant → turn/end), 6 events from startSeq. */
  135. export function plainTurn(startSeq: number, turn: number, ask: string, answer: string): SessionEvent[] {
  136. return [
  137. ev.turnStart(startSeq, turn),
  138. ev.user(startSeq + 1, ask),
  139. ev.stepStart(startSeq + 2, turn),
  140. ev.assistant(startSeq + 3, turn, answer),
  141. ev.stepEnd(startSeq + 4, turn),
  142. ev.turnEnd(startSeq + 5, turn),
  143. ]
  144. }
  145. /** Wrap raw events in the journal envelope returned by history. */
  146. export function entries(events: readonly SessionEvent[]): SessionEventEntry[] {
  147. return events.map(event => ({ type: 'event', event: event as unknown as SessionWireEvent }))
  148. }
  149. /** Build one view-less history response value. */
  150. export function historyValue(events: readonly SessionEvent[], hasMore = false): SessionPage {
  151. return {
  152. records: entries(events),
  153. hasMore,
  154. }
  155. }