Extend rate limit reads with usage capabilities (#42358)

## What changed

- Accept optional `account/rateLimits/read` capabilities for Luna Reserve support
  and lightweight background polling while preserving omitted and `null` params.
- Forward the Luna Reserve capability only for eligible ChatGPT sessions, and let
  lightweight polls skip the separate reset-credit detail request.
- Return the backend's `ordinaryUsageAllowed` decision only when its account and
  user identity match the active session.

## Testing

- Cover capability defaults, eligible and restricted authentication modes,
  lightweight polling, identity filtering, and backend permission handling.

GitOrigin-RevId: f407a3ae1e70e411fdf245f072a16e453f59cd87
This commit is contained in:
dhruvgupta-oai
2026-09-02 21:01:04 +00:00
committed by copyberry
parent 0227158fd5
commit 577a4fcd06
26 changed files with 459 additions and 28 deletions
+15 -6
View File
@@ -2154,6 +2154,11 @@ mod tests {
"params?: GetAccountTokenUsageParams | undefined, }"
);
assert!(client_request_ts.contains(LEGACY_ACCOUNT_USAGE_REQUEST));
const LEGACY_ACCOUNT_RATE_LIMITS_REQUEST: &str = concat!(
"{ \"method\": \"account/rateLimits/read\", id: RequestId, ",
"params?: GetAccountRateLimitsParams | undefined, }"
);
assert!(client_request_ts.contains(LEGACY_ACCOUNT_RATE_LIMITS_REQUEST));
let account_usage_response_ts = std::str::from_utf8(
fixture_tree
.get(Path::new("v2/GetAccountTokenUsageResponse.ts"))
@@ -2232,12 +2237,16 @@ mod tests {
});
let contents = std::str::from_utf8(contents)?;
// The stable usage RPC originally required `params: undefined`. Keep that exact
// legacy value accepted while extending the same method with optional thread params.
let legacy_account_usage_undefined = path == Path::new("ClientRequest.ts")
&& contents.matches("| undefined").count() == 1
&& contents.contains(LEGACY_ACCOUNT_USAGE_REQUEST);
if contents.contains("| undefined") && !legacy_account_usage_undefined {
// Both stable usage RPCs originally required `params: undefined`. Preserve that
// source compatibility only for these exact envelopes, not arbitrary new fields.
let checked_contents = if path == Path::new("ClientRequest.ts") {
contents
.replace(LEGACY_ACCOUNT_USAGE_REQUEST, "")
.replace(LEGACY_ACCOUNT_RATE_LIMITS_REQUEST, "")
} else {
contents.to_owned()
};
if checked_contents.contains("| undefined") {
undefined_offenders.push(path.clone());
}
@@ -493,6 +493,16 @@ enum GetAccountTokenUsageParamsTypeScript {
Undefined,
}
/// Preserve omitted/undefined params while exporting the new usage capability type.
#[allow(dead_code)]
#[derive(TS)]
#[ts(untagged)]
enum GetAccountRateLimitsParamsTypeScript {
Params(v2::GetAccountRateLimitsParams),
#[ts(type = "undefined")]
Undefined,
}
client_request_definitions! {
Initialize => "initialize" {
params: v1::InitializeParams,
@@ -1232,7 +1242,7 @@ client_request_definitions! {
},
GetAccountRateLimits => "account/rateLimits/read" {
params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
params: #[ts(optional, as = "Option<GetAccountRateLimitsParamsTypeScript>", inline)] #[serde(default, skip_serializing_if = "Option::is_none")] v2::NullableGetAccountRateLimitsParams,
serialization: None,
response: v2::GetAccountRateLimitsResponse,
},
@@ -308,10 +308,30 @@ impl fmt::Debug for ChatgptAuthTokensRefreshResponse {
}
}
/// Usage-read capabilities of the requesting client, never inferred from its experiment arm.
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct GetAccountRateLimitsParams {
/// The client supports automatic Luna Reserve fallback. For eligible ChatGPT CLI users,
/// allow the backend to record experiment exposure after ordinary usage is blocked.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub supports_luna_reserve: bool,
/// Skip the separate reset-credit detail lookup for background usage polls. The usage
/// response still includes the available count; omitted/false preserves detailed reads.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub exclude_reset_credit_details: bool,
}
pub type NullableGetAccountRateLimitsParams = Option<GetAccountRateLimitsParams>;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct GetAccountRateLimitsResponse {
/// Backend permission for ordinary included usage, validated against the active account.
/// Null means unavailable; clients must not infer recovery from percentages or reset times.
pub ordinary_usage_allowed: Option<bool>,
/// Backward-compatible single-bucket view; mirrors the historical payload.
pub rate_limits: RateLimitSnapshot,
/// Multi-bucket view keyed by metered `limit_id` (for example, `codex`).