Reusable WebSocket session management, auth, and debug API for llming applications
Project description
Real-time JS ↔ Python commands, AI-debuggable sessions, and MCP control — out of the box.
LLMing-Com connects JavaScript frontends to Python backends over WebSockets with structured commands, session management, cookie-based authentication, and a debug API that AI agents can use to inspect and control running applications.
Why?
- WS-first UI traffic --
WSRoutergives you FastAPI-style namespaced dispatch for WebSocket JSON messages. One socket carries every UI command and query. - AI controls and debugs your app -- The debug API and
@commanddecorator expose a parallel HTTP/MCP surface for AI agents and tooling, separate from the UI socket. - One decorator, one debug command -- Define a debug/admin command once with
@command; get an HTTP endpoint, JSON schema, and MCP tool for free. - Sessions just work -- Type-safe registry with TTL cleanup, WebSocket lifecycle management, and connection superseding built in.
Transport Policy
Two surfaces, two router types -- pick by audience, not by preference:
| Audience | Transport | Router | Used for |
|---|---|---|---|
| UI / app frontend | WebSocket | WSRouter |
All command and query traffic between the live frontend and backend |
| AI agents, MCP clients, ops tools | HTTP | build_command_router / build_debug_router |
Debug/admin surface: session inspection, ws_send forwarding, @command-decorated debug actions |
| Anyone | HTTP | (your own FastAPI routes) | Large or static content only -- file uploads, blob downloads, asset serving |
Do not add HTTP routes for UI commands -- those belong on WSRouter. Do not push large blobs through the WS message pipe -- those belong on plain HTTP endpoints. The @command framework is for the debug/admin surface; it is not a UI command system.
Features
- HMAC-SHA256 cookie authentication (session + identity tokens with expiry)
- Generic session registry with singleton pattern and TTL cleanup
- WebSocket transport with connection superseding and rate limiting
WSRouter-- FastAPI-style namespaced dispatch for WS messages, nestable viainclude(), auto-replies with_req_idmatching- JavaScript client with auto-reconnect, heartbeat, and session-loss detection (framework-agnostic)
- Declarative
@commandframework for the debug/admin surface, with auto-generated REST + MCP endpoints - Debug API with IP whitelisting, audit logging, and trusted proxy support
- Thread-safe in-memory data store with namespace isolation
- Mock auth system for headless and E2E testing
- MCP server (HTTP/SSE + stdio) for AI agent integration
Quick Start
UI commands (WSRouter)
Namespaced dispatch for WS JSON messages. Each module owns a WSRouter(prefix=...); assemble them into a root router and dispatch from your controller. Handlers may return a dict -- the router auto-replies on the same socket and forwards _req_id for request/response matching.
# windows.py
from llming_com import WSRouter
router = WSRouter(prefix="windows")
@router.handler("list")
async def list_windows(controller):
return {"windows": [...]}
@router.handler("focus")
async def focus(controller, window_id: str):
await controller.focus(window_id)
return {"ok": True}
# app.py -- assemble and dispatch
from llming_com import WSRouter
root = WSRouter()
root.include(router) # → windows.list, windows.focus
table = root.build_dispatch_table()
async def on_message(entry, msg):
await root.dispatch(msg["type"], msg, entry.controller, _table=table)
Debug commands (@command)
For the AI/MCP/debug surface only. Generates an HTTP REST endpoint and an MCP tool from a single declaration.
from llming_com import command, CommandScope
@command("greet", description="Greet a user", scope=CommandScope.SESSION, http_method="POST")
async def greet(controller, name: str):
await controller.send({"type": "greeting", "text": f"Hello, {name}!"})
return {"status": "sent"}
WebSocket
from fastapi import FastAPI, WebSocket
from llming_com import run_websocket_session, BaseSessionRegistry
app = FastAPI()
@app.websocket("/ws/{session_id}")
async def ws(websocket: WebSocket, session_id: str):
await run_websocket_session(
websocket, session_id, BaseSessionRegistry.get(),
on_message=lambda entry, msg: entry.controller.handle_message(msg),
)
Debug API
from llming_com import build_debug_router
app.include_router(build_debug_router(registry, api_key_env="DEBUG_KEY"))
# GET /debug/sessions
# GET /debug/sessions/{id}
# POST /debug/sessions/{id}/ws_send
JavaScript Client (auto-reconnect)
<script src="/llming-com/llming-ws.js"></script>
<script>
const ws = new LlmingWebSocket('ws://localhost:8001/ws/my-session', {
onMessage(msg) { console.log('Got:', msg); },
onReconnecting(info) { console.log(`Reconnecting ${info.attempt}/${info.maxAttempts}`); },
onSessionLost(info) { location.href = '/login'; },
});
ws.connect();
ws.send({ type: 'command', name: 'ping' });
</script>
Mount the static files from Python:
from llming_com import mount_client_static
mount_client_static(app) # serves /llming-com/llming-ws.js
Works with any JS framework (vanilla, Vue, React, Svelte, etc.). Features:
- Exponential-backoff reconnect (configurable max attempts and backoff)
- Heartbeat keepalive (15s default) with ack timeout — shows warning banner if server stops responding
- Handles llming-com close codes (4004 not-found, 4001 superseded)
- Optional built-in reconnect/warning banner (
showBanner: falseto disable) - Offline mode:
new LlmingWebSocket(url, { offline: true })makesconnect()andsend()no-ops; useful for self-contained demo bundles where every message is served by an in-page mock. Auto-detected whenwindow.__LLMING_OFFLINE__istrue. - Zero dependencies, no DOM required (works in Web Workers too)
Cookie Auth
from llming_com import get_auth
auth = get_auth()
token = auth.sign_auth_token("session-abc")
response.set_cookie(auth.auth_cookie_name, token, httponly=True, secure=True, samesite="lax")
if auth.verify_auth_cookie(request):
session_id = auth.get_auth_session_id(request)
print(f"Authenticated: {session_id}")
Always read cookie names from the AuthManager instance (auth.auth_cookie_name, auth.session_cookie_name, auth.identity_cookie_name). Don't import the module-level AUTH_COOKIE_NAME etc. — those only resolve to the default-prefix names and defeat per-app isolation.
Unified auth across pages
get_auth() returns a process-wide singleton. Every page in an app — hub, chat, admin, anything else the host mounts — that calls get_auth() receives the same AuthManager, with the same HMAC secret and the same cookie names. Cookies set on path="/" during OAuth callback are readable by every subsequent request, so a user who authenticates on the hub page can navigate to the chat page without re-login, and vice versa. Identity tokens have a 30-day TTL by default; session tokens default to 24 hours.
The flow in a typical multi-page app (e.g. a landing hub + a chat page):
- User visits
/(hub). Host'ssession_setupcallback callsget_auth().verify_identity_cookie(request). - No valid identity → host returns
None, hub callsoauth_start_handler→ OAuth provider redirect. - Provider redirects back with
?code=…. Host'soauth_callback_handlerexchanges the code, callsauth.sign_identity_token(sid), sets the identity + auth cookies. - User navigates to
/chat. Chat's session factory calls the sameget_auth().verify_identity_cookie(request), gets a valid session, builds the chat session without a second OAuth round-trip.
The host chooses cookie scope: keep path="/" so every page under the domain sees the cookies.
Per-app isolation (app_name)
When two different llming-com apps share a domain (e.g. example.com/chat and example.com/admin, deployed independently), cookie-name collisions cause cross-talk. Solve this at the top of the host by constructing the singleton with an explicit app_name:
# In your app's startup module, BEFORE any get_auth() call:
from llming_com.auth import AuthManager
import llming_com.auth as _auth_mod
_auth_mod._default_instance = AuthManager(app_name="myapp")
Once that's done, every subsequent get_auth() in any page returns the app-named manager. Cookies become myapp_auth / myapp_session / myapp_identity, cleanly separated from a sibling app using otherapp_*. Pages don't change — they already read names from the instance.
Project Structure
llming_com/ Core library (auth, session, transport, commands, debug, data store)
llming_com/static/ JavaScript client (LlmingWebSocket)
tests/ Pytest suite
samples/ Example applications (run with: LLMING_AUTH_SECRET=demo python samples/demo_app.py)
docs/ Documentation and assets
Development
git clone https://github.com/Alyxion/llming-com.git
cd llming-com
poetry install
LLMING_AUTH_SECRET=dev-secret pytest tests/ -q
License
MIT -- Copyright 2026 Michael Ikemann
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file llming_com-0.1.3.tar.gz.
File metadata
- Download URL: llming_com-0.1.3.tar.gz
- Upload date:
- Size: 36.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.1.1 CPython/3.13.11 Darwin/25.2.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5507c661b349dc1f309277fd43b39b73be0b56a3d05b6636ffd18cb3769b3937
|
|
| MD5 |
27c75d68cd912fa27a3808a3d26262a1
|
|
| BLAKE2b-256 |
df085715ca49c33654186eea9a3445231e225e5ff88c100238631e4d62ad7f8d
|
File details
Details for the file llming_com-0.1.3-py3-none-any.whl.
File metadata
- Download URL: llming_com-0.1.3-py3-none-any.whl
- Upload date:
- Size: 42.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.1.1 CPython/3.13.11 Darwin/25.2.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bb02c124f52ad14ad59352b732915409aa92c88e90b098acaa758afabdaee1dd
|
|
| MD5 |
346bfe25f933f2142345c4b3c150fd95
|
|
| BLAKE2b-256 |
56062d23e07a7799942edebe91c7ca230590fd46235ab43682da3221db5ef052
|