rohitc1612's picture
Initial commit: vuln-patch-env OpenEnv hackathon submission
2b050c2
"""
server/app.py — OpenEnv-compatible FastAPI application.
This is the canonical app entry point expected by `openenv validate`.
The root server.py re-exports this for backwards compatibility.
"""
from fastapi import FastAPI, Request
from environment import VulnPatchEnv
app = FastAPI(
title="vuln-patch-env",
description="OpenEnv environment for code vulnerability detection and patching.",
version="1.0.0",
)
# One shared environment instance per server process (stateless reset on each call)
_env = VulnPatchEnv()
@app.get("/")
async def health_check():
return {"status": "running", "message": "vuln-patch-env OpenEnv Server is live"}
@app.get("/health")
async def health():
"""Required by openenv validate — must return {"status": "healthy"}."""
return {"status": "healthy"}
@app.get("/metadata")
async def metadata():
"""Required by openenv validate — must return name and description."""
return {
"name": "vuln-patch-env",
"description": (
"A real-world code security environment where AI agents detect "
"and patch vulnerabilities (hardcoded secrets, SQL injection, "
"command injection) in Python code."
),
"version": "1.0.0",
"tasks": ["easy", "medium", "hard"],
}
@app.get("/schema")
async def schema():
"""Required by openenv validate — must return action, observation and state schemas."""
from environment import Action, Observation
return {
"action": Action.model_json_schema(),
"observation": Observation.model_json_schema(),
"state": Observation.model_json_schema(), # state has same shape as observation
}
@app.post("/mcp")
async def mcp_endpoint(request: Request):
"""
Minimal Model Context Protocol (MCP) endpoint.
Required by openenv validate — must return a JSON-RPC 2.0 envelope.
"""
try:
body = await request.json()
except Exception:
body = {}
return {
"jsonrpc": "2.0",
"id": body.get("id", 1),
"result": {
"name": "vuln-patch-env",
"description": "OpenEnv environment for code vulnerability patching.",
"tools": ["reset", "step", "state"],
},
}
@app.post("/reset")
async def reset_endpoint(request: Request):
"""Reset the environment and return the initial observation."""
try:
body = await request.json()
task = body.get("task", "easy")
except Exception:
task = "easy"
_env.task = task
obs = _env.reset()
return {"status": "ok", "observation": obs.model_dump()}
@app.post("/step")
async def step_endpoint(request: Request):
"""Take one step in the environment."""
from environment import Action
try:
body = await request.json()
action = Action(**body)
except Exception as e:
return {"error": str(e)}, 400
obs, reward, done, info = _env.step(action)
return {
"observation": obs.model_dump(),
"reward": reward.value,
"done": done,
"info": info.model_dump(),
}
@app.get("/state")
async def state_endpoint():
"""Return the current environment state."""
return {"observation": _env.state().model_dump()}
def main():
"""Entry point for the server script (used by pyproject.toml [project.scripts])."""
import uvicorn
uvicorn.run("server.app:app", host="0.0.0.0", port=7860, reload=False)
if __name__ == "__main__":
main()