diff --git a/codex-rs/app-server/tests/common/models_cache.rs b/codex-rs/app-server/tests/common/models_cache.rs index a38da2e07f..90f81bf5b2 100644 --- a/codex-rs/app-server/tests/common/models_cache.rs +++ b/codex-rs/app-server/tests/common/models_cache.rs @@ -34,6 +34,7 @@ fn preset_to_info(preset: &ModelPreset, priority: i32) -> ModelInfo { default_reasoning_summary: ReasoningSummary::Auto, support_verbosity: false, default_verbosity: None, + show_nux_new: false, apply_patch_tool_type: None, truncation_policy: TruncationPolicyConfig::bytes(10_000), supports_parallel_tool_calls: false, diff --git a/codex-rs/codex-api/tests/models_integration.rs b/codex-rs/codex-api/tests/models_integration.rs index 08a0860594..e940e8afbd 100644 --- a/codex-rs/codex-api/tests/models_integration.rs +++ b/codex-rs/codex-api/tests/models_integration.rs @@ -82,6 +82,7 @@ async fn models_client_hits_models_endpoint() { default_reasoning_summary: ReasoningSummary::Auto, support_verbosity: false, default_verbosity: None, + show_nux_new: false, apply_patch_tool_type: None, truncation_policy: TruncationPolicyConfig::bytes(10_000), supports_parallel_tool_calls: false, diff --git a/codex-rs/core/src/models_manager/model_info.rs b/codex-rs/core/src/models_manager/model_info.rs index 79b8520ecd..455bd5fcf2 100644 --- a/codex-rs/core/src/models_manager/model_info.rs +++ b/codex-rs/core/src/models_manager/model_info.rs @@ -69,6 +69,7 @@ pub(crate) fn model_info_from_slug(slug: &str) -> ModelInfo { visibility: ModelVisibility::None, supported_in_api: true, priority: 99, + show_nux_new: false, upgrade: None, base_instructions: BASE_INSTRUCTIONS.to_string(), model_messages: local_personality_messages_for_slug(slug), diff --git a/codex-rs/core/tests/suite/model_switching.rs b/codex-rs/core/tests/suite/model_switching.rs index 159c1a5cab..bffaa248de 100644 --- a/codex-rs/core/tests/suite/model_switching.rs +++ b/codex-rs/core/tests/suite/model_switching.rs @@ -237,6 +237,7 @@ async fn model_change_from_image_to_text_strips_prior_image_content() -> Result< default_reasoning_summary: ReasoningSummary::Auto, support_verbosity: false, default_verbosity: None, + show_nux_new: false, apply_patch_tool_type: None, truncation_policy: TruncationPolicyConfig::bytes(10_000), supports_parallel_tool_calls: false, @@ -395,6 +396,7 @@ async fn model_switch_to_smaller_model_updates_token_context_window() -> Result< default_reasoning_summary: ReasoningSummary::Auto, support_verbosity: false, default_verbosity: None, + show_nux_new: false, apply_patch_tool_type: None, truncation_policy: TruncationPolicyConfig::bytes(10_000), supports_parallel_tool_calls: false, diff --git a/codex-rs/core/tests/suite/models_cache_ttl.rs b/codex-rs/core/tests/suite/models_cache_ttl.rs index 2ecf157512..563c0bffcd 100644 --- a/codex-rs/core/tests/suite/models_cache_ttl.rs +++ b/codex-rs/core/tests/suite/models_cache_ttl.rs @@ -339,6 +339,7 @@ fn test_remote_model(slug: &str, priority: i32) -> ModelInfo { default_reasoning_summary: ReasoningSummary::Auto, support_verbosity: false, default_verbosity: None, + show_nux_new: false, apply_patch_tool_type: None, truncation_policy: TruncationPolicyConfig::bytes(10_000), supports_parallel_tool_calls: false, diff --git a/codex-rs/core/tests/suite/personality.rs b/codex-rs/core/tests/suite/personality.rs index 09d0137995..0000cd725f 100644 --- a/codex-rs/core/tests/suite/personality.rs +++ b/codex-rs/core/tests/suite/personality.rs @@ -602,6 +602,7 @@ async fn remote_model_friendly_personality_instructions_with_feature() -> anyhow default_reasoning_summary: ReasoningSummary::Auto, support_verbosity: false, default_verbosity: None, + show_nux_new: false, apply_patch_tool_type: None, truncation_policy: TruncationPolicyConfig::bytes(10_000), supports_parallel_tool_calls: false, @@ -710,6 +711,7 @@ async fn user_turn_personality_remote_model_template_includes_update_message() - default_reasoning_summary: ReasoningSummary::Auto, support_verbosity: false, default_verbosity: None, + show_nux_new: false, apply_patch_tool_type: None, truncation_policy: TruncationPolicyConfig::bytes(10_000), supports_parallel_tool_calls: false, diff --git a/codex-rs/core/tests/suite/remote_models.rs b/codex-rs/core/tests/suite/remote_models.rs index c89d5341da..a36822db9d 100644 --- a/codex-rs/core/tests/suite/remote_models.rs +++ b/codex-rs/core/tests/suite/remote_models.rs @@ -297,6 +297,7 @@ async fn remote_models_remote_model_uses_unified_exec() -> Result<()> { default_reasoning_summary: ReasoningSummary::Auto, support_verbosity: false, default_verbosity: None, + show_nux_new: false, apply_patch_tool_type: None, truncation_policy: TruncationPolicyConfig::bytes(10_000), supports_parallel_tool_calls: false, @@ -534,6 +535,7 @@ async fn remote_models_apply_remote_base_instructions() -> Result<()> { default_reasoning_summary: ReasoningSummary::Auto, support_verbosity: false, default_verbosity: None, + show_nux_new: false, apply_patch_tool_type: None, truncation_policy: TruncationPolicyConfig::bytes(10_000), supports_parallel_tool_calls: false, @@ -995,6 +997,7 @@ fn test_remote_model_with_policy( default_reasoning_summary: ReasoningSummary::Auto, support_verbosity: false, default_verbosity: None, + show_nux_new: false, apply_patch_tool_type: None, truncation_policy, supports_parallel_tool_calls: false, diff --git a/codex-rs/core/tests/suite/rmcp_client.rs b/codex-rs/core/tests/suite/rmcp_client.rs index cf56a1bb2d..cea528348f 100644 --- a/codex-rs/core/tests/suite/rmcp_client.rs +++ b/codex-rs/core/tests/suite/rmcp_client.rs @@ -401,6 +401,7 @@ async fn stdio_image_responses_are_sanitized_for_text_only_model() -> anyhow::Re default_reasoning_summary: ReasoningSummary::Auto, support_verbosity: false, default_verbosity: None, + show_nux_new: false, apply_patch_tool_type: None, truncation_policy: TruncationPolicyConfig::bytes(10_000), supports_parallel_tool_calls: false, diff --git a/codex-rs/core/tests/suite/view_image.rs b/codex-rs/core/tests/suite/view_image.rs index c4f8ebec36..4d341dd6b7 100644 --- a/codex-rs/core/tests/suite/view_image.rs +++ b/codex-rs/core/tests/suite/view_image.rs @@ -678,6 +678,7 @@ async fn view_image_tool_returns_unsupported_message_for_text_only_model() -> an default_reasoning_summary: ReasoningSummary::Auto, support_verbosity: false, default_verbosity: None, + show_nux_new: false, apply_patch_tool_type: None, truncation_policy: TruncationPolicyConfig::bytes(10_000), supports_parallel_tool_calls: false, diff --git a/codex-rs/protocol/src/openai_models.rs b/codex-rs/protocol/src/openai_models.rs index 9b0f25e27c..fbfc9f0000 100644 --- a/codex-rs/protocol/src/openai_models.rs +++ b/codex-rs/protocol/src/openai_models.rs @@ -123,6 +123,9 @@ pub struct ModelPreset { pub upgrade: Option, /// Whether this preset should appear in the picker UI. pub show_in_picker: bool, + /// Whether the TUI should surface the new-model startup tip for this preset. + #[serde(default)] + pub show_nux_new: bool, /// whether this model is supported in the api pub supported_in_api: bool, /// Input modalities accepted when composing user turns for this preset. @@ -225,6 +228,8 @@ pub struct ModelInfo { pub visibility: ModelVisibility, pub supported_in_api: bool, pub priority: i32, + #[serde(default)] + pub show_nux_new: bool, pub upgrade: Option, pub base_instructions: String, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -410,6 +415,7 @@ impl From for ModelPreset { migration_markdown: Some(upgrade.migration_markdown.clone()), }), show_in_picker: info.visibility == ModelVisibility::List, + show_nux_new: info.show_nux_new, supported_in_api: info.supported_in_api, input_modalities: info.input_modalities, } @@ -495,6 +501,7 @@ mod tests { visibility: ModelVisibility::List, supported_in_api: true, priority: 1, + show_nux_new: false, upgrade: None, base_instructions: "base".to_string(), model_messages: spec, @@ -668,4 +675,50 @@ mod tests { ); assert_eq!(personality_variables.get_personality_message(None), None); } + + #[test] + fn model_info_defaults_show_nux_new_to_false_when_omitted() { + let model: ModelInfo = serde_json::from_value(serde_json::json!({ + "slug": "test-model", + "display_name": "Test Model", + "description": null, + "supported_reasoning_levels": [], + "shell_type": "shell_command", + "visibility": "list", + "supported_in_api": true, + "priority": 1, + "upgrade": null, + "base_instructions": "base", + "model_messages": null, + "supports_reasoning_summaries": false, + "default_reasoning_summary": "auto", + "support_verbosity": false, + "default_verbosity": null, + "apply_patch_tool_type": null, + "truncation_policy": { + "mode": "bytes", + "limit": 10000 + }, + "supports_parallel_tool_calls": false, + "context_window": null, + "auto_compact_token_limit": null, + "effective_context_window_percent": 95, + "experimental_supported_tools": [], + "input_modalities": ["text", "image"], + "prefer_websockets": false + })) + .expect("deserialize model info"); + + assert!(!model.show_nux_new); + } + + #[test] + fn model_preset_preserves_show_nux_new() { + let preset = ModelPreset::from(ModelInfo { + show_nux_new: true, + ..test_model(None) + }); + + assert!(preset.show_nux_new); + } }