b4fbd6fe9f
Deploy Site / deploy-vercel (push) Has been skipped
Deploy Site / deploy-docs (push) Has been skipped
Build Skills Index / build-index (push) Has been skipped
CI / Deny unrelated histories (push) Has been skipped
CI / Detect affected areas (push) Successful in 27m35s
CI / OSV scan (push) Failing after 4s
CI / Build&Test Docker image (push) Successful in 9s
CI / Supply-chain scan (push) Has been skipped
CI / Lint Docker scripts (push) Failing after 5m13s
CI / Check contributors (push) Failing after 12m8s
CI / Docs Site (push) Failing after 12m8s
CI / TypeScript (push) Failing after 12m8s
CI / Python lints (push) Failing after 12m9s
CI / Python tests (push) Failing after 12m9s
CI / Check uv.lock (push) Failing after 23m22s
CI / CI timing report (push) Has been cancelled
Build Skills Index / trigger-deploy (push) Has been cancelled
CI / All required checks pass (push) Has been cancelled
84 lines
3.2 KiB
Python
84 lines
3.2 KiB
Python
"""HTTP routes for memory-provider OAuth connect, mounted by ``web_server``.
|
|
|
|
Kept out of ``web_server.py`` so the memory feature's surface stays in the
|
|
memory layer. Dispatch is by convention: a provider's flow lives at
|
|
``plugins.memory.<provider>.oauth_flow`` exposing ``start_loopback_flow_background``
|
|
and ``get_flow_status``; a provider without that module simply 404s. No provider
|
|
is named here.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
from contextlib import contextmanager
|
|
from typing import Optional
|
|
|
|
from fastapi import APIRouter, HTTPException
|
|
|
|
router = APIRouter(prefix="/api/memory/providers")
|
|
|
|
|
|
def _resolve_flow(provider: str):
|
|
"""Return a provider's OAuth flow module by convention, or raise 404."""
|
|
if not provider.isidentifier():
|
|
raise HTTPException(status_code=404, detail=f"unknown memory provider {provider!r}")
|
|
try:
|
|
return importlib.import_module(f"plugins.memory.{provider}.oauth_flow")
|
|
except ImportError:
|
|
raise HTTPException(status_code=404, detail=f"{provider} does not support OAuth connect")
|
|
|
|
|
|
@contextmanager
|
|
def _scope_to_profile(profile: Optional[str]):
|
|
"""Scope config resolution to ``profile`` so the flow's eager path resolve
|
|
targets that profile's honcho.json. None/""/"current" leaves it untouched."""
|
|
requested = (profile or "").strip()
|
|
if not requested or requested.lower() == "current":
|
|
yield
|
|
return
|
|
|
|
from hermes_cli import profiles as profiles_mod
|
|
from hermes_constants import reset_hermes_home_override, set_hermes_home_override
|
|
|
|
try:
|
|
profiles_mod.validate_profile_name(requested)
|
|
except ValueError as e:
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
if not profiles_mod.profile_exists(requested):
|
|
raise HTTPException(status_code=404, detail=f"Profile '{requested}' does not exist.")
|
|
|
|
token = set_hermes_home_override(str(profiles_mod.get_profile_dir(requested)))
|
|
try:
|
|
yield
|
|
finally:
|
|
reset_hermes_home_override(token)
|
|
|
|
|
|
@router.post("/{provider}/oauth/start")
|
|
async def start_memory_oauth(provider: str, profile: Optional[str] = None):
|
|
"""Begin a provider's zero-CLI OAuth flow — opens the browser and captures
|
|
the grant via the loopback listener. Returns immediately; poll status."""
|
|
flow = _resolve_flow(provider)
|
|
try:
|
|
# The flow resolves its config path eagerly inside this scope; the worker
|
|
# thread it spawns outlives the request and the override.
|
|
with _scope_to_profile(profile):
|
|
return flow.start_loopback_flow_background()
|
|
except HTTPException:
|
|
raise
|
|
except Exception as exc:
|
|
raise HTTPException(status_code=500, detail=f"Failed to start {provider} OAuth: {exc}")
|
|
|
|
|
|
@router.get("/{provider}/oauth/status")
|
|
async def memory_oauth_status(provider: str, profile: Optional[str] = None):
|
|
"""Poll a provider's OAuth flow: idle | pending | connected | error."""
|
|
flow = _resolve_flow(provider)
|
|
try:
|
|
with _scope_to_profile(profile):
|
|
return flow.get_flow_status()
|
|
except HTTPException:
|
|
raise
|
|
except Exception as exc:
|
|
raise HTTPException(status_code=500, detail=f"Failed to read {provider} OAuth status: {exc}")
|