diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 56099effe15..89b9c88ae3c 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -706,17 +706,29 @@ function anthropicBindsThinking(apiId: string) { const ANTHROPIC_BLOCK_BINDING = { prefixMismatchBehavior: "drop_block" } function anthropicBlockBinding(model: Provider.Model, options: { [x: string]: any }) { + const sdk = sdkKey(model.api.npm) + const key = sdk === "bedrock" ? "reasoningConfig" : sdk === "anthropic" ? "thinking" : undefined + // Consume the OpenCode-only opt-out even on models outside the default scope. + if (key && options[key]?.blockBinding === false) { + const result = { ...options, [key]: { ...options[key] } } + delete result[key].blockBinding + if (Object.keys(result[key]).length === 0) delete result[key] + return result + } + if (!anthropicBindsThinking(model.api.id)) return options switch (model.api.npm) { case "@ai-sdk/anthropic": case "@ai-sdk/google-vertex/anthropic": { const thinking = options.thinking ?? { type: "adaptive" } if (thinking.type !== "adaptive" && thinking.type !== "enabled") return options + if (thinking.blockBinding !== undefined) return options return { ...options, thinking: { ...thinking, blockBinding: ANTHROPIC_BLOCK_BINDING } } } case "@ai-sdk/amazon-bedrock": { const reasoningConfig = options.reasoningConfig ?? { type: "adaptive" } if (reasoningConfig.type !== "adaptive" && reasoningConfig.type !== "enabled") return options + if (reasoningConfig.blockBinding !== undefined) return options return { ...options, reasoningConfig: { ...reasoningConfig, blockBinding: ANTHROPIC_BLOCK_BINDING } } } } diff --git a/packages/opencode/test/provider/transform.test.ts b/packages/opencode/test/provider/transform.test.ts index bfe4a0aa9b6..9d767581b70 100644 --- a/packages/opencode/test/provider/transform.test.ts +++ b/packages/opencode/test/provider/transform.test.ts @@ -938,6 +938,21 @@ describe("ProviderTransform.providerOptions", () => { ).toEqual({ [sdk.key]: { [sdk.option]: { type: "enabled", budgetTokens: 4000 } } }) }) + test.each(["claude-fable-5-1", "claude-opus-5"])("honors explicit binding controls for %s", (id) => { + const model = claude(sdk.npm, id) + const options = Object.freeze({ + [sdk.option]: Object.freeze({ type: "adaptive", blockBinding: false }), + }) + expect(ProviderTransform.providerOptions(model, options)).toEqual({ + [sdk.key]: { [sdk.option]: { type: "adaptive" } }, + }) + expect(ProviderTransform.providerOptions(model, { [sdk.option]: { blockBinding: false } })).toEqual({ + [sdk.key]: {}, + }) + const custom = { [sdk.option]: { type: "adaptive", blockBinding: { prefixMismatchBehavior: "error" } } } + expect(ProviderTransform.providerOptions(model, custom)).toEqual({ [sdk.key]: custom }) + }) + test.each([ ["claude-opus-5", "default"], ["claude-opus-5", "high"],