-
Notifications
You must be signed in to change notification settings - Fork 15.9k
Expand file tree
/
Copy pathfileStateCache.ts
More file actions
153 lines (130 loc) · 4.35 KB
/
fileStateCache.ts
File metadata and controls
153 lines (130 loc) · 4.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import { LRUCache } from 'lru-cache'
import { normalize } from 'path'
export type FileState = {
content: string
timestamp: number
offset: number | undefined
limit: number | undefined
// True when this entry was populated by auto-injection (e.g. CLAUDE.md) and
// the injected content did not match disk (stripped HTML comments, stripped
// frontmatter, truncated MEMORY.md). The model has only seen a partial view;
// Edit/Write must require an explicit Read first. `content` here holds the
// RAW disk bytes (for getChangedFiles diffing), not what the model saw.
isPartialView?: boolean
}
// Default max entries for read file state caches
export const READ_FILE_STATE_CACHE_SIZE = 100
// Default size limit for file state caches (25MB)
// This prevents unbounded memory growth from large file contents
const DEFAULT_MAX_CACHE_SIZE_BYTES = 25 * 1024 * 1024
/**
* A file state cache that normalizes all path keys before access.
* This ensures consistent cache hits regardless of whether callers pass
* relative vs absolute paths with redundant segments (e.g. /foo/../bar)
* or mixed path separators on Windows (/ vs \).
*/
export class FileStateCache {
private cache: LRUCache<string, FileState>
constructor(maxEntries: number, maxSizeBytes: number) {
this.cache = new LRUCache<string, FileState>({
max: maxEntries,
maxSize: maxSizeBytes,
sizeCalculation: value => {
const c = value.content
const s =
typeof c === 'string'
? c
: c === null || c === undefined
? ''
: typeof c === 'object'
? JSON.stringify(c)
: String(c)
return Math.max(1, Buffer.byteLength(s, 'utf8'))
},
})
}
get(key: string): FileState | undefined {
return this.cache.get(normalize(key))
}
set(key: string, value: FileState): this {
this.cache.set(normalize(key), value)
return this
}
has(key: string): boolean {
return this.cache.has(normalize(key))
}
delete(key: string): boolean {
return this.cache.delete(normalize(key))
}
clear(): void {
this.cache.clear()
}
get size(): number {
return this.cache.size
}
get max(): number {
return this.cache.max
}
get maxSize(): number {
return this.cache.maxSize
}
get calculatedSize(): number {
return this.cache.calculatedSize
}
keys(): Generator<string> {
return this.cache.keys()
}
entries(): Generator<[string, FileState]> {
return this.cache.entries()
}
dump(): ReturnType<LRUCache<string, FileState>['dump']> {
return this.cache.dump()
}
load(entries: ReturnType<LRUCache<string, FileState>['dump']>): void {
this.cache.load(entries)
}
}
/**
* Factory function to create a size-limited FileStateCache.
* Uses LRUCache's built-in size-based eviction to prevent memory bloat.
* Note: Images are not cached (see FileReadTool) so size limit is mainly
* for large text files, notebooks, and other editable content.
*/
export function createFileStateCacheWithSizeLimit(
maxEntries: number,
maxSizeBytes: number = DEFAULT_MAX_CACHE_SIZE_BYTES,
): FileStateCache {
return new FileStateCache(maxEntries, maxSizeBytes)
}
// Helper function to convert cache to object (used by compact.ts)
export function cacheToObject(
cache: FileStateCache,
): Record<string, FileState> {
return Object.fromEntries(cache.entries())
}
// Helper function to get all keys from cache (used by several components)
export function cacheKeys(cache: FileStateCache): string[] {
return Array.from(cache.keys())
}
// Helper function to clone a FileStateCache
// Preserves size limit configuration from the source cache
export function cloneFileStateCache(cache: FileStateCache): FileStateCache {
const cloned = createFileStateCacheWithSizeLimit(cache.max, cache.maxSize)
cloned.load(cache.dump())
return cloned
}
// Merge two file state caches, with more recent entries (by timestamp) overriding older ones
export function mergeFileStateCaches(
first: FileStateCache,
second: FileStateCache,
): FileStateCache {
const merged = cloneFileStateCache(first)
for (const [filePath, fileState] of second.entries()) {
const existing = merged.get(filePath)
// Only override if the new entry is more recent
if (!existing || fileState.timestamp > existing.timestamp) {
merged.set(filePath, fileState)
}
}
return merged
}