@bitseek/hermes-webui 0.1.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +213 -0
- package/bin/hermes-webui.mjs +588 -0
- package/package.json +25 -0
- package/scripts/sync-vendor.mjs +74 -0
- package/templates/launchd/com.bitseek.hermes-webui.plist +21 -0
- package/templates/systemd/hermes-webui.service +13 -0
- package/templates/windows/hermes-webui-task.ps1 +3 -0
- package/vendor/agent-frontend-shell/.bitseek-source.json +6 -0
- package/vendor/agent-frontend-shell/.dockerignore +7 -0
- package/vendor/agent-frontend-shell/.env.docker.example +89 -0
- package/vendor/agent-frontend-shell/.env.example +34 -0
- package/vendor/agent-frontend-shell/.github/FUNDING.yml +3 -0
- package/vendor/agent-frontend-shell/.github/workflows/browser-smoke.yml +42 -0
- package/vendor/agent-frontend-shell/.github/workflows/docker-smoke.yml +233 -0
- package/vendor/agent-frontend-shell/.github/workflows/native-windows-startup.yml +132 -0
- package/vendor/agent-frontend-shell/.github/workflows/release.yml +57 -0
- package/vendor/agent-frontend-shell/.github/workflows/tests.yml +88 -0
- package/vendor/agent-frontend-shell/.vscode/launch.json +59 -0
- package/vendor/agent-frontend-shell/.vscode/settings.json +13 -0
- package/vendor/agent-frontend-shell/AGENTS.md +80 -0
- package/vendor/agent-frontend-shell/ARCHITECTURE.md +1658 -0
- package/vendor/agent-frontend-shell/BUGS.md +52 -0
- package/vendor/agent-frontend-shell/CHANGELOG.md +7295 -0
- package/vendor/agent-frontend-shell/CONTRIBUTING.md +205 -0
- package/vendor/agent-frontend-shell/CONTRIBUTORS.md +107 -0
- package/vendor/agent-frontend-shell/DESIGN.md +173 -0
- package/vendor/agent-frontend-shell/Dockerfile +91 -0
- package/vendor/agent-frontend-shell/LICENSE +21 -0
- package/vendor/agent-frontend-shell/README-CUSTOM.md +76 -0
- package/vendor/agent-frontend-shell/README.md +705 -0
- package/vendor/agent-frontend-shell/ROADMAP.md +351 -0
- package/vendor/agent-frontend-shell/SPRINTS.md +147 -0
- package/vendor/agent-frontend-shell/TESTING.md +1932 -0
- package/vendor/agent-frontend-shell/THEMES.md +170 -0
- package/vendor/agent-frontend-shell/api/__init__.py +1 -0
- package/vendor/agent-frontend-shell/api/agent_health.py +392 -0
- package/vendor/agent-frontend-shell/api/agent_sessions.py +782 -0
- package/vendor/agent-frontend-shell/api/auth.py +592 -0
- package/vendor/agent-frontend-shell/api/background.py +87 -0
- package/vendor/agent-frontend-shell/api/clarify.py +238 -0
- package/vendor/agent-frontend-shell/api/commands.py +124 -0
- package/vendor/agent-frontend-shell/api/compression_anchor.py +134 -0
- package/vendor/agent-frontend-shell/api/config.py +5178 -0
- package/vendor/agent-frontend-shell/api/dashboard_probe.py +255 -0
- package/vendor/agent-frontend-shell/api/extensions.py +253 -0
- package/vendor/agent-frontend-shell/api/gateway_chat.py +435 -0
- package/vendor/agent-frontend-shell/api/gateway_watcher.py +230 -0
- package/vendor/agent-frontend-shell/api/goals.py +608 -0
- package/vendor/agent-frontend-shell/api/helpers.py +474 -0
- package/vendor/agent-frontend-shell/api/kanban_bridge.py +1255 -0
- package/vendor/agent-frontend-shell/api/metering.py +194 -0
- package/vendor/agent-frontend-shell/api/models.py +4210 -0
- package/vendor/agent-frontend-shell/api/oauth.py +770 -0
- package/vendor/agent-frontend-shell/api/onboarding.py +1046 -0
- package/vendor/agent-frontend-shell/api/passkeys.py +365 -0
- package/vendor/agent-frontend-shell/api/profiles.py +1499 -0
- package/vendor/agent-frontend-shell/api/providers.py +2175 -0
- package/vendor/agent-frontend-shell/api/request_diagnostics.py +160 -0
- package/vendor/agent-frontend-shell/api/rollback.py +320 -0
- package/vendor/agent-frontend-shell/api/routes.py +13990 -0
- package/vendor/agent-frontend-shell/api/run_journal.py +284 -0
- package/vendor/agent-frontend-shell/api/runner_client.py +156 -0
- package/vendor/agent-frontend-shell/api/runtime_adapter.py +431 -0
- package/vendor/agent-frontend-shell/api/session_discoverability.py +640 -0
- package/vendor/agent-frontend-shell/api/session_events.py +45 -0
- package/vendor/agent-frontend-shell/api/session_lifecycle.py +208 -0
- package/vendor/agent-frontend-shell/api/session_ops.py +207 -0
- package/vendor/agent-frontend-shell/api/session_recovery.py +655 -0
- package/vendor/agent-frontend-shell/api/skill_usage.py +32 -0
- package/vendor/agent-frontend-shell/api/startup.py +128 -0
- package/vendor/agent-frontend-shell/api/state_sync.py +187 -0
- package/vendor/agent-frontend-shell/api/streaming.py +7048 -0
- package/vendor/agent-frontend-shell/api/system_health.py +167 -0
- package/vendor/agent-frontend-shell/api/terminal.py +410 -0
- package/vendor/agent-frontend-shell/api/turn_journal.py +214 -0
- package/vendor/agent-frontend-shell/api/updates.py +1261 -0
- package/vendor/agent-frontend-shell/api/upload.py +322 -0
- package/vendor/agent-frontend-shell/api/usage.py +26 -0
- package/vendor/agent-frontend-shell/api/workspace.py +867 -0
- package/vendor/agent-frontend-shell/api/workspace_git.py +1261 -0
- package/vendor/agent-frontend-shell/api/worktrees.py +357 -0
- package/vendor/agent-frontend-shell/bootstrap.py +492 -0
- package/vendor/agent-frontend-shell/ctl.sh +427 -0
- package/vendor/agent-frontend-shell/docker-compose.custom.yml +26 -0
- package/vendor/agent-frontend-shell/docker-compose.three-container.yml +168 -0
- package/vendor/agent-frontend-shell/docker-compose.two-container.yml +147 -0
- package/vendor/agent-frontend-shell/docker-compose.yml +57 -0
- package/vendor/agent-frontend-shell/docker_init.bash +459 -0
- package/vendor/agent-frontend-shell/docs/CONTRACTS.md +207 -0
- package/vendor/agent-frontend-shell/docs/EXTENSIONS.md +212 -0
- package/vendor/agent-frontend-shell/docs/ISSUES.md +23 -0
- package/vendor/agent-frontend-shell/docs/UIUX-GUIDE.md +196 -0
- package/vendor/agent-frontend-shell/docs/advanced-chat-setup.md +83 -0
- package/vendor/agent-frontend-shell/docs/docker.md +337 -0
- package/vendor/agent-frontend-shell/docs/onboarding-agent-checklist.md +207 -0
- package/vendor/agent-frontend-shell/docs/onboarding.md +202 -0
- package/vendor/agent-frontend-shell/docs/remote-access.md +75 -0
- package/vendor/agent-frontend-shell/docs/rfcs/README.md +53 -0
- package/vendor/agent-frontend-shell/docs/rfcs/agent-source-boundary.md +70 -0
- package/vendor/agent-frontend-shell/docs/rfcs/canonical-session-resolution.md +124 -0
- package/vendor/agent-frontend-shell/docs/rfcs/hermes-run-adapter-contract.md +1079 -0
- package/vendor/agent-frontend-shell/docs/rfcs/turn-journal.md +195 -0
- package/vendor/agent-frontend-shell/docs/rfcs/webui-run-state-consistency-contract.md +157 -0
- package/vendor/agent-frontend-shell/docs/supervisor.md +280 -0
- package/vendor/agent-frontend-shell/docs/troubleshooting.md +132 -0
- package/vendor/agent-frontend-shell/docs/ui-ux/index.html +863 -0
- package/vendor/agent-frontend-shell/docs/ui-ux/two-stage-proposal.html +768 -0
- package/vendor/agent-frontend-shell/docs/why-hermes.md +489 -0
- package/vendor/agent-frontend-shell/docs/workspace-git.md +92 -0
- package/vendor/agent-frontend-shell/docs/wsl-autostart.md +126 -0
- package/vendor/agent-frontend-shell/eslint.runtime-guard.config.mjs +35 -0
- package/vendor/agent-frontend-shell/extensions/bitseek-design-system.md +330 -0
- package/vendor/agent-frontend-shell/extensions/branding/assets/apple-touch-icon.png +0 -0
- package/vendor/agent-frontend-shell/extensions/branding/assets/empty-logo.svg +739 -0
- package/vendor/agent-frontend-shell/extensions/branding/assets/favicon-192.png +0 -0
- package/vendor/agent-frontend-shell/extensions/branding/assets/favicon-32.png +0 -0
- package/vendor/agent-frontend-shell/extensions/branding/assets/favicon-512.png +0 -0
- package/vendor/agent-frontend-shell/extensions/branding/assets/favicon-512.svg +745 -0
- package/vendor/agent-frontend-shell/extensions/branding/assets/favicon.ico +0 -0
- package/vendor/agent-frontend-shell/extensions/branding/assets/favicon.svg +745 -0
- package/vendor/agent-frontend-shell/extensions/branding/assets/titlebar-icon-v2.svg +751 -0
- package/vendor/agent-frontend-shell/extensions/branding/assets/titlebar-icon-v3.svg +739 -0
- package/vendor/agent-frontend-shell/extensions/branding/assets/titlebar-icon.svg +745 -0
- package/vendor/agent-frontend-shell/extensions/branding/branding.js +112 -0
- package/vendor/agent-frontend-shell/extensions/branding/config.json +14 -0
- package/vendor/agent-frontend-shell/extensions/branding/manifest.json +53 -0
- package/vendor/agent-frontend-shell/extensions/index.js +67 -0
- package/vendor/agent-frontend-shell/extensions/loader/hermes-loader.js +77 -0
- package/vendor/agent-frontend-shell/extensions/manifest.json +16 -0
- package/vendor/agent-frontend-shell/extensions/pages/ai-teammates/page.css +333 -0
- package/vendor/agent-frontend-shell/extensions/pages/ai-teammates/page.js +487 -0
- package/vendor/agent-frontend-shell/extensions/pages/manifest.json +6 -0
- package/vendor/agent-frontend-shell/extensions/pages/registry.css +56 -0
- package/vendor/agent-frontend-shell/extensions/pages/registry.js +302 -0
- package/vendor/agent-frontend-shell/extensions/themes/bitseek/index.css +93 -0
- package/vendor/agent-frontend-shell/extensions/themes/bitseek/index.js +98 -0
- package/vendor/agent-frontend-shell/install.sh +63 -0
- package/vendor/agent-frontend-shell/mcp_server.py +567 -0
- package/vendor/agent-frontend-shell/package.json +12 -0
- package/vendor/agent-frontend-shell/pyproject.toml +56 -0
- package/vendor/agent-frontend-shell/pytest.ini +3 -0
- package/vendor/agent-frontend-shell/requirements.txt +5 -0
- package/vendor/agent-frontend-shell/server.py +624 -0
- package/vendor/agent-frontend-shell/start.ps1 +210 -0
- package/vendor/agent-frontend-shell/start.sh +65 -0
- package/vendor/agent-frontend-shell/static/apple-touch-icon.png +0 -0
- package/vendor/agent-frontend-shell/static/boot.js +1990 -0
- package/vendor/agent-frontend-shell/static/commands.js +1402 -0
- package/vendor/agent-frontend-shell/static/favicon-192.png +0 -0
- package/vendor/agent-frontend-shell/static/favicon-32.png +0 -0
- package/vendor/agent-frontend-shell/static/favicon-512.png +0 -0
- package/vendor/agent-frontend-shell/static/favicon-512.svg +18 -0
- package/vendor/agent-frontend-shell/static/favicon.ico +0 -0
- package/vendor/agent-frontend-shell/static/favicon.svg +20 -0
- package/vendor/agent-frontend-shell/static/i18n.js +15389 -0
- package/vendor/agent-frontend-shell/static/icons.js +92 -0
- package/vendor/agent-frontend-shell/static/index.html +1506 -0
- package/vendor/agent-frontend-shell/static/login.js +177 -0
- package/vendor/agent-frontend-shell/static/manifest.json +53 -0
- package/vendor/agent-frontend-shell/static/messages.js +3521 -0
- package/vendor/agent-frontend-shell/static/onboarding.js +800 -0
- package/vendor/agent-frontend-shell/static/panels.js +7995 -0
- package/vendor/agent-frontend-shell/static/pwa-startup.js +83 -0
- package/vendor/agent-frontend-shell/static/sessions.js +5165 -0
- package/vendor/agent-frontend-shell/static/style.css +4774 -0
- package/vendor/agent-frontend-shell/static/sw.js +173 -0
- package/vendor/agent-frontend-shell/static/terminal.js +632 -0
- package/vendor/agent-frontend-shell/static/ui.js +8997 -0
- package/vendor/agent-frontend-shell/static/vendor/js-yaml/4.1.0/js-yaml.min.js +2 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_AMS-Regular.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_AMS-Regular.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Bold.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Regular.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Bold.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Bold.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Bold.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-BoldItalic.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Italic.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Italic.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Italic.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Regular.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Regular.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Main-Regular.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-BoldItalic.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-Italic.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-Italic.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Math-Italic.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Bold.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Italic.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Regular.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Script-Regular.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Script-Regular.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Script-Regular.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size1-Regular.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size1-Regular.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size2-Regular.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size2-Regular.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size3-Regular.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size3-Regular.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size4-Regular.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size4-Regular.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Typewriter-Regular.woff +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/katex.min.css +1 -0
- package/vendor/agent-frontend-shell/static/vendor/katex/0.16.22/katex.min.js +1 -0
- package/vendor/agent-frontend-shell/static/vendor/smd.min.js +29 -0
- package/vendor/agent-frontend-shell/static/workspace.js +680 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Hermes Web UI -- Streaming performance metering.
|
|
3
|
+
|
|
4
|
+
Tracks Tokens Per Second (TPS) across active WebUI streams. Metering data is
|
|
5
|
+
emitted via SSE events so a streaming assistant message can update its own
|
|
6
|
+
header while the turn is running.
|
|
7
|
+
|
|
8
|
+
Architecture
|
|
9
|
+
────────────
|
|
10
|
+
Each streaming session is tracked independently. TPS per stream is:
|
|
11
|
+
|
|
12
|
+
stream_tps = total_stream_deltas / (last_delta_ts - first_delta_ts)
|
|
13
|
+
|
|
14
|
+
The global tps is the average of all currently active streams' TPS values.
|
|
15
|
+
This correctly represents the system's real-time capacity regardless of how
|
|
16
|
+
many sessions are running or how long each has been streaming.
|
|
17
|
+
|
|
18
|
+
For HIGH/LOW tracking, every stats snapshot records the current global tps
|
|
19
|
+
(only when > 0 — idle periods are skipped) into a rolling 60-minute history.
|
|
20
|
+
The max/min of that history gives the peak throughput observed over the past hour.
|
|
21
|
+
|
|
22
|
+
The ticker in streaming.py calls get_interval() — it returns 1.0 when streams
|
|
23
|
+
are actively receiving output deltas so message headers update at 1 Hz, and 10.0 when idle
|
|
24
|
+
so the ticker exits and no idle readings are emitted.
|
|
25
|
+
|
|
26
|
+
Usage from api/streaming.py
|
|
27
|
+
─────────────────────────────
|
|
28
|
+
from api.metering import meter
|
|
29
|
+
|
|
30
|
+
meter().begin_session(stream_id) # stream starts
|
|
31
|
+
meter().record_token(stream_id, running_output_deltas)
|
|
32
|
+
meter().record_reasoning(stream_id, running_reasoning_deltas)
|
|
33
|
+
|
|
34
|
+
The SSE `metering` event payload:
|
|
35
|
+
{
|
|
36
|
+
"tps": 47.3, # omitted/null until a real reading exists
|
|
37
|
+
"tps_available": true, # frontend must hide TPS when false
|
|
38
|
+
"estimated": false, # never show byte/character-size estimates
|
|
39
|
+
"high": 52.1,
|
|
40
|
+
"low": 31.4,
|
|
41
|
+
"active": 1,
|
|
42
|
+
}
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
from __future__ import annotations
|
|
46
|
+
|
|
47
|
+
import threading
|
|
48
|
+
import time
|
|
49
|
+
from dataclasses import dataclass
|
|
50
|
+
|
|
51
|
+
_HOUR_SECS = 3600.0 # rolling window for HIGH/LOW tracking
|
|
52
|
+
_STALE_SECS = 60.0 # consider a session inactive after this
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class _SessionMeter:
|
|
57
|
+
output_tokens: int = 0
|
|
58
|
+
reasoning_tokens: int = 0
|
|
59
|
+
first_token_ts: float = 0.0 # time.monotonic() of first token received
|
|
60
|
+
last_token_ts: float = 0.0 # time.monotonic() of last token received
|
|
61
|
+
|
|
62
|
+
def total_tokens(self) -> int:
|
|
63
|
+
return self.output_tokens + self.reasoning_tokens
|
|
64
|
+
|
|
65
|
+
def tps(self) -> float | None:
|
|
66
|
+
if self.first_token_ts == 0.0 or self.last_token_ts <= self.first_token_ts:
|
|
67
|
+
return None
|
|
68
|
+
return self.total_tokens() / (self.last_token_ts - self.first_token_ts)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class GlobalMeter:
|
|
72
|
+
"""Thread-safe global streaming meter.
|
|
73
|
+
|
|
74
|
+
Tracks per-session TPS, averages them for a global tps, and maintains a
|
|
75
|
+
60-minute rolling history of global tps snapshots for HIGH/LOW reporting.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
__slots__ = (
|
|
79
|
+
'_lock',
|
|
80
|
+
'_sessions', # stream_id -> _SessionMeter
|
|
81
|
+
'_readings', # [(monotonic_ts, tps), ...] rolling 60-minute history
|
|
82
|
+
'_window_start', # monotonic ts of current window
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def __init__(self) -> None:
|
|
86
|
+
self._lock = threading.Lock()
|
|
87
|
+
self._sessions: dict[str, _SessionMeter] = {}
|
|
88
|
+
self._readings: list[tuple[float, float]] = []
|
|
89
|
+
self._window_start: float = time.monotonic()
|
|
90
|
+
|
|
91
|
+
# ── Public API ────────────────────────────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
def begin_session(self, stream_id: str) -> None:
|
|
94
|
+
with self._lock:
|
|
95
|
+
self._sessions[stream_id] = _SessionMeter()
|
|
96
|
+
|
|
97
|
+
def get_interval(self) -> float:
|
|
98
|
+
"""Return 1.0 when sessions are actively receiving tokens, 10.0 when idle.
|
|
99
|
+
|
|
100
|
+
Used by the streaming ticker to run at 1 Hz during work and exit when
|
|
101
|
+
there is nothing to measure.
|
|
102
|
+
"""
|
|
103
|
+
now = time.monotonic()
|
|
104
|
+
with self._lock:
|
|
105
|
+
# Only count sessions that have received at least one token recently.
|
|
106
|
+
active_sids = {
|
|
107
|
+
sid for sid, s in self._sessions.items()
|
|
108
|
+
if s.first_token_ts > 0 and (now - s.last_token_ts) <= _STALE_SECS
|
|
109
|
+
}
|
|
110
|
+
return 1.0 if active_sids else 10.0
|
|
111
|
+
|
|
112
|
+
def record_token(self, stream_id: str, running_output_tokens: int) -> None:
|
|
113
|
+
now = time.monotonic()
|
|
114
|
+
with self._lock:
|
|
115
|
+
s = self._sessions.get(stream_id)
|
|
116
|
+
if s is None:
|
|
117
|
+
return
|
|
118
|
+
if s.first_token_ts == 0.0:
|
|
119
|
+
s.first_token_ts = now
|
|
120
|
+
s.last_token_ts = now
|
|
121
|
+
s.output_tokens = running_output_tokens
|
|
122
|
+
|
|
123
|
+
def record_reasoning(self, stream_id: str, running_reasoning_tokens: int) -> None:
|
|
124
|
+
now = time.monotonic()
|
|
125
|
+
with self._lock:
|
|
126
|
+
s = self._sessions.get(stream_id)
|
|
127
|
+
if s is None:
|
|
128
|
+
return
|
|
129
|
+
if s.first_token_ts == 0.0:
|
|
130
|
+
s.first_token_ts = now
|
|
131
|
+
s.last_token_ts = now
|
|
132
|
+
s.reasoning_tokens = running_reasoning_tokens
|
|
133
|
+
|
|
134
|
+
def end_session(self, stream_id: str, final_output_tokens: int, input_tokens: int = 0) -> None:
|
|
135
|
+
with self._lock:
|
|
136
|
+
self._sessions.pop(stream_id, None)
|
|
137
|
+
|
|
138
|
+
def get_stats(self) -> dict:
|
|
139
|
+
now = time.monotonic()
|
|
140
|
+
with self._lock:
|
|
141
|
+
# Prune stale sessions
|
|
142
|
+
stale = [
|
|
143
|
+
sid for sid, s in self._sessions.items()
|
|
144
|
+
if s.first_token_ts > 0 and (now - s.last_token_ts) > _STALE_SECS
|
|
145
|
+
]
|
|
146
|
+
for sid in stale:
|
|
147
|
+
self._sessions.pop(sid, None)
|
|
148
|
+
|
|
149
|
+
# Reset window if everything went stale
|
|
150
|
+
if not self._sessions:
|
|
151
|
+
self._window_start = now
|
|
152
|
+
|
|
153
|
+
# Compute global tps: average only streams with a real reading. The
|
|
154
|
+
# UI hides TPS entirely when this is unavailable instead of showing
|
|
155
|
+
# placeholder/estimated values.
|
|
156
|
+
active = [s for s in self._sessions.values() if s.first_token_ts > 0]
|
|
157
|
+
active_tps = [v for s in active for v in [s.tps()] if v is not None and v > 0]
|
|
158
|
+
if active_tps:
|
|
159
|
+
global_tps = sum(active_tps) / len(active_tps)
|
|
160
|
+
else:
|
|
161
|
+
global_tps = None
|
|
162
|
+
|
|
163
|
+
# Prune readings older than 1 hour
|
|
164
|
+
cutoff = now - _HOUR_SECS
|
|
165
|
+
self._readings = [(ts, v) for ts, v in self._readings if ts > cutoff]
|
|
166
|
+
|
|
167
|
+
# Only record this snapshot for HIGH/LOW if there is active work.
|
|
168
|
+
# This prevents idle periods from flooding the history and keeps
|
|
169
|
+
# HIGH/LOW meaningful for the past hour of actual throughput.
|
|
170
|
+
if global_tps is not None and global_tps > 0:
|
|
171
|
+
self._readings.append((now, global_tps))
|
|
172
|
+
|
|
173
|
+
# HIGH/LOW from the past hour (skip near-zero idle readings)
|
|
174
|
+
active_readings = [v for _, v in self._readings if v >= 1.0]
|
|
175
|
+
high = max(active_readings) if active_readings else 0.0
|
|
176
|
+
low = min(active_readings) if active_readings else 0.0
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
'tps': round(global_tps, 1) if global_tps is not None else None,
|
|
180
|
+
'tps_available': global_tps is not None,
|
|
181
|
+
'estimated': False,
|
|
182
|
+
'high': round(high, 1) if high else None,
|
|
183
|
+
'low': round(low, 1) if low else None,
|
|
184
|
+
'active': len(self._sessions),
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
# ── Module-level singleton ─────────────────────────────────────────────────────
|
|
189
|
+
|
|
190
|
+
_meter = GlobalMeter()
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def meter() -> GlobalMeter:
|
|
194
|
+
return _meter
|