Compare commits

...

1 Commits

Author SHA1 Message Date
James Chu
9b42be8d6b [codex] Thread backend-selected near-limit prompt state through client models 2026-04-29 13:21:00 -04:00
30 changed files with 1064 additions and 3 deletions

View File

@@ -934,6 +934,62 @@
],
"type": "object"
},
"CurrentUsageLimitNudgeState": {
"oneOf": [
{
"properties": {
"type": {
"enum": [
"unknown"
],
"title": "UnknownCurrentUsageLimitNudgeStateType",
"type": "string"
}
},
"required": [
"type"
],
"title": "UnknownCurrentUsageLimitNudgeState",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"inactive"
],
"title": "InactiveCurrentUsageLimitNudgeStateType",
"type": "string"
}
},
"required": [
"type"
],
"title": "InactiveCurrentUsageLimitNudgeState",
"type": "object"
},
{
"properties": {
"nudge": {
"$ref": "#/definitions/UsageLimitNudge"
},
"type": {
"enum": [
"active"
],
"title": "ActiveCurrentUsageLimitNudgeStateType",
"type": "string"
}
},
"required": [
"nudge",
"type"
],
"title": "ActiveCurrentUsageLimitNudgeState",
"type": "object"
}
]
},
"DeprecationNoticeNotification": {
"properties": {
"details": {
@@ -2422,6 +2478,16 @@
}
]
},
"currentUsageLimitNudge": {
"allOf": [
{
"$ref": "#/definitions/CurrentUsageLimitNudgeState"
}
],
"default": {
"type": "unknown"
}
},
"limitId": {
"type": [
"string",
@@ -4343,6 +4409,34 @@
],
"type": "string"
},
"UsageLimitNudge": {
"properties": {
"action": {
"$ref": "#/definitions/UsageLimitNudgeAction"
},
"key": {
"type": "string"
},
"threshold": {
"format": "uint8",
"minimum": 0.0,
"type": "integer"
}
},
"required": [
"action",
"key",
"threshold"
],
"type": "object"
},
"UsageLimitNudgeAction": {
"enum": [
"add_credits",
"upgrade"
],
"type": "string"
},
"UserInput": {
"oneOf": [
{

View File

@@ -7636,6 +7636,62 @@
],
"type": "object"
},
"CurrentUsageLimitNudgeState": {
"oneOf": [
{
"properties": {
"type": {
"enum": [
"unknown"
],
"title": "UnknownCurrentUsageLimitNudgeStateType",
"type": "string"
}
},
"required": [
"type"
],
"title": "UnknownCurrentUsageLimitNudgeState",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"inactive"
],
"title": "InactiveCurrentUsageLimitNudgeStateType",
"type": "string"
}
},
"required": [
"type"
],
"title": "InactiveCurrentUsageLimitNudgeState",
"type": "object"
},
{
"properties": {
"nudge": {
"$ref": "#/definitions/v2/UsageLimitNudge"
},
"type": {
"enum": [
"active"
],
"title": "ActiveCurrentUsageLimitNudgeStateType",
"type": "string"
}
},
"required": [
"nudge",
"type"
],
"title": "ActiveCurrentUsageLimitNudgeState",
"type": "object"
}
]
},
"DeprecationNoticeNotification": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
@@ -12114,6 +12170,16 @@
}
]
},
"currentUsageLimitNudge": {
"allOf": [
{
"$ref": "#/definitions/v2/CurrentUsageLimitNudgeState"
}
],
"default": {
"type": "unknown"
}
},
"limitId": {
"type": [
"string",
@@ -17186,6 +17252,34 @@
"title": "TurnSteerResponse",
"type": "object"
},
"UsageLimitNudge": {
"properties": {
"action": {
"$ref": "#/definitions/v2/UsageLimitNudgeAction"
},
"key": {
"type": "string"
},
"threshold": {
"format": "uint8",
"minimum": 0.0,
"type": "integer"
}
},
"required": [
"action",
"key",
"threshold"
],
"type": "object"
},
"UsageLimitNudgeAction": {
"enum": [
"add_credits",
"upgrade"
],
"type": "string"
},
"UserInput": {
"oneOf": [
{

View File

@@ -4155,6 +4155,62 @@
],
"type": "object"
},
"CurrentUsageLimitNudgeState": {
"oneOf": [
{
"properties": {
"type": {
"enum": [
"unknown"
],
"title": "UnknownCurrentUsageLimitNudgeStateType",
"type": "string"
}
},
"required": [
"type"
],
"title": "UnknownCurrentUsageLimitNudgeState",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"inactive"
],
"title": "InactiveCurrentUsageLimitNudgeStateType",
"type": "string"
}
},
"required": [
"type"
],
"title": "InactiveCurrentUsageLimitNudgeState",
"type": "object"
},
{
"properties": {
"nudge": {
"$ref": "#/definitions/UsageLimitNudge"
},
"type": {
"enum": [
"active"
],
"title": "ActiveCurrentUsageLimitNudgeStateType",
"type": "string"
}
},
"required": [
"nudge",
"type"
],
"title": "ActiveCurrentUsageLimitNudgeState",
"type": "object"
}
]
},
"DeprecationNoticeNotification": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
@@ -8788,6 +8844,16 @@
}
]
},
"currentUsageLimitNudge": {
"allOf": [
{
"$ref": "#/definitions/CurrentUsageLimitNudgeState"
}
],
"default": {
"type": "unknown"
}
},
"limitId": {
"type": [
"string",
@@ -15072,6 +15138,34 @@
"title": "TurnSteerResponse",
"type": "object"
},
"UsageLimitNudge": {
"properties": {
"action": {
"$ref": "#/definitions/UsageLimitNudgeAction"
},
"key": {
"type": "string"
},
"threshold": {
"format": "uint8",
"minimum": 0.0,
"type": "integer"
}
},
"required": [
"action",
"key",
"threshold"
],
"type": "object"
},
"UsageLimitNudgeAction": {
"enum": [
"add_credits",
"upgrade"
],
"type": "string"
},
"UserInput": {
"oneOf": [
{

View File

@@ -22,6 +22,62 @@
],
"type": "object"
},
"CurrentUsageLimitNudgeState": {
"oneOf": [
{
"properties": {
"type": {
"enum": [
"unknown"
],
"title": "UnknownCurrentUsageLimitNudgeStateType",
"type": "string"
}
},
"required": [
"type"
],
"title": "UnknownCurrentUsageLimitNudgeState",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"inactive"
],
"title": "InactiveCurrentUsageLimitNudgeStateType",
"type": "string"
}
},
"required": [
"type"
],
"title": "InactiveCurrentUsageLimitNudgeState",
"type": "object"
},
{
"properties": {
"nudge": {
"$ref": "#/definitions/UsageLimitNudge"
},
"type": {
"enum": [
"active"
],
"title": "ActiveCurrentUsageLimitNudgeStateType",
"type": "string"
}
},
"required": [
"nudge",
"type"
],
"title": "ActiveCurrentUsageLimitNudgeState",
"type": "object"
}
]
},
"PlanType": {
"enum": [
"free",
@@ -61,6 +117,16 @@
}
]
},
"currentUsageLimitNudge": {
"allOf": [
{
"$ref": "#/definitions/CurrentUsageLimitNudgeState"
}
],
"default": {
"type": "unknown"
}
},
"limitId": {
"type": [
"string",
@@ -141,6 +207,34 @@
"usedPercent"
],
"type": "object"
},
"UsageLimitNudge": {
"properties": {
"action": {
"$ref": "#/definitions/UsageLimitNudgeAction"
},
"key": {
"type": "string"
},
"threshold": {
"format": "uint8",
"minimum": 0.0,
"type": "integer"
}
},
"required": [
"action",
"key",
"threshold"
],
"type": "object"
},
"UsageLimitNudgeAction": {
"enum": [
"add_credits",
"upgrade"
],
"type": "string"
}
},
"properties": {

View File

@@ -22,6 +22,62 @@
],
"type": "object"
},
"CurrentUsageLimitNudgeState": {
"oneOf": [
{
"properties": {
"type": {
"enum": [
"unknown"
],
"title": "UnknownCurrentUsageLimitNudgeStateType",
"type": "string"
}
},
"required": [
"type"
],
"title": "UnknownCurrentUsageLimitNudgeState",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"inactive"
],
"title": "InactiveCurrentUsageLimitNudgeStateType",
"type": "string"
}
},
"required": [
"type"
],
"title": "InactiveCurrentUsageLimitNudgeState",
"type": "object"
},
{
"properties": {
"nudge": {
"$ref": "#/definitions/UsageLimitNudge"
},
"type": {
"enum": [
"active"
],
"title": "ActiveCurrentUsageLimitNudgeStateType",
"type": "string"
}
},
"required": [
"nudge",
"type"
],
"title": "ActiveCurrentUsageLimitNudgeState",
"type": "object"
}
]
},
"PlanType": {
"enum": [
"free",
@@ -61,6 +117,16 @@
}
]
},
"currentUsageLimitNudge": {
"allOf": [
{
"$ref": "#/definitions/CurrentUsageLimitNudgeState"
}
],
"default": {
"type": "unknown"
}
},
"limitId": {
"type": [
"string",
@@ -141,6 +207,34 @@
"usedPercent"
],
"type": "object"
},
"UsageLimitNudge": {
"properties": {
"action": {
"$ref": "#/definitions/UsageLimitNudgeAction"
},
"key": {
"type": "string"
},
"threshold": {
"format": "uint8",
"minimum": 0.0,
"type": "integer"
}
},
"required": [
"action",
"key",
"threshold"
],
"type": "object"
},
"UsageLimitNudgeAction": {
"enum": [
"add_credits",
"upgrade"
],
"type": "string"
}
},
"properties": {

View File

@@ -0,0 +1,6 @@
// 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.
import type { UsageLimitNudge } from "./UsageLimitNudge";
export type CurrentUsageLimitNudgeState = { "type": "unknown" } | { "type": "inactive" } | { "type": "active", nudge: UsageLimitNudge, };

View File

@@ -3,7 +3,8 @@
// 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 { CurrentUsageLimitNudgeState } from "./CurrentUsageLimitNudgeState";
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, rateLimitReachedType: RateLimitReachedType | 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, currentUsageLimitNudge: CurrentUsageLimitNudgeState, };

