Skip to content

Commit 709b1ff

Browse files
authored
fix: allign error codes with the latest spec (#826)
1. Aligned with https://a2a-protocol.org/latest/specification/#54-error-code-mappings. 2. Added roundtrip tests to `test_client_server_integration.py`. 3. Renamed `AuthenticatedExtendedCardNotConfiguredError` -> `ExtendedAgentCardNotConfiguredError`.
1 parent d889cc9 commit 709b1ff

11 files changed

Lines changed: 101 additions & 43 deletions

File tree

src/a2a/compat/v0_3/jsonrpc_adapter.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
from a2a.server.jsonrpc_models import (
3939
JSONRPCError as CoreJSONRPCError,
4040
)
41-
from a2a.utils.errors import AuthenticatedExtendedCardNotConfiguredError
41+
from a2a.utils.errors import ExtendedAgentCardNotConfiguredError
4242
from a2a.utils.helpers import maybe_await
4343

4444

@@ -248,7 +248,7 @@ async def get_authenticated_extended_card(
248248
) -> types_v03.AgentCard:
249249
"""Handles the 'agent/authenticatedExtendedCard' JSON-RPC method."""
250250
if not self.agent_card.capabilities.extended_agent_card:
251-
raise AuthenticatedExtendedCardNotConfiguredError(
251+
raise ExtendedAgentCardNotConfiguredError(
252252
message='Authenticated card not supported'
253253
)
254254

src/a2a/compat/v0_3/rest_adapter.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
rest_stream_error_handler,
4444
)
4545
from a2a.utils.errors import (
46-
AuthenticatedExtendedCardNotConfiguredError,
46+
ExtendedAgentCardNotConfiguredError,
4747
InvalidRequestError,
4848
)
4949
from a2a.utils.helpers import maybe_await
@@ -126,7 +126,7 @@ async def handle_authenticated_agent_card(
126126
) -> dict[str, Any]:
127127
"""Hook for per credential agent card response."""
128128
if not self.agent_card.capabilities.extended_agent_card:
129-
raise AuthenticatedExtendedCardNotConfiguredError(
129+
raise ExtendedAgentCardNotConfiguredError(
130130
message='Authenticated card not supported'
131131
)
132132
card_to_serve = self.extended_agent_card

src/a2a/server/apps/rest/rest_adapter.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
rest_stream_error_handler,
4646
)
4747
from a2a.utils.errors import (
48-
AuthenticatedExtendedCardNotConfiguredError,
48+
ExtendedAgentCardNotConfiguredError,
4949
InvalidRequestError,
5050
)
5151

@@ -192,7 +192,7 @@ async def _handle_authenticated_agent_card(
192192
A JSONResponse containing the authenticated card.
193193
"""
194194
if not self.agent_card.capabilities.extended_agent_card:
195-
raise AuthenticatedExtendedCardNotConfiguredError(
195+
raise ExtendedAgentCardNotConfiguredError(
196196
message='Authenticated card not supported'
197197
)
198198
card_to_serve = self.extended_agent_card

src/a2a/server/request_handlers/grpc_handler.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,14 @@ def build(self, context: grpc.aio.ServicerContext) -> ServerCallContext:
9090
types.InvalidParamsError: grpc.StatusCode.INVALID_ARGUMENT,
9191
types.InternalError: grpc.StatusCode.INTERNAL,
9292
types.TaskNotFoundError: grpc.StatusCode.NOT_FOUND,
93-
types.TaskNotCancelableError: grpc.StatusCode.UNIMPLEMENTED,
93+
types.TaskNotCancelableError: grpc.StatusCode.FAILED_PRECONDITION,
9494
types.PushNotificationNotSupportedError: grpc.StatusCode.UNIMPLEMENTED,
9595
types.UnsupportedOperationError: grpc.StatusCode.UNIMPLEMENTED,
96-
types.ContentTypeNotSupportedError: grpc.StatusCode.UNIMPLEMENTED,
96+
types.ContentTypeNotSupportedError: grpc.StatusCode.INVALID_ARGUMENT,
9797
types.InvalidAgentResponseError: grpc.StatusCode.INTERNAL,
98+
types.ExtendedAgentCardNotConfiguredError: grpc.StatusCode.FAILED_PRECONDITION,
99+
types.ExtensionSupportRequiredError: grpc.StatusCode.FAILED_PRECONDITION,
100+
types.VersionNotSupportedError: grpc.StatusCode.UNIMPLEMENTED,
98101
}
99102

100103

src/a2a/server/request_handlers/jsonrpc_handler.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@
3535
from a2a.utils.errors import (
3636
JSON_RPC_ERROR_CODE_MAP,
3737
A2AError,
38-
AuthenticatedExtendedCardNotConfiguredError,
3938
ContentTypeNotSupportedError,
39+
ExtendedAgentCardNotConfiguredError,
40+
ExtensionSupportRequiredError,
4041
InternalError,
4142
InvalidAgentResponseError,
4243
InvalidParamsError,
@@ -46,6 +47,7 @@
4647
TaskNotCancelableError,
4748
TaskNotFoundError,
4849
UnsupportedOperationError,
50+
VersionNotSupportedError,
4951
)
5052
from a2a.utils.helpers import maybe_await, validate
5153
from a2a.utils.telemetry import SpanKind, trace_class
@@ -61,11 +63,13 @@
6163
UnsupportedOperationError: JSONRPCError,
6264
ContentTypeNotSupportedError: JSONRPCError,
6365
InvalidAgentResponseError: JSONRPCError,
64-
AuthenticatedExtendedCardNotConfiguredError: JSONRPCError,
66+
ExtendedAgentCardNotConfiguredError: JSONRPCError,
6567
InternalError: JSONRPCInternalError,
6668
InvalidParamsError: JSONRPCError,
6769
InvalidRequestError: JSONRPCError,
6870
MethodNotFoundError: JSONRPCError,
71+
ExtensionSupportRequiredError: JSONRPCError,
72+
VersionNotSupportedError: JSONRPCError,
6973
}
7074

7175

@@ -446,8 +450,8 @@ async def get_authenticated_extended_card(
446450
"""
447451
request_id = self._get_request_id(context)
448452
if not self.agent_card.capabilities.extended_agent_card:
449-
raise AuthenticatedExtendedCardNotConfiguredError(
450-
message='Authenticated card not supported'
453+
raise ExtendedAgentCardNotConfiguredError(
454+
message='The agent does not have an extended agent card configured'
451455
)
452456

453457
base_card = self.extended_agent_card

src/a2a/server/request_handlers/response_helpers.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@
2929
from a2a.utils.errors import (
3030
JSON_RPC_ERROR_CODE_MAP,
3131
A2AError,
32-
AuthenticatedExtendedCardNotConfiguredError,
3332
ContentTypeNotSupportedError,
33+
ExtendedAgentCardNotConfiguredError,
34+
ExtensionSupportRequiredError,
3435
InternalError,
3536
InvalidAgentResponseError,
3637
InvalidParamsError,
@@ -40,6 +41,7 @@
4041
TaskNotCancelableError,
4142
TaskNotFoundError,
4243
UnsupportedOperationError,
44+
VersionNotSupportedError,
4345
)
4446

4547

@@ -50,11 +52,13 @@
5052
UnsupportedOperationError: JSONRPCError,
5153
ContentTypeNotSupportedError: JSONRPCError,
5254
InvalidAgentResponseError: JSONRPCError,
53-
AuthenticatedExtendedCardNotConfiguredError: JSONRPCError,
55+
ExtendedAgentCardNotConfiguredError: JSONRPCError,
5456
InvalidParamsError: JSONRPCError,
5557
InvalidRequestError: JSONRPCError,
5658
MethodNotFoundError: JSONRPCError,
5759
InternalError: JSONRPCInternalError,
60+
ExtensionSupportRequiredError: JSONRPCError,
61+
VersionNotSupportedError: JSONRPCError,
5862
}
5963

6064

src/a2a/types/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,9 @@
5252

5353
# Import SDK-specific error types from utils.errors
5454
from a2a.utils.errors import (
55-
AuthenticatedExtendedCardNotConfiguredError,
5655
ContentTypeNotSupportedError,
56+
ExtendedAgentCardNotConfiguredError,
57+
ExtensionSupportRequiredError,
5758
InternalError,
5859
InvalidAgentResponseError,
5960
InvalidParamsError,
@@ -63,6 +64,7 @@
6364
TaskNotCancelableError,
6465
TaskNotFoundError,
6566
UnsupportedOperationError,
67+
VersionNotSupportedError,
6668
)
6769

6870

@@ -99,6 +101,7 @@
99101
'ContentTypeNotSupportedError',
100102
'DeleteTaskPushNotificationConfigRequest',
101103
'DeviceCodeOAuthFlow',
104+
'ExtensionSupportRequiredError',
102105
'GetExtendedAgentCardRequest',
103106
'GetTaskPushNotificationConfigRequest',
104107
'GetTaskRequest',
@@ -139,4 +142,5 @@
139142
'TaskStatus',
140143
'TaskStatusUpdateEvent',
141144
'UnsupportedOperationError',
145+
'VersionNotSupportedError',
142146
]

src/a2a/utils/error_handlers.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@
2626
)
2727
from a2a.utils.errors import (
2828
A2AError,
29-
AuthenticatedExtendedCardNotConfiguredError,
3029
ContentTypeNotSupportedError,
30+
ExtendedAgentCardNotConfiguredError,
31+
ExtensionSupportRequiredError,
3132
InternalError,
3233
InvalidAgentResponseError,
3334
InvalidParamsError,
@@ -37,6 +38,7 @@
3738
TaskNotCancelableError,
3839
TaskNotFoundError,
3940
UnsupportedOperationError,
41+
VersionNotSupportedError,
4042
)
4143

