import { IEmbeddingGenerator } from '../interfaces/IEmbeddingGenerator.js'; import { IVectorStore } from '../interfaces/IVectorStore.js'; /** * Options for a cache write. * * THE TTL USED TO HAVE NOWHERE TO GO. `set` took two byte-size arguments and * nothing else, so thirty-one call sites passed their TTL into one of them: * `set(key, value, 86400, tokensSaved)` records 86400 as the entry's * compressed size, expires nothing ever, and corrupts every ratio computed * from the size columns. The intent was always real; the parameter was not. */ export interface CacheSetOptions { /** * Seconds until the entry stops being served. Omitted, zero or negative * means it never expires, which is what every existing four-argument call * site already gets. */ ttlSeconds?: number; } export interface CacheEntry { key: string; value: string; compressedSize: number; originalSize: number; hitCount: number; createdAt: number; lastAccessedAt: number; /** Epoch ms after which the entry stops being served; null means never. */ expiresAt?: number | null; } export interface CacheStats { totalEntries: number; totalHits: number; totalMisses: number; hitRate: number; totalCompressedSize: number; totalOriginalSize: number; compressionRatio: number; semanticHits?: number; semanticHitRate?: number; } export interface SemanticCachingConfig { similarityThreshold?: number; topK?: number; enabled?: boolean; } /** * WHERE A TOOL'S CACHE ACTUALLY GOES. * * TOKEN_OPTIMIZER_CACHE_DIR is documented as the way to relocate the cache, and * CacheEngine honours it -- but only when no path is passed. Forty-six tools * pass one: `new CacheEngine(join(homedir(), '.hypercontext', 'cache'), 100)`. * For every one of them the environment variable did nothing at all, silently, * so a caller who set it to isolate a run still read and wrote the shared cache * in their home directory. A bench that set it to get a cold cache measured a * warm one and published the cache hit as the tool's saving. * * The legacy default stays exactly where it is, so nobody's existing cache * moves; the override is what starts working. * * @param legacyDefault the location that site has always used */ export declare function resolveCacheLocation(legacyDefault: string): string; export declare class CacheEngine { private db; private memoryCache; private dbPath; /** * The last recency stamp this engine issued, so the next one is strictly larger. * * `Date.now()` HAS MILLISECOND RESOLUTION AND A CACHE DOES NOT. Twenty writes * and three reads take well under a millisecond, so every row carried the same * `last_accessed_at` and the eviction order fell through to the `key ASC` * tiebreaker -- which evicted entries that had just been read in favour of * entries nobody had touched, because "key10" sorts before "key15". The LRU * eviction test was skipped for "timing-dependent behavior" rather than fixed, * so the behaviour went unasserted. * * Per engine, not per database: two processes on one file can still interleave, * which was already true and strictly worse at millisecond resolution. Seeded * from the stored maximum on first use so a reopened cache does not hand out * stamps below rows already on disk. It can run ahead of the wall clock by one * millisecond per operation in a burst, which is harmless -- these values are * only ever compared with each other. */ private recencyStamp; /** * Set when the on-disk database could not be opened and an in-memory * database was used instead. Read by the doctor and by `cache_audit` so a * degraded server says so rather than silently losing every cache write. */ private degradedReason; private stats; private embeddingGenerator?; private vectorStore?; private semanticConfig; constructor(dbPath?: string, maxMemoryItems?: number, embeddingGenerator?: IEmbeddingGenerator, vectorStore?: IVectorStore, semanticConfig?: SemanticCachingConfig); /** * Get a value from cache (synchronous, exact match only) * For backward compatibility, this method only performs exact key matching * Use getWithSemantic() for semantic similarity search */ get(key: string): string | null; /** * Get a value from cache with semantic matching enabled * First tries exact key match, then semantic similarity if enabled */ getWithSemantic(key: string): Promise; /** * Get a value from cache using exact key match (synchronous) */ private getExact; /** * Get a value from cache using semantic similarity matching * Searches for similar queries and returns the closest match above threshold */ private getSemanticMatch; /** * Get a value from cache with metadata (including compression info) */ getWithMetadata(key: string): { content: string; compressedSize: number; } | null; /** * Set a value in cache (synchronous, without semantic embedding) * For backward compatibility */ set(key: string, value: string, originalSize: number, compressedSize: number, options?: CacheSetOptions): void; /** * Set a value in cache with semantic embedding * Also generates and stores embedding if semantic caching is enabled */ setWithSemantic(key: string, value: string, originalSize: number, compressedSize: number, options?: CacheSetOptions): Promise; /** * Delete a value from cache (synchronous) */ delete(key: string): boolean; /** * Delete a value from cache with semantic embedding removal * Also removes the embedding if semantic caching is enabled */ deleteWithSemantic(key: string): Promise; /** * Clear all cache (synchronous) */ clear(): void; /** * Clear all cache including vector store * Also clears the vector store if semantic caching is enabled */ clearWithSemantic(): Promise; /** * Why this cache is not persisting, or null when it is healthy. * * A degraded cache behaves exactly like a healthy one except that nothing * survives the process, so it is invisible from the outside. Anything that * reports on cache health has to ask. */ getDegradedReason(): string | null; /** Where the database actually lives -- `:memory:` when degraded. */ getDbPath(): string; /** * Get cache statistics */ getStats(): CacheStats; /** * Evict least recently used entries to stay under size limit */ evictLRU(maxSizeBytes: number): number; /** * Get all cache entries (for debugging/monitoring) */ getAllEntries(): CacheEntry[]; /** * Update hit count and last accessed time */ /** An entry past its expiry is a miss, not a stale hit. */ private isExpired; /** * Remove an entry that has outlived its TTL, from both tiers. * * Reads are expected to survive a cache that has become unwritable -- the * caller still gets the right answer (a miss) whether or not the row could * be deleted -- so the failure is reported and not thrown. */ private dropExpired; /** Strictly increasing, and never below what is already stored. */ private nextRecencyStamp; private updateHitCount; /** * Get the database path currently in use */ getDatabasePath(): string; /** * Close database connection */ close(): void; } //# sourceMappingURL=cache-engine.d.ts.map