"""HTTP API proxy — call 9Router's REST API from OpenSwarm.""" import logging import httpx from backend.ports import NINE_ROUTER_PORT logger = logging.getLogger(__name__) NINE_ROUTER_URL = f"http://localhost:{NINE_ROUTER_PORT}" NINE_ROUTER_API = f"{NINE_ROUTER_URL}/api" NINE_ROUTER_V1 = f"{NINE_ROUTER_URL}/v1" async def get_usage_stats(period: str = "all") -> dict | None: """Get usage statistics from 9Router.""" try: async with httpx.AsyncClient(timeout=5.0) as client: r = await client.get(f"{NINE_ROUTER_API}/usage/stats", params={"period": period}) if r.status_code == 200: return r.json() except Exception as e: logger.debug(f"9Router usage stats fetch failed: {e}") return None async def get_providers() -> list[dict]: """Get all providers and their connection status from 9Router.""" try: async with httpx.AsyncClient(timeout=5.0) as client: r = await client.get(f"{NINE_ROUTER_API}/providers") if r.status_code == 200: return r.json() except Exception as e: logger.debug(f"9Router providers fetch failed: {e}") return [] async def start_oauth(provider: str) -> dict: """Start OAuth flow for a provider. For device_code providers (github, qwen, kiro): returns {user_code, verification_uri, device_code} For authorization_code providers (claude, codex, gemini-cli): returns {authUrl, codeVerifier, state} """ async with httpx.AsyncClient(timeout=15.0) as client: try: r = await client.get(f"{NINE_ROUTER_API}/oauth/{provider}/device-code") if r.status_code == 200: data = r.json() return { "flow": "device_code", "user_code": data.get("user_code", ""), "verification_uri": data.get("verification_uri", data.get("verification_uri_complete", "")), "device_code": data.get("device_code", ""), "code_verifier": data.get("codeVerifier", ""), "extra_data": {k: v for k, v in data.items() if k.startswith("_")}, } except Exception: pass callback_url = f"http://localhost:{NINE_ROUTER_PORT}/callback" r = await client.get( f"{NINE_ROUTER_API}/oauth/{provider}/authorize", params={"redirect_uri": callback_url}, ) r.raise_for_status() data = r.json() redirect_uri = callback_url return { "flow": "authorization_code", "auth_url": data.get("authUrl", ""), "code_verifier": data.get("codeVerifier", ""), "state": data.get("state", ""), "redirect_uri": redirect_uri, } async def poll_oauth( provider: str, device_code: str, code_verifier: str | None = None, extra_data: dict | None = None, ) -> dict: """Poll for OAuth completion. Returns: {success: true, connection: {...}} or {success: false, pending: true} """ body: dict = {"deviceCode": device_code} if code_verifier: body["codeVerifier"] = code_verifier if extra_data: body["extraData"] = extra_data async with httpx.AsyncClient(timeout=15.0) as client: r = await client.post( f"{NINE_ROUTER_API}/oauth/{provider}/poll", json=body, ) r.raise_for_status() return r.json() async def exchange_oauth( provider: str, code: str, redirect_uri: str, code_verifier: str, state: str = "", ) -> dict: """Exchange OAuth code for tokens via 9Router.""" url = f"{NINE_ROUTER_API}/oauth/{provider}/exchange" payload = { "code": code, "redirectUri": redirect_uri, "codeVerifier": code_verifier, "state": state, } logger.info(f"exchange_oauth: POST {url} provider={provider} redirect_uri={redirect_uri}") async with httpx.AsyncClient(timeout=15.0) as client: r = await client.post(url, json=payload) logger.info(f"exchange_oauth: status={r.status_code}") r.raise_for_status() return r.json() async def get_models() -> list[dict]: """Get all available models from 9Router.""" try: async with httpx.AsyncClient(timeout=5.0) as client: r = await client.get(f"{NINE_ROUTER_V1}/models") if r.status_code == 200: data = r.json() models = data.get("data", []) return [ { "value": m.get("id", ""), "label": m.get("id", "").split("/")[-1] if "/" in m.get("id", "") else m.get("id", ""), "context_window": 200_000, "provider": m.get("owned_by", "subscription"), } for m in models ] except Exception as e: logger.debug(f"9Router models fetch failed: {e}") return []