diff --git a/internal/translator/gemini-cli/openai/chat-completions/gemini-cli_openai_request.go b/internal/translator/gemini-cli/openai/chat-completions/gemini-cli_openai_request.go index 5da6c2c6..a5edbcae 100644 --- a/internal/translator/gemini-cli/openai/chat-completions/gemini-cli_openai_request.go +++ b/internal/translator/gemini-cli/openai/chat-completions/gemini-cli_openai_request.go @@ -33,8 +33,10 @@ func ConvertOpenAIRequestToGeminiCLI(modelName string, inputRawJSON []byte, _ bo out, _ = sjson.SetBytes(out, "model", modelName) // Reasoning effort -> thinkingBudget/include_thoughts + // Note: OpenAI official fields take precedence over extra_body.google.thinking_config re := gjson.GetBytes(rawJSON, "reasoning_effort") - if re.Exists() && util.ModelSupportsThinking(modelName) { + hasOfficialThinking := re.Exists() + if hasOfficialThinking && util.ModelSupportsThinking(modelName) { switch re.String() { case "none": out, _ = sjson.DeleteBytes(out, "request.generationConfig.thinkingConfig.include_thoughts") @@ -52,6 +54,19 @@ func ConvertOpenAIRequestToGeminiCLI(modelName string, inputRawJSON []byte, _ bo } } + // Cherry Studio extension extra_body.google.thinking_config (effective only when official fields are absent) + if !hasOfficialThinking && util.ModelSupportsThinking(modelName) { + if tc := gjson.GetBytes(rawJSON, "extra_body.google.thinking_config"); tc.Exists() && tc.IsObject() { + if v := tc.Get("thinking_budget"); v.Exists() { + budget := util.NormalizeThinkingBudget(modelName, int(v.Int())) + out, _ = sjson.SetBytes(out, "request.generationConfig.thinkingConfig.thinkingBudget", budget) + } + if v := tc.Get("include_thoughts"); v.Exists() { + out, _ = sjson.SetBytes(out, "request.generationConfig.thinkingConfig.include_thoughts", v.Bool()) + } + } + } + // Temperature/top_p/top_k if tr := gjson.GetBytes(rawJSON, "temperature"); tr.Exists() && tr.Type == gjson.Number { out, _ = sjson.SetBytes(out, "request.generationConfig.temperature", tr.Num) diff --git a/internal/translator/gemini/openai/chat-completions/gemini_openai_request.go b/internal/translator/gemini/openai/chat-completions/gemini_openai_request.go index f89ec16a..b842569d 100644 --- a/internal/translator/gemini/openai/chat-completions/gemini_openai_request.go +++ b/internal/translator/gemini/openai/chat-completions/gemini_openai_request.go @@ -33,8 +33,10 @@ func ConvertOpenAIRequestToGemini(modelName string, inputRawJSON []byte, _ bool) out, _ = sjson.SetBytes(out, "model", modelName) // Reasoning effort -> thinkingBudget/include_thoughts + // Note: OpenAI official fields take precedence over extra_body.google.thinking_config re := gjson.GetBytes(rawJSON, "reasoning_effort") - if re.Exists() && util.ModelSupportsThinking(modelName) { + hasOfficialThinking := re.Exists() + if hasOfficialThinking && util.ModelSupportsThinking(modelName) { switch re.String() { case "none": out, _ = sjson.DeleteBytes(out, "generationConfig.thinkingConfig.include_thoughts") @@ -52,6 +54,20 @@ func ConvertOpenAIRequestToGemini(modelName string, inputRawJSON []byte, _ bool) } } + // Cherry Studio extension extra_body.google.thinking_config (effective only when official fields are absent) + if !hasOfficialThinking && util.ModelSupportsThinking(modelName) { + if tc := gjson.GetBytes(rawJSON, "extra_body.google.thinking_config"); tc.Exists() && tc.IsObject() { + if v := tc.Get("thinking_budget"); v.Exists() { + // Normalize budget to model range + budget := util.NormalizeThinkingBudget(modelName, int(v.Int())) + out, _ = sjson.SetBytes(out, "generationConfig.thinkingConfig.thinkingBudget", budget) + } + if v := tc.Get("include_thoughts"); v.Exists() { + out, _ = sjson.SetBytes(out, "generationConfig.thinkingConfig.include_thoughts", v.Bool()) + } + } + } + // Temperature/top_p/top_k if tr := gjson.GetBytes(rawJSON, "temperature"); tr.Exists() && tr.Type == gjson.Number { out, _ = sjson.SetBytes(out, "generationConfig.temperature", tr.Num) diff --git a/internal/translator/gemini/openai/responses/gemini_openai-responses_request.go b/internal/translator/gemini/openai/responses/gemini_openai-responses_request.go index b54c2730..7f14280d 100644 --- a/internal/translator/gemini/openai/responses/gemini_openai-responses_request.go +++ b/internal/translator/gemini/openai/responses/gemini_openai-responses_request.go @@ -243,7 +243,10 @@ func ConvertOpenAIResponsesRequestToGemini(modelName string, inputRawJSON []byte out, _ = sjson.Set(out, "generationConfig.stopSequences", sequences) } - if reasoningEffort := root.Get("reasoning.effort"); reasoningEffort.Exists() && util.ModelSupportsThinking(modelName) { + // OpenAI official reasoning fields take precedence + hasOfficialThinking := root.Get("reasoning.effort").Exists() + if hasOfficialThinking && util.ModelSupportsThinking(modelName) { + reasoningEffort := root.Get("reasoning.effort") switch reasoningEffort.String() { case "none": out, _ = sjson.Set(out, "generationConfig.thinkingConfig.include_thoughts", false) @@ -262,5 +265,18 @@ func ConvertOpenAIResponsesRequestToGemini(modelName string, inputRawJSON []byte out, _ = sjson.Set(out, "generationConfig.thinkingConfig.thinkingBudget", -1) } } + + // Cherry Studio extension (applies only when official fields are missing) + if !hasOfficialThinking && util.ModelSupportsThinking(modelName) { + if tc := root.Get("extra_body.google.thinking_config"); tc.Exists() && tc.IsObject() { + if v := tc.Get("thinking_budget"); v.Exists() { + budget := util.NormalizeThinkingBudget(modelName, int(v.Int())) + out, _ = sjson.Set(out, "generationConfig.thinkingConfig.thinkingBudget", budget) + } + if v := tc.Get("include_thoughts"); v.Exists() { + out, _ = sjson.Set(out, "generationConfig.thinkingConfig.include_thoughts", v.Bool()) + } + } + } return []byte(out) }