diff --git a/src/adapters/openai-responses.ts b/src/adapters/openai-responses.ts index 2900fd58b2..4d974cc527 100644 --- a/src/adapters/openai-responses.ts +++ b/src/adapters/openai-responses.ts @@ -2458,7 +2458,9 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig): // Last, so promoted namespace children are also cleared of Codex-private fields. outBody = stripCanonicalOnlyToolFields(outBody, provider.supportsOpenAiWebSearchToolFields === false); } - if (!forward) outBody = normalizeOpenCodeGoAdditionalTools(outBody, url); + if (!forward) { + outBody = normalizeOpenCodeGoAdditionalTools(outBody, url, parsed._replayPrefixLen); + } // Same predicate as the routedCompaction gate in handleResponses(): an authMode check would // let a noncanonical custom forward provider skip this rewrite while the server still routes // it as a summarizer turn (#422). The compaction body build removes the tool surface and must diff --git a/src/adapters/opencode-go-additional-tools.ts b/src/adapters/opencode-go-additional-tools.ts index 35e3aaa3e4..7e3609c134 100644 --- a/src/adapters/opencode-go-additional-tools.ts +++ b/src/adapters/opencode-go-additional-tools.ts @@ -3,7 +3,11 @@ function isRecord(value: unknown): value is Record { } /** Console Go accepts public tools but rejects the private additional_tools input wrapper. */ -export function normalizeOpenCodeGoAdditionalTools(body: unknown, responseUrl: string): unknown { +export function normalizeOpenCodeGoAdditionalTools( + body: unknown, + responseUrl: string, + replayPrefixLength = 0, +): unknown { let destination: URL; try { destination = new URL(responseUrl); @@ -20,10 +24,16 @@ export function normalizeOpenCodeGoAdditionalTools(body: unknown, responseUrl: s const input: unknown[] = []; const promoted: unknown[] = []; + const currentTurnStart = Number.isFinite(replayPrefixLength) + ? Math.min(body.input.length, Math.max(0, Math.trunc(replayPrefixLength))) + : 0; let changed = false; - for (const item of body.input) { + for (const [index, item] of body.input.entries()) { if (isRecord(item) && item.type === "additional_tools" && Array.isArray(item.tools)) { changed = true; + // Replayed wrappers are conversation history, not authority for the current request. + // Go cannot accept the wrapper itself, so remove it without promoting its catalog. + if (index < currentTurnStart) continue; // Custom/search/namespace lowering already owns identity and deduplication. This pass // only moves declarations, including hosted tools that intentionally have no name. for (const tool of item.tools) promoted.push(tool); diff --git a/tests/providers/opencode-go-grok46-responses.test.ts b/tests/providers/opencode-go-grok46-responses.test.ts index d35fa6cf8c..cadea0a9b5 100644 --- a/tests/providers/opencode-go-grok46-responses.test.ts +++ b/tests/providers/opencode-go-grok46-responses.test.ts @@ -26,6 +26,7 @@ function buildRequest( modelId: string, rawBody: Record, configuredProvider = provider(), + replayPrefixLength?: number, ) { return createResponsesPassthroughAdapter(configuredProvider).buildRequest({ modelId, @@ -33,6 +34,7 @@ function buildRequest( stream: true, options: {}, _rawBody: { model: modelId, input: "ping", ...rawBody }, + ...(replayPrefixLength === undefined ? {} : { _replayPrefixLen: replayPrefixLength }), }, { headers: new Headers() }); } @@ -201,6 +203,26 @@ describe("OpenCode Go additional_tools placement", () => { .toMatchObject({ input: [], tools: [], tool_choice: "none" }); }); + test("does not promote additional tools restored from continuation history", () => { + const historicalWeb = { type: "web_search" }; + const request = buildRequest("gpt-5.6-luna", { + tools: [], + input: [ + { type: "additional_tools", tools: [historicalWeb] }, + { type: "message", role: "assistant", content: "history" }, + { type: "message", role: "user", content: "continue" }, + ], + }, provider(), 2); + + expect(JSON.parse(request.body)).toMatchObject({ + input: [ + { type: "message", role: "assistant", content: "history" }, + { type: "message", role: "user", content: "continue" }, + ], + tools: [], + }); + }); + test("activates tools loaded by tool search before moving their catalog", () => { const sent = build("gpt-5.6-luna", { input: [ { type: "additional_tools", tools: [{ ...lookup, defer_loading: true }] },