docs: document server-side rendering pipeline, response timing middleware, and Telegram pairing API

- Add comprehensive documentation for backend content rendering in AGENTS.md, detailing the new `render_content` and `render_title` Jinja globals built on mistune with media processing, emoji shortcodes, and XSS protection
- Document the `X-Response-Time` header and bottom-left render time indicator in README.md
- Update bot token pricing documentation to clarify fallback vs gateway cost headers
- Add `email_accounts` to soft-delete tables and `idx_users_role` composite index in database schema
- Implement `telegram_pairings` and `telegram_links` table creation with column migration and indexes
- Add `/profile/{username}/telegram` endpoint to docs API with request/unpair actions
- Register `TelegramService` in main.py lifespan and add `response_timing` middleware emitting `X-Response-Time` header
- Introduce `TelegramPairForm` model and `guard_public_host_sync` synchronous host validation function
This commit is contained in:
2026-06-18 22:09:34 +00:00
parent 95dca73291
commit 6ceca3d0d4
146 changed files with 6079 additions and 392 deletions
+24 -9
View File
@@ -19,6 +19,7 @@ from devplacepy.utils import (
_user_from_session,
get_current_user,
is_admin,
is_primary_admin,
require_user,
)
from devplacepy.services.audit import record as audit
@@ -59,9 +60,10 @@ def _resolve_ws_owner(websocket: WebSocket):
user.get("username", ""),
user.get("api_key", ""),
is_admin(user),
is_primary_admin(user),
)
guest_id = websocket.cookies.get(GUEST_COOKIE) or uuid_utils.uuid7().hex
return "guest", guest_id, "guest", "", False
guest_id = websocket.cookies.get(GUEST_COOKIE) or ""
return "guest", guest_id, "guest", "", False, False
def _owner_from_request(request: Request):
@@ -157,8 +159,9 @@ async def devii_usage(request: Request):
owner_kind, owner_id, _ = _owner_from_request(request)
if svc is None:
return JSONResponse({"used_pct": 0.0, "turns_today": 0})
viewer_is_admin = is_admin(get_current_user(request))
spent = svc.spent_24h(owner_kind, owner_id) if owner_id else 0.0
limit = svc.daily_limit_for(owner_kind)
limit = svc.daily_limit_for(owner_kind, viewer_is_admin)
turns = svc.hub().ledger.turns_24h(owner_kind, owner_id) if owner_id else 0
used_pct = round(min(100.0, spent / limit * 100), 1) if limit > 0 else 0.0
payload = {
@@ -166,7 +169,7 @@ async def devii_usage(request: Request):
"turns_today": turns,
"owner_kind": owner_kind,
}
if is_admin(get_current_user(request)):
if viewer_is_admin:
payload["spent_24h"] = round(spent, 6)
payload["limit"] = limit
return JSONResponse(payload)
@@ -212,8 +215,8 @@ async def devii_ws(websocket: WebSocket):
channel = websocket.query_params.get("channel", "main")
if channel not in CHANNELS:
channel = "main"
owner_kind, owner_id, username, api_key, owner_is_admin = _resolve_ws_owner(
websocket
owner_kind, owner_id, username, api_key, owner_is_admin, owner_is_primary_admin = (
_resolve_ws_owner(websocket)
)
if owner_kind == "guest" and not svc.guests_enabled():
await websocket.send_json(
@@ -221,6 +224,12 @@ async def devii_ws(websocket: WebSocket):
)
await websocket.close(code=1008)
return
if owner_kind == "guest" and not owner_id:
await websocket.send_json(
{"type": "error", "text": "Open Devii from the page to start a session."}
)
await websocket.close(code=1008)
return
session = svc.hub().get_or_create(
owner_kind,
@@ -229,6 +238,7 @@ async def devii_ws(websocket: WebSocket):
api_key,
svc.instance_base_url(),
is_admin=owner_is_admin,
is_primary_admin=owner_is_primary_admin,
channel=channel,
)
session.attach(websocket)
@@ -248,9 +258,8 @@ async def devii_ws(websocket: WebSocket):
if command == "reset":
await session.reset()
continue
limit = svc.daily_limit_for(owner_kind, owner_is_admin)
spent = svc.spent_24h(owner_kind, owner_id)
if limit > 0 and spent >= limit:
if svc.quota_exceeded(owner_kind, owner_id, owner_is_admin):
limit = svc.daily_limit_for(owner_kind, owner_is_admin)
audit.record_system(
"ai.quota.exceeded",
actor_kind=owner_kind if owner_kind == "guest" else "user",
@@ -283,6 +292,12 @@ async def devii_ws(websocket: WebSocket):
bool(data.get("visible", True)),
bool(data.get("focused", False)),
)
elif kind == "clientinfo":
offset = data.get("offset")
session.set_clientinfo(
str(data.get("timezone", "") or "").strip(),
int(offset) if isinstance(offset, (int, float)) else None,
)
elif kind in ("avatar_result", "client_result"):
session.resolve_query(str(data.get("id", "")), data.get("result"))
except WebSocketDisconnect: