use codex_protocol::config_types::ReasoningSummary; use codex_protocol::openai_models::ConfigShellToolType; use codex_protocol::openai_models::ModelInfo; use codex_protocol::openai_models::ModelInstructionsVariables; use codex_protocol::openai_models::ModelMessages; use codex_protocol::openai_models::ModelVisibility; use codex_protocol::openai_models::TruncationMode; use codex_protocol::openai_models::TruncationPolicyConfig; use codex_protocol::openai_models::WebSearchToolType; use codex_protocol::openai_models::default_input_modalities; use crate::config::ModelsManagerConfig; use codex_utils_output_truncation::approx_bytes_for_tokens; use tracing::warn; pub const BASE_INSTRUCTIONS: &str = include_str!("../prompt.md"); const DEFAULT_PERSONALITY_HEADER: &str = "You are Codex, a coding agent based on GPT-5. You and the user share the same workspace and collaborate to achieve the user's goals."; const LOCAL_FRIENDLY_TEMPLATE: &str = "You optimize for team morale and being a supportive teammate as much as code quality."; const LOCAL_PRAGMATIC_TEMPLATE: &str = "You are a deeply pragmatic, effective software engineer."; const PERSONALITY_PLACEHOLDER: &str = "{{ personality }}"; pub fn with_config_overrides(mut model: ModelInfo, config: &ModelsManagerConfig) -> ModelInfo { if let Some(supports_reasoning_summaries) = config.model_supports_reasoning_summaries && supports_reasoning_summaries { model.supports_reasoning_summaries = true; } if let Some(context_window) = config.model_context_window { model.context_window = Some( model .max_context_window .map_or(context_window, |max_context_window| { context_window.min(max_context_window) }), ); } if let Some(auto_compact_token_limit) = config.model_auto_compact_token_limit { model.auto_compact_token_limit = Some(auto_compact_token_limit); } if let Some(token_limit) = config.tool_output_token_limit { model.truncation_policy = match model.truncation_policy.mode { TruncationMode::Bytes => { let byte_limit = i64::try_from(approx_bytes_for_tokens(token_limit)).unwrap_or(i64::MAX); TruncationPolicyConfig::bytes(byte_limit) } TruncationMode::Tokens => { let limit = i64::try_from(token_limit).unwrap_or(i64::MAX); TruncationPolicyConfig::tokens(limit) } }; } if let Some(base_instructions) = &config.base_instructions { model.base_instructions = base_instructions.clone(); clear_instruction_messages(&mut model); } else if !config.personality_enabled { clear_instruction_messages(&mut model); } model } fn clear_instruction_messages(model: &mut ModelInfo) { if let Some(model_messages) = &mut model.model_messages { model_messages.instructions_template = None; model_messages.instructions_variables = None; if model_messages.approvals.is_none() && model_messages.auto_review.is_none() { model.model_messages = None; } } } /// Build a minimal fallback model descriptor for missing/unknown slugs. pub fn model_info_from_slug(slug: &str) -> ModelInfo { warn!("Unknown model {slug} is used. This will use fallback model metadata."); ModelInfo { slug: slug.to_string(), display_name: slug.to_string(), description: None, default_reasoning_level: None, supported_reasoning_levels: Vec::new(), shell_type: ConfigShellToolType::Default, visibility: ModelVisibility::None, supported_in_api: true, priority: 99, additional_speed_tiers: Vec::new(), service_tiers: Vec::new(), default_service_tier: None, availability_nux: None, upgrade: None, base_instructions: BASE_INSTRUCTIONS.to_string(), model_messages: local_personality_messages_for_slug(slug), include_skills_usage_instructions: false, supports_reasoning_summaries: false, default_reasoning_summary: ReasoningSummary::Auto, support_verbosity: false, default_verbosity: None, apply_patch_tool_type: None, web_search_tool_type: WebSearchToolType::Text, truncation_policy: TruncationPolicyConfig::bytes(/*limit*/ 10_000), supports_parallel_tool_calls: false, supports_image_detail_original: false, context_window: Some(272_000), max_context_window: Some(272_000), auto_compact_token_limit: None, comp_hash: None, effective_context_window_percent: 95, experimental_supported_tools: Vec::new(), input_modalities: default_input_modalities(), used_fallback_model_metadata: true, // this is the fallback model metadata supports_search_tool: false, use_responses_lite: false, auto_review_model_override: None, tool_mode: None, multi_agent_version: None, } } fn local_personality_messages_for_slug(slug: &str) -> Option { match slug { "gpt-5.2-codex" | "exp-codex-personality" => Some(ModelMessages { instructions_template: Some(format!( "{DEFAULT_PERSONALITY_HEADER}\n\n{PERSONALITY_PLACEHOLDER}\n\n{BASE_INSTRUCTIONS}" )), instructions_variables: Some(ModelInstructionsVariables { personality_default: Some(String::new()), personality_friendly: Some(LOCAL_FRIENDLY_TEMPLATE.to_string()), personality_pragmatic: Some(LOCAL_PRAGMATIC_TEMPLATE.to_string()), }), approvals: None, auto_review: None, }), _ => None, } } #[cfg(test)] #[path = "model_info_tests.rs"] mod tests;