| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970 |
- /**
- * Request-header reconstruction utilities over full `request/header` session
- * events. Anyone holding a session log reconstructs the {@link EpochHeader}
- * any request was built under by taking the latest canonical snapshot; the
- * loop uses the same equality helper to avoid logging unchanged headers.
- *
- * @module dsh-session/request-header
- */
- import { callConfigEquals } from '@deepseek-ai/dsh-llm'
- import type { Message, ToolSchema } from '@deepseek-ai/dsh-llm'
- import type { EpochHeader, SessionEvent } from './types.ts'
- /**
- * Normalize a header to canonical form: an empty system prompt, an empty tool
- * list, and an empty session prefix become absent fields, matching how requests
- * are built. Logging, folding, and comparison use this one representation.
- * @param header - the header to normalize (not mutated).
- * @returns the canonical header.
- */
- export function canonicalHeader(header: EpochHeader): EpochHeader {
- return {
- config: header.config,
- ...header.system !== undefined && header.system.length > 0 ? { system: header.system } : {},
- ...header.tools !== undefined && header.tools.length > 0 ? { tools: header.tools } : {},
- ...header.messagePrefix !== undefined && header.messagePrefix.length > 0 ? { messagePrefix: header.messagePrefix } : {},
- }
- }
- /** Canonical JSON equality for tool schemas assembled through the same path. */
- function sameSchema(a: ToolSchema, b: ToolSchema): boolean {
- return JSON.stringify(a) === JSON.stringify(b)
- }
- /** Canonical JSON equality over session-prefix arrays; absence equals empty. */
- function sameMessages(a: readonly Message[] | undefined, b: readonly Message[] | undefined): boolean {
- return JSON.stringify(a ?? []) === JSON.stringify(b ?? [])
- }
- /**
- * Field-wise equality over canonical headers. Tool schemas compare in order;
- * the session prefix compares as canonical JSON.
- * @param a - one canonical header.
- * @param b - the other.
- * @returns whether config, system, tools, and session prefix all match.
- */
- export function headerEquals(a: EpochHeader, b: EpochHeader): boolean {
- if (!callConfigEquals(a.config, b.config) || a.system !== b.system) return false
- if (!sameMessages(a.messagePrefix, b.messagePrefix)) return false
- const at = a.tools ?? []
- const bt = b.tools ?? []
- return at.length === bt.length && at.every((tool, i) => sameSchema(tool, bt[i] as ToolSchema))
- }
- /**
- * Fold the header events of a log (or any prefix) into the
- * {@link EpochHeader} in force after the last snapshot. Non-header events are
- * skipped. This is the pure offline reconstruction path; the live session
- * tracks the same fold incrementally.
- * @param events - session events in log order.
- * @param from - a previously folded state to continue from.
- * @returns the latest canonical header, or undefined when none exists yet.
- */
- export function foldRequestHeader(events: readonly SessionEvent[], from?: EpochHeader): EpochHeader | undefined {
- let state = from
- for (const event of events) {
- if (event.type === 'request/header') state = canonicalHeader(event.data.header)
- }
- return state
- }
|