/** * GPT-5 Harmony-header leakage detection and recovery. * * Background and policy: see `docs/ERRATA-GPT5-HARMONY.md`. This module * implements §3 of that document: detection by signal fusion, plus a * truncate-and-resume primitive for the `edit` tool when its input is in * hashline DSL form. Other tools and surfaces fall through to * abort-and-retry handled by the agent loop. */ import type { AssistantMessage, Model, ToolCall } from "../types"; declare const SIGNAL_ORDER: readonly ["M", "C", "G", "S", "B", "R", "T"]; export type HarmonySignalClass = "H" | (typeof SIGNAL_ORDER)[number]; export type HarmonySurface = "assistant_text" | "assistant_thinking" | "tool_arg"; export interface HarmonySignal { classes: HarmonySignalClass[]; start: number; end: number; text: string; } export interface HarmonyDetection { surface: HarmonySurface; contentIndex?: number; toolName?: string; toolCallId?: string; signals: HarmonySignal[]; } export interface HarmonyAuditEvent { action: "truncate_resume" | "abort_retry" | "escalated"; surface: HarmonySurface; signal: string; retryN: number; model: string; provider: string; toolName?: string; removedLen: number; removedSha8: string; removedPreview: string; removedBlob?: string; } export interface HarmonyRecoveredToolCall { message: AssistantMessage; removed: string; } /** * Whether to run leak detection on responses from this model. We default-on * for every openai-codex model rather than enumerating ids, so a future * gpt-5.6 (or whatever) doesn't silently bypass the mitigation. Detection * itself is cheap; the cost of missing a leak on a new model is not. */ export declare function isHarmonyLeakMitigationTarget(model: Model): boolean; export declare function signalListLabel(signals: readonly HarmonySignal[]): string; /** * Detect harmony-protocol leakage in `text`. Returns undefined if clean. * * Trip rule: `H` alone, or `M` paired with at least one co-signal * (`C`/`G`/`S`/`B`/`R`/`T`). Bare `M` does not trip — this document, its * tests, and bug reports legitimately carry the marker. * * The `tool_arg` surface is held to a stricter rule. A tool argument is * arbitrary file/data content that can legitimately carry the marker, a * channel word, harmony control tokens, or a non-Latin script run (editing * these very fixtures does exactly that). The only robust leak signal there * is content trailing the structurally-valid parse, so a `tool_arg` detection * additionally requires the `T` co-signal. Absent a `parsedEnd` boundary `T` * is never set, so `tool_arg` scanning stays inert and a legitimate codex tool * call is never hard-aborted. `assistant_text`/`assistant_thinking` keep the * base rule. * * `parsedEnd`, when supplied, marks the byte at which a structurally valid * tool-argument parse ends; markers at or past it set the `T` co-signal. * `contentIndex`/`toolName`/`toolCallId` flow through to the returned * detection for downstream auditing. */ export declare function detectHarmonyLeak(text: string, surface: HarmonySurface, options?: { parsedEnd?: number; contentIndex?: number; toolName?: string; toolCallId?: string; }): HarmonyDetection | undefined; /** * Scan an assistant message's content blocks; return the first detection. * * `toolArgParseEnd`, when supplied, resolves the byte offset at which a tool * call's structurally-valid argument parse ends (the `T` co-signal in * {@link detectHarmonyLeak}). Callers that can parse a tool's argument DSL pass * it to enable `tool_arg` leak detection; omitting it keeps that surface inert * — the safe default the agent loop relies on, since it cannot bound a streamed * tool DSL and must never hard-abort a legitimate tool call. */ export declare function detectHarmonyLeakInAssistantMessage(message: AssistantMessage, toolArgParseEnd?: (toolCall: ToolCall) => number | undefined): HarmonyDetection | undefined; /** * Truncate a contaminated tool call at the start of the contaminated line and * append the tool's recovery sentinel. Returns a recovered AssistantMessage * (containing only the cleaned tool call), a synthetic continuation user * message asking the model to re-issue the rest, and the removed substring * for auditing. Returns undefined when the tool is not recovery-eligible or * the truncation would leave nothing meaningful to dispatch. * * `providerPayload` is dropped from the recovered message: for Codex the * encrypted reasoning blob is opaque/signed and we cannot validate that it is * uncontaminated. The model re-reasons on the next turn. */ export declare function recoverHarmonyToolCall(message: AssistantMessage, detection: HarmonyDetection): HarmonyRecoveredToolCall | undefined; /** * Return the contaminated substring from `message` for audit purposes when * recovery is not applicable (abort path). Walks from the first detected * signal to end-of-content within the relevant block. Returns "" if the * detection cannot be resolved against the message. */ export declare function extractHarmonyRemoved(message: AssistantMessage, detection: HarmonyDetection): string; export declare function createHarmonyAuditEvent(params: { action: HarmonyAuditEvent["action"]; detection: HarmonyDetection; model: Model; retryN: number; removed: string; }): HarmonyAuditEvent; export {};