diff --git a/codex-rs/app-server-protocol/schema/json/ServerNotification.json b/codex-rs/app-server-protocol/schema/json/ServerNotification.json index 4e1e7e86b1..cd35e33f68 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerNotification.json +++ b/codex-rs/app-server-protocol/schema/json/ServerNotification.json @@ -2925,6 +2925,7 @@ "self_serve_business_usage_based", "business", "ent26", + "enterprise_cbp_automation", "enterprise_cbp_usage_based", "enterprise", "edu", diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index ea8503f064..a1d3cd4c51 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -14155,6 +14155,7 @@ "self_serve_business_usage_based", "business", "ent26", + "enterprise_cbp_automation", "enterprise_cbp_usage_based", "enterprise", "edu", diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index f076266a40..fa03272c9d 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -10497,6 +10497,7 @@ "self_serve_business_usage_based", "business", "ent26", + "enterprise_cbp_automation", "enterprise_cbp_usage_based", "enterprise", "edu", diff --git a/codex-rs/app-server-protocol/schema/json/v2/AccountRateLimitsUpdatedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/AccountRateLimitsUpdatedNotification.json index b86fa1307b..cecb3e0caf 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/AccountRateLimitsUpdatedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/AccountRateLimitsUpdatedNotification.json @@ -34,6 +34,7 @@ "self_serve_business_usage_based", "business", "ent26", + "enterprise_cbp_automation", "enterprise_cbp_usage_based", "enterprise", "edu", diff --git a/codex-rs/app-server-protocol/schema/json/v2/AccountUpdatedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/AccountUpdatedNotification.json index ff573e82b7..89c4c826f4 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/AccountUpdatedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/AccountUpdatedNotification.json @@ -67,6 +67,7 @@ "self_serve_business_usage_based", "business", "ent26", + "enterprise_cbp_automation", "enterprise_cbp_usage_based", "enterprise", "edu", diff --git a/codex-rs/app-server-protocol/schema/json/v2/GetAccountRateLimitsResponse.json b/codex-rs/app-server-protocol/schema/json/v2/GetAccountRateLimitsResponse.json index bd33d63b1e..81d52e4a98 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/GetAccountRateLimitsResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/GetAccountRateLimitsResponse.json @@ -34,6 +34,7 @@ "self_serve_business_usage_based", "business", "ent26", + "enterprise_cbp_automation", "enterprise_cbp_usage_based", "enterprise", "edu", diff --git a/codex-rs/app-server-protocol/schema/json/v2/GetAccountResponse.json b/codex-rs/app-server-protocol/schema/json/v2/GetAccountResponse.json index 68842c6723..a9b0e71560 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/GetAccountResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/GetAccountResponse.json @@ -80,6 +80,7 @@ "self_serve_business_usage_based", "business", "ent26", + "enterprise_cbp_automation", "enterprise_cbp_usage_based", "enterprise", "edu", diff --git a/codex-rs/app-server-protocol/schema/typescript/PlanType.ts b/codex-rs/app-server-protocol/schema/typescript/PlanType.ts index 116b8dfba8..88174d049b 100644 --- a/codex-rs/app-server-protocol/schema/typescript/PlanType.ts +++ b/codex-rs/app-server-protocol/schema/typescript/PlanType.ts @@ -2,4 +2,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type PlanType = "free" | "go" | "plus" | "pro" | "prolite" | "team" | "self_serve_business_prolite" | "self_serve_business_usage_based" | "business" | "ent26" | "enterprise_cbp_usage_based" | "enterprise" | "edu" | "unknown"; +export type PlanType = "free" | "go" | "plus" | "pro" | "prolite" | "team" | "self_serve_business_prolite" | "self_serve_business_usage_based" | "business" | "ent26" | "enterprise_cbp_automation" | "enterprise_cbp_usage_based" | "enterprise" | "edu" | "unknown"; diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 0980343753..475e4bfba8 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -2114,7 +2114,7 @@ The JSON-RPC auth/account surface exposes request/response methods plus server-i ### Authentication modes -Codex supports these authentication modes. The current mode is surfaced in `account/updated` (`authMode`), which also includes the current ChatGPT `planType` when available, and can be inferred from `account/read`. Self-serve Business ProLite accounts use the `self_serve_business_prolite` plan type. +Codex supports these authentication modes. The current mode is surfaced in `account/updated` (`authMode`), which also includes the current ChatGPT `planType` when available, and can be inferred from `account/read`. Self-serve Business ProLite accounts use the `self_serve_business_prolite` plan type; Enterprise automation accounts use `enterprise_cbp_automation`. - **API key (`apiKey`)**: Caller supplies an OpenAI API key via `account/login/start` with `type: "apiKey"`. The API key is saved and used for API requests. - **ChatGPT managed (`chatgpt`)** (recommended): Codex owns the ChatGPT OAuth flow and refresh tokens. Start via `account/login/start` with `type: "chatgpt"` for the browser flow or `type: "chatgptDeviceCode"` for device code; Codex persists tokens to disk and refreshes them automatically. diff --git a/codex-rs/app-server/tests/suite/auth.rs b/codex-rs/app-server/tests/suite/auth.rs index de40a45c51..504c48fef8 100644 --- a/codex-rs/app-server/tests/suite/auth.rs +++ b/codex-rs/app-server/tests/suite/auth.rs @@ -154,7 +154,7 @@ async fn personal_access_token_without_email_supports_auth_status_and_account_re "email": null, "chatgpt_user_id": "user-123", "chatgpt_account_id": "account-123", - "chatgpt_plan_type": "pro", + "chatgpt_plan_type": "enterprise_cbp_automation", "chatgpt_account_is_fedramp": false, }))) .expect(1..) @@ -208,12 +208,20 @@ async fn personal_access_token_without_email_supports_auth_status_and_account_re .and_then(|account| account.get("email")), Some(&serde_json::Value::Null), ); + assert_eq!( + response + .result + .get("account") + .and_then(|account| account.get("planType")) + .and_then(serde_json::Value::as_str), + Some("enterprise_cbp_automation"), + ); assert_eq!( to_response::(response)?, GetAccountResponse { account: Some(Account::Chatgpt { email: None, - plan_type: AccountPlanType::Pro, + plan_type: AccountPlanType::EnterpriseCbpAutomation, }), requires_openai_auth: true, } diff --git a/codex-rs/app-server/tests/suite/v2/rate_limits.rs b/codex-rs/app-server/tests/suite/v2/rate_limits.rs index 9f25f389f0..b55ebe4b3b 100644 --- a/codex-rs/app-server/tests/suite/v2/rate_limits.rs +++ b/codex-rs/app-server/tests/suite/v2/rate_limits.rs @@ -127,7 +127,7 @@ async fn get_account_rate_limits_returns_snapshot() -> Result<()> { .expect("parse second reset credit grant timestamp") .timestamp(); let response_body = json!({ - "plan_type": "self_serve_business_prolite", + "plan_type": "enterprise_cbp_automation", "rate_limit": { "allowed": true, "limit_reached": false, @@ -251,7 +251,7 @@ async fn get_account_rate_limits_returns_snapshot() -> Result<()> { resets_at: secondary_reset_timestamp, }), spend_control_reached: Some(false), - plan_type: Some(AccountPlanType::SelfServeBusinessProLite), + plan_type: Some(AccountPlanType::EnterpriseCbpAutomation), rate_limit_reached_type: Some(RateLimitReachedType::WorkspaceMemberUsageLimitReached), }, rate_limits_by_limit_id: Some( @@ -279,7 +279,7 @@ async fn get_account_rate_limits_returns_snapshot() -> Result<()> { resets_at: secondary_reset_timestamp, }), spend_control_reached: Some(false), - plan_type: Some(AccountPlanType::SelfServeBusinessProLite), + plan_type: Some(AccountPlanType::EnterpriseCbpAutomation), rate_limit_reached_type: Some( RateLimitReachedType::WorkspaceMemberUsageLimitReached, ), @@ -299,7 +299,7 @@ async fn get_account_rate_limits_returns_snapshot() -> Result<()> { credits: None, individual_limit: None, spend_control_reached: None, - plan_type: Some(AccountPlanType::SelfServeBusinessProLite), + plan_type: Some(AccountPlanType::EnterpriseCbpAutomation), rate_limit_reached_type: None, }, ), diff --git a/codex-rs/backend-client/src/client.rs b/codex-rs/backend-client/src/client.rs index 5912d8a1e0..da9b0a3d3f 100644 --- a/codex-rs/backend-client/src/client.rs +++ b/codex-rs/backend-client/src/client.rs @@ -693,6 +693,9 @@ impl Client { } crate::types::PlanType::Business => AccountPlanType::Business, crate::types::PlanType::Ent26 => AccountPlanType::Ent26, + crate::types::PlanType::EnterpriseCbpAutomation => { + AccountPlanType::EnterpriseCbpAutomation + } crate::types::PlanType::EnterpriseCbpUsageBased => { AccountPlanType::EnterpriseCbpUsageBased } @@ -751,6 +754,10 @@ mod tests { Client::map_plan_type(crate::types::PlanType::EnterpriseCbpUsageBased), AccountPlanType::EnterpriseCbpUsageBased ); + assert_eq!( + Client::map_plan_type(crate::types::PlanType::EnterpriseCbpAutomation), + AccountPlanType::EnterpriseCbpAutomation + ); let ent26 = serde_json::from_str::("\"ent26\"") .expect("ent26 backend plan should deserialize"); assert_eq!(Client::map_plan_type(ent26), AccountPlanType::Ent26); diff --git a/codex-rs/cloud-config/src/service_tests.rs b/codex-rs/cloud-config/src/service_tests.rs index 0274e10ef7..1c07ab0bcd 100644 --- a/codex-rs/cloud-config/src/service_tests.rs +++ b/codex-rs/cloud-config/src/service_tests.rs @@ -417,6 +417,7 @@ async fn get_bundle_allows_eligible_workspace_plans_and_writes_cache() { for plan_type in [ "business", "ent26", + "enterprise_cbp_automation", "enterprise_cbp_usage_based", "enterprise", "hc", diff --git a/codex-rs/codex-backend-openapi-models/src/models/rate_limit_status_payload.rs b/codex-rs/codex-backend-openapi-models/src/models/rate_limit_status_payload.rs index 13cfe3f73f..2e1866aff5 100644 --- a/codex-rs/codex-backend-openapi-models/src/models/rate_limit_status_payload.rs +++ b/codex-rs/codex-backend-openapi-models/src/models/rate_limit_status_payload.rs @@ -120,6 +120,8 @@ pub enum PlanType { Business, #[serde(rename = "ent26")] Ent26, + #[serde(rename = "enterprise_cbp_automation")] + EnterpriseCbpAutomation, #[serde(rename = "enterprise_cbp_usage_based")] EnterpriseCbpUsageBased, #[serde(rename = "education")] diff --git a/codex-rs/login/src/token_data_tests.rs b/codex-rs/login/src/token_data_tests.rs index 78ccd3aea9..8f4debcd9d 100644 --- a/codex-rs/login/src/token_data_tests.rs +++ b/codex-rs/login/src/token_data_tests.rs @@ -122,6 +122,27 @@ fn id_token_info_parses_usage_based_business_plans() { assert_eq!(enterprise_cbp.is_workspace_account(), true); } +#[test] +fn id_token_info_parses_enterprise_cbp_automation_plan() { + let jwt = fake_jwt(serde_json::json!({ + "email": "service-account@example.com", + "https://api.openai.com/auth": { + "chatgpt_plan_type": "enterprise_cbp_automation" + } + })); + + let info = parse_chatgpt_jwt_claims(&jwt).expect("should parse"); + assert_eq!( + info.get_chatgpt_plan_type().as_deref(), + Some("Enterprise (Automation)") + ); + assert_eq!( + info.get_chatgpt_plan_type_raw().as_deref(), + Some("enterprise_cbp_automation") + ); + assert_eq!(info.is_workspace_account(), true); +} + #[test] fn id_token_info_parses_self_serve_business_prolite_plan() { let jwt = fake_jwt(serde_json::json!({ diff --git a/codex-rs/protocol/src/account.rs b/codex-rs/protocol/src/account.rs index 544cd8a51d..f8713f5ad1 100644 --- a/codex-rs/protocol/src/account.rs +++ b/codex-rs/protocol/src/account.rs @@ -25,6 +25,9 @@ pub enum PlanType { SelfServeBusinessUsageBased, Business, Ent26, + #[serde(rename = "enterprise_cbp_automation")] + #[ts(rename = "enterprise_cbp_automation")] + EnterpriseCbpAutomation, #[serde(rename = "enterprise_cbp_usage_based")] #[ts(rename = "enterprise_cbp_usage_based")] EnterpriseCbpUsageBased, @@ -58,7 +61,10 @@ impl PlanType { pub fn is_business_like(self) -> bool { matches!( self, - Self::Business | Self::Ent26 | Self::EnterpriseCbpUsageBased + Self::Business + | Self::Ent26 + | Self::EnterpriseCbpAutomation + | Self::EnterpriseCbpUsageBased ) } @@ -70,6 +76,7 @@ impl PlanType { | Self::SelfServeBusinessUsageBased | Self::Business | Self::Ent26 + | Self::EnterpriseCbpAutomation | Self::EnterpriseCbpUsageBased | Self::Enterprise | Self::Edu @@ -99,6 +106,7 @@ impl From for PlanType { KnownPlan::SelfServeBusinessUsageBased => Self::SelfServeBusinessUsageBased, KnownPlan::Business => Self::Business, KnownPlan::Ent26 => Self::Ent26, + KnownPlan::EnterpriseCbpAutomation => Self::EnterpriseCbpAutomation, KnownPlan::EnterpriseCbpUsageBased => Self::EnterpriseCbpUsageBased, KnownPlan::Enterprise => Self::Enterprise, KnownPlan::Edu => Self::Edu, @@ -130,6 +138,11 @@ mod tests { .expect("enterprise cbp usage based should serialize"), "\"enterprise_cbp_usage_based\"" ); + assert_eq!( + serde_json::to_string(&PlanType::EnterpriseCbpAutomation) + .expect("enterprise cbp automation should serialize"), + "\"enterprise_cbp_automation\"" + ); assert_eq!( serde_json::to_string(&PlanType::Ent26).expect("ent26 should serialize"), "\"ent26\"" @@ -157,6 +170,11 @@ mod tests { .expect("enterprise cbp usage based should deserialize"), PlanType::EnterpriseCbpUsageBased ); + assert_eq!( + serde_json::from_str::("\"enterprise_cbp_automation\"") + .expect("enterprise cbp automation should deserialize"), + PlanType::EnterpriseCbpAutomation + ); assert_eq!( serde_json::from_str::("\"ent26\"").expect("ent26 should deserialize"), PlanType::Ent26 @@ -173,6 +191,7 @@ mod tests { assert_eq!(PlanType::Business.is_business_like(), true); assert_eq!(PlanType::Ent26.is_business_like(), true); + assert_eq!(PlanType::EnterpriseCbpAutomation.is_business_like(), true); assert_eq!(PlanType::EnterpriseCbpUsageBased.is_business_like(), true); assert_eq!(PlanType::Team.is_business_like(), false); } @@ -190,6 +209,10 @@ mod tests { ); assert_eq!(PlanType::Business.is_workspace_account(), true); assert_eq!(PlanType::Ent26.is_workspace_account(), true); + assert_eq!( + PlanType::EnterpriseCbpAutomation.is_workspace_account(), + true + ); assert_eq!( PlanType::EnterpriseCbpUsageBased.is_workspace_account(), true @@ -209,6 +232,10 @@ mod tests { PlanType::from(AuthPlanType::Known(KnownPlan::EnterpriseCbpUsageBased)), PlanType::EnterpriseCbpUsageBased ); + assert_eq!( + PlanType::from(AuthPlanType::Known(KnownPlan::EnterpriseCbpAutomation)), + PlanType::EnterpriseCbpAutomation + ); assert_eq!( PlanType::from(AuthPlanType::Known(KnownPlan::Ent26)), PlanType::Ent26 diff --git a/codex-rs/protocol/src/auth.rs b/codex-rs/protocol/src/auth.rs index 90289facc4..3003eb6bf8 100644 --- a/codex-rs/protocol/src/auth.rs +++ b/codex-rs/protocol/src/auth.rs @@ -77,6 +77,7 @@ impl PlanType { } "business" => Self::Known(KnownPlan::Business), "ent26" => Self::Known(KnownPlan::Ent26), + "enterprise_cbp_automation" => Self::Known(KnownPlan::EnterpriseCbpAutomation), "enterprise_cbp_usage_based" => Self::Known(KnownPlan::EnterpriseCbpUsageBased), "enterprise" | "hc" => Self::Known(KnownPlan::Enterprise), "education" | "edu" => Self::Known(KnownPlan::Edu), @@ -100,6 +101,8 @@ pub enum KnownPlan { SelfServeBusinessUsageBased, Business, Ent26, + #[serde(rename = "enterprise_cbp_automation")] + EnterpriseCbpAutomation, #[serde(rename = "enterprise_cbp_usage_based")] EnterpriseCbpUsageBased, #[serde(alias = "hc")] @@ -121,6 +124,7 @@ impl KnownPlan { Self::SelfServeBusinessUsageBased => "Self Serve Business Usage Based", Self::Business => "Business", Self::Ent26 => "Enterprise", + Self::EnterpriseCbpAutomation => "Enterprise (Automation)", Self::EnterpriseCbpUsageBased => "Enterprise CBP Usage Based", Self::Enterprise => "Enterprise", Self::Edu => "Edu", @@ -139,6 +143,7 @@ impl KnownPlan { Self::SelfServeBusinessUsageBased => "self_serve_business_usage_based", Self::Business => "business", Self::Ent26 => "ent26", + Self::EnterpriseCbpAutomation => "enterprise_cbp_automation", Self::EnterpriseCbpUsageBased => "enterprise_cbp_usage_based", Self::Enterprise => "enterprise", Self::Edu => "edu", @@ -153,6 +158,7 @@ impl KnownPlan { | Self::SelfServeBusinessUsageBased | Self::Business | Self::Ent26 + | Self::EnterpriseCbpAutomation | Self::EnterpriseCbpUsageBased | Self::Enterprise | Self::Edu @@ -201,5 +207,10 @@ mod tests { .expect("education should deserialize"), PlanType::Known(KnownPlan::Edu) ); + assert_eq!( + serde_json::from_str::("\"enterprise_cbp_automation\"") + .expect("enterprise cbp automation should deserialize"), + PlanType::Known(KnownPlan::EnterpriseCbpAutomation) + ); } } diff --git a/codex-rs/protocol/src/error.rs b/codex-rs/protocol/src/error.rs index d9e5922a02..6eb195f7bd 100644 --- a/codex-rs/protocol/src/error.rs +++ b/codex-rs/protocol/src/error.rs @@ -691,6 +691,7 @@ impl std::fmt::Display for UsageLimitReachedError { | KnownPlan::SelfServeBusinessUsageBased | KnownPlan::Business | KnownPlan::Ent26 + | KnownPlan::EnterpriseCbpAutomation | KnownPlan::EnterpriseCbpUsageBased, )) => { format!( diff --git a/codex-rs/protocol/src/error_tests.rs b/codex-rs/protocol/src/error_tests.rs index bec04e7969..631faf3d94 100644 --- a/codex-rs/protocol/src/error_tests.rs +++ b/codex-rs/protocol/src/error_tests.rs @@ -336,7 +336,11 @@ fn usage_limit_reached_error_formats_team_plan() { #[test] fn usage_limit_reached_error_formats_business_plan_without_reset() { - for plan in [KnownPlan::Business, KnownPlan::Ent26] { + for plan in [ + KnownPlan::Business, + KnownPlan::Ent26, + KnownPlan::EnterpriseCbpAutomation, + ] { let err = UsageLimitReachedError { plan_type: Some(PlanType::Known(plan)), resets_at: None, diff --git a/codex-rs/tui/src/status/helpers.rs b/codex-rs/tui/src/status/helpers.rs index 4f6f926525..03a3baa192 100644 --- a/codex-rs/tui/src/status/helpers.rs +++ b/codex-rs/tui/src/status/helpers.rs @@ -97,7 +97,9 @@ pub(crate) fn compose_account_display( } pub(crate) fn plan_type_display_name(plan_type: PlanType) -> String { - if plan_type.is_team_like() { + if plan_type == PlanType::EnterpriseCbpAutomation { + "Enterprise (Automation)".to_string() + } else if plan_type.is_team_like() { "Business".to_string() } else if plan_type.is_business_like() { "Enterprise".to_string() @@ -222,6 +224,7 @@ mod tests { (PlanType::SelfServeBusinessProLite, "Business"), (PlanType::SelfServeBusinessUsageBased, "Business"), (PlanType::Business, "Enterprise"), + (PlanType::EnterpriseCbpAutomation, "Enterprise (Automation)"), (PlanType::EnterpriseCbpUsageBased, "Enterprise"), (PlanType::Enterprise, "Enterprise"), (PlanType::Edu, "Edu"), diff --git a/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_shows_chatgpt_plan_without_email.snap b/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_shows_chatgpt_plan_without_email.snap index 4ac5239b94..0aba51f99a 100644 --- a/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_shows_chatgpt_plan_without_email.snap +++ b/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_shows_chatgpt_plan_without_email.snap @@ -14,7 +14,7 @@ expression: sanitized │ Directory: [[workspace]] │ │ Permissions: Custom (workspace with network access, Ask for approval) │ │ Agents.md: │ -│ Account: Enterprise │ +│ Account: Enterprise (Automation) │ │ │ │ Limits: data not available yet │ ╰──────────────────────────────────────────────────────────────────────────╯ diff --git a/codex-rs/tui/src/status/tests.rs b/codex-rs/tui/src/status/tests.rs index e8032a4486..ca28311421 100644 --- a/codex-rs/tui/src/status/tests.rs +++ b/codex-rs/tui/src/status/tests.rs @@ -365,7 +365,7 @@ async fn status_snapshot_shows_chatgpt_plan_without_email() { write_chatgpt_auth( temp_home.path(), - ChatGptAuthFixture::new("access-chatgpt").plan_type("enterprise"), + ChatGptAuthFixture::new("access-chatgpt").plan_type("enterprise_cbp_automation"), AuthCredentialsStoreMode::File, ) .expect("write email-less ChatGPT auth"); @@ -384,7 +384,7 @@ async fn status_snapshot_shows_chatgpt_plan_without_email() { account_display, StatusAccountDisplay::ChatGpt { email: None, - plan: Some("Enterprise".to_string()), + plan: Some("Enterprise (Automation)".to_string()), } ); let usage = TokenUsage::default();