diff --git a/run_agent.py b/run_agent.py index 59a547f0d..3a939d161 100644 --- a/run_agent.py +++ b/run_agent.py @@ -1369,8 +1369,9 @@ class AIAgent: ] if assistant_message.tool_calls: - msg["tool_calls"] = [ - { + tc_list = [] + for tool_call in assistant_message.tool_calls: + tc_dict = { "id": tool_call.id, "type": tool_call.type, "function": { @@ -1378,8 +1379,17 @@ class AIAgent: "arguments": tool_call.function.arguments } } - for tool_call in assistant_message.tool_calls - ] + # Preserve extra_content (e.g. Gemini thought_signature) so it + # is sent back on subsequent API calls. Without this, Gemini 3 + # thinking models reject the request with a 400 error. + extra = getattr(tool_call, "extra_content", None) + if extra is not None: + # Convert Pydantic models to plain dicts for JSON safety + if hasattr(extra, "model_dump"): + extra = extra.model_dump() + tc_dict["extra_content"] = extra + tc_list.append(tc_dict) + msg["tool_calls"] = tc_list return msg diff --git a/tests/test_run_agent.py b/tests/test_run_agent.py index 2d3703933..ad90bd270 100644 --- a/tests/test_run_agent.py +++ b/tests/test_run_agent.py @@ -546,6 +546,24 @@ class TestBuildAssistantMessage: result = agent._build_assistant_message(msg, "stop") assert result["content"] == "" + def test_tool_call_extra_content_preserved(self, agent): + """Gemini thinking models attach extra_content with thought_signature + to tool calls. This must be preserved so subsequent API calls include it.""" + tc = _mock_tool_call(name="get_weather", arguments='{"city":"NYC"}', call_id="c2") + tc.extra_content = {"google": {"thought_signature": "abc123"}} + msg = _mock_assistant_msg(content="", tool_calls=[tc]) + result = agent._build_assistant_message(msg, "tool_calls") + assert result["tool_calls"][0]["extra_content"] == { + "google": {"thought_signature": "abc123"} + } + + def test_tool_call_without_extra_content(self, agent): + """Standard tool calls (no thinking model) should not have extra_content.""" + tc = _mock_tool_call(name="web_search", arguments='{}', call_id="c3") + msg = _mock_assistant_msg(content="", tool_calls=[tc]) + result = agent._build_assistant_message(msg, "tool_calls") + assert "extra_content" not in result["tool_calls"][0] + class TestFormatToolsForSystemMessage: def test_no_tools_returns_empty_array(self, agent):