From f7b9bef4f98c630dd113057b4a48fdc8fe9c25e9 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Sat, 29 Nov 2025 02:47:35 -0500 Subject: [PATCH 1/2] 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 --- lib/synth-instruction.ts | 277 +++++++++++++-------------------------- 1 file changed, 93 insertions(+), 184 deletions(-) diff --git a/lib/synth-instruction.ts b/lib/synth-instruction.ts index e171437..5427e4c 100644 --- a/lib/synth-instruction.ts +++ b/lib/synth-instruction.ts @@ -4,123 +4,96 @@ export interface ToolTracker { } export function createToolTracker(): ToolTracker { - return { - seenToolResultIds: new Set(), - toolResultCount: 0 - } + return { seenToolResultIds: new Set(), toolResultCount: 0 } } -// ============================================================================ -// OpenAI Chat / Anthropic Format -// ============================================================================ - -function countToolResults(messages: any[], tracker: ToolTracker): number { - let newCount = 0 - - for (const m of messages) { - if (m.role === 'tool' && m.tool_call_id) { - const id = String(m.tool_call_id).toLowerCase() - if (!tracker.seenToolResultIds.has(id)) { - tracker.seenToolResultIds.add(id) - newCount++ - } - } else if (m.role === 'user' && Array.isArray(m.content)) { - for (const part of m.content) { - if (part.type === 'tool_result' && part.tool_use_id) { - const id = String(part.tool_use_id).toLowerCase() - if (!tracker.seenToolResultIds.has(id)) { - tracker.seenToolResultIds.add(id) - newCount++ - } - } - } - } - } - - tracker.toolResultCount += newCount - return newCount +/** Adapter interface for format-specific message operations */ +interface MessageFormatAdapter { + countToolResults(messages: any[], tracker: ToolTracker): number + appendNudge(messages: any[], nudgeText: string): void } -/** - * Counts new tool results and injects nudge instruction every N tool results. - * Returns true if injection happened. - */ -export function injectNudge( +/** Generic nudge injection - counts tool results and injects nudge every N results */ +function injectNudgeCore( messages: any[], tracker: ToolTracker, nudgeText: string, - freq: number + freq: number, + adapter: MessageFormatAdapter ): boolean { const prevCount = tracker.toolResultCount - const newCount = countToolResults(messages, tracker) - + const newCount = adapter.countToolResults(messages, tracker) if (newCount > 0) { - // Check if we crossed a multiple of freq const prevBucket = Math.floor(prevCount / freq) const newBucket = Math.floor(tracker.toolResultCount / freq) if (newBucket > prevBucket) { - // Inject at the END of messages so it's in immediate context - return appendNudge(messages, nudgeText) + adapter.appendNudge(messages, nudgeText) + return true } } return false } -export function isIgnoredUserMessage(msg: any): boolean { - if (!msg || msg.role !== 'user') { - return false - } +// ============================================================================ +// OpenAI Chat / Anthropic Format +// ============================================================================ - // Skip ignored or synthetic messages - if (msg.ignored || msg.info?.ignored || msg.synthetic) { - return true +const openaiAdapter: MessageFormatAdapter = { + countToolResults(messages, tracker) { + let newCount = 0 + for (const m of messages) { + if (m.role === 'tool' && m.tool_call_id) { + const id = String(m.tool_call_id).toLowerCase() + if (!tracker.seenToolResultIds.has(id)) { + tracker.seenToolResultIds.add(id) + newCount++ + } + } else if (m.role === 'user' && Array.isArray(m.content)) { + for (const part of m.content) { + if (part.type === 'tool_result' && part.tool_use_id) { + const id = String(part.tool_use_id).toLowerCase() + if (!tracker.seenToolResultIds.has(id)) { + tracker.seenToolResultIds.add(id) + newCount++ + } + } + } + } + } + tracker.toolResultCount += newCount + return newCount + }, + appendNudge(messages, nudgeText) { + messages.push({ role: 'user', content: nudgeText, synthetic: true }) } +} +export function isIgnoredUserMessage(msg: any): boolean { + if (!msg || msg.role !== 'user') return false + if (msg.ignored || msg.info?.ignored || msg.synthetic) return true if (Array.isArray(msg.content) && msg.content.length > 0) { - const allPartsIgnored = msg.content.every((part: any) => part?.ignored) - if (allPartsIgnored) { - return true - } + if (msg.content.every((part: any) => part?.ignored)) return true } - return false } -/** - * Appends a nudge message at the END of the messages array as a new user message. - * This ensures it's in the model's immediate context, not buried in old messages. - */ -function appendNudge(messages: any[], nudgeText: string): boolean { - messages.push({ - role: 'user', - content: nudgeText, - synthetic: true - }) - return true +export function injectNudge(messages: any[], tracker: ToolTracker, nudgeText: string, freq: number): boolean { + return injectNudgeCore(messages, tracker, nudgeText, freq, openaiAdapter) } export function injectSynth(messages: any[], instruction: string): boolean { - // Find the last user message that is not ignored for (let i = messages.length - 1; i >= 0; i--) { const msg = messages[i] if (msg.role === 'user' && !isIgnoredUserMessage(msg)) { - // Avoid double-injecting the same instruction if (typeof msg.content === 'string') { - if (msg.content.includes(instruction)) { - return false - } + if (msg.content.includes(instruction)) return false msg.content = msg.content + '\n\n' + instruction } else if (Array.isArray(msg.content)) { const alreadyInjected = msg.content.some( (part: any) => part?.type === 'text' && typeof part.text === 'string' && part.text.includes(instruction) ) - if (alreadyInjected) { - return false - } - msg.content.push({ - type: 'text', - text: instruction - }) + if (alreadyInjected) return false + msg.content.push({ type: 'text', text: instruction }) } return true } @@ -132,72 +105,42 @@ export function injectSynth(messages: any[], instruction: string): boolean { // Google/Gemini Format (body.contents with parts) // ============================================================================ -function countToolResultsGemini(contents: any[], tracker: ToolTracker): number { - let newCount = 0 - - for (const content of contents) { - if (!Array.isArray(content.parts)) continue - - for (const part of content.parts) { - if (part.functionResponse) { - // Use function name + index as a pseudo-ID since Gemini doesn't have tool call IDs - const funcName = part.functionResponse.name?.toLowerCase() || 'unknown' - const pseudoId = `gemini:${funcName}:${tracker.seenToolResultIds.size}` - if (!tracker.seenToolResultIds.has(pseudoId)) { - tracker.seenToolResultIds.add(pseudoId) - newCount++ +const geminiAdapter: MessageFormatAdapter = { + countToolResults(contents, tracker) { + let newCount = 0 + for (const content of contents) { + if (!Array.isArray(content.parts)) continue + for (const part of content.parts) { + if (part.functionResponse) { + const funcName = part.functionResponse.name?.toLowerCase() || 'unknown' + const pseudoId = `gemini:${funcName}:${tracker.seenToolResultIds.size}` + if (!tracker.seenToolResultIds.has(pseudoId)) { + tracker.seenToolResultIds.add(pseudoId) + newCount++ + } } } } + tracker.toolResultCount += newCount + return newCount + }, + appendNudge(contents, nudgeText) { + contents.push({ role: 'user', parts: [{ text: nudgeText }] }) } - - tracker.toolResultCount += newCount - return newCount } -/** - * Counts new tool results and injects nudge instruction every N tool results (Gemini format). - * Returns true if injection happened. - */ -export function injectNudgeGemini( - contents: any[], - tracker: ToolTracker, - nudgeText: string, - freq: number -): boolean { - const prevCount = tracker.toolResultCount - const newCount = countToolResultsGemini(contents, tracker) - - if (newCount > 0) { - const prevBucket = Math.floor(prevCount / freq) - const newBucket = Math.floor(tracker.toolResultCount / freq) - if (newBucket > prevBucket) { - return appendNudgeGemini(contents, nudgeText) - } - } - return false -} - -function appendNudgeGemini(contents: any[], nudgeText: string): boolean { - contents.push({ - role: 'user', - parts: [{ text: nudgeText }] - }) - return true +export function injectNudgeGemini(contents: any[], tracker: ToolTracker, nudgeText: string, freq: number): boolean { + return injectNudgeCore(contents, tracker, nudgeText, freq, geminiAdapter) } export function injectSynthGemini(contents: any[], instruction: string): boolean { - // Find the last user content that is not ignored for (let i = contents.length - 1; i >= 0; i--) { const content = contents[i] if (content.role === 'user' && Array.isArray(content.parts)) { - // Check if already injected const alreadyInjected = content.parts.some( (part: any) => part?.text && typeof part.text === 'string' && part.text.includes(instruction) ) - if (alreadyInjected) { - return false - } + if (alreadyInjected) return false content.parts.push({ text: instruction }) return true } @@ -209,77 +152,43 @@ export function injectSynthGemini(contents: any[], instruction: string): boolean // OpenAI Responses API Format (body.input with type-based items) // ============================================================================ -function countToolResultsResponses(input: any[], tracker: ToolTracker): number { - let newCount = 0 - - for (const item of input) { - if (item.type === 'function_call_output' && item.call_id) { - const id = String(item.call_id).toLowerCase() - if (!tracker.seenToolResultIds.has(id)) { - tracker.seenToolResultIds.add(id) - newCount++ +const responsesAdapter: MessageFormatAdapter = { + countToolResults(input, tracker) { + let newCount = 0 + for (const item of input) { + if (item.type === 'function_call_output' && item.call_id) { + const id = String(item.call_id).toLowerCase() + if (!tracker.seenToolResultIds.has(id)) { + tracker.seenToolResultIds.add(id) + newCount++ + } } } + tracker.toolResultCount += newCount + return newCount + }, + appendNudge(input, nudgeText) { + input.push({ type: 'message', role: 'user', content: nudgeText }) } - - tracker.toolResultCount += newCount - return newCount -} - -/** - * Counts new tool results and injects nudge instruction every N tool results (Responses API format). - * Returns true if injection happened. - */ -export function injectNudgeResponses( - input: any[], - tracker: ToolTracker, - nudgeText: string, - freq: number -): boolean { - const prevCount = tracker.toolResultCount - const newCount = countToolResultsResponses(input, tracker) - - if (newCount > 0) { - const prevBucket = Math.floor(prevCount / freq) - const newBucket = Math.floor(tracker.toolResultCount / freq) - if (newBucket > prevBucket) { - return appendNudgeResponses(input, nudgeText) - } - } - return false } -function appendNudgeResponses(input: any[], nudgeText: string): boolean { - input.push({ - type: 'message', - role: 'user', - content: nudgeText - }) - return true +export function injectNudgeResponses(input: any[], tracker: ToolTracker, nudgeText: string, freq: number): boolean { + return injectNudgeCore(input, tracker, nudgeText, freq, responsesAdapter) } export function injectSynthResponses(input: any[], instruction: string): boolean { - // Find the last user message in the input array for (let i = input.length - 1; i >= 0; i--) { const item = input[i] if (item.type === 'message' && item.role === 'user') { - // Check if already injected if (typeof item.content === 'string') { - if (item.content.includes(instruction)) { - return false - } + if (item.content.includes(instruction)) return false item.content = item.content + '\n\n' + instruction } else if (Array.isArray(item.content)) { const alreadyInjected = item.content.some( (part: any) => part?.type === 'input_text' && typeof part.text === 'string' && part.text.includes(instruction) ) - if (alreadyInjected) { - return false - } - item.content.push({ - type: 'input_text', - text: instruction - }) + if (alreadyInjected) return false + item.content.push({ type: 'input_text', text: instruction }) } return true } From c0f97fc5812f7e03ac263046b350bdc92356bec4 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Sat, 29 Nov 2025 02:47:54 -0500 Subject: [PATCH 2/2] v0.3.23 - Bump version --- README.md | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index cfea467..1ee51cb 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Add to your OpenCode config: ```jsonc // opencode.jsonc { - "plugin": ["@tarquinen/opencode-dcp@0.3.22"] + "plugin": ["@tarquinen/opencode-dcp@0.3.23"] } ``` diff --git a/package-lock.json b/package-lock.json index 4340a3a..473f3eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@tarquinen/opencode-dcp", - "version": "0.3.22", + "version": "0.3.23", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@tarquinen/opencode-dcp", - "version": "0.3.22", + "version": "0.3.23", "license": "MIT", "dependencies": { "@ai-sdk/openai-compatible": "^1.0.27", diff --git a/package.json b/package.json index 6a18fdd..0f19cef 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@tarquinen/opencode-dcp", - "version": "0.3.22", + "version": "0.3.23", "type": "module", "description": "OpenCode plugin that optimizes token usage by pruning obsolete tool outputs from conversation context", "main": "./dist/index.js",