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: 7 additions & 6 deletions src/a2a/compat/v0_3/rest_transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from google.protobuf.json_format import MessageToDict, Parse, ParseDict

from a2a.client.card_resolver import parse_agent_card
from a2a.client.client import ClientCallContext
from a2a.client.errors import A2AClientError
from a2a.client.transports.base import ClientTransport
Expand Down Expand Up @@ -46,6 +47,7 @@
)
from a2a.utils.constants import PROTOCOL_VERSION_0_3, VERSION_HEADER
from a2a.utils.errors import JSON_RPC_ERROR_CODE_MAP, MethodNotFoundError
from a2a.utils.proto_utils import warn_on_missing_required_fields
from a2a.utils.telemetry import SpanKind, trace_class


Expand Down Expand Up @@ -331,12 +333,11 @@ async def get_extended_agent_card(
response_data = await self._execute_request(
'GET', '/v1/card', context=context
)
resp_proto = ParseDict(
Comment thread
Iwaniukooo11 marked this conversation as resolved.
Resolved
response_data, a2a_v0_3_pb2.AgentCard(), ignore_unknown_fields=True
)
card = conversions.to_core_agent_card(
proto_utils.FromProto.agent_card(resp_proto)
)
# v0.3 servers send the card as JSON. parse_agent_card reads it like
# the card resolver does, and still accepts a card missing required
# fields, which only gets a warning.
card = parse_agent_card(response_data)
warn_on_missing_required_fields(card, 'v0.3 extended agent card')
self.agent_card = card
return card

Expand Down
51 changes: 51 additions & 0 deletions tests/compat/v0_3/test_rest_transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import pytest

from a2a.client.errors import A2AClientError
from a2a.compat.v0_3 import types as types_v03
from a2a.compat.v0_3.rest_transport import CompatRestTransport
from a2a.types.a2a_pb2 import (
AgentCapabilities,
Expand Down Expand Up @@ -191,6 +192,56 @@ async def mock_send_request(*args, **kwargs):
assert response.capabilities.extended_agent_card is True


@pytest.mark.asyncio
async def test_compat_rest_transport_get_extended_agent_card_keeps_security(
transport,
):
"""v0.3 servers send the card as JSON, not as the v0.3 proto."""
card = types_v03.AgentCard(
name='ExtendedAgent',
description='Extended',
version='1.0.0',
url='http://example.com',
capabilities=types_v03.AgentCapabilities(),
default_input_modes=['text/plain'],
default_output_modes=['text/plain'],
skills=[
types_v03.AgentSkill(
id='skill-1',
name='Skill',
description='Skill',
tags=['tag'],
security=[{'bearer': []}],
)
],
security_schemes={
'bearer': types_v03.SecurityScheme(
root=types_v03.HTTPAuthSecurityScheme(scheme='bearer')
)
},
security=[{'bearer': []}],
supports_authenticated_extended_card=True,
)

async def mock_send_request(*args, **kwargs):
# Same serialization as REST03Handler.on_get_extended_agent_card.
return card.model_dump(mode='json', exclude_none=True)

transport._send_request = mock_send_request

response = await transport.get_extended_agent_card(
GetExtendedAgentCardRequest()
)

scheme = response.security_schemes['bearer']
assert scheme.WhichOneof('scheme') == 'http_auth_security_scheme'
assert scheme.http_auth_security_scheme.scheme == 'bearer'
assert list(response.security_requirements[0].schemes) == ['bearer']
assert list(response.skills[0].security_requirements[0].schemes) == [
'bearer'
]


@pytest.mark.asyncio
async def test_compat_rest_transport_get_extended_agent_card_not_supported(
transport,
Expand Down
Loading