use chrono::DateTime; use chrono::Utc; use codex_core::test_support::all_model_presets; use codex_protocol::config_types::ReasoningSummary; use codex_protocol::openai_models::ConfigShellToolType; use codex_protocol::openai_models::ModelInfo; use codex_protocol::openai_models::ModelPreset; use codex_protocol::openai_models::ModelVisibility; use codex_protocol::openai_models::TruncationPolicyConfig; use codex_protocol::openai_models::default_input_modalities; use serde_json::json; use std::path::Path; /// Convert a ModelPreset to ModelInfo for cache storage. fn preset_to_info(preset: &ModelPreset, priority: i32) -> ModelInfo { ModelInfo { slug: preset.id.clone(), display_name: preset.display_name.clone(), description: Some(preset.description.clone()), default_reasoning_level: Some(preset.default_reasoning_effort), supported_reasoning_levels: preset.supported_reasoning_efforts.clone(), shell_type: ConfigShellToolType::ShellCommand, visibility: if preset.show_in_picker { ModelVisibility::List } else { ModelVisibility::Hide }, supported_in_api: preset.supported_in_api, priority, upgrade: preset.upgrade.as_ref().map(|u| u.into()), base_instructions: "base instructions".to_string(), model_messages: None, supports_reasoning_summaries: false, default_reasoning_summary: ReasoningSummary::Auto, support_verbosity: false, default_verbosity: None, availability_nux: None, apply_patch_tool_type: None, web_search_tool_type: Default::default(), truncation_policy: TruncationPolicyConfig::bytes(/*limit*/ 10_000), supports_parallel_tool_calls: false, supports_image_detail_original: false, context_window: Some(272_000), auto_compact_token_limit: None, effective_context_window_percent: 95, experimental_supported_tools: Vec::new(), input_modalities: default_input_modalities(), used_fallback_model_metadata: false, supports_search_tool: false, } } /// Write a models_cache.json file to the codex home directory. /// This prevents ModelsManager from making network requests to refresh models. /// The cache will be treated as fresh (within TTL) and used instead of fetching from the network. /// Uses bundled-catalog-derived presets, converted to ModelInfo format. pub fn write_models_cache(codex_home: &Path) -> std::io::Result<()> { // Get a stable bundled-catalog-derived preset list and filter for picker-visible entries. let presets: Vec<&ModelPreset> = all_model_presets() .iter() .filter(|preset| preset.show_in_picker) .collect(); // Convert presets to ModelInfo, assigning priorities (lower = earlier in list). // Priority is used for sorting, so the first model gets the lowest priority. let models: Vec = presets .iter() .enumerate() .map(|(idx, preset)| { // Lower priority = earlier in list. let priority = idx as i32; preset_to_info(preset, priority) }) .collect(); write_models_cache_with_models(codex_home, models) } /// Write a models_cache.json file with specific models. /// Useful when tests need specific models to be available. pub fn write_models_cache_with_models( codex_home: &Path, models: Vec, ) -> std::io::Result<()> { let cache_path = codex_home.join("models_cache.json"); // DateTime serializes to RFC3339 format by default with serde let fetched_at: DateTime = Utc::now(); let client_version = codex_core::models_manager::client_version_to_whole(); let cache = json!({ "fetched_at": fetched_at, "etag": null, "client_version": client_version, "models": models }); std::fs::write(cache_path, serde_json::to_string_pretty(&cache)?) }