From 996fb2a1511661ac4580e4601e5ef5c7961bdf2e Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Wed, 10 Dec 2025 11:11:04 -0600 Subject: [PATCH 1/4] wip --- packages/opencode/src/provider/transform.ts | 58 +++++++++++++++------ packages/opencode/src/session/compaction.ts | 1 - packages/opencode/src/session/prompt.ts | 4 +- packages/opencode/src/session/summary.ts | 4 +- 4 files changed, 45 insertions(+), 22 deletions(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 891025cde4..23b2f269c1 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -74,6 +74,7 @@ export namespace ProviderTransform { return result } + // TODO: is this actually correct??? Or should it just match the other reasoning_content handling // DeepSeek: Handle reasoning_content for tool call continuations // - With tool calls: Include reasoning_content in providerOptions so model can continue reasoning // - Without tool calls: Strip reasoning (new turn doesn't need previous reasoning) @@ -110,6 +111,45 @@ export namespace ProviderTransform { content: filteredContent, } } + + return msg + }) + } + + if ( + model.capabilities.interleaved && + typeof model.capabilities.interleaved === "object" && + model.capabilities.interleaved.field === "reasoning_content" + ) { + return msgs.map((msg) => { + if (msg.role === "assistant" && Array.isArray(msg.content)) { + const reasoningParts = msg.content.filter((part: any) => part.type === "reasoning") + const reasoningText = reasoningParts.map((part: any) => part.text).join("") + + // Filter out reasoning parts from content + const filteredContent = msg.content.filter((part: any) => part.type !== "reasoning") + + // Include reasoning_content directly on the message for all assistant messages + if (reasoningText) { + return { + ...msg, + content: filteredContent, + providerOptions: { + ...msg.providerOptions, + openaiCompatible: { + ...(msg.providerOptions as any)?.openaiCompatible, + reasoning_content: reasoningText, + }, + }, + } + } + + return { + ...msg, + content: filteredContent, + } + } + return msg }) } @@ -273,23 +313,7 @@ export namespace ProviderTransform { return options } - export function providerOptions(model: Provider.Model, options: { [x: string]: any }, messages: ModelMessage[]) { - if (model.capabilities.interleaved && typeof model.capabilities.interleaved === "object") { - const cot = [] - const assistantMessages = messages.filter((msg) => msg.role === "assistant") - for (const msg of assistantMessages) { - for (const part of msg.content) { - if (typeof part === "string") { - continue - } - if (part.type === "reasoning") { - cot.push(part) - } - } - } - options[model.capabilities.interleaved.field] = cot - } - + export function providerOptions(model: Provider.Model, options: { [x: string]: any }) { switch (model.api.npm) { case "@ai-sdk/openai": case "@ai-sdk/azure": diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index c25e16dc5e..45bab9ae6e 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -143,7 +143,6 @@ export namespace SessionCompaction { providerOptions: ProviderTransform.providerOptions( model, pipe({}, mergeDeep(ProviderTransform.options(model, input.sessionID)), mergeDeep(model.options)), - [], ), headers: model.headers, abortSignal: input.abort, diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 15e9349624..71b99ab0d5 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -593,7 +593,7 @@ export namespace SessionPrompt { OUTPUT_TOKEN_MAX, ), abortSignal: abort, - providerOptions: ProviderTransform.providerOptions(model, params.options, messages), + providerOptions: ProviderTransform.providerOptions(model, params.options), stopWhen: stepCountIs(1), temperature: params.temperature, topP: params.topP, @@ -1473,7 +1473,7 @@ export namespace SessionPrompt { await generateText({ // use higher # for reasoning models since reasoning tokens eat up a lot of the budget maxOutputTokens: small.capabilities.reasoning ? 3000 : 20, - providerOptions: ProviderTransform.providerOptions(small, options, []), + providerOptions: ProviderTransform.providerOptions(small, options), messages: [ ...SystemPrompt.title(small.providerID).map( (x): ModelMessage => ({ diff --git a/packages/opencode/src/session/summary.ts b/packages/opencode/src/session/summary.ts index 09cdeb23a8..ab6a986862 100644 --- a/packages/opencode/src/session/summary.ts +++ b/packages/opencode/src/session/summary.ts @@ -91,7 +91,7 @@ export namespace SessionSummary { if (textPart && !userMsg.summary?.title) { const result = await generateText({ maxOutputTokens: small.capabilities.reasoning ? 1500 : 20, - providerOptions: ProviderTransform.providerOptions(small, options, []), + providerOptions: ProviderTransform.providerOptions(small, options), messages: [ ...SystemPrompt.title(small.providerID).map( (x): ModelMessage => ({ @@ -144,7 +144,7 @@ export namespace SessionSummary { const result = await generateText({ model: language, maxOutputTokens: 100, - providerOptions: ProviderTransform.providerOptions(small, options, []), + providerOptions: ProviderTransform.providerOptions(small, options), messages: [ ...SystemPrompt.summarize(small.providerID).map( (x): ModelMessage => ({ From 8d1824edfc79ce0a053eab678d0f24fea408ef29 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Wed, 10 Dec 2025 11:12:23 -0600 Subject: [PATCH 2/4] fixes --- packages/opencode/src/provider/transform.ts | 50 +++------------------ 1 file changed, 5 insertions(+), 45 deletions(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 23b2f269c1..3de7f92a03 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -74,52 +74,12 @@ export namespace ProviderTransform { return result } - // TODO: is this actually correct??? Or should it just match the other reasoning_content handling - // DeepSeek: Handle reasoning_content for tool call continuations - // - With tool calls: Include reasoning_content in providerOptions so model can continue reasoning - // - Without tool calls: Strip reasoning (new turn doesn't need previous reasoning) - // See: https://api-docs.deepseek.com/guides/thinking_mode - if (model.providerID === "deepseek" || model.api.id.toLowerCase().includes("deepseek")) { - return msgs.map((msg) => { - if (msg.role === "assistant" && Array.isArray(msg.content)) { - const reasoningParts = msg.content.filter((part: any) => part.type === "reasoning") - const hasToolCalls = msg.content.some((part: any) => part.type === "tool-call") - const reasoningText = reasoningParts.map((part: any) => part.text).join("") - - // Filter out reasoning parts from content - const filteredContent = msg.content.filter((part: any) => part.type !== "reasoning") - - // If this message has tool calls and reasoning, include reasoning_content - // so DeepSeek can continue reasoning after tool execution - if (hasToolCalls && reasoningText) { - return { - ...msg, - content: filteredContent, - providerOptions: { - ...msg.providerOptions, - openaiCompatible: { - ...(msg.providerOptions as any)?.openaiCompatible, - reasoning_content: reasoningText, - }, - }, - } - } - - // For final answers (no tool calls), just strip reasoning - return { - ...msg, - content: filteredContent, - } - } - - return msg - }) - } - if ( - model.capabilities.interleaved && - typeof model.capabilities.interleaved === "object" && - model.capabilities.interleaved.field === "reasoning_content" + model.providerID === "deepseek" || + model.api.id.toLowerCase().includes("deepseek") || + (model.capabilities.interleaved && + typeof model.capabilities.interleaved === "object" && + model.capabilities.interleaved.field === "reasoning_content") ) { return msgs.map((msg) => { if (msg.role === "assistant" && Array.isArray(msg.content)) { From 72aff7044426564d93e21de4b8c747a284a6e4ac Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Wed, 10 Dec 2025 13:20:40 -0600 Subject: [PATCH 3/4] wip --- packages/opencode/src/provider/transform.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 3de7f92a03..f4c8890ac2 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -219,6 +219,10 @@ export namespace ProviderTransform { } } + if (model.providerID === "baseten") { + result["chat_template_args"] = { enable_thinking: true } + } + if (model.providerID === "openai" || providerOptions?.setCacheKey) { result["promptCacheKey"] = sessionID } From f0070461d85111ee0875b9a86ac26af843fa7db9 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Wed, 10 Dec 2025 13:21:16 -0600 Subject: [PATCH 4/4] wip --- packages/opencode/src/provider/transform.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 7cf64ff4cf..2ae31acf8b 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -212,11 +212,13 @@ export namespace ProviderTransform { ): Record { const result: Record = {} - // switch to providerID later, for now use this if (model.api.npm === "@openrouter/ai-sdk-provider") { result["usage"] = { include: true, } + if (model.api.id.includes("gemini-3")) { + result["reasoning"] = { effort: "high" } + } } if (model.providerID === "baseten") { @@ -227,9 +229,6 @@ export namespace ProviderTransform { result["promptCacheKey"] = sessionID } - if (model.api.npm === "@openrouter/ai-sdk-provider" && model.api.id.includes("gemini-3")) { - result["reasoning"] = { effort: "high" } - } if (model.api.npm === "@ai-sdk/google" || model.api.npm === "@ai-sdk/google-vertex") { result["thinkingConfig"] = { thinkingLevel: "high",