Skip to content

feat(usage): desktop usage summary surface (Phase 1 of #77221) - #77251

Draft
tneemo wants to merge 4 commits into
NousResearch:mainfrom
tneemo:feat/desktop-usage-summary
Draft

feat(usage): desktop usage summary surface (Phase 1 of #77221)#77251
tneemo wants to merge 4 commits into
NousResearch:mainfrom
tneemo:feat/desktop-usage-summary

Conversation

@tneemo

@tneemo tneemo commented Aug 3, 2026

Copy link
Copy Markdown

Phase 1: local token/cost usage summary in the Desktop app

Closes #77221 (Phase 1 — summary surface; charts/heatmap are deliberately Phase 2).

Backend — usage.summary JSON-RPC method

tui_gateway/methods_config.py adds a usage.summary handler that aggregates the existing local metering tables (sessions + session_model_usage) — no parallel metering system:

  • total_sessions, token_sessions, cost_sessions
  • total_estimated_cost_usd, most_expensive_session_usd, cheapest_session_usd
  • total_input_tokens, total_output_tokens, total_cache_read_tokens

Degrades gracefully to zeros when the DB is unavailable (matches the other handlers' fail-soft pattern). Session-level columns are used as a compatibility fallback for older DBs that lack session_model_usage.

Frontend — bundled "Usage" plugin

apps/desktop/src/plugins/usage/ adds a bundled plugin (auto-discovered by the existing import.meta.glob('../plugins/*/plugin.{ts,tsx}')):

  • plugin.tsx — registers ROUTES_AREA (/usage) + SIDEBAR_NAV_AREA ("Usage", codicon graph-line)
  • page.tsx — calls host.request('usage.summary') on mount and renders summary cards (sessions, spend USD, tokens, most/least expensive session), using the plugin SDK's Loader/ErrorState and the app's dark-theme CSS variables

Verification

  • python -c "import ast; ast.parse(open('tui_gateway/methods_config.py').read())" — clean
  • npx tsc --noEmit (apps/desktop) — clean
  • npx vitest run src/contrib/ src/plugins/ — 13/13 pass
  • RPC exercised against a real state.db: 12 sessions, 9 token-bearing, 491M cache-read tokens aggregated correctly
  • Cheapness bug fixed during validation: cheapest_session_usd now ignores zero-cost sessions (MIN(CASE WHEN estimated_cost_usd > 0 ...)) instead of returning 0

Phase 2 (not in this PR)

GitHub-style cost/token heatmap, cumulative-cost line graph, per-model/provider breakdowns, daily/weekly/monthly views — per the issue's full spec.

…7221)

Add a usage.summary JSON-RPC method (tui_gateway/methods_config.py) that
aggregates the existing local metering tables (sessions +
session_model_usage) into a cheap summary: session counts, token totals
(input/output/cache-read), and estimated spend, degrading gracefully to
zeros when the DB is unavailable.

Add a bundled desktop 'Usage' plugin (apps/desktop/src/plugins/usage)
with summary cards rendered via host.request('usage.summary'), registered
through ROUTES_AREA + SIDEBAR_NAV_AREA (auto-discovered by the plugins
glob). Charts/heatmap are deliberately Phase 2.

Co-authored-by: codex (gpt-5.6-luna)
Copilot AI review requested due to automatic review settings August 3, 2026 02:28
@tneemo
tneemo marked this pull request as draft August 3, 2026 02:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an initial “Usage” surface to the Hermes Desktop app by exposing a lightweight local-DB usage aggregate over JSON-RPC and rendering it as a new plugin page. This fits the existing desktop plugin architecture (route + sidebar contribution) and reuses the existing state.db metering tables rather than introducing a new metering path.

Changes:

  • Adds a new usage.summary JSON-RPC handler that aggregates session/token/cost totals from state.db.
  • Introduces a bundled Desktop plugin that registers /usage + sidebar nav and renders summary cards from usage.summary.
  • Implements a basic UI layout with loading/error states via the plugin SDK.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
tui_gateway/methods_config.py Adds usage.summary RPC that aggregates metering totals from sessions / session_model_usage.
apps/desktop/src/plugins/usage/plugin.tsx Registers the “Usage” route and sidebar navigation entry via the plugin SDK.
apps/desktop/src/plugins/usage/page.tsx Fetches usage.summary and renders summary cards (sessions, tokens, costs).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +83 to +99
by_session = db._conn.execute(
"""
SELECT COALESCE(SUM(estimated_cost_usd), 0) AS estimated_cost_usd
FROM session_model_usage
GROUP BY session_id
"""
).fetchall()
payload.update(
total_estimated_cost_usd=float(usage["estimated_cost_usd"] or 0.0),
total_input_tokens=int(usage["input_tokens"] or 0),
total_output_tokens=int(usage["output_tokens"] or 0),
total_cache_read_tokens=int(usage["cache_read_tokens"] or 0),
most_expensive_session_usd=max(
(float(row["estimated_cost_usd"] or 0.0) for row in by_session),
default=0.0,
),
)
Comment thread apps/desktop/src/plugins/usage/page.tsx Outdated
Comment on lines +28 to +29
const formatUsd = (value: number) =>
new Intl.NumberFormat(undefined, { currency: 'USD', style: 'currency', minimumFractionDigits: 2 }).format(value || 0)
Comment on lines +42 to +46
SUM(CASE WHEN COALESCE(input_tokens, 0) > 0
OR COALESCE(output_tokens, 0) > 0
OR COALESCE(cache_read_tokens, 0) > 0
OR COALESCE(cache_write_tokens, 0) > 0
THEN 1 ELSE 0 END) AS token_sessions,
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/desktop Electron desktop app (apps/desktop/*) comp/tui Terminal UI (ui-tui/ + tui_gateway/) area/usage-cost Token accounting, usage reporting, billing, cost tracking labels Aug 3, 2026
@tneemo

tneemo commented Aug 3, 2026

Copy link
Copy Markdown
Author

Withdrawing this draft in favor of #77537 / #77572

Closing this Phase-1 draft. The same surface (usage summary cards) plus the full Phase-2 scope (daily sparkline, per-model/provider breakdown) is implemented in #77537 and #77572 by reusing the existing /api/analytics/usage endpoint — which is the better architecture (no parallel RPC). My review notes from building this (token-category consistency, micro-cost precision, single-source cost extrema) are posted on those PRs. No point keeping a competing implementation open.

The branch remains on the fork if anyone wants the usage.summary RPC + session_model_usage aggregation as reference.

@tneemo tneemo closed this Aug 3, 2026
- Align token categories: add total_cache_write_tokens so the token-session
  count (which includes cache-write activity) matches a displayed total.
- Preserve micro-cost precision: formatUsd uses 6 decimals for values in
  (0, 0.01) instead of collapsing to $0.00 (e.g. cheapest session $0.000056).
- Derive both cost extrema from the same per-session aggregate (filtered to
  positive costs) so cheapest/most-expensive tell one story.
@tneemo tneemo reopened this Aug 3, 2026
@tneemo

tneemo commented Aug 3, 2026

Copy link
Copy Markdown
Author

Reopened — this PR follows the issue's specified architecture (draft)

I closed this earlier thinking the newer PRs (#77537 / #77572) superseded it. Re-examining the #77221 spec, this PR is the one that implements the architecture the issue explicitly asks for:

"A desktop surface (page + sidebar nav, registered through apps/desktop/src/contrib...) reads the existing tables via a new usage.* RPC in tui_gateway/methods_config.py wrapping InsightsEngine + a per-day aggregation — no parallel metering system."

This PR: new usage.summary JSON-RPC in tui_gateway/methods_config.py + bundled plugin auto-discovered through the contrib system (src/plugins/usage/), aggregating the existing sessions + session_model_usage tables. Matches the requested surface + RPC + no-parallel-metering.

#77537/#77572: register via routes.ts/surfaces.tsx directly and consume the existing /api/analytics/usage HTTP endpoint. A different (also valid) approach — but not the usage.* RPC-through-contrib architecture the issue specifies.

Review findings addressed (pushed 4881ac96b)

The automated triage on this PR surfaced three correctness items, all fixed:

  1. Token categories agree — added total_cache_write_tokens so the token-session count (which includes cache-write activity) matches a displayed total.
  2. Micro-cost precisionformatUsd now shows 6 decimals for values in (0, 0.01) instead of collapsing $0.000056 to $0.00.
  3. Single-source cost extrema — cheapest and most expensive are derived from the same per-session aggregate (positive costs only), so the two numbers tell one story.

Consolidation note

Happy to let maintainers decide which architecture to land. If they prefer the /api/analytics/usage route taken by #77537/#77572, the useful visualizations there (daily sparkline, per-model breakdown) can be incorporated here once consolidation is confirmed — I'd rather not duplicate work while the direction is open. For now this PR stays a draft, ready to either proceed with the specified RPC architecture or fold in the newer visualizations.

tneemo added 2 commits August 3, 2026 08:37
The RPC already returned total_cache_write_tokens but the page's
total-token calculation omitted it — making the displayed total disagree
with the token-session count (review finding). Include it.
- Pin formatUsd to the en-US locale (Intl.NumberFormat('en-US', ...))
  instead of the ambient locale, so USD rendering is consistent
  regardless of the system language.
- Compute cost extrema directly in SQL (MAX/MIN over the per-session
  aggregate subquery) instead of fetchall() + Python min/max.
@tneemo

tneemo commented Aug 3, 2026

Copy link
Copy Markdown
Author

Addressed: Copilot's locale + SQL-extrema suggestions (pushed 2a0c2eede)

The follow-up triage correctly noted the cache-write inconsistency is resolved and flagged the two remaining Copilot suggestions. Both are now fixed:

  1. Locale pinnedformatUsd now uses Intl.NumberFormat('en-US', ...) instead of the ambient locale, so USD rendering is consistent regardless of the system language (the previous undefined locale could render $ differently on zh/other locales).

  2. SQL-native extrema — cost extrema are now computed directly in SQL:

    SELECT MAX(session_cost) AS most_expensive_session_usd,
           MIN(session_cost) AS cheapest_session_usd
    FROM (SELECT SUM(COALESCE(estimated_cost_usd, 0)) AS session_cost
          FROM session_model_usage
          WHERE COALESCE(estimated_cost_usd, 0) > 0
          GROUP BY session_id)

    instead of fetchall() + Python min/max. One round-trip, no client-side scan.

Verification: Python AST + tsc --noEmit clean; the RPC runs against the local state.db (15 sessions) and the new query returns correctly (extrema are 0.0 here because this host has no estimated costs configured, but the SQL path executes — with a cost-bearing ledger it returns the true MAX/MIN).

Remaining scope: still deliberately Phase 1 — heatmap, cumulative graph, time-range and model/provider views remain out of this diff, per the architecture-decision note (maintainers pick between this usage.*-RPC-through-contrib path and the /api/analytics/usage route in #77537).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/usage-cost Token accounting, usage reporting, billing, cost tracking comp/desktop Electron desktop app (apps/desktop/*) comp/tui Terminal UI (ui-tui/ + tui_gateway/) P3 Low — cosmetic, nice to have type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(usage): desktop app has no local token/cost analytics surface despite full metering in core

3 participants