import { AgentProfile } from '@tangle-network/agent-interface';

/**
 * Profile fingerprinting — prove WHICH profile a turn actually executed.
 *
 * The backtest invariant this exists to enforce: an eval's score is only worth
 * publishing if the benchmarked profile IS the shipped profile. The failure
 * mode is structural, not hypothetical — a product's eval composed the
 * production profile in one module, executed a hand-rolled stub in another,
 * and stamped the composed profile's identity onto the stub's scorecard.
 * Nothing compared the two, so nothing could notice.
 *
 * A `ProfileFingerprint` is a channelled identity of the profile handed to the
 * sandbox SDK: the system-prompt digest plus the names of every capability
 * surface (MCP servers, subagents, file mounts, hub connections) and the
 * model/harness the turn dispatched at. It is deliberately NOT a byte-exhaustive
 * serialization — channels are what drift in practice, and a channelled diff
 * names the surface that moved instead of reporting "bytes differ".
 *
 * The write half of the seam is `StreamSandboxPromptOptions.onProfileResolved`
 * (`/sandbox`): the one place the final profile exists is inside
 * `streamSandboxPrompt` after the system-prompt override, the MCP merge, and
 * reasoning-effort attachment, so that is where the fingerprint is taken. A
 * caller re-deriving a profile to fingerprint it would reintroduce the exact
 * gap this closes.
 */

/** The dispatch context a profile cannot see but a turn's identity includes. */
interface ProfileFingerprintContext {
    model?: string;
    harness?: string;
}
/** Channelled identity of one executed (or composed) profile. */
interface ProfileFingerprint {
    /** sha256 over every channel below — one value to log/compare. */
    hash: string;
    /** sha256 of `prompt.systemPrompt` ('' when absent). */
    promptSha: string;
    /** UTF-8 byte length of `prompt.systemPrompt`. */
    promptBytes: number;
    /** Sorted MCP server keys. */
    mcpKeys: string[];
    /** Sorted subagent names. */
    subagentNames: string[];
    /** Sorted `resources.files[].path` mounts. */
    fileMountPaths: string[];
    /** Sorted hub connection ids (alias-qualified when present). */
    connectionIds: string[];
    model?: string;
    harness?: string;
}
/** Fingerprint a profile as the SDK would receive it. */
declare function fingerprintAgentProfile(profile: AgentProfile, context?: ProfileFingerprintContext): Promise<ProfileFingerprint>;
/** One drifted channel between two fingerprints, rendered as comparable strings. */
interface ProfileDriftEntry {
    channel: 'promptSha' | 'promptBytes' | 'mcpKeys' | 'subagentNames' | 'fileMountPaths' | 'connectionIds' | 'model' | 'harness';
    a: string;
    b: string;
}
interface ProfileDrift {
    equal: boolean;
    drift: ProfileDriftEntry[];
}
/** Channel-by-channel comparison of two fingerprints. */
declare function diffProfileFingerprints(a: ProfileFingerprint, b: ProfileFingerprint): ProfileDrift;
/** Human-readable drift report; exactly 'profiles identical' when equal. */
declare function formatProfileDrift(drift: ProfileDrift): string;

/**
 * The composed-system-prompt byte budget, split out of `./index` so it imports
 * NOTHING.
 *
 * `./index` pulls `@tangle-network/agent-eval` (the evolvable-section seam), so
 * `/sandbox` — which has no agent-eval import and must not gain one — could not
 * enforce the budget from there. The gate is the same function either way; only
 * its module home moved. `./index` re-exports every symbol below, so the
 * published `/profile` surface is byte-identical.
 *
 * Why the gate belongs at more than one call site: `composeAgentProfile` is
 * OPT-IN. A product may hand-build its `AgentProfile`, and one does —
 * creative-agent's create-time profile is deliberately minimal and its full
 * system prompt rides the PER-TURN backend (`buildPromptBackend`), which never
 * touches the composer. `/sandbox` therefore runs this gate at the three points
 * where the profile that actually executes exists.
 */
/** Byte budget on the FINAL composed `prompt.systemPrompt`. Past this the
 *  model degrades sharply (a 122,659-byte prompt shipped once and the model
 *  returned empty answers), so the default gate throws well before that. */
declare const DEFAULT_MAX_SYSTEM_PROMPT_BYTES = 40000;
/** Budget config for the composed system prompt. */
interface ComposeProfileBudget {
    /** Byte cap on the composed `prompt.systemPrompt`.
     *  Default {@link DEFAULT_MAX_SYSTEM_PROMPT_BYTES}. */
    maxSystemPromptBytes?: number;
    /** Downgrade the over-budget throw to a `console.warn` — the escape hatch
     *  for a product with a known-big prompt that must still ship (it yells on
     *  every compose instead of blocking). */
    warnOnly?: boolean;
    /** Required to raise {@link maxSystemPromptBytes} above
     *  {@link DEFAULT_MAX_SYSTEM_PROMPT_BYTES} or to set {@link warnOnly}: a
     *  written reason naming what stays inline and why it cannot be mounted.
     *  Weakening the cap is a product decision that outlives the person making
     *  it, and the usual cause is reference material concatenated into the prompt
     *  that belongs in `resources.files`; demanding the sentence here keeps that
     *  from happening by accident. */
    overBudgetReason?: string;
}
/** Largest markdown-heading-delimited sections of a prompt, by UTF-8 bytes.
 *  Cheap heuristic: split on `#`-heading lines; the preamble before the first
 *  heading reports as "(preamble)". */
declare function largestPromptSections(prompt: string, top?: number): Array<{
    title: string;
    bytes: number;
}>;
/** Enforce {@link ComposeProfileBudget} on a composed system prompt: over
 *  budget throws (or warns with `warnOnly`) with the actual size and the
 *  top-3 largest sections. Exported so a product assembling its prompt
 *  outside `composeAgentProfile` (e.g. via the `/prompt` assembler) can
 *  run the same gate at its own final-composition point. */
declare function assertSystemPromptWithinBudget(systemPrompt: string, budget?: ComposeProfileBudget, 
/** Prefixed to the message so a throw from a turn/provision choke point says
 *  WHERE it fired — "composed systemPrompt" alone reads like a compose-time
 *  error even when it fired on `driveSandboxTurn`. */
origin?: string): void;
/** Run {@link assertSystemPromptWithinBudget} over a profile-shaped object's
 *  `prompt.systemPrompt`. Structural on purpose: `/sandbox` calls this on the
 *  SDK's `AgentProfile` and on the product-supplied seam result without either
 *  module importing the other's types. A profile with no string systemPrompt
 *  is a no-op — there is nothing to measure.
 *
 *  The `hint` exists because this gate can fire on a product that ALREADY made
 *  a deliberate budget decision somewhere else. gtm-agent composes with
 *  `{ maxSystemPromptBytes: 50_000 }`; without the hint, the shell's 40 KB
 *  default reads as the gate contradicting a choice the product already made
 *  rather than as "declare the same number here too". */
declare function assertProfilePromptWithinBudget(profile: {
    prompt?: {
        systemPrompt?: unknown;
    };
} | undefined, budget?: ComposeProfileBudget, origin?: string, hint?: string): void;

export { type ComposeProfileBudget as C, DEFAULT_MAX_SYSTEM_PROMPT_BYTES as D, type ProfileDrift as P, type ProfileDriftEntry as a, type ProfileFingerprint as b, type ProfileFingerprintContext as c, assertProfilePromptWithinBudget as d, assertSystemPromptWithinBudget as e, diffProfileFingerprints as f, fingerprintAgentProfile as g, formatProfileDrift as h, largestPromptSections as l };
