|  | 
|  | 1 | +"""A2A-compatible wrapper for Strands Agent. | 
|  | 2 | +
 | 
|  | 3 | +This module provides the A2AAgent class, which adapts a Strands Agent to the A2A protocol, | 
|  | 4 | +allowing it to be used in A2A-compatible systems. | 
|  | 5 | +""" | 
|  | 6 | + | 
|  | 7 | +import logging | 
|  | 8 | +from typing import Any, Literal | 
|  | 9 | + | 
|  | 10 | +import uvicorn | 
|  | 11 | +from a2a.server.apps import A2AFastAPIApplication, A2AStarletteApplication | 
|  | 12 | +from a2a.server.request_handlers import DefaultRequestHandler | 
|  | 13 | +from a2a.server.tasks import InMemoryTaskStore | 
|  | 14 | +from a2a.types import AgentCapabilities, AgentCard, AgentSkill | 
|  | 15 | +from fastapi import FastAPI | 
|  | 16 | +from starlette.applications import Starlette | 
|  | 17 | + | 
|  | 18 | +from ...agent.agent import Agent as SAAgent | 
|  | 19 | +from .executor import StrandsA2AExecutor | 
|  | 20 | + | 
|  | 21 | +logger = logging.getLogger(__name__) | 
|  | 22 | + | 
|  | 23 | + | 
|  | 24 | +class A2AAgent: | 
|  | 25 | +    """A2A-compatible wrapper for Strands Agent.""" | 
|  | 26 | + | 
|  | 27 | +    def __init__( | 
|  | 28 | +        self, | 
|  | 29 | +        agent: SAAgent, | 
|  | 30 | +        *, | 
|  | 31 | +        # AgentCard | 
|  | 32 | +        host: str = "0.0.0.0", | 
|  | 33 | +        port: int = 9000, | 
|  | 34 | +        version: str = "0.0.1", | 
|  | 35 | +    ): | 
|  | 36 | +        """Initialize an A2A-compatible agent from a Strands agent. | 
|  | 37 | +
 | 
|  | 38 | +        Args: | 
|  | 39 | +            agent: The Strands Agent to wrap with A2A compatibility. | 
|  | 40 | +            name: The name of the agent, used in the AgentCard. | 
|  | 41 | +            description: A description of the agent's capabilities, used in the AgentCard. | 
|  | 42 | +            host: The hostname or IP address to bind the A2A server to. Defaults to "0.0.0.0". | 
|  | 43 | +            port: The port to bind the A2A server to. Defaults to 9000. | 
|  | 44 | +            version: The version of the agent. Defaults to "0.0.1". | 
|  | 45 | +        """ | 
|  | 46 | +        self.host = host | 
|  | 47 | +        self.port = port | 
|  | 48 | +        self.http_url = f"http://{self.host}:{self.port}/" | 
|  | 49 | +        self.version = version | 
|  | 50 | +        self.strands_agent = agent | 
|  | 51 | +        self.name = self.strands_agent.name | 
|  | 52 | +        self.description = self.strands_agent.description | 
|  | 53 | +        # TODO: enable configurable capabilities and request handler | 
|  | 54 | +        self.capabilities = AgentCapabilities() | 
|  | 55 | +        self.request_handler = DefaultRequestHandler( | 
|  | 56 | +            agent_executor=StrandsA2AExecutor(self.strands_agent), | 
|  | 57 | +            task_store=InMemoryTaskStore(), | 
|  | 58 | +        ) | 
|  | 59 | +        logger.info("Strands' integration with A2A is experimental. Be aware of frequent breaking changes.") | 
|  | 60 | + | 
|  | 61 | +    @property | 
|  | 62 | +    def public_agent_card(self) -> AgentCard: | 
|  | 63 | +        """Get the public AgentCard for this agent. | 
|  | 64 | +
 | 
|  | 65 | +        The AgentCard contains metadata about the agent, including its name, | 
|  | 66 | +        description, URL, version, skills, and capabilities. This information | 
|  | 67 | +        is used by other agents and systems to discover and interact with this agent. | 
|  | 68 | +
 | 
|  | 69 | +        Returns: | 
|  | 70 | +            AgentCard: The public agent card containing metadata about this agent. | 
|  | 71 | +
 | 
|  | 72 | +        Raises: | 
|  | 73 | +            ValueError: If name or description is None or empty. | 
|  | 74 | +        """ | 
|  | 75 | +        if not self.name: | 
|  | 76 | +            raise ValueError("A2A agent name cannot be None or empty") | 
|  | 77 | +        if not self.description: | 
|  | 78 | +            raise ValueError("A2A agent description cannot be None or empty") | 
|  | 79 | + | 
|  | 80 | +        return AgentCard( | 
|  | 81 | +            name=self.name, | 
|  | 82 | +            description=self.description, | 
|  | 83 | +            url=self.http_url, | 
|  | 84 | +            version=self.version, | 
|  | 85 | +            skills=self.agent_skills, | 
|  | 86 | +            defaultInputModes=["text"], | 
|  | 87 | +            defaultOutputModes=["text"], | 
|  | 88 | +            capabilities=self.capabilities, | 
|  | 89 | +        ) | 
|  | 90 | + | 
|  | 91 | +    @property | 
|  | 92 | +    def agent_skills(self) -> list[AgentSkill]: | 
|  | 93 | +        """Get the list of skills this agent provides. | 
|  | 94 | +
 | 
|  | 95 | +        Skills represent specific capabilities that the agent can perform. | 
|  | 96 | +        Strands agent tools are adapted to A2A skills. | 
|  | 97 | +
 | 
|  | 98 | +        Returns: | 
|  | 99 | +            list[AgentSkill]: A list of skills this agent provides. | 
|  | 100 | +        """ | 
|  | 101 | +        # TODO: translate Strands tools (native & MCP) to skills | 
|  | 102 | +        return [] | 
|  | 103 | + | 
|  | 104 | +    def to_starlette_app(self) -> Starlette: | 
|  | 105 | +        """Create a Starlette application for serving this agent via HTTP. | 
|  | 106 | +
 | 
|  | 107 | +        This method creates a Starlette application that can be used to serve | 
|  | 108 | +        the agent via HTTP using the A2A protocol. | 
|  | 109 | +
 | 
|  | 110 | +        Returns: | 
|  | 111 | +            Starlette: A Starlette application configured to serve this agent. | 
|  | 112 | +        """ | 
|  | 113 | +        return A2AStarletteApplication(agent_card=self.public_agent_card, http_handler=self.request_handler).build() | 
|  | 114 | + | 
|  | 115 | +    def to_fastapi_app(self) -> FastAPI: | 
|  | 116 | +        """Create a FastAPI application for serving this agent via HTTP. | 
|  | 117 | +
 | 
|  | 118 | +        This method creates a FastAPI application that can be used to serve | 
|  | 119 | +        the agent via HTTP using the A2A protocol. | 
|  | 120 | +
 | 
|  | 121 | +        Returns: | 
|  | 122 | +            FastAPI: A FastAPI application configured to serve this agent. | 
|  | 123 | +        """ | 
|  | 124 | +        return A2AFastAPIApplication(agent_card=self.public_agent_card, http_handler=self.request_handler).build() | 
|  | 125 | + | 
|  | 126 | +    def serve(self, app_type: Literal["fastapi", "starlette"] = "starlette", **kwargs: Any) -> None: | 
|  | 127 | +        """Start the A2A server with the specified application type. | 
|  | 128 | +
 | 
|  | 129 | +        This method starts an HTTP server that exposes the agent via the A2A protocol. | 
|  | 130 | +        The server can be implemented using either FastAPI or Starlette, depending on | 
|  | 131 | +        the specified app_type. | 
|  | 132 | +
 | 
|  | 133 | +        Args: | 
|  | 134 | +            app_type: The type of application to serve, either "fastapi" or "starlette". | 
|  | 135 | +                Defaults to "starlette". | 
|  | 136 | +            **kwargs: Additional keyword arguments to pass to uvicorn.run. | 
|  | 137 | +        """ | 
|  | 138 | +        try: | 
|  | 139 | +            logger.info("Starting Strands A2A server...") | 
|  | 140 | +            if app_type == "fastapi": | 
|  | 141 | +                uvicorn.run(self.to_fastapi_app(), host=self.host, port=self.port, **kwargs) | 
|  | 142 | +            else: | 
|  | 143 | +                uvicorn.run(self.to_starlette_app(), host=self.host, port=self.port, **kwargs) | 
|  | 144 | +        except KeyboardInterrupt: | 
|  | 145 | +            logger.warning("Strands A2A server shutdown requested (KeyboardInterrupt).") | 
|  | 146 | +        except Exception: | 
|  | 147 | +            logger.exception("Strands A2A server encountered exception.") | 
|  | 148 | +        finally: | 
|  | 149 | +            logger.info("Strands A2A server has shutdown.") | 
0 commit comments