feat: deduplicate read_file history to optimize context length

Adds deduplicateReadFileHistory() method to Task class to retain only the latest read_file result per file, optimizing context length and token usage.

- Introduces deduplicateReadFileHistory() in Task class to keep only the latest read_file result for each file in apiConversationHistory
- Invoked in attemptApiRequest() to clean up history before making a new API request
- Iterates over apiConversationHistory to find and remove older read_file entries for the same file
- Ensures only the most recent read_file entry is retained, reducing context length and token usage

Co-authored-by: axb <qindi@staff.weibo.com>
This commit is contained in:
Roo Code 2025-07-18 05:36:49 +00:00
parent 38d8edf05a
commit c80b24af41

View file

@ -1670,6 +1670,8 @@ export class Task extends EventEmitter<ClineEvents> {
profileThresholds = {},
} = state ?? {}
this.deduplicateReadFileHistory()
// Get condensing configuration for automatic triggers
const customCondensingPrompt = state?.customCondensingPrompt
const condensingApiConfigId = state?.condensingApiConfigId
@ -1893,6 +1895,41 @@ export class Task extends EventEmitter<ClineEvents> {
yield* iterator
}
deduplicateReadFileHistory() {
for (let i = this.apiConversationHistory.length - 1; i >= 0; i--) {
const conversation = this.apiConversationHistory[i]
if (conversation.role !== "user") continue
const content = conversation.content
if (typeof content === "string") continue
const firstItem = content[0]
if (typeof firstItem === "string" || !("type" in firstItem) || firstItem.type !== "text") continue
const toolUseText = firstItem.text
if (!toolUseText || !toolUseText.startsWith("[read_file for ")) continue
for (let j = i - 1; j >= 0; j--) {
const prevConversation = this.apiConversationHistory[j]
if (prevConversation.role === "assistant") continue
const prevContent = prevConversation.content
if (typeof prevContent === "string") continue
const prevFirstItem = prevContent[0]
if (typeof prevFirstItem === "string" || !("type" in prevFirstItem) || prevFirstItem.type !== "text")
continue
if (prevFirstItem.text === toolUseText && prevContent.length === 3) {
prevContent.splice(1, 1)
break
}
}
}
}
// Checkpoints
public async checkpointSave(force: boolean = false) {