Honor workspace spend controls in rate-limit handling (#33187)

## Why

Sparse and out-of-order rate-limit updates can omit account metadata or let an
older full read overwrite a newer workspace hard stop. Credit availability also
does not always include a displayable balance.

## What changed

- Propagate the backend's `spendControlReached` state through rate-limit
  snapshots and the app-server protocol, preserving it across sparse updates.
- Keep workspace hard-stop classifications for error routing and ignore stale
  full-read results after a newer hard-stop notification.
- Base TUI warnings and model-switch prompts on credit availability and hard-stop
  state, and show `Credits: Available` when a finite balance is hidden.

## Testing

Added coverage for spend-control mapping, snapshot merging, out-of-order reads,
workspace error routing, warning and prompt behavior, and credit status display.

GitOrigin-RevId: 18b09d493d5a620a7c079fb721ec50b38dd5b04c
This commit is contained in:
stefanstokic-oai authored and copyberry committed 2026-07-14 22:43:26 +00:00
1 parent be0e0d791a
commit 4df8027a97
42 files changed
+1259 -120

No files matched your search

@@ -532,6 +532,8 @@ pub struct RateLimitSnapshot {
pub secondary: Option<RateLimitWindow>,
pub credits: Option<CreditsSnapshot>,
pub individual_limit: Option<SpendControlLimitSnapshot>,
/// Backend-reported spend-control state. `None` is unavailable, not a sparse-update recovery.
pub spend_control_reached: Option<bool>,
pub plan_type: Option<PlanType>,
pub rate_limit_reached_type: Option<RateLimitReachedType>,
}
@@ -545,6 +547,7 @@ impl From<CoreRateLimitSnapshot> for RateLimitSnapshot {
secondary: value.secondary.map(RateLimitWindow::from),
credits: value.credits.map(CreditsSnapshot::from),
individual_limit: value.individual_limit.map(SpendControlLimitSnapshot::from),
spend_control_reached: value.spend_control_reached,
plan_type: value.plan_type,
rate_limit_reached_type: value
.rate_limit_reached_type