jester1177/cloudnative-devops-debug-env
0
1"""FastAPI server for the Cloud-Native DevOps Debug Environment."""2 3from pathlib import Path4from typing import Optional5 6import uvicorn7from fastapi import FastAPI, HTTPException8from fastapi.middleware.cors import CORSMiddleware9from fastapi.responses import HTMLResponse10from fastapi.staticfiles import StaticFiles11 12from server.environment import CloudNativeDebugEnvironment13from server.graders import run_grader14from server.models import (15 Action,16 BaselineRequest,17 BaselineResponse,18 EnvironmentInfo,19 GraderRequest,20 GraderResponse,21 Observation,22 ResetRequest,23 ResetResponse,24 StateResponse,25 StepRequest,26 StepResponse,27 TaskInfo,28)29from server.tasks.task_registry import TASK_REGISTRY30 31STATIC_DIR = Path(__file__).resolve().parent / "static"32 33app = FastAPI(34 title="Cloud-Native Debug Environment",35 description="OpenEnv-style environment for Docker + GitHub Actions + Kubernetes debugging",36 version="1.0.0",37)38 39app.add_middleware(40 CORSMiddleware,41 allow_origins=["*"],42 allow_credentials=True,43 allow_methods=["*"],44 allow_headers=["*"],45)46 47# Serve static assets (CSS, JS, images if needed later)48app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")49 50env: Optional[CloudNativeDebugEnvironment] = None51 52 53@app.get("/", response_class=HTMLResponse)54async def root():55 html_path = STATIC_DIR / "index.html"56 return HTMLResponse(content=html_path.read_text(encoding="utf-8"), status_code=200)57 58 59@app.get("/health")60async def health():61 return {"status": "healthy"}62 63 64@app.get("/metadata")65async def metadata():66 return {67 "name": "cloud-native-devops-env",68 "description": "Debug broken GitHub Actions workflows, Dockerfiles, and Kubernetes manifests. AI agents identify and fix cloud-native deployment pipeline issues.",69 "version": "1.0.0",70 "author": "Krishna",71 "tags": ["devops", "docker", "github-actions", "kubernetes", "debugging", "infrastructure", "cloud-native"],72 }73 74 75@app.get("/schema")76async def schema():77 return {78 "action": Action.model_json_schema(),79 "observation": Observation.model_json_schema(),80 "state": StateResponse.model_json_schema(),81 }82 83 84@app.post("/mcp")85async def mcp(request: dict = None):86 """JSON-RPC 2.0 MCP endpoint."""87 request = request or {}88 method = request.get("method", "")89 req_id = request.get("id", 1)90 91 if method == "initialize":92 return {93 "jsonrpc": "2.0",94 "id": req_id,95 "result": {96 "protocolVersion": "2024-11-05",97 "capabilities": {"tools": {}},98 "serverInfo": {"name": "cloud-native-devops-env", "version": "1.0.0"},99 },100 }101 elif method == "tools/list":102 return {103 "jsonrpc": "2.0",104 "id": req_id,105 "result": {106 "tools": [107 {108 "name": "reset",109 "description": "Reset the environment and start a new episode",110 "inputSchema": ResetRequest.model_json_schema(),111 },112 {113 "name": "step",114 "description": "Take an action in the environment",115 "inputSchema": Action.model_json_schema(),116 },117 {118 "name": "get_state",119 "description": "Get the current environment state",120 "inputSchema": {"type": "object", "properties": {}},121 },122 ]123 },124 }125 else:126 return {127 "jsonrpc": "2.0",128 "id": req_id,129 "error": {"code": -32601, "message": f"Method not found: {method}"},130 }131 132 133@app.post("/reset", response_model=ResetResponse)134async def reset(request: Optional[ResetRequest] = None):135 global env136 137 request = request or ResetRequest()138 env = CloudNativeDebugEnvironment()139 try:140 observation = env.reset(141 task_id=request.task_id,142 scenario_id=request.scenario_id,143 seed=request.seed,144 )145 except ValueError as exc:146 raise HTTPException(status_code=400, detail=str(exc)) from exc147 148 return ResetResponse(149 observation=observation,150 info={151 "task_id": env.current_task_id,152 "scenario_id": env.current_scenario_id,153 "difficulty": env.current_difficulty,154 },155 )156 157 158@app.post("/step", response_model=StepResponse)159async def step(request: StepRequest):160 global env161 162 if env is None:163 raise HTTPException(status_code=400, detail="Environment not initialized. Call /reset first.")164 165 observation, reward, done, info = env.step(request.action)166 return StepResponse(observation=observation, reward=reward, done=done, info=info)167 168 169@app.get("/state", response_model=StateResponse)170async def get_state():171 global env172 173 if env is None:174 raise HTTPException(status_code=400, detail="Environment not initialized. Call /reset first.")175 176 return StateResponse(177 observation=env.get_observation(),178 episode_reward=env.episode_reward,179 steps_taken=env.step_count,180 done=env.done,181 )182 183 184@app.get("/info", response_model=EnvironmentInfo)185async def get_info():186 tasks = [187 TaskInfo(188 id=task_id,189 name=task_cls.NAME,190 description=task_cls.DESCRIPTION,191 difficulty=task_cls.DIFFICULTY,192 num_scenarios=len(task_cls.SCENARIOS),193 )194 for task_id, task_cls in TASK_REGISTRY.items()195 ]196 return EnvironmentInfo(197 tasks=tasks,198 max_steps=10,199 action_space=Action.model_json_schema(),200 observation_space=Observation.model_json_schema(),201 )202 203 204@app.get("/tasks")205async def get_tasks():206 return {207 "tasks": [208 {209 "id": task_id,210 "name": task_cls.NAME,211 "description": task_cls.DESCRIPTION,212 "difficulty": task_cls.DIFFICULTY.value,213 }214 for task_id, task_cls in TASK_REGISTRY.items()215 ]216 }217 218 219@app.post("/grader", response_model=GraderResponse)220async def grade(request: GraderRequest):221 result = run_grader(task_id=request.task_id, trajectory=request.trajectory)222 return GraderResponse(result=result)223 224 225@app.post("/baseline", response_model=BaselineResponse)226async def run_baseline(request: Optional[BaselineRequest] = None):227 request = request or BaselineRequest()228 229 from baseline_runner import run_baseline_episodes230 231 results = run_baseline_episodes(task_id=request.task_id, num_episodes=request.num_episodes)232 aggregate = sum(r.score for r in results) / len(results) if results else 0.0233 return BaselineResponse(results=results, aggregate_score=aggregate)234 235 236def main():237 uvicorn.run(app, host="0.0.0.0", port=7860)238 239 240if __name__ == "__main__":241 main()242 