mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-06 08:18:39 +00:00
fix: remove cache logic from read_file deduplication feature
- Removed cache window logic from deduplicateReadFileHistory method - Removed getRecentFileContent method from Task.ts - Removed cache-related code from readFileTool.ts - Removed readFileDeduplicationCacheMinutes setting from all type definitions - Updated tests to remove cache-related test cases - Verified all cache-related code has been removed The deduplication feature now deduplicates all duplicate read_file results regardless of their age.
This commit is contained in:
parent
44f4bd5194
commit
c98889cebf
9 changed files with 1 additions and 659 deletions
|
|
@ -120,7 +120,6 @@ export const globalSettingsSchema = z.object({
|
|||
diffEnabled: z.boolean().optional(),
|
||||
fuzzyMatchThreshold: z.number().optional(),
|
||||
experiments: experimentsSchema.optional(),
|
||||
readFileDeduplicationCacheMinutes: z.number().optional(),
|
||||
|
||||
codebaseIndexModels: codebaseIndexModelsSchema.optional(),
|
||||
codebaseIndexConfig: codebaseIndexConfigSchema.optional(),
|
||||
|
|
|
|||
|
|
@ -329,79 +329,6 @@ export class Task extends EventEmitter<ClineEvents> {
|
|||
return readApiMessages({ taskId: this.taskId, globalStoragePath: this.globalStoragePath })
|
||||
}
|
||||
|
||||
public async getRecentFileContent(filePath: string): Promise<string | null> {
|
||||
// Check if the experimental feature is enabled
|
||||
const state = await this.providerRef.deref()?.getState()
|
||||
if (!state?.experiments || !experiments.isEnabled(state.experiments, EXPERIMENT_IDS.READ_FILE_DEDUPLICATION)) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Get the cache window from settings
|
||||
const cacheMinutes = state?.readFileDeduplicationCacheMinutes ?? 5
|
||||
if (cacheMinutes === 0) {
|
||||
// Cache is disabled
|
||||
return null
|
||||
}
|
||||
|
||||
const cacheWindowMs = cacheMinutes * 60 * 1000
|
||||
const now = Date.now()
|
||||
|
||||
// Check recent conversation history for this file
|
||||
for (let i = this.apiConversationHistory.length - 1; i >= 0; i--) {
|
||||
const message = this.apiConversationHistory[i]
|
||||
|
||||
// Only process user messages
|
||||
if (message.role !== "user") {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip messages outside the cache window
|
||||
if (message.ts && now - message.ts > cacheWindowMs) {
|
||||
break
|
||||
}
|
||||
|
||||
// Process content blocks
|
||||
if (Array.isArray(message.content)) {
|
||||
for (const block of message.content) {
|
||||
if (block.type === "text" && typeof block.text === "string") {
|
||||
// Check for read_file results in text blocks
|
||||
const readFileMatch = block.text.match(/\[read_file(?:\s+for\s+'([^']+)')?.*?\]\s*Result:/i)
|
||||
|
||||
if (readFileMatch) {
|
||||
// Extract file paths from the result content
|
||||
const resultContent = block.text.substring(block.text.indexOf("Result:") + 7).trim()
|
||||
|
||||
// Handle new XML format
|
||||
const xmlFileMatches = resultContent.matchAll(
|
||||
/<file>\s*<path>([^<]+)<\/path>[\s\S]*?<content[^>]*?>([\s\S]*?)<\/content>/g,
|
||||
)
|
||||
for (const match of xmlFileMatches) {
|
||||
const matchedPath = match[1].trim()
|
||||
const content = match[2].trim()
|
||||
if (matchedPath === filePath) {
|
||||
return content
|
||||
}
|
||||
}
|
||||
|
||||
// Handle legacy format (single file)
|
||||
if (
|
||||
readFileMatch[1] &&
|
||||
readFileMatch[1] === filePath &&
|
||||
!resultContent.includes("<files>")
|
||||
) {
|
||||
// For legacy format, the content is directly after "Result:"
|
||||
// Remove any leading/trailing whitespace
|
||||
return resultContent.trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
public async deduplicateReadFileHistory(): Promise<void> {
|
||||
// Check if the experimental feature is enabled
|
||||
const state = await this.providerRef.deref()?.getState()
|
||||
|
|
@ -409,10 +336,6 @@ export class Task extends EventEmitter<ClineEvents> {
|
|||
return
|
||||
}
|
||||
|
||||
// Get the cache window from settings, defaulting to 5 minutes if not set
|
||||
const cacheMinutes = state?.readFileDeduplicationCacheMinutes ?? 5
|
||||
const cacheWindowMs = cacheMinutes * 60 * 1000
|
||||
const now = Date.now()
|
||||
const seenFiles = new Map<string, { messageIndex: number; blockIndex: number }>()
|
||||
const blocksToRemove = new Map<number, Set<number>>() // messageIndex -> Set of blockIndexes to remove
|
||||
|
||||
|
|
@ -425,11 +348,6 @@ export class Task extends EventEmitter<ClineEvents> {
|
|||
continue
|
||||
}
|
||||
|
||||
// Skip messages within the cache window
|
||||
if (message.ts && now - message.ts < cacheWindowMs) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Process content blocks
|
||||
if (Array.isArray(message.content)) {
|
||||
for (let j = 0; j < message.content.length; j++) {
|
||||
|
|
|
|||
|
|
@ -1611,37 +1611,6 @@ describe("Cline", () => {
|
|||
}
|
||||
})
|
||||
|
||||
it("should preserve messages within cache window", async () => {
|
||||
const now = Date.now()
|
||||
cline.apiConversationHistory = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: "[read_file for 'test.ts'] Result:\n<files><file><path>test.ts</path><content>old content</content></file></files>",
|
||||
},
|
||||
],
|
||||
ts: now - 10 * 60 * 1000, // 10 minutes ago
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: "[read_file for 'test.ts'] Result:\n<files><file><path>test.ts</path><content>recent content</content></file></files>",
|
||||
},
|
||||
],
|
||||
ts: now - 2 * 60 * 1000, // 2 minutes ago (within 5 minute cache window)
|
||||
},
|
||||
]
|
||||
|
||||
await cline.deduplicateReadFileHistory()
|
||||
|
||||
// Should keep both messages (recent one is within cache window)
|
||||
expect(cline.apiConversationHistory).toHaveLength(2)
|
||||
})
|
||||
|
||||
it("should handle multi-file reads", async () => {
|
||||
const now = Date.now()
|
||||
cline.apiConversationHistory = [
|
||||
|
|
@ -1944,498 +1913,6 @@ describe("Cline", () => {
|
|||
expect(content[0].text).toContain("new2")
|
||||
}
|
||||
})
|
||||
|
||||
it("should use configurable cache time limit", async () => {
|
||||
// Test with 0 minutes (no cache window)
|
||||
mockProvider.getState.mockResolvedValue({
|
||||
experiments: {
|
||||
[EXPERIMENT_IDS.READ_FILE_DEDUPLICATION]: true,
|
||||
},
|
||||
readFileDeduplicationCacheMinutes: 0,
|
||||
})
|
||||
|
||||
const now = Date.now()
|
||||
cline.apiConversationHistory = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: "[read_file for 'test.ts'] Result:\n<files><file><path>test.ts</path><content>old content</content></file></files>",
|
||||
},
|
||||
],
|
||||
ts: now - 1000, // 1 second ago
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: "[read_file for 'test.ts'] Result:\n<files><file><path>test.ts</path><content>new content</content></file></files>",
|
||||
},
|
||||
],
|
||||
ts: now - 500, // 0.5 seconds ago
|
||||
},
|
||||
]
|
||||
|
||||
await cline.deduplicateReadFileHistory()
|
||||
|
||||
// With 0 cache window, should deduplicate even very recent reads
|
||||
expect(cline.apiConversationHistory).toHaveLength(1)
|
||||
const content = cline.apiConversationHistory[0].content
|
||||
if (Array.isArray(content) && content[0]?.type === "text") {
|
||||
expect(content[0].text).toContain("new content")
|
||||
}
|
||||
})
|
||||
|
||||
it("should use custom cache time limit from settings", async () => {
|
||||
// Test with 10 minutes cache window
|
||||
mockProvider.getState.mockResolvedValue({
|
||||
experiments: {
|
||||
[EXPERIMENT_IDS.READ_FILE_DEDUPLICATION]: true,
|
||||
},
|
||||
readFileDeduplicationCacheMinutes: 10,
|
||||
})
|
||||
|
||||
const now = Date.now()
|
||||
cline.apiConversationHistory = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: "[read_file for 'test.ts'] Result:\n<files><file><path>test.ts</path><content>old content</content></file></files>",
|
||||
},
|
||||
],
|
||||
ts: now - 15 * 60 * 1000, // 15 minutes ago
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: "[read_file for 'test.ts'] Result:\n<files><file><path>test.ts</path><content>recent content</content></file></files>",
|
||||
},
|
||||
],
|
||||
ts: now - 8 * 60 * 1000, // 8 minutes ago (within 10 minute window)
|
||||
},
|
||||
]
|
||||
|
||||
await cline.deduplicateReadFileHistory()
|
||||
|
||||
// Should keep both messages (recent one is within 10 minute cache window)
|
||||
expect(cline.apiConversationHistory).toHaveLength(2)
|
||||
})
|
||||
|
||||
it("should default to 5 minutes when setting is undefined", async () => {
|
||||
// Test with undefined setting (should default to 5 minutes)
|
||||
mockProvider.getState.mockResolvedValue({
|
||||
experiments: {
|
||||
[EXPERIMENT_IDS.READ_FILE_DEDUPLICATION]: true,
|
||||
},
|
||||
// readFileDeduplicationCacheMinutes is undefined
|
||||
})
|
||||
|
||||
const now = Date.now()
|
||||
cline.apiConversationHistory = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: "[read_file for 'test.ts'] Result:\n<files><file><path>test.ts</path><content>old content</content></file></files>",
|
||||
},
|
||||
],
|
||||
ts: now - 10 * 60 * 1000, // 10 minutes ago
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: "[read_file for 'test.ts'] Result:\n<files><file><path>test.ts</path><content>recent content</content></file></files>",
|
||||
},
|
||||
],
|
||||
ts: now - 3 * 60 * 1000, // 3 minutes ago (within default 5 minute window)
|
||||
},
|
||||
]
|
||||
|
||||
await cline.deduplicateReadFileHistory()
|
||||
|
||||
// Should keep both messages (recent one is within default 5 minute cache window)
|
||||
expect(cline.apiConversationHistory).toHaveLength(2)
|
||||
})
|
||||
|
||||
it("should handle large cache time limits", async () => {
|
||||
// Test with 60 minutes (1 hour) cache window
|
||||
mockProvider.getState.mockResolvedValue({
|
||||
experiments: {
|
||||
[EXPERIMENT_IDS.READ_FILE_DEDUPLICATION]: true,
|
||||
},
|
||||
readFileDeduplicationCacheMinutes: 60,
|
||||
})
|
||||
|
||||
const now = Date.now()
|
||||
cline.apiConversationHistory = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: "[read_file for 'test.ts'] Result:\n<files><file><path>test.ts</path><content>old content</content></file></files>",
|
||||
},
|
||||
],
|
||||
ts: now - 2 * 60 * 60 * 1000, // 2 hours ago
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: "[read_file for 'test.ts'] Result:\n<files><file><path>test.ts</path><content>recent content</content></file></files>",
|
||||
},
|
||||
],
|
||||
ts: now - 30 * 60 * 1000, // 30 minutes ago (within 60 minute window)
|
||||
},
|
||||
]
|
||||
|
||||
await cline.deduplicateReadFileHistory()
|
||||
|
||||
// Should keep both messages (recent one is within 60 minute cache window)
|
||||
expect(cline.apiConversationHistory).toHaveLength(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe("getRecentFileContent", () => {
|
||||
let mockProvider: any
|
||||
let mockApiConfig: any
|
||||
let task: Task
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
|
||||
mockApiConfig = {
|
||||
apiProvider: "anthropic",
|
||||
apiKey: "test-key",
|
||||
}
|
||||
|
||||
mockProvider = {
|
||||
context: {
|
||||
globalStorageUri: { fsPath: "/test/storage" },
|
||||
},
|
||||
getState: vi.fn().mockResolvedValue({
|
||||
experiments: {
|
||||
[EXPERIMENT_IDS.READ_FILE_DEDUPLICATION]: true,
|
||||
},
|
||||
readFileDeduplicationCacheMinutes: 5,
|
||||
}),
|
||||
postStateToWebview: vi.fn().mockResolvedValue(undefined),
|
||||
postMessageToWebview: vi.fn().mockResolvedValue(undefined),
|
||||
updateTaskHistory: vi.fn().mockResolvedValue(undefined),
|
||||
}
|
||||
|
||||
task = new Task({
|
||||
provider: mockProvider,
|
||||
apiConfiguration: mockApiConfig,
|
||||
task: "test task",
|
||||
startTask: false,
|
||||
})
|
||||
})
|
||||
|
||||
it("should return null when feature is disabled", async () => {
|
||||
mockProvider.getState.mockResolvedValue({
|
||||
experiments: {
|
||||
[EXPERIMENT_IDS.READ_FILE_DEDUPLICATION]: false,
|
||||
},
|
||||
})
|
||||
|
||||
const now = Date.now()
|
||||
task.apiConversationHistory = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: "[read_file for 'test.ts'] Result:\n<files><file><path>test.ts</path><content>test content</content></file></files>",
|
||||
},
|
||||
],
|
||||
ts: now - 1000, // 1 second ago
|
||||
},
|
||||
]
|
||||
|
||||
const result = await task.getRecentFileContent("test.ts")
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it("should return recent file content within cache window", async () => {
|
||||
const now = Date.now()
|
||||
task.apiConversationHistory = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: "[read_file for 'test.ts'] Result:\n<files><file><path>test.ts</path><content>recent content</content></file></files>",
|
||||
},
|
||||
],
|
||||
ts: now - 2 * 60 * 1000, // 2 minutes ago (within 5 minute window)
|
||||
},
|
||||
]
|
||||
|
||||
const result = await task.getRecentFileContent("test.ts")
|
||||
expect(result).toBe("recent content")
|
||||
})
|
||||
|
||||
it("should return null for files outside cache window", async () => {
|
||||
const now = Date.now()
|
||||
task.apiConversationHistory = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: "[read_file for 'test.ts'] Result:\n<files><file><path>test.ts</path><content>old content</content></file></files>",
|
||||
},
|
||||
],
|
||||
ts: now - 10 * 60 * 1000, // 10 minutes ago (outside 5 minute window)
|
||||
},
|
||||
]
|
||||
|
||||
const result = await task.getRecentFileContent("test.ts")
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it("should return most recent content when multiple reads exist", async () => {
|
||||
const now = Date.now()
|
||||
task.apiConversationHistory = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: "[read_file for 'test.ts'] Result:\n<files><file><path>test.ts</path><content>old content</content></file></files>",
|
||||
},
|
||||
],
|
||||
ts: now - 4 * 60 * 1000, // 4 minutes ago
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: [{ type: "text" as const, text: "Processing..." }],
|
||||
ts: now - 3 * 60 * 1000,
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: "[read_file for 'test.ts'] Result:\n<files><file><path>test.ts</path><content>newer content</content></file></files>",
|
||||
},
|
||||
],
|
||||
ts: now - 2 * 60 * 1000, // 2 minutes ago
|
||||
},
|
||||
]
|
||||
|
||||
const result = await task.getRecentFileContent("test.ts")
|
||||
expect(result).toBe("newer content")
|
||||
})
|
||||
|
||||
it("should handle multi-file reads", async () => {
|
||||
const now = Date.now()
|
||||
task.apiConversationHistory = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: "[read_file for 'file1.ts', 'file2.ts'] Result:\n<files><file><path>file1.ts</path><content>content1</content></file><file><path>file2.ts</path><content>content2</content></file></files>",
|
||||
},
|
||||
],
|
||||
ts: now - 2 * 60 * 1000,
|
||||
},
|
||||
]
|
||||
|
||||
const result1 = await task.getRecentFileContent("file1.ts")
|
||||
expect(result1).toBe("content1")
|
||||
|
||||
const result2 = await task.getRecentFileContent("file2.ts")
|
||||
expect(result2).toBe("content2")
|
||||
})
|
||||
|
||||
it("should return null for non-existent files", async () => {
|
||||
const now = Date.now()
|
||||
task.apiConversationHistory = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: "[read_file for 'test.ts'] Result:\n<files><file><path>test.ts</path><content>test content</content></file></files>",
|
||||
},
|
||||
],
|
||||
ts: now - 1000,
|
||||
},
|
||||
]
|
||||
|
||||
const result = await task.getRecentFileContent("other.ts")
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it("should handle legacy format", async () => {
|
||||
const now = Date.now()
|
||||
task.apiConversationHistory = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: "[read_file for 'legacy.ts'] Result:\nFile content without XML wrapper",
|
||||
},
|
||||
],
|
||||
ts: now - 1000,
|
||||
},
|
||||
]
|
||||
|
||||
const result = await task.getRecentFileContent("legacy.ts")
|
||||
expect(result).toBe("File content without XML wrapper")
|
||||
})
|
||||
|
||||
it("should ignore assistant messages", async () => {
|
||||
const now = Date.now()
|
||||
task.apiConversationHistory = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: "[read_file for 'test.ts'] Result:\n<files><file><path>test.ts</path><content>assistant content</content></file></files>",
|
||||
},
|
||||
],
|
||||
ts: now - 1000,
|
||||
},
|
||||
]
|
||||
|
||||
const result = await task.getRecentFileContent("test.ts")
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it("should handle messages without timestamps", async () => {
|
||||
task.apiConversationHistory = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: "[read_file for 'test.ts'] Result:\n<files><file><path>test.ts</path><content>test content</content></file></files>",
|
||||
},
|
||||
],
|
||||
// No ts property - should be treated as recent
|
||||
},
|
||||
]
|
||||
|
||||
const result = await task.getRecentFileContent("test.ts")
|
||||
expect(result).toBe("test content")
|
||||
})
|
||||
|
||||
it("should use custom cache time from settings", async () => {
|
||||
mockProvider.getState.mockResolvedValue({
|
||||
experiments: {
|
||||
[EXPERIMENT_IDS.READ_FILE_DEDUPLICATION]: true,
|
||||
},
|
||||
readFileDeduplicationCacheMinutes: 10,
|
||||
})
|
||||
|
||||
const now = Date.now()
|
||||
task.apiConversationHistory = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: "[read_file for 'test.ts'] Result:\n<files><file><path>test.ts</path><content>content within 10 min</content></file></files>",
|
||||
},
|
||||
],
|
||||
ts: now - 8 * 60 * 1000, // 8 minutes ago (within 10 minute window)
|
||||
},
|
||||
]
|
||||
|
||||
const result = await task.getRecentFileContent("test.ts")
|
||||
expect(result).toBe("content within 10 min")
|
||||
})
|
||||
|
||||
it("should handle 0 cache time (no caching)", async () => {
|
||||
mockProvider.getState.mockResolvedValue({
|
||||
experiments: {
|
||||
[EXPERIMENT_IDS.READ_FILE_DEDUPLICATION]: true,
|
||||
},
|
||||
readFileDeduplicationCacheMinutes: 0,
|
||||
})
|
||||
|
||||
const now = Date.now()
|
||||
task.apiConversationHistory = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: "[read_file for 'test.ts'] Result:\n<files><file><path>test.ts</path><content>very recent content</content></file></files>",
|
||||
},
|
||||
],
|
||||
ts: now - 100, // 0.1 seconds ago
|
||||
},
|
||||
]
|
||||
|
||||
const result = await task.getRecentFileContent("test.ts")
|
||||
expect(result).toBeNull() // With 0 cache time, nothing is cached
|
||||
})
|
||||
|
||||
it("should handle malformed content gracefully", async () => {
|
||||
const now = Date.now()
|
||||
task.apiConversationHistory = [
|
||||
{
|
||||
role: "user",
|
||||
content: "string content instead of array", // Invalid format
|
||||
ts: now - 1000,
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "image" as const,
|
||||
source: { type: "base64" as const, media_type: "image/png", data: "..." },
|
||||
},
|
||||
], // Non-text block
|
||||
ts: now - 500,
|
||||
},
|
||||
]
|
||||
|
||||
const result = await task.getRecentFileContent("test.ts")
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it("should handle empty conversation history", async () => {
|
||||
task.apiConversationHistory = []
|
||||
const result = await task.getRecentFileContent("test.ts")
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it("should handle file paths with special characters", async () => {
|
||||
const now = Date.now()
|
||||
task.apiConversationHistory = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: "[read_file for '@scope/package/file.ts'] Result:\n<files><file><path>@scope/package/file.ts</path><content>scoped content</content></file></files>",
|
||||
},
|
||||
],
|
||||
ts: now - 1000,
|
||||
},
|
||||
]
|
||||
|
||||
const result = await task.getRecentFileContent("@scope/package/file.ts")
|
||||
expect(result).toBe("scoped content")
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -130,9 +130,6 @@ describe("read_file tool with maxReadFileLine setting", () => {
|
|||
// Add the deduplicateReadFileHistory method to the mock
|
||||
mockCline.deduplicateReadFileHistory = vi.fn().mockReturnValue(undefined)
|
||||
|
||||
// Add the getRecentFileContent method to the mock
|
||||
mockCline.getRecentFileContent = vi.fn().mockResolvedValue(null)
|
||||
|
||||
toolResult = undefined
|
||||
})
|
||||
|
||||
|
|
@ -392,9 +389,6 @@ describe("read_file tool XML output structure", () => {
|
|||
// Add the deduplicateReadFileHistory method to the mock
|
||||
mockCline.deduplicateReadFileHistory = vi.fn().mockReturnValue(undefined)
|
||||
|
||||
// Add the getRecentFileContent method to the mock
|
||||
mockCline.getRecentFileContent = vi.fn().mockResolvedValue(null)
|
||||
|
||||
toolResult = undefined
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -431,45 +431,7 @@ export async function readFileTool(
|
|||
const fullPath = path.resolve(cline.cwd, relPath)
|
||||
const { maxReadFileLine = -1 } = (await cline.providerRef.deref()?.getState()) ?? {}
|
||||
|
||||
// Check if we have recent content for this file (deduplication)
|
||||
const recentContent = await cline.getRecentFileContent(relPath)
|
||||
if (recentContent !== null) {
|
||||
// We have recent content, use it instead of reading the file again
|
||||
const lines = recentContent.split("\n")
|
||||
const totalLines = lines.length
|
||||
|
||||
// Handle range reads
|
||||
if (fileResult.lineRanges && fileResult.lineRanges.length > 0) {
|
||||
const rangeResults: string[] = []
|
||||
for (const range of fileResult.lineRanges) {
|
||||
const selectedLines = lines.slice(range.start - 1, range.end).join("\n")
|
||||
const content = addLineNumbers(selectedLines, range.start)
|
||||
const lineRangeAttr = ` lines="${range.start}-${range.end}"`
|
||||
rangeResults.push(`<content${lineRangeAttr}>\n${content}</content>`)
|
||||
}
|
||||
updateFileResult(relPath, {
|
||||
xmlContent: `<file><path>${relPath}</path>\n${rangeResults.join("\n")}\n<notice>Using cached content from recent read</notice>\n</file>`,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle normal file read with cached content
|
||||
const lineRangeAttr = ` lines="1-${totalLines}"`
|
||||
let xmlInfo = totalLines > 0 ? `<content${lineRangeAttr}>\n${recentContent}</content>\n` : `<content/>`
|
||||
|
||||
if (totalLines === 0) {
|
||||
xmlInfo += `<notice>File is empty</notice>\n`
|
||||
} else {
|
||||
xmlInfo += `<notice>Using cached content from recent read</notice>\n`
|
||||
}
|
||||
|
||||
updateFileResult(relPath, {
|
||||
xmlContent: `<file><path>${relPath}</path>\n${xmlInfo}</file>`,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
// Process approved files (no cached content available)
|
||||
// Process approved files
|
||||
try {
|
||||
const [totalLines, isBinary] = await Promise.all([countFileLines(fullPath), isBinaryFile(fullPath)])
|
||||
|
||||
|
|
|
|||
|
|
@ -1444,9 +1444,6 @@ export class ClineProvider
|
|||
maxDiagnosticMessages,
|
||||
} = state
|
||||
|
||||
// Get readFileDeduplicationCacheMinutes with default value
|
||||
const readFileDeduplicationCacheMinutes = state.readFileDeduplicationCacheMinutes ?? 5
|
||||
|
||||
const telemetryKey = process.env.POSTHOG_API_KEY
|
||||
const machineId = vscode.env.machineId
|
||||
const mergedAllowedCommands = this.mergeAllowedCommands(allowedCommands)
|
||||
|
|
@ -1536,7 +1533,6 @@ export class ClineProvider
|
|||
language: language ?? formatLanguage(vscode.env.language),
|
||||
renderContext: this.renderContext,
|
||||
maxReadFileLine: maxReadFileLine ?? -1,
|
||||
readFileDeduplicationCacheMinutes: readFileDeduplicationCacheMinutes ?? 5,
|
||||
maxConcurrentFileReads: maxConcurrentFileReads ?? 5,
|
||||
settingsImportedAt: this.settingsImportedAt,
|
||||
terminalCompressProgressBar: terminalCompressProgressBar ?? true,
|
||||
|
|
@ -1707,7 +1703,6 @@ export class ClineProvider
|
|||
telemetrySetting: stateValues.telemetrySetting || "unset",
|
||||
showRooIgnoredFiles: stateValues.showRooIgnoredFiles ?? true,
|
||||
maxReadFileLine: stateValues.maxReadFileLine ?? -1,
|
||||
readFileDeduplicationCacheMinutes: stateValues.readFileDeduplicationCacheMinutes ?? 5,
|
||||
maxConcurrentFileReads: stateValues.maxConcurrentFileReads ?? 5,
|
||||
historyPreviewCollapsed: stateValues.historyPreviewCollapsed ?? false,
|
||||
cloudUserInfo,
|
||||
|
|
|
|||
|
|
@ -542,7 +542,6 @@ describe("ClineProvider", () => {
|
|||
profileThresholds: {},
|
||||
hasOpenedModeSelector: false,
|
||||
diagnosticsEnabled: true,
|
||||
readFileDeduplicationCacheMinutes: 5,
|
||||
}
|
||||
|
||||
const message: ExtensionMessage = {
|
||||
|
|
|
|||
|
|
@ -281,7 +281,6 @@ export type ExtensionState = Pick<
|
|||
maxWorkspaceFiles: number // Maximum number of files to include in current working directory details (0-500)
|
||||
showRooIgnoredFiles: boolean // Whether to show .rooignore'd files in listings
|
||||
maxReadFileLine: number // Maximum number of lines to read from a file before truncating
|
||||
readFileDeduplicationCacheMinutes: number // Cache window in minutes for read_file deduplication (0 = no cache)
|
||||
|
||||
experiments: Experiments // Map of experiment IDs to their enabled state
|
||||
|
||||
|
|
|
|||
|
|
@ -202,7 +202,6 @@ export interface WebviewMessage {
|
|||
| "saveCodeIndexSettingsAtomic"
|
||||
| "requestCodeIndexSecretStatus"
|
||||
| "requestCommands"
|
||||
| "readFileDeduplicationCacheMinutes"
|
||||
text?: string
|
||||
editedMessageContent?: string
|
||||
tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "account"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue