mirror of
https://github.com/openai/codex.git
synced 2026-10-30 19:18:49 +00:00
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:
@@ -471,6 +471,19 @@ macro_rules! client_response_payload_from_impl {
|
||||
($variant:ident, $response:ty, manual) => {};
|
||||
}
|
||||
|
||||
/// Preserve explicit `undefined` accepted by the original stable usage request.
|
||||
///
|
||||
/// A Rust-based TypeScript proxy retains dependency discovery; a raw `#[ts(type = ...)]`
|
||||
/// override would silently omit the generated params import and schema fixture.
|
||||
#[allow(dead_code)]
|
||||
#[derive(TS)]
|
||||
#[ts(untagged)]
|
||||
enum GetAccountTokenUsageParamsTypeScript {
|
||||
Params(v2::GetAccountTokenUsageParams),
|
||||
#[ts(type = "undefined")]
|
||||
Undefined,
|
||||
}
|
||||
|
||||
client_request_definitions! {
|
||||
Initialize => "initialize" {
|
||||
params: v1::InitializeParams,
|
||||
@@ -1095,7 +1108,7 @@ client_request_definitions! {
|
||||
},
|
||||
|
||||
GetAccountTokenUsage => "account/usage/read" {
|
||||
params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
|
||||
params: #[ts(optional, as = "Option<GetAccountTokenUsageParamsTypeScript>", inline)] #[serde(default, skip_serializing_if = "Option::is_none")] v2::NullableGetAccountTokenUsageParams,
|
||||
serialization: None,
|
||||
response: v2::GetAccountTokenUsageResponse,
|
||||
},
|
||||
@@ -2850,6 +2863,56 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_get_account_thread_usage() -> Result<()> {
|
||||
let request = ClientRequest::GetAccountTokenUsage {
|
||||
request_id: RequestId::Integer(1),
|
||||
params: Some(v2::GetAccountTokenUsageParams {
|
||||
thread_id: Some("thread-123".to_string()),
|
||||
}),
|
||||
};
|
||||
assert_eq!(
|
||||
json!({
|
||||
"method": "account/usage/read",
|
||||
"id": 1,
|
||||
"params": { "threadId": "thread-123" },
|
||||
}),
|
||||
serde_json::to_value(&request)?,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_legacy_get_account_token_usage_response() -> Result<()> {
|
||||
let response: v2::GetAccountTokenUsageResponse = serde_json::from_value(json!({
|
||||
"summary": {
|
||||
"lifetimeTokens": null,
|
||||
"peakDailyTokens": null,
|
||||
"longestRunningTurnSec": null,
|
||||
"currentStreakDays": null,
|
||||
"longestStreakDays": null,
|
||||
},
|
||||
"dailyUsageBuckets": null,
|
||||
}))?;
|
||||
|
||||
assert_eq!(
|
||||
response,
|
||||
v2::GetAccountTokenUsageResponse {
|
||||
summary: v2::AccountTokenUsageSummary {
|
||||
lifetime_tokens: None,
|
||||
peak_daily_tokens: None,
|
||||
longest_running_turn_sec: None,
|
||||
current_streak_days: None,
|
||||
longest_streak_days: None,
|
||||
},
|
||||
daily_usage_buckets: None,
|
||||
thread_usage: None,
|
||||
},
|
||||
);
|
||||
assert_eq!(serde_json::to_value(response)?["threadUsage"], json!(null));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_get_workspace_messages() -> Result<()> {
|
||||
let request = ClientRequest::GetWorkspaceMessages {
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
Reference in New Issue
Block a user