Skip to content

fix: detect low balance and prompt for top up#7166

Merged
michaelneale merged 18 commits intomainfrom
tetrate-credits-exhausted-browser-open
Feb 19, 2026
Merged

fix: detect low balance and prompt for top up#7166
michaelneale merged 18 commits intomainfrom
tetrate-credits-exhausted-browser-open

Conversation

@michaelneale
Copy link
Collaborator

@michaelneale michaelneale commented Feb 11, 2026

attempt to detect if there is no credit and open a browser to tetrate so user can top it up

Goose app:

Goose.out.of.credits.mov

CLI:

CLI.out.of.credits.mov

When the Tetrate provider detects that a user's credits have been
exhausted, this change:

1. Adds a new ProviderError::CreditsExhausted variant with details
   and an optional top_up_url field
2. Detects credit exhaustion in the Tetrate provider via:
   - HTTP 402 (Payment Required) status codes
   - Error code 402 in response body
   - Credit-related keywords in error messages (covers cases where
     credit exhaustion arrives as 429 or in 200 OK error bodies)
3. Opens the user's default browser to the Tetrate dashboard
   (https://router.tetrate.ai/dashboard) for easy credit top-up
4. Falls back to printing the URL if the browser can't be opened
5. Shows a clear message explaining what happened and how to continue
6. Does NOT retry on CreditsExhausted (it's not transient)
7. Adds generic HTTP 402 handling in openai_compatible for other providers

Uses the existing webbrowser crate (already a dependency for OAuth).
…assumption

Follow-up improvements to the credits-exhausted browser-open feature:

1. Unit tests (35 total):
   - 29 tests in tetrate.rs covering is_credits_exhausted() positive
     cases (all 16 phrases), case-insensitivity, embedded matches,
     negative cases (rate limit, server error, auth, context length,
     empty, generic, model-not-found), error variant fields, display
     format, telemetry type, and retry exclusion.
   - 6 tests in openai_compatible.rs covering map_http_error_to_provider_error
     for HTTP 402 (with/without payload), 429, 401, 400+context, 500.

2. Verified HTTP 402 assumption:
   - No documentation found confirming Tetrate's exact error format for
     credit exhaustion. Added detailed code comment explaining we
     defensively handle HTTP 402, error code 402 in response bodies,
     AND keyword-based detection as a safety net.

3. Generalized the design:
   - Removed hardcoded Tetrate dashboard URL fallback from agent.rs.
   - When top_up_url is None (generic 402 from unknown provider), shows
     a provider-agnostic message without attempting to open a browser.
   - When top_up_url is Some (Tetrate or any future provider), opens
     the browser as before.

4. Browser opening verification:
   - Confirmed webbrowser crate v1.0 (same as Tetrate OAuth flow in
     signup_tetrate). Works on macOS, Linux, Windows.
   - Added code comment in agent.rs noting the cross-platform mechanism.
The agent is a core library and should not do UI actions like opening
browsers. Different consumers (CLI, desktop app, API) need to handle
credits-exhausted differently.

Changes:
- Add SystemNotificationType::CreditsExhausted variant to message.rs
  with documented data schema ({top_up_url: string | null})
- agent.rs now yields a structured CreditsExhausted notification with
  the user-facing message and top_up_url in the data field — no
  browser opening, no webbrowser dependency
- goose-cli output.rs handles the new notification type: renders the
  message in yellow and calls webbrowser::open() if a top_up_url is
  present in the notification data
- goose-server passes the notification through as a serialized SSE
  event (no changes needed) — desktop app can handle it however it
  wants (dialog, button, etc.)

This follows the same pattern as ThinkingMessage and InlineMessage
notifications: the agent produces structured events, the UI layer
decides how to present them.
Add desktop app support for the CreditsExhausted system notification
that flows from the agent through goose-server SSE events.

Changes:
- openapi.json: Add 'creditsExhausted' to SystemNotificationType enum
- types.gen.ts: Add 'creditsExhausted' to the union type
- CreditsExhaustedNotification.tsx: New component that renders a
  yellow warning banner with the error message and a 'Top Up Credits'
  button that opens the provider's dashboard URL via
  window.electron.openExternal()
- ProgressiveMessageList.tsx: Detect creditsExhausted notifications
  and render CreditsExhaustedNotification before other system
  notification checks

The notification data schema is {top_up_url: string | null}. When
top_up_url is present the button is shown; when null only the message
is displayed. goose-server required no changes — it already serializes
the full Message (including SystemNotificationContent with data) as
SSE JSON events.
Copy link
Collaborator

@DOsinga DOsinga left a comment

Choose a reason for hiding this comment

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

This looks like a very good addition, but I think we should remove all the clutter from tetrate and rely on the http code which they do provide.

// If the provider supplied a top-up URL, try to open
// the user's browser so they can add credits. Uses the
// `webbrowser` crate (same cross-platform mechanism as
// the Tetrate OAuth sign-up flow).
Copy link
Collaborator

Choose a reason for hiding this comment

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

I could do without that comment (but also realizing I'm losing that battle0

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

you know - I didn't look at a single bit of this PR, not even once (at least other than having AI.staged describe it too me - as wasn't sure if people cared!) but now they do, will tidy up

"Your credits have been exhausted: {details}\n\n\
Please check your account with your provider to add more \
credits, then retry your last message to continue."
)
Copy link
Collaborator

Choose a reason for hiding this comment

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

could remove the one line duplication here

InlineMessage,
/// Provider credits have been exhausted. The `data` field of the
/// notification may contain `{"top_up_url": "..."}` so the UI layer
/// can open the user's browser or show a clickable link.
Copy link
Collaborator

Choose a reason for hiding this comment

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

this comment should go - or we need to document the others too

use serde_json::json;

#[test]
fn http_402_maps_to_credits_exhausted() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't see these tests fail unless we change functionality, but if you want to keep them, can we change them into the test_case pattern?

if (hasCreditsExhaustedNotification(message)) {
return (
<div
key={message.id ?? `msg-${index}-${message.created}`}
Copy link
Collaborator

Choose a reason for hiding this comment

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

this (we already have this) leads to duplicate keys since we also render the main message with this id (and also message.id should really be set)

}) => {
const notification = message.content.find(
(content): content is SystemNotificationContent & { type: 'systemNotification' } =>
content.type === 'systemNotification' && content.notificationType === 'creditsExhausted'
Copy link
Collaborator

Choose a reason for hiding this comment

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

this is probably also based on the previous way this works, but we really should refactor this to just go through the message content and render them based on their type

"gpt-4.1",
];
pub const TETRATE_DOC_URL: &str = "https://router.tetrate.ai";
pub const TETRATE_DASHBOARD_URL: &str = "https://router.tetrate.ai/dashboard";
Copy link
Collaborator

Choose a reason for hiding this comment

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

this reads like we changed our mind half way? like, the 402 that Tetrate returns should really be enough and we handle that using the openai compatible error handler, so all we really should need from tetrate is the top up url

…slop, use test_case

- Remove comment slop from output.rs, agent.rs, message.rs, tetrate.rs
- Deduplicate error logging in agent.rs CreditsExhausted arm
- Remove doc comment from CreditsExhausted variant (consistency with others)
- Convert openai_compatible.rs tests to test_case pattern
- Simplify tetrate.rs: rely on openai_compatible 402 handling, just enrich
  with dashboard URL. Remove is_credits_exhausted and all its tests.
- Fix duplicate React keys in ProgressiveMessageList by using index-based keys
- Refactor notification rendering: components take notification content directly,
  unified getSystemNotification + renderSystemNotification dispatches by type
@michaelneale michaelneale marked this pull request as ready for review February 13, 2026 04:57
Copilot AI review requested due to automatic review settings February 13, 2026 04:57
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds end-to-end handling for “credits exhausted” across providers and frontends, so users are notified and can navigate to a top-up page (notably for the Tetrate provider).

Changes:

  • Introduces a new creditsExhausted system notification type in the OpenAPI schema/typegen and Rust message models.
  • Maps HTTP 402 Payment Required to a new ProviderError::CreditsExhausted, enriches it with a Tetrate dashboard URL, and emits a user-facing system notification from the agent.
  • Adds UI/CLI rendering for the new notification, including opening the top-up URL.

Reviewed changes

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

Show a summary per file
File Description
ui/desktop/src/components/context_management/SystemNotificationInline.tsx Refactors inline notification rendering to accept a notification object + adds helper selector.
ui/desktop/src/components/context_management/CreditsExhaustedNotification.tsx New UI component + selector for rendering “credits exhausted” with an optional top-up button.
ui/desktop/src/components/ProgressiveMessageList.tsx Routes system notifications through type-based rendering (inline vs credits exhausted).
ui/desktop/src/api/types.gen.ts Extends generated SystemNotificationType union with creditsExhausted.
ui/desktop/openapi.json Extends OpenAPI enum for SystemNotificationType with creditsExhausted.
crates/goose/src/providers/tetrate.rs Enriches credits-exhausted errors with a dashboard URL and wires enrichment into request paths.
crates/goose/src/providers/openai_compatible.rs Maps HTTP 402 to ProviderError::CreditsExhausted + adds tests.
crates/goose/src/providers/errors.rs Adds ProviderError::CreditsExhausted variant + telemetry mapping.
crates/goose/src/conversation/message.rs Adds SystemNotificationType::CreditsExhausted for frontend notifications.
crates/goose/src/agents/agent.rs Converts ProviderError::CreditsExhausted into a system notification with top_up_url data.
crates/goose-cli/src/session/output.rs Renders credits-exhausted notification and attempts to open the top-up URL in a browser.

@michaelneale michaelneale changed the title example: tetrate detect low balance fix: detect low balance and prompt for top up Feb 13, 2026
@michaelneale michaelneale marked this pull request as draft February 13, 2026 05:37
@DOsinga
Copy link
Collaborator

DOsinga commented Feb 16, 2026

pushed some changes to simplify things

Douwe Osinga and others added 8 commits February 16, 2026 18:37
Signed-off-by: raj-subhankar <subhankar.rj@gmail.com>
Signed-off-by: raj-subhankar <subhankar.rj@gmail.com>
Signed-off-by: raj-subhankar <subhankar.rj@gmail.com>
Signed-off-by: raj-subhankar <subhankar.rj@gmail.com>
Signed-off-by: raj-subhankar <subhankar.rj@gmail.com>
@raj-subhankar raj-subhankar force-pushed the tetrate-credits-exhausted-browser-open branch from 2659d26 to 51ae6dd Compare February 18, 2026 07:43
Signed-off-by: raj-subhankar <subhankar.rj@gmail.com>
Signed-off-by: raj-subhankar <subhankar.rj@gmail.com>
@raj-subhankar raj-subhankar force-pushed the tetrate-credits-exhausted-browser-open branch from 85d1ae2 to 87f4c5a Compare February 18, 2026 10:21
Signed-off-by: raj-subhankar <subhankar.rj@gmail.com>
@raj-subhankar raj-subhankar marked this pull request as ready for review February 18, 2026 10:44
Copilot AI review requested due to automatic review settings February 18, 2026 10:44
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.

@michaelneale michaelneale added this pull request to the merge queue Feb 19, 2026
Merged via the queue into main with commit 629108d Feb 19, 2026
21 checks passed
@michaelneale michaelneale deleted the tetrate-credits-exhausted-browser-open branch February 19, 2026 02:25
katzdave added a commit that referenced this pull request Feb 19, 2026
* 'main' of github.com:block/goose: (40 commits)
  Remove trailing space from links (#7156)
  fix: detect low balance and prompt for top up (#7166)
  feat(apps): add support for MCP apps to sample (#7039)
  Typescript SDK for ACP extension methods (#7319)
  chore: upgrade to rmcp 0.16.0 (#7274)
  docs: add monitoring subagent activity section (#7323)
  docs: document Desktop UI recipe editing for model/provider and extensions (#7327)
  docs: add CLAUDE_THINKING_BUDGET and CLAUDE_THINKING_ENABLED environm… (#7330)
  fix: display 'Code Mode' instead of 'code_execution' in CLI (#7321)
  docs: add Permission Policy documentation for MCP Apps (#7325)
  update RPI plan prompt (#7326)
  docs: add CLI syntax highlighting theme customization (#7324)
  fix(cli): replace shell-based update with native Rust implementation (#7148)
  docs: rename Code Execution extension to Code Mode extension (#7316)
  docs: remove ALPHA_FEATURES flag from documentation (#7315)
  docs: escape variable syntax in recipes (#7314)
  docs: update OTel environment variable and config guides (#7221)
  docs: system proxy settings (#7311)
  docs: add Summon extension tutorial and update Skills references (#7310)
  docs: agent session id (#7289)
  ...
jh-block added a commit that referenced this pull request Feb 19, 2026
* origin/main:
  fix(ci): deflake smoke tests for Google models (#7344)
  feat: add Cerebras provider support (#7339)
  fix: skip whitespace-only text blocks in Anthropic message (#7343)
  fix(goose-acp): heap allocations (#7322)
  Remove trailing space from links (#7156)
  fix: detect low balance and prompt for top up (#7166)
  feat(apps): add support for MCP apps to sample (#7039)
  Typescript SDK for ACP extension methods (#7319)
  chore: upgrade to rmcp 0.16.0 (#7274)
  docs: add monitoring subagent activity section (#7323)
  docs: document Desktop UI recipe editing for model/provider and extensions (#7327)
  docs: add CLAUDE_THINKING_BUDGET and CLAUDE_THINKING_ENABLED environm… (#7330)
  fix: display 'Code Mode' instead of 'code_execution' in CLI (#7321)
  docs: add Permission Policy documentation for MCP Apps (#7325)
  update RPI plan prompt (#7326)
  docs: add CLI syntax highlighting theme customization (#7324)
  fix(cli): replace shell-based update with native Rust implementation (#7148)
  docs: rename Code Execution extension to Code Mode extension (#7316)
aharvard added a commit that referenced this pull request Feb 19, 2026
* origin/main: (29 commits)
  fix(ci): deflake smoke tests for Google models (#7344)
  feat: add Cerebras provider support (#7339)
  fix: skip whitespace-only text blocks in Anthropic message (#7343)
  fix(goose-acp): heap allocations (#7322)
  Remove trailing space from links (#7156)
  fix: detect low balance and prompt for top up (#7166)
  feat(apps): add support for MCP apps to sample (#7039)
  Typescript SDK for ACP extension methods (#7319)
  chore: upgrade to rmcp 0.16.0 (#7274)
  docs: add monitoring subagent activity section (#7323)
  docs: document Desktop UI recipe editing for model/provider and extensions (#7327)
  docs: add CLAUDE_THINKING_BUDGET and CLAUDE_THINKING_ENABLED environm… (#7330)
  fix: display 'Code Mode' instead of 'code_execution' in CLI (#7321)
  docs: add Permission Policy documentation for MCP Apps (#7325)
  update RPI plan prompt (#7326)
  docs: add CLI syntax highlighting theme customization (#7324)
  fix(cli): replace shell-based update with native Rust implementation (#7148)
  docs: rename Code Execution extension to Code Mode extension (#7316)
  docs: remove ALPHA_FEATURES flag from documentation (#7315)
  docs: escape variable syntax in recipes (#7314)
  ...

# Conflicts:
#	ui/desktop/src/components/McpApps/McpAppRenderer.tsx
#	ui/desktop/src/components/McpApps/types.ts
katzdave added a commit that referenced this pull request Feb 19, 2026
* 'main' of github.com:block/goose: (24 commits)
  Docs: claude code uses stream-json (#7358)
  Improve link confirmation modal (#7333)
  fix(ci): deflake smoke tests for Google models (#7344)
  feat: add Cerebras provider support (#7339)
  fix: skip whitespace-only text blocks in Anthropic message (#7343)
  fix(goose-acp): heap allocations (#7322)
  Remove trailing space from links (#7156)
  fix: detect low balance and prompt for top up (#7166)
  feat(apps): add support for MCP apps to sample (#7039)
  Typescript SDK for ACP extension methods (#7319)
  chore: upgrade to rmcp 0.16.0 (#7274)
  docs: add monitoring subagent activity section (#7323)
  docs: document Desktop UI recipe editing for model/provider and extensions (#7327)
  docs: add CLAUDE_THINKING_BUDGET and CLAUDE_THINKING_ENABLED environm… (#7330)
  fix: display 'Code Mode' instead of 'code_execution' in CLI (#7321)
  docs: add Permission Policy documentation for MCP Apps (#7325)
  update RPI plan prompt (#7326)
  docs: add CLI syntax highlighting theme customization (#7324)
  fix(cli): replace shell-based update with native Rust implementation (#7148)
  docs: rename Code Execution extension to Code Mode extension (#7316)
  ...
michaelneale added a commit that referenced this pull request Feb 19, 2026
* main: (46 commits)
  chore(deps): bump hono from 4.11.9 to 4.12.0 in /ui/desktop (#7369)
  Include 3rd-party license copy for JavaScript/CSS minified files (#7352)
  docs for reasoning env var (#7367)
  docs: update skills detail page to reference Goose Summon extension (#7350)
  fix(apps): restore MCP app sampling support reverted by #6933 (#7366)
  feat: TUI client of goose-acp (#7362)
  docs: agent variable (#7365)
  docs: pass env vars to shell (#7361)
  docs: update sandbox topic (#7336)
  feat: add local inference provider with llama.cpp backend and HuggingFace model management (#6933)
  Docs: claude code uses stream-json (#7358)
  Improve link confirmation modal (#7333)
  fix(ci): deflake smoke tests for Google models (#7344)
  feat: add Cerebras provider support (#7339)
  fix: skip whitespace-only text blocks in Anthropic message (#7343)
  fix(goose-acp): heap allocations (#7322)
  Remove trailing space from links (#7156)
  fix: detect low balance and prompt for top up (#7166)
  feat(apps): add support for MCP apps to sample (#7039)
  Typescript SDK for ACP extension methods (#7319)
  ...
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.

3 participants

Comments