Skip to content

Commit 2ac639e

Browse files
committed
supersede writes logic
1 parent 2c7ab58 commit 2ac639e

File tree

1 file changed

+78
-0
lines changed

1 file changed

+78
-0
lines changed

lib/strategies/supersede-writes.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { PluginConfig } from "../config"
22
import { Logger } from "../logger"
33
import type { SessionState, WithParts } from "../state"
4+
import { buildToolIdList } from "../messages/utils"
5+
import { calculateTokensSaved } from "./utils"
46

57
/**
68
* Supersede Writes strategy - prunes write tool inputs for files that have
@@ -19,4 +21,80 @@ export const supersedeWrites = (
1921
if (!config.strategies.supersedeWrites.enabled) {
2022
return
2123
}
24+
25+
// Build list of all tool call IDs from messages (chronological order)
26+
const allToolIds = buildToolIdList(state, messages, logger)
27+
if (allToolIds.length === 0) {
28+
return
29+
}
30+
31+
// Filter out IDs already pruned
32+
const alreadyPruned = new Set(state.prune.toolIds)
33+
34+
const unprunedIds = allToolIds.filter(id => !alreadyPruned.has(id))
35+
if (unprunedIds.length === 0) {
36+
return
37+
}
38+
39+
// Track write tools by file path: filePath -> [{ id, index }]
40+
// We track index to determine chronological order
41+
const writesByFile = new Map<string, { id: string, index: number }[]>()
42+
43+
// Track read file paths with their index
44+
const readsByFile = new Map<string, number[]>()
45+
46+
for (let i = 0; i < allToolIds.length; i++) {
47+
const id = allToolIds[i]
48+
const metadata = state.toolParameters.get(id)
49+
if (!metadata) {
50+
continue
51+
}
52+
53+
const filePath = metadata.parameters?.filePath
54+
if (!filePath) {
55+
continue
56+
}
57+
58+
if (metadata.tool === 'write') {
59+
if (!writesByFile.has(filePath)) {
60+
writesByFile.set(filePath, [])
61+
}
62+
writesByFile.get(filePath)!.push({ id, index: i })
63+
} else if (metadata.tool === 'read') {
64+
if (!readsByFile.has(filePath)) {
65+
readsByFile.set(filePath, [])
66+
}
67+
readsByFile.get(filePath)!.push(i)
68+
}
69+
}
70+
71+
// Find writes that are superseded by subsequent reads
72+
const newPruneIds: string[] = []
73+
74+
for (const [filePath, writes] of writesByFile.entries()) {
75+
const reads = readsByFile.get(filePath)
76+
if (!reads || reads.length === 0) {
77+
continue
78+
}
79+
80+
// For each write, check if there's a read that comes after it
81+
for (const write of writes) {
82+
// Skip if already pruned
83+
if (alreadyPruned.has(write.id)) {
84+
continue
85+
}
86+
87+
// Check if any read comes after this write
88+
const hasSubsequentRead = reads.some(readIndex => readIndex > write.index)
89+
if (hasSubsequentRead) {
90+
newPruneIds.push(write.id)
91+
}
92+
}
93+
}
94+
95+
if (newPruneIds.length > 0) {
96+
state.stats.totalPruneTokens += calculateTokensSaved(state, messages, newPruneIds)
97+
state.prune.toolIds.push(...newPruneIds)
98+
logger.debug(`Marked ${newPruneIds.length} superseded write tool calls for pruning`)
99+
}
22100
}

0 commit comments

Comments
 (0)