Skip to content

Commit dd26467

Browse files
enieuwyzemaj
authored andcommitted
fix(auth): treat prolite as pro-capable
1 parent e789d70 commit dd26467

2 files changed

Lines changed: 64 additions & 2 deletions

File tree

code-rs/core/src/auth.rs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,8 +259,9 @@ impl CodexAuth {
259259
pub fn supports_pro_only_models(&self) -> bool {
260260
self.uses_codex_backend()
261261
&& self
262-
.get_plan_type()
263-
.is_some_and(|plan| plan.eq_ignore_ascii_case("pro"))
262+
.get_current_token_data()
263+
.and_then(|t| t.id_token.chatgpt_plan_type)
264+
.is_some_and(|plan| plan.supports_pro_only_models())
264265
}
265266

266267
fn get_current_auth_json(&self) -> Option<AuthDotJson> {
@@ -430,6 +431,7 @@ pub fn login_with_chatgpt_auth_tokens(
430431
"free" => PlanType::Known(KnownPlan::Free),
431432
"plus" => PlanType::Known(KnownPlan::Plus),
432433
"pro" => PlanType::Known(KnownPlan::Pro),
434+
"prolite" => PlanType::Known(KnownPlan::ProLite),
433435
"team" => PlanType::Known(KnownPlan::Team),
434436
"business" => PlanType::Known(KnownPlan::Business),
435437
"enterprise" => PlanType::Known(KnownPlan::Enterprise),
@@ -1120,6 +1122,27 @@ mod tests {
11201122
)
11211123
}
11221124

1125+
#[test]
1126+
fn prolite_account_supports_pro_only_models() {
1127+
let auth = CodexAuth::from_tokens_with_originator(
1128+
TokenData {
1129+
id_token: IdTokenInfo {
1130+
email: Some("user@example.com".to_string()),
1131+
chatgpt_plan_type: Some(PlanType::Known(KnownPlan::ProLite)),
1132+
chatgpt_account_is_fedramp: false,
1133+
raw_jwt: "header.payload.signature".to_string(),
1134+
},
1135+
access_token: "test-access-token".to_string(),
1136+
refresh_token: "test-refresh-token".to_string(),
1137+
account_id: None,
1138+
},
1139+
None,
1140+
"code_cli_rs",
1141+
);
1142+
1143+
assert!(auth.supports_pro_only_models());
1144+
}
1145+
11231146
/// Even if the OPENAI_API_KEY is set in auth.json, if the plan is not in
11241147
/// [`TokenData::is_plan_that_should_use_api_key`], it should use
11251148
/// [`AuthMode::ChatGPT`].

code-rs/core/src/token_data.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ impl PlanType {
7373
Self::Unknown(s) => s.clone(),
7474
}
7575
}
76+
77+
pub(crate) fn supports_pro_only_models(&self) -> bool {
78+
matches!(self, Self::Known(KnownPlan::Pro | KnownPlan::ProLite))
79+
|| matches!(self, Self::Unknown(plan) if plan.eq_ignore_ascii_case("prolite"))
80+
}
7681
}
7782

7883
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@@ -81,6 +86,7 @@ pub(crate) enum KnownPlan {
8186
Free,
8287
Plus,
8388
Pro,
89+
ProLite,
8490
Team,
8591
Business,
8692
Enterprise,
@@ -224,6 +230,39 @@ mod tests {
224230
assert!(!info.is_fedramp_account());
225231
}
226232

233+
#[test]
234+
fn prolite_plan_supports_pro_only_models() {
235+
#[derive(Serialize)]
236+
struct Header {
237+
alg: &'static str,
238+
typ: &'static str,
239+
}
240+
let header = Header {
241+
alg: "none",
242+
typ: "JWT",
243+
};
244+
let payload = serde_json::json!({
245+
"email": "user@example.com",
246+
"https://api.openai.com/auth": {
247+
"chatgpt_plan_type": "prolite"
248+
}
249+
});
250+
251+
fn b64url_no_pad(bytes: &[u8]) -> String {
252+
base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(bytes)
253+
}
254+
255+
let header_b64 = b64url_no_pad(&serde_json::to_vec(&header).unwrap());
256+
let payload_b64 = b64url_no_pad(&serde_json::to_vec(&payload).unwrap());
257+
let signature_b64 = b64url_no_pad(b"sig");
258+
let fake_jwt = format!("{header_b64}.{payload_b64}.{signature_b64}");
259+
260+
let info = parse_id_token(&fake_jwt).expect("should parse");
261+
let plan = info.chatgpt_plan_type.expect("plan should parse");
262+
assert_eq!(plan.as_string(), "prolite");
263+
assert!(plan.supports_pro_only_models());
264+
}
265+
227266
#[test]
228267
fn id_token_info_handles_missing_fields() {
229268
#[derive(Serialize)]

0 commit comments

Comments
 (0)