@@ -2,16 +2,13 @@ import { partial_ratio } from "fuzzball"
22import type { WithParts , CompressSummary } from "../state"
33import type { Logger } from "../logger"
44
5- /**
6- * Configuration for fuzzy matching behavior
7- */
85export interface FuzzyConfig {
9- minScore : number // Minimum score to accept (0-100)
10- minGap : number // Minimum gap between best and second-best match
6+ minScore : number
7+ minGap : number
118}
129
1310export const DEFAULT_FUZZY_CONFIG : FuzzyConfig = {
14- minScore : 85 ,
11+ minScore : 95 ,
1512 minGap : 15 ,
1613}
1714
@@ -22,10 +19,6 @@ interface MatchResult {
2219 matchType : "exact" | "fuzzy"
2320}
2421
25- /**
26- * Extracts all textual content from a message for matching purposes.
27- * Includes: text, reasoning, tool (input/output), compaction, and subtask parts.
28- */
2922function extractMessageContent ( msg : WithParts ) : string {
3023 const parts = Array . isArray ( msg . parts ) ? msg . parts : [ ]
3124 let content = ""
@@ -83,9 +76,6 @@ function extractMessageContent(msg: WithParts): string {
8376 return content
8477}
8578
86- /**
87- * Find all exact substring matches across messages and compress summaries.
88- */
8979function findExactMatches (
9080 messages : WithParts [ ] ,
9181 searchString : string ,
@@ -130,9 +120,6 @@ function findExactMatches(
130120 return matches
131121}
132122
133- /**
134- * Find all fuzzy substring matches above the minimum score threshold.
135- */
136123function findFuzzyMatches (
137124 messages : WithParts [ ] ,
138125 searchString : string ,
@@ -180,12 +167,6 @@ function findFuzzyMatches(
180167 return matches
181168}
182169
183- /**
184- * Searches messages for a string and returns the message ID where it's found.
185- * Uses exact matching first, then falls back to fuzzy matching with confidence thresholds.
186- * Searches in text parts, tool outputs, tool inputs, and compress summaries.
187- * Throws an error if no confident match is found.
188- */
189170export function findStringInMessages (
190171 messages : WithParts [ ] ,
191172 searchString : string ,
@@ -194,47 +175,68 @@ export function findStringInMessages(
194175 stringType : "startString" | "endString" ,
195176 fuzzyConfig : FuzzyConfig = DEFAULT_FUZZY_CONFIG ,
196177) : { messageId : string ; messageIndex : number } {
197- // ============ PHASE 1: Exact Match ============
198- const exactMatches = findExactMatches ( messages , searchString , compressSummaries )
178+ const searchableMessages = messages . length > 1 ? messages . slice ( 0 , - 1 ) : messages
179+ const lastMessage = messages . length > 0 ? messages [ messages . length - 1 ] : undefined
180+
181+ const exactMatches = findExactMatches ( searchableMessages , searchString , compressSummaries )
199182
200183 if ( exactMatches . length === 1 ) {
201184 return { messageId : exactMatches [ 0 ] . messageId , messageIndex : exactMatches [ 0 ] . messageIndex }
202185 }
203186
204187 if ( exactMatches . length > 1 ) {
205188 throw new Error (
206- `Found multiple exact matches for ${ stringType } . ` +
207- `Provide more surrounding context to uniquely identify the intended match.` ,
189+ `Found multiple matches for ${ stringType } . ` +
190+ `Provide more surrounding context to uniquely identify the intended match.` ,
208191 )
209192 }
210193
211- // ============ PHASE 2: Fuzzy Match ============
212194 const fuzzyMatches = findFuzzyMatches (
213- messages ,
195+ searchableMessages ,
214196 searchString ,
215197 compressSummaries ,
216198 fuzzyConfig . minScore ,
217199 )
218200
219201 if ( fuzzyMatches . length === 0 ) {
202+ if ( lastMessage ) {
203+ const lastMsgContent = extractMessageContent ( lastMessage )
204+ const lastMsgIndex = messages . length - 1
205+ if ( lastMsgContent . includes ( searchString ) ) {
206+ // logger.info(
207+ // `${stringType} found in last message (last resort) at index ${lastMsgIndex}`,
208+ // )
209+ return {
210+ messageId : lastMessage . info . id ,
211+ messageIndex : lastMsgIndex ,
212+ }
213+ }
214+ }
215+
220216 throw new Error (
221- `${ stringType } not found in conversation (exact or fuzzy) . ` +
222- `Make sure the string exists and is spelled correctly.` ,
217+ `${ stringType } not found in conversation. ` +
218+ `Make sure the string exists and is spelled correctly.` ,
223219 )
224220 }
225221
226- // Sort by score descending to find best match
227222 fuzzyMatches . sort ( ( a , b ) => b . score - a . score )
228223
229224 const best = fuzzyMatches [ 0 ]
230225 const secondBest = fuzzyMatches [ 1 ]
231226
227+ // Log fuzzy match candidates
228+ // logger.info(
229+ // `Fuzzy match for ${stringType}: best=${best.score}% (msg ${best.messageIndex})` +
230+ // (secondBest
231+ // ? `, secondBest=${secondBest.score}% (msg ${secondBest.messageIndex})`
232+ // : ""),
233+ // )
234+
232235 // Check confidence gap - best must be significantly better than second best
233236 if ( secondBest && best . score - secondBest . score < fuzzyConfig . minGap ) {
234237 throw new Error (
235- `Ambiguous fuzzy match for ${ stringType } : ` +
236- `two candidates scored similarly (${ best . score } % vs ${ secondBest . score } %). ` +
237- `Provide more unique text to disambiguate.` ,
238+ `Found multiple matches for ${ stringType } . ` +
239+ `Provide more unique surrounding context to disambiguate.` ,
238240 )
239241 }
240242
@@ -245,9 +247,6 @@ export function findStringInMessages(
245247 return { messageId : best . messageId , messageIndex : best . messageIndex }
246248}
247249
248- /**
249- * Collects all tool callIDs from messages between start and end indices (inclusive).
250- */
251250export function collectToolIdsInRange (
252251 messages : WithParts [ ] ,
253252 startIndex : number ,
@@ -271,9 +270,6 @@ export function collectToolIdsInRange(
271270 return toolIds
272271}
273272
274- /**
275- * Collects all message IDs from messages between start and end indices (inclusive).
276- */
277273export function collectMessageIdsInRange (
278274 messages : WithParts [ ] ,
279275 startIndex : number ,
@@ -291,10 +287,6 @@ export function collectMessageIdsInRange(
291287 return messageIds
292288}
293289
294- /**
295- * Collects all textual content (text parts, tool inputs, and tool outputs)
296- * from a range of messages. Used for token estimation.
297- */
298290export function collectContentInRange (
299291 messages : WithParts [ ] ,
300292 startIndex : number ,
0 commit comments