import logging import os from contextlib import asynccontextmanager import httpx from dotenv import load_dotenv from fastapi import FastAPI, HTTPException from agents.backend_dev.BackendDeveloperAgent import BackendDeveloperAgent from agents.backend_dev.context_builder import YouTrackContextBuilder from common.llm_client import LLMClient from common.youtrack_mcp_client import YouTrackMCPClient, IssueNotFound logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) load_dotenv() def env(key: str) -> str: value = os.environ.get(key) if not value: raise RuntimeError(f"Missing required env var: {key}") return value @asynccontextmanager async def lifespan(app: FastAPI): mcp = await YouTrackMCPClient( str(os.getenv('YOUTRACK_MCP_SERVER')), str(os.getenv('YOUTRACK_MCP_TOKEN')), ).connect() http_for_attachments = httpx.AsyncClient( headers={"Authorization": f"Bearer {env('YOUTRACK_MCP_TOKEN')}"}, proxy=env("HTTPS_PROXY"), timeout=httpx.Timeout(30.0, connect=10.0, read=60.0), follow_redirects=True, ) app.state.backend_agent = BackendDeveloperAgent( YouTrackContextBuilder(mcp, http_for_attachments), LLMClient( base_url=env("LLM_ADDRESS"), api_key=env("LLM_API_KEY"), model=env("LLM_MODEL"), ), ) yield # ---- shutdown ---- await mcp.close() app = FastAPI(lifespan=lifespan) @app.get("/") async def root(): return {"message": "Hello World"} @app.get("/hello/{name}") async def say_hello(name: str): return {"message": f"Hello {name}"} @app.get("/backend") async def backend_task(task: str): agent: BackendDeveloperAgent = app.state.backend_agent try: logger.info(f"Received task: {task}") plan = await agent.plan_issue(task) logger.info("Task planning successful.") print(agent.render(plan)) return {"plan": plan} except IssueNotFound: logger.error(f"Issue {task} not found.") raise HTTPException(status_code=404, detail=f"Issue {task} not found") except Exception as e: logger.exception("An unexpected error occurred") return {"error": str(e)}