diff --git a/internal/runtime/executor/claude_executor.go b/internal/runtime/executor/claude_executor.go index 0c31f424..2fbb235b 100644 --- a/internal/runtime/executor/claude_executor.go +++ b/internal/runtime/executor/claude_executor.go @@ -74,6 +74,9 @@ func (e *ClaudeExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, r } body = applyPayloadConfig(e.cfg, req.Model, body) + // Disable thinking if tool_choice forces tool use (Anthropic API constraint) + body = disableThinkingIfToolChoiceForced(body) + // Ensure max_tokens > thinking.budget_tokens when thinking is enabled body = ensureMaxTokensForThinking(req.Model, body) @@ -185,6 +188,9 @@ func (e *ClaudeExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A body = checkSystemInstructions(body) body = applyPayloadConfig(e.cfg, req.Model, body) + // Disable thinking if tool_choice forces tool use (Anthropic API constraint) + body = disableThinkingIfToolChoiceForced(body) + // Ensure max_tokens > thinking.budget_tokens when thinking is enabled body = ensureMaxTokensForThinking(req.Model, body) @@ -461,6 +467,19 @@ func (e *ClaudeExecutor) injectThinkingConfig(modelName string, metadata map[str return util.ApplyClaudeThinkingConfig(body, budget) } +// disableThinkingIfToolChoiceForced checks if tool_choice forces tool use and disables thinking. +// Anthropic API does not allow thinking when tool_choice is set to "any" or a specific tool. +// See: https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking#important-considerations +func disableThinkingIfToolChoiceForced(body []byte) []byte { + toolChoiceType := gjson.GetBytes(body, "tool_choice.type").String() + // "auto" is allowed with thinking, but "any" or "tool" (specific tool) are not + if toolChoiceType == "any" || toolChoiceType == "tool" { + // Remove thinking configuration entirely to avoid API error + body, _ = sjson.DeleteBytes(body, "thinking") + } + return body +} + // ensureMaxTokensForThinking ensures max_tokens > thinking.budget_tokens when thinking is enabled. // Anthropic API requires this constraint; violating it returns a 400 error. // This function should be called after all thinking configuration is finalized.