Skip to content

feat(desktop): show running session cost in the desktop status bar (#67765) - #67834

Draft
DavidMetcalfe wants to merge 1 commit into
NousResearch:mainfrom
DavidMetcalfe:feat/desktop-session-cost-chip
Draft

feat(desktop): show running session cost in the desktop status bar (#67765)#67834
DavidMetcalfe wants to merge 1 commit into
NousResearch:mainfrom
DavidMetcalfe:feat/desktop-session-cost-chip

Conversation

@DavidMetcalfe

Copy link
Copy Markdown
Contributor

Status: DRAFT — implementation pending

This PR is being filed as a draft to align on direction before implementation work begins. The brief below describes the intended scope. Source changes will follow in a follow-up commit (or a follow-up PR) once a maintainer weighs in on the placement and config-toggle conventions.

Why draft-first

Per the brief: the chip's core behavior (1, 4, 5 in the Acceptance section) is independent and can ship anytime. The accuracy-label behavior (2, 3) depends on the underlying data being honest, which is being addressed by PR #67770 (rehydration) and PR #67790 (priority ladder).

If you'd prefer the chip land strictly after those two PRs land in main, that's fine — say so and I'll close this draft and re-file when the dependencies are merged. If you're comfortable with the chip landing independently (with the graceful-degradation behavior described below), say so and I'll implement on this branch.

Problem

The desktop status bar shows context-window usage (apps/desktop/src/app/shell/hooks/use-statusbar-items.tsx:101, 145-146) but doesn't show what the active session has cost so far. The agents panel shows per-subagent cost (apps/desktop/src/app/agents/index.tsx:219) but the main session — which is what the user actually pays attention to — has no running dollar readout.

This is a polish gap, not a missing-feature: the backend already computes, accumulates, and persists per-call cost in real time. The wire payload just doesn't carry it.

What's already there (verified)

  • session_model_usage table at hermes_state.py:836-856 — per-call token deltas (input / output / cache-read / cache-write / reasoning) and estimated_cost_usd keyed by (session_id, model, billing_provider, billing_base_url, billing_mode, task). Issues Session model/billing fields don't reflect mid-session model switches → incorrect metrics #51607 (mid-session /model attribution) and Auxiliary model API calls are invisible in analytics / workspace dashboard #23270 (aux-task isolation) already solved at the schema level.
  • Pricing engine at agent/usage_pricing.py:105-810 (_OFFICIAL_DOCS_PRICING) — 50+ snapshot entries for Anthropic 4.0–4.8, OpenAI GPT-5.6/4o/4.1/o3, DeepSeek v4/chat/reasoner, Google Gemini 2.5-pro, Fireworks variants, with separate rates for cache-read and cache-write.
  • Live catalog fetch at agent/usage_pricing.py:997-1024 (get_pricing_entry) — OpenRouter and any other provider with a base_url returns live rates from {base_url}/models. Snapshot is the fallback.
  • Per-call chokepoint at agent/conversation_loop.py:2353-2369 and agent/codex_runtime.py:77, 157update_token_counts() is called after every API response.
  • Renderer hot path at apps/desktop/src/store/session.ts:305-310 ($currentUsage atom) → apps/desktop/src/app/shell/hooks/use-statusbar-items.tsx:101, 129 (read in useStatusbarItems) → derives contextUsage and contextBar chips. Same atom can carry cost_usd.
  • i18n keys already exist at apps/desktop/src/i18n/en.ts:1167 (actualCost).

What's missing

  • tui_gateway/server.py:3569-3626 (_get_usage) returns model, input, output, reasoning, prompt, completion, total, calls, context_*no cost_usd, no cost_status.
  • apps/desktop/src/types/hermes.ts:434-443 (UsageStats) declares cost_usd?: number (optional, never populated today).
  • No chip in the status bar reads or renders cost for the main session.

Acceptance

This PR is done when:

  1. The desktop status bar renders a session-cost chip alongside context-usage for any session with non-zero cost_usd.
  2. After any gateway restart followed by a session resume, the chip shows the correct accumulated cost (not a fresh $0.00) — this requires Issue: Rehydration Bug to be merged.
  3. After mixing provider routes (e.g., switching mid-session between Anthropic-direct and OpenRouter), the chip's accuracy label reflects the most-accurate source seen — this requires Issue: Priority Ladder Bug to be merged.
  4. The chip respects the display.show_session_cost config toggle (hidden when set to false).
  5. The chip format is localized in en / zh / zh-hant / ja.

Items 1, 4, 5 can land in any order (independently testable). Items 2 and 3 require the linked bugs.

Graceful degradation (this PR is independent of #67770 and #67764)

If landed alone (without #67762 and #67764):

  • The chip will render $X.XX est. based on the latest call's cost and status.
  • After a gateway restart, the chip may briefly show $0.00 until the next API call lands (cosmetic; the display.show_session_cost: false toggle lets users opt out).
  • The accuracy label may be optimistic or pessimistic depending on which call happens to be latest.

None of these are crashes. Users with display.show_session_cost: false are unaffected. No silent cost data corruption is possible because the chip is read-only on the wire payload.

Open questions for reviewers

  • Placement: the chip would slot in at apps/desktop/src/app/shell/hooks/use-statusbar-items.tsx:432-439 between context-usage and session-timer, mirroring the context-usage item shape. Acceptable, or do you prefer composer pill / sidebar tab?
  • Responsive layout: a 7th chip in the right-side status bar stack crowds 13" laptops (verified by visual reality-check, screenshot at ~/Library/Application Support/Hermes/composer-images/composer_2026-07-19_21-17-23-010_da5ada.png). At viewports narrower than ~900px should the chip (a) collapse to just $${cost} dropping the status indicator, (b) overflow into ellipsis with the full label in the title tooltip, or (c) hide below a breakpoint? I prefer (a).
  • Config toggle namespace: display.* matches the display.* settings convention. Anything else (e.g., feature_flags.*, ui.*) preferred?

Notes

  • Per apps/desktop/AGENTS.md, npx tsc -b . reports 11 pre-existing TypeScript errors in src/lib/incremental-external-store-runtime.ts and npx vitest run reports 65 failing test files / 382 failing tests with the same root cause (pnpm hoisting / duplicate @assistant-ui/core packages). These are pre-existing on main at commit 36f2a966c and unrelated to cost tracking. Implementation will land unit tests with the existing status bar test files that pass cleanly today (apps/desktop/src/store/session-color.test.ts, apps/desktop/src/store/projects.test.ts).
  • Cache invariant: read-only on the renderer side; emits an additional field on the gateway side. Per-conversation prompt caching preserved (per root AGENTS.md).
  • The 10-item task list in the brief is in the comment block below for reference.

Closes #67765


Task list (from the implementation brief)

  • Support passing morph snapshot id #2 (BLOCKER bug) — Rehydrate agent.session_estimated_cost_usd and agent.session_cost_status from SQLite on agent construction. Without this, the chip's value will visibly reset to $0.00 mid-session after any gateway restart.
  • Architecture planning #3 (SHOULD-FIX bug) — Replace the most-recent-call-wins COALESCE(cost_status, cost_status) semantics across hermes_state.py:2887, 2908, 3087, plus the unconditional in-memory assignments at agent/conversation_loop.py:2321 and agent/codex_runtime.py:150, with a sticky priority ladder (actual and included are sticky; estimated/unknown reflect the latest call).
  • Extend tui_gateway/server.py:3569-3626 (_get_usage) to read agent.session_estimated_cost_usd and agent.session_cost_status and emit them as cost_usd and cost_status in the returned dict.
  • Extend apps/desktop/src/types/hermes.ts:434-443 (UsageStats) with cost_status?: "actual" | "estimated" | "included" | "unknown" (the field is already optional).
  • Add a usageCostLabel(currentUsage) helper to apps/desktop/src/lib/statusbar.ts:42-48 (mirror the usageContextLabel pattern).
  • Insert a session-cost item in coreRightStatusbarItems at apps/desktop/src/app/shell/hooks/use-statusbar-items.tsx:432-439 between context-usage and session-timer, mirroring the context-usage shape.
  • Add openSessionCost: string to the statusbar block in apps/desktop/src/i18n/en.ts:2196-2226 and equivalents in zh.ts, zh-hant.ts, ja.ts; update apps/desktop/src/i18n/types.ts:1788+.
  • Add estimatedCost: (cost: string) => string to apps/desktop/src/i18n/en.ts:~1166 alongside existing actualCost; type definition at apps/desktop/src/i18n/types.ts:1028.
  • Add the display.show_session_cost: bool config toggle (default true) to the config schema and surface it in the settings panel if display.* toggles are user-facing there.
  • Add the narrow-viewport overflow handler: on viewports < ~900px wide, collapse the chip to just $${cost} without the status indicator, OR add CSS text-overflow: clip to truncate gracefully.
  • Add regression tests.

Previous PRs in this series

@alt-glitch alt-glitch added type/feature New feature or request comp/desktop Electron desktop app (apps/desktop/*) area/billing Account usage, credit usage, billing (cross-cutting) P3 Low — cosmetic, nice to have needs-decision Awaiting maintainer decision before any implementation labels Jul 20, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related to #67765 and the broader #60673/#66932 cost-visibility work. This is a draft with no implementation patch yet; please choose the shared event and status-bar scope before code lands.

…tainer review)

Draft placeholder commit to back the PR linked from NousResearch#67765. The
implementation itself lands in a follow-up commit once a maintainer
weighs in on chip placement and config-toggle conventions. See the
PR body for the full design rationale.
@teknium1

Copy link
Copy Markdown
Contributor

Thanks for documenting the intended scope before implementation.

Problems

  • This draft has no implementation to review: the sole changed file says code is pending maintainer direction (docs/prs/67834-draft-placeholder.md:7-9).
  • The direction needs an explicit product decision. Merged commit fd2a35b169 removed user-facing cost estimates because provider data could produce misleading cost figures; it specifically removed cost_usd from tui_gateway/server.py. Current _get_usage() still returns only token/context fields (tui_gateway/server.py:3959-4016).

Suggested changes

  • Resolve whether this should reverse or narrow that prior no-cost policy before landing code.
  • If approved, submit the actual gateway-to-desktop implementation with behavior tests for unavailable and estimated cost data.

Automated hermes-sweeper review.

@DavidMetcalfe

DavidMetcalfe commented Jul 25, 2026

Copy link
Copy Markdown
Contributor Author

@teknium1 Thanks for the review — and for calling out the prior policy from fd2a35b16. That commit's rationale (unreliable cost estimates on providers that don't report cached_tokens, producing misleading near-zero figures) is exactly the right framing.

This PR's design is a narrowing of that policy, not a reversal. Here's how:

The dependency PR #67790 (sticky cost_status priority ladder) introduces a confidence-ranked status: actual (3) > included (2) > estimated (1) > unknown (0). Once any call in a session reports actual or included, the accumulated status stays there — a single estimated call can't downgrade a session that already has real cost data from OpenRouter or Anthropic.

The session-cost chip would then render with an accuracy label reflecting that status: "$X.XX actual" vs "$X.XX est." vs "$X.XX incl.". This directly addresses the fd2a35b16 failure mode: providers like ollama-cloud that don't report cached_tokens would produce estimated or unknown status, and the user sees "$0.01 est." — they know it's approximate. Providers that report real cost get "$4.27 actual" — the user knows it's accurate.

The rehydration PR #67770 ensures the cost counter persists across gateway restarts (reads from session_model_usage), and PR #67790 makes the status sticky, so the display doesn't flip between actual and estimated on every call.

Open question: Does the team consider this narrowing approach (accuracy-labeled cost chip, gated on the sticky status ladder) an acceptable direction, or do you prefer a different path — e.g., gating the chip only on an explicit display.show_session_cost toggle with no accuracy label, or waiting for Phase 2 (provider-reported actual_cost_usd) before any cost surface?

I'm happy either way — just want to confirm the policy direction before writing code.

@teknium1 teknium1 added sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users area/sessions Session lifecycle, resume, persistence, history area/usage-cost Token accounting, usage reporting, billing, cost tracking labels Jul 30, 2026
@DavidMetcalfe

Copy link
Copy Markdown
Contributor Author

@teknium1 @OutThisLife Just checking in on this draft — no rush, but the open questions from my reply to the review (Jul 25) are still unanswered, and I'd like to implement once the direction is set:

  1. Policy direction — is the accuracy-labeled session-cost chip an acceptable narrowing of the no-cost policy from fd2a35b169? That commit removed user-facing cost estimates because provider data could mislead; the design here addresses the same failure mode directly: cost is only displayed with a confidence label (actual / incl. / est. / unknown) driven by the sticky cost_status ladder (fix(agent): sticky cost_status priority ladder (#67764) #67790), so an under-reporting provider shows an explicit est. rather than a silently wrong number.
  2. Placement — status bar next to the existing context-usage chip (my recommendation), composer pill, or a separate panel?
  3. Config toggle — is display.show_session_cost (default true) the right gate?
  4. Merge order — should this land independently of fix(agent): rehydrate session cost counters on agent construction (#67762) #67770/fix(agent): sticky cost_status priority ladder (#67764) #67790 (graceful degradation is documented in the body), or wait for them?

The full scope is in the PR body and issue #67765. For what it's worth, the pricing data feeding the estimates (#85388, DeepSeek peak/off-peak) is rebased, mergeable, and CI-green, so the chip's numbers will be accurate once that lands.

Happy to adjust the scope (e.g., chip without the accuracy label) if the team prefers — just say the word and I'll implement on this branch.

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

Labels

area/billing Account usage, credit usage, billing (cross-cutting) area/sessions Session lifecycle, resume, persistence, history area/usage-cost Token accounting, usage reporting, billing, cost tracking comp/desktop Electron desktop app (apps/desktop/*) needs-decision Awaiting maintainer decision before any implementation P3 Low — cosmetic, nice to have sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users type/feature New feature or request

Projects

None yet

3 participants