From 5d3588962ad218ad8be099a73e915e9adf1b4a26 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Wed, 1 Jul 2026 11:43:38 -0500 Subject: [PATCH] fix(provider): preserve legacy reasoning fallback guards --- packages/opencode/src/provider/transform.ts | 60 +++++++++++++++++++ .../opencode/test/provider/provider.test.ts | 29 +++++++++ .../opencode/test/provider/transform.test.ts | 37 ++++++++++-- 3 files changed, 122 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index f0a0e077b3..07f54f1ddb 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -812,6 +812,63 @@ function reasoningOptionVariants(model: Provider.Model): Record> | undefined { + const id = model.id.toLowerCase() + const apiID = model.api.id.toLowerCase() + const includes = (value: string) => id.includes(value) || apiID.includes(value) + const glm52 = ["glm-5.2", "glm-5-2", "glm-5p2"].some(includes) + + if (glm52 && model.api.npm === "@openrouter/ai-sdk-provider") { + return { + high: { reasoning: { effort: "high" } }, + xhigh: { reasoning: { effort: "xhigh" } }, + } + } + if (glm52 && model.api.npm === "@ai-sdk/openai-compatible") { + return { + high: { reasoningEffort: "high" }, + max: { reasoningEffort: "max" }, + } + } + if (glm52 && model.api.npm === "@ai-sdk/anthropic") { + return { + high: { effort: "high" }, + max: { effort: "max" }, + } + } + + if ( + includes("deepseek-chat") || + includes("deepseek-reasoner") || + includes("deepseek-r1") || + includes("deepseek-v3") || + includes("minimax") || + (includes("glm") && !glm52) || + includes("kimi") || + includes("k2p") || + includes("qwen") || + includes("big-pickle") + ) { + return {} + } + + if (includes("grok") && includes("grok-3-mini")) { + if (model.api.npm === "@openrouter/ai-sdk-provider") { + return { + low: { reasoning: { effort: "low" } }, + high: { reasoning: { effort: "high" } }, + } + } + return { + low: { reasoningEffort: "low" }, + high: { reasoningEffort: "high" }, + } + } + if (includes("grok")) return {} + + return undefined +} + export function variants(model: Provider.Model): Record> { if (!model.capabilities.reasoning) return {} @@ -830,6 +887,9 @@ export function variants(model: Provider.Model): Record [effort, { reasoning: { effort } }])) diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts index 3222a836fe..405c99a0a3 100644 --- a/packages/opencode/test/provider/provider.test.ts +++ b/packages/opencode/test/provider/provider.test.ts @@ -1677,6 +1677,35 @@ it.instance( }, ) +it.instance( + "custom config deepseek reasoning model without variants keeps legacy empty variants", + Effect.gen(function* () { + const providers = yield* list + const model = providers[ProviderV2.ID.make("custom-deepseek")].models["deepseek-r1"] + expect(model.reasoning_options).toBeUndefined() + expect(model.variants).toEqual({}) + }), + { + config: { + provider: { + "custom-deepseek": { + name: "Custom DeepSeek", + npm: "@ai-sdk/openai-compatible", + env: [], + models: { + "deepseek-r1": { + name: "DeepSeek R1", + reasoning: true, + limit: { context: 128000, output: 16000 }, + }, + }, + options: { apiKey: "test-key" }, + }, + }, + }, + }, +) + it.instance( "custom model with variants enabled and disabled", Effect.gen(function* () { diff --git a/packages/opencode/test/provider/transform.test.ts b/packages/opencode/test/provider/transform.test.ts index 9b21429a46..62331d64e8 100644 --- a/packages/opencode/test/provider/transform.test.ts +++ b/packages/opencode/test/provider/transform.test.ts @@ -3135,12 +3135,27 @@ describe("ProviderTransform.variants", () => { url: "https://api.deepseek.com", npm: "@ai-sdk/openai-compatible", }, - reasoning_options: [], }) const result = ProviderTransform.variants(model) expect(result).toEqual({}) }) + test.each(["deepseek-r1", "qwen3", "kimi-k2", "glm-4.6", "big-pickle"])( + "%s without reasoning_options keeps legacy empty fallback", + (apiID) => { + const model = createMockModel({ + id: `custom/${apiID}`, + providerID: "custom", + api: { + id: apiID, + url: "https://api.custom.com", + npm: "@ai-sdk/openai-compatible", + }, + }) + expect(ProviderTransform.variants(model)).toEqual({}) + }, + ) + test("minimax returns empty object", () => { const model = createMockModel({ id: "minimax/minimax-model", @@ -3150,7 +3165,6 @@ describe("ProviderTransform.variants", () => { url: "https://api.minimax.com", npm: "@ai-sdk/openai-compatible", }, - reasoning_options: [], }) const result = ProviderTransform.variants(model) expect(result).toEqual({}) @@ -3479,7 +3493,7 @@ describe("ProviderTransform.variants", () => { }) describe("@ai-sdk/xai", () => { - test("returns WIDELY_SUPPORTED_EFFORTS with reasoningEffort", () => { + test("grok-3 returns empty object", () => { const model = createMockModel({ id: "xai/grok-3", providerID: "xai", @@ -3490,8 +3504,23 @@ describe("ProviderTransform.variants", () => { }, }) const result = ProviderTransform.variants(model) - expect(Object.keys(result)).toEqual(["low", "medium", "high"]) + expect(result).toEqual({}) + }) + + test("grok-3-mini returns low and high with reasoningEffort", () => { + const model = createMockModel({ + id: "xai/grok-3-mini", + providerID: "xai", + api: { + id: "grok-3-mini", + url: "https://api.x.ai", + npm: "@ai-sdk/xai", + }, + }) + const result = ProviderTransform.variants(model) + expect(Object.keys(result)).toEqual(["low", "high"]) expect(result.low).toEqual({ reasoningEffort: "low" }) + expect(result.high).toEqual({ reasoningEffort: "high" }) }) })