/** * Per-loop-instance transmission bookkeeping for the reconstructability * contract: which header event to append before a request so the session log * always explains the request (the reconstructability RFC). The loop is * otherwise transmission-stateless — the comparison baseline is the log's own * folded header (`Session.requestHeader()`), so resume and fork need no * special path: a fresh loop instance simply logs a `'resume'` snapshot on * its first request and full changed-header snapshots from there. * * @module dsh-agent-loop/request-log */ import { headerEquals } from '@deepseek-ai/dsh-session' import type { EpochHeader, Session } from '@deepseek-ai/dsh-session' import type { Message } from '@deepseek-ai/dsh-llm' /** Per-loop-instance bookkeeping: whether THIS instance has logged a header yet. */ export interface TransmissionLog { /** True once this loop instance appended its anchoring `request/header` snapshot. */ loggedHeader: boolean /** * The instance's composed session prefix (the `agent/session-prefix` * waterfall's deep-frozen product), cached on the instance's first * request-building step and reused verbatim for every request it sends — * the structural guarantee that the prefix never changes mid-session. * `undefined` until composed. */ sessionPrefix?: Message[] } /** * Fresh bookkeeping for a newly-started loop instance. * @returns state with `loggedHeader` false, so the instance's first request appends an anchoring snapshot. */ export function createTransmissionLog(): TransmissionLog { return { loggedHeader: false } } /** * Append whatever header event this request owes the log, so folding the log * reproduces the header the request was built under. Exactly one of three * things happens: * * 1. This loop instance has not logged a header yet → a full `request/header` * snapshot anchors the fold: reason `'initial'` when the log has no header * events at all (a new conversation), `'resume'` when it does (process * restart, fork seed — the boundary itself is a recorded fact, so the * snapshot is appended even when nothing changed). * 2. The header equals the folded baseline → nothing; the log already * explains this request. * 3. It differs → a full snapshot with reason `'change'`. * * @param session - the session whose log explains the request. * @param state - this loop instance's bookkeeping (mutated on first log). * @param header - the canonical header the request will ACTUALLY use * (post-`agent/request`). */ export function recordRequestHeader(session: Session, state: TransmissionLog, header: EpochHeader): void { if (!state.loggedHeader) { session.append('request/header', { header, reason: session.requestHeader() === undefined ? 'initial' : 'resume' }) state.loggedHeader = true return } // This instance logged a snapshot, so the fold is necessarily defined. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const baseline = session.requestHeader()! if (headerEquals(baseline, header)) return session.append('request/header', { header, reason: 'change' }) }