View File

@@ -0,0 +1,6 @@
// 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.
import type { UsageLimitNudgeAction } from "./UsageLimitNudgeAction";
export type UsageLimitNudge = { key: string, threshold: number, action: UsageLimitNudgeAction, };

View File

@@ -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 UsageLimitNudgeAction = "add_credits" | "upgrade";

View File

@@ -75,6 +75,7 @@ export type { ConfiguredHookHandler } from "./ConfiguredHookHandler";
export type { ConfiguredHookMatcherGroup } from "./ConfiguredHookMatcherGroup";
export type { ContextCompactedNotification } from "./ContextCompactedNotification";
export type { CreditsSnapshot } from "./CreditsSnapshot";
export type { CurrentUsageLimitNudgeState } from "./CurrentUsageLimitNudgeState";
export type { DeprecationNoticeNotification } from "./DeprecationNoticeNotification";
export type { DeviceKeyAlgorithm } from "./DeviceKeyAlgorithm";
export type { DeviceKeyCreateParams } from "./DeviceKeyCreateParams";
@@ -403,6 +404,8 @@ export type { TurnStartedNotification } from "./TurnStartedNotification";
export type { TurnStatus } from "./TurnStatus";
export type { TurnSteerParams } from "./TurnSteerParams";
export type { TurnSteerResponse } from "./TurnSteerResponse";
export type { UsageLimitNudge } from "./UsageLimitNudge";
export type { UsageLimitNudgeAction } from "./UsageLimitNudgeAction";
export type { UserInput } from "./UserInput";
export type { WarningNotification } from "./WarningNotification";
export type { WebSearchAction } from "./WebSearchAction";

