| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import crypto from "node:crypto"; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| export class LRUCache { |
| |
| #cache = new Map(); |
| #maxSize; |
| #defaultTTL; |
| #currentSize = 0; |
| #stats = { hits: 0, misses: 0, evictions: 0 }; |
|
|
| |
| |
| |
| |
| |
| constructor(options: any = {}) { |
| this.#maxSize = options.maxSize ?? 100; |
| this.#defaultTTL = options.defaultTTL ?? 300000; |
| } |
|
|
| |
| |
| |
| |
| |
| static generateKey(params) { |
| const normalized = JSON.stringify(params, Object.keys(params).sort()); |
| return crypto.createHash("sha256").update(normalized).digest("hex").slice(0, 16); |
| } |
|
|
| |
| |
| |
| |
| |
| get(key) { |
| const entry = this.#cache.get(key); |
|
|
| if (!entry) { |
| this.#stats.misses++; |
| return undefined; |
| } |
|
|
| |
| if (Date.now() - entry.createdAt > entry.ttl) { |
| this.#cache.delete(key); |
| this.#currentSize--; |
| this.#stats.misses++; |
| return undefined; |
| } |
|
|
| |
| this.#cache.delete(key); |
| entry.hits++; |
| this.#cache.set(key, entry); |
|
|
| this.#stats.hits++; |
| return entry.value; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| set(key, value, ttl) { |
| |
| if (this.#cache.has(key)) { |
| this.#cache.delete(key); |
| this.#currentSize--; |
| } |
|
|
| |
| while (this.#currentSize >= this.#maxSize) { |
| const oldestKey = this.#cache.keys().next().value; |
| this.#cache.delete(oldestKey); |
| this.#currentSize--; |
| this.#stats.evictions++; |
| } |
|
|
| const entry = { |
| key, |
| value, |
| createdAt: Date.now(), |
| ttl: ttl ?? this.#defaultTTL, |
| size: JSON.stringify(value).length, |
| hits: 0, |
| }; |
|
|
| this.#cache.set(key, entry); |
| this.#currentSize++; |
| } |
|
|
| |
| |
| |
| |
| |
| has(key) { |
| const entry = this.#cache.get(key); |
| if (!entry) return false; |
| if (Date.now() - entry.createdAt > entry.ttl) { |
| this.#cache.delete(key); |
| this.#currentSize--; |
| return false; |
| } |
| return true; |
| } |
|
|
| |
| |
| |
| |
| |
| delete(key) { |
| if (this.#cache.has(key)) { |
| this.#cache.delete(key); |
| this.#currentSize--; |
| return true; |
| } |
| return false; |
| } |
|
|
| |
| clear() { |
| this.#cache.clear(); |
| this.#currentSize = 0; |
| } |
|
|
| |
| getStats() { |
| const total = this.#stats.hits + this.#stats.misses; |
| return { |
| size: this.#currentSize, |
| maxSize: this.#maxSize, |
| ...this.#stats, |
| hitRate: total > 0 ? (this.#stats.hits / total) * 100 : 0, |
| }; |
| } |
| } |
|
|
| |
|
|
| let promptCache; |
|
|
| |
| |
| |
| |
| |
| export function getPromptCache(options?: any) { |
| if (!promptCache) { |
| promptCache = new LRUCache({ |
| maxSize: parseInt(process.env.PROMPT_CACHE_MAX_SIZE || "200", 10), |
| defaultTTL: parseInt(process.env.PROMPT_CACHE_TTL_MS || "600000", 10), |
| ...options, |
| }); |
| } |
| return promptCache; |
| } |
|
|