Show estimated thread usage in /status (#38281)

## What changed

- Extend `account/usage/read` with an optional `threadId` request and a
  backward-compatible `threadUsage` response containing estimated credits,
  optional USD cost, and model, reasoning, speed, and token breakdowns.
- Fetch thread usage for eligible Business and Enterprise plans when `/status`
  is opened, then update the rendered status card asynchronously.
- Preserve terminal scrollback when replacing a visible status-card tail, and
  append a refreshed card when the original tail can no longer be updated.

## Testing

- Add app-server coverage for authentication, canonical thread IDs,
  unavailable billing routes, and malformed IDs.
- Add TUI coverage for usage formatting, stale responses, retries, history
  updates, terminal reflow, and scrollback preservation.

GitOrigin-RevId: 49300914b3fba917c0f08e8595363e04e5ba6d37
This commit is contained in:
Felipe Coury authored and copyberry committed 2026-08-13 00:30:08 +00:00
1 parent 27a98dde4d
commit f1a1fce26a
55 files changed
+2903 -60

No files matched your search

@@ -1,3 +1,4 @@
use super::ThreadUsage;
use crate::JsonSchema;
use crate::TS;
use crate::protocol::common::AuthMode;
@@ -383,12 +384,27 @@ pub enum ConsumeAccountRateLimitResetCreditOutcome {
AlreadyRedeemed,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct GetAccountTokenUsageParams {
/// When present, read estimated usage for this thread instead of account-wide token activity.
#[ts(optional = nullable)]
pub thread_id: Option<String>,
}
pub type NullableGetAccountTokenUsageParams = Option<GetAccountTokenUsageParams>;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct GetAccountTokenUsageResponse {
pub summary: AccountTokenUsageSummary,
pub daily_usage_buckets: Option<Vec<AccountTokenUsageDailyBucket>>,
/// Estimated usage when a thread was requested and its billing route is available.
#[serde(default)]
#[ts(optional, as = "Option<Option<ThreadUsage>>")]
pub thread_usage: Option<ThreadUsage>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
@@ -26,6 +26,7 @@ mod remote_control;
mod review;
mod thread;
mod thread_data;
mod thread_usage;
mod turn;
mod windows_sandbox;
@@ -56,6 +57,7 @@ pub use review::*;
pub use shared::*;
pub use thread::*;
pub use thread_data::*;
pub use thread_usage::*;
pub use turn::*;
pub use windows_sandbox::*;
@@ -0,0 +1,29 @@
use crate::JsonSchema;
use crate::TS;
use serde::Deserialize;
use serde::Serialize;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadUsage {
pub thread_id: String,
pub estimated_usage_credits_micros: i64,
pub estimated_usage_usd_micros: Option<i64>,
pub groups: Vec<ThreadUsageBreakdownGroup>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadUsageBreakdownGroup {
pub model: Option<String>,
pub reasoning_effort: Option<String>,
pub speed: Option<String>,
pub estimated_usage_credits_micros: i64,
pub net_new_input_tokens: Option<i64>,
pub cached_input_tokens: Option<i64>,
pub input_tokens: Option<i64>,
pub output_tokens: Option<i64>,
pub total_tokens: Option<i64>,
}