Spaces:
Sleeping
Sleeping
| import os | |
| import sys | |
| from fastapi import FastAPI, Request, BackgroundTasks, HTTPException | |
| import uvicorn | |
| from contextlib import asynccontextmanager | |
| # Import the core agent workflow we built earlier | |
| from src.main import build_and_run_graph | |
| async def lifespan(app: FastAPI): | |
| print("π Context Brain Webhook Server Starting up...") | |
| yield | |
| print("π Context Brain Webhook Server Shutting down...") | |
| app = FastAPI(title="GitLab Context Brain Webhook", lifespan=lifespan) | |
| def run_agent_safely(project_id: str, mr_iid: str): | |
| """ | |
| Wrapper to run the LangGraph pipeline. It intentionally catches SystemExit | |
| because the original main.py script uses sys.exit(1) to fail the CI Job. | |
| In a webhook server context, we don't want sys.exit() to kill the entire FastAPI server! | |
| """ | |
| print(f"\n[BACKGROUND THREAD] Starting Agent for Project {project_id} / MR {mr_iid}") | |
| try: | |
| build_and_run_graph(project_id, mr_iid) | |
| except SystemExit as e: | |
| print(f"[BACKGROUND THREAD] Agent finished logic. (Caught graceful exit code: {e.code})") | |
| except Exception as e: | |
| print(f"[BACKGROUND THREAD] Agent encountered a fatal error: {e}") | |
| finally: | |
| print(f"[BACKGROUND THREAD] Finished processing Project {project_id} / MR {mr_iid}\n") | |
| async def health_check(): | |
| return {"status": "ok", "message": "Context Brain Webhook is running!"} | |
| async def gitlab_webhook(request: Request, background_tasks: BackgroundTasks): | |
| """ | |
| GitLab will POST to this endpoint whenever an event occurs. | |
| """ | |
| # 1. Parse JSON Payload | |
| try: | |
| payload = await request.json() | |
| except Exception: | |
| raise HTTPException(status_code=400, detail="Invalid JSON Payload") | |
| # 2. Filter for Merge Request Events Only | |
| object_kind = payload.get("object_kind") | |
| if object_kind != "merge_request": | |
| return {"status": "ignored", "reason": f"Event type '{object_kind}' is not a merge request event."} | |
| # 3. Extract IDs needed for the API | |
| project = payload.get("project", {}) | |
| project_id = str(project.get("id")) | |
| attributes = payload.get("object_attributes", {}) | |
| mr_iid = str(attributes.get("iid")) | |
| action = attributes.get("action") # "open", "update", "close" | |
| if not project_id or not mr_iid: | |
| raise HTTPException(status_code=400, detail="Missing project.id or object_attributes.iid in payload") | |
| # We typically only care about new or updated MRs | |
| if action not in ["open", "update", "reopen"]: | |
| return {"status": "ignored", "reason": f"MR action '{action}' doesn't require analysis."} | |
| if action == "update" and "oldrev" not in attributes: | |
| # Ignore updates that don't change code (e.g. label changes or comment edits) | |
| # This prevents an infinite loop where the agent labels the MR, triggering itself! | |
| return {"status": "ignored", "reason": "Update event contains no code changes."} | |
| print(f"\nπ Webhook Triggered! Project: {project_id} | MR: {mr_iid} | Action: {action}") | |
| # 4. Offload heavy lifting to a background task | |
| # We must respond to GitLab immediately (within 10 seconds), otherwise it marks the webhook as failed. | |
| background_tasks.add_task(run_agent_safely, project_id, mr_iid) | |
| return { | |
| "status": "accepted", | |
| "message": f"Processing Project {project_id} MR {mr_iid} in the background." | |
| } | |
| if __name__ == "__main__": | |
| uvicorn.run("webhook:app", host="0.0.0.0", port=8000, reload=True) | |