View File

@@ -61,6 +61,7 @@ use codex_protocol::protocol::AgentStatus as CoreAgentStatus;
use codex_protocol::protocol::AskForApproval as CoreAskForApproval;
use codex_protocol::protocol::CodexErrorInfo as CoreCodexErrorInfo;
use codex_protocol::protocol::CreditsSnapshot as CoreCreditsSnapshot;
use codex_protocol::protocol::CurrentUsageLimitNudgeState as CoreCurrentUsageLimitNudgeState;
use codex_protocol::protocol::ExecCommandSource as CoreExecCommandSource;
use codex_protocol::protocol::ExecCommandStatus as CoreExecCommandStatus;
use codex_protocol::protocol::GranularApprovalConfig as CoreGranularApprovalConfig;
@@ -99,6 +100,8 @@ use codex_protocol::protocol::SubAgentSource as CoreSubAgentSource;
use codex_protocol::protocol::ThreadGoalStatus as CoreThreadGoalStatus;
use codex_protocol::protocol::TokenUsage as CoreTokenUsage;
use codex_protocol::protocol::TokenUsageInfo as CoreTokenUsageInfo;
use codex_protocol::protocol::UsageLimitNudge as CoreUsageLimitNudge;
use codex_protocol::protocol::UsageLimitNudgeAction as CoreUsageLimitNudgeAction;
use codex_protocol::request_permissions::PermissionGrantScope as CorePermissionGrantScope;
use codex_protocol::request_permissions::RequestPermissionProfile as CoreRequestPermissionProfile;
use codex_protocol::user_input::ByteRange as CoreByteRange;
@@ -7514,10 +7517,14 @@ pub struct RateLimitSnapshot {
pub credits: Option<CreditsSnapshot>,
pub plan_type: Option<PlanType>,
pub rate_limit_reached_type: Option<RateLimitReachedType>,
#[serde(default)]
pub current_usage_limit_nudge: CurrentUsageLimitNudgeState,
}
impl From<CoreRateLimitSnapshot> for RateLimitSnapshot {
fn from(value: CoreRateLimitSnapshot) -> Self {
let current_usage_limit_nudge =
CurrentUsageLimitNudgeState::from(value.current_usage_limit_nudge_state());
Self {
limit_id: value.limit_id,
limit_name: value.limit_name,
@@ -7528,6 +7535,79 @@ impl From<CoreRateLimitSnapshot> for RateLimitSnapshot {
rate_limit_reached_type: value
.rate_limit_reached_type
.map(RateLimitReachedType::from),
current_usage_limit_nudge,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "camelCase")]
#[ts(tag = "type", export_to = "v2/")]
pub enum CurrentUsageLimitNudgeState {
Unknown,
Inactive,
Active { nudge: UsageLimitNudge },
}
impl Default for CurrentUsageLimitNudgeState {
fn default() -> Self {
Self::Unknown
}
}
impl From<CoreCurrentUsageLimitNudgeState> for CurrentUsageLimitNudgeState {
fn from(value: CoreCurrentUsageLimitNudgeState) -> Self {
match value {
CoreCurrentUsageLimitNudgeState::Unknown => Self::Unknown,
CoreCurrentUsageLimitNudgeState::Inactive => Self::Inactive,
CoreCurrentUsageLimitNudgeState::Active(nudge) => Self::Active {
nudge: UsageLimitNudge::from(nudge),
},
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct UsageLimitNudge {
pub key: String,
pub threshold: u8,
pub action: UsageLimitNudgeAction,
}
impl From<CoreUsageLimitNudge> for UsageLimitNudge {
fn from(value: CoreUsageLimitNudge) -> Self {
Self {
key: value.key,
threshold: value.threshold.as_percent(),
action: value.action.into(),
}
}
}
#[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 UsageLimitNudgeAction {
AddCredits,
Upgrade,
}
impl From<CoreUsageLimitNudgeAction> for UsageLimitNudgeAction {
fn from(value: CoreUsageLimitNudgeAction) -> Self {
match value {
CoreUsageLimitNudgeAction::AddCredits => Self::AddCredits,
CoreUsageLimitNudgeAction::Upgrade => Self::Upgrade,
}
}
}
impl From<UsageLimitNudgeAction> for CoreUsageLimitNudgeAction {
fn from(value: UsageLimitNudgeAction) -> Self {
match value {
UsageLimitNudgeAction::AddCredits => Self::AddCredits,
UsageLimitNudgeAction::Upgrade => Self::Upgrade,
}
}
}
@@ -10803,4 +10883,94 @@ mod tests {
"unexpected error: {err}"
);
}
#[test]
fn rate_limit_snapshot_preserves_current_usage_limit_nudge_states() {
let active: RateLimitSnapshot = serde_json::from_value(json!({
"limitId": "codex",
"limitName": null,
"primary": null,
"secondary": null,
"credits": null,
"planType": null,
"rateLimitReachedType": null,
"currentUsageLimitNudge": {
"type": "active",
"nudge": {
"key": "near_limit_75_upgrade",
"threshold": 75,
"action": "upgrade"
}
}
}))
.expect("active snapshot should deserialize");
assert_eq!(
active.current_usage_limit_nudge,
CurrentUsageLimitNudgeState::Active {
nudge: UsageLimitNudge {
key: "near_limit_75_upgrade".to_string(),
threshold: 75,
action: UsageLimitNudgeAction::Upgrade,
},
}
);
assert_eq!(
serde_json::to_value(&active)
.expect("active snapshot should serialize")
.get("currentUsageLimitNudge"),
Some(&json!({
"type": "active",
"nudge": {
"key": "near_limit_75_upgrade",
"threshold": 75,
"action": "upgrade"
}
}))
);
let inactive: RateLimitSnapshot = serde_json::from_value(json!({
"limitId": "codex",
"limitName": null,
"primary": null,
"secondary": null,
"credits": null,
"planType": null,
"rateLimitReachedType": null,
"currentUsageLimitNudge": {
"type": "inactive"
}
}))
.expect("inactive snapshot should deserialize");
assert_eq!(
inactive.current_usage_limit_nudge,
CurrentUsageLimitNudgeState::Inactive
);
assert_eq!(
serde_json::to_value(&inactive)
.expect("inactive snapshot should serialize")
.get("currentUsageLimitNudge"),
Some(&json!({ "type": "inactive" }))
);
let missing: RateLimitSnapshot = serde_json::from_value(json!({
"limitId": "codex",
"limitName": null,
"primary": null,
"secondary": null,
"credits": null,
"planType": null,
"rateLimitReachedType": null
}))
.expect("missing snapshot should deserialize");
assert_eq!(
missing.current_usage_limit_nudge,
CurrentUsageLimitNudgeState::Unknown
);
assert_eq!(
serde_json::to_value(&missing)
.expect("missing snapshot should serialize")
.get("currentUsageLimitNudge"),
Some(&json!({ "type": "unknown" }))
);
}
}

View File

@@ -1651,7 +1651,7 @@ Field notes:
```json
{ "method": "account/rateLimits/read", "id": 7 }
{ "id": 7, "result": { "rateLimits": { "primary": { "usedPercent": 25, "windowDurationMins": 15, "resetsAt": 1730947200 }, "secondary": null, "rateLimitReachedType": null } } }
{ "id": 7, "result": { "rateLimits": { "primary": { "usedPercent": 25, "windowDurationMins": 15, "resetsAt": 1730947200 }, "secondary": null, "rateLimitReachedType": null, "currentUsageLimitNudge": { "type": "active", "nudge": { "key": "near_limit_75_add_credits", "threshold": 75, "action": "add_credits" } } } } }
{ "method": "account/rateLimits/updated", "params": { "rateLimits": { … } } }
```
@@ -1661,6 +1661,7 @@ Field notes:
- `windowDurationMins` is the quota window length.
- `resetsAt` is a Unix timestamp (seconds) for the next reset.
- `rateLimitReachedType` identifies the backend-classified limit state when one has been reached.
- `currentUsageLimitNudge` is the current backend-selected near-limit prompt state. Its `type` is `unknown`, `inactive`, or `active`; active states include the nudge payload under `nudge`.
### 8) Notify a workspace owner about a limit

View File

@@ -4471,6 +4471,7 @@ mod tests {
}),
plan_type: None,
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
};
handle_token_count_event(

View File

@@ -666,6 +666,7 @@ mod tests {
use codex_app_server_protocol::ApplyPatchApprovalParams;
use codex_app_server_protocol::AuthMode;
use codex_app_server_protocol::ConfigWarningNotification;
use codex_app_server_protocol::CurrentUsageLimitNudgeState;
use codex_app_server_protocol::DynamicToolCallParams;
use codex_app_server_protocol::FileChangeRequestApprovalParams;
use codex_app_server_protocol::GuardianWarningNotification;
@@ -751,6 +752,7 @@ mod tests {
credits: None,
plan_type: Some(PlanType::Plus),
rate_limit_reached_type: None,
current_usage_limit_nudge: CurrentUsageLimitNudgeState::Unknown,
},
});
@@ -770,7 +772,10 @@ mod tests {
"secondary": null,
"credits": null,
"planType": "plus",
"rateLimitReachedType": null
"rateLimitReachedType": null,
"currentUsageLimitNudge": {
"type": "unknown"
}
}
},
}),

