diff --git a/backend/apps/subscriptions/NineRouter/NineRouterClient.py b/backend/apps/subscriptions/NineRouter/NineRouterClient.py index e097c8f2..bf7ccca2 100644 --- a/backend/apps/subscriptions/NineRouter/NineRouterClient.py +++ b/backend/apps/subscriptions/NineRouter/NineRouterClient.py @@ -1,31 +1,11 @@ """HTTP client for 9Router's REST API.""" -import logging - import httpx from typeguard import typechecked +from backend.apps.subscriptions.NineRouter.constants import NINE_ROUTER_API, NINE_ROUTER_V1 from backend.ports import NINE_ROUTER_PORT -logger = logging.getLogger(__name__) - -NINE_ROUTER_URL: str = f"http://localhost:{NINE_ROUTER_PORT}" -NINE_ROUTER_API: str = f"{NINE_ROUTER_URL}/api" -NINE_ROUTER_V1: str = f"{NINE_ROUTER_URL}/v1" - - -@typechecked -async def get_usage_stats(period: str = "all") -> dict | None: - 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 - - @typechecked async def get_providers() -> list[dict] | dict: try: @@ -34,7 +14,7 @@ async def get_providers() -> list[dict] | dict: if r.status_code == 200: return r.json() except Exception as e: - logger.debug(f"9Router providers fetch failed: {e}") + print(f"9Router providers fetch failed: {e}") return [] @@ -110,10 +90,10 @@ async def exchange_oauth( "codeVerifier": code_verifier, "state": state, } - logger.info(f"exchange_oauth: provider={provider} redirect_uri={redirect_uri}") + print(f"exchange_oauth: provider={provider} redirect_uri={redirect_uri}") async with httpx.AsyncClient(timeout=15.0) as client: r = await client.post(f"{NINE_ROUTER_API}/oauth/{provider}/exchange", json=payload) - logger.info(f"exchange_oauth: status={r.status_code}") + print(f"exchange_oauth: status={r.status_code}") r.raise_for_status() return r.json() @@ -136,5 +116,5 @@ async def get_models() -> list[dict]: for m in models ] except Exception as e: - logger.debug(f"9Router models fetch failed: {e}") + print(f"9Router models fetch failed: {e}") return [] diff --git a/backend/apps/subscriptions/NineRouter/NineRouterProcess.py b/backend/apps/subscriptions/NineRouter/NineRouterProcess.py index e7cf995b..b336a777 100644 --- a/backend/apps/subscriptions/NineRouter/NineRouterProcess.py +++ b/backend/apps/subscriptions/NineRouter/NineRouterProcess.py @@ -5,7 +5,6 @@ Claude/ChatGPT/Gemini subscriptions to OpenSwarm without API keys. """ import asyncio -import logging import os import shutil import subprocess @@ -15,14 +14,10 @@ import httpx from typeguard import typechecked from backend.ports import NINE_ROUTER_PORT +from backend.apps.subscriptions.NineRouter.constants import NINE_ROUTER_V1, NINE_ROUTER_URL -logger = logging.getLogger(__name__) - -NINE_ROUTER_URL: str = f"http://localhost:{NINE_ROUTER_PORT}" -NINE_ROUTER_V1: str = f"{NINE_ROUTER_URL}/v1" - -_process: subprocess.Popen | None = None -_THIS_DIR: str = os.path.dirname(os.path.abspath(__file__)) +P_PROCESS: subprocess.Popen | None = None +P_THIS_DIR: str = os.path.dirname(os.path.abspath(__file__)) def _forward_output(pipe) -> None: @@ -55,12 +50,12 @@ def _find_9router_dir() -> str | None: _is_packaged: bool = os.environ.get("OPENSWARM_PACKAGED") == "1" if _is_packaged: - _resources: str = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(_THIS_DIR)))) + _resources: str = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(P_THIS_DIR)))) _candidate: str = os.path.join(_resources, "9router") if os.path.isdir(_candidate): return _candidate else: - _backend_dir: str = os.path.dirname(os.path.dirname(os.path.dirname(_THIS_DIR))) + _backend_dir: str = os.path.dirname(os.path.dirname(os.path.dirname(P_THIS_DIR))) _project_root: str = os.path.dirname(_backend_dir) _candidate = os.path.join(_project_root, "9router") if os.path.isdir(_candidate): @@ -83,7 +78,7 @@ def _find_node() -> str | None: @typechecked async def ensure_running() -> None: """Start 9Router if not already running.""" - global _process + global P_PROCESS _is_packaged: bool = os.environ.get("OPENSWARM_PACKAGED") == "1" if is_running(): @@ -173,14 +168,14 @@ async def ensure_running() -> None: } try: - _process = subprocess.Popen( + P_PROCESS = subprocess.Popen( cmd, cwd=cwd, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env, ) - threading.Thread(target=_forward_output, args=(_process.stdout,), daemon=True).start() + threading.Thread(target=_forward_output, args=(P_PROCESS.stdout,), daemon=True).start() timeout: int = 20 if _is_packaged else 30 for _ in range(timeout * 2): @@ -196,15 +191,15 @@ async def ensure_running() -> None: @typechecked def stop() -> None: - global _process - if _process: + global P_PROCESS + if P_PROCESS: try: - _process.terminate() - _process.wait(timeout=5) + P_PROCESS.terminate() + P_PROCESS.wait(timeout=5) except Exception: try: - _process.kill() + P_PROCESS.kill() except Exception: pass - _process = None - logger.info("9Router stopped") + P_PROCESS = None + print("9Router stopped") diff --git a/backend/apps/subscriptions/NineRouter/constants.py b/backend/apps/subscriptions/NineRouter/constants.py new file mode 100644 index 00000000..d2734833 --- /dev/null +++ b/backend/apps/subscriptions/NineRouter/constants.py @@ -0,0 +1,5 @@ +from backend.ports import NINE_ROUTER_PORT + +NINE_ROUTER_URL: str = f"http://localhost:{NINE_ROUTER_PORT}" +NINE_ROUTER_API: str = f"{NINE_ROUTER_URL}/api" +NINE_ROUTER_V1: str = f"{NINE_ROUTER_URL}/v1" \ No newline at end of file diff --git a/backend/apps/subscriptions/html_constants.py b/backend/apps/subscriptions/html_constants.py new file mode 100644 index 00000000..d57ec047 --- /dev/null +++ b/backend/apps/subscriptions/html_constants.py @@ -0,0 +1,23 @@ + + +SUCCESS_HTML: str = ( + '' + '
' + '
' + '

Connected!

' + '

You can close this window

' + '
' + '' + '' +) + +ERROR_STYLE: str = ( + 'style="background:#1a1a1a;color:#fff;display:flex;align-items:center;' + 'justify-content:center;height:100vh;font-family:sans-serif"' +) + diff --git a/backend/apps/subscriptions/subscriptions.py b/backend/apps/subscriptions/subscriptions.py index 90a067e1..1ff207bb 100644 --- a/backend/apps/subscriptions/subscriptions.py +++ b/backend/apps/subscriptions/subscriptions.py @@ -6,7 +6,6 @@ for the frontend to connect/disconnect subscription providers """ import asyncio -import logging import httpx from contextlib import asynccontextmanager @@ -23,12 +22,11 @@ from backend.apps.subscriptions.NineRouter.NineRouterClient import ( get_providers, get_models, start_oauth, poll_oauth, exchange_oauth, NINE_ROUTER_API, ) +from backend.apps.subscriptions.html_constants import SUCCESS_HTML, ERROR_STYLE +from typing import Dict -logger = logging.getLogger(__name__) - -_pending_oauth: dict[str, dict] = {} -_ensure_task: Optional[asyncio.Task] = None - +P_PENDING_OAUTH: Dict[str, dict] = {} +P_ENSURE_TASK: Optional[asyncio.Task] = None # --------------------------------------------------------------------------- # Lifespan @@ -39,11 +37,11 @@ async def subscriptions_lifespan(): try: await ensure_running() except Exception as e: - logger.warning(f"9Router auto-start failed: {e}") + print(f"9Router auto-start failed: {e}") yield - global _ensure_task - if _ensure_task and not _ensure_task.done(): - _ensure_task.cancel() + global P_ENSURE_TASK + if P_ENSURE_TASK and not P_ENSURE_TASK.done(): + P_ENSURE_TASK.cancel() try: stop() except Exception: @@ -59,10 +57,10 @@ subscriptions = SubApp("subscriptions", subscriptions_lifespan) @subscriptions.router.get("/status") async def subscriptions_status() -> dict: - global _ensure_task + global P_ENSURE_TASK if not is_running(): - if _ensure_task is None or _ensure_task.done(): - _ensure_task = asyncio.create_task(ensure_running()) + if P_ENSURE_TASK is None or P_ENSURE_TASK.done(): + P_ENSURE_TASK = asyncio.create_task(ensure_running()) return {"running": False, "providers": [], "models": []} providers = await get_providers() models = await get_models() @@ -83,7 +81,7 @@ async def subscriptions_connect(body: dict) -> dict: try: result: dict = await start_oauth(provider) if result.get("flow") == "authorization_code" and result.get("state"): - _pending_oauth[result["state"]] = { + P_PENDING_OAUTH[result["state"]] = { "provider": provider, "code_verifier": result.get("code_verifier", ""), "redirect_uri": result.get("redirect_uri", ""), @@ -132,7 +130,7 @@ async def subscriptions_disconnect(body: dict) -> dict: @subscriptions.router.get("/pending/{state}") async def subscriptions_pending(state: str): - pending: Optional[dict] = _pending_oauth.get(state) + pending: Optional[dict] = P_PENDING_OAUTH.get(state) if not pending: return JSONResponse( {"error": "not found"}, status_code=404, @@ -145,28 +143,6 @@ async def subscriptions_pending(state: str): }, headers={"Access-Control-Allow-Origin": "*"}) -_SUCCESS_HTML: str = ( - '' - '
' - '
' - '