4244

@@ -56,7 +58,9 @@
5658
| type[UnsupportedOperationError]
5759
| type[ContentTypeNotSupportedError]
5860
| type[InvalidAgentResponseError]
59-
| type[AuthenticatedExtendedCardNotConfiguredError]
61+
| type[ExtendedAgentCardNotConfiguredError]
62+
| type[ExtensionSupportRequiredError]
63+
| type[VersionNotSupportedError]
6064
)
6165

6266
A2AErrorToHttpStatus: dict[_A2AErrorType, int] = {
@@ -73,7 +77,9 @@
7377
UnsupportedOperationError: 501,
7478
ContentTypeNotSupportedError: 415,
7579
InvalidAgentResponseError: 502,
76-
AuthenticatedExtendedCardNotConfiguredError: 404,
80+
ExtendedAgentCardNotConfiguredError: 400,
81+
ExtensionSupportRequiredError: 400,
82+
VersionNotSupportedError: 400,
7783
}
7884

7985

src/a2a/utils/errors.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ class InvalidAgentResponseError(A2AError):
5858
message = 'Invalid agent response'
5959

6060

61-
class AuthenticatedExtendedCardNotConfiguredError(A2AError):
61+
class ExtendedAgentCardNotConfiguredError(A2AError):
6262
"""Exception raised when the authenticated extended card is not configured."""
6363