View File

@@ -5,6 +5,7 @@ use app_test_support::to_response;
use app_test_support::write_chatgpt_auth;
use codex_app_server_protocol::AddCreditsNudgeCreditType;
use codex_app_server_protocol::AddCreditsNudgeEmailStatus;
use codex_app_server_protocol::CurrentUsageLimitNudgeState;
use codex_app_server_protocol::GetAccountRateLimitsResponse;
use codex_app_server_protocol::JSONRPCError;
use codex_app_server_protocol::JSONRPCResponse;
@@ -183,6 +184,7 @@ async fn get_account_rate_limits_returns_snapshot() -> Result<()> {
credits: None,
plan_type: Some(AccountPlanType::Pro),
rate_limit_reached_type: Some(RateLimitReachedType::WorkspaceMemberUsageLimitReached),
current_usage_limit_nudge: CurrentUsageLimitNudgeState::Unknown,
},
rate_limits_by_limit_id: Some(
[
@@ -206,6 +208,7 @@ async fn get_account_rate_limits_returns_snapshot() -> Result<()> {
rate_limit_reached_type: Some(
RateLimitReachedType::WorkspaceMemberUsageLimitReached,
),
current_usage_limit_nudge: CurrentUsageLimitNudgeState::Unknown,
},
),
(
@@ -222,6 +225,7 @@ async fn get_account_rate_limits_returns_snapshot() -> Result<()> {
credits: None,
plan_type: Some(AccountPlanType::Pro),
rate_limit_reached_type: None,
current_usage_limit_nudge: CurrentUsageLimitNudgeState::Unknown,
},
),
]

View File

