Load models from static file (#8153)

- Load models from static file as a fallback
- Make API users use this file directly
- Add tests to make sure updates to the file always serialize
This commit is contained in:
Ahmed Ibrahim
2025-12-17 14:34:13 -08:00
committed by GitHub
parent a9a7cf3488
commit 927a6acbea
10 changed files with 606 additions and 85 deletions

View File

@@ -762,7 +762,7 @@ async fn includes_configured_effort_in_request() -> anyhow::Result<()> {
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn includes_no_effort_in_request() -> anyhow::Result<()> {
async fn includes_default_effort_in_request() -> anyhow::Result<()> {
skip_if_no_network!(Ok(()));
let server = MockServer::start().await;
@@ -791,7 +791,7 @@ async fn includes_no_effort_in_request() -> anyhow::Result<()> {
.get("reasoning")
.and_then(|t| t.get("effort"))
.and_then(|v| v.as_str()),
None
Some("medium")
);
Ok(())

View File

@@ -276,6 +276,7 @@ async fn compact_resume_and_fork_preserve_model_history_view() {
"tool_choice": "auto",
"parallel_tool_calls": false,
"reasoning": {
"effort": "medium",
"summary": "auto"
},
"store": false,
@@ -345,6 +346,7 @@ async fn compact_resume_and_fork_preserve_model_history_view() {
"tool_choice": "auto",
"parallel_tool_calls": false,
"reasoning": {
"effort": "medium",
"summary": "auto"
},
"store": false,
@@ -405,6 +407,7 @@ async fn compact_resume_and_fork_preserve_model_history_view() {
"tool_choice": "auto",
"parallel_tool_calls": false,
"reasoning": {
"effort": "medium",
"summary": "auto"
},
"store": false,
@@ -485,6 +488,7 @@ async fn compact_resume_and_fork_preserve_model_history_view() {
"tool_choice": "auto",
"parallel_tool_calls": false,
"reasoning": {
"effort": "medium",
"summary": "auto"
},
"store": false,
@@ -565,6 +569,7 @@ async fn compact_resume_and_fork_preserve_model_history_view() {
"tool_choice": "auto",
"parallel_tool_calls": false,
"reasoning": {
"effort": "medium",
"summary": "auto"
},
"store": false,

View File

@@ -114,7 +114,14 @@ fn gpt_5_1_codex() -> ModelPreset {
),
],
is_default: false,
upgrade: Some(gpt_5_1_codex_max_upgrade()),
upgrade: Some(gpt_5_1_codex_max_upgrade(
"gpt-5.1-codex",
vec![
ReasoningEffort::Low,
ReasoningEffort::Medium,
ReasoningEffort::High,
],
)),
show_in_picker: true,
}
}
@@ -137,7 +144,10 @@ fn gpt_5_1_codex_mini() -> ModelPreset {
),
],
is_default: false,
upgrade: Some(gpt_5_1_codex_max_upgrade()),
upgrade: Some(gpt_5_1_codex_max_upgrade(
"gpt-5.1-codex-mini",
vec![ReasoningEffort::Medium, ReasoningEffort::High],
)),
show_in_picker: true,
}
}
@@ -162,7 +172,7 @@ fn gpt_5_2() -> ModelPreset {
),
effort(
ReasoningEffort::High,
"Greater reasoning depth for complex or ambiguous problems",
"Maximizes reasoning depth for complex or ambiguous problems",
),
effort(
ReasoningEffort::XHigh,
@@ -197,16 +207,59 @@ fn gpt_5_1() -> ModelPreset {
),
],
is_default: false,
upgrade: Some(gpt_5_1_codex_max_upgrade()),
upgrade: Some(gpt_5_1_codex_max_upgrade(
"gpt-5.1",
vec![
ReasoningEffort::Low,
ReasoningEffort::Medium,
ReasoningEffort::High,
],
)),
show_in_picker: true,
}
}
fn gpt_5_1_codex_max_upgrade() -> codex_protocol::openai_models::ModelUpgrade {
fn gpt_5_1_codex_max_upgrade(
migration_config_key: &str,
supported_efforts: Vec<ReasoningEffort>,
) -> codex_protocol::openai_models::ModelUpgrade {
use std::collections::HashMap;
fn nearest_effort(effort: ReasoningEffort, supported: &[ReasoningEffort]) -> ReasoningEffort {
supported
.iter()
.min_by_key(|candidate| (effort_rank(effort) - effort_rank(**candidate)).abs())
.copied()
.unwrap_or(ReasoningEffort::Low)
}
fn effort_rank(effort: ReasoningEffort) -> i32 {
match effort {
ReasoningEffort::None => 0,
ReasoningEffort::Minimal => 1,
ReasoningEffort::Low => 2,
ReasoningEffort::Medium => 3,
ReasoningEffort::High => 4,
ReasoningEffort::XHigh => 5,
}
}
let mut mapping = HashMap::new();
for effort in [
ReasoningEffort::None,
ReasoningEffort::Minimal,
ReasoningEffort::Low,
ReasoningEffort::Medium,
ReasoningEffort::High,
ReasoningEffort::XHigh,
] {
mapping.insert(effort, nearest_effort(effort, &supported_efforts));
}
codex_protocol::openai_models::ModelUpgrade {
id: "gpt-5.1-codex-max".to_string(),
reasoning_effort_mapping: None,
migration_config_key: "hide_gpt-5.1-codex-max_migration_prompt".to_string(),
reasoning_effort_mapping: Some(mapping),
migration_config_key: migration_config_key.to_string(),
}
}

View File

@@ -59,60 +59,8 @@ fn assert_tool_names(body: &serde_json::Value, expected_names: &[&str]) {
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn codex_mini_latest_tools() -> anyhow::Result<()> {
skip_if_no_network!(Ok(()));
use pretty_assertions::assert_eq;
let server = start_mock_server().await;
let req1 = mount_sse_once(&server, sse_completed("resp-1")).await;
let req2 = mount_sse_once(&server, sse_completed("resp-2")).await;
let TestCodex { codex, .. } = test_codex()
.with_config(|config| {
config.user_instructions = Some("be consistent and helpful".to_string());
config.features.disable(Feature::ApplyPatchFreeform);
config.model = Some("codex-mini-latest".to_string());
})
.build(&server)
.await?;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
text: "hello 1".into(),
}],
})
.await?;
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TaskComplete(_))).await;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
text: "hello 2".into(),
}],
})
.await?;
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TaskComplete(_))).await;
let expected_instructions = [
include_str!("../../prompt.md"),
include_str!("../../../apply-patch/apply_patch_tool_instructions.md"),
]
.join("\n");
let body0 = req1.single_request().body_json();
assert_eq!(
body0["instructions"],
serde_json::json!(expected_instructions),
);
let body1 = req2.single_request().body_json();
assert_eq!(
body1["instructions"],
serde_json::json!(expected_instructions),
);
Ok(())
fn normalize_newlines(text: &str) -> String {
text.replace("\r\n", "\n")
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
@@ -204,6 +152,70 @@ async fn prompt_tools_are_consistent_across_requests() -> anyhow::Result<()> {
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn codex_mini_latest_tools() -> anyhow::Result<()> {
skip_if_no_network!(Ok(()));
use pretty_assertions::assert_eq;
let server = start_mock_server().await;
let req1 = mount_sse_once(&server, sse_completed("resp-1")).await;
let req2 = mount_sse_once(&server, sse_completed("resp-2")).await;
let TestCodex { codex, .. } = test_codex()
.with_config(|config| {
config.user_instructions = Some("be consistent and helpful".to_string());
config.features.disable(Feature::ApplyPatchFreeform);
config.model = Some("codex-mini-latest".to_string());
})
.build(&server)
.await?;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
text: "hello 1".into(),
}],
})
.await?;
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TaskComplete(_))).await;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
text: "hello 2".into(),
}],
})
.await?;
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TaskComplete(_))).await;
let expected_instructions = [
include_str!("../../prompt.md"),
include_str!("../../../apply-patch/apply_patch_tool_instructions.md"),
]
.join("\n");
let body0 = req1.single_request().body_json();
let instructions0 = body0["instructions"]
.as_str()
.expect("instructions should be a string");
assert_eq!(
normalize_newlines(instructions0),
normalize_newlines(&expected_instructions)
);
let body1 = req2.single_request().body_json();
let instructions1 = body1["instructions"]
.as_str()
.expect("instructions should be a string");
assert_eq!(
normalize_newlines(instructions1),
normalize_newlines(&expected_instructions)
);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn prefixes_context_and_instructions_once_and_consistently_across_requests()
-> anyhow::Result<()> {

View File

@@ -319,7 +319,7 @@ async fn remote_models_preserve_builtin_presets() -> Result<()> {
let mut config = load_default_config_for_test(&codex_home);
config.features.enable(Feature::RemoteModels);
let auth = CodexAuth::from_api_key("dummy");
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
let provider = ModelProviderInfo {
base_url: Some(format!("{}/v1", server.uri())),
..built_in_model_providers()["openai"].clone()
@@ -436,7 +436,7 @@ async fn build_remote_models_harness<F>(
where
F: FnOnce(&mut Config),
{
let auth = CodexAuth::from_api_key("dummy");
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
let home = Arc::new(TempDir::new()?);
let cwd = Arc::new(TempDir::new()?);