-
-
Notifications
You must be signed in to change notification settings - Fork 220
Expand file tree
/
Copy pathsupersede-writes.ts
More file actions
126 lines (111 loc) · 4.09 KB
/
Copy pathsupersede-writes.ts
File metadata and controls
126 lines (111 loc) · 4.09 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
import { PluginConfig } from "../config"
import { Logger } from "../logger"
import type { SessionState, WithParts } from "../state"
import { getFilePathsFromParameters, isProtected } from "../protected-file-patterns"
import { getLastUserMessage } from "../shared-utils"
import { getTotalToolTokens } from "./utils"
/**
* Supersede Writes strategy - prunes write tool inputs for files that have
* subsequently been read. When a file is written and later read, the original
* write content becomes redundant since the current file state is captured
* in the read result.
*
* Modifies the session state in place to add pruned tool call IDs.
*/
export const supersedeWrites = (
state: SessionState,
logger: Logger,
config: PluginConfig,
messages: WithParts[],
): void => {
if (state.manualMode && !config.manualMode.automaticStrategies) {
return
}
if (!config.strategies.supersedeWrites.enabled) {
return
}
const allToolIds = state.toolIdList
if (allToolIds.length === 0) {
return
}
// Filter out IDs already pruned
const unprunedIds = allToolIds.filter((id) => !state.prune.tools.has(id))
if (unprunedIds.length === 0) {
return
}
// Track write tools by file path: filePath -> [{ id, index }]
// We track index to determine chronological order
const writesByFile = new Map<string, { id: string; index: number }[]>()
// Track read file paths with their index
const readsByFile = new Map<string, number[]>()
for (let i = 0; i < allToolIds.length; i++) {
const id = allToolIds[i]
const metadata = state.toolParameters.get(id)
if (!metadata) {
continue
}
const filePaths = getFilePathsFromParameters(metadata.tool, metadata.parameters)
if (filePaths.length === 0) {
continue
}
const filePath = filePaths[0]
if (isProtected(filePaths, config.protectedFilePatterns)) {
continue
}
if (metadata.tool === "write") {
if (!writesByFile.has(filePath)) {
writesByFile.set(filePath, [])
}
const writes = writesByFile.get(filePath)
if (writes) {
writes.push({ id, index: i })
}
} else if (metadata.tool === "read") {
if (!readsByFile.has(filePath)) {
readsByFile.set(filePath, [])
}
const reads = readsByFile.get(filePath)
if (reads) {
reads.push(i)
}
}
}
// Find writes that are superseded by subsequent reads
const newPruneIds: string[] = []
for (const [filePath, writes] of writesByFile.entries()) {
const reads = readsByFile.get(filePath)
if (!reads || reads.length === 0) {
continue
}
// For each write, check if there's a read that comes after it
for (const write of writes) {
// Skip if already pruned
if (state.prune.tools.has(write.id)) {
continue
}
// Check if any read comes after this write
const hasSubsequentRead = reads.some((readIndex) => readIndex > write.index)
if (hasSubsequentRead) {
newPruneIds.push(write.id)
}
}
}
if (newPruneIds.length > 0) {
const decisionMessageId = getLastUserMessage(messages)?.info.id || ""
if (!decisionMessageId) {
logger.warn("Supersede writes prune origin unavailable - missing user message")
}
state.stats.totalPruneTokens += getTotalToolTokens(state, newPruneIds)
for (const id of newPruneIds) {
const entry = state.toolParameters.get(id)
state.prune.tools.set(id, entry?.tokenCount ?? 0)
if (decisionMessageId) {
state.prune.origins.set(id, {
source: "supersedeWrites",
originMessageId: decisionMessageId,
})
}
}
logger.debug(`Marked ${newPruneIds.length} superseded write tool calls for pruning`)
}
}