@@ -15,6 +15,8 @@ use codex_protocol::protocol::CreditsSnapshot;
use codex_protocol::protocol::RateLimitReachedType;
use codex_protocol::protocol::RateLimitSnapshot;
use codex_protocol::protocol::RateLimitWindow;
use codex_protocol::protocol::UsageLimitNudgeAction;
use codex_protocol::protocol::UsageLimitNudgeStatePayload;
use reqwest::StatusCode;
use reqwest::header::CONTENT_TYPE;
use reqwest::header::HeaderMap;
@@ -452,6 +454,9 @@ impl Client {
.rate_limit_reached_type
.flatten()
.and_then(|details| Self::map_rate_limit_reached_type(details.kind));
let current_usage_limit_nudge = payload
.current_usage_limit_nudge
.map(|nudge| Self::map_usage_limit_nudge_state(*nudge));
let mut snapshots = vec![Self::make_rate_limit_snapshot(
Some("codex".to_string()),
/*limit_name*/ None,
@@ -459,6 +464,7 @@ impl Client {
payload.credits.flatten().map(|details| *details),
plan_type,
rate_limit_reached_type,
current_usage_limit_nudge,
)];
if let Some(additional) = payload.additional_rate_limits.flatten() {
snapshots.extend(additional.into_iter().map(|details| {
@@ -469,6 +475,7 @@ impl Client {
/*credits*/ None,
plan_type,
/*rate_limit_reached_type*/ None,
/*current_usage_limit_nudge*/ None,
)
}));
}
@@ -482,6 +489,7 @@ impl Client {
credits: Option<crate::types::CreditStatusDetails>,
plan_type: Option<AccountPlanType>,
rate_limit_reached_type: Option<RateLimitReachedType>,
current_usage_limit_nudge: Option<UsageLimitNudgeStatePayload>,
) -> RateLimitSnapshot {
let (primary, secondary) = match rate_limit {
Some(details) => (
@@ -498,6 +506,29 @@ impl Client {
credits: Self::map_credits(credits),
plan_type,
rate_limit_reached_type,
current_usage_limit_nudge,
}
}
fn map_usage_limit_nudge_state(
nudge: crate::types::UsageLimitNudgeState,
) -> UsageLimitNudgeStatePayload {
match nudge {
crate::types::UsageLimitNudgeState::Inactive => UsageLimitNudgeStatePayload::Inactive,
crate::types::UsageLimitNudgeState::Active {
key,
threshold,
action,
} => UsageLimitNudgeStatePayload::Active {
key,
threshold,
action: match action {
crate::types::UsageLimitNudgeAction::AddCredits => {
UsageLimitNudgeAction::AddCredits
}
crate::types::UsageLimitNudgeAction::Upgrade => UsageLimitNudgeAction::Upgrade,
},
},
}
}
@@ -661,6 +692,7 @@ mod tests {
rate_limit_reached_type: Some(Some(BackendRateLimitReachedType {
kind: RateLimitReachedKind::WorkspaceMemberCreditsDepleted,
})),
current_usage_limit_nudge: None,
};
let snapshots = Client::rate_limit_snapshots_from_payload(payload);
@@ -713,6 +745,7 @@ mod tests {
}])),
credits: None,
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
};
let snapshots = Client::rate_limit_snapshots_from_payload(payload);
@@ -739,6 +772,7 @@ mod tests {
credits: None,
plan_type: Some(AccountPlanType::Pro),
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
},
RateLimitSnapshot {
limit_id: Some("codex".to_string()),
@@ -752,6 +786,7 @@ mod tests {
credits: None,
plan_type: Some(AccountPlanType::Pro),
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
},
];
@@ -796,6 +831,7 @@ mod tests {
credits: None,
additional_rate_limits: None,
rate_limit_reached_type: Some(Some(BackendRateLimitReachedType { kind })),
current_usage_limit_nudge: None,
};
let snapshots = Client::rate_limit_snapshots_from_payload(payload);
@@ -811,12 +847,72 @@ mod tests {
credits: None,
additional_rate_limits: None,
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
};
let snapshots = Client::rate_limit_snapshots_from_payload(payload);
assert_eq!(snapshots[0].rate_limit_reached_type, None);
}
#[test]
fn usage_payload_preserves_active_current_usage_limit_nudge() {
let payload = RateLimitStatusPayload {
plan_type: crate::types::PlanType::Plus,
rate_limit: None,
credits: None,
additional_rate_limits: None,
rate_limit_reached_type: None,
current_usage_limit_nudge: Some(Box::new(crate::types::UsageLimitNudgeState::Active {
key: "near_limit_75_add_credits".to_string(),
threshold: 75,
action: crate::types::UsageLimitNudgeAction::AddCredits,
})),
};
let snapshots = Client::rate_limit_snapshots_from_payload(payload);
assert_eq!(
snapshots[0].current_usage_limit_nudge,
Some(UsageLimitNudgeStatePayload::Active {
key: "near_limit_75_add_credits".to_string(),
threshold: 75,
action: UsageLimitNudgeAction::AddCredits,
})
);
}
#[test]
fn usage_payload_preserves_explicitly_inactive_current_usage_limit_nudge() {
let payload = RateLimitStatusPayload {
plan_type: crate::types::PlanType::Plus,
rate_limit: None,
credits: None,
additional_rate_limits: None,
rate_limit_reached_type: None,
current_usage_limit_nudge: Some(Box::new(crate::types::UsageLimitNudgeState::Inactive)),
};
let snapshots = Client::rate_limit_snapshots_from_payload(payload);
assert_eq!(
snapshots[0].current_usage_limit_nudge,
Some(UsageLimitNudgeStatePayload::Inactive)
);
}
#[test]
fn usage_payload_preserves_missing_current_usage_limit_nudge() {
let payload = RateLimitStatusPayload {
plan_type: crate::types::PlanType::Plus,
rate_limit: None,
credits: None,
additional_rate_limits: None,
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
};
let snapshots = Client::rate_limit_snapshots_from_payload(payload);
assert_eq!(snapshots[0].current_usage_limit_nudge, None);
}
#[test]
fn add_credits_nudge_email_uses_expected_paths_and_bodies() {
let codex_client = Client {

View File

@@ -7,6 +7,8 @@ pub use codex_backend_openapi_models::models::RateLimitStatusDetails;
pub use codex_backend_openapi_models::models::RateLimitStatusPayload;
pub use codex_backend_openapi_models::models::RateLimitWindowSnapshot;
pub use codex_backend_openapi_models::models::TaskListItem;
pub use codex_backend_openapi_models::models::UsageLimitNudgeAction;
pub use codex_backend_openapi_models::models::UsageLimitNudgeState;
use serde::Deserialize;
use serde::de::Deserializer;

View File

@@ -2,6 +2,7 @@ use codex_protocol::account::PlanType;
use codex_protocol::protocol::CreditsSnapshot;
use codex_protocol::protocol::RateLimitSnapshot;
use codex_protocol::protocol::RateLimitWindow;
use codex_protocol::protocol::UsageLimitNudgeStatePayload;
use http::HeaderMap;
use serde::Deserialize;
use std::collections::BTreeSet;
@@ -94,6 +95,7 @@ pub fn parse_rate_limit_for_limit(
credits,
plan_type: None,
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
})
}
@@ -126,6 +128,7 @@ struct RateLimitEvent {
credits: Option<RateLimitEventCredits>,
metered_limit_name: Option<String>,
limit_name: Option<String>,
current_usage_limit_nudge: Option<UsageLimitNudgeStatePayload>,
}
pub fn parse_rate_limit_event(payload: &str) -> Option<RateLimitSnapshot> {
@@ -158,6 +161,7 @@ pub fn parse_rate_limit_event(payload: &str) -> Option<RateLimitSnapshot> {
credits,
plan_type: event.plan_type,
rate_limit_reached_type: None,
current_usage_limit_nudge: event.current_usage_limit_nudge,
})
}
@@ -365,4 +369,55 @@ mod tests {
assert_eq!(updates[0].secondary, None);
assert_eq!(updates[0].credits, None);
}
#[test]
fn parse_rate_limit_event_preserves_active_current_usage_limit_nudge() {
let snapshot = parse_rate_limit_event(
r#"{
"type": "codex.rate_limits",
"current_usage_limit_nudge": {
"type": "active",
"key": "near_limit_90_upgrade",
"threshold": 90,
"action": "upgrade"
}
}"#,
)
.expect("snapshot");
assert_eq!(
snapshot.current_usage_limit_nudge,
Some(UsageLimitNudgeStatePayload::Active {
key: "near_limit_90_upgrade".to_string(),
threshold: 90,
action: codex_protocol::protocol::UsageLimitNudgeAction::Upgrade,
})
);
}
#[test]
fn parse_rate_limit_event_preserves_explicitly_inactive_current_usage_limit_nudge() {
let snapshot = parse_rate_limit_event(
r#"{
"type": "codex.rate_limits",
"current_usage_limit_nudge": {
"type": "inactive"
}
}"#,
)
.expect("snapshot");
assert_eq!(
snapshot.current_usage_limit_nudge,
Some(UsageLimitNudgeStatePayload::Inactive)
);
}
#[test]
fn parse_rate_limit_event_preserves_missing_current_usage_limit_nudge() {
let snapshot =
parse_rate_limit_event(r#"{ "type": "codex.rate_limits" }"#).expect("snapshot");
assert_eq!(snapshot.current_usage_limit_nudge, None);
}
}

