chatgpt-bridge / api /main.py
triflix's picture
initial deploy
ed11361 verified
"""FastAPI entry point. Lifespan owns the browser session."""
from __future__ import annotations
import asyncio
import logging
import sys
from contextlib import asynccontextmanager
from pathlib import Path
import structlog
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from api.routes_auth import router as auth_router # noqa: E402
from api.routes_chat import router as chat_router # noqa: E402
from api.routes_control import router as control_router # noqa: E402
from api.routes_images import router as images_router # noqa: E402
from api.routes_models import router as models_router # noqa: E402
from api.routes_sessions import router as sessions_router # noqa: E402
from chatgpt_client.browser import SessionDeadError, session # noqa: E402
from chatgpt_client.models import registry # noqa: E402
from config import settings # noqa: E402
def _setup_logging() -> None:
logging.basicConfig(level=getattr(logging, settings.log_level.upper(), logging.INFO))
structlog.configure(
processors=[
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.add_log_level,
structlog.processors.StackInfoRenderer(),
structlog.dev.ConsoleRenderer(),
]
)
_setup_logging()
log = structlog.get_logger("chatgpt_bridge")
async def _models_refresh_loop() -> None:
while True:
await asyncio.sleep(settings.models_refresh_interval_s)
try:
await registry.refresh(session)
except Exception as e:
log.warning("models.refresh.loop_error", error=str(e))
@asynccontextmanager
async def lifespan(app: FastAPI):
try:
await session.start()
except SessionDeadError as e:
log.error("startup.session_dead", error=str(e))
yield
return
except Exception as e:
log.exception("startup.failed", error=str(e))
yield
return
await registry.refresh(session)
refresh_task = asyncio.create_task(_models_refresh_loop())
log.info("api.ready", host=settings.host, port=settings.port, models=registry.slugs)
try:
yield
finally:
refresh_task.cancel()
try:
await refresh_task
except (asyncio.CancelledError, Exception):
pass
await session.stop()
app = FastAPI(title="ChatGPT Bridge", version="0.1.0", lifespan=lifespan)
@app.middleware("http")
async def auth_middleware(request: Request, call_next):
if settings.api_key and request.url.path.startswith("/v1/"):
auth = request.headers.get("authorization", "")
token = auth.removeprefix("Bearer ").strip()
if token != settings.api_key:
return JSONResponse(status_code=401, content={"error": {"message": "Invalid API key"}})
return await call_next(request)
@app.get("/healthz")
async def healthz():
return {
"status": "ok" if session.alive else "session_dead",
"models": registry.slugs,
}
app.include_router(chat_router)
app.include_router(models_router)
app.include_router(sessions_router)
app.include_router(images_router)
app.include_router(auth_router)
app.include_router(control_router)