11export interface ToolTracker {
22 seenToolResultIds : Set < string >
3- toolResultCount : number
3+ toolResultCount : number // Tools since last prune
44 skipNextIdle : boolean
55 getToolName ?: ( callId : string ) => string | undefined
66}
@@ -9,9 +9,9 @@ export function createToolTracker(): ToolTracker {
99 return { seenToolResultIds : new Set ( ) , toolResultCount : 0 , skipNextIdle : false }
1010}
1111
12- export function resetToolTrackerCount ( tracker : ToolTracker , freq : number ) : void {
13- const currentBucket = Math . floor ( tracker . toolResultCount / freq )
14- tracker . toolResultCount = currentBucket * freq
12+ /** Reset tool count to 0 (called after a prune event) */
13+ export function resetToolTrackerCount ( tracker : ToolTracker ) : void {
14+ tracker . toolResultCount = 0
1515}
1616
1717/** Adapter interface for format-specific message operations */
@@ -20,23 +20,21 @@ interface MessageFormatAdapter {
2020 appendNudge ( messages : any [ ] , nudgeText : string ) : void
2121}
2222
23- /** Generic nudge injection - counts tool results and injects nudge every N results */
23+ /** Generic nudge injection - nudges every fetch once tools since last prune exceeds freq */
2424function injectNudgeCore (
2525 messages : any [ ] ,
2626 tracker : ToolTracker ,
2727 nudgeText : string ,
2828 freq : number ,
2929 adapter : MessageFormatAdapter
3030) : boolean {
31- const prevCount = tracker . toolResultCount
32- const newCount = adapter . countToolResults ( messages , tracker )
33- if ( newCount > 0 ) {
34- const prevBucket = Math . floor ( prevCount / freq )
35- const newBucket = Math . floor ( tracker . toolResultCount / freq )
36- if ( newBucket > prevBucket ) {
37- adapter . appendNudge ( messages , nudgeText )
38- return true
39- }
31+ // Count any new tool results
32+ adapter . countToolResults ( messages , tracker )
33+
34+ // Once we've exceeded the threshold, nudge on every fetch
35+ if ( tracker . toolResultCount > freq ) {
36+ adapter . appendNudge ( messages , nudgeText )
37+ return true
4038 }
4139 return false
4240}
@@ -79,27 +77,29 @@ const openaiAdapter: MessageFormatAdapter = {
7977 return newCount
8078 } ,
8179 appendNudge ( messages , nudgeText ) {
82- messages . push ( { role : 'user' , content : nudgeText , synthetic : true } )
80+ messages . push ( { role : 'user' , content : nudgeText } )
8381 }
8482}
8583
86- export function isIgnoredUserMessage ( msg : any ) : boolean {
87- if ( ! msg || msg . role !== 'user' ) return false
88- if ( msg . ignored || msg . info ?. ignored || msg . synthetic ) return true
89- if ( Array . isArray ( msg . content ) && msg . content . length > 0 ) {
90- if ( msg . content . every ( ( part : any ) => part ?. ignored ) ) return true
91- }
92- return false
93- }
94-
9584export function injectNudge ( messages : any [ ] , tracker : ToolTracker , nudgeText : string , freq : number ) : boolean {
9685 return injectNudgeCore ( messages , tracker , nudgeText , freq , openaiAdapter )
9786}
9887
99- export function injectSynth ( messages : any [ ] , instruction : string ) : boolean {
88+ /** Check if a message content matches nudge text (OpenAI/Anthropic format) */
89+ function isNudgeMessage ( msg : any , nudgeText : string ) : boolean {
90+ if ( typeof msg . content === 'string' ) {
91+ return msg . content === nudgeText
92+ }
93+ return false
94+ }
95+
96+ export function injectSynth ( messages : any [ ] , instruction : string , nudgeText : string ) : boolean {
10097 for ( let i = messages . length - 1 ; i >= 0 ; i -- ) {
10198 const msg = messages [ i ]
102- if ( msg . role === 'user' && ! isIgnoredUserMessage ( msg ) ) {
99+ if ( msg . role === 'user' ) {
100+ // Skip nudge messages - find real user message
101+ if ( isNudgeMessage ( msg , nudgeText ) ) continue
102+
103103 if ( typeof msg . content === 'string' ) {
104104 if ( msg . content . includes ( instruction ) ) return false
105105 msg . content = msg . content + '\n\n' + instruction
@@ -151,10 +151,22 @@ export function injectNudgeGemini(contents: any[], tracker: ToolTracker, nudgeTe
151151 return injectNudgeCore ( contents , tracker , nudgeText , freq , geminiAdapter )
152152}
153153
154- export function injectSynthGemini ( contents : any [ ] , instruction : string ) : boolean {
154+ /** Check if a Gemini content matches nudge text */
155+ function isNudgeContentGemini ( content : any , nudgeText : string ) : boolean {
156+ if ( Array . isArray ( content . parts ) && content . parts . length === 1 ) {
157+ const part = content . parts [ 0 ]
158+ return part ?. text === nudgeText
159+ }
160+ return false
161+ }
162+
163+ export function injectSynthGemini ( contents : any [ ] , instruction : string , nudgeText : string ) : boolean {
155164 for ( let i = contents . length - 1 ; i >= 0 ; i -- ) {
156165 const content = contents [ i ]
157166 if ( content . role === 'user' && Array . isArray ( content . parts ) ) {
167+ // Skip nudge messages - find real user message
168+ if ( isNudgeContentGemini ( content , nudgeText ) ) continue
169+
158170 const alreadyInjected = content . parts . some (
159171 ( part : any ) => part ?. text && typeof part . text === 'string' && part . text . includes ( instruction )
160172 )
@@ -198,10 +210,21 @@ export function injectNudgeResponses(input: any[], tracker: ToolTracker, nudgeTe
198210 return injectNudgeCore ( input , tracker , nudgeText , freq , responsesAdapter )
199211}
200212
201- export function injectSynthResponses ( input : any [ ] , instruction : string ) : boolean {
213+ /** Check if a Responses API item matches nudge text */
214+ function isNudgeItemResponses ( item : any , nudgeText : string ) : boolean {
215+ if ( typeof item . content === 'string' ) {
216+ return item . content === nudgeText
217+ }
218+ return false
219+ }
220+
221+ export function injectSynthResponses ( input : any [ ] , instruction : string , nudgeText : string ) : boolean {
202222 for ( let i = input . length - 1 ; i >= 0 ; i -- ) {
203223 const item = input [ i ]
204224 if ( item . type === 'message' && item . role === 'user' ) {
225+ // Skip nudge messages - find real user message
226+ if ( isNudgeItemResponses ( item , nudgeText ) ) continue
227+
205228 if ( typeof item . content === 'string' ) {
206229 if ( item . content . includes ( instruction ) ) return false
207230 item . content = item . content + '\n\n' + instruction
0 commit comments