 f3cbb5c6f7
			
		
	
	f3cbb5c6f7
	
	
	
		
			
			- Parameterize CORS_ORIGINS in docker-compose.swarm.yml - Add .env.example with configuration options - Create comprehensive LOCAL_DEVELOPMENT.md guide - Update README.md with environment variable documentation - Provide alternatives for local development without production domain 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			332 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			332 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| """
 | |
| CLI Agents API endpoints
 | |
| Provides REST API for managing CLI-based agents in the Hive system.
 | |
| """
 | |
| 
 | |
| from fastapi import APIRouter, HTTPException, Depends
 | |
| from sqlalchemy.orm import Session
 | |
| from typing import Dict, Any, List
 | |
| from pydantic import BaseModel
 | |
| 
 | |
| from ..core.database import get_db
 | |
| from ..models.agent import Agent as ORMAgent
 | |
| from ..core.hive_coordinator import HiveCoordinator, Agent, AgentType
 | |
| from ..cli_agents.cli_agent_manager import get_cli_agent_manager
 | |
| 
 | |
| router = APIRouter(prefix="/api/cli-agents", tags=["cli-agents"])
 | |
| 
 | |
| 
 | |
| class CliAgentRegistration(BaseModel):
 | |
|     """Request model for CLI agent registration"""
 | |
|     id: str
 | |
|     host: str
 | |
|     node_version: str
 | |
|     model: str = "gemini-2.5-pro"
 | |
|     specialization: str = "general_ai"
 | |
|     max_concurrent: int = 2
 | |
|     agent_type: str = "gemini"  # CLI agent type (gemini, etc.)
 | |
|     command_timeout: int = 60
 | |
|     ssh_timeout: int = 5
 | |
| 
 | |
| 
 | |
| class CliAgentResponse(BaseModel):
 | |
|     """Response model for CLI agent operations"""
 | |
|     id: str
 | |
|     endpoint: str
 | |
|     model: str
 | |
|     specialization: str
 | |
|     agent_type: str
 | |
|     cli_config: Dict[str, Any]
 | |
|     status: str
 | |
|     max_concurrent: int
 | |
|     current_tasks: int
 | |
| 
 | |
| 
 | |
| @router.post("/register", response_model=Dict[str, Any])
 | |
| async def register_cli_agent(
 | |
|     agent_data: CliAgentRegistration,
 | |
|     db: Session = Depends(get_db)
 | |
| ):
 | |
|     """Register a new CLI agent"""
 | |
|     
 | |
|     # Check if agent already exists
 | |
|     existing_agent = db.query(ORMAgent).filter(ORMAgent.id == agent_data.id).first()
 | |
|     if existing_agent:
 | |
|         raise HTTPException(status_code=400, detail=f"Agent {agent_data.id} already exists")
 | |
|     
 | |
|     try:
 | |
|         # Get CLI agent manager
 | |
|         cli_manager = get_cli_agent_manager()
 | |
|         
 | |
|         # Create CLI configuration
 | |
|         cli_config = {
 | |
|             "host": agent_data.host,
 | |
|             "node_version": agent_data.node_version,
 | |
|             "model": agent_data.model,
 | |
|             "specialization": agent_data.specialization,
 | |
|             "max_concurrent": agent_data.max_concurrent,
 | |
|             "command_timeout": agent_data.command_timeout,
 | |
|             "ssh_timeout": agent_data.ssh_timeout,
 | |
|             "agent_type": agent_data.agent_type
 | |
|         }
 | |
|         
 | |
|         # Test CLI agent connectivity before registration (optional for development)
 | |
|         health = {"cli_healthy": True, "test_skipped": True}
 | |
|         try:
 | |
|             test_agent = cli_manager.cli_factory.create_agent(f"test-{agent_data.id}", cli_config)
 | |
|             health = await test_agent.health_check()
 | |
|             await test_agent.cleanup()  # Clean up test agent
 | |
|             
 | |
|             if not health.get("cli_healthy", False):
 | |
|                 print(f"⚠️ CLI agent connectivity test failed for {agent_data.host}, but proceeding with registration")
 | |
|                 health["cli_healthy"] = False
 | |
|                 health["warning"] = f"Connectivity test failed for {agent_data.host}"
 | |
|         except Exception as e:
 | |
|             print(f"⚠️ CLI agent connectivity test error for {agent_data.host}: {e}, proceeding anyway")
 | |
|             health = {"cli_healthy": False, "error": str(e), "test_skipped": True}
 | |
|         
 | |
|         # Map specialization to Hive AgentType
 | |
|         specialization_mapping = {
 | |
|             "general_ai": AgentType.GENERAL_AI,
 | |
|             "reasoning": AgentType.REASONING,
 | |
|             "code_analysis": AgentType.PROFILER,
 | |
|             "documentation": AgentType.DOCS_WRITER,
 | |
|             "testing": AgentType.TESTER,
 | |
|             "cli_gemini": AgentType.CLI_GEMINI
 | |
|         }
 | |
|         
 | |
|         hive_specialty = specialization_mapping.get(agent_data.specialization, AgentType.GENERAL_AI)
 | |
