Skip to content

Commit f7adff5

Browse files
authored
Merge pull request #375 from Opencode-DCP/dev
fix: search transformed message view for compress boundaries
2 parents 3313f76 + baff657 commit f7adff5

File tree

3 files changed

+61
-83
lines changed

3 files changed

+61
-83
lines changed

lib/messages/prune.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ const pruneFullTool = (state: SessionState, logger: Logger, messages: WithParts[
3838
if (part.type !== "tool") {
3939
continue
4040
}
41+
42+
if (part.tool === "compress" && part.state.status === "completed") {
43+
partsToRemove.push(part.callID)
44+
continue
45+
}
46+
4147
if (!state.prune.tools.has(part.callID)) {
4248
continue
4349
}

lib/tools/compress.ts

Lines changed: 51 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { loadPrompt } from "../prompts"
77
import { getCurrentParams, getTotalToolTokens, countMessageTextTokens } from "../strategies/utils"
88
import { findStringInMessages, collectToolIdsInRange, collectMessageIdsInRange } from "./utils"
99
import { sendCompressNotification } from "../ui/notification"
10+
import { prune as applyPruneTransforms } from "../messages/prune"
1011

1112
const COMPRESS_TOOL_DESCRIPTION = loadPrompt("compress-tool-spec")
1213

@@ -82,37 +83,56 @@ export function createCompressTool(ctx: PruneToolContext): ReturnType<typeof too
8283
ctx.config.manualMode.enabled,
8384
)
8485

86+
const transformedMessages = structuredClone(messages) as WithParts[]
87+
applyPruneTransforms(state, logger, ctx.config, transformedMessages)
88+
8589
const startResult = findStringInMessages(
86-
messages,
90+
transformedMessages,
8791
startString,
8892
logger,
89-
state.compressSummaries,
9093
"startString",
9194
)
9295
const endResult = findStringInMessages(
93-
messages,
96+
transformedMessages,
9497
endString,
9598
logger,
96-
state.compressSummaries,
9799
"endString",
98100
)
99101

100-
if (startResult.messageIndex > endResult.messageIndex) {
102+
let rawStartIndex = messages.findIndex((m) => m.info.id === startResult.messageId)
103+
let rawEndIndex = messages.findIndex((m) => m.info.id === endResult.messageId)
104+
105+
// If a boundary matched inside a synthetic compress summary message,
106+
// resolve it back to the summary's anchor message in the raw messages
107+
if (rawStartIndex === -1) {
108+
const summary = state.compressSummaries.find((s) => s.summary.includes(startString))
109+
if (summary) {
110+
rawStartIndex = messages.findIndex((m) => m.info.id === summary.anchorMessageId)
111+
}
112+
}
113+
if (rawEndIndex === -1) {
114+
const summary = state.compressSummaries.find((s) => s.summary.includes(endString))
115+
if (summary) {
116+
rawEndIndex = messages.findIndex((m) => m.info.id === summary.anchorMessageId)
117+
}
118+
}
119+
120+
if (rawStartIndex === -1 || rawEndIndex === -1) {
121+
throw new Error(`Failed to map boundary matches back to raw messages`)
122+
}
123+
124+
if (rawStartIndex > rawEndIndex) {
101125
throw new Error(
102126
`startString appears after endString in the conversation. Start must come before end.`,
103127
)
104128
}
105129

106-
const containedToolIds = collectToolIdsInRange(
107-
messages,
108-
startResult.messageIndex,
109-
endResult.messageIndex,
110-
)
130+
const containedToolIds = collectToolIdsInRange(messages, rawStartIndex, rawEndIndex)
111131

112132
const containedMessageIds = collectMessageIdsInRange(
113133
messages,
114-
startResult.messageIndex,
115-
endResult.messageIndex,
134+
rawStartIndex,
135+
rawEndIndex,
116136
)
117137

118138
// Remove any existing summaries whose anchors are now inside this range
@@ -132,38 +152,45 @@ export function createCompressTool(ctx: PruneToolContext): ReturnType<typeof too
132152
}
133153
state.compressSummaries.push(compressSummary)
134154

155+
const compressedMessageIds = containedMessageIds.filter(
156+
(id) => !state.prune.messages.has(id),
157+
)
158+
const compressedToolIds = containedToolIds.filter((id) => !state.prune.tools.has(id))
159+
135160
let textTokens = 0
136-
for (let i = startResult.messageIndex; i <= endResult.messageIndex; i++) {
137-
const msgId = messages[i].info.id
138-
if (!state.prune.messages.has(msgId)) {
139-
const tokens = countMessageTextTokens(messages[i])
161+
for (const msgId of compressedMessageIds) {
162+
const msg = messages.find((m) => m.info.id === msgId)
163+
if (msg) {
164+
const tokens = countMessageTextTokens(msg)
140165
textTokens += tokens
141166
state.prune.messages.set(msgId, tokens)
142167
}
143168
}
144-
const newToolIds = containedToolIds.filter((id) => !state.prune.tools.has(id))
145-
const toolTokens = getTotalToolTokens(state, newToolIds)
146-
for (const id of newToolIds) {
169+
const toolTokens = getTotalToolTokens(state, compressedToolIds)
170+
for (const id of compressedToolIds) {
147171
const entry = state.toolParameters.get(id)
148172
state.prune.tools.set(id, entry?.tokenCount ?? 0)
149173
}
150174
const estimatedCompressedTokens = textTokens + toolTokens
151175

152176
state.stats.pruneTokenCounter += estimatedCompressedTokens
153177

178+
const rawStartResult = { messageId: startResult.messageId, messageIndex: rawStartIndex }
179+
const rawEndResult = { messageId: endResult.messageId, messageIndex: rawEndIndex }
180+
154181
const currentParams = getCurrentParams(state, messages, logger)
155182
await sendCompressNotification(
156183
client,
157184
logger,
158185
ctx.config,
159186
state,
160187
sessionId,
161-
containedToolIds,
162-
containedMessageIds,
188+
compressedToolIds,
189+
compressedMessageIds,
163190
topic,
164191
summary,
165-
startResult,
166-
endResult,
192+
rawStartResult,
193+
rawEndResult,
167194
messages.length,
168195
currentParams,
169196
)
@@ -184,8 +211,7 @@ export function createCompressTool(ctx: PruneToolContext): ReturnType<typeof too
184211
logger.error("Failed to persist state", { error: err.message }),
185212
)
186213

187-
const messagesCompressed = endResult.messageIndex - startResult.messageIndex + 1
188-
return `Compressed ${messagesCompressed} messages (${containedToolIds.length} tool calls) into summary. The content will be replaced with your summary.`
214+
return `Compressed ${compressedMessageIds.length} messages (${compressedToolIds.length} tool calls) into summary. The content will be replaced with your summary.`
189215
},
190216
})
191217
}

