Files
2026-07-13 13:39:12 +08:00

45 lines
1.8 KiB
TypeScript

import { extractTextContent } from "./messageContent.ts";
import { checkFidelity, type FidelityGateConfig } from "./fidelityGate.ts";
import type { CompressionResult } from "./types.ts";
import type { StackAccumulator } from "./stackedStepCore.ts";
import { getCompressionEngine } from "./engines/registry.ts";
function bodyToText(body: Record<string, unknown>): string {
const messages = body.messages;
if (!Array.isArray(messages)) return "";
return messages
.map((m) => extractTextContent((m as { content?: unknown }).content as never))
.join("\n");
}
/**
* Fidelity gate (opt-in, independent of TV1). Called at each advance point AFTER mergeStackStep
* pushed the step's breakdown entry. Off → zero-cost `return true` (byte-identical legacy). On a
* fidelity failure it marks the just-pushed breakdown entry rejected (no advance) and returns false.
*/
export function gateAdvance(
result: CompressionResult,
inputBody: Record<string, unknown>,
fidelityGate: FidelityGateConfig | undefined,
acc: StackAccumulator,
engineId?: string
): boolean {
if (!fidelityGate?.enabled) return true;
if (engineId && getCompressionEngine(engineId)?.sampling) return true; // lossy-by-design, CCR-recoverable
const verdict = checkFidelity(bodyToText(inputBody), bodyToText(result.body), fidelityGate);
if (verdict.passed) return true;
// mergeStackStep only pushed a breakdown entry when result.stats exists; only then is
// acc.breakdown's last entry THIS step's (else it would be the prior engine's — don't touch it).
if (result.stats) {
const last = acc.breakdown[acc.breakdown.length - 1];
if (last) {
last.rejected = true;
last.rejectReason = verdict.detail ?? verdict.failedInvariant;
last.compressedTokens = last.originalTokens;
last.savingsPercent = 0;
}
}
acc.fallbackApplied = true;
return false;
}