Skip to content

Commit d2f2e18

Browse files
authored
fix: properly convert metadata to dict in RequestContext.metadata (a2aproject#1081)
A leftover from Pydantic -> protobuf migration.
1 parent 501255c commit d2f2e18

2 files changed

Lines changed: 26 additions & 3 deletions

File tree

src/a2a/server/agent_execution/context.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from typing import Any
22

3+
from google.protobuf import json_format
4+
35
from a2a.helpers.proto_helpers import get_message_text
46
from a2a.server.context import ServerCallContext
57
from a2a.server.id_generator import (
@@ -147,8 +149,10 @@ def call_context(self) -> ServerCallContext:
147149
@property
148150
def metadata(self) -> dict[str, Any]:
149151
"""Metadata associated with the request, if available."""
150-
if self._params and self._params.metadata:
151-
return dict(self._params.metadata)
152+
if self._params:
153+
# MessageToDict recurses into nested Struct/ListValue fields;
154+
# dict would leak raw protobuf objects to callers.
155+
return json_format.MessageToDict(self._params.metadata)
152156
return {}
153157

154158
@property

tests/server/agent_execution/test_context.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import json
12
import uuid
23

34
from unittest.mock import Mock, patch
@@ -13,6 +14,7 @@
1314
Task,
1415
)
1516
from a2a.utils.errors import InvalidParamsError
17+
from google.protobuf import struct_pb2
1618

1719

1820
class TestRequestContext:
@@ -270,10 +272,27 @@ def test_metadata_property_without_content(self) -> None:
270272

271273
def test_metadata_property_with_content(self, mock_params: Mock) -> None:
272274
"""Test metadata property returns the metadata from params."""
273-
mock_params.metadata = {'key': 'value'}
275+
struct = struct_pb2.Struct()
276+
struct['key'] = 'value'
277+
mock_params.metadata = struct
274278
context = RequestContext(ServerCallContext(), request=mock_params)
275279
assert context.metadata == {'key': 'value'}
276280

281+
def test_metadata_property_with_real_struct_nested(self) -> None:
282+
"""Regression: `dict(struct)` leaves nested Struct/ListValue as raw
283+
protobuf objects, breaking JSON serialization of the returned dict.
284+
"""
285+
request = SendMessageRequest()
286+
request.metadata['flat_str'] = 'value1'
287+
request.metadata['nested'] = {'a': 1, 'b': [1, 2, 3]}
288+
289+
md = RequestContext(ServerCallContext(), request=request).metadata
290+
291+
assert json.loads(json.dumps(md)) == {
292+
'flat_str': 'value1',
293+
'nested': {'a': 1.0, 'b': [1.0, 2.0, 3.0]},
294+
}
295+
277296
def test_init_with_existing_ids_in_message(
278297
self, mock_message: Mock, mock_params: Mock
279298
) -> None:

0 commit comments

Comments
 (0)