feat: add api key auth, devii agent, openai gateway, and admin service management
This commit introduces a comprehensive set of new features including API key authentication with CLI management commands (get, reset, backfill), a Devii agentic assistant with WebSocket terminal and session bootstrap, an OpenAI-compatible LLM gateway service, and an admin service management panel. It also adds Playwright browser automation for bot support, configures internal gateway URLs, refactors content editing/deletion to support JSON API responses, and updates documentation across AGENTS.md, README.md, and the developer docs site.
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any, Callable
|
||||
|
||||
from devplacepy.database import db
|
||||
|
||||
from .agentic import LessonStore
|
||||
from .config import Settings
|
||||
from .cost.tracker import Pricing
|
||||
from .llm import LLMClient
|
||||
from .session import DeviiSession
|
||||
from .store import ConversationStore, TurnAudit, UsageLedger
|
||||
from .tasks.store import TaskStore, memory_db
|
||||
|
||||
logger = logging.getLogger("devii.hub")
|
||||
|
||||
SettingsBuilder = Callable[[str, str], "tuple[Settings, Pricing]"]
|
||||
|
||||
|
||||
class DeviiHub:
|
||||
def __init__(self, settings_builder: SettingsBuilder) -> None:
|
||||
self._build = settings_builder
|
||||
self._stores: dict[str, Any] = {
|
||||
"conversations": ConversationStore(),
|
||||
"ledger": UsageLedger(),
|
||||
"turns": TurnAudit(),
|
||||
}
|
||||
self._sessions: dict[tuple[str, str], DeviiSession] = {}
|
||||
|
||||
@property
|
||||
def ledger(self) -> UsageLedger:
|
||||
return self._stores["ledger"]
|
||||
|
||||
def get_or_create(
|
||||
self, owner_kind: str, owner_id: str, username: str, api_key: str, base_url: str
|
||||
) -> DeviiSession:
|
||||
key = (owner_kind, owner_id)
|
||||
session = self._sessions.get(key)
|
||||
if session is not None:
|
||||
return session
|
||||
settings, pricing = self._build(api_key, base_url)
|
||||
llm = LLMClient(settings)
|
||||
# Persistent, owner-isolated stores for signed-in users; ephemeral in-memory for guests.
|
||||
owned_db = db if owner_kind == "user" else memory_db()
|
||||
task_store = TaskStore(owned_db, owner_kind, owner_id)
|
||||
lessons = LessonStore(owned_db, owner_kind, owner_id)
|
||||
session = DeviiSession(
|
||||
owner_kind, owner_id, username, settings, llm, lessons, pricing, task_store, self._stores
|
||||
)
|
||||
if owner_kind == "user":
|
||||
saved = self._stores["conversations"].load(owner_kind, owner_id)
|
||||
if saved:
|
||||
session.restore_history(saved)
|
||||
self._sessions[key] = session
|
||||
logger.info("Created session %s/%s (total %d)", owner_kind, owner_id, len(self._sessions))
|
||||
return session
|
||||
|
||||
def find(self, owner_kind: str, owner_id: str) -> DeviiSession | None:
|
||||
return self._sessions.get((owner_kind, owner_id))
|
||||
|
||||
def active_sessions(self) -> int:
|
||||
return len(self._sessions)
|
||||
|
||||
def connection_count(self) -> int:
|
||||
return sum(s.connection_count for s in self._sessions.values())
|
||||
|
||||
async def gc_idle(self) -> int:
|
||||
removed = 0
|
||||
for key, session in list(self._sessions.items()):
|
||||
if session.connection_count == 0:
|
||||
await session.aclose()
|
||||
del self._sessions[key]
|
||||
removed += 1
|
||||
if removed:
|
||||
logger.info("GC removed %d idle session(s)", removed)
|
||||
return removed
|
||||
|
||||
async def aclose(self) -> None:
|
||||
for session in list(self._sessions.values()):
|
||||
await session.aclose()
|
||||
self._sessions.clear()
|
||||
Reference in New Issue
Block a user