request-log.ts 3.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. /**
  2. * Per-loop-instance transmission bookkeeping for the reconstructability
  3. * contract: which header event to append before a request so the session log
  4. * always explains the request (the reconstructability RFC). The loop is
  5. * otherwise transmission-stateless — the comparison baseline is the log's own
  6. * folded header (`Session.requestHeader()`), so resume and fork need no
  7. * special path: a fresh loop instance simply logs a `'resume'` snapshot on
  8. * its first request and deltas from there.
  9. *
  10. * @module dsh-agent-loop/request-log
  11. */
  12. import { diffHeader, headerEquals, applyHeaderDelta } from '@deepseek-ai/dsh-session'
  13. import type { EpochHeader, Session } from '@deepseek-ai/dsh-session'
  14. import type { Message } from '@deepseek-ai/dsh-llm'
  15. /** Per-loop-instance bookkeeping: whether THIS instance has logged a header yet. */
  16. export interface TransmissionLog {
  17. /** True once this loop instance appended its anchoring `request/header` snapshot. */
  18. loggedHeader: boolean
  19. /**
  20. * The instance's composed session prefix (the `agent/session-prefix`
  21. * waterfall's deep-frozen product), cached on the instance's first
  22. * request-building step and reused verbatim for every request it sends —
  23. * the structural guarantee that the prefix never changes mid-session.
  24. * `undefined` until composed.
  25. */
  26. sessionPrefix?: Message[]
  27. }
  28. /**
  29. * Fresh bookkeeping for a newly-started loop instance.
  30. * @returns state with `loggedHeader` false, so the instance's first request appends an anchoring snapshot.
  31. */
  32. export function createTransmissionLog(): TransmissionLog {
  33. return { loggedHeader: false }
  34. }
  35. /**
  36. * Append whatever header event this request owes the log, so folding the log
  37. * reproduces the header the request was built under. Exactly one of four
  38. * things happens:
  39. *
  40. * 1. This loop instance has not logged a header yet → a full `request/header`
  41. * snapshot anchors the fold: reason `'initial'` when the log has no header
  42. * events at all (a new conversation), `'resume'` when it does (process
  43. * restart, fork seed — the boundary itself is a recorded fact, so the
  44. * snapshot is appended even when nothing changed).
  45. * 2. The header equals the folded baseline → nothing; the log already
  46. * explains this request.
  47. * 3. It differs and the delta round-trips (`applyHeaderDelta` on the baseline
  48. * reproduces the header exactly) → a `request/header-delta`.
  49. * 4. It differs and the delta encoding cannot express the change (a pure tool
  50. * reordering) → a full snapshot with reason `'fallback'`; deltas are an
  51. * encoding optimization, never a correctness dependency.
  52. *
  53. * @param session - the session whose log explains the request.
  54. * @param state - this loop instance's bookkeeping (mutated on first log).
  55. * @param header - the canonical header the request will ACTUALLY use
  56. * (post-`agent/request`).
  57. */
  58. export function recordRequestHeader(session: Session, state: TransmissionLog, header: EpochHeader): void {
  59. if (!state.loggedHeader) {
  60. session.append('request/header', { header, reason: session.requestHeader() === undefined ? 'initial' : 'resume' })
  61. state.loggedHeader = true
  62. return
  63. }
  64. // This instance logged a snapshot, so the fold is necessarily defined.
  65. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  66. const baseline = session.requestHeader()!
  67. if (headerEquals(baseline, header)) return
  68. const delta = diffHeader(baseline, header)
  69. /* v8 ignore next -- headerEquals false ⟹ diffHeader defined: both compare the same four parts */
  70. if (delta === undefined) return
  71. if (headerEquals(applyHeaderDelta(baseline, delta), header)) {
  72. session.append('request/header-delta', delta)
  73. } else {
  74. session.append('request/header', { header, reason: 'fallback' })
  75. }
  76. }