Skip to content

Commit 03ef66a

Browse files
committed
feat(file-reads): Implement file-read caching
1 parent 44ea42f commit 03ef66a

File tree

2 files changed

+34
-21
lines changed

2 files changed

+34
-21
lines changed

src/core/services/__tests__/fileReadCacheService.spec.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,10 @@ describe("fileReadCacheService", () => {
8383
{
8484
fileName: MOCK_FILE_PATH,
8585
mtime: CURRENT_MTIME,
86-
loadedRanges: [{ start: 1, end: 10 }],
86+
lineRanges: [{ start: 1, end: 10 }],
8787
},
8888
],
89-
},
89+
} as any,
9090
]
9191
const result = await processAndFilterReadRequest(MOCK_FILE_PATH, requestedRanges, conversationHistory)
9292
expect(result.status).toBe("REJECT_ALL")
@@ -101,10 +101,10 @@ describe("fileReadCacheService", () => {
101101
{
102102
fileName: MOCK_FILE_PATH,
103103
mtime: CURRENT_MTIME,
104-
loadedRanges: [{ start: 1, end: 10 }],
104+
lineRanges: [{ start: 1, end: 10 }],
105105
},
106106
],
107-
},
107+
} as any,
108108
]
109109
const result = await processAndFilterReadRequest(MOCK_FILE_PATH, requestedRanges, conversationHistory)
110110
expect(result.status).toBe("ALLOW_PARTIAL")
@@ -119,10 +119,10 @@ describe("fileReadCacheService", () => {
119119
{
120120
fileName: MOCK_FILE_PATH,
121121
mtime: CURRENT_MTIME - 100, // Older mtime
122-
loadedRanges: [{ start: 1, end: 10 }],
122+
lineRanges: [{ start: 1, end: 10 }],
123123
},
124124
],
125-
},
125+
} as any,
126126
]
127127
const result = await processAndFilterReadRequest(MOCK_FILE_PATH, requestedRanges, conversationHistory)
128128
expect(result.status).toBe("ALLOW_ALL")
@@ -137,10 +137,10 @@ describe("fileReadCacheService", () => {
137137
{
138138
fileName: "/another/file.txt",
139139
mtime: CURRENT_MTIME,
140-
loadedRanges: [{ start: 1, end: 10 }],
140+
lineRanges: [{ start: 1, end: 10 }],
141141
},
142142
],
143-
},
143+
} as any,
144144
]
145145
const result = await processAndFilterReadRequest(MOCK_FILE_PATH, requestedRanges, conversationHistory)
146146
expect(result.status).toBe("ALLOW_ALL")
@@ -156,20 +156,20 @@ describe("fileReadCacheService", () => {
156156
{
157157
fileName: MOCK_FILE_PATH,
158158
mtime: CURRENT_MTIME - 100,
159-
loadedRanges: [{ start: 1, end: 20 }],
159+
lineRanges: [{ start: 1, end: 20 }],
160160
},
161161
],
162-
},
162+
} as any,
163163
{
164164
// Newer, correct mtime but only partial coverage
165165
files: [
166166
{
167167
fileName: MOCK_FILE_PATH,
168168
mtime: CURRENT_MTIME,
169-
loadedRanges: [{ start: 1, end: 5 }],
169+
lineRanges: [{ start: 1, end: 5 }],
170170
},
171171
],
172-
},
172+
} as any,
173173
]
174174
const result = await processAndFilterReadRequest(MOCK_FILE_PATH, requestedRanges, conversationHistory)
175175
expect(result.status).toBe("ALLOW_PARTIAL")

src/core/services/fileReadCacheService.ts

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export interface LineRange {
88

99
export interface FileMetadata {
1010
fileName: string
11-
mtime: string
11+
mtime: number
1212
lineRanges: LineRange[]
1313
}
1414

@@ -24,9 +24,9 @@ export interface ConversationMessage {
2424
}
2525

2626
type CacheResult =
27-
| { status: "ALLOW_ALL" }
27+
| { status: "ALLOW_ALL"; rangesToRead: LineRange[] }
2828
| { status: "ALLOW_PARTIAL"; rangesToRead: LineRange[] }
29-
| { status: "REJECT_ALL" }
29+
| { status: "REJECT_ALL"; rangesToRead: LineRange[] }
3030

3131
/**
3232
* Checks if two line ranges overlap.
@@ -44,7 +44,7 @@ function rangesOverlap(r1: LineRange, r2: LineRange): boolean {
4444
* @param toSubtract - The range to subtract.
4545
* @returns An array of ranges remaining after subtraction.
4646
*/
47-
function subtractRange(from: LineRange, toSubtract: LineRange): LineRange[] {
47+
export function subtractRange(from: LineRange, toSubtract: LineRange): LineRange[] {
4848
// No overlap
4949
if (from.end < toSubtract.start || from.start > toSubtract.end) {
5050
return [from]
@@ -61,6 +61,19 @@ function subtractRange(from: LineRange, toSubtract: LineRange): LineRange[] {
6161
return remainingRanges
6262
}
6363

64+
/**
65+
* Subtracts a set of ranges from another set of ranges.
66+
*/
67+
export function subtractRanges(originals: LineRange[], toRemoves: LineRange[]): LineRange[] {
68+
let remaining = [...originals]
69+
70+
for (const toRemove of toRemoves) {
71+
remaining = remaining.flatMap((original) => subtractRange(original, toRemove))
72+
}
73+
74+
return remaining
75+
}
76+
6477
/**
6578
* Processes a read request against cached file data in conversation history.
6679
* @param requestedFilePath - The full path of the file being requested.
@@ -85,7 +98,7 @@ export async function processAndFilterReadRequest(
8598
// This logic is simplified; in a real scenario, you'd get the line count.
8699
// For this example, we'll assume we can't determine the full range without reading the file,
87100
// so we proceed with ALLOW_ALL if no ranges are specified.
88-
return { status: "ALLOW_ALL" }
101+
return { status: "ALLOW_ALL", rangesToRead: requestedRanges }
89102
}
90103

91104
for (const message of conversationHistory) {
@@ -107,20 +120,20 @@ export async function processAndFilterReadRequest(
107120
}
108121

109122
if (rangesToRead.length === 0) {
110-
return { status: "REJECT_ALL" }
123+
return { status: "REJECT_ALL", rangesToRead: [] }
111124
} else if (rangesToRead.length < requestedRanges.length) {
112125
return { status: "ALLOW_PARTIAL", rangesToRead }
113126
} else {
114-
return { status: "ALLOW_ALL" }
127+
return { status: "ALLOW_ALL", rangesToRead: requestedRanges }
115128
}
116129
} catch (error) {
117130
// If we can't get file stats, it's safer to allow the read.
118131
if (error.code === "ENOENT") {
119132
// File doesn't exist, let the regular tool handle it.
120-
return { status: "ALLOW_ALL" }
133+
return { status: "ALLOW_ALL", rangesToRead: requestedRanges }
121134
}
122135
console.error(`Error processing file read request for ${requestedFilePath}:`, error)
123136
// On other errors, allow the read to proceed to handle it.
124-
return { status: "ALLOW_ALL" }
137+
return { status: "ALLOW_ALL", rangesToRead: requestedRanges }
125138
}
126139
}

0 commit comments

Comments
 (0)