feat(core,frontends,agents): agent kinds, frontend routing, anthropic on conversations, vault mirror
This commit is contained in:
@@ -34,7 +34,7 @@ from beaver_gateway.frontends.base import Frontend
|
||||
from beaver_gateway.storage.models import Usage
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import AsyncIterator
|
||||
from collections.abc import AsyncIterator, Mapping
|
||||
|
||||
from beaver_gateway.core.conversations import Conversations
|
||||
from beaver_gateway.frontends.base import GatewayRuntime
|
||||
@@ -48,18 +48,26 @@ SCOPE = "api"
|
||||
|
||||
|
||||
class ApiFrontend(Frontend):
|
||||
name = "api"
|
||||
kinds = ("master", "branch", "deep", "job")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
host: str = "0.0.0.0", # noqa: S104
|
||||
port: int = 8004,
|
||||
public_base_url: str | None = None,
|
||||
default_agents: Mapping[str, str] | None = None,
|
||||
) -> None:
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.public_base_url = public_base_url.rstrip("/") if public_base_url else None
|
||||
self.default_agents = dict(default_agents or {})
|
||||
self._app: FastAPI | None = None
|
||||
|
||||
def agent_for(self, kind: str) -> str | None:
|
||||
return self.default_agents.get(kind)
|
||||
|
||||
def configure(self, runtime: GatewayRuntime) -> None:
|
||||
if runtime.conversations is None or runtime.bus is None:
|
||||
msg = "ApiFrontend needs runtime.conversations and runtime.bus"
|
||||
@@ -130,6 +138,30 @@ def _build_app(runtime: GatewayRuntime) -> FastAPI: # noqa: PLR0915
|
||||
async def healthz() -> dict[str, str]:
|
||||
return {"status": "ok"}
|
||||
|
||||
@app.get("/api/agents")
|
||||
async def list_agents(request: Request) -> dict[str, Any]:
|
||||
await require_token(request, runtime, scope=SCOPE)
|
||||
return {
|
||||
"agents": [
|
||||
{
|
||||
"name": a.name,
|
||||
"model": a.model,
|
||||
"kinds": list(getattr(a, "kinds", ())),
|
||||
}
|
||||
for a in runtime.agents
|
||||
],
|
||||
"frontends": [
|
||||
{
|
||||
"name": fe.name,
|
||||
"kinds": list(fe.kinds),
|
||||
"default_agents": {
|
||||
k: fe.agent_for(k) for k in fe.kinds if fe.agent_for(k)
|
||||
},
|
||||
}
|
||||
for fe in conversations.frontends
|
||||
],
|
||||
}
|
||||
|
||||
@app.get("/api/conversations")
|
||||
async def list_conversations(request: Request) -> dict[str, Any]:
|
||||
await require_token(request, runtime, scope=SCOPE)
|
||||
@@ -149,10 +181,10 @@ def _build_app(runtime: GatewayRuntime) -> FastAPI: # noqa: PLR0915
|
||||
raise HTTPException(
|
||||
status.HTTP_400_BAD_REQUEST, f"kind must be one of {KINDS[:-1]}"
|
||||
)
|
||||
if not isinstance(agent, str) or agent not in runtime.agents:
|
||||
raise HTTPException(
|
||||
status.HTTP_400_BAD_REQUEST, "unknown or missing `agent`"
|
||||
)
|
||||
if agent is not None and (
|
||||
not isinstance(agent, str) or agent not in runtime.agents
|
||||
):
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST, "unknown `agent`")
|
||||
seed = str(data.get("seed") or "clean")
|
||||
if seed not in SEEDS:
|
||||
raise HTTPException(
|
||||
@@ -170,13 +202,13 @@ def _build_app(runtime: GatewayRuntime) -> FastAPI: # noqa: PLR0915
|
||||
window=int_or_none(data, "window"),
|
||||
origin="api",
|
||||
)
|
||||
except ValueError as exc:
|
||||
except (ValueError, LookupError) as exc:
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST, str(exc)) from exc
|
||||
await audit.log(
|
||||
runtime,
|
||||
actor=f"token:{token}",
|
||||
kind="api_spawn",
|
||||
agent_name=agent,
|
||||
agent_name=conv.agent_name,
|
||||
conversation=conv.external_id,
|
||||
seed=seed,
|
||||
)
|
||||
@@ -276,7 +308,9 @@ def _build_app(runtime: GatewayRuntime) -> FastAPI: # noqa: PLR0915
|
||||
raise HTTPException(
|
||||
status.HTTP_400_BAD_REQUEST, f"seed must be one of {SEEDS}"
|
||||
)
|
||||
agent = str(data.get("agent") or parent.agent_name)
|
||||
agent = data.get("agent")
|
||||
if agent is not None and not isinstance(agent, str):
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST, "`agent` must be a string")
|
||||
try:
|
||||
child = await conversations.spawn(
|
||||
kind="branch",
|
||||
@@ -294,7 +328,7 @@ def _build_app(runtime: GatewayRuntime) -> FastAPI: # noqa: PLR0915
|
||||
runtime,
|
||||
actor=f"token:{token}",
|
||||
kind="api_branch",
|
||||
agent_name=agent,
|
||||
agent_name=child.agent_name,
|
||||
conversation=child.external_id,
|
||||
parent=parent.external_id,
|
||||
seed=seed,
|
||||
@@ -350,12 +384,15 @@ def _build_app(runtime: GatewayRuntime) -> FastAPI: # noqa: PLR0915
|
||||
data = await body_of(request)
|
||||
frontend = text_of(data, "frontend")
|
||||
external_id = text_of(data, "external_id")
|
||||
await conversations.bind(
|
||||
conv,
|
||||
frontend=frontend,
|
||||
external_id=external_id,
|
||||
visible=bool(data.get("visible", True)),
|
||||
)
|
||||
try:
|
||||
await conversations.bind(
|
||||
conv,
|
||||
frontend=frontend,
|
||||
external_id=external_id,
|
||||
visible=bool(data.get("visible", True)),
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST, str(exc)) from exc
|
||||
return await conversations.describe(conv)
|
||||
|
||||
@app.patch("/api/conversations/{public_id}/flags")
|
||||
|
||||
Reference in New Issue
Block a user