Connected!

' - '

You can close this window

' - '
' - '' - '' -) - -_ERROR_STYLE: str = ( - 'style="background:#1a1a1a;color:#fff;display:flex;align-items:center;' - 'justify-content:center;height:100vh;font-family:sans-serif"' -) - - @subscriptions.router.get("/callback") async def subscriptions_callback(request: Request): code: str = request.query_params.get("code", "") @@ -176,14 +152,14 @@ async def subscriptions_callback(request: Request): if error: desc: str = request.query_params.get("error_description", error) return HTMLResponse( - f'
' + f'
' f'

Authorization failed

{desc}

' ) - pending: Optional[dict] = _pending_oauth.pop(state, None) + pending: Optional[dict] = P_PENDING_OAUTH.pop(state, None) if not pending: return HTMLResponse( - f'
' + f'
' f'

Session expired

Please try connecting again.

' ) @@ -193,10 +169,10 @@ async def subscriptions_callback(request: Request): pending["redirect_uri"], pending["code_verifier"], state, ) except Exception as e: - logger.error(f"OAuth callback: exchange failed for provider={pending['provider']}: {e}") + print(f"OAuth callback: exchange failed for provider={pending['provider']}: {e}") return HTMLResponse( - f'
' + f'
' f'

Connection failed

{e}

' ) - return HTMLResponse(_SUCCESS_HTML) + return HTMLResponse(SUCCESS_HTML)