diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index 8a329d067..f24cc9bc6 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -90,6 +90,7 @@ wildmatch = { workspace = true } [features] deterministic_process_ids = [] +test-support = [] [target.'cfg(target_os = "linux")'.dependencies] diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index d35f95e42..89435ee6d 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -14,6 +14,7 @@ use crate::compact_remote::run_inline_remote_auto_compact_task; use crate::exec_policy::load_exec_policy_for_features; use crate::features::Feature; use crate::features::Features; +use crate::openai_models::model_family::ModelFamily; use crate::openai_models::models_manager::ModelsManager; use crate::parse_command::parse_command; use crate::parse_turn_item; @@ -398,35 +399,39 @@ pub(crate) struct SessionSettingsUpdate { } impl Session { - fn make_turn_context( - auth_manager: Option>, - models_manager: Arc, - otel_event_manager: &OtelEventManager, - provider: ModelProviderInfo, - session_configuration: &SessionConfiguration, - conversation_id: ConversationId, - sub_id: String, - ) -> TurnContext { + fn build_per_turn_config(session_configuration: &SessionConfiguration) -> Config { let config = session_configuration.original_config_do_not_use.clone(); - let features = &config.features; let mut per_turn_config = (*config).clone(); per_turn_config.model = session_configuration.model.clone(); per_turn_config.model_reasoning_effort = session_configuration.model_reasoning_effort; per_turn_config.model_reasoning_summary = session_configuration.model_reasoning_summary; - per_turn_config.features = features.clone(); - let model_family = - models_manager.construct_model_family(&per_turn_config.model, &per_turn_config); + per_turn_config.features = config.features.clone(); + per_turn_config + } + + #[allow(clippy::too_many_arguments)] + fn make_turn_context( + auth_manager: Option>, + otel_event_manager: &OtelEventManager, + provider: ModelProviderInfo, + session_configuration: &SessionConfiguration, + mut per_turn_config: Config, + model_family: ModelFamily, + conversation_id: ConversationId, + sub_id: String, + ) -> TurnContext { if let Some(model_info) = get_model_info(&model_family) { per_turn_config.model_context_window = Some(model_info.context_window); } let otel_event_manager = otel_event_manager.clone().with_model( session_configuration.model.as_str(), - session_configuration.model.as_str(), + model_family.slug.as_str(), ); + let per_turn_config = Arc::new(per_turn_config); let client = ModelClient::new( - Arc::new(per_turn_config.clone()), + per_turn_config.clone(), auth_manager, model_family.clone(), otel_event_manager, @@ -439,7 +444,7 @@ impl Session { let tools_config = ToolsConfig::new(&ToolsConfigParams { model_family: &model_family, - features, + features: &per_turn_config.features, }); TurnContext { @@ -452,14 +457,14 @@ impl Session { user_instructions: session_configuration.user_instructions.clone(), approval_policy: session_configuration.approval_policy, sandbox_policy: session_configuration.sandbox_policy.clone(), - shell_environment_policy: config.shell_environment_policy.clone(), + shell_environment_policy: per_turn_config.shell_environment_policy.clone(), tools_config, final_output_json_schema: None, - codex_linux_sandbox_exe: config.codex_linux_sandbox_exe.clone(), + codex_linux_sandbox_exe: per_turn_config.codex_linux_sandbox_exe.clone(), tool_call_gate: Arc::new(ReadinessFlag::new()), exec_policy: session_configuration.exec_policy.clone(), truncation_policy: TruncationPolicy::new( - &per_turn_config, + per_turn_config.as_ref(), model_family.truncation_policy, ), } @@ -545,7 +550,9 @@ impl Session { }); } - let model_family = models_manager.construct_model_family(&config.model, &config); + let model_family = models_manager + .construct_model_family(&config.model, &config) + .await; // todo(aibrahim): why are we passing model here while it can change? let otel_event_manager = OtelEventManager::new( conversation_id, @@ -768,12 +775,19 @@ impl Session { session_configuration }; + let per_turn_config = Self::build_per_turn_config(&session_configuration); + let model_family = self + .services + .models_manager + .construct_model_family(&per_turn_config.model, &per_turn_config) + .await; let mut turn_context: TurnContext = Self::make_turn_context( Some(Arc::clone(&self.services.auth_manager)), - Arc::clone(&self.services.models_manager), &self.services.otel_event_manager, session_configuration.provider.clone(), &session_configuration, + per_turn_config, + model_family, self.conversation_id, sub_id, ); @@ -1907,7 +1921,8 @@ async fn spawn_review_thread( let review_model_family = sess .services .models_manager - .construct_model_family(&model, &config); + .construct_model_family(&model, &config) + .await; // For reviews, disable web_search and view_image regardless of global settings. let mut review_features = sess.features.clone(); review_features @@ -2812,15 +2827,12 @@ mod tests { fn otel_event_manager( conversation_id: ConversationId, config: &Config, - models_manager: &ModelsManager, + model_family: &ModelFamily, ) -> OtelEventManager { OtelEventManager::new( conversation_id, config.model.as_str(), - models_manager - .construct_model_family(&config.model, config) - .slug - .as_str(), + model_family.slug.as_str(), None, Some("test@test.com".to_string()), Some(AuthMode::ChatGPT), @@ -2843,9 +2855,6 @@ mod tests { let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key")); let models_manager = Arc::new(ModelsManager::new(auth_manager.clone())); - let otel_event_manager = - otel_event_manager(conversation_id, config.as_ref(), &models_manager); - let session_configuration = SessionConfiguration { provider: config.model_provider.clone(), model: config.model.clone(), @@ -2862,6 +2871,11 @@ mod tests { exec_policy: Arc::new(RwLock::new(ExecPolicy::empty())), session_source: SessionSource::Exec, }; + let per_turn_config = Session::build_per_turn_config(&session_configuration); + let model_family = + ModelsManager::construct_model_family_offline(&per_turn_config.model, &per_turn_config); + let otel_event_manager = + otel_event_manager(conversation_id, config.as_ref(), &model_family); let state = SessionState::new(session_configuration.clone()); @@ -2875,16 +2889,17 @@ mod tests { show_raw_agent_reasoning: config.show_raw_agent_reasoning, auth_manager: auth_manager.clone(), otel_event_manager: otel_event_manager.clone(), - models_manager: models_manager.clone(), + models_manager, tool_approvals: Mutex::new(ApprovalStore::default()), }; let turn_context = Session::make_turn_context( Some(Arc::clone(&auth_manager)), - models_manager, &otel_event_manager, session_configuration.provider.clone(), &session_configuration, + per_turn_config, + model_family, conversation_id, "turn_id".to_string(), ); @@ -2922,9 +2937,6 @@ mod tests { let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key")); let models_manager = Arc::new(ModelsManager::new(auth_manager.clone())); - let otel_event_manager = - otel_event_manager(conversation_id, config.as_ref(), &models_manager); - let session_configuration = SessionConfiguration { provider: config.model_provider.clone(), model: config.model.clone(), @@ -2941,6 +2953,11 @@ mod tests { exec_policy: Arc::new(RwLock::new(ExecPolicy::empty())), session_source: SessionSource::Exec, }; + let per_turn_config = Session::build_per_turn_config(&session_configuration); + let model_family = + ModelsManager::construct_model_family_offline(&per_turn_config.model, &per_turn_config); + let otel_event_manager = + otel_event_manager(conversation_id, config.as_ref(), &model_family); let state = SessionState::new(session_configuration.clone()); @@ -2954,16 +2971,17 @@ mod tests { show_raw_agent_reasoning: config.show_raw_agent_reasoning, auth_manager: Arc::clone(&auth_manager), otel_event_manager: otel_event_manager.clone(), - models_manager: models_manager.clone(), + models_manager, tool_approvals: Mutex::new(ApprovalStore::default()), }; let turn_context = Arc::new(Session::make_turn_context( Some(Arc::clone(&auth_manager)), - models_manager, &otel_event_manager, session_configuration.provider.clone(), &session_configuration, + per_turn_config, + model_family, conversation_id, "turn_id".to_string(), )); diff --git a/codex-rs/core/src/openai_models/models_manager.rs b/codex-rs/core/src/openai_models/models_manager.rs index fd0fea362..22edf04ff 100644 --- a/codex-rs/core/src/openai_models/models_manager.rs +++ b/codex-rs/core/src/openai_models/models_manager.rs @@ -61,7 +61,14 @@ impl ModelsManager { Ok(models) } - pub fn construct_model_family(&self, model: &str, config: &Config) -> ModelFamily { + pub async fn construct_model_family(&self, model: &str, config: &Config) -> ModelFamily { + find_family_for_model(model) + .with_config_overrides(config) + .with_remote_overrides(self.remote_models.read().await.clone()) + } + + #[cfg(any(test, feature = "test-support"))] + pub fn construct_model_family_offline(model: &str, config: &Config) -> ModelFamily { find_family_for_model(model).with_config_overrides(config) } diff --git a/codex-rs/core/src/sandboxing/assessment.rs b/codex-rs/core/src/sandboxing/assessment.rs index 8a34a9332..b7a9c952d 100644 --- a/codex-rs/core/src/sandboxing/assessment.rs +++ b/codex-rs/core/src/sandboxing/assessment.rs @@ -126,7 +126,9 @@ pub(crate) async fn assess_command( output_schema: Some(sandbox_assessment_schema()), }; - let model_family = models_manager.construct_model_family(&config.model, &config); + let model_family = models_manager + .construct_model_family(&config.model, &config) + .await; let child_otel = parent_otel.with_model(config.model.as_str(), model_family.slug.as_str()); diff --git a/codex-rs/core/tests/chat_completions_payload.rs b/codex-rs/core/tests/chat_completions_payload.rs index db1407455..1449a833d 100644 --- a/codex-rs/core/tests/chat_completions_payload.rs +++ b/codex-rs/core/tests/chat_completions_payload.rs @@ -1,8 +1,6 @@ use std::sync::Arc; use codex_app_server_protocol::AuthMode; -use codex_core::AuthManager; -use codex_core::CodexAuth; use codex_core::ContentItem; use codex_core::LocalShellAction; use codex_core::LocalShellExecAction; @@ -73,9 +71,7 @@ async fn run_request(input: Vec) -> Value { let config = Arc::new(config); let conversation_id = ConversationId::new(); - let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key")); - let models_manager = Arc::new(ModelsManager::new(auth_manager)); - let model_family = models_manager.construct_model_family(&config.model, &config); + let model_family = ModelsManager::construct_model_family_offline(&config.model, &config); let otel_event_manager = OtelEventManager::new( conversation_id, config.model.as_str(), diff --git a/codex-rs/core/tests/chat_completions_sse.rs b/codex-rs/core/tests/chat_completions_sse.rs index 0351263eb..fe7ec5894 100644 --- a/codex-rs/core/tests/chat_completions_sse.rs +++ b/codex-rs/core/tests/chat_completions_sse.rs @@ -1,6 +1,5 @@ use assert_matches::assert_matches; use codex_core::AuthManager; -use codex_core::openai_models::models_manager::ModelsManager; use std::sync::Arc; use tracing_test::traced_test; @@ -12,6 +11,7 @@ use codex_core::Prompt; use codex_core::ResponseEvent; use codex_core::ResponseItem; use codex_core::WireApi; +use codex_core::openai_models::models_manager::ModelsManager; use codex_otel::otel_event_manager::OtelEventManager; use codex_protocol::ConversationId; use codex_protocol::models::ReasoningItemContent; @@ -74,8 +74,7 @@ async fn run_stream_with_bytes(sse_body: &[u8]) -> Vec { let conversation_id = ConversationId::new(); let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key")); let auth_mode = auth_manager.get_auth_mode(); - let models_manager = Arc::new(ModelsManager::new(auth_manager)); - let model_family = models_manager.construct_model_family(&config.model, &config); + let model_family = ModelsManager::construct_model_family_offline(&config.model, &config); let otel_event_manager = OtelEventManager::new( conversation_id, config.model.as_str(), diff --git a/codex-rs/core/tests/common/Cargo.toml b/codex-rs/core/tests/common/Cargo.toml index 75af1b4dd..09da4bc70 100644 --- a/codex-rs/core/tests/common/Cargo.toml +++ b/codex-rs/core/tests/common/Cargo.toml @@ -11,7 +11,7 @@ path = "lib.rs" anyhow = { workspace = true } assert_cmd = { workspace = true } base64 = { workspace = true } -codex-core = { workspace = true } +codex-core = { workspace = true, features = ["test-support"] } codex-protocol = { workspace = true } notify = { workspace = true } regex-lite = { workspace = true } diff --git a/codex-rs/core/tests/responses_headers.rs b/codex-rs/core/tests/responses_headers.rs index 02423f3df..d79de7216 100644 --- a/codex-rs/core/tests/responses_headers.rs +++ b/codex-rs/core/tests/responses_headers.rs @@ -65,9 +65,7 @@ async fn responses_stream_includes_subagent_header_on_review() { let conversation_id = ConversationId::new(); let auth_mode = AuthMode::ChatGPT; - let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key")); - let models_manager = Arc::new(ModelsManager::new(auth_manager)); - let model_family = models_manager.construct_model_family(&config.model, &config); + let model_family = ModelsManager::construct_model_family_offline(&config.model, &config); let otel_event_manager = OtelEventManager::new( conversation_id, config.model.as_str(), @@ -157,9 +155,7 @@ async fn responses_stream_includes_subagent_header_on_other() { let conversation_id = ConversationId::new(); let auth_mode = AuthMode::ChatGPT; - let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key")); - let models_manager = Arc::new(ModelsManager::new(auth_manager)); - let model_family = models_manager.construct_model_family(&config.model, &config); + let model_family = ModelsManager::construct_model_family_offline(&config.model, &config); let otel_event_manager = OtelEventManager::new( conversation_id, @@ -250,16 +246,16 @@ async fn responses_respects_model_family_overrides_from_config() { let config = Arc::new(config); let conversation_id = ConversationId::new(); - let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key")); - let models_manager = Arc::new(ModelsManager::new(auth_manager.clone())); - let model_family = models_manager.construct_model_family(&config.model, &config); + let auth_mode = + AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key")).get_auth_mode(); + let model_family = ModelsManager::construct_model_family_offline(&config.model, &config); let otel_event_manager = OtelEventManager::new( conversation_id, config.model.as_str(), model_family.slug.as_str(), None, Some("test@test.com".to_string()), - auth_manager.get_auth_mode(), + auth_mode, false, "test".to_string(), ); diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index a508ae681..8b3d63a41 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -1015,11 +1015,9 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() { let effort = config.model_reasoning_effort; let summary = config.model_reasoning_summary; let config = Arc::new(config); - + let model_family = ModelsManager::construct_model_family_offline(&config.model, &config); let conversation_id = ConversationId::new(); let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key")); - let models_manager = Arc::new(ModelsManager::new(auth_manager.clone())); - let model_family = models_manager.construct_model_family(&config.model, &config); let otel_event_manager = OtelEventManager::new( conversation_id, config.model.as_str(), diff --git a/codex-rs/core/tests/suite/prompt_caching.rs b/codex-rs/core/tests/suite/prompt_caching.rs index 95f2d35cd..2bc71298d 100644 --- a/codex-rs/core/tests/suite/prompt_caching.rs +++ b/codex-rs/core/tests/suite/prompt_caching.rs @@ -137,6 +137,7 @@ async fn prompt_tools_are_consistent_across_requests() -> anyhow::Result<()> { let base_instructions = conversation_manager .get_models_manager() .construct_model_family(&config.model, &config) + .await .base_instructions .clone(); diff --git a/codex-rs/tui/Cargo.toml b/codex-rs/tui/Cargo.toml index 248205c42..4e5fad06b 100644 --- a/codex-rs/tui/Cargo.toml +++ b/codex-rs/tui/Cargo.toml @@ -105,6 +105,7 @@ arboard = { workspace = true } [dev-dependencies] +codex-core = { workspace = true, features = ["test-support"] } assert_matches = { workspace = true } chrono = { workspace = true, features = ["serde"] } insta = { workspace = true } diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index 28535e536..10d11d053 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -302,7 +302,10 @@ impl App { }; let enhanced_keys_supported = tui.enhanced_keys_supported(); - + let model_family = conversation_manager + .get_models_manager() + .construct_model_family(&config.model, &config) + .await; let mut chat_widget = match resume_selection { ResumeSelection::StartFresh | ResumeSelection::Exit => { let init = crate::chatwidget::ChatWidgetInit { @@ -317,6 +320,7 @@ impl App { feedback: feedback.clone(), skills: skills.clone(), is_first_run, + model_family, }; ChatWidget::new(init, conversation_manager.clone()) } @@ -343,6 +347,7 @@ impl App { feedback: feedback.clone(), skills: skills.clone(), is_first_run, + model_family, }; ChatWidget::new_from_existing( init, @@ -481,6 +486,11 @@ impl App { } async fn handle_event(&mut self, tui: &mut tui::Tui, event: AppEvent) -> Result { + let model_family = self + .server + .get_models_manager() + .construct_model_family(&self.config.model, &self.config) + .await; match event { AppEvent::NewSession => { let summary = session_summary( @@ -500,6 +510,7 @@ impl App { feedback: self.feedback.clone(), skills: self.skills.clone(), is_first_run: false, + model_family, }; self.chat_widget = ChatWidget::new(init, self.server.clone()); if let Some(summary) = summary { @@ -549,6 +560,7 @@ impl App { feedback: self.feedback.clone(), skills: self.skills.clone(), is_first_run: false, + model_family: model_family.clone(), }; self.chat_widget = ChatWidget::new_from_existing( init, @@ -677,7 +689,12 @@ impl App { self.on_update_reasoning_effort(effort); } AppEvent::UpdateModel(model) => { - self.chat_widget.set_model(&model); + let model_family = self + .server + .get_models_manager() + .construct_model_family(&model, &self.config) + .await; + self.chat_widget.set_model(&model, model_family); self.config.model = model; } AppEvent::OpenReasoningPopup { model } => { diff --git a/codex-rs/tui/src/app_backtrack.rs b/codex-rs/tui/src/app_backtrack.rs index 2f59872bc..ca9de52e2 100644 --- a/codex-rs/tui/src/app_backtrack.rs +++ b/codex-rs/tui/src/app_backtrack.rs @@ -340,6 +340,7 @@ impl App { let session_configured = new_conv.session_configured; let init = crate::chatwidget::ChatWidgetInit { config: cfg, + model_family: self.chat_widget.get_model_family(), frame_requester: tui.frame_requester(), app_event_tx: self.app_event_tx.clone(), initial_prompt: None, diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index e3b57ce9d..41fe181b3 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -11,6 +11,7 @@ use codex_core::config::Config; use codex_core::config::types::Notifications; use codex_core::git_info::current_branch_name; use codex_core::git_info::local_git_branches; +use codex_core::openai_models::model_family::ModelFamily; use codex_core::openai_models::models_manager::ModelsManager; use codex_core::project_doc::DEFAULT_PROJECT_DOC_FILENAME; use codex_core::protocol::AgentMessageDeltaEvent; @@ -261,6 +262,7 @@ pub(crate) struct ChatWidgetInit { pub(crate) feedback: codex_feedback::CodexFeedback, pub(crate) skills: Option>, pub(crate) is_first_run: bool, + pub(crate) model_family: ModelFamily, } #[derive(Default)] @@ -277,6 +279,7 @@ pub(crate) struct ChatWidget { bottom_pane: BottomPane, active_cell: Option>, config: Config, + model_family: ModelFamily, auth_manager: Arc, models_manager: Arc, session_header: SessionHeader, @@ -465,15 +468,13 @@ impl ChatWidget { } fn on_agent_reasoning_final(&mut self) { + let reasoning_summary_format = self.get_model_family().reasoning_summary_format; // At the end of a reasoning block, record transcript-only content. self.full_reasoning_buffer.push_str(&self.reasoning_buffer); - let model_family = self - .models_manager - .construct_model_family(&self.config.model, &self.config); if !self.full_reasoning_buffer.is_empty() { let cell = history_cell::new_reasoning_summary_block( self.full_reasoning_buffer.clone(), - &model_family, + reasoning_summary_format, ); self.add_boxed_history(cell); } @@ -647,6 +648,9 @@ impl ChatWidget { self.stream_controller = None; self.maybe_show_pending_rate_limit_prompt(); } + pub(crate) fn get_model_family(&self) -> ModelFamily { + self.model_family.clone() + } fn on_error(&mut self, message: String) { self.finalize_turn(); @@ -1249,6 +1253,7 @@ impl ChatWidget { feedback, skills, is_first_run, + model_family, } = common; let mut rng = rand::rng(); let placeholder = EXAMPLE_PROMPTS[rng.random_range(0..EXAMPLE_PROMPTS.len())].to_string(); @@ -1270,6 +1275,7 @@ impl ChatWidget { }), active_cell: None, config: config.clone(), + model_family, auth_manager, models_manager, session_header: SessionHeader::new(config.model), @@ -1329,6 +1335,7 @@ impl ChatWidget { models_manager, feedback, skills, + model_family, .. } = common; let mut rng = rand::rng(); @@ -1353,6 +1360,7 @@ impl ChatWidget { }), active_cell: None, config: config.clone(), + model_family, auth_manager, models_manager, session_header: SessionHeader::new(config.model), @@ -1785,7 +1793,7 @@ impl ChatWidget { EventMsg::AgentReasoning(AgentReasoningEvent { .. }) => self.on_agent_reasoning_final(), EventMsg::AgentReasoningRawContent(AgentReasoningRawContentEvent { text }) => { self.on_agent_reasoning_delta(text); - self.on_agent_reasoning_final() + self.on_agent_reasoning_final(); } EventMsg::AgentReasoningSectionBreak(_) => self.on_reasoning_section_break(), EventMsg::TaskStarted(_) => self.on_task_started(), @@ -2843,9 +2851,10 @@ impl ChatWidget { } /// Set the model in the widget's config copy. - pub(crate) fn set_model(&mut self, model: &str) { + pub(crate) fn set_model(&mut self, model: &str, model_family: ModelFamily) { self.session_header.set_model(model); self.config.model = model.to_string(); + self.model_family = model_family; } pub(crate) fn add_info_message(&mut self, message: String, hint: Option) { diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index 6f2e656a5..229e075e7 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -10,6 +10,7 @@ use codex_core::CodexAuth; use codex_core::config::Config; use codex_core::config::ConfigOverrides; use codex_core::config::ConfigToml; +use codex_core::openai_models::models_manager::ModelsManager; use codex_core::protocol::AgentMessageDeltaEvent; use codex_core::protocol::AgentMessageEvent; use codex_core::protocol::AgentReasoningDeltaEvent; @@ -345,6 +346,7 @@ async fn helpers_are_available_and_do_not_panic() { let (tx_raw, _rx) = unbounded_channel::(); let tx = AppEventSender::new(tx_raw); let cfg = test_config(); + let model_family = ModelsManager::construct_model_family_offline(&cfg.model, &cfg); let conversation_manager = Arc::new(ConversationManager::with_auth(CodexAuth::from_api_key( "test", ))); @@ -361,6 +363,7 @@ async fn helpers_are_available_and_do_not_panic() { feedback: codex_feedback::CodexFeedback::new(), skills: None, is_first_run: true, + model_family, }; let mut w = ChatWidget::new(init, conversation_manager); // Basic construction sanity. @@ -394,6 +397,7 @@ fn make_chatwidget_manual() -> ( bottom_pane: bottom, active_cell: None, config: cfg.clone(), + model_family: ModelsManager::construct_model_family_offline(&cfg.model, &cfg), auth_manager: auth_manager.clone(), models_manager: Arc::new(ModelsManager::new(auth_manager)), session_header: SessionHeader::new(cfg.model), diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index e70a31ffc..945ed1f49 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -27,7 +27,6 @@ use codex_common::format_env_display::format_env_display; use codex_core::config::Config; use codex_core::config::types::McpServerTransportConfig; use codex_core::config::types::ReasoningSummaryFormat; -use codex_core::openai_models::model_family::ModelFamily; use codex_core::protocol::FileChange; use codex_core::protocol::McpAuthStatus; use codex_core::protocol::McpInvocation; @@ -1421,9 +1420,9 @@ pub(crate) fn new_view_image_tool_call(path: PathBuf, cwd: &Path) -> PlainHistor pub(crate) fn new_reasoning_summary_block( full_reasoning_buffer: String, - model_family: &ModelFamily, + reasoning_summary_format: ReasoningSummaryFormat, ) -> Box { - if model_family.reasoning_summary_format == ReasoningSummaryFormat::Experimental { + if reasoning_summary_format == ReasoningSummaryFormat::Experimental { // Experimental format is following: // ** header ** // @@ -1513,8 +1512,6 @@ mod tests { use crate::exec_cell::CommandOutput; use crate::exec_cell::ExecCall; use crate::exec_cell::ExecCell; - use codex_core::AuthManager; - use codex_core::CodexAuth; use codex_core::config::Config; use codex_core::config::ConfigOverrides; use codex_core::config::ConfigToml; @@ -1527,7 +1524,6 @@ mod tests { use pretty_assertions::assert_eq; use serde_json::json; use std::collections::HashMap; - use std::sync::Arc; use codex_core::protocol::ExecCommandSource; use mcp_types::CallToolResult; @@ -2326,13 +2322,12 @@ mod tests { #[test] fn reasoning_summary_block() { let config = test_config(); - let auth_manager = - AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key")); - let models_manager = Arc::new(ModelsManager::new(auth_manager)); - let model_family = models_manager.construct_model_family(&config.model, &config); + let reasoning_format = + ModelsManager::construct_model_family_offline(&config.model, &config) + .reasoning_summary_format; let cell = new_reasoning_summary_block( "**High level reasoning**\n\nDetailed reasoning goes here.".to_string(), - &model_family, + reasoning_format, ); let rendered_display = render_lines(&cell.display_lines(80)); @@ -2345,12 +2340,13 @@ mod tests { #[test] fn reasoning_summary_block_returns_reasoning_cell_when_feature_disabled() { let config = test_config(); - let auth_manager = - AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key")); - let models_manager = Arc::new(ModelsManager::new(auth_manager)); - let model_family = models_manager.construct_model_family(&config.model, &config); - let cell = - new_reasoning_summary_block("Detailed reasoning goes here.".to_string(), &model_family); + let reasoning_format = + ModelsManager::construct_model_family_offline(&config.model, &config) + .reasoning_summary_format; + let cell = new_reasoning_summary_block( + "Detailed reasoning goes here.".to_string(), + reasoning_format, + ); let rendered = render_transcript(cell.as_ref()); assert_eq!(rendered, vec!["• Detailed reasoning goes here."]); @@ -2362,11 +2358,7 @@ mod tests { config.model = "gpt-3.5-turbo".to_string(); config.model_supports_reasoning_summaries = Some(true); config.model_reasoning_summary_format = Some(ReasoningSummaryFormat::Experimental); - let auth_manager = - AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key")); - let models_manager = Arc::new(ModelsManager::new(auth_manager)); - - let model_family = models_manager.construct_model_family(&config.model, &config); + let model_family = ModelsManager::construct_model_family_offline(&config.model, &config); assert_eq!( model_family.reasoning_summary_format, ReasoningSummaryFormat::Experimental @@ -2374,7 +2366,7 @@ mod tests { let cell = new_reasoning_summary_block( "**High level reasoning**\n\nDetailed reasoning goes here.".to_string(), - &model_family, + model_family.reasoning_summary_format, ); let rendered_display = render_lines(&cell.display_lines(80)); @@ -2384,13 +2376,12 @@ mod tests { #[test] fn reasoning_summary_block_falls_back_when_header_is_missing() { let config = test_config(); - let auth_manager = - AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key")); - let models_manager = Arc::new(ModelsManager::new(auth_manager)); - let model_family = models_manager.construct_model_family(&config.model, &config); + let reasoning_format = + ModelsManager::construct_model_family_offline(&config.model, &config) + .reasoning_summary_format; let cell = new_reasoning_summary_block( "**High level reasoning without closing".to_string(), - &model_family, + reasoning_format, ); let rendered = render_transcript(cell.as_ref()); @@ -2400,13 +2391,12 @@ mod tests { #[test] fn reasoning_summary_block_falls_back_when_summary_is_missing() { let config = test_config(); - let auth_manager = - AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key")); - let models_manager = Arc::new(ModelsManager::new(auth_manager)); - let model_family = models_manager.construct_model_family(&config.model, &config); + let reasoning_format = + ModelsManager::construct_model_family_offline(&config.model, &config) + .reasoning_summary_format; let cell = new_reasoning_summary_block( "**High level reasoning without closing**".to_string(), - &model_family, + reasoning_format.clone(), ); let rendered = render_transcript(cell.as_ref()); @@ -2414,7 +2404,7 @@ mod tests { let cell = new_reasoning_summary_block( "**High level reasoning without closing**\n\n ".to_string(), - &model_family, + reasoning_format, ); let rendered = render_transcript(cell.as_ref()); @@ -2424,13 +2414,12 @@ mod tests { #[test] fn reasoning_summary_block_splits_header_and_summary_when_present() { let config = test_config(); - let auth_manager = - AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key")); - let models_manager = Arc::new(ModelsManager::new(auth_manager)); - let model_family = models_manager.construct_model_family(&config.model, &config); + let reasoning_format = + ModelsManager::construct_model_family_offline(&config.model, &config) + .reasoning_summary_format; let cell = new_reasoning_summary_block( "**High level plan**\n\nWe should fix the bug next.".to_string(), - &model_family, + reasoning_format, ); let rendered_display = render_lines(&cell.display_lines(80));