Files
hive/backend/app/api/cli_agents.py
anthonyrawlins f3cbb5c6f7 Add environment configuration and local development documentation
- 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>
2025-07-10 18:20:52 +10:00

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
}