Skip to content

Commit f7b9bef

Browse files
committed
refactor: introduce adapter pattern for nudge injection across message formats
Consolidate duplicate code for OpenAI Chat, Gemini, and Responses API formats by introducing a MessageFormatAdapter interface. This creates a single generic injectNudgeCore function that works with any adapter, reducing code duplication and improving maintainability. - Create MessageFormatAdapter interface with countToolResults and appendNudge - Implement openaiAdapter, geminiAdapter, and responsesAdapter - Condense verbose multi-line conditionals into compact single-line form - Remove redundant comments while preserving functionality
1 parent 9183550 commit f7b9bef

File tree

1 file changed

+93
-184
lines changed

1 file changed

+93
-184
lines changed

lib/synth-instruction.ts

Lines changed: 93 additions & 184 deletions
Original file line numberDiff line numberDiff line change
@@ -4,123 +4,96 @@ export interface ToolTracker {
44
}
55

66
export function createToolTracker(): ToolTracker {
7-
return {
8-
seenToolResultIds: new Set(),
9-
toolResultCount: 0
10-
}
7+
return { seenToolResultIds: new Set(), toolResultCount: 0 }
118
}
129

13-
// ============================================================================
14-
// OpenAI Chat / Anthropic Format
15-
// ============================================================================
16-
17-
function countToolResults(messages: any[], tracker: ToolTracker): number {
18-
let newCount = 0
19-
20-
for (const m of messages) {
21-
if (m.role === 'tool' && m.tool_call_id) {
22-
const id = String(m.tool_call_id).toLowerCase()
23-
if (!tracker.seenToolResultIds.has(id)) {
24-
tracker.seenToolResultIds.add(id)
25-
newCount++
26-
}
27-
} else if (m.role === 'user' && Array.isArray(m.content)) {
28-
for (const part of m.content) {
29-
if (part.type === 'tool_result' && part.tool_use_id) {
30-
const id = String(part.tool_use_id).toLowerCase()
31-
if (!tracker.seenToolResultIds.has(id)) {
32-
tracker.seenToolResultIds.add(id)
33-
newCount++
34-
}
35-
}
36-
}
37-
}
38-
}
39-
40-
tracker.toolResultCount += newCount
41-
return newCount
10+
/** Adapter interface for format-specific message operations */
11+
interface MessageFormatAdapter {
12+
countToolResults(messages: any[], tracker: ToolTracker): number
13+
appendNudge(messages: any[], nudgeText: string): void
4214
}
4315

44-
/**
45-
* Counts new tool results and injects nudge instruction every N tool results.
46-
* Returns true if injection happened.
47-
*/
48-
export function injectNudge(
16+
/** Generic nudge injection - counts tool results and injects nudge every N results */
17+
function injectNudgeCore(
4918
messages: any[],
5019
tracker: ToolTracker,
5120
nudgeText: string,
52-
freq: number
21+
freq: number,
22+
adapter: MessageFormatAdapter
5323
): boolean {
5424
const prevCount = tracker.toolResultCount
55-
const newCount = countToolResults(messages, tracker)
56-
25+
const newCount = adapter.countToolResults(messages, tracker)
5726
if (newCount > 0) {
58-
// Check if we crossed a multiple of freq
5927
const prevBucket = Math.floor(prevCount / freq)
6028
const newBucket = Math.floor(tracker.toolResultCount / freq)
6129
if (newBucket > prevBucket) {
62-
// Inject at the END of messages so it's in immediate context
63-
return appendNudge(messages, nudgeText)
30+
adapter.appendNudge(messages, nudgeText)
31+
return true
6432
}
6533
}
6634
return false
6735
}
6836

69-
export function isIgnoredUserMessage(msg: any): boolean {
70-
if (!msg || msg.role !== 'user') {
71-
return false
72-
}
37+
// ============================================================================
38+
// OpenAI Chat / Anthropic Format
39+
// ============================================================================
7340

74-
// Skip ignored or synthetic messages
75-
if (msg.ignored || msg.info?.ignored || msg.synthetic) {
76-
return true
41+
const openaiAdapter: MessageFormatAdapter = {
42+
countToolResults(messages, tracker) {
43+
let newCount = 0
44+
for (const m of messages) {
45+
if (m.role === 'tool' && m.tool_call_id) {
46+
const id = String(m.tool_call_id).toLowerCase()
47+
if (!tracker.seenToolResultIds.has(id)) {
48+
tracker.seenToolResultIds.add(id)
49+
newCount++
50+
}
51+
} else if (m.role === 'user' && Array.isArray(m.content)) {
52+
for (const part of m.content) {
53+
if (part.type === 'tool_result' && part.tool_use_id) {
54+
const id = String(part.tool_use_id).toLowerCase()
55+
if (!tracker.seenToolResultIds.has(id)) {
56+
tracker.seenToolResultIds.add(id)
57+
newCount++
58+
}
59+
}
60+
}
61+
}
62+
}
63+
tracker.toolResultCount += newCount
64+
return newCount
65+
},
66+
appendNudge(messages, nudgeText) {
67+
messages.push({ role: 'user', content: nudgeText, synthetic: true })
7768
}
69+
}
7870

71+
export function isIgnoredUserMessage(msg: any): boolean {
72+
if (!msg || msg.role !== 'user') return false
73+
if (msg.ignored || msg.info?.ignored || msg.synthetic) return true
7974
if (Array.isArray(msg.content) && msg.content.length > 0) {
80-
const allPartsIgnored = msg.content.every((part: any) => part?.ignored)
81-
if (allPartsIgnored) {
82-
return true
83-
}
75+
if (msg.content.every((part: any) => part?.ignored)) return true
8476
}
85-
8677
return false
8778
}
8879

89-
/**
90-
* Appends a nudge message at the END of the messages array as a new user message.
91-
* This ensures it's in the model's immediate context, not buried in old messages.
92-
*/
93-
function appendNudge(messages: any[], nudgeText: string): boolean {
94-
messages.push({
95-
role: 'user',
96-
content: nudgeText,
97-
synthetic: true
98-
})
99-
return true
80+
export function injectNudge(messages: any[], tracker: ToolTracker, nudgeText: string, freq: number): boolean {
81+
return injectNudgeCore(messages, tracker, nudgeText, freq, openaiAdapter)
10082
}
10183

10284
export function injectSynth(messages: any[], instruction: string): boolean {
103-
// Find the last user message that is not ignored
10485
for (let i = messages.length - 1; i >= 0; i--) {
10586
const msg = messages[i]
10687
if (msg.role === 'user' && !isIgnoredUserMessage(msg)) {
107-
// Avoid double-injecting the same instruction
10888
if (typeof msg.content === 'string') {
109-
if (msg.content.includes(instruction)) {
110-
return false
111-
}
89+
if (msg.content.includes(instruction)) return false
11290
msg.content = msg.content + '\n\n' + instruction
11391
} else if (Array.isArray(msg.content)) {
11492
const alreadyInjected = msg.content.some(
11593
(part: any) => part?.type === 'text' && typeof part.text === 'string' && part.text.includes(instruction)
11694
)
117-
if (alreadyInjected) {
118-
return false
119-
}
120-
msg.content.push({
121-
type: 'text',
122-
text: instruction
123-
})
95+
if (alreadyInjected) return false
96+
msg.content.push({ type: 'text', text: instruction })
12497
}
12598
return true
12699
}
@@ -132,72 +105,42 @@ export function injectSynth(messages: any[], instruction: string): boolean {
132105
// Google/Gemini Format (body.contents with parts)
133106
// ============================================================================
134107

135-
function countToolResultsGemini(contents: any[], tracker: ToolTracker): number {
136-
let newCount = 0
137-
138-
for (const content of contents) {
139-
if (!Array.isArray(content.parts)) continue
140-
141-
for (const part of content.parts) {
142-
if (part.functionResponse) {
143-
// Use function name + index as a pseudo-ID since Gemini doesn't have tool call IDs
144-
const funcName = part.functionResponse.name?.toLowerCase() || 'unknown'
145-
const pseudoId = `gemini:${funcName}:${tracker.seenToolResultIds.size}`
146-
if (!tracker.seenToolResultIds.has(pseudoId)) {
147-
tracker.seenToolResultIds.add(pseudoId)
148-
newCount++
108+
const geminiAdapter: MessageFormatAdapter = {
109+
countToolResults(contents, tracker) {
110+
let newCount = 0
111+
for (const content of contents) {
112+
if (!Array.isArray(content.parts)) continue
113+
for (const part of content.parts) {
114+
if (part.functionResponse) {
115+
const funcName = part.functionResponse.name?.toLowerCase() || 'unknown'
116+
const pseudoId = `gemini:${funcName}:${tracker.seenToolResultIds.size}`
117+
if (!tracker.seenToolResultIds.has(pseudoId)) {
118+
tracker.seenToolResultIds.add(pseudoId)
119+
newCount++
120+
}
149121
}
150122
}
151123
}
124+
tracker.toolResultCount += newCount
125+
return newCount
126+
},
127+
appendNudge(contents, nudgeText) {
128+
contents.push({ role: 'user', parts: [{ text: nudgeText }] })
152129
}
153-
154-
tracker.toolResultCount += newCount
155-
return newCount
156130
}
157131

158-
/**
159-
* Counts new tool results and injects nudge instruction every N tool results (Gemini format).
160-
* Returns true if injection happened.
161-
*/
162-
export function injectNudgeGemini(
163-
contents: any[],
164-
tracker: ToolTracker,
165-
nudgeText: string,
166-
freq: number
167-
): boolean {
168-
const prevCount = tracker.toolResultCount
169-
const newCount = countToolResultsGemini(contents, tracker)
170-
171-
if (newCount > 0) {
172-
const prevBucket = Math.floor(prevCount / freq)
173-
const newBucket = Math.floor(tracker.toolResultCount / freq)
174-
if (newBucket > prevBucket) {
175-
return appendNudgeGemini(contents, nudgeText)
176-
}
177-
}
178-
return false
179-
}
180-
181-
function appendNudgeGemini(contents: any[], nudgeText: string): boolean {
182-
contents.push({
183-
role: 'user',
184-
parts: [{ text: nudgeText }]
185-
})
186-
return true
132+
export function injectNudgeGemini(contents: any[], tracker: ToolTracker, nudgeText: string, freq: number): boolean {
133+
return injectNudgeCore(contents, tracker, nudgeText, freq, geminiAdapter)
187134
}
188135

189136
export function injectSynthGemini(contents: any[], instruction: string): boolean {
190-
// Find the last user content that is not ignored
191137
for (let i = contents.length - 1; i >= 0; i--) {
192138
const content = contents[i]
193139
if (content.role === 'user' && Array.isArray(content.parts)) {
194-
// Check if already injected
195140
const alreadyInjected = content.parts.some(
196141
(part: any) => part?.text && typeof part.text === 'string' && part.text.includes(instruction)
197142
)
198-
if (alreadyInjected) {
199-
return false
200-
}
143+
if (alreadyInjected) return false
201144
content.parts.push({ text: instruction })
202145
return true
203146
}
@@ -209,77 +152,43 @@ export function injectSynthGemini(contents: any[], instruction: string): boolean
209152
// OpenAI Responses API Format (body.input with type-based items)
210153
// ============================================================================
211154

212-
function countToolResultsResponses(input: any[], tracker: ToolTracker): number {
213-
let newCount = 0
214-
215-
for (const item of input) {
216-
if (item.type === 'function_call_output' && item.call_id) {
217-
const id = String(item.call_id).toLowerCase()
218-
if (!tracker.seenToolResultIds.has(id)) {
219-
tracker.seenToolResultIds.add(id)
220-
newCount++
155+
const responsesAdapter: MessageFormatAdapter = {
156+
countToolResults(input, tracker) {
157+
let newCount = 0
158+
for (const item of input) {
159+
if (item.type === 'function_call_output' && item.call_id) {
160+
const id = String(item.call_id).toLowerCase()
161+
if (!tracker.seenToolResultIds.has(id)) {
162+
tracker.seenToolResultIds.add(id)
163+
newCount++
164+
}
221165
}
222166
}
167+
tracker.toolResultCount += newCount
168+
return newCount
169+
},
170+
appendNudge(input, nudgeText) {
171+
input.push({ type: 'message', role: 'user', content: nudgeText })
223172
}
224-
225-
tracker.toolResultCount += newCount
226-
return newCount
227-
}
228-
229-
/**
230-
* Counts new tool results and injects nudge instruction every N tool results (Responses API format).
231-
* Returns true if injection happened.
232-
*/
233-
export function injectNudgeResponses(
234-
input: any[],
235-
tracker: ToolTracker,
236-
nudgeText: string,
237-
freq: number
238-
): boolean {
239-
const prevCount = tracker.toolResultCount
240-
const newCount = countToolResultsResponses(input, tracker)
241-
242-
if (newCount > 0) {
243-
const prevBucket = Math.floor(prevCount / freq)
244-
const newBucket = Math.floor(tracker.toolResultCount / freq)
245-
if (newBucket > prevBucket) {
246-
return appendNudgeResponses(input, nudgeText)
247-
}
248-
}
249-
return false
250173
}
251174

252-
function appendNudgeResponses(input: any[], nudgeText: string): boolean {
253-
input.push({
254-
type: 'message',
255-
role: 'user',
256-
content: nudgeText
257-
})
258-
return true
175+
export function injectNudgeResponses(input: any[], tracker: ToolTracker, nudgeText: string, freq: number): boolean {
176+
return injectNudgeCore(input, tracker, nudgeText, freq, responsesAdapter)
259177
}
260178

261179
export function injectSynthResponses(input: any[], instruction: string): boolean {
262-
// Find the last user message in the input array
263180
for (let i = input.length - 1; i >= 0; i--) {
264181
const item = input[i]
265182
if (item.type === 'message' && item.role === 'user') {
266-
// Check if already injected
267183
if (typeof item.content === 'string') {
268-
if (item.content.includes(instruction)) {
269-
return false
270-
}
184+
if (item.content.includes(instruction)) return false
271185
item.content = item.content + '\n\n' + instruction
272186
} else if (Array.isArray(item.content)) {
273187
const alreadyInjected = item.content.some(
274188
(part: any) => part?.type === 'input_text' && typeof part.text === 'string' && part.text.includes(instruction)
275189
)
276-
if (alreadyInjected) {
277-
return false
278-
}
279-
item.content.push({
280-
type: 'input_text',
281-
text: instruction
282-
})
190+
if (alreadyInjected) return false
191+
item.content.push({ type: 'input_text', text: instruction })
283192
}
284193
return true
285194
}

0 commit comments

Comments
 (0)