diff --git a/src/opencode_a2a/parts/text.py b/src/opencode_a2a/parts/text.py index b2250a7..b756e3e 100644 --- a/src/opencode_a2a/parts/text.py +++ b/src/opencode_a2a/parts/text.py @@ -15,10 +15,6 @@ def extract_text_from_parts(parts: Any) -> str: part_text = part.get("text") if isinstance(part_text, str): texts.append(part_text) - elif part_type == "reasoning": - part_text = part.get("text") - if isinstance(part_text, str): - texts.append(part_text) if texts: return "".join(texts).strip() return "" diff --git a/tests/execution/test_streaming_output_contract_core.py b/tests/execution/test_streaming_output_contract_core.py index d80580c..bb42bde 100644 --- a/tests/execution/test_streaming_output_contract_core.py +++ b/tests/execution/test_streaming_output_contract_core.py @@ -345,6 +345,47 @@ async def test_streaming_emits_final_snapshot_only_when_stream_has_no_final_answ assert final_event.last_chunk is True +@pytest.mark.asyncio +async def test_streaming_final_snapshot_does_not_repeat_reasoning_content() -> None: + client = DummyStreamingClient( + stream_events_payload=[ + _event(session_id="ses-1", role="assistant", part_type="reasoning", delta="draft plan"), + ], + response_text="final answer from send_message", + response_raw={ + "parts": [ + {"type": "reasoning", "text": "draft plan"}, + {"type": "text", "text": "final answer from send_message"}, + ] + }, + ) + executor = OpencodeAgentExecutor(client, streaming_enabled=True) + executor._should_stream = lambda context: True # type: ignore[method-assign] + queue = DummyEventQueue() + + await executor.execute( + make_request_context(task_id="task-3b", context_id="ctx-3b", text="hello"), queue + ) + + reasoning_updates = [ + event + for event in _artifact_updates(queue) + if _artifact_stream_meta(event)["block_type"] == "reasoning" + ] + assert len(reasoning_updates) == 1 + assert _part_text(reasoning_updates[0]) == "draft plan" + + text_updates = [ + event + for event in _artifact_updates(queue) + if _artifact_stream_meta(event)["block_type"] == "text" + ] + assert len(text_updates) == 1 + assert _part_text(text_updates[0]) == "final answer from send_message" + assert "draft plan" not in _part_text(text_updates[0]) + assert _artifact_stream_meta(text_updates[0])["source"] == "final_snapshot" + + @pytest.mark.asyncio async def test_execute_serializes_send_message_per_session() -> None: client = DummyStreamingClient( diff --git a/tests/parts/test_parts_text.py b/tests/parts/test_parts_text.py index dab2182..b2aceea 100644 --- a/tests/parts/test_parts_text.py +++ b/tests/parts/test_parts_text.py @@ -35,7 +35,7 @@ def test_extract_text_from_parts_returns_text_parts_only() -> None: assert extract_text_from_parts(parts) == "final answer" -def test_extract_text_from_parts_merges_text_and_reasoning_parts() -> None: +def test_extract_text_from_parts_ignores_reasoning_parts() -> None: parts = [ "skip-me", { @@ -52,4 +52,4 @@ def test_extract_text_from_parts_merges_text_and_reasoning_parts() -> None: }, ] - assert extract_text_from_parts(parts) == "draft answer" + assert extract_text_from_parts(parts) == "answer" diff --git a/tests/upstream/test_opencode_upstream_client_params.py b/tests/upstream/test_opencode_upstream_client_params.py index d9ceaec..d294b84 100644 --- a/tests/upstream/test_opencode_upstream_client_params.py +++ b/tests/upstream/test_opencode_upstream_client_params.py @@ -807,6 +807,39 @@ async def fake_post(path: str, *, params=None, json=None, timeout=_UNSET, **_kwa await client.close() +@pytest.mark.asyncio +async def test_send_message_response_text_ignores_reasoning_parts(monkeypatch) -> None: + client = OpencodeUpstreamClient( + make_settings( + a2a_bearer_token="t-1", + opencode_timeout=1.0, + a2a_log_level="DEBUG", + a2a_log_payloads=False, + ) + ) + + async def fake_post(path: str, *, params=None, json=None, timeout=_UNSET, **_kwargs): + del path, params, json, timeout + return _DummyResponse( + { + "info": {"id": "m-2"}, + "parts": [ + {"type": "reasoning", "text": "draft plan"}, + {"type": "text", "text": "final answer"}, + ], + } + ) + + monkeypatch.setattr(client._client, "post", fake_post) + + message = await client.send_message("ses-1", "hello") + + assert message.message_id == "m-2" + assert message.text == "final answer" + + await client.close() + + @pytest.mark.asyncio async def test_interrupt_request_helpers_ignore_invalid_and_trim_values() -> None: client = OpencodeUpstreamClient(