Skip to content

Commit 7bbba82

Browse files
committed
Fix: fixing JSONRPC error mapping
Final Error Mapping (JSON-RPC 2.0 Compliant): -32700: Parse error (invalid JSON) -32600: Invalid Request (missing/invalid jsonrpc, invalid id type, oversized payload) -32601: Method not found (unknown method names) -32602: Invalid params (valid method, invalid parameters) -32603: Internal error (unexpected server exceptions) -32000 to -32099: Server-defined errors (A2A-specific, pass through unchanged)
1 parent 72b2ee7 commit 7bbba82

File tree

2 files changed

+96
-15
lines changed

2 files changed

+96
-15
lines changed

src/a2a/server/apps/jsonrpc/jsonrpc_app.py

Lines changed: 94 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,14 @@
2828
GetTaskPushNotificationConfigRequest,
2929
GetTaskRequest,
3030
InternalError,
31+
InvalidParamsError,
3132
InvalidRequestError,
3233
JSONParseError,
3334
JSONRPCError,
3435
JSONRPCErrorResponse,
3536
JSONRPCRequest,
3637
JSONRPCResponse,
38+
MethodNotFoundError,
3739
ListTaskPushNotificationConfigRequest,
3840
SendMessageRequest,
3941
SendStreamingMessageRequest,
@@ -267,17 +269,102 @@ async def _handle_requests(self, request: Request) -> Response: # noqa: PLR0911
267269
body = await request.json()
268270
if isinstance(body, dict):
269271
request_id = body.get('id')
272+
# Ensure request_id is valid for JSON-RPC response (str/int/None only)
273+
if request_id is not None and not isinstance(
274+
request_id, (str, int)
275+
):
276+
request_id = None
277+
# Treat very large payloads as invalid request (-32600) before routing
278+
with contextlib.suppress(Exception):
279+
content_length = int(request.headers.get('content-length', '0'))
280+
if content_length and content_length > 1_000_000:
281+
return self._generate_error_response(
282+
request_id,
283+
A2AError(
284+
root=InvalidRequestError(
285+
message='Payload too large'
286+
)
287+
),
288+
)
289+
logger.info(f'**********Request body: {body}')
290+
# 1) Validate base JSON-RPC structure only (-32600 on failure)
291+
try:
292+
base_request = JSONRPCRequest.model_validate(body)
293+
except ValidationError as e:
294+
traceback.print_exc()
295+
return self._generate_error_response(
296+
request_id,
297+
A2AError(
298+
root=InvalidRequestError(data=json.loads(e.json()))
299+
),
300+
)
270301

271-
# First, validate the basic JSON-RPC structure. This is crucial
272-
# because the A2ARequest model is a discriminated union where some
273-
# request types have default values for the 'method' field
274-
JSONRPCRequest.model_validate(body)
275-
276-
a2a_request = A2ARequest.model_validate(body)
302+
# 2) Route by method name; unknown -> -32601, known -> validate params (-32602 on failure)
303+
method = base_request.method
304+
try:
305+
match method:
306+
case 'message/send':
307+
specific_request = SendMessageRequest.model_validate(
308+
body
309+
)
310+
case 'message/stream':
311+
specific_request = (
312+
SendStreamingMessageRequest.model_validate(body)
313+
)
314+
case 'tasks/get':
315+
specific_request = GetTaskRequest.model_validate(body)
316+
case 'tasks/cancel':
317+
specific_request = CancelTaskRequest.model_validate(
318+
body
319+
)
320+
case 'tasks/pushNotificationConfig/set':
321+
specific_request = (
322+
SetTaskPushNotificationConfigRequest.model_validate(
323+
body
324+
)
325+
)
326+
case 'tasks/pushNotificationConfig/get':
327+
specific_request = (
328+
GetTaskPushNotificationConfigRequest.model_validate(
329+
body
330+
)
331+
)
332+
case 'tasks/pushNotificationConfig/list':
333+
specific_request = ListTaskPushNotificationConfigRequest.model_validate(
334+
body
335+
)
336+
case 'tasks/pushNotificationConfig/delete':
337+
specific_request = DeleteTaskPushNotificationConfigRequest.model_validate(
338+
body
339+
)
340+
case 'tasks/resubscribe':
341+
specific_request = (
342+
TaskResubscriptionRequest.model_validate(body)
343+
)
344+
case 'agent/getAuthenticatedExtendedCard':
345+
specific_request = (
346+
GetAuthenticatedExtendedCardRequest.model_validate(
347+
body
348+
)
349+
)
350+
case _:
351+
return self._generate_error_response(
352+
request_id, A2AError(root=MethodNotFoundError())
353+
)
354+
except ValidationError as e:
355+
traceback.print_exc()
356+
return self._generate_error_response(
357+
request_id,
358+
A2AError(
359+
root=InvalidParamsError(data=json.loads(e.json()))
360+
),
361+
)
277362

363+
# 3) Build call context and wrap the request for downstream handling
278364
call_context = self._context_builder.build(request)
279365

280-
request_id = a2a_request.root.id
366+
request_id = specific_request.id
367+
a2a_request = A2ARequest(root=specific_request)
281368
request_obj = a2a_request.root
282369

283370
if isinstance(
@@ -301,12 +388,6 @@ async def _handle_requests(self, request: Request) -> Response: # noqa: PLR0911
301388
return self._generate_error_response(
302389
None, A2AError(root=JSONParseError(message=str(e)))
303390
)
304-
except ValidationError as e:
305-
traceback.print_exc()
306-
return self._generate_error_response(
307-
request_id,
308-
A2AError(root=InvalidRequestError(data=json.loads(e.json()))),
309-
)
310391
except HTTPException as e:
311392
if e.status_code == HTTP_413_REQUEST_ENTITY_TOO_LARGE:
312393
return self._generate_error_response(

src/a2a/types.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -637,15 +637,15 @@ class JSONRPCRequest(A2ABaseModel):
637637
A unique identifier established by the client. It must be a String, a Number, or null.
638638
The server must reply with the same value in the response. This property is omitted for notifications.
639639
"""
640-
jsonrpc: Literal['2.0'] = '2.0'
640+
jsonrpc: Literal['2.0']
641641
"""
642642
The version of the JSON-RPC protocol. MUST be exactly "2.0".
643643
"""
644644
method: str
645645
"""
646646
A string containing the name of the method to be invoked.
647647
"""
648-
params: dict[str, Any] | None = None
648+
params: Any = None
649649
"""
650650
A structured value holding the parameter values to be used during the method invocation.
651651
"""

0 commit comments

Comments
 (0)