From f7de2ae0ebba31b6de6af2df2ff816a314691edd Mon Sep 17 00:00:00 2001 From: Phil Stephens Date: Fri, 22 Aug 2025 04:50:38 +0000 Subject: [PATCH 1/9] Update proto conversion utilities --- src/a2a/utils/proto_utils.py | 89 ++++++++++++++++++++++++++++++------ 1 file changed, 75 insertions(+), 14 deletions(-) diff --git a/src/a2a/utils/proto_utils.py b/src/a2a/utils/proto_utils.py index e8f9e7181..8278aaf75 100644 --- a/src/a2a/utils/proto_utils.py +++ b/src/a2a/utils/proto_utils.py @@ -46,14 +46,7 @@ def metadata( ) -> struct_pb2.Struct | None: if metadata is None: return None - return struct_pb2.Struct( - # TODO: Add support for other types. - fields={ - key: struct_pb2.Value(string_value=value) - for key, value in metadata.items() - if isinstance(value, str) - } - ) + return dict_to_struct(metadata) @classmethod def part(cls, part: types.Part) -> a2a_pb2.Part: @@ -81,7 +74,9 @@ def file( ) -> a2a_pb2.FilePart: if isinstance(file, types.FileWithUri): return a2a_pb2.FilePart( - file_with_uri=file.uri, mime_type=file.mime_type, name=file.name + file_with_uri=file.uri, + mime_type=file.mime_type, + name=file.name ) return a2a_pb2.FilePart( file_with_bytes=file.bytes.encode('utf-8'), @@ -324,6 +319,23 @@ def capabilities( return a2a_pb2.AgentCapabilities( streaming=bool(capabilities.streaming), push_notifications=bool(capabilities.push_notifications), + extensions=[ + cls.extension(x) for x in capabilities.extensions + ] + if capabilities.extensions + else None, + ) + + @classmethod + def extension( + cls, + extension: types.AgentExtension, + ) -> a2a_pb2.AgentExtension: + return a2a_pb2.AgentExtension( + uri=extension.uri, + description=extension.description, + params=dict_to_struct(extension.params), + required=extension.required, ) @classmethod @@ -477,11 +489,9 @@ def message(cls, message: a2a_pb2.Message) -> types.Message: @classmethod def metadata(cls, metadata: struct_pb2.Struct) -> dict[str, Any]: - return { - key: value.string_value - for key, value in metadata.fields.items() - if value.string_value - } + if not metadata: + return {} + return struct_to_dict(metadata) @classmethod def part(cls, part: a2a_pb2.Part) -> types.Part: @@ -777,6 +787,23 @@ def capabilities( return types.AgentCapabilities( streaming=capabilities.streaming, push_notifications=capabilities.push_notifications, + extensions=[ + cls.agent_extension(x) for x in capabilities.extensions + ] + if capabilities.extensions + else None, + ) + + @classmethod + def agent_extension( + cls, + extension: a2a_pb2.AgentExtension, + ) -> types.AgentExtension: + return types.AgentExtension( + uri=extension.uri, + description=extension.description, + params=struct_to_dict(extension.params), + required=extension.required, ) @classmethod @@ -916,3 +943,37 @@ def role(cls, role: a2a_pb2.Role) -> types.Role: return types.Role.agent case _: return types.Role.agent + + +def struct_to_dict(struct: struct_pb2.Struct) -> dict[str, Any]: + """Converts a Struct proto to a Python dict.""" + + def convert(value: struct_pb2.Value) -> Any: + if value.HasField('list_value'): + return [convert(v) for v in value.list_value.values] + elif value.HasField('struct_value'): + return {k: convert(v) for k, v in value.struct_value.fields.items()} + elif value.HasField('number_value'): + return value.number_value + elif value.HasField('string_value'): + return value.string_value + elif value.HasField('bool_value'): + return value.bool_value + elif value.HasField('null_value'): + return None + else: + raise ValueError(f'Unsupported type: {value}') + + return {k: convert(v) for k, v in struct.fields.items()} + + +def dict_to_struct(dictionary: dict[str, Any]) -> struct_pb2.Struct: + """Converts a Python dict to a Struct proto.""" + struct = struct_pb2.Struct() + for key, val in dictionary.items(): + if isinstance(val, dict): + struct[key] = dict_to_struct(val) + else: + struct[key] = val + return struct + From 50b61cb94bdbf83ed7aaa9a3a8fb302c0bec7fa2 Mon Sep 17 00:00:00 2001 From: Phil Stephens Date: Fri, 22 Aug 2025 04:59:14 +0000 Subject: [PATCH 2/9] Update proto conversion utilities --- src/a2a/utils/proto_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/a2a/utils/proto_utils.py b/src/a2a/utils/proto_utils.py index 8278aaf75..4b53bbd59 100644 --- a/src/a2a/utils/proto_utils.py +++ b/src/a2a/utils/proto_utils.py @@ -334,7 +334,7 @@ def extension( return a2a_pb2.AgentExtension( uri=extension.uri, description=extension.description, - params=dict_to_struct(extension.params), + params=dict_to_struct(extension.params) if extension.params else None, required=extension.required, ) From 5c99db36a906841c9b16c55b6af0fb1b18eacb63 Mon Sep 17 00:00:00 2001 From: Phil Stephens Date: Fri, 22 Aug 2025 05:05:35 +0000 Subject: [PATCH 3/9] Update proto conversion utilities --- src/a2a/utils/proto_utils.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/a2a/utils/proto_utils.py b/src/a2a/utils/proto_utils.py index 4b53bbd59..03e187d3f 100644 --- a/src/a2a/utils/proto_utils.py +++ b/src/a2a/utils/proto_utils.py @@ -320,10 +320,8 @@ def capabilities( streaming=bool(capabilities.streaming), push_notifications=bool(capabilities.push_notifications), extensions=[ - cls.extension(x) for x in capabilities.extensions - ] - if capabilities.extensions - else None, + cls.extension(x) for x in capabilities.extensions or [] + ], ) @classmethod @@ -489,7 +487,7 @@ def message(cls, message: a2a_pb2.Message) -> types.Message: @classmethod def metadata(cls, metadata: struct_pb2.Struct) -> dict[str, Any]: - if not metadata: + if not metadata.fields: return {} return struct_to_dict(metadata) @@ -789,9 +787,7 @@ def capabilities( push_notifications=capabilities.push_notifications, extensions=[ cls.agent_extension(x) for x in capabilities.extensions - ] - if capabilities.extensions - else None, + ], ) @classmethod From f1115a0d57a265848964c18014e3f3de2672c611 Mon Sep 17 00:00:00 2001 From: Phil Stephens Date: Fri, 22 Aug 2025 05:07:47 +0000 Subject: [PATCH 4/9] Update proto conversion utilities --- src/a2a/utils/proto_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/a2a/utils/proto_utils.py b/src/a2a/utils/proto_utils.py index 03e187d3f..f64cee559 100644 --- a/src/a2a/utils/proto_utils.py +++ b/src/a2a/utils/proto_utils.py @@ -489,7 +489,7 @@ def message(cls, message: a2a_pb2.Message) -> types.Message: def metadata(cls, metadata: struct_pb2.Struct) -> dict[str, Any]: if not metadata.fields: return {} - return struct_to_dict(metadata) + return struct_to_dict(metadata) @classmethod def part(cls, part: a2a_pb2.Part) -> types.Part: From 8bcc14160407dd9d02638f09fa1623aff821ec20 Mon Sep 17 00:00:00 2001 From: Phil Stephens Date: Fri, 22 Aug 2025 05:10:56 +0000 Subject: [PATCH 5/9] Update proto conversion utilities --- src/a2a/utils/proto_utils.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/a2a/utils/proto_utils.py b/src/a2a/utils/proto_utils.py index f64cee559..df4221e8f 100644 --- a/src/a2a/utils/proto_utils.py +++ b/src/a2a/utils/proto_utils.py @@ -947,15 +947,15 @@ def struct_to_dict(struct: struct_pb2.Struct) -> dict[str, Any]: def convert(value: struct_pb2.Value) -> Any: if value.HasField('list_value'): return [convert(v) for v in value.list_value.values] - elif value.HasField('struct_value'): + if value.HasField('struct_value'): return {k: convert(v) for k, v in value.struct_value.fields.items()} - elif value.HasField('number_value'): + if value.HasField('number_value'): return value.number_value - elif value.HasField('string_value'): + if value.HasField('string_value'): return value.string_value - elif value.HasField('bool_value'): + if value.HasField('bool_value'): return value.bool_value - elif value.HasField('null_value'): + if value.HasField('null_value'): return None else: raise ValueError(f'Unsupported type: {value}') From 7e08be1c5bf051665d6c054bfd027264412ce1b3 Mon Sep 17 00:00:00 2001 From: Phil Stephens Date: Fri, 22 Aug 2025 05:15:39 +0000 Subject: [PATCH 6/9] Update proto conversion utilities --- src/a2a/utils/proto_utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/a2a/utils/proto_utils.py b/src/a2a/utils/proto_utils.py index df4221e8f..bbea0f690 100644 --- a/src/a2a/utils/proto_utils.py +++ b/src/a2a/utils/proto_utils.py @@ -957,8 +957,7 @@ def convert(value: struct_pb2.Value) -> Any: return value.bool_value if value.HasField('null_value'): return None - else: - raise ValueError(f'Unsupported type: {value}') + raise ValueError(f'Unsupported type: {value}') return {k: convert(v) for k, v in struct.fields.items()} From e91b31ca137a8932fec1a1d21da64258f4c71956 Mon Sep 17 00:00:00 2001 From: Phil Stephens Date: Fri, 22 Aug 2025 05:23:17 +0000 Subject: [PATCH 7/9] Update proto conversion utilities --- src/a2a/utils/proto_utils.py | 65 ++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/src/a2a/utils/proto_utils.py b/src/a2a/utils/proto_utils.py index bbea0f690..c9ae88c34 100644 --- a/src/a2a/utils/proto_utils.py +++ b/src/a2a/utils/proto_utils.py @@ -74,9 +74,7 @@ def file( ) -> a2a_pb2.FilePart: if isinstance(file, types.FileWithUri): return a2a_pb2.FilePart( - file_with_uri=file.uri, - mime_type=file.mime_type, - name=file.name + file_with_uri=file.uri, mime_type=file.mime_type, name=file.name ) return a2a_pb2.FilePart( file_with_bytes=file.bytes.encode('utf-8'), @@ -332,7 +330,9 @@ def extension( return a2a_pb2.AgentExtension( uri=extension.uri, description=extension.description, - params=dict_to_struct(extension.params) if extension.params else None, + params=dict_to_struct(extension.params) + if extension.params + else None, required=extension.required, ) @@ -487,9 +487,9 @@ def message(cls, message: a2a_pb2.Message) -> types.Message: @classmethod def metadata(cls, metadata: struct_pb2.Struct) -> dict[str, Any]: - if not metadata.fields: + if not metadata.fields: return {} - return struct_to_dict(metadata) + return struct_to_dict(metadata) @classmethod def part(cls, part: a2a_pb2.Part) -> types.Part: @@ -942,33 +942,32 @@ def role(cls, role: a2a_pb2.Role) -> types.Role: def struct_to_dict(struct: struct_pb2.Struct) -> dict[str, Any]: - """Converts a Struct proto to a Python dict.""" - - def convert(value: struct_pb2.Value) -> Any: - if value.HasField('list_value'): - return [convert(v) for v in value.list_value.values] - if value.HasField('struct_value'): - return {k: convert(v) for k, v in value.struct_value.fields.items()} - if value.HasField('number_value'): - return value.number_value - if value.HasField('string_value'): - return value.string_value - if value.HasField('bool_value'): - return value.bool_value - if value.HasField('null_value'): - return None - raise ValueError(f'Unsupported type: {value}') - - return {k: convert(v) for k, v in struct.fields.items()} + """Converts a Struct proto to a Python dict.""" + + def convert(value: struct_pb2.Value) -> Any: + if value.HasField('list_value'): + return [convert(v) for v in value.list_value.values] + if value.HasField('struct_value'): + return {k: convert(v) for k, v in value.struct_value.fields.items()} + if value.HasField('number_value'): + return value.number_value + if value.HasField('string_value'): + return value.string_value + if value.HasField('bool_value'): + return value.bool_value + if value.HasField('null_value'): + return None + raise ValueError(f'Unsupported type: {value}') + return {k: convert(v) for k, v in struct.fields.items()} -def dict_to_struct(dictionary: dict[str, Any]) -> struct_pb2.Struct: - """Converts a Python dict to a Struct proto.""" - struct = struct_pb2.Struct() - for key, val in dictionary.items(): - if isinstance(val, dict): - struct[key] = dict_to_struct(val) - else: - struct[key] = val - return struct +def dict_to_struct(dictionary: dict[str, Any]) -> struct_pb2.Struct: + """Converts a Python dict to a Struct proto.""" + struct = struct_pb2.Struct() + for key, val in dictionary.items(): + if isinstance(val, dict): + struct[key] = dict_to_struct(val) + else: + struct[key] = val + return struct From f8e8262c134e72038d26355da06af0db49925895 Mon Sep 17 00:00:00 2001 From: Phil Stephens Date: Fri, 22 Aug 2025 17:51:10 +0000 Subject: [PATCH 8/9] Update proto conversion utilities --- src/a2a/utils/proto_utils.py | 34 ++++++++++++---------------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/src/a2a/utils/proto_utils.py b/src/a2a/utils/proto_utils.py index c9ae88c34..98eeef36e 100644 --- a/src/a2a/utils/proto_utils.py +++ b/src/a2a/utils/proto_utils.py @@ -489,7 +489,7 @@ def message(cls, message: a2a_pb2.Message) -> types.Message: def metadata(cls, metadata: struct_pb2.Struct) -> dict[str, Any]: if not metadata.fields: return {} - return struct_to_dict(metadata) + return json_format.MessageToDict(metadata) @classmethod def part(cls, part: a2a_pb2.Part) -> types.Part: @@ -798,7 +798,7 @@ def agent_extension( return types.AgentExtension( uri=extension.uri, description=extension.description, - params=struct_to_dict(extension.params), + params=json_format.MessageToDict(extension.params), required=extension.required, ) @@ -941,29 +941,19 @@ def role(cls, role: a2a_pb2.Role) -> types.Role: return types.Role.agent -def struct_to_dict(struct: struct_pb2.Struct) -> dict[str, Any]: - """Converts a Struct proto to a Python dict.""" - - def convert(value: struct_pb2.Value) -> Any: - if value.HasField('list_value'): - return [convert(v) for v in value.list_value.values] - if value.HasField('struct_value'): - return {k: convert(v) for k, v in value.struct_value.fields.items()} - if value.HasField('number_value'): - return value.number_value - if value.HasField('string_value'): - return value.string_value - if value.HasField('bool_value'): - return value.bool_value - if value.HasField('null_value'): - return None - raise ValueError(f'Unsupported type: {value}') +def dict_to_struct(dictionary: dict[str, Any]) -> struct_pb2.Struct: + """Converts a Python dict to a Struct proto. - return {k: convert(v) for k, v in struct.fields.items()} + Unforunately, using the json_format.ParseDict does not work because this + wants the dictionary to be an exact match of the Struct proto with fields + and keys and values, not the traditional python dict struture. + Args: + dictionary: The Python dict to convert. -def dict_to_struct(dictionary: dict[str, Any]) -> struct_pb2.Struct: - """Converts a Python dict to a Struct proto.""" + Returns: + The Struct proto. + """ struct = struct_pb2.Struct() for key, val in dictionary.items(): if isinstance(val, dict): From 81a7f235d68d2ad941bef794f1861bcfd2da2d2a Mon Sep 17 00:00:00 2001 From: Holt Skinner <13262395+holtskinner@users.noreply.github.com> Date: Fri, 22 Aug 2025 18:56:08 +0100 Subject: [PATCH 9/9] Update src/a2a/utils/proto_utils.py --- src/a2a/utils/proto_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/a2a/utils/proto_utils.py b/src/a2a/utils/proto_utils.py index 98eeef36e..4aabdd1d2 100644 --- a/src/a2a/utils/proto_utils.py +++ b/src/a2a/utils/proto_utils.py @@ -944,9 +944,9 @@ def role(cls, role: a2a_pb2.Role) -> types.Role: def dict_to_struct(dictionary: dict[str, Any]) -> struct_pb2.Struct: """Converts a Python dict to a Struct proto. - Unforunately, using the json_format.ParseDict does not work because this + Unfortunately, using `json_format.ParseDict` does not work because this wants the dictionary to be an exact match of the Struct proto with fields - and keys and values, not the traditional python dict struture. + and keys and values, not the traditional Python dict structure. Args: dictionary: The Python dict to convert.