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
@@ -25,7 +25,21 @@ def _now() -> datetime:
def _iso(moment: datetime) -> str:
return moment.isoformat()
return moment.isoformat(timespec="microseconds")
def _hours_since(iso_value: Optional[str], now: datetime) -> float:
try:
moment = datetime.fromisoformat(iso_value)
except (TypeError, ValueError):
return 0.0
return max(0.0, (now - moment).total_seconds() / 3600.0)
def _project_monthly(cost: float, observed_hours: float) -> float:
if observed_hours >= 1.0:
return cost / observed_hours * 24 * 30
return cost * 30
def _pset(values: list[float]) -> dict:
@@ -163,6 +177,11 @@ def build_analytics(
first_hour[owner] = bucket
total_cost = sum(float(r.get("cost_usd") or 0) for r in rows)
token_cost = sum(
float(r.get("cost_usd") or 0)
for r in rows
if int(r.get("total_tokens") or 0) > 0
)
input_cost = sum(float(r.get("input_cost_usd") or 0) for r in rows)
output_cost = sum(float(r.get("output_cost_usd") or 0) for r in rows)
cost_this_hour = sum(
@@ -170,8 +189,12 @@ def build_analytics(
for r in rows
if r["created_at"][:13] == hour_start
)
cost_24h = sum(
float(r.get("cost_usd") or 0) for r in rows if r["created_at"] >= day_cutoff
last_24h_rows = [r for r in rows if r["created_at"] >= day_cutoff]
cost_24h = sum(float(r.get("cost_usd") or 0) for r in last_24h_rows)
observed_24h_hours = (
min(24.0, _hours_since(last_24h_rows[0]["created_at"], now))
if last_24h_rows
else 0.0
)
prompt_total = sum(int(r.get("prompt_tokens") or 0) for r in rows)
@@ -187,7 +210,7 @@ def build_analytics(
chat_cache_hits = sum(
int(r.get("cache_hit_tokens") or 0)
for r in rows
if r.get("backend") == "chat"
if r.get("backend") == "chat" and not int(r.get("native_cost") or 0)
)
caching_savings = chat_cache_hits / PER_MILLION * rate_delta
@@ -308,8 +331,8 @@ def build_analytics(
"output_usd": round(output_cost, 6),
"this_hour_usd": round(cost_this_hour, 6),
"last_24h_usd": round(cost_24h, 6),
"projected_monthly_usd": round(cost_24h * 30, 2),
"effective_per_1k_tokens_usd": round(total_cost / total_tokens * 1000, 6)
"projected_monthly_usd": round(_project_monthly(cost_24h, observed_24h_hours), 2),
"effective_per_1k_tokens_usd": round(token_cost / total_tokens * 1000, 6)
if total_tokens
else 0.0,
"caching_savings_usd": round(caching_savings, 6),
@@ -509,7 +532,9 @@ def build_user_usage(
avg_latency = round(sum(latencies) / len(latencies), 1) if latencies else 0.0
tps = _positive(rows, "tokens_per_second")
avg_tps = round(sum(tps) / len(tps), 2) if tps else 0.0
cost_per_hour = total_cost / hours
observed_hours = min(float(hours), _hours_since(rows[0]["created_at"], now))
rate_hours = observed_hours if observed_hours >= 1.0 else float(hours)
cost_per_hour = total_cost / rate_hours
cost_per_request = total_cost / requests if requests else 0.0
return {
@@ -539,7 +564,9 @@ def build_user_usage(
"by_backend": _top_group(rows, lambda r: r.get("backend") or "unknown", 0),
"hourly": _user_hourly(rows),
"notes": {
"projection": "30-day projection extrapolates the full 24h spend (24h cost x 30)"
"projection": "30-day projection from the observed hourly burn rate "
"(per-hour spend x 24 x 30); windows with under an hour of activity fall "
"back to a conservative window-average to avoid over-extrapolation"
},
}
+25 -9
View File
@@ -225,6 +225,7 @@ class GatewayRuntime:
handle_start = time.monotonic()
messages = body.get("messages", []) or []
vision_cost = 0.0
if cfg["gateway_vision_enabled"]:
augmenter = VisionAugmenter(
cfg["gateway_vision_url"],
@@ -238,6 +239,7 @@ class GatewayRuntime:
)
messages = await augmenter.augment_messages(client, messages)
self.vision_calls += augmenter.calls
vision_cost = augmenter.cost_usd
messages = apply_system_directives(messages, cfg.get("gateway_system_preamble", ""))
@@ -300,9 +302,13 @@ class GatewayRuntime:
base["success"] = success
base["error_category"] = category
base["usage"] = usage
return usage_response_headers(
self._ledger.record(base, pricing, context_map)
)
row = self._ledger.record(base, pricing, context_map)
headers = usage_response_headers(row)
if vision_cost and headers:
chat_cost = float((row or {}).get("cost_usd") or 0.0)
headers["X-Gateway-Cost-USD"] = f"{chat_cost + vision_cost:.8f}"
headers["X-Gateway-Vision-Cost-USD"] = f"{vision_cost:.8f}"
return headers
if timing["circuit_open"]:
resp_headers = finalize(503, False, "circuit_open")
@@ -370,6 +376,7 @@ class GatewayRuntime:
self, body: dict, cfg: dict, owner: tuple, user_agent: str, log=None
):
log = log or (lambda message: None)
vision_cost = 0.0
overlay = embed_overlay(body.get("model"), cfg)
if overlay:
cfg = {**cfg, **overlay}
@@ -465,9 +472,13 @@ class GatewayRuntime:
base["success"] = success
base["error_category"] = category
base["usage"] = usage
return usage_response_headers(
self._ledger.record(base, pricing, context_map)
)
row = self._ledger.record(base, pricing, context_map)
headers = usage_response_headers(row)
if vision_cost and headers:
chat_cost = float((row or {}).get("cost_usd") or 0.0)
headers["X-Gateway-Cost-USD"] = f"{chat_cost + vision_cost:.8f}"
headers["X-Gateway-Vision-Cost-USD"] = f"{vision_cost:.8f}"
return headers
if timing["circuit_open"]:
resp_headers = finalize(503, False, "circuit_open")
@@ -538,6 +549,7 @@ class GatewayRuntime:
log=None,
):
log = log or (lambda message: None)
vision_cost = 0.0
client, sem = self._ensure(cfg)
pricing = pricing_from_cfg(cfg)
context_map = parse_context_map(cfg.get("gateway_model_context_map"))
@@ -583,9 +595,13 @@ class GatewayRuntime:
base["success"] = success
base["error_category"] = category
base["usage"] = usage
return usage_response_headers(
self._ledger.record(base, pricing, context_map)
)
row = self._ledger.record(base, pricing, context_map)
headers = usage_response_headers(row)
if vision_cost and headers:
chat_cost = float((row or {}).get("cost_usd") or 0.0)
headers["X-Gateway-Cost-USD"] = f"{chat_cost + vision_cost:.8f}"
headers["X-Gateway-Vision-Cost-USD"] = f"{vision_cost:.8f}"
return headers
if timing["circuit_open"]:
resp_headers = finalize(503, False, "circuit_open")
@@ -354,6 +354,7 @@ def chat_overlay(requested_model: Optional[str], base_cfg: dict) -> Optional[dic
overlay["gateway_vision_enabled"] = True
overlay["gateway_vision_model"] = route.vision_model
overlay["gateway_vision_price_input_per_m"] = route.price_input_per_m
overlay["gateway_vision_price_output_per_m"] = route.price_output_per_m
vision_provider = route.vision_provider or route.provider
if vision_provider:
_provider_overlay(
+50 -15
View File
@@ -25,7 +25,7 @@ def _now() -> datetime:
def _iso(moment: datetime) -> str:
return moment.isoformat()
return moment.isoformat(timespec="microseconds")
@dataclass(frozen=True)
@@ -120,24 +120,30 @@ def normalize_usage(usage: Optional[dict]) -> dict:
def compute_cost(
usage: dict, norm: dict, pricing: Pricing, backend: str
) -> tuple[float, float, float, bool]:
native = usage.get("cost") if isinstance(usage, dict) else None
if isinstance(native, (int, float)) and not isinstance(native, bool):
total = float(native)
denom = norm["prompt"] + norm["completion"]
input_cost = total * norm["prompt"] / denom if denom > 0 else 0.0
return total, input_cost, total - input_cost, True
if backend == "vision":
input_cost = norm["prompt"] / PER_MILLION * pricing.vision_input_per_m
output_cost = norm["completion"] / PER_MILLION * pricing.vision_output_per_m
return input_cost + output_cost, input_cost, output_cost, False
if backend == "embed":
elif backend == "embed":
input_cost = norm["prompt"] / PER_MILLION * pricing.embed_input_per_m
return input_cost, input_cost, 0.0, False
input_cost = (
norm["cache_hit"] / PER_MILLION * pricing.chat_cache_hit_per_m
+ norm["cache_miss"] / PER_MILLION * pricing.chat_cache_miss_per_m
)
output_cost = norm["completion"] / PER_MILLION * pricing.chat_output_per_m
output_cost = 0.0
else:
input_cost = (
norm["cache_hit"] / PER_MILLION * pricing.chat_cache_hit_per_m
+ norm["cache_miss"] / PER_MILLION * pricing.chat_cache_miss_per_m
)
output_cost = norm["completion"] / PER_MILLION * pricing.chat_output_per_m
native = usage.get("cost") if isinstance(usage, dict) else None
if isinstance(native, (int, float)) and not isinstance(native, bool):
total = max(0.0, float(native))
modeled = input_cost + output_cost
if modeled > 0:
input_share = input_cost / modeled
elif norm["prompt"] + norm["completion"] > 0:
input_share = norm["prompt"] / (norm["prompt"] + norm["completion"])
else:
input_share = 0.0
native_input = total * input_share
return total, native_input, total - native_input, True
return input_cost + output_cost, input_cost, output_cost, False
@@ -249,6 +255,35 @@ def usage_response_headers(row: Optional[dict]) -> dict:
return headers
def parse_usage_headers(headers) -> Optional[dict]:
if not headers or "X-Gateway-Cost-USD" not in headers:
return None
def _int(name: str) -> int:
try:
return int(headers.get(name) or 0)
except (TypeError, ValueError):
return 0
def _float(name: str) -> float:
try:
return float(headers.get(name) or 0.0)
except (TypeError, ValueError):
return 0.0
return {
"calls": 1,
"model": headers.get("X-Gateway-Model") or "",
"prompt_tokens": _int("X-Gateway-Prompt-Tokens"),
"completion_tokens": _int("X-Gateway-Completion-Tokens"),
"total_tokens": _int("X-Gateway-Total-Tokens"),
"cost_usd": _float("X-Gateway-Cost-USD"),
"native_cost": headers.get("X-Gateway-Cost-Native") == "1",
"upstream_latency_ms": _float("X-Gateway-Upstream-Latency-Ms"),
"total_latency_ms": _float("X-Gateway-Total-Latency-Ms"),
}
class GatewayUsageLedger:
def record(self, raw: dict, pricing: Pricing, context_map: dict) -> Optional[dict]:
try:
+4 -1
View File
@@ -106,11 +106,12 @@ class VisionAugmenter:
self.pricing = pricing
self.context_map = context_map or {}
self.calls = 0
self.cost_usd = 0.0
def _record(self, latency_ms, status_code, success, category, usage):
if self.ledger is None or self.pricing is None:
return
self.ledger.record(
row = self.ledger.record(
{
"owner_kind": self.owner[0],
"owner_id": self.owner[1],
@@ -129,6 +130,8 @@ class VisionAugmenter:
self.pricing,
self.context_map,
)
if row:
self.cost_usd += float(row.get("cost_usd") or 0.0)
async def _describe_one(self, client: httpx.AsyncClient, image_block: dict) -> str:
if not self.vision_key: