262 lines
7.5 KiB
Python
262 lines
7.5 KiB
Python
# -*- 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(),
|
|
)
|