Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions src/opencode_a2a/parts/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 ""
41 changes: 41 additions & 0 deletions tests/execution/test_streaming_output_contract_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
4 changes: 2 additions & 2 deletions tests/parts/test_parts_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
{
Expand All @@ -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"
33 changes: 33 additions & 0 deletions tests/upstream/test_opencode_upstream_client_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Loading