Support the ent26 enterprise plan (#35238)

## What changed

- Recognize `ent26` in authentication, account protocol, backend rate-limit
  payloads, and generated app-server schemas.
- Treat it as an enterprise workspace plan for cloud-config eligibility and
  business-style usage-limit guidance.
- Cover token parsing, serialization, backend mapping, plan classification,
  cloud-config access, and usage-limit formatting with tests.

GitOrigin-RevId: 537d897c8b1d80bcdb66d0fd402563fbb8c5a277
This commit is contained in:
bwanner-oai
2026-07-24 20:00:59 +00:00
committed by copyberry
parent 99744cfe04
commit 544007d006
16 changed files with 71 additions and 13 deletions
@@ -2915,6 +2915,7 @@
"team",
"self_serve_business_usage_based",
"business",
"ent26",
"enterprise_cbp_usage_based",
"enterprise",
"edu",
@@ -13999,6 +13999,7 @@
"team",
"self_serve_business_usage_based",
"business",
"ent26",
"enterprise_cbp_usage_based",
"enterprise",
"edu",
@@ -10341,6 +10341,7 @@
"team",
"self_serve_business_usage_based",
"business",
"ent26",
"enterprise_cbp_usage_based",
"enterprise",
"edu",
@@ -32,6 +32,7 @@
"team",
"self_serve_business_usage_based",
"business",
"ent26",
"enterprise_cbp_usage_based",
"enterprise",
"edu",
@@ -65,6 +65,7 @@
"team",
"self_serve_business_usage_based",
"business",
"ent26",
"enterprise_cbp_usage_based",
"enterprise",
"edu",
@@ -32,6 +32,7 @@
"team",
"self_serve_business_usage_based",
"business",
"ent26",
"enterprise_cbp_usage_based",
"enterprise",
"edu",
@@ -78,6 +78,7 @@
"team",
"self_serve_business_usage_based",
"business",
"ent26",
"enterprise_cbp_usage_based",
"enterprise",
"edu",
+1 -1
View File
@@ -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_usage_based" | "business" | "enterprise_cbp_usage_based" | "enterprise" | "edu" | "unknown";
export type PlanType = "free" | "go" | "plus" | "pro" | "prolite" | "team" | "self_serve_business_usage_based" | "business" | "ent26" | "enterprise_cbp_usage_based" | "enterprise" | "edu" | "unknown";
+4
View File
@@ -689,6 +689,7 @@ impl Client {
AccountPlanType::SelfServeBusinessUsageBased
}
crate::types::PlanType::Business => AccountPlanType::Business,
crate::types::PlanType::Ent26 => AccountPlanType::Ent26,
crate::types::PlanType::EnterpriseCbpUsageBased => {
AccountPlanType::EnterpriseCbpUsageBased
}
@@ -740,6 +741,9 @@ mod tests {
Client::map_plan_type(crate::types::PlanType::EnterpriseCbpUsageBased),
AccountPlanType::EnterpriseCbpUsageBased
);
let ent26 = serde_json::from_str::<crate::types::PlanType>("\"ent26\"")
.expect("ent26 backend plan should deserialize");
assert_eq!(Client::map_plan_type(ent26), AccountPlanType::Ent26);
}
#[test]
@@ -416,6 +416,7 @@ async fn get_bundle_skips_individual_plan() {
async fn get_bundle_allows_eligible_workspace_plans_and_writes_cache() {
for plan_type in [
"business",
"ent26",
"enterprise_cbp_usage_based",
"enterprise",
"hc",
@@ -116,6 +116,8 @@ pub enum PlanType {
SelfServeBusinessUsageBased,
#[serde(rename = "business")]
Business,
#[serde(rename = "ent26")]
Ent26,
#[serde(rename = "enterprise_cbp_usage_based")]
EnterpriseCbpUsageBased,
#[serde(rename = "education")]
+15
View File
@@ -69,6 +69,21 @@ fn id_token_info_parses_hc_plan_as_enterprise() {
assert_eq!(info.is_workspace_account(), true);
}
#[test]
fn id_token_info_parses_ent26_plan() {
let fake_jwt = fake_jwt(serde_json::json!({
"email": "user@example.com",
"https://api.openai.com/auth": {
"chatgpt_plan_type": "ent26"
}
}));
let info = parse_chatgpt_jwt_claims(&fake_jwt).expect("should parse");
assert_eq!(info.get_chatgpt_plan_type().as_deref(), Some("Enterprise"));
assert_eq!(info.get_chatgpt_plan_type_raw().as_deref(), Some("ent26"));
assert_eq!(info.is_workspace_account(), true);
}
#[test]
fn id_token_info_parses_usage_based_business_plans() {
let self_serve_business_jwt = fake_jwt(serde_json::json!({
+22 -1
View File
@@ -21,6 +21,7 @@ pub enum PlanType {
#[ts(rename = "self_serve_business_usage_based")]
SelfServeBusinessUsageBased,
Business,
Ent26,
#[serde(rename = "enterprise_cbp_usage_based")]
#[ts(rename = "enterprise_cbp_usage_based")]
EnterpriseCbpUsageBased,
@@ -49,7 +50,10 @@ impl PlanType {
}
pub fn is_business_like(self) -> bool {
matches!(self, Self::Business | Self::EnterpriseCbpUsageBased)
matches!(
self,
Self::Business | Self::Ent26 | Self::EnterpriseCbpUsageBased
)
}
pub fn is_workspace_account(self) -> bool {
@@ -58,6 +62,7 @@ impl PlanType {
Self::Team
| Self::SelfServeBusinessUsageBased
| Self::Business
| Self::Ent26
| Self::EnterpriseCbpUsageBased
| Self::Enterprise
| Self::Edu
@@ -85,6 +90,7 @@ impl From<KnownPlan> for PlanType {
KnownPlan::Team => Self::Team,
KnownPlan::SelfServeBusinessUsageBased => Self::SelfServeBusinessUsageBased,
KnownPlan::Business => Self::Business,
KnownPlan::Ent26 => Self::Ent26,
KnownPlan::EnterpriseCbpUsageBased => Self::EnterpriseCbpUsageBased,
KnownPlan::Enterprise => Self::Enterprise,
KnownPlan::Edu => Self::Edu,
@@ -111,6 +117,10 @@ mod tests {
.expect("enterprise cbp usage based should serialize"),
"\"enterprise_cbp_usage_based\""
);
assert_eq!(
serde_json::to_string(&PlanType::Ent26).expect("ent26 should serialize"),
"\"ent26\""
);
assert_eq!(
serde_json::to_string(&PlanType::ProLite).expect("prolite should serialize"),
"\"prolite\""
@@ -129,6 +139,10 @@ mod tests {
.expect("enterprise cbp usage based should deserialize"),
PlanType::EnterpriseCbpUsageBased
);
assert_eq!(
serde_json::from_str::<PlanType>("\"ent26\"").expect("ent26 should deserialize"),
PlanType::Ent26
);
}
#[test]
@@ -136,8 +150,10 @@ mod tests {
assert_eq!(PlanType::Team.is_team_like(), true);
assert_eq!(PlanType::SelfServeBusinessUsageBased.is_team_like(), true);
assert_eq!(PlanType::Business.is_team_like(), false);
assert_eq!(PlanType::Ent26.is_team_like(), false);
assert_eq!(PlanType::Business.is_business_like(), true);
assert_eq!(PlanType::Ent26.is_business_like(), true);
assert_eq!(PlanType::EnterpriseCbpUsageBased.is_business_like(), true);
assert_eq!(PlanType::Team.is_business_like(), false);
}
@@ -150,6 +166,7 @@ mod tests {
true
);
assert_eq!(PlanType::Business.is_workspace_account(), true);
assert_eq!(PlanType::Ent26.is_workspace_account(), true);
assert_eq!(
PlanType::EnterpriseCbpUsageBased.is_workspace_account(),
true
@@ -165,6 +182,10 @@ mod tests {
PlanType::from(AuthPlanType::Known(KnownPlan::EnterpriseCbpUsageBased)),
PlanType::EnterpriseCbpUsageBased
);
assert_eq!(
PlanType::from(AuthPlanType::Known(KnownPlan::Ent26)),
PlanType::Ent26
);
assert_eq!(
PlanType::from(AuthPlanType::Known(KnownPlan::Enterprise)),
PlanType::Enterprise
+5
View File
@@ -75,6 +75,7 @@ impl PlanType {
Self::Known(KnownPlan::SelfServeBusinessUsageBased)
}
"business" => Self::Known(KnownPlan::Business),
"ent26" => Self::Known(KnownPlan::Ent26),
"enterprise_cbp_usage_based" => Self::Known(KnownPlan::EnterpriseCbpUsageBased),
"enterprise" | "hc" => Self::Known(KnownPlan::Enterprise),
"education" | "edu" => Self::Known(KnownPlan::Edu),
@@ -95,6 +96,7 @@ pub enum KnownPlan {
#[serde(rename = "self_serve_business_usage_based")]
SelfServeBusinessUsageBased,
Business,
Ent26,
#[serde(rename = "enterprise_cbp_usage_based")]
EnterpriseCbpUsageBased,
#[serde(alias = "hc")]
@@ -114,6 +116,7 @@ impl KnownPlan {
Self::Team => "Team",
Self::SelfServeBusinessUsageBased => "Self Serve Business Usage Based",
Self::Business => "Business",
Self::Ent26 => "Enterprise",
Self::EnterpriseCbpUsageBased => "Enterprise CBP Usage Based",
Self::Enterprise => "Enterprise",
Self::Edu => "Edu",
@@ -130,6 +133,7 @@ impl KnownPlan {
Self::Team => "team",
Self::SelfServeBusinessUsageBased => "self_serve_business_usage_based",
Self::Business => "business",
Self::Ent26 => "ent26",
Self::EnterpriseCbpUsageBased => "enterprise_cbp_usage_based",
Self::Enterprise => "enterprise",
Self::Edu => "edu",
@@ -142,6 +146,7 @@ impl KnownPlan {
Self::Team
| Self::SelfServeBusinessUsageBased
| Self::Business
| Self::Ent26
| Self::EnterpriseCbpUsageBased
| Self::Enterprise
| Self::Edu
+1
View File
@@ -688,6 +688,7 @@ impl std::fmt::Display for UsageLimitReachedError {
KnownPlan::Team
| KnownPlan::SelfServeBusinessUsageBased
| KnownPlan::Business
| KnownPlan::Ent26
| KnownPlan::EnterpriseCbpUsageBased,
)) => {
format!(
+13 -11
View File
@@ -336,17 +336,19 @@ fn usage_limit_reached_error_formats_team_plan() {
#[test]
fn usage_limit_reached_error_formats_business_plan_without_reset() {
let err = UsageLimitReachedError {
plan_type: Some(PlanType::Known(KnownPlan::Business)),
resets_at: None,
rate_limits: Some(Box::new(rate_limit_snapshot())),
promo_message: None,
rate_limit_reached_type: None,
};
assert_eq!(
err.to_string(),
"You've hit your usage limit. To get more access now, send a request to your admin or try again later."
);
for plan in [KnownPlan::Business, KnownPlan::Ent26] {
let err = UsageLimitReachedError {
plan_type: Some(PlanType::Known(plan)),
resets_at: None,
rate_limits: Some(Box::new(rate_limit_snapshot())),
promo_message: None,
rate_limit_reached_type: None,
};
assert_eq!(
err.to_string(),
"You've hit your usage limit. To get more access now, send a request to your admin or try again later."
);
}
}
#[test]