feat(claude): surface per-turn usage as an opt-in footer#391
Open
herikwebb wants to merge 7 commits into
Open
Conversation
added 7 commits
July 5, 2026 09:10
…Message The Claude Agent SDK ends every turn with a ResultMessage carrying duration_ms, token usage, and total_cost_usd. The receive loop dropped it in its silent else branch, so users had no signal of what a turn cost short of typing /cost. Format it as a small italic footer (e.g. '12.3s · 45.2K in (38.1K cached) / 1.2K out · $0.0842') streamed after the turn's content. Error results produce no footer (the error path already reports), zero cost is omitted (subscription sessions report 0.0 and showing $0.0000 would read as a billing claim), and malformed usage values degrade to whatever segments remain rather than failing the turn.
Review follow-up (PR #47): the footer is streamed as another MarkdownData chunk right after the assistant's last text block; without a leading paragraph break it concatenated onto prose that doesn't end in a blank line ('Done.*12.3s ...*'), breaking the emphasis markup. Prefix the footer with a blank line and pin it with a test.
The footer streamed after every turn is persistent visual noise, and its cost figure comes from the SDK's total_cost_usd, which the CLI prices from public list rates — wrong for subscription logins (marginal cost is $0), enterprise-negotiated rates, and non-first-party endpoints. Add a 'show_turn_usage' claude_setting (default off) surfaced as a Settings-panel toggle, and only emit the footer when it is on. Gate the $ segment on a direct API key being configured so the duration/token counts (always accurate) still show while a dollar amount we can't stand behind is omitted.
The cost gate only checked for a direct API key, but a user with a custom base_url (proxy, gateway, Bedrock/Vertex front, OpenAI-compatible endpoint) plus a key would still see Claude public-list-price costs that don't match their actual billing — the very case the footer's cost suppression is meant to avoid. Extract _should_show_turn_cost, requiring both a direct API key and Anthropic's own endpoint (unset or api.anthropic.com base_url); a custom base_url now suppresses the dollar figure while duration/tokens still render. Add regression tests covering the base_url cases.
…st settings The cost gate read api_key/base_url only from claude_settings, but NBI overlays ANTHROPIC_API_KEY/ANTHROPIC_BASE_URL onto the CLI subprocess env merged over the server's os.environ — so a blank setting falls through to an ambient value the CLI actually uses. An env-provided custom base_url would then show Anthropic list-price cost for a proxy/gateway endpoint. Add _resolve_effective_credential mirroring that precedence (setting wins, else env) and route _should_show_turn_cost through it, so the gate honors env-provided keys and endpoints. Add env-resolution regression tests and an autouse fixture keeping the suite hermetic.
Drop the billing caveats from the settings toggle title — those belong in the docs, not the settings UI. Keep a one-line description.
Explain the Show usage after each turn toggle and when the cost figure is shown vs omitted (direct API key on the default endpoint only), matching the setting's trimmed UI description.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The Claude Agent SDK ends every turn with a
ResultMessagecarryingduration_ms, token usage, andtotal_cost_usd. The receive loop dropped it in its silentelsebranch, so users had no signal of what a turn cost short of typing/cost.Change
format_result_usagehelper renders a small italic footer streamed after the turn's content, e.g.*12.3s · 45.2K in (38.1K cached) / 1.2K out · $0.0842*. Prefixed with a paragraph break so it can't concatenate onto prose that lacks a trailing blank line.show_turn_usageClaude setting (default off), surfaced as a Settings-panel toggle — a footer on every turn is persistent noise, so it's off unless the user asks for it.total_cost_usdis priced from the CLI's public list rates, which are wrong for subscription logins (marginal cost $0), enterprise-negotiated rates, and non-first-party endpoints._should_show_turn_costgates the$segment on both a direct API key and Anthropic's own endpoint, resolving each from the environment the CLI actually inherits (ANTHROPIC_API_KEY/ANTHROPIC_BASE_URL), not just the settings panel. Duration and token counts — always accurate — still render when cost is suppressed.ResultMessageinstances), the cost gate (API key /base_url/ env-resolution matrix), and the paragraph-break guard.Testing
Full Python suite passes. Manually verified in JupyterLab:
time · tokens · $cost.time · tokens, no$.base_url+ key:time · tokens, no$(proxy/gateway billing can't be trusted).