from fastapi import FastAPI, Depends, HTTPException from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse from sqlalchemy.orm import Session from pydantic import BaseModel from datetime import datetime, timezone from typing import Optional import json from database import get_db, init_db from models import Agent, Run app = FastAPI(title="Agent Command Center", version="1.0.0") # --- Pydantic schemas --- class AgentCreate(BaseModel): id: str name: str description: str = "" schedule: str = "manual" class AgentUpdate(BaseModel): name: Optional[str] = None description: Optional[str] = None schedule: Optional[str] = None status: Optional[str] = None class RunCreate(BaseModel): status: str = "running" output: str = "" error: str = "" metadata: dict = {} class RunUpdate(BaseModel): status: Optional[str] = None output: Optional[str] = None error: Optional[str] = None finished_at: Optional[str] = None metadata: Optional[dict] = None # --- API routes --- @app.get("/api/health") def health(): return {"status": "ok", "service": "agent-command-center"} @app.get("/api/agents") def list_agents(db: Session = Depends(get_db)): agents = db.query(Agent).all() result = [] for a in agents: last_run = db.query(Run).filter(Run.agent_id == a.id).order_by(Run.started_at.desc()).first() recent_runs = db.query(Run).filter(Run.agent_id == a.id).order_by(Run.started_at.desc()).limit(10).all() success_streak = 0 for r in recent_runs: if r.status == "success": success_streak += 1 else: break result.append({ "id": a.id, "name": a.name, "description": a.description, "schedule": a.schedule, "status": a.status, "created_at": a.created_at.isoformat() if a.created_at else None, "last_run": { "status": last_run.status, "started_at": last_run.started_at.isoformat() if last_run.started_at else None, "finished_at": last_run.finished_at.isoformat() if last_run.finished_at else None, } if last_run else None, "success_streak": success_streak, "total_runs": db.query(Run).filter(Run.agent_id == a.id).count(), }) return result @app.post("/api/agents") def create_agent(agent: AgentCreate, db: Session = Depends(get_db)): existing = db.query(Agent).filter(Agent.id == agent.id).first() if existing: raise HTTPException(status_code=409, detail="Agent already exists") new_agent = Agent( id=agent.id, name=agent.name, description=agent.description, schedule=agent.schedule, ) db.add(new_agent) db.commit() return {"id": new_agent.id, "status": "created"} @app.get("/api/agents/{agent_id}") def get_agent(agent_id: str, db: Session = Depends(get_db)): agent = db.query(Agent).filter(Agent.id == agent_id).first() if not agent: raise HTTPException(status_code=404, detail="Agent not found") runs = db.query(Run).filter(Run.agent_id == agent_id).order_by(Run.started_at.desc()).limit(50).all() return { "id": agent.id, "name": agent.name, "description": agent.description, "schedule": agent.schedule, "status": agent.status, "created_at": agent.created_at.isoformat() if agent.created_at else None, "runs": [{ "id": r.id, "started_at": r.started_at.isoformat() if r.started_at else None, "finished_at": r.finished_at.isoformat() if r.finished_at else None, "status": r.status, "output": r.output, "error": r.error, "metadata": r.metadata_, } for r in runs], } @app.put("/api/agents/{agent_id}") def update_agent(agent_id: str, update: AgentUpdate, db: Session = Depends(get_db)): agent = db.query(Agent).filter(Agent.id == agent_id).first() if not agent: raise HTTPException(status_code=404, detail="Agent not found") for field, value in update.model_dump(exclude_none=True).items(): setattr(agent, field, value) db.commit() return {"id": agent.id, "status": "updated"} @app.post("/api/agents/{agent_id}/runs") def create_run(agent_id: str, run: RunCreate, db: Session = Depends(get_db)): agent = db.query(Agent).filter(Agent.id == agent_id).first() if not agent: raise HTTPException(status_code=404, detail="Agent not found") new_run = Run( agent_id=agent_id, status=run.status, output=run.output, error=run.error, metadata_=run.metadata, ) if run.status in ("success", "failed"): new_run.finished_at = datetime.now(timezone.utc) db.add(new_run) db.commit() return {"id": new_run.id, "status": new_run.status} @app.put("/api/runs/{run_id}") def update_run(run_id: int, update: RunUpdate, db: Session = Depends(get_db)): run = db.query(Run).filter(Run.id == run_id).first() if not run: raise HTTPException(status_code=404, detail="Run not found") if update.status is not None: run.status = update.status if update.output is not None: run.output = update.output if update.error is not None: run.error = update.error if update.metadata is not None: run.metadata_ = update.metadata if update.finished_at is not None: run.finished_at = datetime.fromisoformat(update.finished_at) elif update.status in ("success", "failed"): run.finished_at = datetime.now(timezone.utc) db.commit() return {"id": run.id, "status": run.status} @app.get("/api/runs") def list_runs(limit: int = 50, db: Session = Depends(get_db)): runs = db.query(Run).order_by(Run.started_at.desc()).limit(limit).all() return [{ "id": r.id, "agent_id": r.agent_id, "started_at": r.started_at.isoformat() if r.started_at else None, "finished_at": r.finished_at.isoformat() if r.finished_at else None, "status": r.status, "output": r.output, "error": r.error, "metadata": r.metadata_, } for r in runs] # --- Static files (frontend) --- app.mount("/static", StaticFiles(directory="static"), name="static") @app.get("/") def root(): return FileResponse("static/index.html") # --- Startup --- @app.on_event("startup") def startup(): init_db()