mirror of
https://github.com/openai/codex.git
synced 2026-06-02 11:22:01 +00:00
[codex] Propagate rate limit reached type (#18227)
## Summary First PR in the split from #17956. - adds the core/app-server `RateLimitReachedType` shape - maps backend `rate_limit_reached_type` into Codex rate-limit snapshots - carries the field through app-server notifications/responses and generated schemas - updates existing constructors/tests for the new optional field ## Validation - `cargo test -p codex-backend-client` - `cargo test -p codex-app-server-protocol` - `cargo test -p codex-app-server rate_limits` - `cargo test -p codex-tui workspace_` - `cargo test -p codex-tui status_` - `just fmt` - `just fix -p codex-backend-client` - `just fix -p codex-app-server-protocol` - `just fix -p codex-app-server` - `just fix -p codex-tui`
This commit is contained in:
@@ -2046,6 +2046,16 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"RateLimitReachedType": {
|
||||
"enum": [
|
||||
"rate_limit_reached",
|
||||
"workspace_owner_credits_depleted",
|
||||
"workspace_member_credits_depleted",
|
||||
"workspace_owner_usage_limit_reached",
|
||||
"workspace_member_usage_limit_reached"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"RateLimitSnapshot": {
|
||||
"properties": {
|
||||
"credits": {
|
||||
@@ -2090,6 +2100,16 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"rateLimitReachedType": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RateLimitReachedType"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"secondary": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
||||
@@ -10692,6 +10692,16 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"RateLimitReachedType": {
|
||||
"enum": [
|
||||
"rate_limit_reached",
|
||||
"workspace_owner_credits_depleted",
|
||||
"workspace_member_credits_depleted",
|
||||
"workspace_owner_usage_limit_reached",
|
||||
"workspace_member_usage_limit_reached"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"RateLimitSnapshot": {
|
||||
"properties": {
|
||||
"credits": {
|
||||
@@ -10736,6 +10746,16 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"rateLimitReachedType": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/RateLimitReachedType"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"secondary": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
||||
@@ -7444,6 +7444,16 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"RateLimitReachedType": {
|
||||
"enum": [
|
||||
"rate_limit_reached",
|
||||
"workspace_owner_credits_depleted",
|
||||
"workspace_member_credits_depleted",
|
||||
"workspace_owner_usage_limit_reached",
|
||||
"workspace_member_usage_limit_reached"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"RateLimitSnapshot": {
|
||||
"properties": {
|
||||
"credits": {
|
||||
@@ -7488,6 +7498,16 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"rateLimitReachedType": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RateLimitReachedType"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"secondary": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
||||
@@ -39,6 +39,16 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"RateLimitReachedType": {
|
||||
"enum": [
|
||||
"rate_limit_reached",
|
||||
"workspace_owner_credits_depleted",
|
||||
"workspace_member_credits_depleted",
|
||||
"workspace_owner_usage_limit_reached",
|
||||
"workspace_member_usage_limit_reached"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"RateLimitSnapshot": {
|
||||
"properties": {
|
||||
"credits": {
|
||||
@@ -83,6 +93,16 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"rateLimitReachedType": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RateLimitReachedType"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"secondary": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
||||
@@ -39,6 +39,16 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"RateLimitReachedType": {
|
||||
"enum": [
|
||||
"rate_limit_reached",
|
||||
"workspace_owner_credits_depleted",
|
||||
"workspace_member_credits_depleted",
|
||||
"workspace_owner_usage_limit_reached",
|
||||
"workspace_member_usage_limit_reached"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"RateLimitSnapshot": {
|
||||
"properties": {
|
||||
"credits": {
|
||||
@@ -83,6 +93,16 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"rateLimitReachedType": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RateLimitReachedType"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"secondary": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type RateLimitReachedType = "rate_limit_reached" | "workspace_owner_credits_depleted" | "workspace_member_credits_depleted" | "workspace_owner_usage_limit_reached" | "workspace_member_usage_limit_reached";
|
||||
@@ -3,6 +3,7 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { PlanType } from "../PlanType";
|
||||
import type { CreditsSnapshot } from "./CreditsSnapshot";
|
||||
import type { RateLimitReachedType } from "./RateLimitReachedType";
|
||||
import type { RateLimitWindow } from "./RateLimitWindow";
|
||||
|
||||
export type RateLimitSnapshot = { limitId: string | null, limitName: string | null, primary: RateLimitWindow | null, secondary: RateLimitWindow | null, credits: CreditsSnapshot | null, planType: PlanType | null, };
|
||||
export type RateLimitSnapshot = { limitId: string | null, limitName: string | null, primary: RateLimitWindow | null, secondary: RateLimitWindow | null, credits: CreditsSnapshot | null, planType: PlanType | null, rateLimitReachedType: RateLimitReachedType | null, };
|
||||
|
||||
@@ -241,6 +241,7 @@ export type { PluginUninstallParams } from "./PluginUninstallParams";
|
||||
export type { PluginUninstallResponse } from "./PluginUninstallResponse";
|
||||
export type { PluginsMigration } from "./PluginsMigration";
|
||||
export type { ProfileV2 } from "./ProfileV2";
|
||||
export type { RateLimitReachedType } from "./RateLimitReachedType";
|
||||
export type { RateLimitSnapshot } from "./RateLimitSnapshot";
|
||||
export type { RateLimitWindow } from "./RateLimitWindow";
|
||||
export type { RawResponseItemCompletedNotification } from "./RawResponseItemCompletedNotification";
|
||||
|
||||
@@ -70,6 +70,7 @@ use codex_protocol::protocol::ModelRerouteReason as CoreModelRerouteReason;
|
||||
use codex_protocol::protocol::NetworkAccess as CoreNetworkAccess;
|
||||
use codex_protocol::protocol::NonSteerableTurnKind as CoreNonSteerableTurnKind;
|
||||
use codex_protocol::protocol::PatchApplyStatus as CorePatchApplyStatus;
|
||||
use codex_protocol::protocol::RateLimitReachedType as CoreRateLimitReachedType;
|
||||
use codex_protocol::protocol::RateLimitSnapshot as CoreRateLimitSnapshot;
|
||||
use codex_protocol::protocol::RateLimitWindow as CoreRateLimitWindow;
|
||||
use codex_protocol::protocol::ReadOnlyAccess as CoreReadOnlyAccess;
|
||||
@@ -6529,6 +6530,7 @@ pub struct RateLimitSnapshot {
|
||||
pub secondary: Option<RateLimitWindow>,
|
||||
pub credits: Option<CreditsSnapshot>,
|
||||
pub plan_type: Option<PlanType>,
|
||||
pub rate_limit_reached_type: Option<RateLimitReachedType>,
|
||||
}
|
||||
|
||||
impl From<CoreRateLimitSnapshot> for RateLimitSnapshot {
|
||||
@@ -6540,6 +6542,60 @@ impl From<CoreRateLimitSnapshot> for RateLimitSnapshot {
|
||||
secondary: value.secondary.map(RateLimitWindow::from),
|
||||
credits: value.credits.map(CreditsSnapshot::from),
|
||||
plan_type: value.plan_type,
|
||||
rate_limit_reached_type: value
|
||||
.rate_limit_reached_type
|
||||
.map(RateLimitReachedType::from),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export_to = "v2/", rename_all = "snake_case")]
|
||||
pub enum RateLimitReachedType {
|
||||
RateLimitReached,
|
||||
WorkspaceOwnerCreditsDepleted,
|
||||
WorkspaceMemberCreditsDepleted,
|
||||
WorkspaceOwnerUsageLimitReached,
|
||||
WorkspaceMemberUsageLimitReached,
|
||||
}
|
||||
|
||||
impl From<CoreRateLimitReachedType> for RateLimitReachedType {
|
||||
fn from(value: CoreRateLimitReachedType) -> Self {
|
||||
match value {
|
||||
CoreRateLimitReachedType::RateLimitReached => Self::RateLimitReached,
|
||||
CoreRateLimitReachedType::WorkspaceOwnerCreditsDepleted => {
|
||||
Self::WorkspaceOwnerCreditsDepleted
|
||||
}
|
||||
CoreRateLimitReachedType::WorkspaceMemberCreditsDepleted => {
|
||||
Self::WorkspaceMemberCreditsDepleted
|
||||
}
|
||||
CoreRateLimitReachedType::WorkspaceOwnerUsageLimitReached => {
|
||||
Self::WorkspaceOwnerUsageLimitReached
|
||||
}
|
||||
CoreRateLimitReachedType::WorkspaceMemberUsageLimitReached => {
|
||||
Self::WorkspaceMemberUsageLimitReached
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RateLimitReachedType> for CoreRateLimitReachedType {
|
||||
fn from(value: RateLimitReachedType) -> Self {
|
||||
match value {
|
||||
RateLimitReachedType::RateLimitReached => Self::RateLimitReached,
|
||||
RateLimitReachedType::WorkspaceOwnerCreditsDepleted => {
|
||||
Self::WorkspaceOwnerCreditsDepleted
|
||||
}
|
||||
RateLimitReachedType::WorkspaceMemberCreditsDepleted => {
|
||||
Self::WorkspaceMemberCreditsDepleted
|
||||
}
|
||||
RateLimitReachedType::WorkspaceOwnerUsageLimitReached => {
|
||||
Self::WorkspaceOwnerUsageLimitReached
|
||||
}
|
||||
RateLimitReachedType::WorkspaceMemberUsageLimitReached => {
|
||||
Self::WorkspaceMemberUsageLimitReached
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4138,6 +4138,7 @@ mod tests {
|
||||
balance: Some("5".to_string()),
|
||||
}),
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
|
||||
handle_token_count_event(
|
||||
|
||||
@@ -736,6 +736,7 @@ mod tests {
|
||||
secondary: None,
|
||||
credits: None,
|
||||
plan_type: Some(PlanType::Plus),
|
||||
rate_limit_reached_type: None,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -754,7 +755,8 @@ mod tests {
|
||||
},
|
||||
"secondary": null,
|
||||
"credits": null,
|
||||
"planType": "plus"
|
||||
"planType": "plus",
|
||||
"rateLimitReachedType": null
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -7,6 +7,7 @@ use codex_app_server_protocol::GetAccountRateLimitsResponse;
|
||||
use codex_app_server_protocol::JSONRPCError;
|
||||
use codex_app_server_protocol::JSONRPCResponse;
|
||||
use codex_app_server_protocol::LoginAccountResponse;
|
||||
use codex_app_server_protocol::RateLimitReachedType;
|
||||
use codex_app_server_protocol::RateLimitSnapshot;
|
||||
use codex_app_server_protocol::RateLimitWindow;
|
||||
use codex_app_server_protocol::RequestId;
|
||||
@@ -118,6 +119,9 @@ async fn get_account_rate_limits_returns_snapshot() -> Result<()> {
|
||||
"reset_at": secondary_reset_timestamp,
|
||||
}
|
||||
},
|
||||
"rate_limit_reached_type": {
|
||||
"type": "workspace_member_usage_limit_reached",
|
||||
},
|
||||
"additional_rate_limits": [
|
||||
{
|
||||
"limit_name": "codex_other",
|
||||
@@ -173,6 +177,7 @@ async fn get_account_rate_limits_returns_snapshot() -> Result<()> {
|
||||
}),
|
||||
credits: None,
|
||||
plan_type: Some(AccountPlanType::Pro),
|
||||
rate_limit_reached_type: Some(RateLimitReachedType::WorkspaceMemberUsageLimitReached),
|
||||
},
|
||||
rate_limits_by_limit_id: Some(
|
||||
[
|
||||
@@ -193,6 +198,9 @@ async fn get_account_rate_limits_returns_snapshot() -> Result<()> {
|
||||
}),
|
||||
credits: None,
|
||||
plan_type: Some(AccountPlanType::Pro),
|
||||
rate_limit_reached_type: Some(
|
||||
RateLimitReachedType::WorkspaceMemberUsageLimitReached,
|
||||
),
|
||||
},
|
||||
),
|
||||
(
|
||||
@@ -208,6 +216,7 @@ async fn get_account_rate_limits_returns_snapshot() -> Result<()> {
|
||||
secondary: None,
|
||||
credits: None,
|
||||
plan_type: Some(AccountPlanType::Pro),
|
||||
rate_limit_reached_type: None,
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::types::CodeTaskDetailsResponse;
|
||||
use crate::types::ConfigFileResponse;
|
||||
use crate::types::PaginatedListTaskListItem;
|
||||
use crate::types::RateLimitReachedKind as BackendRateLimitReachedKind;
|
||||
use crate::types::RateLimitStatusPayload;
|
||||
use crate::types::TurnAttemptsSiblingTurnsResponse;
|
||||
use anyhow::Result;
|
||||
@@ -9,6 +10,7 @@ use codex_login::CodexAuth;
|
||||
use codex_login::default_client::get_codex_user_agent;
|
||||
use codex_protocol::account::PlanType as AccountPlanType;
|
||||
use codex_protocol::protocol::CreditsSnapshot;
|
||||
use codex_protocol::protocol::RateLimitReachedType;
|
||||
use codex_protocol::protocol::RateLimitSnapshot;
|
||||
use codex_protocol::protocol::RateLimitWindow;
|
||||
use reqwest::StatusCode;
|
||||
@@ -412,12 +414,17 @@ impl Client {
|
||||
payload: RateLimitStatusPayload,
|
||||
) -> Vec<RateLimitSnapshot> {
|
||||
let plan_type = Some(Self::map_plan_type(payload.plan_type));
|
||||
let rate_limit_reached_type = payload
|
||||
.rate_limit_reached_type
|
||||
.flatten()
|
||||
.and_then(|details| Self::map_rate_limit_reached_type(details.kind));
|
||||
let mut snapshots = vec![Self::make_rate_limit_snapshot(
|
||||
Some("codex".to_string()),
|
||||
/*limit_name*/ None,
|
||||
payload.rate_limit.flatten().map(|details| *details),
|
||||
payload.credits.flatten().map(|details| *details),
|
||||
plan_type,
|
||||
rate_limit_reached_type,
|
||||
)];
|
||||
if let Some(additional) = payload.additional_rate_limits.flatten() {
|
||||
snapshots.extend(additional.into_iter().map(|details| {
|
||||
@@ -427,6 +434,7 @@ impl Client {
|
||||
details.rate_limit.flatten().map(|rate_limit| *rate_limit),
|
||||
/*credits*/ None,
|
||||
plan_type,
|
||||
/*rate_limit_reached_type*/ None,
|
||||
)
|
||||
}));
|
||||
}
|
||||
@@ -439,6 +447,7 @@ impl Client {
|
||||
rate_limit: Option<crate::types::RateLimitStatusDetails>,
|
||||
credits: Option<crate::types::CreditStatusDetails>,
|
||||
plan_type: Option<AccountPlanType>,
|
||||
rate_limit_reached_type: Option<RateLimitReachedType>,
|
||||
) -> RateLimitSnapshot {
|
||||
let (primary, secondary) = match rate_limit {
|
||||
Some(details) => (
|
||||
@@ -454,6 +463,30 @@ impl Client {
|
||||
secondary,
|
||||
credits: Self::map_credits(credits),
|
||||
plan_type,
|
||||
rate_limit_reached_type,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_rate_limit_reached_type(
|
||||
kind: BackendRateLimitReachedKind,
|
||||
) -> Option<RateLimitReachedType> {
|
||||
match kind {
|
||||
BackendRateLimitReachedKind::RateLimitReached => {
|
||||
Some(RateLimitReachedType::RateLimitReached)
|
||||
}
|
||||
BackendRateLimitReachedKind::WorkspaceOwnerCreditsDepleted => {
|
||||
Some(RateLimitReachedType::WorkspaceOwnerCreditsDepleted)
|
||||
}
|
||||
BackendRateLimitReachedKind::WorkspaceMemberCreditsDepleted => {
|
||||
Some(RateLimitReachedType::WorkspaceMemberCreditsDepleted)
|
||||
}
|
||||
BackendRateLimitReachedKind::WorkspaceOwnerUsageLimitReached => {
|
||||
Some(RateLimitReachedType::WorkspaceOwnerUsageLimitReached)
|
||||
}
|
||||
BackendRateLimitReachedKind::WorkspaceMemberUsageLimitReached => {
|
||||
Some(RateLimitReachedType::WorkspaceMemberUsageLimitReached)
|
||||
}
|
||||
BackendRateLimitReachedKind::Unknown => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -521,6 +554,8 @@ impl Client {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use codex_backend_openapi_models::models::AdditionalRateLimitDetails;
|
||||
use codex_backend_openapi_models::models::RateLimitReachedKind;
|
||||
use codex_backend_openapi_models::models::RateLimitReachedType as BackendRateLimitReachedType;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
@@ -574,6 +609,9 @@ mod tests {
|
||||
balance: Some(Some("9.99".to_string())),
|
||||
..Default::default()
|
||||
}))),
|
||||
rate_limit_reached_type: Some(Some(BackendRateLimitReachedType {
|
||||
kind: RateLimitReachedKind::WorkspaceMemberCreditsDepleted,
|
||||
})),
|
||||
};
|
||||
|
||||
let snapshots = Client::rate_limit_snapshots_from_payload(payload);
|
||||
@@ -598,6 +636,10 @@ mod tests {
|
||||
})
|
||||
);
|
||||
assert_eq!(snapshots[0].plan_type, Some(AccountPlanType::Pro));
|
||||
assert_eq!(
|
||||
snapshots[0].rate_limit_reached_type,
|
||||
Some(RateLimitReachedType::WorkspaceMemberCreditsDepleted)
|
||||
);
|
||||
|
||||
assert_eq!(snapshots[1].limit_id.as_deref(), Some("codex_other"));
|
||||
assert_eq!(snapshots[1].limit_name.as_deref(), Some("codex_other"));
|
||||
@@ -607,6 +649,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(snapshots[1].credits, None);
|
||||
assert_eq!(snapshots[1].plan_type, Some(AccountPlanType::Pro));
|
||||
assert_eq!(snapshots[1].rate_limit_reached_type, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -620,6 +663,7 @@ mod tests {
|
||||
rate_limit: None,
|
||||
}])),
|
||||
credits: None,
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
|
||||
let snapshots = Client::rate_limit_snapshots_from_payload(payload);
|
||||
@@ -645,6 +689,7 @@ mod tests {
|
||||
secondary: None,
|
||||
credits: None,
|
||||
plan_type: Some(AccountPlanType::Pro),
|
||||
rate_limit_reached_type: None,
|
||||
},
|
||||
RateLimitSnapshot {
|
||||
limit_id: Some("codex".to_string()),
|
||||
@@ -657,6 +702,7 @@ mod tests {
|
||||
secondary: None,
|
||||
credits: None,
|
||||
plan_type: Some(AccountPlanType::Pro),
|
||||
rate_limit_reached_type: None,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -667,4 +713,58 @@ mod tests {
|
||||
.unwrap_or_else(|| snapshots[0].clone());
|
||||
assert_eq!(preferred.limit_id.as_deref(), Some("codex"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usage_payload_maps_every_rate_limit_reached_type() {
|
||||
let cases = [
|
||||
(
|
||||
RateLimitReachedKind::RateLimitReached,
|
||||
Some(RateLimitReachedType::RateLimitReached),
|
||||
),
|
||||
(
|
||||
RateLimitReachedKind::WorkspaceOwnerCreditsDepleted,
|
||||
Some(RateLimitReachedType::WorkspaceOwnerCreditsDepleted),
|
||||
),
|
||||
(
|
||||
RateLimitReachedKind::WorkspaceMemberCreditsDepleted,
|
||||
Some(RateLimitReachedType::WorkspaceMemberCreditsDepleted),
|
||||
),
|
||||
(
|
||||
RateLimitReachedKind::WorkspaceOwnerUsageLimitReached,
|
||||
Some(RateLimitReachedType::WorkspaceOwnerUsageLimitReached),
|
||||
),
|
||||
(
|
||||
RateLimitReachedKind::WorkspaceMemberUsageLimitReached,
|
||||
Some(RateLimitReachedType::WorkspaceMemberUsageLimitReached),
|
||||
),
|
||||
(RateLimitReachedKind::Unknown, None),
|
||||
];
|
||||
|
||||
for (kind, expected) in cases {
|
||||
let payload = RateLimitStatusPayload {
|
||||
plan_type: crate::types::PlanType::Plus,
|
||||
rate_limit: None,
|
||||
credits: None,
|
||||
additional_rate_limits: None,
|
||||
rate_limit_reached_type: Some(Some(BackendRateLimitReachedType { kind })),
|
||||
};
|
||||
|
||||
let snapshots = Client::rate_limit_snapshots_from_payload(payload);
|
||||
assert_eq!(snapshots[0].rate_limit_reached_type, expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usage_payload_preserves_absent_rate_limit_reached_type() {
|
||||
let payload = RateLimitStatusPayload {
|
||||
plan_type: crate::types::PlanType::Plus,
|
||||
rate_limit: None,
|
||||
credits: None,
|
||||
additional_rate_limits: None,
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
|
||||
let snapshots = Client::rate_limit_snapshots_from_payload(payload);
|
||||
assert_eq!(snapshots[0].rate_limit_reached_type, None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ pub use codex_backend_openapi_models::models::ConfigFileResponse;
|
||||
pub use codex_backend_openapi_models::models::CreditStatusDetails;
|
||||
pub use codex_backend_openapi_models::models::PaginatedListTaskListItem;
|
||||
pub use codex_backend_openapi_models::models::PlanType;
|
||||
pub use codex_backend_openapi_models::models::RateLimitReachedKind;
|
||||
pub use codex_backend_openapi_models::models::RateLimitStatusDetails;
|
||||
pub use codex_backend_openapi_models::models::RateLimitStatusPayload;
|
||||
pub use codex_backend_openapi_models::models::RateLimitWindowSnapshot;
|
||||
|
||||
@@ -93,6 +93,7 @@ pub fn parse_rate_limit_for_limit(
|
||||
secondary,
|
||||
credits,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -156,6 +157,7 @@ pub fn parse_rate_limit_event(payload: &str) -> Option<RateLimitSnapshot> {
|
||||
secondary,
|
||||
credits,
|
||||
plan_type: event.plan_type,
|
||||
rate_limit_reached_type: None,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,8 @@ pub use self::additional_rate_limit_details::AdditionalRateLimitDetails;
|
||||
|
||||
pub(crate) mod rate_limit_status_payload;
|
||||
pub use self::rate_limit_status_payload::PlanType;
|
||||
pub use self::rate_limit_status_payload::RateLimitReachedKind;
|
||||
pub use self::rate_limit_status_payload::RateLimitReachedType;
|
||||
pub use self::rate_limit_status_payload::RateLimitStatusPayload;
|
||||
|
||||
pub(crate) mod rate_limit_status_details;
|
||||
|
||||
@@ -37,6 +37,13 @@ pub struct RateLimitStatusPayload {
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub additional_rate_limits: Option<Option<Vec<models::AdditionalRateLimitDetails>>>,
|
||||
#[serde(
|
||||
rename = "rate_limit_reached_type",
|
||||
default,
|
||||
with = "::serde_with::rust::double_option",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub rate_limit_reached_type: Option<Option<RateLimitReachedType>>,
|
||||
}
|
||||
|
||||
impl RateLimitStatusPayload {
|
||||
@@ -46,10 +53,36 @@ impl RateLimitStatusPayload {
|
||||
rate_limit: None,
|
||||
credits: None,
|
||||
additional_rate_limits: None,
|
||||
rate_limit_reached_type: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RateLimitReachedType {
|
||||
#[serde(rename = "type")]
|
||||
pub kind: RateLimitReachedKind,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, Default,
|
||||
)]
|
||||
pub enum RateLimitReachedKind {
|
||||
#[serde(rename = "rate_limit_reached")]
|
||||
RateLimitReached,
|
||||
#[serde(rename = "workspace_owner_credits_depleted")]
|
||||
WorkspaceOwnerCreditsDepleted,
|
||||
#[serde(rename = "workspace_member_credits_depleted")]
|
||||
WorkspaceMemberCreditsDepleted,
|
||||
#[serde(rename = "workspace_owner_usage_limit_reached")]
|
||||
WorkspaceOwnerUsageLimitReached,
|
||||
#[serde(rename = "workspace_member_usage_limit_reached")]
|
||||
WorkspaceMemberUsageLimitReached,
|
||||
#[serde(rename = "unknown", other)]
|
||||
#[default]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, Default,
|
||||
)]
|
||||
|
||||
@@ -2070,6 +2070,7 @@ async fn set_rate_limits_retains_previous_credits() {
|
||||
balance: Some("10.00".to_string()),
|
||||
}),
|
||||
plan_type: Some(codex_protocol::account::PlanType::Plus),
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
state.set_rate_limits(initial.clone());
|
||||
|
||||
@@ -2088,6 +2089,7 @@ async fn set_rate_limits_retains_previous_credits() {
|
||||
}),
|
||||
credits: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
state.set_rate_limits(update.clone());
|
||||
|
||||
@@ -2100,6 +2102,7 @@ async fn set_rate_limits_retains_previous_credits() {
|
||||
secondary: update.secondary,
|
||||
credits: initial.credits,
|
||||
plan_type: initial.plan_type,
|
||||
rate_limit_reached_type: None,
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -2176,6 +2179,7 @@ async fn set_rate_limits_updates_plan_type_when_present() {
|
||||
balance: Some("15.00".to_string()),
|
||||
}),
|
||||
plan_type: Some(codex_protocol::account::PlanType::Plus),
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
state.set_rate_limits(initial.clone());
|
||||
|
||||
@@ -2190,6 +2194,7 @@ async fn set_rate_limits_updates_plan_type_when_present() {
|
||||
secondary: None,
|
||||
credits: None,
|
||||
plan_type: Some(codex_protocol::account::PlanType::Pro),
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
state.set_rate_limits(update.clone());
|
||||
|
||||
@@ -2202,6 +2207,7 @@ async fn set_rate_limits_updates_plan_type_when_present() {
|
||||
secondary: update.secondary,
|
||||
credits: initial.credits,
|
||||
plan_type: update.plan_type,
|
||||
rate_limit_reached_type: None,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -87,6 +87,7 @@ async fn set_rate_limits_defaults_limit_id_to_codex_when_missing() {
|
||||
secondary: None,
|
||||
credits: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
@@ -114,6 +115,7 @@ async fn set_rate_limits_defaults_to_codex_when_limit_id_missing_after_other_buc
|
||||
secondary: None,
|
||||
credits: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
});
|
||||
state.set_rate_limits(RateLimitSnapshot {
|
||||
limit_id: None,
|
||||
@@ -126,6 +128,7 @@ async fn set_rate_limits_defaults_to_codex_when_limit_id_missing_after_other_buc
|
||||
secondary: None,
|
||||
credits: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
@@ -157,6 +160,7 @@ async fn set_rate_limits_carries_credits_and_plan_type_from_codex_to_codex_other
|
||||
balance: Some("50".to_string()),
|
||||
}),
|
||||
plan_type: Some(codex_protocol::account::PlanType::Plus),
|
||||
rate_limit_reached_type: None,
|
||||
});
|
||||
|
||||
state.set_rate_limits(RateLimitSnapshot {
|
||||
@@ -170,6 +174,7 @@ async fn set_rate_limits_carries_credits_and_plan_type_from_codex_to_codex_other
|
||||
secondary: None,
|
||||
credits: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
@@ -189,6 +194,7 @@ async fn set_rate_limits_carries_credits_and_plan_type_from_codex_to_codex_other
|
||||
balance: Some("50".to_string()),
|
||||
}),
|
||||
plan_type: Some(codex_protocol::account::PlanType::Plus),
|
||||
rate_limit_reached_type: None,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2375,7 +2375,8 @@ async fn token_count_includes_rate_limits_snapshot() {
|
||||
"resets_at": 1704074400
|
||||
},
|
||||
"credits": null,
|
||||
"plan_type": null
|
||||
"plan_type": null,
|
||||
"rate_limit_reached_type": null
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -2426,7 +2427,8 @@ async fn token_count_includes_rate_limits_snapshot() {
|
||||
"resets_at": 1704074400
|
||||
},
|
||||
"credits": null,
|
||||
"plan_type": null
|
||||
"plan_type": null,
|
||||
"rate_limit_reached_type": null
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -2500,7 +2502,8 @@ async fn usage_limit_error_emits_rate_limit_event() -> anyhow::Result<()> {
|
||||
"resets_at": null
|
||||
},
|
||||
"credits": null,
|
||||
"plan_type": null
|
||||
"plan_type": null,
|
||||
"rate_limit_reached_type": null
|
||||
});
|
||||
|
||||
let submission_id = codex
|
||||
|
||||
@@ -1033,7 +1033,8 @@ async fn responses_websocket_usage_limit_error_emits_rate_limit_event() {
|
||||
"resets_at": null
|
||||
},
|
||||
"credits": null,
|
||||
"plan_type": null
|
||||
"plan_type": null,
|
||||
"rate_limit_reached_type": null
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@@ -36,6 +36,7 @@ fn rate_limit_snapshot() -> RateLimitSnapshot {
|
||||
}),
|
||||
credits: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2200,6 +2200,18 @@ pub struct RateLimitSnapshot {
|
||||
pub secondary: Option<RateLimitWindow>,
|
||||
pub credits: Option<CreditsSnapshot>,
|
||||
pub plan_type: Option<crate::account::PlanType>,
|
||||
pub rate_limit_reached_type: Option<RateLimitReachedType>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(rename_all = "snake_case")]
|
||||
pub enum RateLimitReachedType {
|
||||
RateLimitReached,
|
||||
WorkspaceOwnerCreditsDepleted,
|
||||
WorkspaceMemberCreditsDepleted,
|
||||
WorkspaceOwnerUsageLimitReached,
|
||||
WorkspaceMemberUsageLimitReached,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
|
||||
|
||||
@@ -1188,6 +1188,7 @@ pub(crate) fn app_server_rate_limit_snapshot_to_core(
|
||||
secondary: snapshot.secondary.map(app_server_rate_limit_window_to_core),
|
||||
credits: snapshot.credits.map(app_server_credits_snapshot_to_core),
|
||||
plan_type: snapshot.plan_type,
|
||||
rate_limit_reached_type: snapshot.rate_limit_reached_type.map(Into::into),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -106,6 +106,7 @@ pub(super) fn snapshot(percent: f64) -> RateLimitSnapshot {
|
||||
secondary: None,
|
||||
credits: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -235,6 +235,7 @@ async fn rate_limit_snapshot_keeps_prior_credits_when_missing_from_headers() {
|
||||
balance: Some("17.5".to_string()),
|
||||
}),
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
}));
|
||||
let initial_balance = chat
|
||||
.rate_limit_snapshots_by_limit_id
|
||||
@@ -254,6 +255,7 @@ async fn rate_limit_snapshot_keeps_prior_credits_when_missing_from_headers() {
|
||||
secondary: None,
|
||||
credits: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
}));
|
||||
|
||||
let display = chat
|
||||
@@ -292,6 +294,7 @@ async fn rate_limit_snapshot_updates_and_retains_plan_type() {
|
||||
}),
|
||||
credits: None,
|
||||
plan_type: Some(PlanType::Plus),
|
||||
rate_limit_reached_type: None,
|
||||
}));
|
||||
assert_eq!(chat.plan_type, Some(PlanType::Plus));
|
||||
|
||||
@@ -310,6 +313,7 @@ async fn rate_limit_snapshot_updates_and_retains_plan_type() {
|
||||
}),
|
||||
credits: None,
|
||||
plan_type: Some(PlanType::Pro),
|
||||
rate_limit_reached_type: None,
|
||||
}));
|
||||
assert_eq!(chat.plan_type, Some(PlanType::Pro));
|
||||
|
||||
@@ -328,6 +332,7 @@ async fn rate_limit_snapshot_updates_and_retains_plan_type() {
|
||||
}),
|
||||
credits: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
}));
|
||||
assert_eq!(chat.plan_type, Some(PlanType::Pro));
|
||||
}
|
||||
@@ -351,6 +356,7 @@ async fn rate_limit_snapshots_keep_separate_entries_per_limit_id() {
|
||||
balance: Some("5.00".to_string()),
|
||||
}),
|
||||
plan_type: Some(PlanType::Pro),
|
||||
rate_limit_reached_type: None,
|
||||
}));
|
||||
|
||||
chat.on_rate_limit_snapshot(Some(RateLimitSnapshot {
|
||||
@@ -364,6 +370,7 @@ async fn rate_limit_snapshots_keep_separate_entries_per_limit_id() {
|
||||
secondary: None,
|
||||
credits: None,
|
||||
plan_type: Some(PlanType::Pro),
|
||||
rate_limit_reached_type: None,
|
||||
}));
|
||||
|
||||
let codex = chat
|
||||
@@ -416,6 +423,7 @@ async fn rate_limit_switch_prompt_skips_non_codex_limit() {
|
||||
secondary: None,
|
||||
credits: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
}));
|
||||
|
||||
assert!(matches!(
|
||||
|
||||
@@ -139,6 +139,7 @@ async fn status_snapshot_includes_reasoning_details() {
|
||||
}),
|
||||
credits: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
|
||||
|
||||
@@ -322,6 +323,7 @@ async fn status_snapshot_includes_monthly_limit() {
|
||||
secondary: None,
|
||||
credits: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
|
||||
|
||||
@@ -373,6 +375,7 @@ async fn status_snapshot_shows_unlimited_credits() {
|
||||
balance: None,
|
||||
}),
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
|
||||
let model_slug = crate::legacy_core::test_support::get_model_offline(config.model.as_deref());
|
||||
@@ -422,6 +425,7 @@ async fn status_snapshot_shows_positive_credits() {
|
||||
balance: Some("12.5".to_string()),
|
||||
}),
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
|
||||
let model_slug = crate::legacy_core::test_support::get_model_offline(config.model.as_deref());
|
||||
@@ -471,6 +475,7 @@ async fn status_snapshot_hides_zero_credits() {
|
||||
balance: Some("0".to_string()),
|
||||
}),
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
|
||||
let model_slug = crate::legacy_core::test_support::get_model_offline(config.model.as_deref());
|
||||
@@ -518,6 +523,7 @@ async fn status_snapshot_hides_when_has_no_credits_flag() {
|
||||
balance: None,
|
||||
}),
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
|
||||
let model_slug = crate::legacy_core::test_support::get_model_offline(config.model.as_deref());
|
||||
@@ -623,6 +629,7 @@ async fn status_snapshot_truncates_in_narrow_terminal() {
|
||||
secondary: None,
|
||||
credits: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
|
||||
|
||||
@@ -786,6 +793,7 @@ async fn status_snapshot_shows_refreshing_limits_notice() {
|
||||
}),
|
||||
credits: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
|
||||
|
||||
@@ -856,6 +864,7 @@ async fn status_snapshot_includes_credits_and_limits() {
|
||||
balance: Some("37.5".to_string()),
|
||||
}),
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
|
||||
|
||||
@@ -909,6 +918,7 @@ async fn status_snapshot_shows_unavailable_limits_message() {
|
||||
secondary: None,
|
||||
credits: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
let captured_at = chrono::Local
|
||||
.with_ymd_and_hms(2024, 6, 7, 8, 9, 10)
|
||||
@@ -965,6 +975,7 @@ async fn status_snapshot_treats_refreshing_empty_limits_as_unavailable() {
|
||||
secondary: None,
|
||||
credits: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
let captured_at = chrono::Local
|
||||
.with_ymd_and_hms(2024, 6, 7, 8, 9, 10)
|
||||
@@ -1035,6 +1046,7 @@ async fn status_snapshot_shows_stale_limits_message() {
|
||||
}),
|
||||
credits: None,
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
|
||||
let now = captured_at + ChronoDuration::minutes(20);
|
||||
@@ -1105,6 +1117,7 @@ async fn status_snapshot_cached_limits_hide_credits_without_flag() {
|
||||
balance: Some("80".to_string()),
|
||||
}),
|
||||
plan_type: None,
|
||||
rate_limit_reached_type: None,
|
||||
};
|
||||
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
|
||||
let now = captured_at + ChronoDuration::minutes(20);
|
||||
|
||||
Reference in New Issue
Block a user