feat(billing): consistent out-of-credits UX across CLI, TUI, and desktop - #69655
Merged
Conversation
Contributor
૮ >ﻌ< ა ci reviewrunning on dd3bd70 looks good to me! |
Collaborator
|
Detect a billing wall once (agent/error_classifier → FailoverReason.billing) and map it to a recovery link + label in one place, then carry that structured BillingBlock to every surface instead of re-parsing free-form error text per surface. - agent/billing_links.py: provider-agnostic slug/host → (label, billing URL) table (single source of truth), Nous-aware (is_nous routes to the in-app flow); unknown providers degrade to a readable label with no invented URL. - conversation_loop: both billing exit paths return a billing_block through one helper; the guidance message carries the derived URL for every provider. - gateway forwards billing_block on message.complete (it was dropped). - @hermes/shared: BillingBlock type shared by desktop + TUI.
On a billing wall, raise a sticky, billing-specific toast (never the generic error toast) and a persistent in-composer banner for the active session — both with one recovery action: Nous → in-app Settings → Billing, other providers → their billing page (deep-linked). The banner reuses the shared StatusRow chrome (no bordered alert, Codicon glyph, shared buttons), and the composer stays usable so slash commands keep working.
Open the shared ConfirmPrompt (full-width, themed, same lane as approval/clarify) rather than a truncating status-bar notice. One recovery action: Nous → /topup (opens the rich billing overlay), other providers → their billing page, or /model to switch when there's no URL. The transcript keeps the full guidance; the dialog is the concise actionable layer.
Pin a provider-agnostic "Out of credits" panel after a billing-classified turn so the one recovery action (Nous → /topup, other providers → their billing page) stays visible instead of scrolling away as prose.
OutThisLife
force-pushed
the
bb/out-of-credits-ux
branch
from
July 22, 2026 23:09
30986a9 to
dd3bd70
Compare
randlee
pushed a commit
to randlee/hermes-agent
that referenced
this pull request
Aug 11, 2026
…dits-ux feat(billing): consistent out-of-credits UX across CLI, TUI, and desktop
33hodl
pushed a commit
to 33hodl/hermes-agent
that referenced
this pull request
Aug 12, 2026
…dits-ux feat(billing): consistent out-of-credits UX across CLI, TUI, and desktop
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.
When an inference call fails because the account is out of credits / payment is
required, each surface behaved differently: desktop showed a generic error toast
(and dropped the backend's structured signal), TUI showed a raw error line, and
the CLI only linked billing for OpenRouter. This makes all surfaces render one
shared, provider-aware billing signal.
Scoped to billing failures only — reuses the existing
FailoverReason.billingclassification, so provider-unreachable / rate-limit / auth errors are untouched.
How it works
Detection stays in
agent/error_classifier.py. New code only maps the failureto a recovery link and carries it as a structured
BillingBlock:agent/billing_links.py— one provider table (slug + base_url host → label,billing URL).
is_nousroutes to the in-app top-up flow; unknown providersdegrade to a label with no URL.
conversation_loop— both billing exit paths return abilling_block; theguidance text carries the derived URL for every provider (was OpenRouter-only).
billing_blockonmessage.complete(was dropped).@hermes/shared—BillingBlocktype for desktop + TUI.No surface disables the composer, so
/topup,/model,/loginkeep working.Per surface
StatusRow, no bordered alert). CTA: Nous → Settings → Billing; others → theirbilling page.
ConfirmPromptdialog (not a truncating status notice).CTA: Nous →
/topup, others → billing page, or/modelif no URL.Commits
feat(billing)— shared signal (billing_links + gateway + shared type)feat(desktop)— toast + bannerfeat(tui)— confirm dialogfeat(cli)— panelTests
test_billing_links.py— provider/host/Nous mapping + degrade.billing-block.test.ts(desktop) — CTA routing + scoped clear.billingDialog.test.ts+ handler wiring (TUI) — copy + Nous→/topup / others→URL.