diff --git a/src/google/adk/cli/adk_web_server.py b/src/google/adk/cli/adk_web_server.py index 1b422fe335..45747a52a1 100644 --- a/src/google/adk/cli/adk_web_server.py +++ b/src/google/adk/cli/adk_web_server.py @@ -280,6 +280,17 @@ class ListMetricsInfoResponse(common.BaseModel): metrics_info: list[MetricInfo] +class AppInfo(common.BaseModel): + name: str + root_agent_name: str + description: str + language: Literal["yaml", "python"] + + +class ListAppsResponse(common.BaseModel): + apps: list[AppInfo] + + def _setup_telemetry( otel_to_cloud: bool = False, internal_exporters: Optional[list[SpanProcessor]] = None, @@ -699,7 +710,14 @@ async def internal_lifespan(app: FastAPI): ) @app.get("/list-apps") - async def list_apps() -> list[str]: + async def list_apps( + detailed: bool = Query( + default=False, description="Return detailed app information" + ) + ) -> list[str] | ListAppsResponse: + if detailed: + apps_info = self.agent_loader.list_agents_detailed() + return ListAppsResponse(apps=[AppInfo(**app) for app in apps_info]) return self.agent_loader.list_agents() @app.get("/debug/trace/{event_id}", tags=[TAG_DEBUG]) diff --git a/src/google/adk/cli/utils/agent_loader.py b/src/google/adk/cli/utils/agent_loader.py index 0755c9147c..6a32ff93ee 100644 --- a/src/google/adk/cli/utils/agent_loader.py +++ b/src/google/adk/cli/utils/agent_loader.py @@ -20,6 +20,8 @@ import os from pathlib import Path import sys +from typing import Any +from typing import Literal from typing import Optional from typing import Union @@ -321,6 +323,50 @@ def list_agents(self) -> list[str]: agent_names.sort() return agent_names + def list_agents_detailed(self) -> list[dict[str, Any]]: + """Lists all agents with detailed metadata (name, description, type).""" + agent_names = self.list_agents() + apps_info = [] + + for agent_name in agent_names: + try: + loaded = self.load_agent(agent_name) + if isinstance(loaded, App): + agent = loaded.root_agent + else: + agent = loaded + + language = self._determine_agent_language(agent_name) + + app_info = { + "name": agent_name, + "root_agent_name": agent.name, + "description": agent.description, + "language": language, + } + apps_info.append(app_info) + + except Exception as e: + logger.error("Failed to load agent '%s': %s", agent_name, e) + continue + + return apps_info + + def _determine_agent_language( + self, agent_name: str + ) -> Literal["yaml", "python"]: + """Determine the type of agent based on file structure.""" + base_path = Path.cwd() / self.agents_dir / agent_name + + if (base_path / "root_agent.yaml").exists(): + return "yaml" + elif (base_path / "agent.py").exists(): + return "python" + elif (base_path / "__init__.py").exists(): + return "python" + + raise ValueError(f"Could not determine agent type for '{agent_name}'.") + def remove_agent_from_cache(self, agent_name: str): # Clear module cache for the agent and its submodules keys_to_delete = [ diff --git a/src/google/adk/cli/utils/base_agent_loader.py b/src/google/adk/cli/utils/base_agent_loader.py index d62a6b8651..bcef0dae42 100644 --- a/src/google/adk/cli/utils/base_agent_loader.py +++ b/src/google/adk/cli/utils/base_agent_loader.py @@ -18,6 +18,7 @@ from abc import ABC from abc import abstractmethod +from typing import Any from typing import Union from ...agents.base_agent import BaseAgent @@ -34,3 +35,15 @@ def load_agent(self, agent_name: str) -> Union[BaseAgent, App]: @abstractmethod def list_agents(self) -> list[str]: """Lists all agents available in the agent loader in alphabetical order.""" + + def list_agents_detailed(self) -> list[dict[str, Any]]: + agent_names = self.list_agents() + return [ + { + 'name': name, + 'display_name': None, + 'description': None, + 'type': None, + } + for name in agent_names + ] diff --git a/tests/unittests/cli/test_fast_api.py b/tests/unittests/cli/test_fast_api.py index 2d7b9472ba..d50bfcd8e5 100755 --- a/tests/unittests/cli/test_fast_api.py +++ b/tests/unittests/cli/test_fast_api.py @@ -190,6 +190,14 @@ def load_agent(self, app_name): def list_agents(self): return ["test_app"] + def list_agents_detailed(self): + return [{ + "name": "test_app", + "root_agent_name": "test_agent", + "description": "A test agent for unit testing", + "language": "python", + }] + return MockAgentLoader(".") @@ -548,6 +556,26 @@ def test_list_apps(test_app): logger.info(f"Listed apps: {data}") +def test_list_apps_detailed(test_app): + """Test listing available applications with detailed metadata.""" + response = test_app.get("/list-apps?detailed=true") + + assert response.status_code == 200 + data = response.json() + assert isinstance(data, dict) + assert "apps" in data + assert isinstance(data["apps"], list) + + for app in data["apps"]: + assert "name" in app + assert "rootAgentName" in app + assert "description" in app + assert "language" in app + assert app["language"] in ["yaml", "python"] + + logger.info(f"Listed apps: {data}") + + def test_create_session_with_id(test_app, test_session_info): """Test creating a session with a specific ID.""" new_session_id = "new_session_id"