feat: one gateway port with path-mounted frontends, markdown chat, collapsible sidebars

This commit is contained in:
hh
2026-08-29 00:49:06 +02:00
parent 540327efa1
commit 16b6bbddda
32 changed files with 691 additions and 441 deletions
+28 -9
View File
@@ -1,11 +1,13 @@
"""Frontend ABC + the runtime context handed to ``configure``.
A frontend is anything that listens on a port and routes inbound traffic
into the gateway. ``GatewayRuntime`` carries everything a frontend may
need that isn't user-config: built registries, per-agent backends, and
the in-memory token store. The user's ``/config/config.py`` defines a
``Gateway`` (lists); ``cli.main`` turns that into a ``GatewayRuntime``
and hands it to each frontend's ``configure``.
A frontend is anything that routes inbound traffic into the gateway: an
HTTP surface mounted under its ``path`` on the single gateway port
(``frontends/root.py``), a poller (Telegram), or both. ``GatewayRuntime``
carries everything a frontend may need that isn't user-config: built
registries, per-agent backends, and the in-memory token store. The
user's ``/config/config.py`` defines a ``Gateway`` (lists); ``cli.main``
turns that into a ``GatewayRuntime`` and hands it to each frontend's
``configure``.
"""
from __future__ import annotations
@@ -17,6 +19,8 @@ from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from collections.abc import Awaitable, Callable, Mapping, Sequence
from starlette.types import ASGIApp
from beaver_gateway.backends.base import Backend
from beaver_gateway.core.auth import TokenStore
from beaver_gateway.core.kinds import Kind
@@ -85,10 +89,20 @@ class GatewayRuntime:
conversations: Any = None
bus: Any = None
pool: Any = None
# External origin the reverse proxy puts in front of the gateway
# (``Gateway.public_url``); ``None`` means "derive from the request".
public_url: str | None = None
class Frontend(ABC):
"""Listens on a port, dispatches into the gateway.
"""Routes inbound traffic into the gateway.
HTTP frontends set ``path`` and return their ASGI app from ``app()``;
``cli`` mounts every such app under that path on the one gateway
port, so ``/anthropic/v1/messages`` reaches the Anthropic frontend's
``/v1/messages``. ``serve()`` is for work outside HTTP - polling,
vault mirrors - and defaults to nothing. ``landing`` marks the app
that ``/`` redirects to (the admin console).
A frontend that shows conversations declares ``name`` (the binding
key) and ``kinds`` (which conversation kinds it shows);
@@ -103,12 +117,17 @@ class Frontend(ABC):
name: str = ""
kinds: tuple[Kind, ...] = ()
path: str | None = None
landing: bool = False
@abstractmethod
def configure(self, runtime: GatewayRuntime) -> None: ...
@abstractmethod
async def serve(self) -> None: ...
def app(self) -> ASGIApp | None:
return None
async def serve(self) -> None:
return
def agent_for(self, kind: Kind) -> str | None: # noqa: ARG002
return None