|         
 | |
|         # Create Hive Agent object
 | |
|         hive_agent = Agent(
 | |
|             id=agent_data.id,
 | |
|             endpoint=f"cli://{agent_data.host}",
 | |
|             model=agent_data.model,
 | |
|             specialty=hive_specialty,
 | |
|             max_concurrent=agent_data.max_concurrent,
 | |
|             current_tasks=0,
 | |
|             agent_type="cli",
 | |
|             cli_config=cli_config
 | |
|         )
 | |
|         
 | |
|         # Register with Hive coordinator (this will also register with CLI manager)
 | |
|         # For now, we'll register directly in the database
 | |
|         db_agent = ORMAgent(
 | |
|             id=hive_agent.id,
 | |
|             name=f"{agent_data.host}-{agent_data.agent_type}",
 | |
|             endpoint=hive_agent.endpoint,
 | |
|             model=hive_agent.model,
 | |
|             specialty=hive_agent.specialty.value,
 | |
|             specialization=hive_agent.specialty.value,  # For compatibility
 | |
|             max_concurrent=hive_agent.max_concurrent,
 | |
|             current_tasks=hive_agent.current_tasks,
 | |
|             agent_type=hive_agent.agent_type,
 | |
|             cli_config=hive_agent.cli_config
 | |
|         )
 | |
|         
 | |
|         db.add(db_agent)
 | |
|         db.commit()
 | |
|         db.refresh(db_agent)
 | |
|         
 | |
|         # Register with CLI manager
 | |
|         cli_manager.create_cli_agent(agent_data.id, cli_config)
 | |
|         
 | |
|         return {
 | |
|             "status": "success",
 | |
|             "message": f"CLI agent {agent_data.id} registered successfully",
 | |
|             "agent_id": agent_data.id,
 | |
|             "endpoint": hive_agent.endpoint,
 | |
|             "health_check": health
 | |
|         }
 | |
|         
 | |
|     except HTTPException:
 | |
|         raise
 | |
|     except Exception as e:
 | |
|         db.rollback()
 | |
|         raise HTTPException(status_code=500, detail=f"Failed to register CLI agent: {str(e)}")
 | |
| 
 | |
| 
 | |
| @router.get("/", response_model=List[CliAgentResponse])
 | |
| async def list_cli_agents(db: Session = Depends(get_db)):
 | |
|     """List all CLI agents"""
 | |
|     
 | |
|     cli_agents = db.query(ORMAgent).filter(ORMAgent.agent_type == "cli").all()
 | |
|     
 | |
|     return [
 | |
|         CliAgentResponse(
 | |
|             id=agent.id,
 | |
|             endpoint=agent.endpoint,
 | |
|             model=agent.model,
 | |
|             specialization=agent.specialty,
 | |
|             agent_type=agent.agent_type,
 | |
|             cli_config=agent.cli_config or {},
 | |
|             status="active",  # TODO: Get actual status from CLI manager
 | |
|             max_concurrent=agent.max_concurrent,
 | |
|             current_tasks=agent.current_tasks
 | |
|         )
 | |
|         for agent in cli_agents
 | |
|     ]
 | |
| 
 | |
| 
 | |
| @router.get("/{agent_id}", response_model=CliAgentResponse)
 | |
| async def get_cli_agent(agent_id: str, db: Session = Depends(get_db)):
 | |
|     """Get details of a specific CLI agent"""
 | |
|     
 | |
|     agent = db.query(ORMAgent).filter(
 | |
|         ORMAgent.id == agent_id,
 | |
|         ORMAgent.agent_type == "cli"
 | |
|     ).first()
 | |
|     
 | |
|     if not agent:
 | |
|         raise HTTPException(status_code=404, detail=f"CLI agent {agent_id} not found")
 | |
|     
 | |
|     return CliAgentResponse(
 | |
|         id=agent.id,
 | |
|         endpoint=agent.endpoint,
 | |
|         model=agent.model,
 | |
|         specialization=agent.specialty,
 | |
|         agent_type=agent.agent_type,
 | |
|         cli_config=agent.cli_config or {},
 | |
|         status="active",  # TODO: Get actual status from CLI manager
 | |
|         max_concurrent=agent.max_concurrent,
 | |
|         current_tasks=agent.current_tasks
 | |
|     )
 | |
| 
 | |
| 
 | |
| @router.post("/{agent_id}/health-check")
 | |
| async def health_check_cli_agent(agent_id: str, db: Session = Depends(get_db)):
 | |
|     """Perform health check on a CLI agent"""
 | |
|     
 | |
|     agent = db.query(ORMAgent).filter(
 | |
|         ORMAgent.id == agent_id,
 | |
|         ORMAgent.agent_type == "cli"
 | |
|     ).first()
 | |
|     
 | |
|     if not agent:
 | |
|         raise HTTPException(status_code=404, detail=f"CLI agent {agent_id} not found")
 | |
|     
 | |
|     try:
 | |
|         cli_manager = get_cli_agent_manager()
 | |
|         cli_agent = cli_manager.get_cli_agent(agent_id)
 | |
|         
 | |
