"use strict";
/**
* @file File state cache — deduplicates redundant file reads within a session.
*
* Inspired by Claude Code's file state cache pattern. Instead of caching file
* content (which doubles memory), we track file metadata (mtime, size, line count).
* When the model tries to re-read a file that hasn't changed, we return a stub
* telling it to use the data from the previous read.
*
* This saves significant context tokens — models frequently re-read the same
* files 3-5 times in a single conversation.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.FileStateCache = void 0;
const MAX_ENTRIES = 200;
/**
* Session-scoped file state cache.
* One instance per toolsProvider call (= one per conversation).
*/
class FileStateCache {
constructor() {
this.entries = new Map();
}
/**
* Check if a file was already fully read and hasn't changed since.
* Returns the cached entry if dedup applies, null otherwise.
*/
check(path, currentMtimeMs) {
const entry = this.entries.get(path);
if (!entry)
return null;
// File changed since last read — invalidate
if (entry.mtimeMs !== currentMtimeMs) {
this.entries.delete(path);
return null;
}
// Only dedup full reads (line-range reads are intentional)
if (!entry.fullRead)
return null;
entry.readCount++;
entry.lastAccessAt = Date.now();
return entry;
}
/**
* Record that a file was successfully read.
*/
record(path, mtimeMs, size, totalLines, fullRead) {
// LRU eviction if at capacity
if (this.entries.size >= MAX_ENTRIES && !this.entries.has(path)) {
let oldest = null;
let oldestTime = Infinity;
for (const [k, v] of this.entries) {
if (v.lastAccessAt < oldestTime) {
oldestTime = v.lastAccessAt;
oldest = k;
}
}
if (oldest)
this.entries.delete(oldest);
}
const existing = this.entries.get(path);
this.entries.set(path, {
path,
mtimeMs,
size,
totalLines,
lastAccessAt: Date.now(),
readCount: existing ? existing.readCount + 1 : 1,
fullRead: fullRead || (existing?.fullRead ?? false),
});
}
/**
* Invalidate a cached entry (after write/modify/delete).
*/
invalidate(path) {
this.entries.delete(path);
}
/**
* Get cache stats (for debugging / system info).
*/
get stats() {
let totalReads = 0;
let dedupedReads = 0;
for (const entry of this.entries.values()) {
totalReads += entry.readCount;
if (entry.readCount > 1)
dedupedReads += entry.readCount - 1;
}
return {
cachedFiles: this.entries.size,
totalReads,
dedupedReads,
};
}
}
exports.FileStateCache = FileStateCache;
//# sourceMappingURL=fileStateCache.js.map