UNPKG

jay-code

Version:

Streamlined AI CLI orchestration engine with mathematical rigor and enterprise-grade reliability

240 lines (200 loc) 4.99 kB
/** * Memory cache implementation with LRU eviction */ import type { MemoryEntry } from '../utils/types.js'; import type { ILogger } from '../core/logger.js'; interface CacheEntry { data: MemoryEntry; size: number; lastAccessed: number; dirty: boolean; } /** * LRU cache for memory entries */ export class MemoryCache { private cache = new Map<string, CacheEntry>(); private currentSize = 0; private hits = 0; private misses = 0; constructor( private maxSize: number, private logger: ILogger, ) {} /** * Gets an entry from the cache */ get(id: string): MemoryEntry | undefined { const entry = this.cache.get(id); if (!entry) { this.misses++; return undefined; } // Update access time entry.lastAccessed = Date.now(); this.hits++; return entry.data; } /** * Sets an entry in the cache */ set(id: string, data: MemoryEntry, dirty = true): void { const size = this.calculateSize(data); // Check if we need to evict entries if (this.currentSize + size > this.maxSize) { this.evict(size); } const entry: CacheEntry = { data, size, lastAccessed: Date.now(), dirty, }; // Update size if replacing existing entry const existing = this.cache.get(id); if (existing) { this.currentSize -= existing.size; } this.cache.set(id, entry); this.currentSize += size; } /** * Deletes an entry from the cache */ delete(id: string): void { const entry = this.cache.get(id); if (entry) { this.currentSize -= entry.size; this.cache.delete(id); } } /** * Gets entries by prefix */ getByPrefix(prefix: string): MemoryEntry[] { const results: MemoryEntry[] = []; for (const [id, entry] of this.cache) { if (id.startsWith(prefix)) { entry.lastAccessed = Date.now(); results.push(entry.data); } } return results; } /** * Gets all dirty entries */ getDirtyEntries(): MemoryEntry[] { const dirtyEntries: MemoryEntry[] = []; for (const entry of this.cache.values()) { if (entry.dirty) { dirtyEntries.push(entry.data); } } return dirtyEntries; } /** * Marks entries as clean */ markClean(ids: string[]): void { for (const id of ids) { const entry = this.cache.get(id); if (entry) { entry.dirty = false; } } } /** * Gets all entries */ getAllEntries(): MemoryEntry[] { return Array.from(this.cache.values()).map((entry) => entry.data); } /** * Gets cache metrics */ getMetrics(): { size: number; entries: number; hitRate: number; maxSize: number; } { const totalRequests = this.hits + this.misses; const hitRate = totalRequests > 0 ? this.hits / totalRequests : 0; return { size: this.currentSize, entries: this.cache.size, hitRate, maxSize: this.maxSize, }; } /** * Clears the cache */ clear(): void { this.cache.clear(); this.currentSize = 0; this.hits = 0; this.misses = 0; } /** * Performs cache maintenance */ performMaintenance(): void { // Remove expired entries if needed // For now, just log metrics const metrics = this.getMetrics(); this.logger.debug('Cache maintenance', metrics); } private calculateSize(entry: MemoryEntry): number { // Rough estimate of memory size let size = 0; // String fields size += entry.id.length * 2; // UTF-16 size += entry.agentId.length * 2; size += entry.sessionId.length * 2; size += entry.type.length * 2; size += entry.content.length * 2; // Tags size += entry.tags.reduce((sum, tag) => sum + tag.length * 2, 0); // JSON objects (rough estimate) size += JSON.stringify(entry.context).length * 2; if (entry.metadata) { size += JSON.stringify(entry.metadata).length * 2; } // Fixed size fields size += 8; // timestamp size += 4; // version size += 100; // overhead return size; } private evict(requiredSpace: number): void { this.logger.debug('Cache eviction triggered', { requiredSpace, currentSize: this.currentSize, }); // Sort entries by last accessed time (oldest first) const entries = Array.from(this.cache.entries()).sort( (a, b) => a[1].lastAccessed - b[1].lastAccessed, ); let freedSpace = 0; const evicted: string[] = []; for (const [id, entry] of entries) { if (freedSpace >= requiredSpace) { break; } // Don't evict dirty entries if possible if (entry.dirty && evicted.length > 0) { continue; } this.cache.delete(id); this.currentSize -= entry.size; freedSpace += entry.size; evicted.push(id); } this.logger.debug('Cache entries evicted', { count: evicted.length, freedSpace, }); } }