View File

@@ -44,3 +44,7 @@ pub use self::rate_limit_window_snapshot::RateLimitWindowSnapshot;
pub(crate) mod credit_status_details;
pub use self::credit_status_details::CreditStatusDetails;
pub(crate) mod usage_limit_nudge;
pub use self::usage_limit_nudge::UsageLimitNudgeAction;
pub use self::usage_limit_nudge::UsageLimitNudgeState;

View File

@@ -44,6 +44,12 @@ pub struct RateLimitStatusPayload {
skip_serializing_if = "Option::is_none"
)]
pub rate_limit_reached_type: Option<Option<RateLimitReachedType>>,
#[serde(
rename = "current_usage_limit_nudge",
default,
skip_serializing_if = "Option::is_none"
)]
pub current_usage_limit_nudge: Option<Box<models::UsageLimitNudgeState>>,
}
impl RateLimitStatusPayload {
@@ -54,6 +60,7 @@ impl RateLimitStatusPayload {
credits: None,
additional_rate_limits: None,
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
}
}
}

View File

@@ -0,0 +1,34 @@
/*
* codex-backend
*
* codex-backend
*
* The version of the OpenAPI document: 0.0.1
*
* Generated by: https://openapi-generator.tech
*/
use serde::Deserialize;
use serde::Serialize;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum UsageLimitNudgeState {
Inactive,
Active {
#[serde(rename = "key")]
key: String,
#[serde(rename = "threshold")]
threshold: u8,
#[serde(rename = "action")]
action: UsageLimitNudgeAction,
},
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub enum UsageLimitNudgeAction {
#[serde(rename = "add_credits")]
AddCredits,
#[serde(rename = "upgrade")]
Upgrade,
}

View File

@@ -2326,6 +2326,7 @@ async fn set_rate_limits_retains_previous_credits() {
}),
plan_type: Some(codex_protocol::account::PlanType::Plus),
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
};
state.set_rate_limits(initial.clone());
@@ -2345,6 +2346,7 @@ async fn set_rate_limits_retains_previous_credits() {
credits: None,
plan_type: None,
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
};
state.set_rate_limits(update.clone());
@@ -2358,6 +2360,7 @@ async fn set_rate_limits_retains_previous_credits() {
credits: initial.credits,
plan_type: initial.plan_type,
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
})
);
}
@@ -2432,6 +2435,7 @@ async fn set_rate_limits_updates_plan_type_when_present() {
}),
plan_type: Some(codex_protocol::account::PlanType::Plus),
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
};
state.set_rate_limits(initial.clone());
@@ -2447,6 +2451,7 @@ async fn set_rate_limits_updates_plan_type_when_present() {
credits: None,
plan_type: Some(codex_protocol::account::PlanType::Pro),
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
};
state.set_rate_limits(update.clone());
@@ -2460,6 +2465,7 @@ async fn set_rate_limits_updates_plan_type_when_present() {
credits: initial.credits,
plan_type: update.plan_type,
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
})
);
}

View File

