feat: add /public/agents endpoint for Agent Console with home_node
This commit is contained in:
@@ -161,6 +161,21 @@ class AgentRead(BaseModel):
|
|||||||
capabilities: List[str] = []
|
capabilities: List[str] = []
|
||||||
|
|
||||||
|
|
||||||
|
class AgentSummary(BaseModel):
|
||||||
|
"""Agent summary for Agent Console"""
|
||||||
|
id: str
|
||||||
|
display_name: str
|
||||||
|
kind: str = "assistant"
|
||||||
|
avatar_url: Optional[str] = None
|
||||||
|
status: str = "offline"
|
||||||
|
is_public: bool = False
|
||||||
|
public_slug: Optional[str] = None
|
||||||
|
public_title: Optional[str] = None
|
||||||
|
district: Optional[str] = None
|
||||||
|
home_node: Optional[HomeNodeView] = None
|
||||||
|
microdao_memberships: List[Dict[str, Any]] = []
|
||||||
|
|
||||||
|
|
||||||
class AgentPresence(BaseModel):
|
class AgentPresence(BaseModel):
|
||||||
"""Agent presence in a room"""
|
"""Agent presence in a room"""
|
||||||
agent_id: str
|
agent_id: str
|
||||||
|
|||||||
@@ -307,6 +307,87 @@ async def get_all_agents() -> List[dict]:
|
|||||||
return [dict(row) for row in rows]
|
return [dict(row) for row in rows]
|
||||||
|
|
||||||
|
|
||||||
|
async def get_agents_with_home_node(
|
||||||
|
kind: Optional[str] = None,
|
||||||
|
node_id: Optional[str] = None,
|
||||||
|
limit: int = 100,
|
||||||
|
offset: int = 0
|
||||||
|
) -> Tuple[List[dict], int]:
|
||||||
|
"""Отримати агентів з інформацією про home_node"""
|
||||||
|
pool = await get_pool()
|
||||||
|
|
||||||
|
params: List[Any] = []
|
||||||
|
where_clauses = ["1=1"]
|
||||||
|
|
||||||
|
if kind:
|
||||||
|
params.append(kind)
|
||||||
|
where_clauses.append(f"a.kind = ${len(params)}")
|
||||||
|
|
||||||
|
if node_id:
|
||||||
|
params.append(node_id)
|
||||||
|
where_clauses.append(f"a.node_id = ${len(params)}")
|
||||||
|
|
||||||
|
where_sql = " AND ".join(where_clauses)
|
||||||
|
|
||||||
|
query = f"""
|
||||||
|
SELECT
|
||||||
|
a.id,
|
||||||
|
a.display_name,
|
||||||
|
a.kind,
|
||||||
|
a.avatar_url,
|
||||||
|
a.status,
|
||||||
|
a.is_public,
|
||||||
|
a.public_slug,
|
||||||
|
a.public_title,
|
||||||
|
a.public_district,
|
||||||
|
a.node_id,
|
||||||
|
nc.node_name AS home_node_name,
|
||||||
|
nc.hostname AS home_node_hostname,
|
||||||
|
nc.roles AS home_node_roles,
|
||||||
|
nc.environment AS home_node_environment,
|
||||||
|
COUNT(*) OVER() AS total_count
|
||||||
|
FROM agents a
|
||||||
|
LEFT JOIN node_cache nc ON a.node_id = nc.node_id
|
||||||
|
WHERE {where_sql}
|
||||||
|
ORDER BY a.display_name
|
||||||
|
LIMIT ${len(params) + 1} OFFSET ${len(params) + 2}
|
||||||
|
"""
|
||||||
|
|
||||||
|
params.append(limit)
|
||||||
|
params.append(offset)
|
||||||
|
|
||||||
|
rows = await pool.fetch(query, *params)
|
||||||
|
if not rows:
|
||||||
|
return [], 0
|
||||||
|
|
||||||
|
total = rows[0]["total_count"]
|
||||||
|
items = []
|
||||||
|
|
||||||
|
for row in rows:
|
||||||
|
data = dict(row)
|
||||||
|
data.pop("total_count", None)
|
||||||
|
|
||||||
|
# Build home_node object
|
||||||
|
if data.get("node_id"):
|
||||||
|
data["home_node"] = {
|
||||||
|
"id": data.get("node_id"),
|
||||||
|
"name": data.get("home_node_name"),
|
||||||
|
"hostname": data.get("home_node_hostname"),
|
||||||
|
"roles": list(data.get("home_node_roles") or []),
|
||||||
|
"environment": data.get("home_node_environment")
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
data["home_node"] = None
|
||||||
|
|
||||||
|
# Clean up intermediate fields
|
||||||
|
for key in ["home_node_name", "home_node_hostname", "home_node_roles", "home_node_environment"]:
|
||||||
|
data.pop(key, None)
|
||||||
|
|
||||||
|
items.append(data)
|
||||||
|
|
||||||
|
return items, total
|
||||||
|
|
||||||
|
|
||||||
async def get_agents_by_room(room_id: str) -> List[dict]:
|
async def get_agents_by_room(room_id: str) -> List[dict]:
|
||||||
"""Отримати агентів у конкретній кімнаті"""
|
"""Отримати агентів у конкретній кімнаті"""
|
||||||
pool = await get_pool()
|
pool = await get_pool()
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from models_city import (
|
|||||||
CityMapResponse,
|
CityMapResponse,
|
||||||
AgentRead,
|
AgentRead,
|
||||||
AgentPresence,
|
AgentPresence,
|
||||||
|
AgentSummary,
|
||||||
HomeNodeView,
|
HomeNodeView,
|
||||||
PublicCitizenSummary,
|
PublicCitizenSummary,
|
||||||
PublicCitizenProfile,
|
PublicCitizenProfile,
|
||||||
@@ -57,6 +58,63 @@ class MicrodaoMembershipPayload(BaseModel):
|
|||||||
is_core: bool = False
|
is_core: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Agents API (for Agent Console)
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
@public_router.get("/agents")
|
||||||
|
async def list_agents(
|
||||||
|
kind: Optional[str] = Query(None, description="Filter by agent kind"),
|
||||||
|
node_id: Optional[str] = Query(None, description="Filter by node_id"),
|
||||||
|
limit: int = Query(100, le=200),
|
||||||
|
offset: int = Query(0, ge=0)
|
||||||
|
):
|
||||||
|
"""Список всіх агентів для Agent Console"""
|
||||||
|
try:
|
||||||
|
agents, total = await repo_city.get_agents_with_home_node(
|
||||||
|
kind=kind,
|
||||||
|
node_id=node_id,
|
||||||
|
limit=limit,
|
||||||
|
offset=offset
|
||||||
|
)
|
||||||
|
|
||||||
|
items: List[AgentSummary] = []
|
||||||
|
for agent in agents:
|
||||||
|
# Build home_node if available
|
||||||
|
home_node_data = agent.get("home_node")
|
||||||
|
home_node = None
|
||||||
|
if home_node_data:
|
||||||
|
home_node = HomeNodeView(
|
||||||
|
id=home_node_data.get("id"),
|
||||||
|
name=home_node_data.get("name"),
|
||||||
|
hostname=home_node_data.get("hostname"),
|
||||||
|
roles=home_node_data.get("roles", []),
|
||||||
|
environment=home_node_data.get("environment")
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get microdao memberships
|
||||||
|
memberships = await repo_city.get_agent_microdao_memberships(agent["id"])
|
||||||
|
|
||||||
|
items.append(AgentSummary(
|
||||||
|
id=agent["id"],
|
||||||
|
display_name=agent["display_name"],
|
||||||
|
kind=agent.get("kind", "assistant"),
|
||||||
|
avatar_url=agent.get("avatar_url"),
|
||||||
|
status=agent.get("status", "offline"),
|
||||||
|
is_public=agent.get("is_public", False),
|
||||||
|
public_slug=agent.get("public_slug"),
|
||||||
|
public_title=agent.get("public_title"),
|
||||||
|
district=agent.get("public_district"),
|
||||||
|
home_node=home_node,
|
||||||
|
microdao_memberships=memberships
|
||||||
|
))
|
||||||
|
|
||||||
|
return {"items": items, "total": total}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to list agents: {e}")
|
||||||
|
raise HTTPException(status_code=500, detail="Failed to list agents")
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Public Citizens API
|
# Public Citizens API
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user