/** * Bounded TTL + singleflight cache for long-lived agent sessions. */ export class TtlLruCache { private readonly map = new Map(); private readonly inflight = new Map>(); constructor( private readonly ttlMs: number, private readonly maxSize: number, ) {} get(key: string): T | undefined { const hit = this.map.get(key); if (!hit) return undefined; if (hit.expires <= Date.now()) { this.map.delete(key); return undefined; } // LRU touch this.map.delete(key); this.map.set(key, hit); return hit.value; } set(key: string, value: T): void { if (this.map.has(key)) this.map.delete(key); this.map.set(key, { expires: Date.now() + this.ttlMs, value }); this.evict(); } async getOrLoad(key: string, loader: () => Promise): Promise { const cached = this.get(key); if (cached !== undefined) return cached; const pending = this.inflight.get(key); if (pending) return pending; const promise = (async () => { try { const value = await loader(); this.set(key, value); return value; } finally { this.inflight.delete(key); } })(); this.inflight.set(key, promise); return promise; } private evict(): void { const now = Date.now(); for (const [k, v] of this.map) { if (v.expires <= now) this.map.delete(k); } while (this.map.size > this.maxSize) { const oldest = this.map.keys().next().value as string | undefined; if (oldest === undefined) break; this.map.delete(oldest); } } } export function abortableSleep(ms: number, parentSignal?: AbortSignal): Promise { if (parentSignal?.aborted) { return Promise.reject(parentSignal.reason ?? new Error("aborted")); } return new Promise((resolve, reject) => { const timer = setTimeout(() => { parentSignal?.removeEventListener("abort", onAbort); resolve(); }, ms); const onAbort = () => { clearTimeout(timer); reject(parentSignal?.reason ?? new Error("aborted")); }; parentSignal?.addEventListener("abort", onAbort, { once: true }); }); } export function isTransientHttpStatus(status: number): boolean { return status === 408 || status === 429 || status >= 500; }