diff --git a/codex-rs/backend-client/src/client.rs b/codex-rs/backend-client/src/client.rs index ec5fd3f61b..6fa36d1ffd 100644 --- a/codex-rs/backend-client/src/client.rs +++ b/codex-rs/backend-client/src/client.rs @@ -351,6 +351,7 @@ impl Client { fn map_plan_type(plan_type: crate::types::PlanType) -> AccountPlanType { match plan_type { crate::types::PlanType::Free => AccountPlanType::Free, + crate::types::PlanType::Go => AccountPlanType::Go, crate::types::PlanType::Plus => AccountPlanType::Plus, crate::types::PlanType::Pro => AccountPlanType::Pro, crate::types::PlanType::Team => AccountPlanType::Team, @@ -358,7 +359,6 @@ impl Client { crate::types::PlanType::Enterprise => AccountPlanType::Enterprise, crate::types::PlanType::Edu | crate::types::PlanType::Education => AccountPlanType::Edu, crate::types::PlanType::Guest - | crate::types::PlanType::Go | crate::types::PlanType::FreeWorkspace | crate::types::PlanType::Quorum | crate::types::PlanType::K12 => AccountPlanType::Unknown, diff --git a/codex-rs/core/src/auth.rs b/codex-rs/core/src/auth.rs index d6cfec5506..d4520dc855 100644 --- a/codex-rs/core/src/auth.rs +++ b/codex-rs/core/src/auth.rs @@ -159,6 +159,7 @@ impl CodexAuth { pub fn account_plan_type(&self) -> Option { let map_known = |kp: &InternalKnownPlan| match kp { InternalKnownPlan::Free => AccountPlanType::Free, + InternalKnownPlan::Go => AccountPlanType::Go, InternalKnownPlan::Plus => AccountPlanType::Plus, InternalKnownPlan::Pro => AccountPlanType::Pro, InternalKnownPlan::Team => AccountPlanType::Team, diff --git a/codex-rs/core/src/error.rs b/codex-rs/core/src/error.rs index 6c284074c9..f9f896c3b8 100644 --- a/codex-rs/core/src/error.rs +++ b/codex-rs/core/src/error.rs @@ -375,9 +375,11 @@ impl std::fmt::Display for UsageLimitReachedError { retry_suffix_after_or(self.resets_at.as_ref()) ) } - Some(PlanType::Known(KnownPlan::Free)) => { - "You've hit your usage limit. Upgrade to Plus to continue using Codex (https://openai.com/chatgpt/pricing)." - .to_string() + Some(PlanType::Known(KnownPlan::Free)) | Some(PlanType::Known(KnownPlan::Go)) => { + format!( + "You've hit your usage limit. Upgrade to Plus to continue using Codex (https://openai.com/chatgpt/pricing),{}", + retry_suffix_after_or(self.resets_at.as_ref()) + ) } Some(PlanType::Known(KnownPlan::Pro)) => format!( "You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits{}", @@ -817,7 +819,20 @@ mod tests { }; assert_eq!( err.to_string(), - "You've hit your usage limit. Upgrade to Plus to continue using Codex (https://openai.com/chatgpt/pricing)." + "You've hit your usage limit. Upgrade to Plus to continue using Codex (https://openai.com/chatgpt/pricing), or try again later." + ); + } + + #[test] + fn usage_limit_reached_error_formats_go_plan() { + let err = UsageLimitReachedError { + plan_type: Some(PlanType::Known(KnownPlan::Go)), + resets_at: None, + rate_limits: Some(rate_limit_snapshot()), + }; + assert_eq!( + err.to_string(), + "You've hit your usage limit. Upgrade to Plus to continue using Codex (https://openai.com/chatgpt/pricing), or try again later." ); } diff --git a/codex-rs/core/src/token_data.rs b/codex-rs/core/src/token_data.rs index 2010af16f7..e38660ff5a 100644 --- a/codex-rs/core/src/token_data.rs +++ b/codex-rs/core/src/token_data.rs @@ -64,6 +64,7 @@ pub(crate) enum PlanType { #[serde(rename_all = "lowercase")] pub(crate) enum KnownPlan { Free, + Go, Plus, Pro, Team, @@ -195,6 +196,38 @@ mod tests { assert_eq!(info.get_chatgpt_plan_type().as_deref(), Some("Pro")); } + #[test] + fn id_token_info_parses_go_plan() { + #[derive(Serialize)] + struct Header { + alg: &'static str, + typ: &'static str, + } + let header = Header { + alg: "none", + typ: "JWT", + }; + let payload = serde_json::json!({ + "email": "user@example.com", + "https://api.openai.com/auth": { + "chatgpt_plan_type": "go" + } + }); + + fn b64url_no_pad(bytes: &[u8]) -> String { + base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(bytes) + } + + let header_b64 = b64url_no_pad(&serde_json::to_vec(&header).unwrap()); + let payload_b64 = b64url_no_pad(&serde_json::to_vec(&payload).unwrap()); + let signature_b64 = b64url_no_pad(b"sig"); + let fake_jwt = format!("{header_b64}.{payload_b64}.{signature_b64}"); + + let info = parse_id_token(&fake_jwt).expect("should parse"); + assert_eq!(info.email.as_deref(), Some("user@example.com")); + assert_eq!(info.get_chatgpt_plan_type().as_deref(), Some("Go")); + } + #[test] fn id_token_info_handles_missing_fields() { #[derive(Serialize)] diff --git a/codex-rs/protocol/src/account.rs b/codex-rs/protocol/src/account.rs index fb707c3a73..1cb58a020f 100644 --- a/codex-rs/protocol/src/account.rs +++ b/codex-rs/protocol/src/account.rs @@ -9,6 +9,7 @@ use ts_rs::TS; pub enum PlanType { #[default] Free, + Go, Plus, Pro, Team,