diff --git a/agent/gemini_native_adapter.py b/agent/gemini_native_adapter.py index 1c25f1e6cf0..b4f6e6386e7 100644 --- a/agent/gemini_native_adapter.py +++ b/agent/gemini_native_adapter.py @@ -270,8 +270,12 @@ def _translate_tool_call_to_gemini(tool_call: Dict[str, Any]) -> Dict[str, Any]: } } thought_signature = _tool_call_extra_signature(tool_call) - if thought_signature: - part["thoughtSignature"] = thought_signature + # Fallback sentinel for cross-provider tool_calls (e.g. fallback from + # xAI/Anthropic to Gemini, where the original tool_call carries no + # Gemini thoughtSignature). Mirrors gemini_cloudcode_adapter.py:106. + # Without this, Gemini 3 thinking models reject replayed history with + # 400 INVALID_ARGUMENT on the missing thoughtSignature. + part["thoughtSignature"] = thought_signature or "skip_thought_signature_validator" return part diff --git a/tests/agent/test_gemini_native_adapter.py b/tests/agent/test_gemini_native_adapter.py index e797d6b2ae7..e7493fe4918 100644 --- a/tests/agent/test_gemini_native_adapter.py +++ b/tests/agent/test_gemini_native_adapter.py @@ -52,6 +52,43 @@ def test_build_native_request_preserves_thought_signature_on_tool_replay(): assert parts[0]["thoughtSignature"] == "sig-123" +def test_build_native_request_emits_sentinel_for_cross_provider_tool_call(): + """Cross-provider tool_calls (xAI/Anthropic -> Gemini fallback) carry no + Gemini thoughtSignature. Without a sentinel, Gemini 3 thinking models + reject the request with 400 INVALID_ARGUMENT. The native adapter must + emit the same ``skip_thought_signature_validator`` sentinel that the + Cloud Code Assist adapter already uses for the same scenario. + """ + from agent.gemini_native_adapter import build_gemini_request + + request = build_gemini_request( + messages=[ + { + "role": "assistant", + "content": "", + "tool_calls": [ + { + "id": "call_1", + "type": "function", + "function": { + "name": "get_weather", + "arguments": '{"city": "Paris"}', + }, + # No extra_content — this tool_call originated from a + # non-Gemini provider during fallback. + } + ], + }, + ], + tools=[], + tool_choice=None, + ) + + parts = request["contents"][0]["parts"] + assert parts[0]["functionCall"]["name"] == "get_weather" + assert parts[0]["thoughtSignature"] == "skip_thought_signature_validator" + + def test_build_native_request_uses_original_function_name_for_tool_result(): from agent.gemini_native_adapter import build_gemini_request