/**
 * replayFrom — deterministic agent run replay.
 *
 * Given a runId, a ReplayLoader (fetches original trace + recorded artifacts),
 * and a ReplayRunner (re-executes the agent with virtualized dependencies),
 * replayFrom produces a ReplayResult containing:
 *   - The replayed trace.
 *   - A match flag: true iff rootHash of replay === rootHash of original.
 *   - A diff array listing step-level field mismatches (when match is false).
 *
 * Design invariant:
 *   Replay proves byte-identical traces when ClockPort, RandomPort, and
 *   LLMResponseCache are virtualized. It does NOT prove LLM provider-side
 *   determinism — the provider may return different tokens under identical
 *   inputs (temperature, context window, batching).
 *
 * @module replay/replay
 */
// ─── NonDeterministicReplayError ─────────────────────────────────────────────
/**
 * Thrown when strict-mode replay detects a non-deterministic deviation
 * that cannot be represented as a diff (e.g. step count mismatch, missing steps).
 *
 * For field-level mismatches, see ReplayResult.diff instead.
 */
export class NonDeterministicReplayError extends Error {
    stepIndex;
    reason;
    constructor(stepIndex, reason) {
        super(`Non-deterministic replay at step ${stepIndex}: ${reason}`);
        this.stepIndex = stepIndex;
        this.reason = reason;
        this.name = "NonDeterministicReplayError";
    }
}
// ─── diffTraces ───────────────────────────────────────────────────────────────
/**
 * Compute a step-level diff between two traces.
 * Returns an empty array if the traces are identical at the step level.
 */
function diffTraces(original, replayed) {
    const diffs = [];
    const minLen = Math.min(original.steps.length, replayed.steps.length);
    for (let i = 0; i < minLen; i++) {
        const origStep = original.steps[i];
        const repStep = replayed.steps[i];
        for (const field of Object.keys(origStep)) {
            const ov = origStep[field];
            const rv = repStep[field];
            // Compare via JSON serialization to handle nested objects correctly
            if (JSON.stringify(ov) !== JSON.stringify(rv)) {
                diffs.push({ stepIndex: i, field, original: ov, replayed: rv });
            }
        }
    }
    // If step counts differ, flag the extra/missing steps
    if (original.steps.length !== replayed.steps.length) {
        diffs.push({
            stepIndex: minLen,
            field: "index",
            original: original.steps.length,
            replayed: replayed.steps.length,
        });
    }
    return diffs;
}
// ─── replayFrom ───────────────────────────────────────────────────────────────
/**
 * Main replay entry point.
 *
 * 1. Loads the original trace and recorded artifacts via ReplayLoader.
 * 2. Builds a ReplayContext with RecordedClock, RecordedRandom, and LLM cache.
 * 3. Re-executes the agent via ReplayRunner.run().
 * 4. Compares rootHashes and computes a step-level diff if they differ.
 *
 * @param runId   The original run identifier to replay.
 * @param loader  Host-provided loader for trace + artifacts.
 * @param runner  Host-provided runner that wires the agent to virtualised deps.
 * @param opts    Optional overrides: replay mode, step index to replay from.
 */
export async function replayFrom(runId, loader, runner, opts) {
    const mode = opts?.mode ?? "strict";
    // 1. Load original artifacts
    const [rawTrace, artifacts] = await Promise.all([
        loader.loadOriginalTrace(runId),
        loader.loadCacheEntries(runId),
    ]);
    // Auto-migrate traces recorded under the draft schema to the frozen 1.0.0
    const originalTrace = rawTrace.schemaVersion === "0.1.0-draft"
        ? { ...rawTrace, schemaVersion: "1.0.0" }
        : rawTrace;
    // 2. Import concrete implementations lazily to avoid circular references
    const { RecordedClock } = await import("./clock.js");
    const { RecordedRandom } = await import("./random.js");
    const clock = new RecordedClock(artifacts.recordedTs);
    const random = new RecordedRandom(artifacts.recordedNext, artifacts.recordedUuids, mode);
    // 3. Build the replay context
    const replayRunId = globalThis.crypto.randomUUID();
    const ctx = {
        originalRunId: runId,
        replayRunId,
        mode,
        clock,
        random,
        cache: artifacts.cache,
    };
    // 4. Re-execute
    const replayTrace = await runner.run(ctx, originalTrace);
    // 5. Compare
    const match = originalTrace.rootHash === replayTrace.rootHash;
    const diff = match ? [] : diffTraces(originalTrace, replayTrace);
    return {
        replayRunId,
        trace: replayTrace,
        match,
        diff,
    };
}
//# sourceMappingURL=replay.js.map