Skip to content
Open
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
13 changes: 12 additions & 1 deletion rogue/run_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,18 @@ async def get_a2a_agent_card(
agent_url: str,
headers: dict[str, str] | None = None,
) -> AgentCard:
if transport == Transport.HTTP:
"""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,
Transport.SSE,
):
try:
response = requests.get(
f"{agent_url}/.well-known/agent.json",
Expand Down
58 changes: 57 additions & 1 deletion rogue/tests/test_run_cli.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import asyncio
import json
from argparse import Namespace
from pathlib import Path
Expand All @@ -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


Expand Down Expand Up @@ -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,
],
)
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
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) -> 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):
asyncio.run(
get_a2a_agent_card(
agent_url="https://localhost:10001",
transport=Transport.CHAT_COMPLETIONS,
)
)
mock_get.assert_not_called()