🎉 Complete CCLI Integration: Phase 4 (MCP Server Updates)

IMPLEMENTATION COMPLETE: Successfully integrated Google Gemini CLI as
mixed agent type in Hive distributed AI platform.

## Phase 4 Achievements:
 Enhanced MCP tools with CLI agent support
 Added hive_register_cli_agent, hive_get_cli_agents tools
 Updated HiveClient interface for CLI agent management
 Mixed agent type coordination via MCP
 Comprehensive error handling and user feedback

## Key Features:
- CLI agent registration with health checks
- Mixed agent dashboard (🤖 Ollama +  CLI)
- Predefined agent quick setup (walnut-gemini, ironwood-gemini)
- SSH-based task execution with connection pooling
- Complete backward compatibility

## Technical Stack:
- MCP Tools: CLI agent management interface
- HiveClient: Enhanced API client with CLI support
- TypeScript: Full type safety for mixed agent operations
- Error Handling: Comprehensive CLI connectivity validation

## Production Ready:
 16 MCP tools with CLI agent coverage
 Mixed agent type task coordination
 Health monitoring and statistics collection
 Robust SSH execution with timeout handling
 Integration tested and validated

Ready for hybrid AI orchestration: 5 Ollama + 2 CLI agents

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
anthonyrawlins
2025-07-10 12:11:27 +10:00
parent 85bf1341f3
commit 2915ee9aa7
9 changed files with 1045 additions and 59 deletions

View File

@@ -0,0 +1,317 @@
"""
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
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):
raise HTTPException(
status_code=400,
detail=f"CLI agent connectivity test failed for {agent_data.host}"
)
# 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,
endpoint=hive_agent.endpoint,
model=hive_agent.model,
specialty=hive_agent.specialty.value,
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": "walnut-gemini",
"host": "walnut",
"node_version": "v22.14.0",
"model": "gemini-2.5-pro",
"specialization": "general_ai",
"max_concurrent": 2,
"agent_type": "gemini"
},
{
"id": "ironwood-gemini",
"host": "ironwood",
"node_version": "v22.17.0",
"model": "gemini-2.5-pro",
"specialization": "reasoning",
"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
}