use crate::ServerConfig;
use axum::{
Json,
extract::{Query, State},
http::StatusCode,
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AgentDetails {
pub name: String,
pub description: String,
pub kind: &'static str,
pub interaction_mode: adk_core::AgentInteractionMode,
pub capabilities: adk_core::AgentCapabilities,
pub services: RuntimeServices,
pub children: Vec<AgentChild>,
#[serde(skip_serializing_if = "Option::is_none")]
pub topology: Option<adk_core::AgentTopology>,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RuntimeServices {
pub telemetry: bool,
pub telemetry_status: TelemetryStatus,
pub artifacts: bool,
pub memory: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum TelemetryStatus {
Disabled,
Configured,
Collecting,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AgentChild {
pub name: String,
pub description: String,
pub capabilities: adk_core::AgentCapabilities,
}
#[derive(Clone)]
pub struct AppsController {
config: ServerConfig,
}
impl AppsController {
pub fn new(config: ServerConfig) -> Self {
Self { config }
}
}
pub async fn list_apps(
State(controller): State<AppsController>,
) -> Result<Json<Vec<String>>, StatusCode> {
let apps = controller.config.agent_loader.list_agents();
Ok(Json(apps))
}
#[derive(Debug, Deserialize)]
pub struct ListAppsQuery {
#[serde(default)]
pub relative_path: Option<String>,
}
#[derive(Debug, Serialize)]
pub struct AppInfo {
pub name: String,
pub description: String,
}
pub async fn list_apps_compat(
State(controller): State<AppsController>,
Query(_query): Query<ListAppsQuery>,
) -> Result<Json<Vec<String>>, StatusCode> {
let apps = controller.config.agent_loader.list_agents();
Ok(Json(apps))
}
pub async fn get_agent_details(
State(controller): State<AppsController>,
axum::extract::Path(name): axum::extract::Path<String>,
) -> Result<Json<AgentDetails>, StatusCode> {
let agent = controller
.config
.agent_loader
.load_agent(&name)
.await
.map_err(|_| StatusCode::NOT_FOUND)?;
let topology = agent.topology();
let interaction_mode = agent.interaction_mode();
let children = agent
.sub_agents()
.iter()
.map(|child| AgentChild {
name: child.name().to_string(),
description: child.description().to_string(),
capabilities: child.capabilities(),
})
.collect::<Vec<_>>();
let kind = if interaction_mode == adk_core::AgentInteractionMode::Realtime {
"realtime"
} else if topology.as_ref().is_some_and(|topology| {
topology
.relationships
.iter()
.any(|relationship| relationship.kind == adk_core::AgentRelationshipKind::Flow)
}) {
"workflow"
} else if topology.is_some() {
"team"
} else if children.is_empty() {
"agent"
} else {
"composite"
};
let telemetry_status = match controller.config.span_exporter.as_ref() {
Some(exporter) if exporter.is_collecting() => TelemetryStatus::Collecting,
Some(_) => TelemetryStatus::Configured,
None => TelemetryStatus::Disabled,
};
Ok(Json(AgentDetails {
name: agent.name().to_string(),
description: agent.description().to_string(),
kind,
interaction_mode,
capabilities: agent.capabilities(),
services: RuntimeServices {
telemetry: controller.config.span_exporter.is_some(),
telemetry_status,
artifacts: controller.config.artifact_service.is_some(),
memory: controller.config.memory_service.is_some(),
},
children,
topology,
}))
}