Skip to content

Commit a7a1550

Browse files
committed
Add complete Google A2A protocol compatibility to python_a2a while maintaining 100% backward compatibility with existing code. This allows direct integration with Google's Agent-to-Agent ecosystem without requiring any code changes for existing users.
Key features: Automatic format detection and conversion between protocols Transparent handling of Google A2A format requests and responses Complete preservation of existing API behavior and data structures Protocol advertisement through agent capabilities Format preference persistence for optimized communication Tested with comprehensive regression testing on existing examples including the travel planner. No API changes required - the adaptation happens automatically behind the scenes.
1 parent 56c1219 commit a7a1550

File tree

11 files changed

+1813
-235
lines changed

11 files changed

+1813
-235
lines changed

.uv/UVManifest.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
# This file enables reproducible builds with UV
33

44
[manifest]
5-
version = "0.5.0" # UVManifest version
5+
version = "0.5.1" # UVManifest version
66

77
[project]
88
name = "python-a2a"
9-
version = "0.5.0" # Updated version
9+
version = "0.5.1" # Updated version
1010

1111
[python]
1212
# List of Python versions supported by the project

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "python-a2a"
7-
version = "0.5.0"
7+
version = "0.5.1"
88
description = "A comprehensive Python library for Google's Agent-to-Agent (A2A) protocol"
99
readme = "README.md"
1010
requires-python = ">=3.9"

python_a2a/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
A Python library for implementing Google's Agent-to-Agent (A2A) protocol.
55
"""
66

7-
__version__ = "0.5.0"
7+
__version__ = "0.5.1"
88

99
# Setup feature flags
1010
import sys

python_a2a/client/http.py

Lines changed: 436 additions & 132 deletions
Large diffs are not rendered by default.

python_a2a/models/conversation.py

Lines changed: 136 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import uuid
66
from dataclasses import dataclass, field
7-
from typing import Dict, List, Optional, Any
7+
from typing import Dict, List, Optional, Any, ClassVar
88

99
from .base import BaseModel
1010
from .message import Message, MessageRole
@@ -20,6 +20,9 @@ class Conversation(BaseModel):
2020
conversation_id: str = field(default_factory=lambda: str(uuid.uuid4()))
2121
messages: List[Message] = field(default_factory=list)
2222
metadata: Optional[Dict[str, Any]] = None
23+
24+
# Add a class variable to track protocol compatibility mode
25+
_GOOGLE_A2A_COMPATIBILITY: ClassVar[bool] = False
2326

2427
def add_message(self, message: Message) -> Message:
2528
"""
@@ -135,9 +138,140 @@ def create_error_message(self, error_message: str,
135138
@classmethod
136139
def from_dict(cls, data: Dict[str, Any]) -> 'Conversation':
137140
"""Create a Conversation from a dictionary"""
141+
# Check if this is Google A2A format with careful detection
142+
if ("messages" in data and isinstance(data.get("messages"), list) and
143+
data["messages"] and "parts" in data["messages"][0] and
144+
isinstance(data["messages"][0].get("parts"), list) and
145+
"role" in data["messages"][0] and not "content" in data["messages"][0]):
146+
return cls.from_google_a2a(data)
147+
148+
# Standard python_a2a format
138149
messages = [Message.from_dict(m) for m in data.get("messages", [])]
139150
return cls(
140151
conversation_id=data.get("conversation_id", str(uuid.uuid4())),
141152
messages=messages,
142153
metadata=data.get("metadata")
143-
)
154+
)
155+
156+
def to_dict(self) -> Dict[str, Any]:
157+
"""Convert Conversation to dictionary representation"""
158+
# Use google format if compatibility mode is enabled
159+
if self._GOOGLE_A2A_COMPATIBILITY:
160+
return self.to_google_a2a()
161+
162+
# Standard python_a2a format
163+
result = {
164+
"conversation_id": self.conversation_id,
165+
"messages": [message.to_dict() for message in self.messages]
166+
}
167+
168+
if self.metadata:
169+
result["metadata"] = self.metadata
170+
171+
return result
172+
173+
@classmethod
174+
def from_google_a2a(cls, data: Dict[str, Any]) -> 'Conversation':
175+
"""Create a Conversation from a Google A2A format dictionary
176+
177+
Args:
178+
data: A dictionary in Google A2A format
179+
180+
Returns:
181+
A Conversation object
182+
"""
183+
if not ("messages" in data and isinstance(data.get("messages"), list)):
184+
raise ValueError("Not a valid Google A2A format conversation")
185+
186+
conversation_id = data.get("conversation_id", str(uuid.uuid4()))
187+
188+
# Process messages
189+
messages = []
190+
for msg_data in data.get("messages", []):
191+
# Skip invalid messages
192+
if not isinstance(msg_data, dict):
193+
continue
194+
195+
# Try to convert from Google A2A format
196+
if "parts" in msg_data and "role" in msg_data and not "content" in msg_data:
197+
try:
198+
# Convert message from Google A2A format
199+
message = Message.from_google_a2a(msg_data)
200+
201+
# Set conversation_id if not already set
202+
if not message.conversation_id:
203+
message.conversation_id = conversation_id
204+
205+
messages.append(message)
206+
except Exception:
207+
# If conversion fails, skip this message
208+
continue
209+
else:
210+
# Try standard format as fallback
211+
try:
212+
message = Message.from_dict(msg_data)
213+
214+
# Set conversation_id if not already set
215+
if not message.conversation_id:
216+
message.conversation_id = conversation_id
217+
218+
messages.append(message)
219+
except Exception:
220+
# If parsing fails, skip this message
221+
continue
222+
223+
return cls(
224+
conversation_id=conversation_id,
225+
messages=messages,
226+
metadata=data.get("metadata")
227+
)
228+
229+
def to_google_a2a(self) -> Dict[str, Any]:
230+
"""Convert to Google A2A format dictionary
231+
232+
Returns:
233+
A dictionary in Google A2A format
234+
"""
235+
# Convert messages to Google A2A format
236+
google_messages = []
237+
for message in self.messages:
238+
# Convert message to Google A2A format
239+
try:
240+
google_messages.append(message.to_google_a2a())
241+
except Exception:
242+
# Skip any messages that can't be converted
243+
continue
244+
245+
# Create the Google A2A conversation dict
246+
result = {
247+
"conversation_id": self.conversation_id,
248+
"messages": google_messages
249+
}
250+
251+
if self.metadata:
252+
result["metadata"] = self.metadata
253+
254+
return result
255+
256+
@classmethod
257+
def enable_google_a2a_compatibility(cls, enable: bool = True) -> None:
258+
"""Enable or disable Google A2A compatibility mode
259+
260+
When enabled, to_dict() will output Google A2A format
261+
262+
Args:
263+
enable: Whether to enable compatibility mode
264+
"""
265+
cls._GOOGLE_A2A_COMPATIBILITY = enable
266+
# Also update Message class compatibility
267+
from .message import Message
268+
Message.enable_google_a2a_compatibility(enable)
269+
270+
@classmethod
271+
def is_google_a2a_compatibility_enabled(cls) -> bool:
272+
"""Check if Google A2A compatibility mode is enabled
273+
274+
Returns:
275+
True if enabled, False otherwise
276+
"""
277+
return cls._GOOGLE_A2A_COMPATIBILITY

0 commit comments

Comments
 (0)