@@ -50,6 +50,7 @@ async fn set_rate_limits_defaults_limit_id_to_codex_when_missing() {
credits: None,
plan_type: None,
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
});
assert_eq!(
@@ -78,6 +79,7 @@ async fn set_rate_limits_defaults_to_codex_when_limit_id_missing_after_other_buc
credits: None,
plan_type: None,
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
});
state.set_rate_limits(RateLimitSnapshot {
limit_id: None,
@@ -91,6 +93,7 @@ async fn set_rate_limits_defaults_to_codex_when_limit_id_missing_after_other_buc
credits: None,
plan_type: None,
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
});
assert_eq!(
@@ -123,6 +126,7 @@ async fn set_rate_limits_carries_credits_and_plan_type_from_codex_to_codex_other
}),
plan_type: Some(codex_protocol::account::PlanType::Plus),
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
});
state.set_rate_limits(RateLimitSnapshot {
@@ -137,6 +141,7 @@ async fn set_rate_limits_carries_credits_and_plan_type_from_codex_to_codex_other
credits: None,
plan_type: None,
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
});
assert_eq!(
@@ -157,6 +162,7 @@ async fn set_rate_limits_carries_credits_and_plan_type_from_codex_to_codex_other
}),
plan_type: Some(codex_protocol::account::PlanType::Plus),
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
})
);
}

View File

@@ -13,6 +13,7 @@ fn snapshot(
credits: None,
plan_type: None,
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
}
}

View File

@@ -37,6 +37,7 @@ fn rate_limit_snapshot() -> RateLimitSnapshot {
credits: None,
plan_type: None,
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
}
}

View File

