mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-25 03:32:32 +00:00
feat(models): add gpt-5.4-mini and simplify auth refresh
This commit is contained in:
@@ -79,6 +79,36 @@ static PRESETS: Lazy<Vec<ModelPreset>> = Lazy::new(|| {
|
||||
pro_only: false,
|
||||
show_in_picker: true,
|
||||
},
|
||||
ModelPreset {
|
||||
id: "gpt-5.4-mini".to_string(),
|
||||
model: "gpt-5.4-mini".to_string(),
|
||||
display_name: "gpt-5.4-mini".to_string(),
|
||||
description: "Smaller GPT-5.4 variant tuned for faster coding loops.".to_string(),
|
||||
default_reasoning_effort: ReasoningEffort::Medium,
|
||||
supported_reasoning_efforts: vec![
|
||||
ReasoningEffortPreset {
|
||||
effort: ReasoningEffort::Low,
|
||||
description: "Fast responses with lighter reasoning".to_string(),
|
||||
},
|
||||
ReasoningEffortPreset {
|
||||
effort: ReasoningEffort::Medium,
|
||||
description: "Balances speed and reasoning depth for everyday tasks".to_string(),
|
||||
},
|
||||
ReasoningEffortPreset {
|
||||
effort: ReasoningEffort::High,
|
||||
description: "Maximizes reasoning depth for complex problems".to_string(),
|
||||
},
|
||||
ReasoningEffortPreset {
|
||||
effort: ReasoningEffort::XHigh,
|
||||
description: "Extra high reasoning depth for complex problems".to_string(),
|
||||
},
|
||||
],
|
||||
supported_text_verbosity: ALL_TEXT_VERBOSITY,
|
||||
is_default: false,
|
||||
upgrade: None,
|
||||
pro_only: false,
|
||||
show_in_picker: true,
|
||||
},
|
||||
ModelPreset {
|
||||
id: "gpt-5.3-codex".to_string(),
|
||||
model: "gpt-5.3-codex".to_string(),
|
||||
@@ -640,6 +670,7 @@ mod tests {
|
||||
fn gpt_5_4_available_for_api_key_auth() {
|
||||
let presets = builtin_model_presets(Some(AuthMode::ApiKey), false);
|
||||
assert!(presets.iter().any(|preset| preset.id == "gpt-5.4"));
|
||||
assert!(presets.iter().any(|preset| preset.id == "gpt-5.4-mini"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+32
-30
@@ -188,43 +188,29 @@ impl CodexAuth {
|
||||
pub async fn get_token_data(&self) -> Result<TokenData, std::io::Error> {
|
||||
let auth_dot_json: Option<AuthDotJson> = self.get_current_auth_json();
|
||||
match auth_dot_json {
|
||||
Some(AuthDotJson {
|
||||
tokens: Some(mut tokens),
|
||||
last_refresh: Some(last_refresh),
|
||||
..
|
||||
}) => {
|
||||
Some(auth_dot_json) => {
|
||||
let mut tokens = auth_dot_json
|
||||
.tokens
|
||||
.clone()
|
||||
.ok_or(std::io::Error::other("Token data is not available."))?;
|
||||
if self.mode == AuthMode::ChatgptAuthTokens {
|
||||
return Ok(tokens);
|
||||
}
|
||||
if last_refresh < Utc::now() - chrono::Duration::days(28) {
|
||||
let refresh_response = tokio::time::timeout(
|
||||
Duration::from_secs(60),
|
||||
try_refresh_token(tokens.refresh_token.clone(), &self.client),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| {
|
||||
std::io::Error::other("timed out while refreshing OpenAI API key")
|
||||
})?
|
||||
.map_err(|err| std::io::Error::other(err))?;
|
||||
if should_proactively_refresh_auth(auth_dot_json.last_refresh) {
|
||||
tokio::time::timeout(Duration::from_secs(60), self.refresh_token())
|
||||
.await
|
||||
.map_err(|_| {
|
||||
std::io::Error::other(
|
||||
"timed out while refreshing OpenAI API key",
|
||||
)
|
||||
})?
|
||||
.map_err(std::io::Error::other)?;
|
||||
|
||||
let updated_auth_dot_json = update_tokens(
|
||||
&self.auth_file,
|
||||
refresh_response.id_token,
|
||||
refresh_response.access_token,
|
||||
refresh_response.refresh_token,
|
||||
)
|
||||
.await?;
|
||||
|
||||
tokens = updated_auth_dot_json
|
||||
.tokens
|
||||
.clone()
|
||||
tokens = self
|
||||
.get_current_token_data()
|
||||
.ok_or(std::io::Error::other(
|
||||
"Token data is not available after refresh.",
|
||||
))?;
|
||||
|
||||
#[expect(clippy::unwrap_used)]
|
||||
let mut auth_lock = self.auth_dot_json.lock().unwrap();
|
||||
*auth_lock = Some(updated_auth_dot_json);
|
||||
}
|
||||
|
||||
Ok(tokens)
|
||||
@@ -346,6 +332,12 @@ impl CodexAuth {
|
||||
}
|
||||
}
|
||||
|
||||
fn should_proactively_refresh_auth(last_refresh: Option<DateTime<Utc>>) -> bool {
|
||||
last_refresh.is_some_and(|last_refresh| {
|
||||
last_refresh < Utc::now() - chrono::Duration::days(28)
|
||||
})
|
||||
}
|
||||
|
||||
pub const OPENAI_API_KEY_ENV_VAR: &str = "OPENAI_API_KEY";
|
||||
pub const CODEX_API_KEY_ENV_VAR: &str = "CODEX_API_KEY";
|
||||
|
||||
@@ -1307,6 +1299,16 @@ mod tests {
|
||||
assert_eq!(updated.access_token, rotated_access);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proactive_refresh_only_triggers_for_stale_chatgpt_auth() {
|
||||
let fresh = Utc::now() - chrono::Duration::days(1);
|
||||
let stale = Utc::now() - chrono::Duration::days(29);
|
||||
|
||||
assert!(!should_proactively_refresh_auth(Some(fresh)));
|
||||
assert!(should_proactively_refresh_auth(Some(stale)));
|
||||
assert!(!should_proactively_refresh_auth(None));
|
||||
}
|
||||
|
||||
struct AuthFileParams {
|
||||
openai_api_key: Option<String>,
|
||||
chatgpt_plan_type: String,
|
||||
|
||||
@@ -922,30 +922,34 @@ impl ModelSelectionView {
|
||||
fn model_rank(model: &str) -> u8 {
|
||||
if model.eq_ignore_ascii_case("gpt-5.4") {
|
||||
0
|
||||
} else if model.eq_ignore_ascii_case("gpt-5.3-codex") {
|
||||
} else if model.eq_ignore_ascii_case("gpt-5.4-mini") {
|
||||
1
|
||||
} else if model.eq_ignore_ascii_case("gpt-5.3-codex-spark") {
|
||||
} else if model.eq_ignore_ascii_case("gpt-5.3-codex") {
|
||||
2
|
||||
} else if model.eq_ignore_ascii_case("gpt-5.2-codex") {
|
||||
} else if model.eq_ignore_ascii_case("gpt-5.3-codex-spark") {
|
||||
3
|
||||
} else if model.eq_ignore_ascii_case("gpt-5.2") {
|
||||
} else if model.eq_ignore_ascii_case("gpt-5.2-codex") {
|
||||
4
|
||||
} else if model.eq_ignore_ascii_case("gpt-5.1-codex-max") {
|
||||
} else if model.eq_ignore_ascii_case("gpt-5.2") {
|
||||
5
|
||||
} else if model.eq_ignore_ascii_case("gpt-5.1-codex") {
|
||||
} else if model.eq_ignore_ascii_case("gpt-5.1-codex-max") {
|
||||
6
|
||||
} else if model.eq_ignore_ascii_case("gpt-5.1-codex-mini") {
|
||||
} else if model.eq_ignore_ascii_case("gpt-5.1-codex") {
|
||||
7
|
||||
} else if model.eq_ignore_ascii_case("gpt-5.1") {
|
||||
} else if model.eq_ignore_ascii_case("gpt-5.1-codex-mini") {
|
||||
8
|
||||
} else {
|
||||
} else if model.eq_ignore_ascii_case("gpt-5.1") {
|
||||
9
|
||||
} else {
|
||||
10
|
||||
}
|
||||
}
|
||||
|
||||
fn model_description(model: &str) -> Option<&'static str> {
|
||||
if model.eq_ignore_ascii_case("gpt-5.4") {
|
||||
Some("Brings together flagship reasoning, coding, and tool use in a single frontier model.")
|
||||
} else if model.eq_ignore_ascii_case("gpt-5.4-mini") {
|
||||
Some("Smaller GPT-5.4 variant tuned for faster coding loops.")
|
||||
} else if model.eq_ignore_ascii_case("gpt-5.3-codex") {
|
||||
Some("Frontier agentic coding, 25% faster than previous models.")
|
||||
} else if model.eq_ignore_ascii_case("gpt-5.3-codex-spark") {
|
||||
@@ -1257,7 +1261,24 @@ mod tests {
|
||||
},
|
||||
];
|
||||
|
||||
let presets = vec![make_preset("gpt-5.3-codex"), make_preset("gpt-5.3-codex-spark"), gpt_5_4];
|
||||
let mut gpt_5_4_mini = make_preset("gpt-5.4-mini");
|
||||
gpt_5_4_mini.supported_reasoning_efforts = vec![
|
||||
ReasoningEffortPreset {
|
||||
effort: ReasoningEffort::Low.into(),
|
||||
description: "Fast responses with lighter reasoning".to_string(),
|
||||
},
|
||||
ReasoningEffortPreset {
|
||||
effort: ReasoningEffort::High.into(),
|
||||
description: "Maximizes reasoning depth".to_string(),
|
||||
},
|
||||
];
|
||||
|
||||
let presets = vec![
|
||||
make_preset("gpt-5.3-codex"),
|
||||
make_preset("gpt-5.3-codex-spark"),
|
||||
gpt_5_4_mini,
|
||||
gpt_5_4,
|
||||
];
|
||||
let (tx, _rx) = mpsc::channel::<AppEvent>();
|
||||
let view = ModelSelectionView::new(
|
||||
presets,
|
||||
@@ -1290,11 +1311,15 @@ mod tests {
|
||||
let gpt_5_4_idx = visible
|
||||
.find("GPT-5.4")
|
||||
.expect("gpt-5.4 header");
|
||||
let gpt_5_4_mini_idx = visible
|
||||
.find("GPT-5.4-Mini")
|
||||
.expect("gpt-5.4-mini header");
|
||||
let gpt_5_3_idx = visible
|
||||
.find("GPT-5.3-Codex")
|
||||
.expect("gpt-5.3-codex header");
|
||||
|
||||
assert!(gpt_5_4_idx < gpt_5_3_idx);
|
||||
assert!(gpt_5_4_idx < gpt_5_4_mini_idx);
|
||||
assert!(gpt_5_4_mini_idx < gpt_5_3_idx);
|
||||
assert!(visible.contains("Mode Settings"));
|
||||
assert!(visible.contains("Fast Mode: enabled"));
|
||||
assert!(visible.contains("1M Context: enabled"));
|
||||
|
||||
Reference in New Issue
Block a user