diff --git a/features/a2a_jsonrpc_wire_format.feature b/features/a2a_jsonrpc_wire_format.feature index c1255857b..974bf83fc 100644 --- a/features/a2a_jsonrpc_wire_format.feature +++ b/features/a2a_jsonrpc_wire_format.feature @@ -189,3 +189,24 @@ Feature: A2A JSON-RPC 2.0 wire format compliance Given a wire-format facade with no services When I dispatch wire-format method "_cleveragents/health/check" with id "test-id-123" and params {} Then the wire-format response id should equal "test-id-123" + + # ------------------------------------------------------------------- + # A2aErrorDetail — JSON-RPC 2.0 field name compliance (issue #2745) + # ------------------------------------------------------------------- + + Scenario: A2aErrorDetail serialises with data field not details + Given an A2aErrorDetail with code "NOT_FOUND" and message "gone" and data {"id": "x"} + When I serialise the error detail to a dict + Then the error detail dict should contain key "data" + And the error detail dict should not contain key "details" + + Scenario: A2aErrorDetail serialises with empty data by default + Given an A2aErrorDetail with code "ERR" and message "error" and no data + When I serialise the error detail to a dict + Then the error detail dict should contain key "data" + And the error detail dict should not contain key "details" + + Scenario: A2aErrorDetail data field preserves payload values + Given an A2aErrorDetail with code "NOT_FOUND" and message "gone" and data {"plan_id": "abc123"} + When I serialise the error detail to a dict + Then the error detail data should contain key "plan_id" diff --git a/features/steps/a2a_jsonrpc_wire_format_steps.py b/features/steps/a2a_jsonrpc_wire_format_steps.py index b254d0873..7b3a42b88 100644 --- a/features/steps/a2a_jsonrpc_wire_format_steps.py +++ b/features/steps/a2a_jsonrpc_wire_format_steps.py @@ -350,5 +350,58 @@ def step_wire_response_id(context: Context, value: str) -> None: ) +# --------------------------------------------------------------------------- +# A2aErrorDetail — JSON-RPC 2.0 field name compliance (issue #2745) +# --------------------------------------------------------------------------- + + +@given( + r'an A2aErrorDetail with code "(?P[^"]+)" and message "(?P[^"]+)" and data (?P.+)' +) +def step_create_error_detail_with_data( + context: Context, code: str, msg: str, data_json: str +) -> None: + import json + + data = json.loads(data_json) + context.error_detail = A2aErrorDetail(code=code, message=msg, data=data) + + +@given( + r'an A2aErrorDetail with code "(?P[^"]+)" and message "(?P[^"]+)" and no data' +) +def step_create_error_detail_no_data(context: Context, code: str, msg: str) -> None: + context.error_detail = A2aErrorDetail(code=code, message=msg) + + +@when("I serialise the error detail to a dict") +def step_serialise_error_detail(context: Context) -> None: + context.error_detail_dict = context.error_detail.model_dump() + + +@then(r'the error detail dict should contain key "(?P[^"]+)"') +def step_error_detail_dict_has_key(context: Context, key: str) -> None: + assert key in context.error_detail_dict, ( + f"Expected key '{key}' in error detail dict, got keys: " + f"{list(context.error_detail_dict.keys())}" + ) + + +@then(r'the error detail dict should not contain key "(?P[^"]+)"') +def step_error_detail_dict_no_key(context: Context, key: str) -> None: + assert key not in context.error_detail_dict, ( + f"Expected key '{key}' NOT in error detail dict, but it was present with value: " + f"{context.error_detail_dict.get(key)!r}" + ) + + +@then(r'the error detail data should contain key "(?P[^"]+)"') +def step_error_detail_data_has_key(context: Context, key: str) -> None: + data = context.error_detail_dict.get("data", {}) + assert key in data, ( + f"Expected key '{key}' in error detail data, got keys: {list(data.keys())}" + ) + + # Reset step matcher to parse (default) so subsequent step files are not affected use_step_matcher("parse") diff --git a/src/cleveragents/a2a/models.py b/src/cleveragents/a2a/models.py index 2d9c26fc7..a7f4f308b 100644 --- a/src/cleveragents/a2a/models.py +++ b/src/cleveragents/a2a/models.py @@ -68,7 +68,7 @@ class A2aErrorDetail(BaseModel): code: str message: str - details: dict[str, Any] = {} + data: dict[str, Any] = {} @field_validator("code", "message") @classmethod