/**
 * LLMResponseCache — content-addressed in-memory cache for LLM responses.
 *
 * Cache key: SHA-256(canonical({provider, model, messages, temperature, seed?}))
 * This makes the cache deterministic across runs given identical inputs,
 * enabling replay of LLM responses without hitting the actual provider.
 *
 * Production adapters (pgsql, Redis) belong to the B-series tasks.
 * This module ships the in-memory implementation + the hash utility.
 *
 * @module replay/llm-cache
 */
import { canonicalize } from "../trace/canonical.js";
// ─── hashLLMCacheKey ──────────────────────────────────────────────────────────
/**
 * Compute the SHA-256 hex hash of a canonical LLMCacheKey.
 *
 * Uses Web Crypto API (globalThis.crypto.subtle) — zero external dependencies.
 * Requires Node >= 20 or any modern browser.
 *
 * Array order in `messages` is significant: two arrays with the same elements
 * in different order will produce different hashes.
 */
export async function hashLLMCacheKey(key) {
    const canonical = canonicalize(key);
    const encoded = new TextEncoder().encode(canonical);
    const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", encoded);
    return bufferToHex(hashBuffer);
}
function bufferToHex(buffer) {
    return Array.from(new Uint8Array(buffer))
        .map((b) => b.toString(16).padStart(2, "0"))
        .join("");
}
// ─── InMemoryLLMResponseCache ─────────────────────────────────────────────────
/**
 * In-memory LLM response cache keyed by SHA-256(canonical(LLMCacheKey)).
 *
 * Intended for:
 *   - Unit and integration tests (deterministic, zero I/O).
 *   - Development replay (fast, ephemeral).
 *
 * NOT intended for production: data is lost on process restart.
 * Production replay should use the PostgreSQL adapter (B-series).
 */
export class InMemoryLLMResponseCache {
    store = new Map();
    async get(key) {
        const hash = await hashLLMCacheKey(key);
        return this.store.get(hash);
    }
    async put(key, response) {
        const hash = await hashLLMCacheKey(key);
        this.store.set(hash, {
            key: hash,
            response,
            recordedAt: Date.now(),
        });
    }
    clear() {
        this.store.clear();
    }
}
// ─── CompositeLLMResponseCache ────────────────────────────────────────────────
/**
 * Composite (two-level) LLM response cache.
 *
 * Lookup order: child cache first, then parent cache (lookup-through).
 * Writes go only to the child cache. In replay mode the child is typically
 * read-only (no new entries), so `put()` is a no-op — pass `readOnly: true`.
 *
 * Use `withParent()` to construct a child cache that inherits responses from a
 * parent cycle without polluting the parent's store.
 */
export class CompositeLLMResponseCache {
    child;
    parent;
    readOnly;
    constructor(child, parent, readOnly = false) {
        this.child = child;
        this.parent = parent;
        this.readOnly = readOnly;
    }
    async get(key) {
        // Child takes priority (own cycle entries override parent)
        const childHit = await this.child.get(key);
        if (childHit !== undefined)
            return childHit;
        return this.parent.get(key);
    }
    async put(key, response) {
        if (this.readOnly)
            return;
        await this.child.put(key, response);
    }
    clear() {
        if (!this.readOnly && typeof this.child.clear === "function") {
            this.child.clear();
        }
    }
}
/**
 * Factory: creates a composite cache that looks up `parentCache` on miss.
 * The child is a fresh InMemoryLLMResponseCache — writes do NOT propagate to
 * the parent (write-back disabled in replay mode by default).
 *
 * @param parentCache  The parent cycle's cache (read-only access).
 * @param readOnly     When true, put() is a no-op (replay write-back disabled).
 *                     Defaults to true for replay safety.
 */
export function withParent(parentCache, readOnly = true) {
    return new CompositeLLMResponseCache(new InMemoryLLMResponseCache(), parentCache, readOnly);
}
//# sourceMappingURL=llm-cache.js.map