lib/tools/utils.ts

Lines changed: 4 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { partial_ratio } from "fuzzball"
2-
import type { WithParts, CompressSummary } from "../state"
2+
import type { WithParts } from "../state"
33
import type { Logger } from "../logger"
44

55
export interface FuzzyConfig {
@@ -76,38 +76,13 @@ function extractMessageContent(msg: WithParts): string {
7676
return content
7777
}
7878

79-
function findExactMatches(
80-
messages: WithParts[],
81-
searchString: string,
82-
compressSummaries: CompressSummary[],
83-
): MatchResult[] {
79+
function findExactMatches(messages: WithParts[], searchString: string): MatchResult[] {
8480
const matches: MatchResult[] = []
85-
const seenMessageIds = new Set<string>()
86-
87-
// Search compress summaries first
88-
for (const summary of compressSummaries) {
89-
if (summary.summary.includes(searchString)) {
90-
const anchorIndex = messages.findIndex((m) => m.info.id === summary.anchorMessageId)
91-
if (anchorIndex !== -1 && !seenMessageIds.has(summary.anchorMessageId)) {
92-
seenMessageIds.add(summary.anchorMessageId)
93-
matches.push({
94-
messageId: summary.anchorMessageId,
95-
messageIndex: anchorIndex,
96-
score: 100,
97-
matchType: "exact",
98-
})
99-
}
100-
}
101-
}
10281

103-
// Search raw messages
10482
for (let i = 0; i < messages.length; i++) {
10583
const msg = messages[i]
106-
if (seenMessageIds.has(msg.info.id)) continue
107-
10884
const content = extractMessageContent(msg)
10985
if (content.includes(searchString)) {
110-
seenMessageIds.add(msg.info.id)
11186
matches.push({
11287
messageId: msg.info.id,
11388
messageIndex: i,
@@ -123,38 +98,15 @@ function findExactMatches(
12398
function findFuzzyMatches(
12499
messages: WithParts[],
125100
searchString: string,
126-
compressSummaries: CompressSummary[],
127101
minScore: number,
128102
): MatchResult[] {
129103
const matches: MatchResult[] = []
130-
const seenMessageIds = new Set<string>()
131104

132-
// Search compress summaries first
133-
for (const summary of compressSummaries) {
134-
const score = partial_ratio(searchString, summary.summary)
135-
if (score >= minScore) {
136-
const anchorIndex = messages.findIndex((m) => m.info.id === summary.anchorMessageId)
137-
if (anchorIndex !== -1 && !seenMessageIds.has(summary.anchorMessageId)) {
138-
seenMessageIds.add(summary.anchorMessageId)
139-
matches.push({
140-
messageId: summary.anchorMessageId,
141-
messageIndex: anchorIndex,
142-
score,
143-
matchType: "fuzzy",
144-
})
145-
}
146-
}
147-
}
148-
149-
// Search raw messages
150105
for (let i = 0; i < messages.length; i++) {
151106
const msg = messages[i]
152-
if (seenMessageIds.has(msg.info.id)) continue
153-
154107
const content = extractMessageContent(msg)
155108
const score = partial_ratio(searchString, content)
156109
if (score >= minScore) {
157-
seenMessageIds.add(msg.info.id)
158110
matches.push({
159111
messageId: msg.info.id,
160112
messageIndex: i,
@@ -171,14 +123,13 @@ export function findStringInMessages(
171123
messages: WithParts[],
172124
searchString: string,
173125
logger: Logger,
174-
compressSummaries: CompressSummary[] = [],
175126
stringType: "startString" | "endString",
176127
fuzzyConfig: FuzzyConfig = DEFAULT_FUZZY_CONFIG,
177128
): { messageId: string; messageIndex: number } {
178129
const searchableMessages = messages.length > 1 ? messages.slice(0, -1) : messages
179130
const lastMessage = messages.length > 0 ? messages[messages.length - 1] : undefined
180131

181-
const exactMatches = findExactMatches(searchableMessages, searchString, compressSummaries)
132+
const exactMatches = findExactMatches(searchableMessages, searchString)
182133

183134
if (exactMatches.length === 1) {
184135
return { messageId: exactMatches[0].messageId, messageIndex: exactMatches[0].messageIndex }
@@ -191,12 +142,7 @@ export function findStringInMessages(
191142
)
192143
}
193144

194-
const fuzzyMatches = findFuzzyMatches(
195-
searchableMessages,
196-
searchString,
197-
compressSummaries,
198-
fuzzyConfig.minScore,
199-
)
145+
const fuzzyMatches = findFuzzyMatches(searchableMessages, searchString, fuzzyConfig.minScore)
200146

201147
if (fuzzyMatches.length === 0) {
202148
if (lastMessage) {

0 commit comments

Comments
 (0)