diff --git a/run_agent.py b/run_agent.py index 6dd28d11f..a39eedc51 100644 --- a/run_agent.py +++ b/run_agent.py @@ -5356,6 +5356,26 @@ class AIAgent: except Exception: pass + def _fire_reasoning_available(self, assistant_message) -> None: + """Fire structured reasoning progress if registered. + + This reuses the existing reasoning extractor so the callback only + receives tagged/structured reasoning, never ordinary assistant reply + text. + """ + cb = self.tool_progress_callback + if cb is None: + return + + reasoning_text = self._extract_reasoning(assistant_message) + if not reasoning_text: + return + + try: + cb("reasoning.available", "_thinking", reasoning_text[:500], None) + except Exception: + pass + def _fire_tool_gen_started(self, tool_name: str) -> None: """Notify display layer that the model is generating tool call arguments. @@ -10833,11 +10853,8 @@ class AIAgent: self.tool_progress_callback("_thinking", first_line) except Exception: pass - elif _think_text: - try: - self.tool_progress_callback("reasoning.available", "_thinking", _think_text[:500], None) - except Exception: - pass + else: + self._fire_reasoning_available(assistant_message) # Check for incomplete (opened but never closed) # This means the model ran out of output tokens mid-reasoning — retry up to 2 times diff --git a/tests/agent/test_subagent_progress.py b/tests/agent/test_subagent_progress.py index 88b2e3790..76bbccc3e 100644 --- a/tests/agent/test_subagent_progress.py +++ b/tests/agent/test_subagent_progress.py @@ -322,6 +322,18 @@ class TestThinkingCallback: ) assert len(calls) == 0 + def test_thinking_callback_keeps_stripped_preview(self): + """Subagent thinking relay should still expose the stripped preview.""" + calls = [] + self._simulate_thinking_callback( + "internal reasoning visible summary", + lambda name, preview=None: calls.append((name, preview)) + ) + assert len(calls) == 1 + assert calls[0][0] == "_thinking" + assert "internal reasoning" in calls[0][1] + assert "visible summary" in calls[0][1] + # ========================================================================= # Gateway batch flush tests @@ -386,4 +398,3 @@ class TestBatchFlush: if __name__ == "__main__": pytest.main([__file__, "-v"]) - diff --git a/tests/run_agent/test_run_agent.py b/tests/run_agent/test_run_agent.py index 9f3341101..20f9dc67d 100644 --- a/tests/run_agent/test_run_agent.py +++ b/tests/run_agent/test_run_agent.py @@ -1212,6 +1212,30 @@ class TestBuildAssistantMessage: assert result["content"] == "" +class TestReasoningAvailableProgress: + def test_emits_only_extracted_reasoning(self, agent): + calls = [] + agent.tool_progress_callback = lambda *args, **kwargs: calls.append(args) + + msg = _mock_assistant_msg( + content="internal analysisThe actual answer." + ) + + agent._fire_reasoning_available(msg) + + assert calls == [("reasoning.available", "_thinking", "internal analysis", None)] + + def test_skips_plain_answer_text(self, agent): + calls = [] + agent.tool_progress_callback = lambda *args, **kwargs: calls.append(args) + + msg = _mock_assistant_msg(content="The actual answer only.") + + agent._fire_reasoning_available(msg) + + assert calls == [] + + class TestFormatToolsForSystemMessage: def test_no_tools_returns_empty_array(self, agent): agent.tools = []