mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-11-06 05:17:15 +00:00
101 lines
5.0 KiB
Python
101 lines
5.0 KiB
Python
"""What each launch surface of the one web-server stack may do.
|
|
|
|
The host serving the app records its surface once on ``app.state.ui_surface``; unset, it reads
|
|
as ``gateway`` on the gateway's own listener for native clients (the app has a
|
|
``gateway_runner``, as the lifespan choice reads) and ``dashboard`` elsewhere (the standalone
|
|
``hermes dashboard`` and ``hermes serve``). Every surface-dependent decision reads this table
|
|
instead of re-deriving it, and :func:`route_on` keeps a route off the surfaces that do not use
|
|
it. ``auth_required`` is decided at runtime by the auth gate, so the private-launch predicate is
|
|
a function over state, not a table field.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Any, Callable
|
|
|
|
from fastapi import HTTPException, Request
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class SurfacePolicy:
|
|
#: Mounts a browser UI; ``serve`` is JSON-RPC/WS only.
|
|
serves_spa: bool
|
|
#: Public HTML may carry the session token. Where it may not, every HTML entry
|
|
#: (``/index.html`` included) gets the token-free bootstrap instead of the raw file.
|
|
html_token: bool
|
|
#: ``/api/host-terminal`` may open host shells (still gated per request).
|
|
host_terminal: bool
|
|
#: An unauthenticated start hands the operator a private launch link.
|
|
launch_link: bool
|
|
#: ``/api/webapp/socket-ticket`` mints the browser's one-use tickets for ``/api/ws`` and the voice
|
|
#: streams on this same origin (accepted by the loopback and gated upgrade gates alike).
|
|
gateway_ticket: bool
|
|
#: Behind the sign-in gate, a write that carries the session cookie (``/auth/logout`` included)
|
|
#: must come from this listener's own origin. Elsewhere the gate keeps main's rule.
|
|
own_origin_cookie_writes: bool
|
|
#: ``/api/webapp-access/*`` serves: native Desktop's Web Access pane calls the gateway's own
|
|
#: listener (the private one, or the Webapp's when it is on).
|
|
web_access: bool
|
|
#: The browser's file routes serve: ``/api/chat/file-upload`` stages a picked file,
|
|
#: ``/api/files/ticket`` mints the path-bound tickets for downloads and media links, and chat
|
|
#: upload bodies are capped before they are parsed (``web_upload_limit``).
|
|
browser_files: bool
|
|
#: ``/api/desktop-plugins/*`` serves this host's desktop plugins to the browser's plugin loader;
|
|
#: native Desktop reads its own disk.
|
|
desktop_plugins: bool
|
|
#: ``/api/ui-preferences`` serves: the browser's door to this machine's UI preferences store
|
|
#: (native Desktop reaches the same file through its preload capability, not over HTTP).
|
|
ui_preferences: bool
|
|
#: ``/api/audio/voice-config`` may hand the client provider keys to call STT/TTS itself. A
|
|
#: browser tab cannot: a provider without CORS (the Nous tool gateway) refuses its fetch, and the
|
|
#: keys would sit in the page. It relays through the host's /api/audio/* endpoints instead.
|
|
client_direct_voice: bool
|
|
|
|
|
|
POLICIES: dict[str, SurfacePolicy] = {
|
|
"serve": SurfacePolicy(
|
|
serves_spa=False, html_token=True, host_terminal=False, launch_link=False, gateway_ticket=False,
|
|
own_origin_cookie_writes=False, web_access=False, browser_files=False,
|
|
desktop_plugins=False, ui_preferences=False, client_direct_voice=True),
|
|
"dashboard": SurfacePolicy(
|
|
serves_spa=True, html_token=True, host_terminal=False, launch_link=False, gateway_ticket=False,
|
|
own_origin_cookie_writes=False, web_access=False, browser_files=False,
|
|
desktop_plugins=False, ui_preferences=False, client_direct_voice=True),
|
|
"gateway": SurfacePolicy(
|
|
serves_spa=True, html_token=True, host_terminal=False, launch_link=False, gateway_ticket=False,
|
|
own_origin_cookie_writes=False, web_access=True, browser_files=False,
|
|
desktop_plugins=False, ui_preferences=False, client_direct_voice=True),
|
|
"webapp": SurfacePolicy(
|
|
serves_spa=True, html_token=False, host_terminal=True, launch_link=True, gateway_ticket=True,
|
|
own_origin_cookie_writes=True, web_access=True, browser_files=True,
|
|
desktop_plugins=True, ui_preferences=True, client_direct_voice=False),
|
|
}
|
|
|
|
|
|
def surface(state: Any) -> str:
|
|
"""The surface *state*'s app serves (see the module docstring)."""
|
|
recorded = getattr(state, "ui_surface", None)
|
|
if recorded:
|
|
return recorded
|
|
return "gateway" if getattr(state, "gateway_runner", None) is not None else "dashboard"
|
|
|
|
|
|
def policy(state: Any) -> SurfacePolicy:
|
|
return POLICIES[surface(state)]
|
|
|
|
|
|
def route_on(field: str) -> Callable[[Request], None]:
|
|
"""A route dependency answering 404 where the serving surface's policy leaves *field* off, so
|
|
the route does not exist there for any caller."""
|
|
def surface_serves(request: Request) -> None:
|
|
if not getattr(policy(request.app.state), field):
|
|
raise HTTPException(status_code=404, detail="Not Found")
|
|
|
|
return surface_serves
|
|
|
|
|
|
def private_launch(state: Any) -> bool:
|
|
"""Webapp on an unauthenticated bind: the launch link is the only way in."""
|
|
return policy(state).launch_link and not getattr(state, "auth_required", False)
|