111111
112112
113113try :
114- from a2a .types import AgentCard
114+ from a2a .types import (
115+ AgentCard ,
116+ TransportProtocol ,
117+ Message ,
118+ TaskIdParams ,
119+ TaskQueryParams ,
120+ )
115121 from a2a .client import ClientConfig , ClientFactory
116- from a2a .utils .constants import TransportProtocol
122+
123+ AgentCard = AgentCard
124+ TransportProtocol = TransportProtocol
125+ Message = Message
126+ ClientConfig = ClientConfig
127+ ClientFactory = ClientFactory
128+ TaskIdParams = TaskIdParams
129+ TaskQueryParams = TaskQueryParams
117130except (ImportError , AttributeError ):
118131 AgentCard = None
119132 TransportProtocol = None
133+ Message = None
120134 ClientConfig = None
121135 ClientFactory = None
122- SendMessageRequest = None
123- GetTaskRequest = None
124- CancelTaskRequest = None
125- GetExtendedAgentCardRequest = None
136+ TaskIdParams = None
137+ TaskQueryParams = None
126138try :
127139 from autogen .agentchat import chat
128140
@@ -653,9 +665,9 @@ def _generate_class_methods_spec_or_raise(
653665 class_method = _to_proto (schema_dict )
654666 class_method [_MODE_KEY_IN_SCHEMA ] = mode
655667 if hasattr (agent , "agent_card" ):
656- class_method [ _A2A_AGENT_CARD ] = json_format . MessageToJson (
657- getattr ( agent , "agent_card" )
658- )
668+ card = getattr ( agent , "agent_card" )
669+ if card is not None :
670+ class_method [ _A2A_AGENT_CARD ] = _serialize_agent_card_to_json ( card )
659671 class_methods_spec .append (class_method )
660672
661673 return class_methods_spec
@@ -1795,53 +1807,79 @@ def _wrap_a2a_operation(method_name: str, agent_card: str) -> Callable[..., list
17951807 Args:
17961808 method_name: The name of the Agent Engine method to call.
17971809 agent_card: The agent card to use for the A2A API call.
1798- Example: { 'name': 'Sample Agent', 'description': ( 'A helpful
1799- assistant agent that can answer questions.' ),
1800- 'supportedInterfaces': [{ 'url': 'http://localhost:8080/a2a/rest/',
1801- 'protocolBinding': 'HTTP+JSON', 'protocolVersion': '1.0', }],
1802- 'version': '1.0.0', 'capabilities': { 'streaming': True,
1803- 'pushNotifications': False, 'extendedAgentCard': True, },
1804- 'defaultInputModes': ['text'], 'defaultOutputModes': ['text'],
1805- 'skills': [{ 'id': 'question_answer', 'name': 'Q&A Agent',
1806- 'description': ( 'A helpful assistant agent that can answer
1807- questions.' ), 'tags': ['Question-Answer'], 'examples': [ 'Who is
1808- leading 2025 F1 Standings?', 'Where can i find an active volcano?',
1809- ], 'inputModes': ['text'], 'outputModes': ['text'], }], }
1810-
1810+ Example:
1811+ {'additionalInterfaces': None,
1812+ 'capabilities': {'extensions': None,
1813+ 'pushNotifications': None,
1814+ 'stateTransitionHistory': None,
1815+ 'streaming': False},
1816+ 'defaultInputModes': ['text'],
1817+ 'defaultOutputModes': ['text'],
1818+ 'description': (
1819+ 'A helpful assistant agent that can answer questions.'
1820+ ),
1821+ 'documentationUrl': None,
1822+ 'iconUrl': None,
1823+ 'name': 'Q&A Agent',
1824+ 'preferredTransport': 'JSONRPC',
1825+ 'protocolVersion': '0.3.0',
1826+ 'provider': None,
1827+ 'security': None,
1828+ 'securitySchemes': None,
1829+ 'signatures': None,
1830+ 'skills': [{
1831+ 'description': (
1832+ 'A helpful assistant agent that can answer questions.'
1833+ ),
1834+ 'examples': ['Who is leading 2025 F1 Standings?',
1835+ 'Where can i find an active volcano?'],
1836+ 'id': 'question_answer',
1837+ 'inputModes': None,
1838+ 'name': 'Q&A Agent',
1839+ 'outputModes': None,
1840+ 'security': None,
1841+ 'tags': ['Question-Answer']}],
1842+ 'supportsAuthenticatedExtendedCard': True,
1843+ 'url': 'http://localhost:8080/',
1844+ 'version': '1.0.0'}
18111845 Returns:
18121846 A callable object that executes the method on the Agent Engine via
18131847 the A2A API.
18141848 """
18151849
18161850 async def _method (self , ** kwargs ) -> Any : # type: ignore[no-untyped-def]
1851+ """Wraps an Agent Engine method, creating a callable for A2A API."""
18171852 if not self .api_client :
18181853 raise ValueError ("api_client is not initialized." )
18191854 if not self .api_resource :
18201855 raise ValueError ("api_resource is not initialized." )
1856+ a2a_agent_card = AgentCard (** json .loads (agent_card ))
1857+ # A2A + AE integration currently only supports Rest API.
1858+ if (
1859+ a2a_agent_card .preferred_transport
1860+ and a2a_agent_card .preferred_transport != TransportProtocol .http_json
1861+ ):
1862+ raise ValueError (
1863+ "Only HTTP+JSON is supported for preferred transport on agent card "
1864+ )
18211865
1822- a2a_agent_card = AgentCard ()
1823- json_format .ParseDict (
1824- json .loads (agent_card ), a2a_agent_card , ignore_unknown_fields = True
1825- )
1866+ # Set preferred transport to HTTP+JSON if not set.
1867+ if not hasattr (a2a_agent_card , "preferred_transport" ):
1868+ a2a_agent_card .preferred_transport = TransportProtocol .http_json
18261869
1827- if a2a_agent_card .supported_interfaces :
1828- interface = a2a_agent_card .supported_interfaces [0 ]
1829- if interface .protocol_binding != TransportProtocol .HTTP_JSON :
1830- raise ValueError (
1831- "Only HTTP+JSON is supported for preferred transport on agent card"
1832- )
1833- else :
1834- raise ValueError ("Agent card does not define any supported interfaces." )
1870+ if not hasattr (a2a_agent_card .capabilities , "streaming" ):
1871+ a2a_agent_card .capabilities .streaming = False
18351872
1873+ # agent_card is set on the class_methods before set_up is invoked.
1874+ # Ensure that the agent_card url is set correctly before the client is created.
18361875 base_url = self .api_client ._api_client ._http_options .base_url .rstrip ("/" )
18371876 api_version = self .api_client ._api_client ._http_options .api_version
1838- a2a_agent_card .supported_interfaces [0 ].url = (
1839- f"{ base_url } /{ api_version } /{ self .api_resource .name } /a2a"
1840- )
1877+ a2a_agent_card .url = f"{ base_url } /{ api_version } /{ self .api_resource .name } /a2a"
18411878
1879+ # Using a2a client, inject the auth token from the global config.
18421880 config = ClientConfig (
1843- supported_protocol_bindings = [
1844- TransportProtocol .HTTP_JSON ,
1881+ supported_transports = [
1882+ TransportProtocol .http_json ,
18451883 ],
18461884 use_client_preference = True ,
18471885 httpx_client = httpx .AsyncClient (
@@ -1860,34 +1898,23 @@ async def _method(self, **kwargs) -> Any: # type: ignore[no-untyped-def]
18601898 factory = ClientFactory (config )
18611899 client = factory .create (a2a_agent_card )
18621900
1863- context = kwargs .pop ("context" , None )
1864- if context is not None :
1865- from a2a .client .client import ClientCallContext
1866-
1867- if not isinstance (context , ClientCallContext ):
1868- actual_context = ClientCallContext ()
1869- if hasattr (context , "state" ):
1870- actual_context .state = context .state
1871- elif isinstance (context , dict ):
1872- actual_context .state = context
1873- context = actual_context
1874-
1875- req = kwargs ["request" ]
18761901 if method_name == "on_message_send" :
1877- response = client .send_message (req , context = context )
1902+ response = client .send_message (Message ( ** kwargs ) )
18781903 chunks = []
18791904 async for chunk in response :
18801905 chunks .append (chunk )
18811906 return chunks
18821907 elif method_name == "on_get_task" :
1883- return await client .get_task (req , context = context )
1908+ response = await client .get_task (TaskQueryParams ( ** kwargs ) )
18841909 elif method_name == "on_cancel_task" :
1885- return await client .cancel_task (req , context = context )
1886- elif method_name == "on_get_extended_agent_card " :
1887- return await client .get_extended_agent_card ( req , context = context )
1910+ response = await client .cancel_task (TaskIdParams ( ** kwargs ) )
1911+ elif method_name == "handle_authenticated_agent_card " :
1912+ response = await client .get_card ( )
18881913 else :
18891914 raise ValueError (f"Unknown method name: { method_name } " )
18901915
1916+ return response
1917+
18911918 return _method # type: ignore[return-value]
18921919
18931920
@@ -2263,3 +2290,59 @@ def _import_autogen_tools_or_warn() -> Optional[types.ModuleType]:
22632290 "call `pip install google-cloud-aiplatform[ag2]`."
22642291 )
22652292 return None
2293+
2294+
2295+ def _serialize_agent_card_to_dict (card : Any ) -> Optional [Dict [str , Any ]]:
2296+ """Validates and serializes an AgentCard to a dictionary representation.
2297+
2298+ Args:
2299+ card: The AgentCard instance (Pydantic model or Protobuf Message).
2300+
2301+ Returns:
2302+ The serialized card as a dictionary.
2303+
2304+ Raises:
2305+ TypeError: If the card type is not supported.
2306+ """
2307+ if card is None :
2308+ return None
2309+
2310+ if hasattr (card , "model_dump" ):
2311+ return typing .cast (dict [str , Any ], card .model_dump (exclude_none = True ))
2312+ elif hasattr (card , "DESCRIPTOR" ):
2313+ from google .protobuf import json_format
2314+
2315+ return typing .cast (dict [str , Any ], json_format .MessageToDict (card ))
2316+ else :
2317+ raise TypeError (
2318+ f"Unsupported AgentCard type: { type (card )} . "
2319+ "Only Pydantic models and Protobuf Messages are supported."
2320+ )
2321+
2322+
2323+ def _serialize_agent_card_to_json (card : Any ) -> Optional [str ]:
2324+ """Validates and serializes an AgentCard to a JSON string representation.
2325+
2326+ Args:
2327+ card: The AgentCard instance (Pydantic model or Protobuf Message).
2328+
2329+ Returns:
2330+ The serialized card as a JSON string.
2331+
2332+ Raises:
2333+ TypeError: If the card type is not supported.
2334+ """
2335+ if card is None :
2336+ return None
2337+
2338+ if hasattr (card , "model_dump_json" ):
2339+ return typing .cast (str , card .model_dump_json ())
2340+ elif hasattr (card , "DESCRIPTOR" ):
2341+ from google .protobuf import json_format
2342+
2343+ return typing .cast (str , json_format .MessageToJson (card ))
2344+ else :
2345+ raise TypeError (
2346+ f"Unsupported AgentCard type: { type (card )} . "
2347+ "Only Pydantic models and Protobuf Messages are supported."
2348+ )
0 commit comments