@@ -2125,6 +2125,25 @@ pub struct RateLimitSnapshot {
pub credits: Option<CreditsSnapshot>,
pub plan_type: Option<crate::account::PlanType>,
pub rate_limit_reached_type: Option<RateLimitReachedType>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub current_usage_limit_nudge: Option<UsageLimitNudgeStatePayload>,
}
impl RateLimitSnapshot {
pub fn current_usage_limit_nudge_state(&self) -> CurrentUsageLimitNudgeState {
match &self.current_usage_limit_nudge {
None => CurrentUsageLimitNudgeState::Unknown,
Some(UsageLimitNudgeStatePayload::Inactive) => CurrentUsageLimitNudgeState::Inactive,
Some(UsageLimitNudgeStatePayload::Active {
key,
threshold,
action,
}) => UsageLimitNudge::from_payload(key, *threshold, *action)
.map(CurrentUsageLimitNudgeState::Active)
.unwrap_or(CurrentUsageLimitNudgeState::Inactive),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, JsonSchema, TS)]
@@ -2138,6 +2157,73 @@ pub enum RateLimitReachedType {
WorkspaceMemberUsageLimitReached,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "snake_case")]
#[ts(tag = "type")]
pub enum UsageLimitNudgeStatePayload {
Inactive,
Active {
key: String,
threshold: u8,
action: UsageLimitNudgeAction,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UsageLimitNudge {
pub key: String,
pub threshold: UsageLimitNudgeThreshold,
pub action: UsageLimitNudgeAction,
}
impl UsageLimitNudge {
fn from_payload(key: &str, threshold: u8, action: UsageLimitNudgeAction) -> Option<Self> {
Some(Self {
key: key.to_string(),
threshold: UsageLimitNudgeThreshold::from_percent(threshold)?,
action,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CurrentUsageLimitNudgeState {
Unknown,
Inactive,
Active(UsageLimitNudge),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UsageLimitNudgeThreshold {
Percent75,
Percent90,
}
impl UsageLimitNudgeThreshold {
fn from_percent(percent: u8) -> Option<Self> {
match percent {
75 => Some(Self::Percent75),
90 => Some(Self::Percent90),
_ => None,
}
}
pub fn as_percent(self) -> u8 {
match self {
Self::Percent75 => 75,
Self::Percent90 => 90,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, JsonSchema, TS)]
#[serde(rename_all = "snake_case")]
#[ts(rename_all = "snake_case")]
pub enum UsageLimitNudgeAction {
AddCredits,
Upgrade,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
pub struct RateLimitWindow {
/// Percentage (0-100) of the window that has been consumed.
@@ -3974,6 +4060,61 @@ mod tests {
use tempfile::NamedTempFile;
use tempfile::TempDir;
fn rate_limit_snapshot_with_nudge(
current_usage_limit_nudge: Option<UsageLimitNudgeStatePayload>,
) -> RateLimitSnapshot {
RateLimitSnapshot {
limit_id: Some("codex".to_string()),
limit_name: None,
primary: None,
secondary: None,
credits: None,
plan_type: None,
rate_limit_reached_type: None,
current_usage_limit_nudge,
}
}
#[test]
fn current_usage_limit_nudge_state_distinguishes_unknown_inactive_and_active() {
assert_eq!(
rate_limit_snapshot_with_nudge(/*current_usage_limit_nudge*/ None)
.current_usage_limit_nudge_state(),
CurrentUsageLimitNudgeState::Unknown
);
assert_eq!(
rate_limit_snapshot_with_nudge(Some(UsageLimitNudgeStatePayload::Inactive))
.current_usage_limit_nudge_state(),
CurrentUsageLimitNudgeState::Inactive
);
assert_eq!(
rate_limit_snapshot_with_nudge(Some(UsageLimitNudgeStatePayload::Active {
key: "near_limit_75_add_credits".to_string(),
threshold: 75,
action: UsageLimitNudgeAction::AddCredits,
}))
.current_usage_limit_nudge_state(),
CurrentUsageLimitNudgeState::Active(UsageLimitNudge {
key: "near_limit_75_add_credits".to_string(),
threshold: UsageLimitNudgeThreshold::Percent75,
action: UsageLimitNudgeAction::AddCredits,
})
);
}
#[test]
fn invalid_current_usage_limit_nudge_threshold_fails_closed() {
assert_eq!(
rate_limit_snapshot_with_nudge(Some(UsageLimitNudgeStatePayload::Active {
key: "near_limit_80_upgrade".to_string(),
threshold: 80,
action: UsageLimitNudgeAction::Upgrade,
}))
.current_usage_limit_nudge_state(),
CurrentUsageLimitNudgeState::Inactive
);
}
fn sorted_writable_roots(roots: Vec<WritableRoot>) -> Vec<(PathBuf, Vec<PathBuf>)> {
let mut sorted_roots: Vec<(PathBuf, Vec<PathBuf>)> = roots
.into_iter()

View File

@@ -111,6 +111,7 @@ use codex_protocol::protocol::ReviewRequest;
use codex_protocol::protocol::ReviewTarget as CoreReviewTarget;
use codex_protocol::protocol::SandboxPolicy;
use codex_protocol::protocol::SessionNetworkProxyRuntime;
use codex_protocol::protocol::UsageLimitNudgeStatePayload;
use codex_utils_absolute_path::AbsolutePathBuf;
use color_eyre::eyre::ContextCompat;
use color_eyre::eyre::Result;
@@ -1464,6 +1465,19 @@ pub(crate) fn app_server_rate_limit_snapshot_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),
current_usage_limit_nudge: match snapshot.current_usage_limit_nudge {
codex_app_server_protocol::CurrentUsageLimitNudgeState::Unknown => None,
codex_app_server_protocol::CurrentUsageLimitNudgeState::Inactive => {
Some(UsageLimitNudgeStatePayload::Inactive)
}
codex_app_server_protocol::CurrentUsageLimitNudgeState::Active { nudge } => {
Some(UsageLimitNudgeStatePayload::Active {
key: nudge.key,
threshold: nudge.threshold,
action: nudge.action.into(),
})
}
},
}
}

View File

@@ -107,6 +107,7 @@ pub(super) fn snapshot(percent: f64) -> RateLimitSnapshot {
credits: None,
plan_type: None,
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
}
}

View File

@@ -267,6 +267,7 @@ async fn rate_limit_snapshot_keeps_prior_credits_when_missing_from_headers() {
}),
plan_type: None,
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
}));
let initial_balance = chat
.rate_limit_snapshots_by_limit_id
@@ -287,6 +288,7 @@ async fn rate_limit_snapshot_keeps_prior_credits_when_missing_from_headers() {
credits: None,
plan_type: None,
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
}));
let display = chat
@@ -326,6 +328,7 @@ async fn rate_limit_snapshot_updates_and_retains_plan_type() {
credits: None,
plan_type: Some(PlanType::Plus),
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
}));
assert_eq!(chat.plan_type, Some(PlanType::Plus));
@@ -345,6 +348,7 @@ async fn rate_limit_snapshot_updates_and_retains_plan_type() {
credits: None,
plan_type: Some(PlanType::Pro),
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
}));
assert_eq!(chat.plan_type, Some(PlanType::Pro));
@@ -364,6 +368,7 @@ async fn rate_limit_snapshot_updates_and_retains_plan_type() {
credits: None,
plan_type: None,
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
}));
assert_eq!(chat.plan_type, Some(PlanType::Pro));
}
@@ -388,6 +393,7 @@ async fn rate_limit_snapshots_keep_separate_entries_per_limit_id() {
}),
plan_type: Some(PlanType::Pro),
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
}));
chat.on_rate_limit_snapshot(Some(RateLimitSnapshot {
@@ -402,6 +408,7 @@ async fn rate_limit_snapshots_keep_separate_entries_per_limit_id() {
credits: None,
plan_type: Some(PlanType::Pro),
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
}));
let codex = chat
@@ -455,6 +462,7 @@ async fn rate_limit_switch_prompt_skips_non_codex_limit() {
credits: None,
plan_type: None,
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
}));
assert!(matches!(

View File

@@ -136,6 +136,7 @@ async fn status_snapshot_includes_reasoning_details() {
credits: None,
plan_type: None,
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
};
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
@@ -317,6 +318,7 @@ async fn status_snapshot_includes_monthly_limit() {
credits: None,
plan_type: None,
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
};
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
@@ -369,6 +371,7 @@ async fn status_snapshot_shows_unlimited_credits() {
}),
plan_type: None,
rate_limit_reached_type: None,
current_usage_limit_nudge: 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());
@@ -419,6 +422,7 @@ async fn status_snapshot_shows_positive_credits() {
}),
plan_type: None,
rate_limit_reached_type: None,
current_usage_limit_nudge: 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());
@@ -469,6 +473,7 @@ async fn status_snapshot_hides_zero_credits() {
}),
plan_type: None,
rate_limit_reached_type: None,
current_usage_limit_nudge: 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());
@@ -517,6 +522,7 @@ async fn status_snapshot_hides_when_has_no_credits_flag() {
}),
plan_type: None,
rate_limit_reached_type: None,
current_usage_limit_nudge: 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() {
credits: None,
plan_type: None,
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
};
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
@@ -787,6 +794,7 @@ async fn status_snapshot_shows_refreshing_limits_notice() {
credits: None,
plan_type: None,
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
};
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
@@ -858,6 +866,7 @@ async fn status_snapshot_includes_credits_and_limits() {
}),
plan_type: None,
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
};
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
@@ -912,6 +921,7 @@ async fn status_snapshot_shows_unavailable_limits_message() {
credits: None,
plan_type: None,
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
};
let captured_at = chrono::Local
.with_ymd_and_hms(2024, 6, 7, 8, 9, 10)
@@ -969,6 +979,7 @@ async fn status_snapshot_treats_refreshing_empty_limits_as_unavailable() {
credits: None,
plan_type: None,
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
};
let captured_at = chrono::Local
.with_ymd_and_hms(2024, 6, 7, 8, 9, 10)
@@ -1040,6 +1051,7 @@ async fn status_snapshot_shows_stale_limits_message() {
credits: None,
plan_type: None,
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
};
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
let now = captured_at + ChronoDuration::minutes(20);
@@ -1111,6 +1123,7 @@ async fn status_snapshot_cached_limits_hide_credits_without_flag() {
}),
plan_type: None,
rate_limit_reached_type: None,
current_usage_limit_nudge: None,
};
let rate_display = rate_limit_snapshot_display(&snapshot, captured_at);
let now = captured_at + ChronoDuration::minutes(20);