diff --git a/agent/context_compressor.py b/agent/context_compressor.py
index ef40cbfafb..aaae09ea2f 100644
--- a/agent/context_compressor.py
+++ b/agent/context_compressor.py
@@ -803,8 +803,16 @@ The user has requested that this compaction PRIORITISE preserving all informatio
if self.summary_model:
call_kwargs["model"] = self.summary_model
response = call_llm(**call_kwargs)
- content = response.choices[0].message.content
- # Handle cases where content is not a string (e.g., dict from llama.cpp)
+ # Normalize dict content (e.g. llama.cpp tool calls) before
+ # extracting, then use extract_content_or_reasoning to handle
+ # models that put all output inside think/reasoning blocks with
+ # empty content field (e.g. DeepSeek-R1, Qwen-QwQ, glm-5-turbo).
+ raw_content = response.choices[0].message.content
+ if isinstance(raw_content, dict):
+ raw_content = str(raw_content) if raw_content else ""
+ response.choices[0].message.content = raw_content
+ from agent.auxiliary_client import extract_content_or_reasoning
+ content = extract_content_or_reasoning(response)
if not isinstance(content, str):
content = str(content) if content else ""
# Redact the summary output as well — the summarizer LLM may
diff --git a/tests/agent/test_context_compressor.py b/tests/agent/test_context_compressor.py
index 8072a58d98..a2872e0994 100644
--- a/tests/agent/test_context_compressor.py
+++ b/tests/agent/test_context_compressor.py
@@ -242,6 +242,68 @@ class TestSummaryFailureCooldown:
assert mock_call.call_count == 1
+class TestReasoningOnlyExtraction:
+ """Regression: reasoning models (DeepSeek-R1, QwQ, glm-5-turbo) that put
+ all output in reasoning/think blocks with empty content must still produce
+ a valid summary via extract_content_or_reasoning."""
+
+ @pytest.fixture
+ def _compressor(self):
+ with patch("agent.context_compressor.get_model_context_length", return_value=100000):
+ return ContextCompressor(model="test", quiet_mode=True)
+
+ def test_reasoning_content_extracted_as_summary(self, _compressor):
+ mock_response = MagicMock()
+ mock_response.choices = [MagicMock()]
+ mock_response.choices[0].message.content = ""
+ mock_response.choices[0].message.reasoning = "The user was working on feature X and completed steps 1-3."
+
+ messages = [
+ {"role": "user", "content": "do something"},
+ {"role": "assistant", "content": "ok"},
+ ]
+
+ with patch("agent.context_compressor.call_llm", return_value=mock_response):
+ summary = _compressor._generate_summary(messages)
+ assert isinstance(summary, str)
+ assert "feature X" in summary
+
+ def test_think_blocks_stripped_and_content_used(self, _compressor):
+ mock_response = MagicMock()
+ mock_response.choices = [MagicMock()]
+ mock_response.choices[0].message.content = "Let me analyze this.\n\n## Summary\nWorked on the API module."
+ mock_response.choices[0].message.reasoning = None
+
+ messages = [
+ {"role": "user", "content": "summarize"},
+ {"role": "assistant", "content": "done"},
+ ]
+
+ with patch("agent.context_compressor.call_llm", return_value=mock_response):
+ summary = _compressor._generate_summary(messages)
+ assert isinstance(summary, str)
+ assert "API module" in summary
+
+ def test_inline_think_tags_stripped(self, _compressor):
+ mock_response = MagicMock()
+ mock_response.choices = [MagicMock()]
+ # extract_content_or_reasoning strips XML tags but not emoji blocks
+ mock_response.choices[0].message.content = "Internal reasoning here.\n\nThe actual summary content."
+ mock_response.choices[0].message.reasoning = None
+
+ messages = [
+ {"role": "user", "content": "summarize"},
+ {"role": "assistant", "content": "done"},
+ ]
+
+ with patch("agent.context_compressor.call_llm", return_value=mock_response):
+ summary = _compressor._generate_summary(messages)
+ assert isinstance(summary, str)
+ assert "actual summary" in summary
+ # Think block content should not leak into summary
+ assert "Internal reasoning" not in summary
+
+
class TestSummaryPrefixNormalization:
def test_legacy_prefix_is_replaced(self):
summary = ContextCompressor._with_summary_prefix("[CONTEXT SUMMARY]: did work")