Skip to content

Per-turn cost display on each assistant message bubble (#503)#1159

Closed
bergeouss wants to merge 2 commits intonesquena:masterfrom
bergeouss:feat/issue-503-per-turn-cost
Closed

Per-turn cost display on each assistant message bubble (#503)#1159
bergeouss wants to merge 2 commits intonesquena:masterfrom
bergeouss:feat/issue-503-per-turn-cost

Conversation

@bergeouss
Copy link
Copy Markdown
Contributor

Thinking Path

Issue #503 identifies that the cost/token display attached to the last assistant message shows cumulative session totals (S.session.input_tokens, S.session.output_tokens). This means the number attached to message 5 is the total cost of the entire conversation, not just that turn — which reads as misleading.

The fix: compute a per-turn delta from the cumulative usage delivered in the done SSE event, store it on the message object, and render it individually on each assistant bubble.

What Changed

static/messages.js

  • At the done SSE event handler, when d.usage arrives (cumulative totals), compute the delta vs the previous session totals (S.session.input_tokens etc.)
  • Store the delta as _turnUsage on the last assistant message: {input_tokens, output_tokens, estimated_cost}
  • Only sets _turnUsage if values actually increased (skips no-op turns)

static/ui.js

  • Replaced the "cumulative usage on last assistant bubble" block with a per-turn loop
  • Iterates over S.messages, finds assistant messages with _turnUsage, and renders inline usage on each one's footer
  • Uses a separate ai counter to correctly map message indices to .assistant-turn DOM rows (since user messages don't create assistant-turn rows)
  • The context ring / composer footer still shows cumulative totals via _syncCtxIndicator() — no information is lost

Backwards compatibility

  • Messages loaded from old sessions won't have _turnUsage — they simply won't show inline usage, which is correct (we don't have per-turn data for them)
  • The _showTokenUsage setting still gates the feature

Closes #503

…a#503)

- messages.js: compute per-turn token/cost delta from cumulative usage
  at done event and store as _turnUsage on the last assistant message
- ui.js: render per-turn usage on each assistant bubble that has
  _turnUsage, replacing the misleading cumulative-total-on-last-bubble
- The context ring/composer footer still shows cumulative totals
- Preserves backwards compatibility: old messages without _turnUsage
  simply won't show inline usage (no visual regression)
Extract _attachTurnUsage() to keep the done handler compact and
within the 3300-char regression test window for S.busy=false ordering.
Functional behavior unchanged — per-turn cost delta still computed and
attached to the last assistant message._turnUsage.
@nesquena-hermes
Copy link
Copy Markdown
Collaborator

Good implementation of per-turn cost display, @bergeouss! The delta-computation approach is the right way to convert cumulative done SSE usage into per-turn numbers without requiring backend changes.

The key design decisions look correct:

  • Delta stored on message (_turnUsage) at done time — avoids re-computing on every render and survives re-renders
  • AI message counter decoupled from overall message index — necessary because user messages don't create .assistant-turn rows
  • Graceful degradation — old sessions without _turnUsage simply show nothing, no broken UI
  • Cumulative context ring preserved — no information loss for the user

The guard if values actually increased is a nice touch to skip no-op tool turns that don't consume tokens.

The _showTokenUsage setting gate means this only renders when the user has opted in to the token display, which is the correct scope.

nesquena-hermes added a commit that referenced this pull request Apr 27, 2026
Merged as v0.50.227. 2634 tests passing, browser QA 21/21 (desktop + mobile). Full attribution below.

Thanks to all 12 contributors:
@jundev0001 (#1138), @franksong2702 (#1142, #1157, #1162), @dso2ng (#1143), @bergeouss (#1145, #1146, #1156, #1159), @jasonjcwu (#1149), @ccqqlo (#1161), @frap129 (#1165)

Two fixes applied during integration and two more by the independent reviewer (@nesquena):
- messages.js: per-turn cost delta capture order (#1159)
- workspace.py: symlink target blocked-roots check + HOME sanity guard (#1149, #1165)
- panels.js: cron unread counter bookkeeping (in-loop increment bug)
- tests/test_symlink_cycle_detection.py: register workspace before session/new
@nesquena-hermes
Copy link
Copy Markdown
Collaborator

Merged as v0.50.227 via batch PR #1168. Thank you @bergeouss! 🎉

JKJameson pushed a commit to JKJameson/hermes-webui that referenced this pull request Apr 29, 2026
Merged as v0.50.227. 2634 tests passing, browser QA 21/21 (desktop + mobile). Full attribution below.

Thanks to all 12 contributors:
@jundev0001 (nesquena#1138), @franksong2702 (nesquena#1142, nesquena#1157, nesquena#1162), @dso2ng (nesquena#1143), @bergeouss (nesquena#1145, nesquena#1146, nesquena#1156, nesquena#1159), @jasonjcwu (nesquena#1149), @ccqqlo (nesquena#1161), @frap129 (nesquena#1165)

Two fixes applied during integration and two more by the independent reviewer (@nesquena):
- messages.js: per-turn cost delta capture order (nesquena#1159)
- workspace.py: symlink target blocked-roots check + HOME sanity guard (nesquena#1149, nesquena#1165)
- panels.js: cron unread counter bookkeeping (in-loop increment bug)
- tests/test_symlink_cycle_detection.py: register workspace before session/new
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ux(chat): per-turn cost display instead of cumulative session total on last bubble

2 participants