Skip to content

Commit d26bd86

Browse files
committed
refactor: use OpenCode session API as single source for tool parameters
1 parent 8fdc4d3 commit d26bd86

File tree

9 files changed

+56
-136
lines changed

9 files changed

+56
-136
lines changed

lib/fetch-wrapper/formats/bedrock.ts

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import type { FormatDescriptor, ToolOutput, ToolTracker } from "../types"
22
import type { PluginState } from "../../state"
3-
import type { Logger } from "../../logger"
4-
import { cacheToolParametersFromMessages } from "../../state/tool-cache"
53

64
function isNudgeMessage(msg: any, nudgeText: string): boolean {
75
if (typeof msg.content === 'string') {
@@ -88,28 +86,6 @@ export const bedrockFormat: FormatDescriptor = {
8886
return body.messages
8987
},
9088

91-
cacheToolParameters(data: any[], state: PluginState, logger?: Logger): void {
92-
// Extract toolUseId and tool name from assistant toolUse blocks
93-
for (const m of data) {
94-
if (m.role === 'assistant' && Array.isArray(m.content)) {
95-
for (const block of m.content) {
96-
if (block.toolUse && block.toolUse.toolUseId) {
97-
const toolUseId = block.toolUse.toolUseId.toLowerCase()
98-
state.toolParameters.set(toolUseId, {
99-
tool: block.toolUse.name,
100-
parameters: block.toolUse.input
101-
})
102-
logger?.debug("bedrock", "Cached tool parameters", {
103-
toolUseId,
104-
toolName: block.toolUse.name
105-
})
106-
}
107-
}
108-
}
109-
}
110-
cacheToolParametersFromMessages(data, state, logger)
111-
},
112-
11389
injectSynth(data: any[], instruction: string, nudgeText: string): boolean {
11490
return injectSynth(data, instruction, nudgeText)
11591
},

lib/fetch-wrapper/formats/gemini.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type { FormatDescriptor, ToolOutput, ToolTracker } from "../types"
22
import type { PluginState } from "../../state"
3-
import type { Logger } from "../../logger"
43

54
function isNudgeContent(content: any, nudgeText: string): boolean {
65
if (Array.isArray(content.parts) && content.parts.length === 1) {
@@ -72,10 +71,6 @@ export const geminiFormat: FormatDescriptor = {
7271
return body.contents
7372
},
7473

75-
cacheToolParameters(_data: any[], _state: PluginState, _logger?: Logger): void {
76-
// No-op: Gemini tool parameters are captured via message events in hooks.ts
77-
},
78-
7974
injectSynth(data: any[], instruction: string, nudgeText: string): boolean {
8075
return injectSynth(data, instruction, nudgeText)
8176
},

lib/fetch-wrapper/formats/openai-chat.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import type { FormatDescriptor, ToolOutput, ToolTracker } from "../types"
22
import type { PluginState } from "../../state"
3-
import type { Logger } from "../../logger"
4-
import { cacheToolParametersFromMessages } from "../../state/tool-cache"
53

64
function isNudgeMessage(msg: any, nudgeText: string): boolean {
75
if (typeof msg.content === 'string') {
@@ -79,10 +77,6 @@ export const openaiChatFormat: FormatDescriptor = {
7977
return body.messages
8078
},
8179

82-
cacheToolParameters(data: any[], state: PluginState, logger?: Logger): void {
83-
cacheToolParametersFromMessages(data, state, logger)
84-
},
85-
8680
injectSynth(data: any[], instruction: string, nudgeText: string): boolean {
8781
return injectSynth(data, instruction, nudgeText)
8882
},

lib/fetch-wrapper/formats/openai-responses.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import type { FormatDescriptor, ToolOutput, ToolTracker } from "../types"
22
import type { PluginState } from "../../state"
3-
import type { Logger } from "../../logger"
4-
import { cacheToolParametersFromInput } from "../../state/tool-cache"
53

64
function isNudgeItem(item: any, nudgeText: string): boolean {
75
if (typeof item.content === 'string') {
@@ -66,10 +64,6 @@ export const openaiResponsesFormat: FormatDescriptor = {
6664
return body.input
6765
},
6866

69-
cacheToolParameters(data: any[], state: PluginState, logger?: Logger): void {
70-
cacheToolParametersFromInput(data, state, logger)
71-
},
72-
7367
injectSynth(data: any[], instruction: string, nudgeText: string): boolean {
7468
return injectSynth(data, instruction, nudgeText)
7569
},

lib/fetch-wrapper/handler.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { FetchHandlerContext, FetchHandlerResult, FormatDescriptor, PrunedI
22
import { type PluginState, ensureSessionRestored } from "../state"
33
import type { Logger } from "../logger"
44
import { buildPrunableToolsList, buildEndInjection } from "./prunable-list"
5+
import { syncToolParametersFromOpenCode } from "../state/tool-cache"
56

67
const PRUNED_CONTENT_MESSAGE = '[Output removed to save context - information superseded or no longer needed]'
78

@@ -65,14 +66,17 @@ export async function handleFormat(
6566

6667
let modified = false
6768

68-
format.cacheToolParameters(data, ctx.state, ctx.logger)
69+
// Sync tool parameters from OpenCode's session API (single source of truth)
70+
const sessionId = ctx.state.lastSeenSessionId
71+
if (sessionId) {
72+
await syncToolParametersFromOpenCode(ctx.client, sessionId, ctx.state, ctx.logger)
73+
}
6974

7075
if (ctx.config.strategies.onTool.length > 0) {
7176
if (format.injectSynth(data, ctx.prompts.synthInstruction, ctx.prompts.nudgeInstruction)) {
7277
modified = true
7378
}
7479

75-
const sessionId = ctx.state.lastSeenSessionId
7680
if (sessionId) {
7781
const toolIds = Array.from(ctx.state.toolParameters.keys())
7882
const alreadyPruned = ctx.state.prunedIds.get(sessionId) ?? []

lib/fetch-wrapper/types.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ export interface FormatDescriptor {
1313
name: string
1414
detect(body: any): boolean
1515
getDataArray(body: any): any[] | undefined
16-
cacheToolParameters(data: any[], state: PluginState, logger?: Logger): void
1716
injectSynth(data: any[], instruction: string, nudgeText: string): boolean
1817
trackNewToolResults(data: any[], tracker: ToolTracker, protectedTools: Set<string>): number
1918
injectPrunableList(data: any[], injection: string): boolean

lib/hooks.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -121,13 +121,6 @@ export function createChatParamsHandler(
121121
toolCallsByName.set(toolName, [])
122122
}
123123
toolCallsByName.get(toolName)!.push(callId)
124-
125-
if (!state.toolParameters.has(callId)) {
126-
state.toolParameters.set(callId, {
127-
tool: part.tool,
128-
parameters: part.input ?? {}
129-
})
130-
}
131124
}
132125
}
133126
}

lib/state/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,13 @@ export interface PluginState {
1515
lastSeenSessionId: string | null
1616
}
1717

18+
export type ToolStatus = "pending" | "running" | "completed" | "error"
19+
1820
export interface ToolParameterEntry {
1921
tool: string
2022
parameters: any
23+
status?: ToolStatus
24+
error?: string
2125
}
2226

2327
export interface ModelInfo {

lib/state/tool-cache.ts

Lines changed: 46 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,111 +1,72 @@
1-
import type { PluginState } from "./index"
1+
import type { PluginState, ToolStatus } from "./index"
22
import type { Logger } from "../logger"
33

4+
/** Maximum number of entries to keep in the tool parameters cache */
5+
const MAX_TOOL_CACHE_SIZE = 500
6+
47
/**
5-
* Cache tool parameters from OpenAI Chat Completions and Anthropic style messages.
6-
* Extracts tool call IDs and their parameters from assistant messages.
7-
*
8-
* Supports:
9-
* - OpenAI format: message.tool_calls[] with id, function.name, function.arguments
10-
* - Anthropic format: message.content[] with type='tool_use', id, name, input
8+
* Sync tool parameters from OpenCode's session.messages() API.
9+
* This is the single source of truth for tool parameters, replacing
10+
* format-specific parsing from LLM API requests.
1111
*/
12-
export function cacheToolParametersFromMessages(
13-
messages: any[],
12+
export async function syncToolParametersFromOpenCode(
13+
client: any,
14+
sessionId: string,
1415
state: PluginState,
1516
logger?: Logger
16-
): void {
17-
let openaiCached = 0
18-
let anthropicCached = 0
17+
): Promise<void> {
18+
try {
19+
const messagesResponse = await client.session.messages({
20+
path: { id: sessionId },
21+
query: { limit: 100 }
22+
})
23+
const messages = messagesResponse.data || messagesResponse
1924

20-
for (const message of messages) {
21-
if (message.role !== 'assistant') {
22-
continue
25+
if (!Array.isArray(messages)) {
26+
return
2327
}
2428

25-
if (Array.isArray(message.tool_calls)) {
26-
for (const toolCall of message.tool_calls) {
27-
if (!toolCall.id || !toolCall.function) {
28-
continue
29-
}
29+
let synced = 0
3030

31-
try {
32-
const params = typeof toolCall.function.arguments === 'string'
33-
? JSON.parse(toolCall.function.arguments)
34-
: toolCall.function.arguments
35-
state.toolParameters.set(toolCall.id.toLowerCase(), {
36-
tool: toolCall.function.name,
37-
parameters: params
38-
})
39-
openaiCached++
40-
} catch (error) {
41-
}
42-
}
43-
}
31+
for (const msg of messages) {
32+
if (!msg.parts) continue
4433

45-
if (Array.isArray(message.content)) {
46-
for (const part of message.content) {
47-
if (part.type !== 'tool_use' || !part.id || !part.name) {
48-
continue
49-
}
34+
for (const part of msg.parts) {
35+
if (part.type !== "tool" || !part.callID) continue
5036

51-
state.toolParameters.set(part.id.toLowerCase(), {
52-
tool: part.name,
53-
parameters: part.input ?? {}
37+
const id = part.callID.toLowerCase()
38+
39+
// Skip if already cached (optimization)
40+
if (state.toolParameters.has(id)) continue
41+
42+
const status = part.state?.status as ToolStatus | undefined
43+
state.toolParameters.set(id, {
44+
tool: part.tool,
45+
parameters: part.state?.input ?? {},
46+
status,
47+
error: status === "error" ? part.state?.error : undefined,
5448
})
55-
anthropicCached++
49+
synced++
5650
}
5751
}
58-
}
59-
60-
if (logger && (openaiCached > 0 || anthropicCached > 0)) {
61-
logger.debug("tool-cache", "Cached tool parameters from messages", {
62-
openaiFormat: openaiCached,
63-
anthropicFormat: anthropicCached,
64-
totalCached: state.toolParameters.size
65-
})
66-
}
67-
}
68-
69-
/**
70-
* Cache tool parameters from OpenAI Responses API format.
71-
* Extracts from input array items with type='function_call'.
72-
*/
73-
export function cacheToolParametersFromInput(
74-
input: any[],
75-
state: PluginState,
76-
logger?: Logger
77-
): void {
78-
let cached = 0
7952

80-
for (const item of input) {
81-
if (item.type !== 'function_call' || !item.call_id || !item.name) {
82-
continue
83-
}
53+
trimToolParametersCache(state)
8454

85-
try {
86-
const params = typeof item.arguments === 'string'
87-
? JSON.parse(item.arguments)
88-
: item.arguments
89-
state.toolParameters.set(item.call_id.toLowerCase(), {
90-
tool: item.name,
91-
parameters: params
55+
if (logger && synced > 0) {
56+
logger.debug("tool-cache", "Synced tool parameters from OpenCode", {
57+
sessionId: sessionId.slice(0, 8),
58+
synced,
59+
totalCached: state.toolParameters.size
9260
})
93-
cached++
94-
} catch (error) {
9561
}
96-
}
97-
98-
if (logger && cached > 0) {
99-
logger.debug("tool-cache", "Cached tool parameters from input", {
100-
responsesApiFormat: cached,
101-
totalCached: state.toolParameters.size
62+
} catch (error) {
63+
logger?.warn("tool-cache", "Failed to sync tool parameters from OpenCode", {
64+
sessionId: sessionId.slice(0, 8),
65+
error: error instanceof Error ? error.message : String(error)
10266
})
10367
}
10468
}
10569

106-
/** Maximum number of entries to keep in the tool parameters cache */
107-
const MAX_TOOL_CACHE_SIZE = 500
108-
10970
/**
11071
* Trim the tool parameters cache to prevent unbounded memory growth.
11172
* Uses FIFO eviction - removes oldest entries first.

0 commit comments

Comments
 (0)