6464
message = 'Authenticated Extended Card is not configured'
@@ -122,7 +122,9 @@ class VersionNotSupportedError(A2AError):
122122
UnsupportedOperationError: -32004,
123123
ContentTypeNotSupportedError: -32005,
124124
InvalidAgentResponseError: -32006,
125-
AuthenticatedExtendedCardNotConfiguredError: -32007,
125+
ExtendedAgentCardNotConfiguredError: -32007,
126+
ExtensionSupportRequiredError: -32008,
127+
VersionNotSupportedError: -32009,
126128
InvalidParamsError: -32602,
127129
InvalidRequestError: -32600,
128130
MethodNotFoundError: -32601,
@@ -137,7 +139,7 @@ class VersionNotSupportedError(A2AError):
137139
UnsupportedOperationError: 'UNSUPPORTED_OPERATION',
138140
ContentTypeNotSupportedError: 'CONTENT_TYPE_NOT_SUPPORTED',
139141
InvalidAgentResponseError: 'INVALID_AGENT_RESPONSE',
140-
AuthenticatedExtendedCardNotConfiguredError: 'EXTENDED_AGENT_CARD_NOT_CONFIGURED',
142+
ExtendedAgentCardNotConfiguredError: 'EXTENDED_AGENT_CARD_NOT_CONFIGURED',
141143
ExtensionSupportRequiredError: 'EXTENSION_SUPPORT_REQUIRED',
142144
VersionNotSupportedError: 'VERSION_NOT_SUPPORTED',
143145
}

tests/integration/test_client_server_integration.py

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,24 @@
5050
TaskStatus,
5151
TaskStatusUpdateEvent,
5252
)
53-
from a2a.utils.constants import TransportProtocol
53+
from a2a.utils.constants import (
54+
TransportProtocol,
55+
)
56+
from a2a.utils.errors import (
57+
ExtendedAgentCardNotConfiguredError,
58+
ContentTypeNotSupportedError,
59+
ExtensionSupportRequiredError,
60+
InternalError,
61+
InvalidAgentResponseError,
62+
InvalidParamsError,
63+
InvalidRequestError,
64+
MethodNotFoundError,
65+
PushNotificationNotSupportedError,
66+
TaskNotCancelableError,
67+
TaskNotFoundError,
68+
UnsupportedOperationError,
69+
VersionNotSupportedError,
70+
)
5471
from a2a.utils.signing import (
5572
create_agent_card_signer,
5673
create_signature_verifier,
@@ -788,6 +805,43 @@ async def test_client_get_signed_base_and_extended_cards(
788805
await client.close()
789806

790807

808+
@pytest.mark.asyncio
809+
@pytest.mark.parametrize(
810+
'error_cls',
811+
[
812+
TaskNotFoundError,
813+
TaskNotCancelableError,
814+
PushNotificationNotSupportedError,
815+
UnsupportedOperationError,
816+
ContentTypeNotSupportedError,
817+
InvalidAgentResponseError,
818+
ExtendedAgentCardNotConfiguredError,
819+
ExtensionSupportRequiredError,
820+
VersionNotSupportedError,
821+
],
822+
)
823+
async def test_client_handles_a2a_errors(transport_setups, error_cls) -> None:
824+
"""Integration test to verify error propagation from handler to client."""
825+
client = transport_setups.client
826+
handler = transport_setups.handler
827+
828+
# Mock the handler to raise the error
829+
handler.on_get_task.side_effect = error_cls('Test error message')
830+
831+
params = GetTaskRequest(id='some-id')
832+
833+
# We expect the client to raise the same error_cls.
834+
with pytest.raises(error_cls) as exc_info:
835+
await client.get_task(request=params)
836+
837+
assert 'Test error message' in str(exc_info.value)
838+
839+
# Reset side_effect for other tests
840+
handler.on_get_task.side_effect = None
841+
842+
await client.close()
843+
844+
791845
@pytest.mark.asyncio
792846
@pytest.mark.parametrize(
793847
'request_kwargs, expected_error_code',

0 commit comments

Comments
 (0)