/**
 * 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';
/**
 * Normalize a header to canonical form: an empty system prompt and empty tool
 * list 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) {
    const adapterDefaults = header.adapterDefaults;
    return {
        config: header.config,
        ...adapterDefaults?.reasoningEffort === true || adapterDefaults?.maxTokens === true
            ? { adapterDefaults }
            : {},
        ...header.system !== undefined && header.system.length > 0 ? { system: header.system } : {},
        ...header.tools !== undefined && header.tools.length > 0 ? { tools: header.tools } : {},
    };
}
/** Canonical JSON equality for tool schemas assembled through the same path. */
function sameSchema(a, b) {
    return JSON.stringify(a) === JSON.stringify(b);
}
/**
 * Field-wise equality over canonical headers. Tool schemas compare in order.
 * @param a - one canonical header.
 * @param b - the other.
 * @returns whether config, system, and tools all match.
 */
export function headerEquals(a, b) {
    if (!callConfigEquals(a.config, b.config)
        || a.adapterDefaults?.reasoningEffort !== b.adapterDefaults?.reasoningEffort
        || a.adapterDefaults?.maxTokens !== b.adapterDefaults?.maxTokens
        || a.system !== b.system)
        return false;
    const at = a.tools ?? [];
    const bt = b.tools ?? [];
    return at.length === bt.length && at.every((tool, i) => sameSchema(tool, bt[i]));
}
/**
 * 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, from) {
    let state = from;
    for (const event of events) {
        if (event.type === 'request/header')
            state = canonicalHeader(event.data.header);
    }
    return state;
}
//# sourceMappingURL=request-header.js.map