diff --git a/tests/test_gemini_thinking_format.py b/tests/test_gemini_thinking_format.py
new file mode 100644
index 00000000..f4c236b4
--- /dev/null
+++ b/tests/test_gemini_thinking_format.py
@@ -0,0 +1,76 @@
+"""Verify Gemini thinking content is properly extracted and wrapped for parse_thinking_trace.
+
+The google-genai SDK separates thinking content from visible text by marking parts with
+thought=True. The SDK filters these out of resp.text, so thinking monologues don't render
+in the Discussion Hub. _send_gemini now calls _extract_gemini_thoughts and wraps the
+extracted text in ... tags so thinking_parser can extract a
+ThinkingSegment.
+"""
+from unittest.mock import MagicMock, patch
+from google.genai.types import Part, Content, Candidate, GenerateContentResponse
+
+from src.ai_client import _extract_gemini_thoughts
+from src.thinking_parser import parse_thinking_trace
+
+
+def test_extract_gemini_thoughts_returns_thinking_only() -> None:
+ """The helper must return concatenated thought=True parts and ignore thought=False parts."""
+ resp = GenerateContentResponse(
+ candidates=[Candidate(content=Content(parts=[
+ Part(text="step 1 reasoning", thought=True),
+ Part(text="visible text 1", thought=False),
+ Part(text="step 2 reasoning", thought=True),
+ Part(text="visible text 2"),
+ ]))]
+ )
+ thoughts = _extract_gemini_thoughts(resp)
+ assert thoughts == "step 1 reasoningstep 2 reasoning"
+
+
+def test_extract_gemini_thoughts_returns_empty_when_no_thoughts() -> None:
+ """No thought parts => empty string (the wrap is conditional)."""
+ resp = GenerateContentResponse(
+ candidates=[Candidate(content=Content(parts=[Part(text="just visible")]))]
+ )
+ assert _extract_gemini_thoughts(resp) == ""
+
+
+def test_extract_gemini_thoughts_handles_missing_attributes() -> None:
+ """Defensive: must not crash on objects without expected attributes."""
+ fake = MagicMock()
+ fake.candidates = [MagicMock()]
+ fake.candidates[0].content.parts = [MagicMock(thought=True, text="thinking text")]
+ assert _extract_gemini_thoughts(fake) == "thinking text"
+ fake.candidates = []
+ assert _extract_gemini_thoughts(fake) == ""
+
+
+def test_gemini_thinking_segment_extractable_after_wrap() -> None:
+ """End-to-end: the wrapped output must be parseable by thinking_parser.parse_thinking_trace."""
+ resp = GenerateContentResponse(
+ candidates=[Candidate(content=Content(parts=[
+ Part(text="my reasoning chain", thought=True),
+ Part(text="final answer"),
+ ]))]
+ )
+ thoughts = _extract_gemini_thoughts(resp)
+ wrapped = f"\n{thoughts}\n\n\nfinal answer"
+ segments, response = parse_thinking_trace(wrapped)
+ assert len(segments) == 1
+ assert segments[0].content == "my reasoning chain"
+ assert segments[0].marker == "thinking"
+ assert response == "final answer"
+
+
+def test_extract_gemini_thoughts_handles_none_resp() -> None:
+ """Defensive: must not crash on None response."""
+ assert _extract_gemini_thoughts(None) == ""
+
+
+if __name__ == "__main__":
+ test_extract_gemini_thoughts_returns_thinking_only()
+ test_extract_gemini_thoughts_returns_empty_when_no_thoughts()
+ test_extract_gemini_thoughts_handles_missing_attributes()
+ test_gemini_thinking_segment_extractable_after_wrap()
+ test_extract_gemini_thoughts_handles_none_resp()
+ print("All Gemini thinking format tests passed!")