Skip to content

Commit 639a663

Browse files
authored
refactor: unify transport name constants usage (#728)
Use enum everywhere, data model uses `str` for protocol names, so custom protocols won't be constrained with the enum. Fixes #705
1 parent a149a09 commit 639a663

10 files changed

Lines changed: 53 additions & 69 deletions

File tree

src/a2a/client/client_factory.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@
2020
AgentInterface,
2121
)
2222
from a2a.utils.constants import (
23-
TRANSPORT_GRPC,
24-
TRANSPORT_HTTP_JSON,
25-
TRANSPORT_JSONRPC,
23+
TransportProtocol,
2624
)
2725

2826

@@ -74,9 +72,9 @@ def __init__(
7472

7573
def _register_defaults(self, supported: list[str]) -> None:
7674
# Empty support list implies JSON-RPC only.
77-
if TRANSPORT_JSONRPC in supported or not supported:
75+
if TransportProtocol.JSONRPC in supported or not supported:
7876
self.register(
79-
TRANSPORT_JSONRPC,
77+
TransportProtocol.JSONRPC,
8078
lambda card, url, config, interceptors: JsonRpcTransport(
8179
config.httpx_client or httpx.AsyncClient(),
8280
card,
@@ -85,9 +83,9 @@ def _register_defaults(self, supported: list[str]) -> None:
8583
config.extensions or None,
8684
),
8785
)
88-
if TRANSPORT_HTTP_JSON in supported:
86+
if TransportProtocol.HTTP_JSON in supported:
8987
self.register(
90-
TRANSPORT_HTTP_JSON,
88+
TransportProtocol.HTTP_JSON,
9189
lambda card, url, config, interceptors: RestTransport(
9290
config.httpx_client or httpx.AsyncClient(),
9391
card,
@@ -96,14 +94,14 @@ def _register_defaults(self, supported: list[str]) -> None:
9694
config.extensions or None,
9795
),
9896
)
99-
if TRANSPORT_GRPC in supported:
97+
if TransportProtocol.GRPC in supported:
10098
if GrpcTransport is None:
10199
raise ImportError(
102100
'To use GrpcClient, its dependencies must be installed. '
103101
'You can install them with \'pip install "a2a-sdk[grpc]"\''
104102
)
105103
self.register(
106-
TRANSPORT_GRPC,
104+
TransportProtocol.GRPC,
107105
GrpcTransport.create,
108106
)
109107

@@ -207,7 +205,7 @@ def create(
207205
server configuration, a `ValueError` is raised.
208206
"""
209207
client_set = self._config.supported_protocol_bindings or [
210-
TRANSPORT_JSONRPC
208+
TransportProtocol.JSONRPC
211209
]
212210
transport_protocol = None
213211
transport_url = None

src/a2a/client/transports/rest.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,7 @@
3535
TaskPushNotificationConfig,
3636
)
3737
from a2a.utils.constants import (
38-
TRANSPORT_HTTP_JSON,
39-
TRANSPORT_JSONRPC,
38+
TransportProtocol,
4039
)
4140
from a2a.utils.telemetry import SpanKind, trace_class
4241

@@ -62,15 +61,15 @@ def __init__(
6261
elif agent_card:
6362
for interface in agent_card.supported_interfaces:
6463
if interface.protocol_binding in (
65-
TRANSPORT_HTTP_JSON,
66-
TRANSPORT_JSONRPC,
64+
TransportProtocol.HTTP_JSON,
65+
TransportProtocol.JSONRPC,
6766
):
6867
self.url = interface.url
6968
break
7069
else:
7170
raise ValueError(
72-
f'AgentCard does not support {TRANSPORT_HTTP_JSON} '
73-
f'or {TRANSPORT_JSONRPC}'
71+
f'AgentCard does not support {TransportProtocol.HTTP_JSON} '
72+
f'or {TransportProtocol.JSONRPC}'
7473
)
7574
else:
7675
raise ValueError('Must provide either agent_card or url')

src/a2a/utils/__init__.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@
1212
DEFAULT_RPC_URL,
1313
EXTENDED_AGENT_CARD_PATH,
1414
PREV_AGENT_CARD_WELL_KNOWN_PATH,
15-
TRANSPORT_GRPC,
16-
TRANSPORT_HTTP_JSON,
17-
TRANSPORT_JSONRPC,
1815
TransportProtocol,
1916
)
2017
from a2a.utils.helpers import (
@@ -45,9 +42,6 @@
4542
'DEFAULT_RPC_URL',
4643
'EXTENDED_AGENT_CARD_PATH',
4744
'PREV_AGENT_CARD_WELL_KNOWN_PATH',
48-
'TRANSPORT_GRPC',
49-
'TRANSPORT_HTTP_JSON',
50-
'TRANSPORT_JSONRPC',
5145
'TransportProtocol',
5246
'append_artifact_to_task',
5347
'are_modalities_compatible',

src/a2a/utils/constants.py

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
"""Constants for well-known URIs used throughout the A2A Python SDK."""
22

3+
from enum import Enum
4+
5+
36
AGENT_CARD_WELL_KNOWN_PATH = '/.well-known/agent-card.json'
47
PREV_AGENT_CARD_WELL_KNOWN_PATH = '/.well-known/agent.json'
58
EXTENDED_AGENT_CARD_PATH = '/agent/authenticatedExtendedCard'
@@ -11,19 +14,12 @@
1114
"""Maximum page size for the `tasks/list` method."""
1215

1316

14-
# Transport protocol constants
15-
# These match the protocol binding values used in AgentCard
16-
TRANSPORT_JSONRPC = 'JSONRPC'
17-
TRANSPORT_HTTP_JSON = 'HTTP+JSON'
18-
TRANSPORT_GRPC = 'GRPC'
19-
20-
21-
class TransportProtocol:
17+
class TransportProtocol(str, Enum):
2218
"""Transport protocol string constants."""
2319

24-
jsonrpc = TRANSPORT_JSONRPC
25-
http_json = TRANSPORT_HTTP_JSON
26-
grpc = TRANSPORT_GRPC
20+
JSONRPC = 'JSONRPC'
21+
HTTP_JSON = 'HTTP+JSON'
22+
GRPC = 'GRPC'
2723

2824

2925
DEFAULT_MAX_CONTENT_LENGTH = 10 * 1024 * 1024 # 10MB

tests/client/test_auth_middleware.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ async def test_client_with_simple_interceptor() -> None:
178178
interceptor = HeaderInterceptor('X-Test-Header', 'Test-Value-123')
179179
card = AgentCard(
180180
supported_interfaces=[
181-
AgentInterface(url=url, protocol_binding=TransportProtocol.jsonrpc)
181+
AgentInterface(url=url, protocol_binding=TransportProtocol.JSONRPC)
182182
],
183183
name='testbot',
184184
description='test bot',
@@ -192,7 +192,7 @@ async def test_client_with_simple_interceptor() -> None:
192192
async with httpx.AsyncClient() as http_client:
193193
config = ClientConfig(
194194
httpx_client=http_client,
195-
supported_protocol_bindings=[TransportProtocol.jsonrpc],
195+
supported_protocol_bindings=[TransportProtocol.JSONRPC],
196196
)
197197
factory = ClientFactory(config)
198198
client = factory.create(card, interceptors=[interceptor])
@@ -310,7 +310,7 @@ async def test_auth_interceptor_variants(
310310
agent_card = AgentCard(
311311
supported_interfaces=[
312312
AgentInterface(
313-
url=test_case.url, protocol_binding=TransportProtocol.jsonrpc
313+
url=test_case.url, protocol_binding=TransportProtocol.JSONRPC
314314
)
315315
],
316316
name=f'{test_case.scheme_name}bot',
@@ -333,7 +333,7 @@ async def test_auth_interceptor_variants(
333333
async with httpx.AsyncClient() as http_client:
334334
config = ClientConfig(
335335
httpx_client=http_client,
336-
supported_protocol_bindings=[TransportProtocol.jsonrpc],
336+
supported_protocol_bindings=[TransportProtocol.JSONRPC],
337337
)
338338
factory = ClientFactory(config)
339339
client = factory.create(agent_card, interceptors=[auth_interceptor])
@@ -362,7 +362,7 @@ async def test_auth_interceptor_skips_when_scheme_not_in_security_schemes(
362362
supported_interfaces=[
363363
AgentInterface(
364364
url='http://agent.com/rpc',
365-
protocol_binding=TransportProtocol.jsonrpc,
365+
protocol_binding=TransportProtocol.JSONRPC,
366366
)
367367
],
368368
name='missingbot',

tests/client/test_client_factory.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def base_agent_card() -> AgentCard:
2525
description='An agent for testing.',
2626
supported_interfaces=[
2727
AgentInterface(
28-
protocol_binding=TransportProtocol.jsonrpc,
28+
protocol_binding=TransportProtocol.JSONRPC,
2929
url='http://primary-url.com',
3030
)
3131
],
@@ -42,8 +42,8 @@ def test_client_factory_selects_preferred_transport(base_agent_card: AgentCard):
4242
config = ClientConfig(
4343
httpx_client=httpx.AsyncClient(),
4444
supported_protocol_bindings=[
45-
TransportProtocol.jsonrpc,
46-
TransportProtocol.http_json,
45+
TransportProtocol.JSONRPC,
46+
TransportProtocol.HTTP_JSON,
4747
],
4848
extensions=['https://example.com/test-ext/v0'],
4949
)
@@ -61,16 +61,16 @@ def test_client_factory_selects_secondary_transport_url(
6161
"""Verify that the factory selects the correct URL for a secondary transport."""
6262
base_agent_card.supported_interfaces.append(
6363
AgentInterface(
64-
protocol_binding=TransportProtocol.http_json,
64+
protocol_binding=TransportProtocol.HTTP_JSON,
6565
url='http://secondary-url.com',
6666
)
6767
)
6868
# Client prefers REST, which is available as a secondary transport
6969
config = ClientConfig(
7070
httpx_client=httpx.AsyncClient(),
7171
supported_protocol_bindings=[
72-
TransportProtocol.http_json,
73-
TransportProtocol.jsonrpc,
72+
TransportProtocol.HTTP_JSON,
73+
TransportProtocol.JSONRPC,
7474
],
7575
use_client_preference=True,
7676
extensions=['https://example.com/test-ext/v0'],
@@ -89,22 +89,22 @@ def test_client_factory_server_preference(base_agent_card: AgentCard):
8989
base_agent_card.supported_interfaces.insert(
9090
0,
9191
AgentInterface(
92-
protocol_binding=TransportProtocol.http_json,
92+
protocol_binding=TransportProtocol.HTTP_JSON,
9393
url='http://primary-url.com',
9494
),
9595
)
9696
base_agent_card.supported_interfaces.append(
9797
AgentInterface(
98-
protocol_binding=TransportProtocol.jsonrpc,
98+
protocol_binding=TransportProtocol.JSONRPC,
9999
url='http://secondary-url.com',
100100
)
101101
)
102102
# Client supports both, but server prefers REST
103103
config = ClientConfig(
104104
httpx_client=httpx.AsyncClient(),
105105
supported_protocol_bindings=[
106-
TransportProtocol.jsonrpc,
107-
TransportProtocol.http_json,
106+
TransportProtocol.JSONRPC,
107+
TransportProtocol.HTTP_JSON,
108108
],
109109
)
110110
factory = ClientFactory(config)

tests/client/transports/test_rest_client.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33

44
import httpx
55
import pytest
6-
from google.protobuf import json_format
76

7+
from google.protobuf import json_format
88
from httpx_sse import EventSource, ServerSentEvent
99

1010
from a2a.client import create_text_message_object
@@ -15,10 +15,9 @@
1515
AgentCapabilities,
1616
AgentCard,
1717
AgentInterface,
18-
Role,
1918
SendMessageRequest,
2019
)
21-
from a2a.utils.constants import TRANSPORT_HTTP_JSON
20+
from a2a.utils.constants import TransportProtocol
2221

2322

2423
@pytest.fixture
@@ -31,7 +30,7 @@ def mock_agent_card() -> MagicMock:
3130
mock = MagicMock(spec=AgentCard, url='http://agent.example.com/api')
3231
mock.supported_interfaces = [
3332
AgentInterface(
34-
protocol_binding=TRANSPORT_HTTP_JSON,
33+
protocol_binding=TransportProtocol.HTTP_JSON,
3534
url='http://agent.example.com/api',
3635
)
3736
]
@@ -276,7 +275,7 @@ async def test_get_card_with_extended_card_support_with_extensions(
276275
capabilities=AgentCapabilities(extended_agent_card=True),
277276
)
278277
interface = agent_card.supported_interfaces.add()
279-
interface.protocol_binding = TRANSPORT_HTTP_JSON
278+
interface.protocol_binding = TransportProtocol.HTTP_JSON
280279
interface.url = 'http://agent.example.com/api'
281280

282281
client = RestTransport(

tests/e2e/push_notifications/test_default_push_notification_support.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ async def test_notification_triggering_with_in_message_config_e2e(
105105
token = uuid.uuid4().hex
106106
a2a_client = ClientFactory(
107107
ClientConfig(
108-
supported_protocol_bindings=[TransportProtocol.http_json],
108+
supported_protocol_bindings=[TransportProtocol.HTTP_JSON],
109109
push_notification_configs=[
110110
PushNotificationConfig(
111111
id='in-message-config',
@@ -114,7 +114,7 @@ async def test_notification_triggering_with_in_message_config_e2e(
114114
)
115115
],
116116
)
117-
).create(minimal_agent_card(agent_server, [TransportProtocol.http_json]))
117+
).create(minimal_agent_card(agent_server, [TransportProtocol.HTTP_JSON]))
118118

119119
# Send a message and extract the returned task.
120120
responses = [
@@ -167,9 +167,9 @@ async def test_notification_triggering_after_config_change_e2e(
167167
# Configure an A2A client without a push notification config.
168168
a2a_client = ClientFactory(
169169
ClientConfig(
170-
supported_protocol_bindings=[TransportProtocol.http_json],
170+
supported_protocol_bindings=[TransportProtocol.HTTP_JSON],
171171
)
172-
).create(minimal_agent_card(agent_server, [TransportProtocol.http_json]))
172+
).create(minimal_agent_card(agent_server, [TransportProtocol.HTTP_JSON]))
173173

174174
# Send a message and extract the returned task.
175175
responses = [

tests/integration/test_client_server_integration.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,7 @@
1919
from a2a.types import a2a_pb2_grpc
2020
from a2a.server.apps import A2AFastAPIApplication, A2ARESTFastAPIApplication
2121
from a2a.server.request_handlers import GrpcHandler, RequestHandler
22-
from a2a.utils.constants import (
23-
TRANSPORT_HTTP_JSON,
24-
TRANSPORT_GRPC,
25-
TRANSPORT_JSONRPC,
26-
)
22+
from a2a.utils.constants import TransportProtocol
2723
from a2a.utils.signing import (
2824
create_agent_card_signer,
2925
create_signature_verifier,
@@ -156,10 +152,12 @@ def agent_card() -> AgentCard:
156152
default_output_modes=['text/plain'],
157153
supported_interfaces=[
158154
AgentInterface(
159-
protocol_binding=TRANSPORT_HTTP_JSON,
155+
protocol_binding=TransportProtocol.HTTP_JSON,
160156
url='http://testserver',
161157
),
162-
AgentInterface(protocol_binding='grpc', url='localhost:50051'),
158+
AgentInterface(
159+
protocol_binding=TransportProtocol.GRPC, url='localhost:50051'
160+
),
163161
],
164162
)
165163

tests/integration/test_end_to_end.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
TaskState,
3434
a2a_pb2_grpc,
3535
)
36-
from a2a.utils import TRANSPORT_GRPC, TRANSPORT_HTTP_JSON, TRANSPORT_JSONRPC
36+
from a2a.utils import TransportProtocol
3737

3838

3939
class MockAgentExecutor(AgentExecutor):
@@ -68,15 +68,15 @@ def agent_card() -> AgentCard:
6868
default_output_modes=['text/plain'],
6969
supported_interfaces=[
7070
AgentInterface(
71-
protocol_binding=TRANSPORT_HTTP_JSON,
71+
protocol_binding=TransportProtocol.HTTP_JSON,
7272
url='http://testserver',
7373
),
7474
AgentInterface(
75-
protocol_binding=TRANSPORT_JSONRPC,
75+
protocol_binding=TransportProtocol.JSONRPC,
7676
url='http://testserver',
7777
),
7878
AgentInterface(
79-
protocol_binding=TRANSPORT_GRPC,
79+
protocol_binding=TransportProtocol.GRPC,
8080
url='localhost:50051',
8181
),
8282
],
@@ -149,7 +149,7 @@ async def grpc_setup(
149149

150150
# Update the gRPC interface dynamically based on the assigned port
151151
for interface in grpc_agent_card.supported_interfaces:
152-
if interface.protocol_binding == TRANSPORT_GRPC:
152+
if interface.protocol_binding == TransportProtocol.GRPC:
153153
interface.url = server_address
154154
break
155155
else:

0 commit comments

Comments
 (0)