From 12c4471d64544ed8198d4eb78af86387f8c9c17c Mon Sep 17 00:00:00 2001 From: Roo Code Date: Tue, 12 May 2026 05:15:13 +0000 Subject: [PATCH] fix(bedrock): exclude temperature for Opus 4.7 and add test coverage - Add supportsTemperature: false to Opus 4.7 model definition - Conditionally exclude temperature from inferenceConfig when unsupported - Add tests for adaptive thinking and temperature exclusion Ported from PR #12288 commits a682355 and c2274d8 --- packages/types/src/providers/bedrock.ts | 1 + .../__tests__/bedrock-reasoning.spec.ts | 104 ++++++++++++++++++ src/api/providers/bedrock.ts | 10 +- 3 files changed, 113 insertions(+), 2 deletions(-) diff --git a/packages/types/src/providers/bedrock.ts b/packages/types/src/providers/bedrock.ts index 62e180ad30..6d91d0e6c3 100644 --- a/packages/types/src/providers/bedrock.ts +++ b/packages/types/src/providers/bedrock.ts @@ -173,6 +173,7 @@ export const bedrockModels = { supportsImages: true, supportsPromptCache: true, supportsReasoningBudget: true, + supportsTemperature: false, inputPrice: 5.0, // $5 per million input tokens (≤200K context) outputPrice: 25.0, // $25 per million output tokens (≤200K context) cacheWritesPrice: 6.25, // $6.25 per million tokens diff --git a/src/api/providers/__tests__/bedrock-reasoning.spec.ts b/src/api/providers/__tests__/bedrock-reasoning.spec.ts index 9dd271744c..ed86dcb9e6 100644 --- a/src/api/providers/__tests__/bedrock-reasoning.spec.ts +++ b/src/api/providers/__tests__/bedrock-reasoning.spec.ts @@ -282,6 +282,110 @@ describe("AwsBedrockHandler - Extended Thinking", () => { expect(reasoningChunks[1].text).toBe(" about this problem.") }) + it("should use adaptive thinking for Opus 4.7 instead of enabled with budget_tokens", async () => { + handler = new AwsBedrockHandler({ + apiProvider: "bedrock", + apiModelId: "anthropic.claude-opus-4-7", + awsRegion: "us-east-1", + enableReasoningEffort: true, + modelMaxTokens: 8192, + modelMaxThinkingTokens: 4096, + }) + + mockSend.mockResolvedValue({ + stream: (async function* () { + yield { messageStart: { role: "assistant" } } + yield { + contentBlockStart: { + content_block: { type: "thinking", thinking: "Thinking adaptively..." }, + contentBlockIndex: 0, + }, + } + yield { metadata: { usage: { inputTokens: 100, outputTokens: 50 } } } + })(), + }) + + const messages = [{ role: "user" as const, content: "Test message" }] + const stream = handler.createMessage("System prompt", messages) + + const chunks = [] + for await (const chunk of stream) { + chunks.push(chunk) + } + + // Opus 4.7 must use thinking.type: "adaptive" with output_config.effort + expect(mockSend).toHaveBeenCalledTimes(1) + expect(capturedPayload).toBeDefined() + expect(capturedPayload.additionalModelRequestFields).toBeDefined() + expect(capturedPayload.additionalModelRequestFields.thinking).toEqual({ + type: "adaptive", + }) + expect(capturedPayload.additionalModelRequestFields.output_config).toEqual({ + effort: "high", + }) + + // Must NOT have budget_tokens (causes 400 error on Opus 4.7) + expect(capturedPayload.additionalModelRequestFields.thinking).not.toHaveProperty("budget_tokens") + }) + + it("should exclude temperature from inferenceConfig for Opus 4.7 (supportsTemperature: false)", async () => { + handler = new AwsBedrockHandler({ + apiProvider: "bedrock", + apiModelId: "anthropic.claude-opus-4-7", + awsRegion: "us-east-1", + modelTemperature: 0.7, + }) + + mockSend.mockResolvedValue({ + stream: (async function* () { + yield { messageStart: { role: "assistant" } } + yield { metadata: { usage: { inputTokens: 100, outputTokens: 50 } } } + })(), + }) + + const messages = [{ role: "user" as const, content: "Test message" }] + const stream = handler.createMessage("System prompt", messages) + + for await (const chunk of stream) { + // consume stream + } + + expect(mockSend).toHaveBeenCalledTimes(1) + expect(capturedPayload).toBeDefined() + // Temperature must NOT be present for Opus 4.7 + expect(capturedPayload.inferenceConfig).not.toHaveProperty("temperature") + // maxTokens should still be present + expect(capturedPayload.inferenceConfig).toHaveProperty("maxTokens") + }) + + it("should include temperature in inferenceConfig for models that support it", async () => { + handler = new AwsBedrockHandler({ + apiProvider: "bedrock", + apiModelId: "anthropic.claude-sonnet-4-20250514-v1:0", + awsRegion: "us-east-1", + modelTemperature: 0.5, + }) + + mockSend.mockResolvedValue({ + stream: (async function* () { + yield { messageStart: { role: "assistant" } } + yield { metadata: { usage: { inputTokens: 100, outputTokens: 50 } } } + })(), + }) + + const messages = [{ role: "user" as const, content: "Test message" }] + const stream = handler.createMessage("System prompt", messages) + + for await (const chunk of stream) { + // consume stream + } + + expect(mockSend).toHaveBeenCalledTimes(1) + expect(capturedPayload).toBeDefined() + // Temperature should be present for Sonnet 4 + expect(capturedPayload.inferenceConfig).toHaveProperty("temperature", 0.5) + }) + it("should support API key authentication", async () => { handler = new AwsBedrockHandler({ apiProvider: "bedrock", diff --git a/src/api/providers/bedrock.ts b/src/api/providers/bedrock.ts index 9283fe0ec1..6de811e2ae 100644 --- a/src/api/providers/bedrock.ts +++ b/src/api/providers/bedrock.ts @@ -431,7 +431,10 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH const inferenceConfig: BedrockInferenceConfig = { maxTokens: modelConfig.maxTokens || (modelConfig.info.maxTokens as number), - temperature: modelConfig.temperature ?? (this.options.modelTemperature as number), + // Only include temperature if the model supports it (Opus 4.7 deprecated temperature) + ...(modelConfig.info.supportsTemperature !== false && { + temperature: modelConfig.temperature ?? (this.options.modelTemperature as number), + }), } // Check if 1M context is enabled for supported Claude 4 models @@ -769,7 +772,10 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH const inferenceConfig: BedrockInferenceConfig = { maxTokens: modelConfig.maxTokens || (modelConfig.info.maxTokens as number), - temperature: modelConfig.temperature ?? (this.options.modelTemperature as number), + // Only include temperature if the model supports it (Opus 4.7 deprecated temperature) + ...(modelConfig.info.supportsTemperature !== false && { + temperature: modelConfig.temperature ?? (this.options.modelTemperature as number), + }), } // For completePrompt, use a unique conversation ID based on the prompt