feat(app-server): expose account token usage [1 of 2] (#25344)

## Why

Token activity is useful account-level context, but terminal clients
need a supported app-server path to fetch it without reaching into
ChatGPT backend details directly. The API should also live under the
broader account usage umbrella so future usage surfaces can be added
without proliferating user-facing concepts.

## What Changed

- Add `codex-backend-client` support for the ChatGPT profile token-usage
payload.
- Add the v2 `account/usage/read` app-server RPC.
- Map lifetime usage, peak daily usage, streak, longest task duration,
and daily buckets into app-server protocol types.
- Gate the request on Codex-backend auth, which supports ChatGPT auth
tokens and AgentIdentity.
- Regenerate the app-server JSON and TypeScript schema fixtures.

## Token Count Source

`account/usage/read` returns the token-usage aggregate supplied by the
ChatGPT profile backend. App-server maps that backend-owned aggregate
into protocol fields; it does not recompute cached-token treatment,
usage multipliers, or raw input/output totals locally.

## Stack

1. feat(app-server): expose account token usage [1 of 2] (this PR)
2. [#25345](https://github.com/openai/codex/pull/25345) feat(tui): add
token activity command [2 of 2]

## How to Test

1. Start an app-server client from this branch while authenticated with
ChatGPT or AgentIdentity.
2. Call `account/usage/read`.
3. Confirm the response includes `summary` and `dailyUsageBuckets`.
4. Also verify a session without Codex-backend auth receives the
existing auth error path.

Targeted tests:
- `just test -p codex-backend-client -p codex-app-server-protocol -p
codex-app-server`
- `just write-app-server-schema`
This commit is contained in:
Felipe Coury
2026-06-05 14:43:44 +00:00
committed by GitHub
parent 0b1512c2c8
commit 5e62c735b2
18 changed files with 553 additions and 19 deletions
@@ -949,6 +949,12 @@ client_request_definitions! {
response: v2::GetAccountRateLimitsResponse,
},
GetAccountTokenUsage => "account/usage/read" {
params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
serialization: None,
response: v2::GetAccountTokenUsageResponse,
},
SendAddCreditsNudgeEmail => "account/sendAddCreditsNudgeEmail" {
params: v2::SendAddCreditsNudgeEmailParams,
serialization: global("account-auth"),
@@ -2372,6 +2378,24 @@ mod tests {
Ok(())
}
#[test]
fn serialize_get_account_token_usage() -> Result<()> {
let request = ClientRequest::GetAccountTokenUsage {
request_id: RequestId::Integer(1),
params: None,
};
assert_eq!(request.id(), &RequestId::Integer(1));
assert_eq!(request.method(), "account/usage/read");
assert_eq!(
json!({
"method": "account/usage/read",
"id": 1,
}),
serde_json::to_value(&request)?,
);
Ok(())
}
#[test]
fn serialize_client_response() -> Result<()> {
let cwd = absolute_path("/tmp");
@@ -258,6 +258,33 @@ pub struct GetAccountRateLimitsResponse {
pub rate_limits_by_limit_id: Option<HashMap<String, RateLimitSnapshot>>,
}
#[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>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct AccountTokenUsageSummary {
pub lifetime_tokens: Option<i64>,
pub peak_daily_tokens: Option<i64>,
pub longest_running_turn_sec: Option<i64>,
pub current_streak_days: Option<i64>,
pub longest_streak_days: Option<i64>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct AccountTokenUsageDailyBucket {
pub start_date: String,
pub tokens: i64,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]