/** * EmbeddingCache — content-hash embedding cache (RFC-002 §3). * * Pattern: transparent Decorator — implements `Embedder` itself, so * every influence-core function (and any other embedder * consumer) takes a plain `Embedder` and the cache is pure * injection: wrap once, thread the SAME instance through * lint + margins + edge weights, and a description embedded * for the catalog lint is free for the margin scorer. * Role: `src/lib/influence-core/` leaf. No agent/runtime imports. * * ## Bounded honesty (the library convention) * * The cache is BOUNDED — `maxEntries` caps memory (LRU eviction), and * the eviction count is VISIBLE via `stats()`. A consumer seeing * `evictions > 0` knows the cache is thrashing and can size it up; * nothing silently degrades out of view. * * ## Keying * * Entries are keyed by `contentHash(text)` — a fast non-cryptographic * 64-bit FNV-1a (length-qualified). NOT a security boundary: it * deduplicates embedding calls, it does not authenticate content. * Collisions are astronomically unlikely at cache scale (≤ maxEntries * live keys) but would silently return the colliding text's vector — * acceptable for a similarity proxy, never rely on it for exact-match * semantics. One cache per embedder instance: keys do NOT encode the * model, so sharing one cache across different embedders would mix * vector spaces (the same reason `MemoryStore` rejects mixed * dimensions). * * ## Concurrency * * In-flight requests coalesce: N concurrent `embed()` calls for the * same text issue ONE inner call (single-flight). Rejections are NOT * cached — a failed embedding retries on the next request. */ import type { Embedder, EmbedArgs, EmbedBatchArgs } from './types.js'; /** * Fast, deterministic, browser-safe content hash (FNV-1a, two 32-bit * lanes + length qualifier → "len-xxxxxxxxyyyyyyyy"). Non-cryptographic * — cache keying only. */ export declare function contentHash(text: string): string; export interface EmbeddingCacheOptions { /** * Maximum cached vectors. Oldest (least recently used) entries are * evicted past the cap; evictions are counted in `stats()`. * Default 1024 — a 30-tool catalog plus a long run's reasoning * steps fit with a wide margin. */ readonly maxEntries?: number; } /** Visible cache health — the bounded-honesty surface. */ export interface EmbeddingCacheStats { /** Vectors currently held. */ readonly size: number; /** The configured cap. */ readonly maxEntries: number; /** Served from cache (includes coalesced in-flight joins). */ readonly hits: number; /** Forwarded to the inner embedder. */ readonly misses: number; /** Entries dropped to respect `maxEntries`. >0 = consider sizing up. */ readonly evictions: number; } /** * Wrap an embedder with a bounded, content-hash-keyed LRU cache. * See module docs for keying, bounds, and concurrency semantics. */ export declare class EmbeddingCache implements Embedder { readonly dimensions: number; private readonly inner; private readonly maxEntries; /** LRU store — Map iteration order is recency (refreshed on hit). */ private readonly vectors; /** Single-flight joins — promises live here until settled. */ private readonly inflight; private hits; private misses; private evictions; constructor(inner: Embedder, options?: EmbeddingCacheOptions); embed(args: EmbedArgs): Promise; embedBatch(args: EmbedBatchArgs): Promise; /** Visible cache health (bounded honesty — see module docs). */ stats(): EmbeddingCacheStats; /** Drop all cached vectors. Stats counters are preserved. */ clear(): void; private refresh; private store; private embedSequential; } /** Factory sugar — `embeddingCache(embedder)` reads like the built-ins. */ export declare function embeddingCache(inner: Embedder, options?: EmbeddingCacheOptions): EmbeddingCache;