1414
1515import logging
1616import os
17- from typing import Callable , override
18-
1917import uvicorn
18+ import inspect
19+
20+ from typing import Callable , override
2021from a2a .server .agent_execution import AgentExecutor
2122from a2a .server .agent_execution .context import RequestContext
2223from a2a .server .apps import A2AStarletteApplication
2627from a2a .server .tasks .task_store import TaskStore
2728from a2a .types import AgentCard
2829from starlette .applications import Starlette
29- from starlette .responses import JSONResponse
30+ from starlette .responses import JSONResponse , Response
3031from starlette .routing import Route
32+ from starlette .requests import Request
3133
3234from agentkit .apps .a2a_app .telemetry import telemetry
3335from agentkit .apps .base_app import BaseAgentkitApp
@@ -41,9 +43,7 @@ async def wrapper(*args, **kwargs):
4143 context : RequestContext = args [1 ]
4244 event_queue : EventQueue = args [2 ]
4345
44- with telemetry .tracer .start_as_current_span (
45- name = "a2a_invocation"
46- ) as span :
46+ with telemetry .tracer .start_as_current_span (name = "a2a_invocation" ) as span :
4747 exception = None
4848 try :
4949 result = await execute_func (
@@ -75,6 +75,7 @@ def __init__(self) -> None:
7575
7676 self ._agent_executor : AgentExecutor | None = None
7777 self ._task_store : TaskStore | None = None
78+ self ._ping_func : Callable | None = None
7879
7980 def agent_executor (self , ** kwargs ) -> Callable :
8081 """Wrap an AgentExecutor class, init it, then bind it to the app instance."""
@@ -86,9 +87,7 @@ def wrapper(cls: type) -> type[AgentExecutor]:
8687 )
8788
8889 if self ._agent_executor :
89- raise RuntimeError (
90- "An executor is already bound to this app instance."
91- )
90+ raise RuntimeError ("An executor is already bound to this app instance." )
9291
9392 # Wrap the execute method for intercepting context and event_queue
9493 cls .execute = _wrap_agent_executor_execute_func (cls .execute )
@@ -119,6 +118,50 @@ def wrapper(cls: type) -> type[TaskStore]:
119118
120119 return wrapper
121120
121+ def ping (self , func : Callable ) -> Callable :
122+ """Register a zero-argument health check function and expose it via GET /ping.
123+
124+ The function must accept no arguments and should return either a string or a dict.
125+ The response shape mirrors SimpleApp: {"status": <str|dict>}.
126+ """
127+ # Ensure zero-argument function similar to SimpleApp
128+ if len (list (inspect .signature (func ).parameters .keys ())) != 0 :
129+ raise AssertionError (
130+ f"Health check function `{ func .__name__ } ` should not receive any arguments."
131+ )
132+
133+ self ._ping_func = func
134+ return func
135+
136+ def _format_ping_status (self , result : str | dict ) -> dict :
137+ # Align behavior with SimpleApp: always wrap into {"status": result}
138+ if isinstance (result , (str , dict )):
139+ return {"status" : result }
140+ logger .error (
141+ f"Health check function { getattr (self ._ping_func , '__name__' , 'unknown' )} must return `dict` or `str` type."
142+ )
143+ return {"status" : "error" , "message" : "Invalid response type." }
144+
145+ async def ping_endpoint (self , request : Request ) -> Response :
146+ if not self ._ping_func :
147+ logger .error ("Ping handler function is not set" )
148+ return Response (status_code = 404 )
149+
150+ try :
151+ result = (
152+ await self ._ping_func ()
153+ if inspect .iscoroutinefunction (self ._ping_func )
154+ else self ._ping_func ()
155+ )
156+ payload = self ._format_ping_status (result )
157+ return JSONResponse (content = payload )
158+ except Exception as e :
159+ logger .exception ("Ping handler function failed: %s" , e )
160+ return JSONResponse (
161+ content = {"status" : "error" , "message" : str (e )},
162+ status_code = 500 ,
163+ )
164+
122165 def add_env_detect_route (self , app : Starlette ):
123166 def is_agentkit_runtime () -> bool :
124167 if os .getenv ("RUNTIME_IAM_ROLE_TRN" , "" ):
@@ -136,6 +179,9 @@ def is_agentkit_runtime() -> bool:
136179 )
137180 app .routes .append (route )
138181
182+ def add_ping_route (self , app : Starlette ):
183+ app .add_route ("/ping" , self .ping_endpoint , methods = ["GET" ])
184+
139185 @override
140186 def run (self , agent_card : AgentCard , host : str , port : int = 8000 ):
141187 if not self ._agent_executor :
@@ -155,6 +201,8 @@ def run(self, agent_card: AgentCard, host: str, port: int = 8000):
155201 ),
156202 ).build ()
157203
204+ # Register routes in the same style
205+ self .add_ping_route (a2a_app )
158206 self .add_env_detect_route (a2a_app )
159207
160208 uvicorn .run (a2a_app , host = host , port = port )
0 commit comments