From fde73cfecdf877b06f5da62b07fcc9930d712f84 Mon Sep 17 00:00:00 2001 From: Sewer56 Date: Thu, 30 Apr 2026 19:04:08 +0100 Subject: [PATCH] fix(provider): split providerOptions key on dot for openai-compatible providers The AI SDK's @ai-sdk/openai-compatible, @ai-sdk/openai, and @ai-sdk/anthropic modules all resolve the providerOptions key by splitting the provider name on '.' and taking the first segment: get providerOptionsName() { return this.config.provider.split('.')[0].trim(); } This means a provider named 'wafer.ai' will look for options under providerOptions['wafer'], NOT providerOptions['wafer.ai']. However, opencode's providerOptions() in transform.ts uses the raw providerID as the key when sdkKey() returns undefined (which it does for @ai-sdk/openai-compatible): const key = sdkKey(model.api.npm) ?? model.providerID return { [key]: options } For provider 'wafer.ai', this puts options under providerOptions['wafer.ai'] - a key the SDK never reads. The result is that provider options like { reasoningEffort: 'medium' } are silently dropped. No reasoning_effort is sent in the API request, so models like DeepSeek-V4-Pro return no reasoning_content. @sdk/openai and @sdk/anthropic are also affected in principle, but currently protected by their sdkKey() mappings (returning 'openai' and 'anthropic' respectively). Those mappings are not guaranteed to stay in sync, so the dot-split is applied to all three packages. Other AI SDK packages (xai, mistral, groq, cohere, togetherai, etc.) use hardcoded providerOptions keys like 'xai' or 'cohere' rather than deriving them from the provider name - applying .split('.')[0] would break those, so they are excluded. Fix: for SDKs that use the providerOptionsName dot-split pattern, apply the same logic here so the key we write matches the key they read. --- packages/opencode/src/provider/transform.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index d47d1fe76ca3..d97e9cb87370 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -1058,7 +1058,20 @@ export function providerOptions(model: Provider.Model, options: { [x: string]: a return result } - const key = sdkKey(model.api.npm) ?? model.providerID + // AI SDK packages that resolve providerOptionsName by splitting the + // provider name on "." (e.g. "wafer.ai" -> "wafer") need the same + // logic here so the key we write matches the key they read. + // Other SDKs (xai, mistral, groq, cohere, etc.) use hardcoded keys + // like "xai" or "cohere" - applying .split(".")[0] would break those. + const usesDotSplitOptions = + model.api.npm === "@ai-sdk/openai-compatible" || + model.api.npm === "@ai-sdk/openai" || + model.api.npm === "@ai-sdk/anthropic" + const key = + sdkKey(model.api.npm) ?? + (usesDotSplitOptions + ? model.providerID.split(".")[0] + : model.providerID) // @ai-sdk/azure delegates to OpenAIChatLanguageModel which reads from // providerOptions["openai"], but OpenAIResponsesLanguageModel checks // "azure" first. Pass both so model options work on either code path.