|         if not cli_agent:
 | |
|             raise HTTPException(status_code=404, detail=f"CLI agent {agent_id} not active in manager")
 | |
|         
 | |
|         health = await cli_agent.health_check()
 | |
|         return health
 | |
|         
 | |
|     except Exception as e:
 | |
|         raise HTTPException(status_code=500, detail=f"Health check failed: {str(e)}")
 | |
| 
 | |
| 
 | |
| @router.get("/statistics/all")
 | |
| async def get_all_cli_agent_statistics():
 | |
|     """Get statistics for all CLI agents"""
 | |
|     
 | |
|     try:
 | |
|         cli_manager = get_cli_agent_manager()
 | |
|         stats = cli_manager.get_agent_statistics()
 | |
|         return stats
 | |
|         
 | |
|     except Exception as e:
 | |
|         raise HTTPException(status_code=500, detail=f"Failed to get statistics: {str(e)}")
 | |
| 
 | |
| 
 | |
| @router.delete("/{agent_id}")
 | |
| async def unregister_cli_agent(agent_id: str, db: Session = Depends(get_db)):
 | |
|     """Unregister a CLI agent"""
 | |
|     
 | |
|     agent = db.query(ORMAgent).filter(
 | |
|         ORMAgent.id == agent_id,
 | |
|         ORMAgent.agent_type == "cli"
 | |
|     ).first()
 | |
|     
 | |
|     if not agent:
 | |
|         raise HTTPException(status_code=404, detail=f"CLI agent {agent_id} not found")
 | |
|     
 | |
|     try:
 | |
|         # Remove from CLI manager if it exists
 | |
|         cli_manager = get_cli_agent_manager()
 | |
|         cli_agent = cli_manager.get_cli_agent(agent_id)
 | |
|         if cli_agent:
 | |
|             await cli_agent.cleanup()
 | |
|             cli_manager.active_agents.pop(agent_id, None)
 | |
|         
 | |
|         # Remove from database
 | |
|         db.delete(agent)
 | |
|         db.commit()
 | |
|         
 | |
|         return {
 | |
|             "status": "success",
 | |
|             "message": f"CLI agent {agent_id} unregistered successfully"
 | |
|         }
 | |
|         
 | |
|     except Exception as e:
 | |
|         db.rollback()
 | |
|         raise HTTPException(status_code=500, detail=f"Failed to unregister CLI agent: {str(e)}")
 | |
| 
 | |
| 
 | |
| @router.post("/register-predefined")
 | |
| async def register_predefined_cli_agents(db: Session = Depends(get_db)):
 | |
|     """Register predefined CLI agents (walnut-gemini, ironwood-gemini)"""
 | |
|     
 | |
|     predefined_configs = [
 | |
|         {
 | |
|             "id": "550e8400-e29b-41d4-a716-446655440001",  # walnut-gemini UUID
 | |
|             "host": "walnut",
 | |
|             "node_version": "v22.14.0",
 | |
|             "model": "gemini-2.5-pro",
 | |
|             "specialization": "general_ai",
 | |
|             "max_concurrent": 2,
 | |
|             "agent_type": "gemini"
 | |
|         },
 | |
|         {
 | |
|             "id": "550e8400-e29b-41d4-a716-446655440002",  # ironwood-gemini UUID
 | |
|             "host": "ironwood", 
 | |
|             "node_version": "v22.17.0",
 | |
|             "model": "gemini-2.5-pro",
 | |
|             "specialization": "reasoning",
 | |
|             "max_concurrent": 2,
 | |
|             "agent_type": "gemini"
 | |
|         },
 | |
|         {
 | |
|             "id": "550e8400-e29b-41d4-a716-446655440003",  # rosewood-gemini UUID
 | |
|             "host": "rosewood",
 | |
|             "node_version": "v22.17.0", 
 | |
|             "model": "gemini-2.5-pro",
 | |
|             "specialization": "cli_gemini",
 | |
|             "max_concurrent": 2,
 | |
|             "agent_type": "gemini"
 | |
|         }
 | |
|     ]
 | |
|     
 | |
|     results = []
 | |
|     
 | |
|     for config in predefined_configs:
 | |
|         try:
 | |
|             # Check if already exists
 | |
|             existing = db.query(ORMAgent).filter(ORMAgent.id == config["id"]).first()
 | |
|             if existing:
 | |
|                 results.append({
 | |
|                     "agent_id": config["id"],
 | |
|                     "status": "already_exists",
 | |
|                     "message": f"Agent {config['id']} already registered"
 | |
|                 })
 | |
|                 continue
 | |
|             
 | |
|             # Register agent
 | |
|             agent_data = CliAgentRegistration(**config)
 | |
|             result = await register_cli_agent(agent_data, db)
 | |
|             results.append(result)
 | |
|             
 | |
|         except Exception as e:
 | |
|             results.append({
 | |
|                 "agent_id": config["id"],
 | |
|                 "status": "failed",
 | |
|                 "error": str(e)
 | |
|             })
 | |
|     
 | |
|     return {
 | |
|         "status": "completed",
 | |
|         "results": results
 | |
|     } |