# -*- coding: utf-8 -*- """Agent API routes for design-time workspace registry CRUD only.""" import logging from pathlib import Path from typing import Any, Dict, List, Optional from fastapi import APIRouter, HTTPException, Depends from pydantic import BaseModel, Field from backend.agents import AgentFactory, get_registry logger = logging.getLogger(__name__) router = APIRouter(prefix="/api/workspaces/{workspace_id}/agents", tags=["agents"]) DESIGN_SCOPE = "design_workspace" def _design_scope_fields() -> dict[str, str]: return { "scope_type": DESIGN_SCOPE, "scope_note": ( "For design-time CRUD routes on this surface, `workspace_id` refers " "to the persistent registry under `workspaces/`." ), } # Request/Response Models class CreateAgentRequest(BaseModel): """Request to create a new agent.""" agent_id: str = Field(..., description="Unique agent identifier") agent_type: str = Field(..., description="Type of agent (e.g., technical_analyst)") name: Optional[str] = Field(None, description="Display name") description: Optional[str] = Field(None, description="Agent description") clone_from: Optional[str] = Field(None, description="Agent ID to clone from") llm_model_config: Optional[Dict[str, Any]] = Field(None, description="LLM model configuration") class UpdateAgentRequest(BaseModel): """Request to update design-time agent metadata.""" name: Optional[str] = None description: Optional[str] = None class AgentResponse(BaseModel): """Agent information response.""" agent_id: str agent_type: str workspace_id: str config_path: str agent_dir: str status: str = "inactive" scope_type: str = DESIGN_SCOPE scope_note: Optional[str] = None # Dependencies def get_agent_factory(): """Get AgentFactory instance.""" return AgentFactory() # Routes @router.post("", response_model=AgentResponse) async def create_agent( workspace_id: str, request: CreateAgentRequest, factory: AgentFactory = Depends(get_agent_factory), registry = Depends(get_registry), ): """ Create a new agent in a design-time workspace registry entry. Args: workspace_id: Workspace identifier request: Agent creation parameters Returns: Created agent information """ # Check workspace exists if not factory.workspaces_root.exists(): raise HTTPException(status_code=404, detail="Workspaces root not found") workspace_dir = factory.workspaces_root / workspace_id if not workspace_dir.exists(): raise HTTPException(status_code=404, detail=f"Workspace '{workspace_id}' not found") try: # Create agent agent = factory.create_agent( agent_id=request.agent_id, agent_type=request.agent_type, workspace_id=workspace_id, clone_from=request.clone_from, ) # Register in registry registry.register( agent_id=request.agent_id, agent_type=request.agent_type, workspace_id=workspace_id, config_path=str(agent.config_path), agent_dir=str(agent.agent_dir), status="inactive", ) return AgentResponse( agent_id=agent.agent_id, agent_type=agent.agent_type, workspace_id=agent.workspace_id, config_path=str(agent.config_path), agent_dir=str(agent.agent_dir), status="inactive", **_design_scope_fields(), ) except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) @router.get("", response_model=List[AgentResponse]) async def list_agents( workspace_id: str, factory: AgentFactory = Depends(get_agent_factory), ): """ List all agents in a design-time workspace registry entry. Args: workspace_id: Workspace identifier Returns: List of agents """ try: agents_data = factory.list_agents(workspace_id=workspace_id) return [ AgentResponse( agent_id=agent["agent_id"], agent_type=agent["agent_type"], workspace_id=workspace_id, config_path=agent["config_path"], agent_dir=str(Path(agent["config_path"]).parent), status="inactive", **_design_scope_fields(), ) for agent in agents_data ] except ValueError as e: raise HTTPException(status_code=404, detail=str(e)) @router.get("/{agent_id}", response_model=AgentResponse) async def get_agent( workspace_id: str, agent_id: str, registry = Depends(get_registry), ): """ Get design-time agent details from the persistent workspace registry. Args: workspace_id: Workspace identifier agent_id: Agent identifier Returns: Agent information """ agent_info = registry.get(agent_id) if not agent_info or agent_info.workspace_id != workspace_id: raise HTTPException(status_code=404, detail=f"Agent '{agent_id}' not found") return AgentResponse( agent_id=agent_info.agent_id, agent_type=agent_info.agent_type, workspace_id=agent_info.workspace_id, config_path=agent_info.config_path, agent_dir=agent_info.agent_dir, status=agent_info.status, **_design_scope_fields(), ) @router.delete("/{agent_id}") async def delete_agent( workspace_id: str, agent_id: str, factory: AgentFactory = Depends(get_agent_factory), registry = Depends(get_registry), ): """ Delete an agent. Args: workspace_id: Workspace identifier agent_id: Agent identifier Returns: Success message """ # Check agent exists in registry agent_info = registry.get(agent_id) if not agent_info or agent_info.workspace_id != workspace_id: raise HTTPException(status_code=404, detail=f"Agent '{agent_id}' not found") # Delete from factory success = factory.delete_agent(agent_id, workspace_id) if not success: raise HTTPException(status_code=404, detail=f"Agent '{agent_id}' not found") # Unregister registry.unregister(agent_id) return {"message": f"Agent '{agent_id}' deleted successfully"} @router.patch("/{agent_id}", response_model=AgentResponse) async def update_agent( workspace_id: str, agent_id: str, request: UpdateAgentRequest, registry = Depends(get_registry), ): """ Update agent configuration. Args: workspace_id: Workspace identifier agent_id: Agent identifier request: Update parameters Returns: Updated agent information """ agent_info = registry.get(agent_id) if not agent_info or agent_info.workspace_id != workspace_id: raise HTTPException(status_code=404, detail=f"Agent '{agent_id}' not found") # Update metadata in registry metadata_updates = {} if request.name: metadata_updates["name"] = request.name if request.description: metadata_updates["description"] = request.description if metadata_updates: registry.update_metadata(agent_id, metadata_updates) # Get updated info agent_info = registry.get(agent_id) return AgentResponse( agent_id=agent_info.agent_id, agent_type=agent_info.agent_type, workspace_id=agent_info.workspace_id, config_path=agent_info.config_path, agent_dir=agent_info.agent_dir, status=agent_info.status, **_design_scope_fields(), )