From 2781f6f0009c5aff061d56dcbdfb38ac8bc0567d Mon Sep 17 00:00:00 2001 From: Diogo_Damasceno <79182561+Diogo-Damasceno@users.noreply.github.com> Date: Thu, 3 Sep 2026 13:32:39 -0300 Subject: [PATCH 1/3] Fix #150: support STREAMABLE_HTTP/SSE in A2A agent card fetch (CLI crash) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit get_a2a_agent_card only accepted Transport.HTTP and raised 'ValueError: Unsupported transport: Transport.STREAMABLE_HTTP for A2A protocol' for any other transport. The CLI calls ping_agent() at startup, so running against an A2A agent exposed over Streamable HTTP (the modern A2A default) crashed the whole CLI on launch (issue #150). The A2A AgentCard is always served over HTTP at /.well-known/agent.json regardless of the RPC transport used for agent calls, so HTTP, STREAMABLE_HTTP and SSE all expose the card the same way. Accept those and only reject transports that genuinely cannot serve an A2A card. Verified: rogue/tests/test_run_cli.py passes (7 passed) — added regression tests covering HTTP/STREAMABLE_HTTP/SSE acceptance and CHAT_COMPLETIONS rejection. ruff check + ruff format clean. --- rogue/run_cli.py | 10 ++++++- rogue/tests/test_run_cli.py | 58 ++++++++++++++++++++++++++++++++++++- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/rogue/run_cli.py b/rogue/run_cli.py index 9ea760c1..196c2d49 100644 --- a/rogue/run_cli.py +++ b/rogue/run_cli.py @@ -410,7 +410,15 @@ async def get_a2a_agent_card( agent_url: str, headers: dict[str, str] | None = None, ) -> AgentCard: - if transport == Transport.HTTP: + # The A2A AgentCard is always served over HTTP at the well-known path, + # regardless of the RPC transport used for agent calls (HTTP, Streamable + # HTTP and SSE all expose the card the same way). Accept those and only + # reject transports that cannot serve an A2A card. + if transport in ( + Transport.HTTP, + Transport.STREAMABLE_HTTP, + Transport.SSE, + ): try: response = requests.get( f"{agent_url}/.well-known/agent.json", diff --git a/rogue/tests/test_run_cli.py b/rogue/tests/test_run_cli.py index d8f23afa..7f93d7a1 100644 --- a/rogue/tests/test_run_cli.py +++ b/rogue/tests/test_run_cli.py @@ -1,3 +1,4 @@ +import asyncio import json from argparse import Namespace from pathlib import Path @@ -7,7 +8,7 @@ from pytest_mock import MockerFixture from rogue.models.cli_input import CLIInput -from rogue.run_cli import get_cli_input +from rogue.run_cli import get_a2a_agent_card, get_cli_input from rogue_sdk.types import AuthType, Protocol, Transport @@ -101,3 +102,58 @@ def test_get_cli_input( cli_args.config_file = config_file assert get_cli_input(cli_args) == expected + + +_AGENT_CARD_JSON = { + "name": "test-agent", + "description": "A test agent", + "url": "https://localhost:10001", + "version": "0.2.0", + "capabilities": {"streaming": False, "pushNotifications": False}, + "defaultInputModes": ["text/plain"], + "defaultOutputModes": ["text/plain"], + "skills": [], +} + + +@pytest.mark.parametrize( + "transport", + [ + Transport.HTTP, + Transport.STREAMABLE_HTTP, + Transport.SSE, + ], +) +def test_get_a2a_agent_card_accepts_http_transports( + transport: Transport, + mocker: MockerFixture, +): + """Regression test for issue #150. + + The A2A agent card is served over HTTP at the well-known path regardless of + the RPC transport, so STREAMABLE_HTTP/SSE must not raise + 'Unsupported transport'. Before the fix only Transport.HTTP was accepted. + """ + mock_get = mocker.patch("rogue.run_cli.requests.get") + mock_get.return_value.json.return_value = _AGENT_CARD_JSON + mock_get.return_value.status_code = 200 + + card = asyncio.run( + get_a2a_agent_card(agent_url="https://localhost:10001", transport=transport) + ) + assert card is not None + assert card.name == "test-agent" + mock_get.assert_called_once() + + +def test_get_a2a_agent_card_rejects_unsupported_transport(mocker: MockerFixture): + """Non-HTTP transports for A2A (e.g. CHAT_COMPLETIONS) still raise.""" + mock_get = mocker.patch("rogue.run_cli.requests.get") + with pytest.raises(ValueError): + asyncio.run( + get_a2a_agent_card( + agent_url="https://localhost:10001", + transport=Transport.CHAT_COMPLETIONS, + ) + ) + mock_get.assert_not_called() From d146f71bcd52d6ab9e63cdbd66d8210e4a20b171 Mon Sep 17 00:00:00 2001 From: Diogo_Damasceno <79182561+Diogo-Damasceno@users.noreply.github.com> Date: Thu, 3 Sep 2026 14:43:30 -0300 Subject: [PATCH 2/3] rogue #178: add docstring to get_a2a_agent_card (address CodeRabbit docstring coverage) CodeRabbit pre-merge check flagged docstring coverage < 80% on the functions touched by this PR. Convert the inline comment on get_a2a_agent_card into a proper docstring and tidy the rejection-test docstring. No behaviour change. Verified: ruff check clean, tests/test_run_cli.py 7 passed. --- rogue/run_cli.py | 11 +++++++---- rogue/tests/test_run_cli.py | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/rogue/run_cli.py b/rogue/run_cli.py index 196c2d49..f6b4fc38 100644 --- a/rogue/run_cli.py +++ b/rogue/run_cli.py @@ -410,10 +410,13 @@ async def get_a2a_agent_card( agent_url: str, headers: dict[str, str] | None = None, ) -> AgentCard: - # The A2A AgentCard is always served over HTTP at the well-known path, - # regardless of the RPC transport used for agent calls (HTTP, Streamable - # HTTP and SSE all expose the card the same way). Accept those and only - # reject transports that cannot serve an A2A card. + """Fetch and validate an A2A agent's AgentCard from its well-known path. + + The A2A AgentCard is always served over HTTP at `/.well-known/agent.json` + regardless of the RPC transport used for agent calls, so HTTP, STREAMABLE_HTTP + and SSE all expose the card the same way. Accept those and only reject + transports that cannot serve an A2A card (e.g. CHAT_COMPLETIONS). + """ if transport in ( Transport.HTTP, Transport.STREAMABLE_HTTP, diff --git a/rogue/tests/test_run_cli.py b/rogue/tests/test_run_cli.py index 7f93d7a1..6c2dc519 100644 --- a/rogue/tests/test_run_cli.py +++ b/rogue/tests/test_run_cli.py @@ -147,7 +147,7 @@ def test_get_a2a_agent_card_accepts_http_transports( def test_get_a2a_agent_card_rejects_unsupported_transport(mocker: MockerFixture): - """Non-HTTP transports for A2A (e.g. CHAT_COMPLETIONS) still raise.""" + """Non-HTTP transports for A2A (e.g. CHAT_COMPLETIONS) must still raise.""" mock_get = mocker.patch("rogue.run_cli.requests.get") with pytest.raises(ValueError): asyncio.run( From 85b7313d2d2845676664ceb4c4d5e057a5a28a60 Mon Sep 17 00:00:00 2001 From: Diogo_Damasceno <79182561+Diogo-Damasceno@users.noreply.github.com> Date: Thu, 3 Sep 2026 16:17:22 -0300 Subject: [PATCH 3/3] test(run_cli): add -> None return annotations to new A2A tests (review nit) --- rogue/tests/test_run_cli.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rogue/tests/test_run_cli.py b/rogue/tests/test_run_cli.py index 6c2dc519..cc813e0a 100644 --- a/rogue/tests/test_run_cli.py +++ b/rogue/tests/test_run_cli.py @@ -124,10 +124,10 @@ def test_get_cli_input( Transport.SSE, ], ) -def test_get_a2a_agent_card_accepts_http_transports( +async def test_get_a2a_agent_card_accepts_http_transports( transport: Transport, mocker: MockerFixture, -): +) -> None: """Regression test for issue #150. The A2A agent card is served over HTTP at the well-known path regardless of @@ -146,7 +146,7 @@ def test_get_a2a_agent_card_accepts_http_transports( mock_get.assert_called_once() -def test_get_a2a_agent_card_rejects_unsupported_transport(mocker: MockerFixture): +def test_get_a2a_agent_card_rejects_unsupported_transport(mocker: MockerFixture) -> None: """Non-HTTP transports for A2A (e.g. CHAT_COMPLETIONS) must still raise.""" mock_get = mocker.patch("rogue.run_cli.requests.get") with pytest.raises(ValueError):