Skip to content

Commit aac8b00

Browse files
committed
wip
1 parent 5f3ea29 commit aac8b00

6 files changed

Lines changed: 815 additions & 812 deletions

File tree

src/a2a/client/card_resolver.py

Lines changed: 106 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,9 @@
66

77
import httpx
88

9-
from google.protobuf.json_format import ParseError
9+
from google.protobuf.json_format import ParseDict, ParseError
1010

1111
from a2a.client.errors import AgentCardResolutionError
12-
from a2a.client.helpers import parse_agent_card
1312
from a2a.types.a2a_pb2 import (
1413
AgentCard,
1514
)
@@ -19,6 +18,111 @@
1918
logger = logging.getLogger(__name__)
2019

2120

21+
def parse_agent_card(agent_card_data: dict[str, Any]) -> AgentCard:
22+
"""Parse AgentCard JSON dictionary and handle backward compatibility."""
23+
_handle_extended_card_compatibility(agent_card_data)
24+
_handle_connection_fields_compatibility(agent_card_data)
25+
_handle_security_compatibility(agent_card_data)
26+
27+
return ParseDict(agent_card_data, AgentCard(), ignore_unknown_fields=True)
28+
29+
30+
def _handle_extended_card_compatibility(
31+
agent_card_data: dict[str, Any],
32+
) -> None:
33+
"""Map legacy supportsAuthenticatedExtendedCard to capabilities."""
34+
if agent_card_data.pop('supportsAuthenticatedExtendedCard', None):
35+
capabilities = agent_card_data.setdefault('capabilities', {})
36+
if 'extendedAgentCard' not in capabilities:
37+
capabilities['extendedAgentCard'] = True
38+
39+
40+
def _handle_connection_fields_compatibility(
41+
agent_card_data: dict[str, Any],
42+
) -> None:
43+
"""Map legacy connection and transport fields to supportedInterfaces."""
44+
main_url = agent_card_data.pop('url', None)
45+
main_transport = agent_card_data.pop('preferredTransport', 'JSONRPC')
46+
version = agent_card_data.pop('protocolVersion', '0.3.0')
47+
additional_interfaces = (
48+
agent_card_data.pop('additionalInterfaces', None) or []
49+
)
50+
51+
if 'supportedInterfaces' not in agent_card_data and main_url:
52+
supported_interfaces = []
53+
supported_interfaces.append(
54+
{
55+
'url': main_url,
56+
'protocolBinding': main_transport,
57+
'protocolVersion': version,
58+
}
59+
)
60+
supported_interfaces.extend(
61+
{
62+
'url': iface.get('url'),
63+
'protocolBinding': iface.get('transport'),
64+
'protocolVersion': version,
65+
}
66+
for iface in additional_interfaces
67+
)
68+
agent_card_data['supportedInterfaces'] = supported_interfaces
69+
70+
71+
def _map_legacy_security(
72+
sec_list: list[dict[str, list[str]]],
73+
) -> list[dict[str, Any]]:
74+
"""Convert a legacy security requirement list into the 1.0.0 Protobuf format."""
75+
return [
76+
{
77+
'schemes': {
78+
scheme_name: {'list': scopes}
79+
for scheme_name, scopes in sec_dict.items()
80+
}
81+
}
82+
for sec_dict in sec_list
83+
]
84+
85+
86+
def _handle_security_compatibility(agent_card_data: dict[str, Any]) -> None:
87+
"""Map legacy security requirements and schemas to their 1.0.0 Protobuf equivalents."""
88+
legacy_security = agent_card_data.pop('security', None)
89+
if (
90+
'securityRequirements' not in agent_card_data
91+
and legacy_security is not None
92+
):
93+
agent_card_data['securityRequirements'] = _map_legacy_security(
94+
legacy_security
95+
)
96+
97+
for skill in agent_card_data.get('skills', []):
98+
legacy_skill_sec = skill.pop('security', None)
99+
if 'securityRequirements' not in skill and legacy_skill_sec is not None:
100+
skill['securityRequirements'] = _map_legacy_security(
101+
legacy_skill_sec
102+
)
103+
104+
security_schemes = agent_card_data.get('securitySchemes', {})
105+
if security_schemes:
106+
type_mapping = {
107+
'apiKey': 'apiKeySecurityScheme',
108+
'http': 'httpAuthSecurityScheme',
109+
'oauth2': 'oauth2SecurityScheme',
110+
'openIdConnect': 'openIdConnectSecurityScheme',
111+
'mutualTLS': 'mtlsSecurityScheme',
112+
}
113+
for scheme in security_schemes.values():
114+
scheme_type = scheme.pop('type', None)
115+
if scheme_type in type_mapping:
116+
# Map legacy 'in' to modern 'location'
117+
if scheme_type == 'apiKey' and 'in' in scheme:
118+
scheme['location'] = scheme.pop('in')
119+
120+
mapped_name = type_mapping[scheme_type]
121+
new_scheme_wrapper = {mapped_name: scheme.copy()}
122+
scheme.clear()
123+
scheme.update(new_scheme_wrapper)
124+
125+
22126
class A2ACardResolver:
23127
"""Agent Card resolver."""
24128

src/a2a/client/helpers.py

Lines changed: 0 additions & 112 deletions
This file was deleted.

0 commit comments

Comments
 (0)