diff --git a/code-rs/app-server/src/code_message_processor.rs b/code-rs/app-server/src/code_message_processor.rs index 901f82c21993..f7f490e47363 100644 --- a/code-rs/app-server/src/code_message_processor.rs +++ b/code-rs/app-server/src/code_message_processor.rs @@ -2239,6 +2239,8 @@ fn map_reasoning_effort_to_wire( code_core::config_types::ReasoningEffort::Minimal => { code_protocol::config_types::ReasoningEffort::Minimal } + code_core::config_types::ReasoningEffort::Disabled => code_protocol::config_types::ReasoningEffort::None, + code_core::config_types::ReasoningEffort::Max => code_protocol::config_types::ReasoningEffort::Max, code_core::config_types::ReasoningEffort::Low => code_protocol::config_types::ReasoningEffort::Low, code_core::config_types::ReasoningEffort::Medium => { code_protocol::config_types::ReasoningEffort::Medium diff --git a/code-rs/code-auto-drive-core/src/auto_coordinator.rs b/code-rs/code-auto-drive-core/src/auto_coordinator.rs index 68ff6054435e..6fa5513f6fce 100644 --- a/code-rs/code-auto-drive-core/src/auto_coordinator.rs +++ b/code-rs/code-auto-drive-core/src/auto_coordinator.rs @@ -113,6 +113,8 @@ fn cli_routing_reasoning_priority(level: ReasoningEffort) -> u8 { ReasoningEffort::Medium => 2, ReasoningEffort::High => 3, ReasoningEffort::XHigh => 4, + ReasoningEffort::Max => 5, + ReasoningEffort::Disabled => 0, ReasoningEffort::None => 5, } } @@ -120,11 +122,13 @@ fn cli_routing_reasoning_priority(level: ReasoningEffort) -> u8 { fn normalize_cli_routing_reasoning_levels(levels: &[ReasoningEffort]) -> Vec { let mut normalized = Vec::new(); for level in [ + ReasoningEffort::Disabled, ReasoningEffort::Minimal, ReasoningEffort::Low, ReasoningEffort::Medium, ReasoningEffort::High, ReasoningEffort::XHigh, + ReasoningEffort::Max, ] { if levels.contains(&level) { normalized.push(level); @@ -140,6 +144,8 @@ fn cli_reasoning_effort_to_str(level: ReasoningEffort) -> &'static str { ReasoningEffort::Medium => "medium", ReasoningEffort::High => "high", ReasoningEffort::XHigh => "xhigh", + ReasoningEffort::Max => "max", + ReasoningEffort::Disabled => "none", ReasoningEffort::None => "minimal", } } @@ -192,10 +198,6 @@ fn normalize_routing_entry_model(model: &str) -> Option { } let normalized = trimmed.to_ascii_lowercase(); - if !normalized.starts_with("gpt-") { - return None; - } - Some(normalized) } @@ -214,10 +216,6 @@ fn normalize_auto_drive_cli_routing_entries( }; let reasoning_levels = normalize_cli_routing_reasoning_levels(&entry.reasoning_levels); - if reasoning_levels.is_empty() { - continue; - } - let description = entry.description.trim().to_string(); if let Some(existing) = normalized .iter_mut() @@ -253,25 +251,14 @@ fn resolve_auto_drive_cli_routing_entries( supports_pro_only_models: bool, available_models: &[String], ) -> Vec { - let mut entries = normalize_auto_drive_cli_routing_entries(&settings.model_routing_entries); - entries.retain(|entry| { - available_models - .iter() - .any(|model| model.eq_ignore_ascii_case(&entry.model)) - }); - - if entries.is_empty() { - return auto_drive_cli_routing_entries_for_auth(auth_mode, supports_pro_only_models) - .into_iter() - .filter(|entry| { - available_models - .iter() - .any(|model| model.eq_ignore_ascii_case(&entry.model)) - }) - .collect(); - } + let entries = normalize_auto_drive_cli_routing_entries(&settings.model_routing_entries); + // Preserve explicit selections, including unavailable ones, so the caller + // can report the requested model instead of silently substituting defaults. + if settings.model_routing_enabled || !entries.is_empty() { return entries; } + auto_drive_cli_routing_entries_for_auth(auth_mode, supports_pro_only_models) + .into_iter().filter(|entry| available_models.iter().any(|model| model.eq_ignore_ascii_case(&entry.model))) + .collect() - entries } fn spark_fallback_model(model: &str) -> Option<&'static str> { @@ -1257,7 +1244,32 @@ mod tests { } #[test] - fn resolve_cli_routing_entries_falls_back_when_enabled_entries_missing() { + fn priority_route_max_survives_schema_and_decision_parser() { + let routes = vec![AutoDriveCliRoutingEntry { + model: "gpt-6.1-sol".into(), reasoning_levels: vec![ReasoningEffort::Max], description: String::new(), + }]; + let schema = build_schema(&[], SchemaFeatures::default(), &routes); + assert!(schema["properties"]["cli_reasoning_effort"]["enum"].as_array().unwrap().contains(&json!("max"))); + let raw = r#"{"finish_status":"continue","status_title":"Work","status_sent_to_user":"Working","cli_milestone_instruction":"Investigate","cli_model":"gpt-6.1-sol","cli_reasoning_effort":"max"}"#; + parse_decision(raw, DecisionParseOptions { require_cli_model_routing: true, allowed_cli_routing_entries: routes }).unwrap(); + assert_eq!(parse_cli_reasoning_effort("none").unwrap(), ReasoningEffort::Disabled); + assert_eq!(parse_cli_reasoning_effort("max").unwrap(), ReasoningEffort::Max); + } + + #[test] + fn default_cli_routes_resolve_actual_agent_model_ids() { + let available = enabled_agent_model_specs_for_auth(Some(AuthMode::Chatgpt), true) + .into_iter().filter_map(|spec| spec.model_args.windows(2) + .find(|pair| pair[0] == "--model").map(|pair| pair[1].to_string())) + .collect::>(); + let routes = resolve_auto_drive_cli_routing_entries(&AutoDriveSettings::default(), + Some(AuthMode::Chatgpt), true, &available); + assert!(routes.iter().any(|entry| entry.model == "gpt-6.1-sol")); + assert!(routes.iter().all(|entry| available.contains(&entry.model))); + } + + #[test] + fn resolve_cli_routing_entries_do_not_replace_disabled_explicit_routes() { let mut settings = AutoDriveSettings::default(); settings.model_routing_entries = vec![AutoDriveModelRoutingEntry { model: "gpt-5.5".to_string(), @@ -1278,12 +1290,11 @@ mod tests { &available_models, ); - assert!(entries.iter().any(|entry| entry.model == AUTO_DRIVE_CLI_MODEL_PRIMARY)); - assert!(entries.iter().any(|entry| entry.model == AUTO_DRIVE_CLI_MODEL_FAST)); + assert!(entries.is_empty(), "disabled explicit routes must not select defaults"); } #[test] - fn resolve_cli_routing_entries_drop_unavailable_models() { + fn resolve_cli_routing_entries_preserve_explicit_unavailable_models() { let settings = AutoDriveSettings { model_routing_entries: vec![ AutoDriveModelRoutingEntry { @@ -1310,12 +1321,12 @@ mod tests { &available_models, ); - assert_eq!(entries.len(), 1); - assert_eq!(entries[0].model, AUTO_DRIVE_CLI_MODEL_PRIMARY); + assert_eq!(entries.len(), 2); + assert_eq!(entries[1].model, "gpt-5.3-codex-experimental"); } #[test] - fn resolve_cli_routing_entries_empty_when_no_available_models() { + fn resolve_cli_routing_entries_preserve_selection_without_discovery() { let settings = AutoDriveSettings { model_routing_entries: vec![AutoDriveModelRoutingEntry { model: AUTO_DRIVE_CLI_MODEL_PRIMARY.to_string(), @@ -1329,7 +1340,7 @@ mod tests { let entries = resolve_auto_drive_cli_routing_entries(&settings, Some(AuthMode::Chatgpt), true, &[]); - assert!(entries.is_empty()); + assert_eq!(entries[0].model, AUTO_DRIVE_CLI_MODEL_PRIMARY); } #[test] @@ -1924,7 +1935,7 @@ fn run_auto_loop( supports_pro_only_models, ) .into_iter() - .map(|spec| spec.slug.to_ascii_lowercase()) + .filter_map(|spec| spec.model_args.windows(2).find(|pair| pair[0] == "--model").map(|pair| pair[1].to_ascii_lowercase())) .filter(|model| model.starts_with("gpt-")) .collect::>(); let allowed_cli_routing_entries = resolve_auto_drive_cli_routing_entries( @@ -1933,6 +1944,26 @@ fn run_auto_loop( supports_pro_only_models, &available_cli_routing_models, ); + if config.auto_drive.model_routing_enabled && allowed_cli_routing_entries.is_empty() { + anyhow::bail!("Auto Drive routing requires an explicitly enabled model entry"); + } + for entry in allowed_cli_routing_entries.iter().filter(|_| config.auto_drive.model_routing_enabled) { + if entry.reasoning_levels.is_empty() { + anyhow::bail!("configured Auto Drive model {} requires a supported reasoning effort", entry.model); + } + if let Some(supported) = code_core::modern_models::reasoning_efforts(&entry.model) { + for effort in &entry.reasoning_levels { + if !supported.contains(effort) || (*effort == ReasoningEffort::Disabled + && auth_mode_for_model_access == Some(code_app_server_protocol::AuthMode::Chatgpt)) { + anyhow::bail!("configured Auto Drive model '{}' does not support reasoning effort '{}'; update auto_drive.model_routing_entries", entry.model, cli_reasoning_effort_to_str(*effort)); + } + } + } + + if !available_cli_routing_models.iter().any(|model| model.eq_ignore_ascii_case(&entry.model)) { + anyhow::bail!("configured Auto Drive model '{}' is unavailable; enable its agent and verify provider access, or change auto_drive.model_routing_entries", entry.model); + } + } let model_provider = config.model_provider.clone(); let model_reasoning_summary = config.model_reasoning_summary; let model_text_verbosity = config.model_text_verbosity; @@ -2865,11 +2896,13 @@ fn build_schema( let mut reasoning_enum: Vec = Vec::new(); for level in [ + ReasoningEffort::Disabled, ReasoningEffort::Minimal, ReasoningEffort::Low, ReasoningEffort::Medium, ReasoningEffort::High, ReasoningEffort::XHigh, + ReasoningEffort::Max, ] { if cli_routing_entries .iter() @@ -3113,46 +3146,9 @@ fn request_decision( preferred_model_slug, ) { Ok(result) => Ok(result), - Err(err) => { - let preferred = preferred_model_slug; - let fallback_candidate = client.default_model_slug().to_string(); - let fallback_slug = if fallback_candidate.eq_ignore_ascii_case(preferred) { - MODEL_SLUG.to_string() - } else { - fallback_candidate - }; - if fallback_slug != preferred_model_slug && should_retry_with_default_model(&err) { - debug!( - preferred = %preferred, - fallback = %fallback_slug, - "auto coordinator falling back to configured model after invalid model error" - ); - let original_error = err.to_string(); - return request_decision_with_model( - runtime, - client, - developer_intro, - primary_goal, - coordinator_prompt.as_deref(), - time_budget_message, - time_budget_deadline, - loop_warning, - schema, - Arc::clone(&conversation), - auto_instructions, - event_tx, - cancel_token, - &fallback_slug, - ) - .map_err(|fallback_err| { - fallback_err.context(format!( - "coordinator fallback with model '{}' failed after original error: {}", - fallback_slug, original_error - )) - }); - } - Err(err) - } + Err(err) => Err(err.context(format!( + "coordinator model '{preferred_model_slug}' failed; verify model access and auto_drive.model configuration (no model substitution performed)" + ))), } } @@ -3507,24 +3503,6 @@ fn build_user_turn_prompt( prompt } -fn should_retry_with_default_model(err: &anyhow::Error) -> bool { - err.chain().any(|cause| { - if let Some(code_err) = cause.downcast_ref::() { - if let CodexErr::UnexpectedStatus(err) = code_err { - if !err.status.is_client_error() { - return false; - } - let body_lower = err.body.to_lowercase(); - return body_lower.contains("invalid model") - || body_lower.contains("unknown model") - || body_lower.contains("model_not_found") - || body_lower.contains("model does not exist"); - } - } - false - }) -} - pub(crate) fn classify_model_error(error: &anyhow::Error) -> RetryDecision { if let Some(code_err) = find_in_chain::(error) { match code_err { @@ -4377,13 +4355,14 @@ fn parse_cli_reasoning_effort(value: &str) -> Result { let normalized = value.trim().to_ascii_lowercase(); match normalized.as_str() { "minimal" => Ok(ReasoningEffort::Minimal), - "none" => Ok(ReasoningEffort::Minimal), + "none" => Ok(ReasoningEffort::Disabled), "low" => Ok(ReasoningEffort::Low), "medium" => Ok(ReasoningEffort::Medium), "high" => Ok(ReasoningEffort::High), "xhigh" => Ok(ReasoningEffort::XHigh), + "max" => Ok(ReasoningEffort::Max), _ => Err(anyhow!( - "unsupported cli_reasoning_effort '{normalized}'; expected one of: minimal, low, medium, high, xhigh" + "unsupported cli_reasoning_effort '{normalized}'; expected one of: none, minimal, low, medium, high, xhigh, max" )), } } diff --git a/code-rs/code-version/src/lib.rs b/code-rs/code-version/src/lib.rs index 1509946cb603..0a06af1198f5 100644 --- a/code-rs/code-version/src/lib.rs +++ b/code-rs/code-version/src/lib.rs @@ -155,10 +155,30 @@ pub fn wire_compatible_version_for_model(model: &str) -> String { max_semver(wire_compatible_version(), required_version).to_string() } +/// Verified current Codex backend protocol; public/proxy headers stay unchanged. +pub fn codex_backend_version_for_model(model: &str) -> String { + let canonical_model = model.rsplit('/').next().unwrap_or(model).trim(); + // The bundled catalog's 0.153 minimum is stale for these releases. + // Verified against the released Codex 0.162.1 contract and a live + // GPT-6.1 Sol OAuth request on 2026-10-09; 0.153 receives model-not-supported. + if matches!(canonical_model, "gpt-6.1-sol" | "gpt-6-astra" | "gpt-6-sol" | "gpt-6-luna" + | "gpt-5.6-sol" | "gpt-5.6-terra" | "gpt-5.6-luna") + { + return max_semver(&wire_compatible_version_for_model(model), "0.162.1").to_string(); + } + wire_compatible_version_for_model(model) +} + #[cfg(test)] mod tests { use super::*; + #[test] + fn priority_model_uses_verified_modern_protocol_floor() { + assert_eq!(codex_backend_version_for_model("gpt-6.1-sol"), max_semver(wire_compatible_version(), "0.162.1")); + assert_eq!(codex_backend_version_for_model("gpt-6-sol"), max_semver(wire_compatible_version(), "0.162.1")); + } + #[test] fn wire_compat_clamps_old_versions() { assert_eq!( diff --git a/code-rs/common/src/model_presets.rs b/code-rs/common/src/model_presets.rs index 7dbb39d06936..8d48cb84a25e 100644 --- a/code-rs/common/src/model_presets.rs +++ b/code-rs/common/src/model_presets.rs @@ -48,7 +48,7 @@ const ALL_TEXT_VERBOSITY: &[TextVerbosityConfig] = &[ ]; static PRESETS: Lazy> = Lazy::new(|| { - vec![ + let mut presets = vec![ ModelPreset { id: "gpt-5.5".to_string(), model: "gpt-5.5".to_string(), @@ -562,7 +562,26 @@ static PRESETS: Lazy> = Lazy::new(|| { pro_only: false, show_in_picker: false, }, - ] + ]; + presets.retain(|preset| code_core::modern_models::modern_model(&preset.id).is_none()); + presets.extend(code_core::modern_models::MODERN_MODELS.iter().map(|spec| ModelPreset { + id: spec.id.to_string(), + model: spec.id.to_string(), + display_name: spec.display_name.to_string(), + description: spec.description.to_string(), + default_reasoning_effort: ReasoningEffort::Medium, + supported_reasoning_efforts: code_core::modern_models::reasoning_efforts(spec.id) + .unwrap_or_default().iter().map(|effort| ReasoningEffortPreset { + effort: (*effort).into(), + description: format!("{} reasoning effort", effort), + }).collect(), + supported_text_verbosity: ALL_TEXT_VERBOSITY, + is_default: false, + upgrade: None, + pro_only: false, + show_in_picker: true, + })); + presets }); pub fn model_preset_available_for_auth( @@ -594,6 +613,14 @@ pub fn builtin_model_presets( }) .filter(|preset| preset.show_in_picker) .cloned() + .map(|mut preset| { + if auth_mode.is_some_and(AuthMode::is_chatgpt) + && code_core::modern_models::modern_model(&preset.model).is_some() + { + preset.supported_reasoning_efforts.retain(|effort| effort.effort != ReasoningEffort::None); + } + preset + }) .collect() } @@ -666,6 +693,18 @@ pub fn allowed_text_verbosity_for_model(model: &str) -> &'static [TextVerbosityC mod tests { use super::*; + #[test] + fn modern_picker_entries_have_exact_ids_labels_and_max_effort() { + let presets = builtin_model_presets(Some(AuthMode::ApiKey), false); + for spec in code_core::modern_models::MODERN_MODELS { + let preset = presets.iter().find(|preset| preset.id == spec.id).expect("picker entry"); + assert_eq!(preset.model, spec.id); + assert_eq!(preset.display_name, spec.display_name); + assert!(preset.supported_reasoning_efforts.iter().any(|entry| entry.effort == ReasoningEffort::Max)); + assert!(!preset.supported_reasoning_efforts.iter().any(|entry| entry.effort == ReasoningEffort::Minimal)); + } + } + #[test] fn only_one_default_model_is_configured() { assert_eq!(PRESETS.iter().filter(|preset| preset.is_default).count(), 1); diff --git a/code-rs/core/src/agent_defaults.rs b/code-rs/core/src/agent_defaults.rs index 70cad29db0f3..260f694ea88f 100644 --- a/code-rs/core/src/agent_defaults.rs +++ b/code-rs/core/src/agent_defaults.rs @@ -40,17 +40,19 @@ const MODELS_MANIFEST: &str = include_str!("../../../codex-rs/models-manager/mod /// CLI-name lookups. pub const DEFAULT_AGENT_NAMES: &[&str] = &[ // Frontline for moderate/challenging tasks + "code-gpt-6.1-sol", "code-gpt-5.5", + "code-gpt-5.6-sol", "code-gpt-5.6-terra", "code-gpt-5.6-luna", - "claude-opus-4.8", + "claude-opus-5-5", "gemini-3.1-pro", // Straightforward / cost-aware - "claude-sonnet-4.6", + "claude-sonnet-5-5", "gemini-3.5-flash", "github-copilot", // Mixed/general and alternates - "claude-haiku-4.5", + "claude-haiku-5-5", "qwen-3-coder", "cloud-gpt-5.1-codex-max", ]; @@ -104,6 +106,7 @@ const AGENT_MODEL_SPECS: &[AgentModelSpec] = &[ description: "Highest-capacity GPT option for tricky reasoning; use when correctness matters most.", enabled_by_default: true, aliases: &[ + "gpt-5.6-terra", "code-gpt-5.2", "code-gpt-5.4", "gpt-5.4", @@ -128,6 +131,7 @@ const AGENT_MODEL_SPECS: &[AgentModelSpec] = &[ description: "Budget coding agent for small changes and quick refactors; use when speed and cost matter.", enabled_by_default: true, aliases: &[ + "gpt-5.6-luna", "gpt-5.4-mini", "code-gpt-5.4-mini", "code-gpt-5.1-codex-mini", @@ -179,22 +183,100 @@ const AGENT_MODEL_SPECS: &[AgentModelSpec] = &[ is_frontline: false, pro_only: true, }, + AgentModelSpec { + slug: "claude-opus-5-5", + family: "claude", + cli: "claude", + read_only_args: CLAUDE_OPUS_READ_ONLY, + write_args: CLAUDE_OPUS_WRITE, + model_args: &["--model", "claude-opus-5-5"], + description: "Claude Opus 5.5 requires Claude Code 2.1.280 or newer; exact model selection.", + enabled_by_default: false, + aliases: &["claude-opus", "claude-opus-5.5"], + gating_env: None, + is_frontline: true, + pro_only: false, + }, + AgentModelSpec { + slug: "claude-sonnet-5-5", + family: "claude", + cli: "claude", + read_only_args: CLAUDE_SONNET_READ_ONLY, + write_args: CLAUDE_SONNET_WRITE, + model_args: &["--model", "claude-sonnet-5-5"], + description: "Claude Sonnet via Claude Code; executes the exact selected model.", + enabled_by_default: true, + aliases: &["claude", "claude-sonnet", "claude-sonnet-5.5"], + gating_env: None, + is_frontline: false, + pro_only: false, + }, + AgentModelSpec { + slug: "claude-haiku-5-5", + family: "claude", + cli: "claude", + read_only_args: CLAUDE_HAIKU_READ_ONLY, + write_args: CLAUDE_HAIKU_WRITE, + model_args: &["--model", "claude-haiku-5-5"], + description: "Claude Haiku via Claude Code; executes the exact selected model.", + enabled_by_default: true, + aliases: &["claude-haiku", "claude-haiku-5.5"], + gating_env: None, + is_frontline: false, + pro_only: false, + }, AgentModelSpec { slug: "claude-opus-4.8", family: "claude", cli: "claude", read_only_args: CLAUDE_OPUS_READ_ONLY, write_args: CLAUDE_OPUS_WRITE, - model_args: &["--model", "opus"], - description: "Higher-capacity Claude model for complex reasoning; use when you want the strongest Claude.", + model_args: &["--model", "claude-opus-4-8"], + description: "Claude Opus 4.8; preserves exact older model selection.", enabled_by_default: true, - aliases: &[ - "claude-opus", - "claude-opus-4.1", - "claude-opus-4.5", - "claude-opus-4.6", - "claude-opus-4.7", - ], + aliases: &["claude-opus-4-8"], + gating_env: None, + is_frontline: true, + pro_only: false, + }, + AgentModelSpec { + slug: "claude-opus-4.7", + family: "claude", + cli: "claude", + read_only_args: CLAUDE_OPUS_READ_ONLY, + write_args: CLAUDE_OPUS_WRITE, + model_args: &["--model", "claude-opus-4-7"], + description: "Claude Opus 4.7; preserves exact older model selection.", + enabled_by_default: true, + aliases: &["claude-opus-4-7"], + gating_env: None, + is_frontline: true, + pro_only: false, + }, + AgentModelSpec { + slug: "claude-opus-4.6", + family: "claude", + cli: "claude", + read_only_args: CLAUDE_OPUS_READ_ONLY, + write_args: CLAUDE_OPUS_WRITE, + model_args: &["--model", "claude-opus-4-6"], + description: "Claude Opus 4.6; preserves exact older model selection.", + enabled_by_default: true, + aliases: &["claude-opus-4-6"], + gating_env: None, + is_frontline: true, + pro_only: false, + }, + AgentModelSpec { + slug: "claude-opus-4.5", + family: "claude", + cli: "claude", + read_only_args: CLAUDE_OPUS_READ_ONLY, + write_args: CLAUDE_OPUS_WRITE, + model_args: &["--model", "claude-opus-4-5-20251101"], + description: "Claude Opus 4.5; preserves exact older model selection.", + enabled_by_default: true, + aliases: &["claude-opus-4-5-20251101"], gating_env: None, is_frontline: true, pro_only: false, @@ -205,10 +287,24 @@ const AGENT_MODEL_SPECS: &[AgentModelSpec] = &[ cli: "claude", read_only_args: CLAUDE_SONNET_READ_ONLY, write_args: CLAUDE_SONNET_WRITE, - model_args: &["--model", "sonnet"], - description: "Balanced Claude model for implementation and debugging; a solid default when you want Claude.", + model_args: &["--model", "claude-sonnet-4-6"], + description: "Claude Sonnet via Claude Code; executes the exact selected model.", enabled_by_default: true, - aliases: &["claude", "claude-sonnet", "claude-sonnet-4.5"], + aliases: &["claude-sonnet-4-6"], + gating_env: None, + is_frontline: false, + pro_only: false, + }, + AgentModelSpec { + slug: "claude-sonnet-4.5", + family: "claude", + cli: "claude", + read_only_args: CLAUDE_SONNET_READ_ONLY, + write_args: CLAUDE_SONNET_WRITE, + model_args: &["--model", "claude-sonnet-4-5-20250929"], + description: "Claude Sonnet via Claude Code; executes the exact selected model.", + enabled_by_default: true, + aliases: &["claude-sonnet-4-5-20250929"], gating_env: None, is_frontline: false, pro_only: false, @@ -219,10 +315,10 @@ const AGENT_MODEL_SPECS: &[AgentModelSpec] = &[ cli: "claude", read_only_args: CLAUDE_HAIKU_READ_ONLY, write_args: CLAUDE_HAIKU_WRITE, - model_args: &["--model", "haiku"], - description: "Fast Claude model for simple tasks, drafts, and quick iterations; pick when latency matters.", + model_args: &["--model", "claude-haiku-4-5-20251001"], + description: "Claude Haiku via Claude Code; executes the exact selected model.", enabled_by_default: true, - aliases: &["claude-haiku"], + aliases: &["claude-haiku-4-5-20251001"], gating_env: None, is_frontline: false, pro_only: false, @@ -353,13 +449,18 @@ fn dynamic_code_agent_specs() -> Vec { fn dynamic_code_agent_spec(model: ManifestModel) -> Option { let track = code_agent_track(&model.slug)?; - if static_agent_model_spec(&model.slug).is_some() { + if static_agent_model_spec(&model.slug).is_some() + || static_agent_model_spec(&format!("code-{}", model.slug)).is_some() + { return None; } let candidate_version = parse_model_version_components(&model.slug)?; let highest_static_version = highest_static_code_track_version(track)?; - if candidate_version <= highest_static_version && model.slug != "gpt-5.5" { + if candidate_version <= highest_static_version + && model.slug != "gpt-5.5" + && crate::modern_models::modern_model(&model.slug).is_none() + { return None; } @@ -703,6 +804,16 @@ pub fn default_params_for(name: &str, read_only: bool) -> Vec { mod tests { use super::*; + #[test] + fn every_modern_openai_model_has_an_exact_agent_route() { + for spec in crate::modern_models::MODERN_MODELS { + let agent = agent_model_spec(spec.id).expect("agent route"); + assert_eq!(agent.model_args, &["--model", spec.id]); + assert_eq!(agent.family, "code"); + } + assert!(agent_model_spec("gpt-6-terra").is_none()); + } + #[test] fn cloud_defaults_are_empty_both_modes() { assert!(default_params_for("cloud", true).is_empty()); @@ -770,13 +881,13 @@ mod tests { #[test] fn refreshed_provider_agent_aliases_resolve_to_current_defaults() { let opus = agent_model_spec("claude-opus-4.6").expect("legacy opus alias resolves"); - assert_eq!(opus.slug, "claude-opus-4.8"); - assert_eq!(opus.model_args, &["--model", "opus"]); + assert_eq!(opus.slug, "claude-opus-4.6"); + assert_eq!(opus.model_args, &["--model", "claude-opus-4-6"]); let sonnet = agent_model_spec("claude-sonnet-4.5").expect("legacy sonnet alias resolves"); - assert_eq!(sonnet.slug, "claude-sonnet-4.6"); - assert_eq!(sonnet.model_args, &["--model", "sonnet"]); + assert_eq!(sonnet.slug, "claude-sonnet-4.5"); + assert_eq!(sonnet.model_args, &["--model", "claude-sonnet-4-5-20250929"]); let gemini = agent_model_spec("gemini").expect("gemini alias resolves"); assert_eq!(gemini.slug, "gemini-3.5-flash"); @@ -861,6 +972,13 @@ mod tests { assert!(!filtered.contains(&"gpt-5.3-codex-spark".to_string())); } + #[test] + fn unverified_opus_generation_is_not_enabled_by_default() { + let spec = agent_model_spec("claude-opus-5-5").unwrap(); + assert!(!spec.is_enabled()); + assert!(!default_agent_configs().iter().any(|agent| agent.name == spec.slug)); + } + #[test] fn dynamic_agent_specs_include_newer_manifest_models() { let spec = agent_model_spec("gpt-5.5").expect("gpt-5.5 spec should be present"); diff --git a/code-rs/core/src/agent_tool.rs b/code-rs/core/src/agent_tool.rs index dfee2c967b31..482369c3c4e0 100644 --- a/code-rs/core/src/agent_tool.rs +++ b/code-rs/core/src/agent_tool.rs @@ -1575,10 +1575,15 @@ async fn execute_model_with_permissions( ) -> Result { let spec_opt = agent_model_spec(model) .or_else(|| config.as_ref().and_then(|cfg| agent_model_spec(&cfg.name))) - .or_else(|| config.as_ref().and_then(|cfg| agent_model_spec(&cfg.command))); +; if let Some(spec) = spec_opt { - if !spec.is_enabled() { + let explicit_opus = spec.slug == "claude-opus-5-5" + && config.as_ref().is_some_and(|cfg| cfg.enabled); + if !spec.is_enabled() && !explicit_opus { + if spec.slug == "claude-opus-5-5" { + return Err("claude-opus-5-5 is unverified and disabled by default; verify Claude Code >=2.1.280 and model access, then configure an explicitly enabled Claude agent".to_string()); + } if let Some(flag) = spec.gating_env { return Err(format!( "agent model '{}' is disabled; set {}=1 to enable it", @@ -1618,7 +1623,8 @@ async fn execute_model_with_permissions( // `codex` binary to be present on PATH. This improves portability, // especially on Windows where global shims may be missing. let model_lower = model.to_lowercase(); - let command_lower = command_for_spawn.to_ascii_lowercase(); + let command_lower = PathBuf::from(&command_for_spawn).file_name() + .and_then(|name| name.to_str()).unwrap_or(&command_for_spawn).to_ascii_lowercase(); fn is_known_family(s: &str) -> bool { matches!( s, @@ -1641,6 +1647,8 @@ async fn execute_model_with_permissions( spec_family } else if is_known_family(model_lower.as_str()) { model_lower.as_str() + } else if command_lower == "coder" || command_lower == "codex" { + "code" } else if is_known_family(command_lower.as_str()) { command_lower.as_str() } else { @@ -1666,9 +1674,11 @@ async fn execute_model_with_permissions( } } - strip_model_flags(&mut final_args); + let explicit_model_args = take_model_flags(&mut final_args)?; - let spec_model_args: Vec = if let Some(spec) = spec_opt { + let spec_model_args: Vec = if !explicit_model_args.is_empty() { + explicit_model_args + } else if let Some(spec) = spec_opt { spec.model_args.iter().map(|arg| (*arg).to_string()).collect() } else { Vec::new() @@ -1676,18 +1686,10 @@ async fn execute_model_with_permissions( let built_in_cloud = family == "cloud" && config.is_none(); - // Clamp reasoning effort to what the target model supports. - let clamped_effort = match reasoning_effort { - code_protocol::config_types::ReasoningEffort::XHigh => { - let lower = slug_for_defaults.to_ascii_lowercase(); - if lower.contains("max") { - reasoning_effort - } else { - code_protocol::config_types::ReasoningEffort::High - } - } - other => other, - }; + // Match the actual selected model, including explicit configuration overrides. + let selected_model = spec_model_args.get(1).map(String::as_str).unwrap_or(model); + let clamped_effort: code_protocol::config_types::ReasoningEffort = + crate::reasoning::clamp_reasoning_effort_for_model(selected_model, reasoning_effort.clone().into()).into(); // Configuration overrides for Codex CLI families. External CLIs (claude, // gemini, antigravity, copilot, qwen) do not understand our config flags, @@ -1716,6 +1718,20 @@ async fn execute_model_with_permissions( strip_model_flags(&mut defaults); final_args.extend(defaults); final_args.extend(spec_model_args.iter().cloned()); + if family == "claude" && matches!(selected_model, + "claude-opus-5-5" | "claude-sonnet-5-5" | "claude-haiku-5-5") + && !final_args.iter().any(|arg| arg == "--effort" || arg.starts_with("--effort=")) + { + let effort = match reasoning_effort { + code_protocol::config_types::ReasoningEffort::Low => "low", + code_protocol::config_types::ReasoningEffort::Medium => "medium", + code_protocol::config_types::ReasoningEffort::High => "high", + code_protocol::config_types::ReasoningEffort::XHigh => "xhigh", + code_protocol::config_types::ReasoningEffort::Max => "max", + _ => return Err(format!("Claude model '{selected_model}' supports low, medium, high, xhigh, or max effort through this CLI")), + }; + final_args.extend(["--effort".to_string(), effort.to_string()]); + } final_args.push("-p".into()); final_args.push(prompt.to_string()); } @@ -2124,6 +2140,28 @@ fn resolve_program_path(use_current_exe: bool, command_for_spawn: &str) -> Resul Ok(std::path::PathBuf::from(command_for_spawn)) } +// Return one explicit model override and remove it from the mode arguments. +fn take_model_flags(args: &mut Vec) -> Result, String> { + let mut selected = None; + let mut retained = Vec::new(); + let mut iter = args.drain(..); + while let Some(arg) = iter.next() { + let value = if arg == "--model" || arg == "-m" { + Some(iter.next().ok_or_else(|| format!("{arg} requires a model identifier"))?) + } else { + arg.strip_prefix("--model=").or_else(|| arg.strip_prefix("-m=")).map(str::to_string) + }; + if let Some(value) = value { + if value.trim().is_empty() { return Err("model identifier must not be empty".to_string()); } + if selected.is_some() { return Err("multiple agent model overrides; configure exactly one --model".to_string()); } + selected = Some(value); + } else { retained.push(arg); } + } + drop(iter); + *args = retained; + Ok(selected.map(|model| vec!["--model".to_string(), model]).unwrap_or_default()) +} + fn strip_model_flags(args: &mut Vec) { let mut i = 0; while i < args.len() { @@ -3027,6 +3065,88 @@ mod tests { assert_eq!(output.trim(), "local-claude"); } + #[tokio::test] + async fn unverified_opus_requires_explicit_agent_configuration() { + let error = execute_model_with_permissions("mock-agent", "claude-opus-5-5", "task", true, + None, None, ReasoningEffort::High, None, None, None).await.unwrap_err(); + assert!(error.contains("disabled by default")); + assert!(error.contains("2.1.280")); + } + + #[tokio::test] + async fn modern_claude_agents_launch_exact_ids_and_effort() { + for model in ["claude-opus-5-5", "claude-sonnet-5-5", "claude-haiku-5-5"] { + let dir = tempdir().expect("tempdir"); + let executable = script_path(dir.path(), "claude"); + write_argv_script(&executable); + let mut cfg = agent_with_command(executable.to_str().expect("path")); + cfg.name = model.to_string(); + let output = execute_model_with_permissions("mock-agent", model, "task", true, + None, Some(cfg), ReasoningEffort::Max, None, None, None).await.expect("launch"); + let args: Vec<&str> = output.trim().split('|').collect(); + let model_index = args.iter().position(|arg| *arg == "--model").expect("model argument"); + assert_eq!(args[model_index + 1], model); + let effort_index = args.iter().position(|arg| *arg == "--effort").expect("effort argument"); + assert_eq!(args[effort_index + 1], "max"); + assert!(args.contains(&"--allowedTools")); + assert!(args.contains(&"-p")); + } + } + + #[tokio::test] + async fn configured_agent_role_preserves_exact_model_override() { + let dir = tempdir().expect("tempdir"); + let executable = script_path(dir.path(), "claude"); + write_argv_script(&executable); + let mut cfg = agent_with_command(executable.to_str().expect("path")); + cfg.name = "investigation".to_string(); + cfg.args = vec!["--model=claude-sonnet-5-5".to_string()]; + let output = execute_model_with_permissions("mock-agent", "investigation", "task", true, + None, Some(cfg), ReasoningEffort::Low, None, None, None).await.expect("launch"); + assert!(output.contains("--model|claude-sonnet-5-5")); + assert!(output.contains("--effort|low")); + assert!(output.contains("-p|task")); + assert!(!output.contains("--model|sonnet")); + } + + #[tokio::test] + async fn explicitly_configured_claude_opus_agent_executes_custom_command() { + let dir = tempdir().expect("tempdir"); + let executable = script_path(dir.path(), "claude"); + write_argv_script(&executable); + let mut cfg = agent_with_command(executable.to_str().expect("path")); + cfg.name = "claude-opus-5-5".to_string(); + cfg.enabled = true; + let output = execute_model_with_permissions("mock-agent", "claude-opus-5-5", "task", true, + None, Some(cfg), ReasoningEffort::High, None, None, None).await.expect("launch"); + let args: Vec<&str> = output.trim().split('|').collect(); + let model_index = args.iter().position(|arg| *arg == "--model").expect("model argument"); + assert_eq!(args[model_index + 1], "claude-opus-5-5"); + let effort_index = args.iter().position(|arg| *arg == "--effort").expect("effort argument"); + assert_eq!(args[effort_index + 1], "high"); + assert!(args.contains(&"--allowedTools")); + assert!(args.contains(&"-p")); + } + + #[tokio::test] + async fn gpt_6_1_agent_launch_preserves_max_and_explicit_override() { + let dir = tempdir().expect("tempdir"); + let executable = script_path(dir.path(), "mock-coder"); + write_argv_script(&executable); + let mut cfg = agent_with_command(executable.to_str().expect("path")); + cfg.name = "code-gpt-6.1-sol".to_string(); + let output = execute_model_with_permissions("mock-agent", "code-gpt-6.1-sol", "task", true, + None, Some(cfg.clone()), ReasoningEffort::Max, None, None, None).await.expect("launch"); + assert!(output.contains("--model|gpt-6.1-sol")); + assert!(output.contains("model_reasoning_effort=max")); + assert!(!output.contains("--model|gpt-6-sol")); + cfg.args = vec!["--model".to_string(), "gpt-6-luna".to_string()]; + let output = execute_model_with_permissions("mock-agent", "code-gpt-6.1-sol", "task", true, + None, Some(cfg), ReasoningEffort::High, None, None, None).await.expect("override launch"); + assert!(output.contains("--model|gpt-6-luna")); + assert!(!output.contains("--model|gpt-6.1-sol")); + } + #[tokio::test] async fn github_copilot_launches_with_agent_mode_flags() { let dir = tempdir().expect("tempdir"); diff --git a/code-rs/core/src/client.rs b/code-rs/core/src/client.rs index 1c937647f8b1..0dc35a428963 100644 --- a/code-rs/core/src/client.rs +++ b/code-rs/core/src/client.rs @@ -1,3 +1,4 @@ +use crate::error::non_retryable_model_error; use std::collections::BTreeMap; use std::io::BufRead; use std::path::Path; @@ -372,6 +373,7 @@ impl ModelClient { match self.provider.wire_api { WireApi::ResponsesWebsocket => Some(preferred_ws_version_from_env()), WireApi::Responses => { + if !self.uses_first_party_openai_model_metadata() { return None; } let prefer_websockets = prompt .model_family_override .as_ref() @@ -401,8 +403,24 @@ impl ModelClient { mut family: ModelFamily, ) -> ModelFamily { if !self.uses_first_party_openai_model_metadata() { + if crate::modern_models::modern_model(&family.slug).is_some() { + let slug = family.slug.clone(); + family = crate::model_family::derive_provider_model_family(&slug); + family.supports_reasoning_summaries = self.config.model_family.supports_reasoning_summaries + && self.config.model_family.slug == slug + && !self.config.model_family.use_responses_lite; + } family.use_responses_lite = false; family.prefer_websockets = false; + } else if !self.auth_manager.as_ref().and_then(|manager| manager.auth()) + .is_some_and(|auth| auth.mode.is_chatgpt()) + { + // Public API keys use public Responses semantics. Lite is a Codex + // backend contract, not a capability implied by the model slug. + family.use_responses_lite = false; + if crate::modern_models::modern_model(&family.slug).is_some() { + family.context_window = Some(crate::modern_models::PUBLIC_CONTEXT_WINDOW); + } } family } @@ -412,7 +430,7 @@ impl ModelClient { // compatibility check identifies the one built-in first-party path; // proxy/custom endpoints must not inherit embedded OpenAI capabilities. self.config.model_provider_id == "openai" - && self.provider.is_public_openai_responses_endpoint() + && self.provider.uses_official_openai_model_catalog() } fn model_family_for_request(&self, prompt: &Prompt, model: &str) -> ModelFamily { @@ -450,10 +468,13 @@ impl ModelClient { req_builder: reqwest::RequestBuilder, model: &str, ) -> reqwest::RequestBuilder { - req_builder.headers(crate::default_client::requested_model_headers( - Some(self.config.responses_originator_header.as_str()), - model, - )) + let originator = Some(self.config.responses_originator_header.as_str()); + let backend = self.uses_first_party_openai_model_metadata() + && self.auth_manager.as_ref().and_then(|manager| manager.auth()).is_some_and(|auth| auth.uses_codex_backend()); + let headers = if backend { + crate::default_client::requested_codex_backend_model_headers(originator, model) + } else { crate::default_client::requested_model_headers(originator, model) }; + req_builder.headers(headers) } /// Attach session-correlation headers for prompt-cache and gateway routing. @@ -480,14 +501,10 @@ impl ModelClient { family: &ModelFamily, effort: ReasoningEffortConfig, ) -> Option { - if self.reasoning_summary_disabled.load(Ordering::Relaxed) { - return None; - } - create_reasoning_param_for_request( family, Some(effort), - self.summary, + self.get_reasoning_summary(), ) } @@ -692,9 +709,14 @@ impl ModelClient { } pub fn get_auto_compact_token_limit(&self) -> Option { - self.config - .model_auto_compact_token_limit - .or_else(|| self.config.model_family.auto_compact_token_limit()) + let limit = self.config.model_auto_compact_token_limit + .or_else(|| self.config.model_family.auto_compact_token_limit()); + if self.uses_first_party_openai_model_metadata() + && crate::modern_models::modern_model(&self.config.model).is_some() + && self.auth_manager.as_ref().and_then(|manager| manager.auth()).is_some_and(|auth| auth.mode.is_chatgpt()) + { + limit.map(|limit| limit.min((crate::modern_models::CODEX_MAX_CONTEXT_WINDOW * 9 / 10) as i64)) + } else { limit } } pub fn get_context_mode(&self) -> Option { @@ -717,6 +739,22 @@ impl ModelClient { let log_tag = env_log_tag .as_deref() .or(prompt.log_tag.as_deref()); + let model = prompt.model_override.as_deref().unwrap_or(&self.config.model); + if self.config.model_provider_id == "openai" + && (self.provider.uses_official_openai_model_catalog() || self.provider.is_public_openai_chat_endpoint()) + { + if model.starts_with("claude-") { + return Err(CodexErr::UnsupportedOperation(format!("'{model}' is an Anthropic model; use a configured Claude agent or an explicitly compatible proxy provider"))); + } + if (model.starts_with("gpt-6") || model.starts_with("gpt-5.6")) + && crate::modern_models::modern_model(model).is_none() + { + return Err(CodexErr::UnsupportedOperation(format!("unverified OpenAI model '{model}'; select an exact model from the catalog or configure its provider explicitly"))); + } + if self.provider.wire_api == WireApi::Chat && model.starts_with("gpt-6") && !prompt.tools.is_empty() { + return Err(CodexErr::UnsupportedOperation(format!("'{model}' tool calling requires the Responses path in Every Code; set wire_api = 'responses'"))); + } + } match self.provider.wire_api { WireApi::Responses => { if let Some(ws_version) = self.active_ws_version_for_prompt(prompt) { @@ -726,6 +764,7 @@ impl ModelClient { { Ok(stream) => Ok(stream), Err(err) => { + if non_retryable_model_error(&err) { return Err(err); } self.websockets_disabled.store(true, Ordering::Relaxed); warn!( "preferred websocket transport failed; falling back to responses HTTP stream: {err}" @@ -759,6 +798,7 @@ impl ModelClient { { Ok(stream) => Ok(stream), Err(err) => { + if non_retryable_model_error(&err) { return Err(err); } self.websockets_disabled.store(true, Ordering::Relaxed); warn!( "responses_websocket transport failed; falling back to responses HTTP stream: {err}" @@ -825,6 +865,18 @@ impl ModelClient { prompt: &Prompt, log_tag: Option<&str>, ws_version: ResponsesWebsocketVersion, + ) -> Result { + let model = prompt.model_override.as_deref().unwrap_or(&self.config.model); + let family = self.model_family_for_request(prompt, model); + self.stream_responses_websocket_for_family(prompt, log_tag, ws_version, family).await + } + + async fn stream_responses_websocket_for_family( + &self, + prompt: &Prompt, + log_tag: Option<&str>, + ws_version: ResponsesWebsocketVersion, + request_family: ModelFamily, ) -> Result { let auth_manager = self.auth_manager.clone(); let auth_mode = auth_manager @@ -837,8 +889,10 @@ impl ModelClient { .model_override .as_deref() .unwrap_or(self.config.model.as_str()); - let effective_effort = clamp_reasoning_effort_for_model(request_model, self.effort); - let request_family = self.model_family_for_request(prompt, request_model); + let mut effective_effort = clamp_reasoning_effort_for_model(request_model, self.effort); + if auth_mode.is_some_and(|mode| mode.is_chatgpt()) && effective_effort == ReasoningEffortConfig::Disabled { + effective_effort = ReasoningEffortConfig::Low; + } let store = should_store_responses(prompt, &self.provider, &request_family); let full_instructions = prompt.get_full_instructions(&request_family); @@ -1009,7 +1063,7 @@ impl ModelClient { .map_or(false, |req| req.headers().contains_key("OpenAI-Beta")); if !has_beta_header { - let beta_value = if self.provider.is_public_openai_responses_endpoint() { + let beta_value = if self.provider.uses_official_openai_model_catalog() { RESPONSES_BETA_HEADER_V1 } else { RESPONSES_BETA_HEADER_EXPERIMENTAL @@ -1260,6 +1314,20 @@ impl ModelClient { return self.stream_responses(prompt, log_tag).await; } + if let WsError::Http(response) = &err { + if matches!(response.status(), StatusCode::NOT_FOUND | StatusCode::METHOD_NOT_ALLOWED) { + self.websockets_disabled.store(true, Ordering::Relaxed); + return self.stream_responses(prompt, log_tag).await; + } + if response.status().is_client_error() + && !matches!(response.status(), StatusCode::TOO_MANY_REQUESTS | StatusCode::NOT_FOUND | StatusCode::METHOD_NOT_ALLOWED) { + return Err(CodexErr::UnexpectedStatus(UnexpectedResponseError { + status: response.status(), + body: response.body().as_ref().map(|body| String::from_utf8_lossy(body).into_owned()).unwrap_or_default(), + request_id: Some(request_id.clone()), + })); + } + } let err = CodexErr::Stream( format!("[ws] failed to connect: {err}"), None, @@ -1294,6 +1362,17 @@ impl ModelClient { /// Implementation for the OpenAI *Responses* experimental API. async fn stream_responses(&self, prompt: &Prompt, log_tag: Option<&str>) -> Result { + let model = prompt.model_override.as_deref().unwrap_or(&self.config.model); + let family = self.model_family_for_request(prompt, model); + self.stream_responses_for_family(prompt, log_tag, family).await + } + + async fn stream_responses_for_family( + &self, + prompt: &Prompt, + log_tag: Option<&str>, + request_family: ModelFamily, + ) -> Result { if let Some(path) = &*CODEX_RS_SSE_FIXTURE { // short circuit for tests warn!(path, "Streaming from fixture"); @@ -1315,8 +1394,10 @@ impl ModelClient { .model_override .as_deref() .unwrap_or(self.config.model.as_str()); - let effective_effort = clamp_reasoning_effort_for_model(request_model, self.effort); - let request_family = self.model_family_for_request(prompt, request_model); + let mut effective_effort = clamp_reasoning_effort_for_model(request_model, self.effort); + if auth_mode.is_some_and(|mode| mode.is_chatgpt()) && effective_effort == ReasoningEffortConfig::Disabled { + effective_effort = ReasoningEffortConfig::Low; + } let store = should_store_responses(prompt, &self.provider, &request_family); let full_instructions = prompt.get_full_instructions(&request_family); @@ -1500,7 +1581,7 @@ impl ModelClient { .map_or(false, |req| req.headers().contains_key("OpenAI-Beta")); if !has_beta_header { - let beta_value = if self.provider.is_public_openai_responses_endpoint() { + let beta_value = if self.provider.uses_official_openai_model_catalog() { RESPONSES_BETA_HEADER_V1 } else { RESPONSES_BETA_HEADER_EXPERIMENTAL @@ -2081,7 +2162,12 @@ impl ModelClient { #[allow(dead_code)] pub fn get_model_context_window(&self) -> Option { - self.config.model_context_window + let window = self.config.model_context_window; + if self.uses_first_party_openai_model_metadata() + && crate::modern_models::modern_model(&self.config.model).is_some() + && self.auth_manager.as_ref().and_then(|manager| manager.auth()).is_some_and(|auth| auth.mode.is_chatgpt()) + { window.map(|limit| limit.min(crate::modern_models::CODEX_MAX_CONTEXT_WINDOW)) } + else { window } } #[allow(dead_code)] @@ -2149,7 +2235,7 @@ impl ModelClient { .map_or(false, |req| req.headers().contains_key("OpenAI-Beta")); if !has_beta_header { - let beta_value = if self.provider.is_public_openai_responses_endpoint() { + let beta_value = if self.provider.uses_official_openai_model_catalog() { RESPONSES_BETA_HEADER_V1 } else { RESPONSES_BETA_HEADER_EXPERIMENTAL @@ -2350,6 +2436,11 @@ fn map_wrapped_websocket_error_event(event: WrappedWebsocketErrorEvent) -> Optio let message = error .message .unwrap_or_else(|| "websocket returned an error event".to_string()); + if error.r#type.as_deref() == Some("invalid_request_error") { + return Some(CodexErr::UnexpectedStatus(UnexpectedResponseError { + status: StatusCode::BAD_REQUEST, body: message, request_id: None, + })); + } return Some(CodexErr::Stream(message, None, None)); } return Some(CodexErr::Stream( @@ -3105,6 +3196,14 @@ async fn process_sse( retry_after, Some(request_id.clone()), )); + } else if error.r#type.as_deref() == Some("invalid_request_error") + || matches!(error.code.as_deref(), Some("model_not_found" | "unsupported_model" | "invalid_value" | "invalid_enum_value" | "unsupported_parameter")) + { + response_error = Some(CodexErr::UnexpectedStatus(UnexpectedResponseError { + status: StatusCode::BAD_REQUEST, + body: serde_json::json!({"error": {"message": error.message, "code": error.code, "param": error.param}}).to_string(), + request_id: Some(request_id.clone()), + })); } else if is_quota_exceeded_error(&error) { response_error = Some(CodexErr::QuotaExceeded); } else if is_server_overloaded_error(&error) { @@ -4650,7 +4749,8 @@ mod tests { let mapped = map_wrapped_websocket_error_event(wrapped).expect("error should map without status"); match mapped { - CodexErr::Stream(message, None, None) => { + CodexErr::UnexpectedStatus(UnexpectedResponseError { status, body: message, .. }) => { + assert_eq!(status, StatusCode::BAD_REQUEST); assert_eq!( message, "The requested model 'gpt-5.3-codex-spark' does not exist." @@ -4660,3 +4760,7 @@ mod tests { } } } + +#[cfg(test)] +#[path = "client_model_tests.rs"] +mod model_tests; diff --git a/code-rs/core/src/client_model_tests.rs b/code-rs/core/src/client_model_tests.rs new file mode 100644 index 000000000000..b1bd81d289c2 --- /dev/null +++ b/code-rs/core/src/client_model_tests.rs @@ -0,0 +1,279 @@ +//! Mock transport tests exercise the same serialized request paths as production. +use super::*; +use crate::config::{Config, ConfigOverrides, ConfigToml}; +use crate::modern_models::MODERN_MODELS; +use tempfile::tempdir; +use wiremock::matchers::{method, path}; +use wiremock::{Mock, MockServer, ResponseTemplate}; + +fn client_for(base_url: String, model: &str, oauth: bool) -> ModelClient { + let home = tempdir().expect("isolated code home"); + let mut config = Config::load_from_base_config_with_overrides( + ConfigToml::default(), ConfigOverrides::default(), home.path().to_path_buf(), + ).expect("config"); + config.model = model.to_string(); + config.model_family = find_family_for_model(model).unwrap_or_else(|| derive_default_model_family(model)); + config.model_provider_id = "openai".to_string(); + let mut provider = config.model_provider.clone(); + provider.base_url = Some(base_url); + provider.requires_openai_auth = false; + provider.request_max_retries = Some(4); + provider.stream_max_retries = Some(4); + provider.supports_websockets = true; + let auth = oauth.then(|| AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing())); + ModelClient::new(Arc::new(config), auth, None, provider, + ReasoningEffortConfig::Max, ReasoningSummaryConfig::None, + TextVerbosityConfig::Medium, Uuid::new_v4(), + Arc::new(Mutex::new(DebugLogger::new(false).expect("debug logger")))) +} + +fn completion() -> Value { + serde_json::json!({"type":"response.completed", "response":{"id":"resp_mock","usage":{"input_tokens":1,"output_tokens":1,"total_tokens":2}}}) +} + +fn prompt() -> Prompt { + let mut prompt = Prompt::default(); + prompt.store = true; + prompt.include_additional_instructions = false; + prompt.input = vec![ResponseItem::Message { + id: None, role: "user".to_string(), + content: vec![ContentItem::InputText { text: "mock request".to_string() }], + end_turn: None, phase: None, + }]; + prompt.tools = vec![crate::agent_tool::create_agent_tool(&["code-gpt-6.1-sol".to_string()])]; + prompt +} + +async fn assert_completed(mut stream: ResponseStream) { + while let Some(event) = timeout(Duration::from_secs(3), stream.next()).await.expect("bounded stream") { + if matches!(event.expect("stream event"), ResponseEvent::Completed { .. }) { return; } + } + panic!("missing completed event"); +} + +fn assert_lite_payload(body: &Value, model: &str) { + assert_eq!(body["model"], model); + assert_eq!(body["store"], false); + assert_eq!(body["parallel_tool_calls"], false); + assert_eq!(body["reasoning"]["effort"], "max"); + assert_eq!(body["reasoning"]["context"], "all_turns"); + assert!(body["reasoning"].get("summary").is_none()); + assert_eq!(body["include"], serde_json::json!(["reasoning.encrypted_content"])); + assert!(!body.to_string().contains("codex-lite")); + assert_eq!(body["input"][0]["type"], "additional_tools"); + assert!(body["input"][0]["tools"].to_string().contains("agent")); +} + +#[tokio::test] +async fn modern_models_serialize_lite_http_requests() { + for spec in MODERN_MODELS { + let server = MockServer::start().await; + Mock::given(method("POST")).and(path("/responses")) + .respond_with(ResponseTemplate::new(200).insert_header("content-type", "text/event-stream") + .set_body_string(format!("data: {}\n\n", completion()))) + .expect(1).mount(&server).await; + let client = client_for(server.uri(), spec.id, true); + let family = find_family_for_model(spec.id).expect("model family"); + assert_completed(client.stream_responses_for_family(&prompt(), None, family).await.expect("stream")).await; + let requests = server.received_requests().await.expect("requests"); + assert_eq!(requests[0].headers.get(X_OPENAI_INTERNAL_CODEX_RESPONSES_LITE_HEADER).expect("lite header"), "true"); + assert_lite_payload(&serde_json::from_slice(&requests[0].body).expect("JSON body"), spec.id); + } +} + +#[tokio::test] +async fn custom_provider_does_not_inherit_lite_and_override_is_exact() { + let server = MockServer::start().await; + Mock::given(method("POST")).and(path("/responses")) + .respond_with(ResponseTemplate::new(200).insert_header("content-type", "text/event-stream") + .set_body_string(format!("data: {}\n\n", completion()))) + .expect(1).mount(&server).await; + let mut client = client_for(server.uri(), "gpt-5.5", false); + client.provider.supports_websockets = false; + client.summary = ReasoningSummaryConfig::Detailed; + let mut prompt = prompt(); + prompt.model_override = Some("gpt-6.1-sol".to_string()); + prompt.model_family_override = find_family_for_model("gpt-6.1-sol"); + assert_completed(client.stream(&prompt).await.expect("custom stream")).await; + let requests = server.received_requests().await.expect("requests"); + assert!(requests[0].headers.get(X_OPENAI_INTERNAL_CODEX_RESPONSES_LITE_HEADER).is_none()); + let body: Value = serde_json::from_slice(&requests[0].body).expect("body"); + assert_eq!(body["model"], "gpt-6.1-sol"); + assert_eq!(body["store"], true); + assert!(body["reasoning"].is_null()); + let family = client.model_family_for_request(&prompt, "gpt-6.1-sol"); + assert_eq!(family.context_window, None); + assert_eq!(family.max_output_tokens, None); + assert!(!family.supports_reasoning_summaries); + assert!(!family.supports_search_tool); + assert!(body["tools"].to_string().contains("agent")); +} + +#[tokio::test] +async fn bad_request_is_not_retried_by_http_transport() { + let server = MockServer::start().await; + Mock::given(method("POST")).respond_with(ResponseTemplate::new(400).set_body_json( + serde_json::json!({"error":{"type":"invalid_request_error","message":"unsupported model"}}))) + .expect(1).mount(&server).await; + let client = client_for(server.uri(), "gpt-6.1-sol", false); + let result = timeout(Duration::from_secs(3), client.stream_responses(&prompt(), None)).await.expect("no retry delay"); + assert!(matches!(result, Err(CodexErr::UnexpectedStatus(_)))); + assert_eq!(server.received_requests().await.expect("requests").len(), 1); +} + +#[tokio::test] +async fn modern_models_serialize_lite_websocket_requests() { + for spec in MODERN_MODELS { + let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.expect("listener"); + let addr = listener.local_addr().expect("address"); + let server = tokio::spawn(async move { + let (tcp, _) = listener.accept().await.expect("connection"); + let mut socket = tokio_tungstenite::accept_hdr_async(tcp, |request: &tokio_tungstenite::tungstenite::handshake::server::Request, response| { + assert_eq!(request.headers().get(X_OPENAI_INTERNAL_CODEX_RESPONSES_LITE_HEADER).expect("lite header"), "true"); + Ok(response) + }).await.expect("handshake"); + let payload = socket.next().await.expect("message").expect("message"); + let body: Value = serde_json::from_str(payload.to_text().expect("text")).expect("JSON"); + socket.send(Message::Text(completion().to_string())).await.expect("completion"); + body + }); + let client = client_for(format!("http://{addr}"), spec.id, true); + let family = find_family_for_model(spec.id).expect("family"); + let stream = client.stream_responses_websocket_for_family(&prompt(), None, ResponsesWebsocketVersion::V2, family).await.expect("stream"); + assert_completed(stream).await; + let body = server.await.expect("server"); + assert_eq!(body["type"], "response.create"); + assert_lite_payload(&body, spec.id); + } +} + +#[tokio::test] +async fn websocket_bad_request_does_not_retry_or_fall_back_to_http() { + let server = MockServer::start().await; + Mock::given(method("GET")).respond_with(ResponseTemplate::new(400).set_body_string("unsupported parameter")) + .expect(1).mount(&server).await; + let mut client = client_for(server.uri(), "gpt-6.1-sol", false); + client.provider.wire_api = WireApi::ResponsesWebsocket; + let result = timeout(Duration::from_secs(3), client.stream(&prompt())).await.expect("bounded failure"); + assert!(matches!(result, Err(CodexErr::UnexpectedStatus(_)))); + let requests = server.received_requests().await.expect("requests"); + assert_eq!(requests.len(), 1); + assert_eq!(requests[0].method, "GET"); +} + +#[tokio::test] +async fn unsupported_first_party_model_has_actionable_diagnostic() { + let client = client_for("https://api.openai.com/v1".to_string(), "gpt-6-terra", false); + match client.stream(&prompt()).await { + Err(CodexErr::UnsupportedOperation(message)) => assert!(message.contains("unverified OpenAI model 'gpt-6-terra'")), + _ => panic!("unverified model must fail before network I/O"), + } +} + +#[tokio::test] +async fn websocket_route_not_found_falls_back_once_to_http() { + let server = MockServer::start().await; + Mock::given(method("GET")).respond_with(ResponseTemplate::new(404)).expect(1).mount(&server).await; + Mock::given(method("POST")).and(path("/responses")) + .respond_with(ResponseTemplate::new(200).insert_header("content-type", "text/event-stream") + .set_body_string(format!("data: {}\n\n", completion()))) + .expect(1).mount(&server).await; + let mut client = client_for(server.uri(), "gpt-6.1-sol", false); + client.provider.wire_api = WireApi::ResponsesWebsocket; + assert_completed(client.stream(&prompt()).await.expect("HTTP fallback")).await; +} + +#[tokio::test] +async fn disabling_summary_preserves_lite_context_and_oauth_compaction_limit() { + let server = MockServer::start().await; + Mock::given(method("POST")).and(path("/responses")) + .respond_with(ResponseTemplate::new(200).insert_header("content-type", "text/event-stream") + .set_body_string(format!("data: {}\n\n", completion()))) + .expect(1).mount(&server).await; + let client = client_for(server.uri(), "gpt-6.1-sol", true); + client.disable_reasoning_summary(); + assert_completed(client.stream_responses_for_family(&prompt(), None, + find_family_for_model("gpt-6.1-sol").expect("family")).await.expect("stream")).await; + let requests = server.received_requests().await.expect("requests"); + assert_lite_payload(&serde_json::from_slice(&requests[0].body).expect("JSON"), "gpt-6.1-sol"); + let mut client = client; + client.provider.base_url = None; + assert!(client.get_auto_compact_token_limit().unwrap() <= 784_800); +} + +/// Opt-in live check: reads existing credentials into memory, uses externally +/// managed auth (no refresh/write), and never logs credentials or persists them. +#[tokio::test] +#[ignore = "requires explicitly authorized existing ChatGPT credentials"] +async fn authenticated_gpt_6_1_sol_executes_without_substitution() { + let auth_path = std::env::var_os("EVERY_CODE_SMOKE_AUTH_PATH").expect("set existing auth file path"); + let auth = crate::auth::try_read_auth_json(std::path::Path::new(&auth_path)).expect("read auth"); + let tokens = auth.tokens.expect("existing OAuth tokens"); + let managed = CodexAuth::from_tokens_with_originator_and_mode(tokens, auth.last_refresh, + "every-code-model-validation", code_app_server_protocol::AuthMode::ChatgptAuthTokens); + let mut client = client_for("https://api.openai.com/v1".to_string(), "gpt-6.1-sol", true); + client.auth_manager = Some(AuthManager::from_auth_for_testing(managed)); + client.provider.base_url = None; + client.provider.supports_websockets = false; + client.effort = ReasoningEffortConfig::Low; + client.provider.request_max_retries = Some(0); + client.provider.stream_max_retries = Some(0); + let mut request = prompt(); + request.tools.clear(); + request.input = vec![ResponseItem::Message { + id: None, role: "user".to_string(), + content: vec![ContentItem::InputText { text: "Reply with exactly MODEL_OK. Do not use tools.".to_string() }], + end_turn: None, phase: None, + }]; + let mut stream = timeout(Duration::from_secs(60), client.stream(&request)).await.expect("bounded request").expect("live stream"); + let mut text = String::new(); + let mut completed = false; + while let Some(event) = timeout(Duration::from_secs(60), stream.next()).await.expect("bounded stream") { + match event.expect("live event") { + ResponseEvent::Created { response_model: Some(model), .. } => assert_eq!(model, "gpt-6.1-sol"), + ResponseEvent::OutputTextDelta { delta, .. } => text.push_str(&delta), + ResponseEvent::Completed { .. } => { completed = true; break; }, + _ => {}, + } + } + assert!(completed); + assert_eq!(text.trim(), "MODEL_OK"); +} + +#[test] +fn modern_backend_protocol_floor_is_scoped_to_oauth() { + for oauth in [false, true] { + let client = client_for("https://api.openai.com/v1".to_string(), "gpt-6.1-sol", oauth); + let request = client.apply_requested_model_headers( + reqwest::Client::new().post("https://example.invalid/responses"), "gpt-6.1-sol").build().expect("request"); + let expected = if oauth { code_version::codex_backend_version_for_model("gpt-6.1-sol") } + else { code_version::wire_compatible_version_for_model("gpt-6.1-sol") }; + assert_eq!(request.headers()["version"], expected); + } + let client = client_for("https://proxy.example/v1".to_string(), "gpt-6.1-sol", true); + let request = client.apply_requested_model_headers( + reqwest::Client::new().post("https://proxy.example/v1/responses"), "gpt-6.1-sol").build().expect("request"); + assert_eq!(request.headers()["version"], code_version::wire_compatible_version_for_model("gpt-6.1-sol")); +} + +#[tokio::test] +async fn modern_public_api_models_serialize_standard_responses() { + for spec in MODERN_MODELS { + let server = MockServer::start().await; + Mock::given(method("POST")).and(path("/responses")) + .respond_with(ResponseTemplate::new(200).insert_header("content-type", "text/event-stream") + .set_body_string(format!("data: {}\n\n", completion()))) + .expect(1).mount(&server).await; + let mut client = client_for("https://api.openai.com/v1".to_string(), spec.id, false); + let family = client.normalize_model_family_for_provider(find_family_for_model(spec.id).expect("family")); + client.provider.base_url = Some(server.uri()); + assert_completed(client.stream_responses_for_family(&prompt(), None, family).await.expect("stream")).await; + let requests = server.received_requests().await.expect("requests"); + let body: Value = serde_json::from_slice(&requests[0].body).expect("body"); + assert_eq!(body["model"], spec.id); + assert_eq!(body["reasoning"]["effort"], "max"); + assert!(body["reasoning"].get("context").is_none()); + assert_eq!(body["store"], true); + assert!(requests[0].headers.get(X_OPENAI_INTERNAL_CODEX_RESPONSES_LITE_HEADER).is_none()); + } +} diff --git a/code-rs/core/src/codex.rs b/code-rs/core/src/codex.rs index 83205a09d739..543c2a2e2df4 100644 --- a/code-rs/core/src/codex.rs +++ b/code-rs/core/src/codex.rs @@ -165,6 +165,8 @@ impl ConfirmGuardPatternRuntime { fn to_proto_reasoning_effort(effort: ReasoningEffortConfig) -> ProtoReasoningEffort { match effort { ReasoningEffortConfig::Minimal => ProtoReasoningEffort::Minimal, + ReasoningEffortConfig::Disabled => ProtoReasoningEffort::None, + ReasoningEffortConfig::Max => ProtoReasoningEffort::Max, ReasoningEffortConfig::Low => ProtoReasoningEffort::Low, ReasoningEffortConfig::Medium => ProtoReasoningEffort::Medium, ReasoningEffortConfig::High => ProtoReasoningEffort::High, diff --git a/code-rs/core/src/codex/streaming.rs b/code-rs/core/src/codex/streaming.rs index 7c69d1a1f106..cde54413596b 100644 --- a/code-rs/core/src/codex/streaming.rs +++ b/code-rs/core/src/codex/streaming.rs @@ -3401,6 +3401,7 @@ async fn run_turn( } Err(CodexErr::QuotaExceeded) => return Err(CodexErr::QuotaExceeded), Err(e) => { + if crate::error::non_retryable_model_error(&e) { return Err(e); } if let CodexErr::Stream(msg, _maybe_delay, _req_id) = &e && is_usage_limit_stream_error(msg) && !did_usage_limit_model_fallback diff --git a/code-rs/core/src/config.rs b/code-rs/core/src/config.rs index 4517595aa712..b5c1eaaba34e 100644 --- a/code-rs/core/src/config.rs +++ b/code-rs/core/src/config.rs @@ -20,7 +20,6 @@ use crate::config_types::Notifications; use crate::config_types::OtelConfig; use crate::config_types::OtelConfigToml; use crate::config_types::OtelExporterKind; -use crate::config_types::default_auto_drive_model_routing_entries; use crate::config_types::ProjectCommandConfig; use crate::config_types::ProjectHookConfig; use crate::config_types::SandboxWorkspaceWrite; @@ -161,11 +160,13 @@ fn normalize_auto_drive_routing_reasoning_levels( ) -> Vec { let mut normalized = Vec::new(); for level in [ + ReasoningEffort::Disabled, ReasoningEffort::Minimal, ReasoningEffort::Low, ReasoningEffort::Medium, ReasoningEffort::High, ReasoningEffort::XHigh, + ReasoningEffort::Max, ] { if levels.contains(&level) { normalized.push(level); @@ -181,10 +182,6 @@ fn normalize_auto_drive_routing_model(model: &str) -> Option { } let normalized = trimmed.to_ascii_lowercase(); - if !normalized.starts_with("gpt-") { - return None; - } - Some(normalized) } @@ -196,10 +193,6 @@ fn sanitize_auto_drive_routing_entries(entries: &mut Vec String { } fn normalize_agent(mut agent: AgentConfig) -> AgentConfig { - if let Some(spec) = agent_model_spec(&agent.name).or_else(|| agent_model_spec(&agent.command)) { + if let Some(spec) = agent_model_spec(&agent.name) { agent.name = spec.slug.to_string(); if agent.command.trim().is_empty() { agent.command = spec.cli.to_string(); @@ -42,7 +42,6 @@ fn normalize_agent(mut agent: AgentConfig) -> AgentConfig { fn canonical_agent_key(agent: &AgentConfig) -> String { agent_model_spec(&agent.name) - .or_else(|| agent_model_spec(&agent.command)) .map(|spec| spec.slug.to_ascii_lowercase()) .unwrap_or_else(|| agent.name.to_ascii_lowercase()) } diff --git a/code-rs/core/src/config_types.rs b/code-rs/core/src/config_types.rs index d6ee22e94a58..fd6c1c88d453 100644 --- a/code-rs/core/src/config_types.rs +++ b/code-rs/core/src/config_types.rs @@ -955,6 +955,12 @@ fn default_auto_drive_model_routing_reasoning_levels() -> Vec { pub fn default_auto_drive_model_routing_entries() -> Vec { vec![ + AutoDriveModelRoutingEntry { + model: "gpt-6.1-sol".to_string(), + enabled: true, + reasoning_levels: vec![ReasoningEffort::Low, ReasoningEffort::Medium, ReasoningEffort::High, ReasoningEffort::XHigh, ReasoningEffort::Max], + description: "Complex coding, investigation, planning, and review.".to_string(), + }, AutoDriveModelRoutingEntry { model: "gpt-5.5".to_string(), enabled: true, @@ -1506,14 +1512,17 @@ impl From for ShellEnvironmentPolicy { #[serde(rename_all = "lowercase")] #[strum(serialize_all = "lowercase")] pub enum ReasoningEffort { - /// Minimal reasoning. Accepts legacy value "none" for backwards compatibility. - #[serde(alias = "none")] + /// No reasoning for models that explicitly support it. + #[serde(rename = "none")] + #[strum(serialize = "none")] + Disabled, Minimal, Low, #[default] Medium, High, XHigh, + Max, /// Deprecated: previously disabled reasoning. Kept for internal use only. #[serde(skip)] None, @@ -1753,13 +1762,13 @@ impl Default for RetentionConfig { impl From for ReasoningEffort { fn from(v: code_protocol::config_types::ReasoningEffort) -> Self { match v { - code_protocol::config_types::ReasoningEffort::None => ReasoningEffort::None, + code_protocol::config_types::ReasoningEffort::None => ReasoningEffort::Disabled, code_protocol::config_types::ReasoningEffort::Minimal => ReasoningEffort::Minimal, code_protocol::config_types::ReasoningEffort::Low => ReasoningEffort::Low, code_protocol::config_types::ReasoningEffort::Medium => ReasoningEffort::Medium, code_protocol::config_types::ReasoningEffort::High => ReasoningEffort::High, + code_protocol::config_types::ReasoningEffort::Max => ReasoningEffort::Max, code_protocol::config_types::ReasoningEffort::XHigh - | code_protocol::config_types::ReasoningEffort::Max | code_protocol::config_types::ReasoningEffort::Ultra | code_protocol::config_types::ReasoningEffort::Persistent => ReasoningEffort::XHigh, code_protocol::config_types::ReasoningEffort::Custom(_) => ReasoningEffort::Medium, @@ -1770,6 +1779,8 @@ impl From for ReasoningEffort { impl From for code_protocol::config_types::ReasoningEffort { fn from(v: ReasoningEffort) -> Self { match v { + ReasoningEffort::Disabled => code_protocol::config_types::ReasoningEffort::None, + ReasoningEffort::Max => code_protocol::config_types::ReasoningEffort::Max, ReasoningEffort::Minimal | ReasoningEffort::None => { code_protocol::config_types::ReasoningEffort::Minimal } diff --git a/code-rs/core/src/default_client.rs b/code-rs/core/src/default_client.rs index 312f9c1cb765..56554324d9b6 100644 --- a/code-rs/core/src/default_client.rs +++ b/code-rs/core/src/default_client.rs @@ -122,6 +122,16 @@ pub fn requested_model_headers(originator_override: Option<&str>, model: &str) - headers } +pub fn requested_codex_backend_model_headers(originator: Option<&str>, model: &str) -> HeaderMap { + let version = code_version::codex_backend_version_for_model(model); + let suffix = USER_AGENT_SUFFIX.lock().ok().and_then(|guard| guard.clone()); + let user_agent = get_code_user_agent_with_version(originator, suffix.as_deref(), &version); + let mut headers = requested_model_headers(originator, model); + headers.insert("version", HeaderValue::from_str(&version).expect("verified backend version")); + headers.insert(reqwest::header::USER_AGENT, HeaderValue::from_str(&user_agent).expect("backend user agent")); + headers +} + pub fn get_code_user_agent_with_suffix( originator_override: Option<&str>, user_agent_suffix: Option<&str>, diff --git a/code-rs/core/src/error.rs b/code-rs/core/src/error.rs index 4e50f398a03c..6b695c1b47c7 100644 --- a/code-rs/core/src/error.rs +++ b/code-rs/core/src/error.rs @@ -505,3 +505,14 @@ mod tests { ); } } + +/// Reject invalid requests before outer turn retries or transport fallback. +pub(crate) fn non_retryable_model_error(error: &CodexErr) -> bool { + match error { + CodexErr::UnexpectedStatus(error) => error.status.is_client_error() + && error.status != reqwest::StatusCode::TOO_MANY_REQUESTS, + CodexErr::RetryLimit(error) => !error.retryable, + CodexErr::UnsupportedOperation(_) | CodexErr::AuthRefreshPermanent(_) => true, + _ => false, + } +} diff --git a/code-rs/core/src/lib.rs b/code-rs/core/src/lib.rs index ad9cfc36ead4..e04aed9981ad 100644 --- a/code-rs/core/src/lib.rs +++ b/code-rs/core/src/lib.rs @@ -38,6 +38,7 @@ pub mod debug_logger; pub mod review_coord; mod environment_context; mod reasoning; +pub mod modern_models; pub mod retention; pub mod telemetry; pub mod timeboxed_exec_guidance; diff --git a/code-rs/core/src/model_family.rs b/code-rs/core/src/model_family.rs index 4a08abb61d22..d39cd87e8ff6 100644 --- a/code-rs/core/src/model_family.rs +++ b/code-rs/core/src/model_family.rs @@ -264,13 +264,13 @@ fn apply_upstream_model_overrides(mut family: ModelFamily) -> ModelFamily { .default_reasoning_level .as_ref() .map(|effort| match effort { - code_protocol::openai_models::ReasoningEffort::None - | code_protocol::openai_models::ReasoningEffort::Minimal => ReasoningEffort::Minimal, + code_protocol::openai_models::ReasoningEffort::None => ReasoningEffort::Disabled, + code_protocol::openai_models::ReasoningEffort::Minimal => ReasoningEffort::Minimal, code_protocol::openai_models::ReasoningEffort::Low => ReasoningEffort::Low, code_protocol::openai_models::ReasoningEffort::Medium => ReasoningEffort::Medium, code_protocol::openai_models::ReasoningEffort::High => ReasoningEffort::High, + code_protocol::openai_models::ReasoningEffort::Max => ReasoningEffort::Max, code_protocol::openai_models::ReasoningEffort::XHigh - | code_protocol::openai_models::ReasoningEffort::Max | code_protocol::openai_models::ReasoningEffort::Ultra | code_protocol::openai_models::ReasoningEffort::Persistent => ReasoningEffort::XHigh, code_protocol::openai_models::ReasoningEffort::Custom(_) => ReasoningEffort::Medium, @@ -321,6 +321,19 @@ pub fn find_family_for_model(slug: &str) -> Option { return Some(family); } + if crate::modern_models::modern_model(slug).is_some() { + return model_family!( + slug, slug, + supports_reasoning_summaries: true, + default_reasoning_effort: Some(ReasoningEffort::Medium), + supports_parallel_tool_calls: true, + uses_shell_command_tool: true, + apply_patch_tool_type: Some(ApplyPatchToolType::Freeform), + max_output_tokens: Some(crate::modern_models::PUBLIC_MAX_OUTPUT_TOKENS), + truncation_policy: TruncationPolicy::Tokens(10_000), + ); + } + let normalized_slug = slug.to_ascii_lowercase(); if normalized_slug.starts_with("qwen3-coder-30b-a3b-instruct") { @@ -548,7 +561,12 @@ pub fn find_family_for_model(slug: &str) -> Option { } pub fn derive_default_model_family(model: &str) -> ModelFamily { - apply_upstream_model_overrides(ModelFamily { + apply_upstream_model_overrides(derive_provider_model_family(model)) +} + +/// Unknown proxy capabilities must not be inferred from an OpenAI model name. +pub fn derive_provider_model_family(model: &str) -> ModelFamily { + ModelFamily { slug: model.to_string(), family: model.to_string(), needs_special_apply_patch_instructions: false, @@ -572,7 +590,7 @@ pub fn derive_default_model_family(model: &str) -> ModelFamily { supports_image_generation: false, base_instructions: BASE_INSTRUCTIONS.to_string(), content_filter_guidance: CONTENT_FILTER_GUIDANCE.to_string(), - }) + } } fn supports_image_generation(model_info: &ModelInfo) -> bool { @@ -588,6 +606,15 @@ mod tests { use super::parse_upstream_models; use super::supports_extended_context; + #[test] + fn gpt_6_1_sol_has_execution_metadata() { + let family = find_family_for_model("gpt-6.1-sol").expect("priority model resolves"); + assert_eq!(family.slug, "gpt-6.1-sol"); + assert!(family.supports_reasoning_summaries); + assert!(family.use_responses_lite); + assert_eq!(family.max_output_tokens, Some(128_000)); + } + #[test] fn image_generation_support_tracks_image_input_modality() { let family = find_family_for_model("gpt-5.4").expect("known upstream model"); @@ -632,7 +659,7 @@ mod tests { assert!(supports_extended_context(model), "{model} should support 1M context"); } - assert!(!supports_extended_context("gpt-5.6-terra")); + assert!(supports_extended_context("gpt-5.6-terra")); assert!(!supports_extended_context("gpt-5.5")); } @@ -681,9 +708,10 @@ pub const fn default_auto_compact_limit_for_context_window(context_window: u64) } pub fn supports_extended_context(model: &str) -> bool { - ["gpt-5.4", "gpt-5.6-luna", "gpt-5.6-sol"] + ["gpt-5.4"] .iter() .any(|supported| model.eq_ignore_ascii_case(supported)) + || crate::modern_models::modern_model(model).is_some() } pub fn resolve_context_mode_limits( @@ -692,7 +720,11 @@ pub fn resolve_context_mode_limits( family: &ModelFamily, ) -> (Option, Option) { match mode { - Some(ContextMode::OneM | ContextMode::Auto) if supports_extended_context(model) => ( + Some(ContextMode::OneM | ContextMode::Auto) if crate::modern_models::modern_model(model).is_some() && family.context_window.is_some() => ( + Some(crate::modern_models::PUBLIC_CONTEXT_WINDOW), + Some(default_auto_compact_limit_for_context_window(crate::modern_models::PUBLIC_MAX_INPUT_TOKENS)), + ), + Some(ContextMode::OneM | ContextMode::Auto) if model.eq_ignore_ascii_case("gpt-5.4") => ( Some(EXTENDED_CONTEXT_WINDOW_1M), Some(default_auto_compact_limit_for_context_window( EXTENDED_CONTEXT_WINDOW_1M, diff --git a/code-rs/core/src/model_provider_info.rs b/code-rs/core/src/model_provider_info.rs index f9aee2bc79aa..efb23d2c132e 100644 --- a/code-rs/core/src/model_provider_info.rs +++ b/code-rs/core/src/model_provider_info.rs @@ -503,6 +503,17 @@ impl ModelProviderInfo { .map_or(false, |base| base.contains("/backend-api")) } + /// First-party catalog provenance, independent of model slug or display name. + pub fn uses_official_openai_model_catalog(&self) -> bool { + if !matches!(self.wire_api, WireApi::Responses | WireApi::ResponsesWebsocket) { return false; } + self.base_url.as_ref().map_or(true, |base| { + reqwest::Url::parse(base).ok().is_some_and(|url| { + url.scheme() == "https" && (url.host_str() == Some("api.openai.com") + || (url.host_str() == Some("chatgpt.com") && url.path().starts_with("/backend-api/codex"))) + }) + }) + } + pub(crate) fn is_public_openai_responses_endpoint(&self) -> bool { if !matches!(self.wire_api, WireApi::Responses | WireApi::ResponsesWebsocket) { return false; diff --git a/code-rs/core/src/modern_models.rs b/code-rs/core/src/modern_models.rs new file mode 100644 index 000000000000..b9a2acde5a3f --- /dev/null +++ b/code-rs/core/src/modern_models.rs @@ -0,0 +1,47 @@ +//! Public OpenAI API specifications verified 2026-10-09. OAuth transport +//! metadata comes from the separate Codex catalog; it is not an API contract. +//! Sources: https://developers.openai.com/api/docs/models/ +use crate::config_types::ReasoningEffort; + +#[derive(Debug)] +pub struct ModernModel { + pub id: &'static str, + pub display_name: &'static str, + pub description: &'static str, + pub supports_no_reasoning: bool, +} + +pub const PUBLIC_CONTEXT_WINDOW: u64 = 1_050_000; +pub const PUBLIC_MAX_INPUT_TOKENS: u64 = 922_000; +pub const PUBLIC_MAX_OUTPUT_TOKENS: u64 = 128_000; +// Observed Codex backend limit, distinct from the public API window. +pub const CODEX_MAX_CONTEXT_WINDOW: u64 = 872_000; + +pub const MODERN_MODELS: &[ModernModel] = &[ + ModernModel { id: "gpt-6.1-sol", display_name: "GPT-6.1 Sol", description: "Complex coding and professional work at lower cost than Astra.", supports_no_reasoning: false }, + ModernModel { id: "gpt-6-astra", display_name: "GPT-6 Astra", description: "Demanding reasoning, coding, and research.", supports_no_reasoning: false }, + ModernModel { id: "gpt-6-sol", display_name: "GPT-6 Sol", description: "Coding and agentic workflows.", supports_no_reasoning: true }, + ModernModel { id: "gpt-6-luna", display_name: "GPT-6 Luna", description: "Focused, high-volume tasks at low cost.", supports_no_reasoning: true }, + ModernModel { id: "gpt-5.6-sol", display_name: "GPT-5.6 Sol", description: "Flagship GPT-5.6 for complex professional work.", supports_no_reasoning: true }, + ModernModel { id: "gpt-5.6-terra", display_name: "GPT-5.6 Terra", description: "Balances intelligence and cost.", supports_no_reasoning: true }, + ModernModel { id: "gpt-5.6-luna", display_name: "GPT-5.6 Luna", description: "Cost-sensitive, high-volume workloads.", supports_no_reasoning: true }, +]; + +const REASONING: &[ReasoningEffort] = &[ + ReasoningEffort::Low, ReasoningEffort::Medium, ReasoningEffort::High, + ReasoningEffort::XHigh, ReasoningEffort::Max, +]; +const OPTIONAL_REASONING: &[ReasoningEffort] = &[ + ReasoningEffort::Disabled, ReasoningEffort::Low, ReasoningEffort::Medium, + ReasoningEffort::High, ReasoningEffort::XHigh, ReasoningEffort::Max, +]; + +pub fn modern_model(model: &str) -> Option<&'static ModernModel> { + MODERN_MODELS.iter().find(|spec| spec.id.eq_ignore_ascii_case(model)) +} + +pub fn reasoning_efforts(model: &str) -> Option<&'static [ReasoningEffort]> { + modern_model(model).map(|spec| { + if spec.supports_no_reasoning { OPTIONAL_REASONING } else { REASONING } + }) +} diff --git a/code-rs/core/src/reasoning.rs b/code-rs/core/src/reasoning.rs index 85fed8e60b23..ac34e556aa3b 100644 --- a/code-rs/core/src/reasoning.rs +++ b/code-rs/core/src/reasoning.rs @@ -64,16 +64,19 @@ const DEFAULT_EFFORTS: &[ReasoningEffort] = &[ fn reasoning_effort_rank(effort: ReasoningEffort) -> u8 { match effort { - ReasoningEffort::Minimal => 0, + ReasoningEffort::Disabled | ReasoningEffort::Minimal => 0, ReasoningEffort::Low => 1, ReasoningEffort::Medium => 2, ReasoningEffort::High => 3, ReasoningEffort::XHigh => 4, - ReasoningEffort::None => 5, + ReasoningEffort::Max | ReasoningEffort::None => 5, } } pub fn supported_reasoning_efforts_for_model(model: &str) -> &'static [ReasoningEffort] { + if let Some(efforts) = crate::modern_models::reasoning_efforts(model) { + return efforts; + } let lower = model.to_ascii_lowercase(); if lower.starts_with("gpt-5.1-codex-max") { @@ -144,6 +147,14 @@ pub fn clamp_reasoning_effort_for_model( mod tests { use super::*; + #[test] + fn modern_models_preserve_exact_max_effort() { + let max: ReasoningEffort = serde_json::from_str("\"max\"").expect("max effort parses"); + for model in ["gpt-5.6-luna", "gpt-5.6-terra", "gpt-5.6-sol", "gpt-6-luna", "gpt-6-sol", "gpt-6-astra", "gpt-6.1-sol"] { + assert_eq!(clamp_reasoning_effort_for_model(model, max), max, "{model}"); + } + } + #[test] fn clamps_xhigh_to_high_for_gpt5() { let clamped = clamp_reasoning_effort_for_model("gpt-5", ReasoningEffort::XHigh); diff --git a/code-rs/core/src/remote_models/mod.rs b/code-rs/core/src/remote_models/mod.rs index c012998ae46c..4b0f77d0402b 100644 --- a/code-rs/core/src/remote_models/mod.rs +++ b/code-rs/core/src/remote_models/mod.rs @@ -418,7 +418,11 @@ impl RemoteModelsManager { pairs.append_pair(k, v); } } - pairs.append_pair("client_version", &format_client_version_to_whole()); + let version = if self.provider.uses_official_openai_model_catalog() + && auth.as_ref().is_some_and(CodexAuth::uses_codex_backend) { + code_version::codex_backend_version_for_model("gpt-6.1-sol") + } else { format_client_version_to_whole() }; + pairs.append_pair("client_version", &version); } Ok(url) @@ -658,13 +662,13 @@ fn map_reasoning_effort(effort: &ProtocolReasoningEffort) -> crate::config_types use crate::config_types::ReasoningEffort as LocalEffort; match effort { - ProtocolReasoningEffort::None => LocalEffort::None, + ProtocolReasoningEffort::None => LocalEffort::Disabled, ProtocolReasoningEffort::Minimal => LocalEffort::Minimal, ProtocolReasoningEffort::Low => LocalEffort::Low, ProtocolReasoningEffort::Medium => LocalEffort::Medium, ProtocolReasoningEffort::High => LocalEffort::High, + ProtocolReasoningEffort::Max => LocalEffort::Max, ProtocolReasoningEffort::XHigh - | ProtocolReasoningEffort::Max | ProtocolReasoningEffort::Ultra | ProtocolReasoningEffort::Persistent => LocalEffort::XHigh, ProtocolReasoningEffort::Custom(_) => LocalEffort::Medium, diff --git a/code-rs/core/src/slash_commands.rs b/code-rs/core/src/slash_commands.rs index efaf1d89b970..88f89a2e61da 100644 --- a/code-rs/core/src/slash_commands.rs +++ b/code-rs/core/src/slash_commands.rs @@ -334,7 +334,7 @@ mod tests { } let defaults = get_default_models(); - assert!(defaults.iter().any(|v| v == "claude-sonnet-4.6")); + assert!(defaults.iter().any(|v| v == "claude-sonnet-5-5")); restore_var("PATH", orig_path); restore_var("HOME", orig_home); diff --git a/code-rs/tui/src/app/events.rs b/code-rs/tui/src/app/events.rs index 04dd2540b331..4ddd4de3eca4 100644 --- a/code-rs/tui/src/app/events.rs +++ b/code-rs/tui/src/app/events.rs @@ -57,6 +57,8 @@ impl App<'_> { let remote_tx = self.app_event_tx.clone(); let remote_auth_manager = self._server.auth_manager(); let remote_provider = self.config.model_provider.clone(); + let remote_first_party = self.config.model_provider_id == "openai" + && remote_provider.uses_official_openai_model_catalog(); let remote_code_home = self.config.code_home.clone(); let remote_using_chatgpt_hint = self.config.using_chatgpt_auth; tokio::spawn(async move { @@ -86,11 +88,12 @@ impl App<'_> { auth_mode, supports_pro_only_models, ); - let presets = crate::remote_model_presets::merge_remote_models( + let presets = crate::remote_model_presets::merge_remote_models_for_provider( remote_models, presets, auth_mode, supports_pro_only_models, + remote_first_party, ); let default_model = remote_manager.default_model_slug(auth_mode).await; remote_tx.send(AppEvent::ModelPresetsUpdated { diff --git a/code-rs/tui/src/app/init.rs b/code-rs/tui/src/app/init.rs index 37a02999ecdb..cd6d9124c63c 100644 --- a/code-rs/tui/src/app/init.rs +++ b/code-rs/tui/src/app/init.rs @@ -61,6 +61,7 @@ impl App<'_> { let remote_tx = app_event_tx.clone(); let remote_auth_manager = auth_manager.clone(); let remote_provider = config.model_provider.clone(); + let remote_first_party = config.model_provider_id == "openai" && remote_provider.uses_official_openai_model_catalog(); let remote_code_home = config.code_home.clone(); let remote_using_chatgpt_hint = config.using_chatgpt_auth; if !crate::chatwidget::is_test_mode() { @@ -91,11 +92,12 @@ impl App<'_> { auth_mode, supports_pro_only_models, ); - let presets = crate::remote_model_presets::merge_remote_models( + let presets = crate::remote_model_presets::merge_remote_models_for_provider( remote_models, presets, auth_mode, supports_pro_only_models, + remote_first_party, ); let default_model = remote_manager.default_model_slug(auth_mode).await; remote_tx.send(AppEvent::ModelPresetsUpdated { diff --git a/code-rs/tui/src/bottom_pane/auto_drive_settings_view.rs b/code-rs/tui/src/bottom_pane/auto_drive_settings_view.rs index ded99a2c452a..32441336d8db 100644 --- a/code-rs/tui/src/bottom_pane/auto_drive_settings_view.rs +++ b/code-rs/tui/src/bottom_pane/auto_drive_settings_view.rs @@ -4,7 +4,6 @@ use crate::colors; use code_core::config_types::{ AutoDriveModelRoutingEntry, ReasoningEffort, - default_auto_drive_model_routing_entries, }; use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers}; use ratatui::buffer::Buffer; @@ -18,12 +17,14 @@ use super::bottom_pane_view::{BottomPaneView, ConditionalUpdate}; use super::settings_panel::{PanelFrameStyle, render_panel}; use super::BottomPane; -const ROUTING_REASONING_LEVELS: [ReasoningEffort; 5] = [ +const ROUTING_REASONING_LEVELS: [ReasoningEffort; 7] = [ + ReasoningEffort::Disabled, ReasoningEffort::Minimal, ReasoningEffort::Low, ReasoningEffort::Medium, ReasoningEffort::High, ReasoningEffort::XHigh, + ReasoningEffort::Max, ]; const ROUTING_DESCRIPTION_MAX_CHARS: usize = 200; @@ -86,7 +87,7 @@ struct RoutingEditorState { model_cursor: usize, enabled: bool, reasoning_cursor: usize, - reasoning_enabled: [bool; 5], + reasoning_enabled: [bool; ROUTING_REASONING_LEVELS.len()], description: String, selected_field: RoutingEditorField, } @@ -97,7 +98,7 @@ impl RoutingEditorState { entry: Option<&AutoDriveModelRoutingEntry>, model_options: &[String], ) -> Self { - let mut reasoning_enabled = [false; 5]; + let mut reasoning_enabled = [false; ROUTING_REASONING_LEVELS.len()]; let mut model_cursor = 0; let mut enabled = true; let mut description = String::new(); @@ -121,11 +122,16 @@ impl RoutingEditorState { reasoning_enabled[high_idx] = true; } + let supported = model_options.get(model_cursor) + .and_then(|model| code_core::modern_models::reasoning_efforts(model)); + let reasoning_cursor = ROUTING_REASONING_LEVELS.iter() + .position(|level| supported.is_none_or(|levels| levels.contains(level))) + .unwrap_or(0); Self { index, model_cursor, enabled, - reasoning_cursor: 0, + reasoning_cursor, reasoning_enabled, description, selected_field: RoutingEditorField::Model, @@ -190,11 +196,7 @@ impl AutoDriveSettingsView { ) -> Self { let diagnostics_enabled = qa_automation_enabled && (review_enabled || cross_check_enabled); let normalized_entries = Self::sanitize_routing_entries(model_routing_entries); - let model_routing_entries = if normalized_entries.is_empty() { - default_auto_drive_model_routing_entries() - } else { - normalized_entries - }; + let model_routing_entries = normalized_entries; let routing_model_options = Self::build_routing_model_options(routing_model_options, &model_routing_entries); @@ -246,10 +248,6 @@ impl AutoDriveSettingsView { } let normalized = trimmed.to_ascii_lowercase(); - if !normalized.starts_with("gpt-") { - return None; - } - Some(normalized) } @@ -272,9 +270,6 @@ impl AutoDriveSettingsView { continue; }; let reasoning_levels = Self::normalize_routing_reasoning_levels(&entry.reasoning_levels); - if reasoning_levels.is_empty() { - continue; - } normalized_entries.push(AutoDriveModelRoutingEntry { model, @@ -375,6 +370,8 @@ impl AutoDriveSettingsView { ReasoningEffort::Medium => "Medium", ReasoningEffort::Low => "Low", ReasoningEffort::Minimal => "Minimal", + ReasoningEffort::Max => "Max", + ReasoningEffort::Disabled => "None", ReasoningEffort::None => "None", } } @@ -465,9 +462,6 @@ impl AutoDriveSettingsView { } self.model_routing_entries.remove(index); - if self.model_routing_entries.is_empty() { - self.model_routing_entries = default_auto_drive_model_routing_entries(); - } let max_row = self.routing_row_count().saturating_sub(1); self.routing_selected_index = self.routing_selected_index.min(max_row); self.send_update(); @@ -750,9 +744,11 @@ impl AutoDriveSettingsView { format!("Enabled: {}", if editor.enabled { "Yes" } else { "No" }), )); + let supported = code_core::modern_models::reasoning_efforts(&model); let reasoning_parts = ROUTING_REASONING_LEVELS .iter() .enumerate() + .filter(|(_, level)| supported.is_none_or(|levels| levels.contains(level))) .map(|(idx, level)| { let cursor = if editor.reasoning_cursor == idx { ">" @@ -994,6 +990,13 @@ impl AutoDriveSettingsView { return; } + if let Some(supported) = code_core::modern_models::reasoning_efforts(&model) { + if reasoning_levels.iter().any(|level| !supported.contains(level)) { + self.set_status_message(format!("Select reasoning levels supported by {model}.")); + return; + } + } + let entry = AutoDriveModelRoutingEntry { model, enabled: editor.enabled, @@ -1014,7 +1017,7 @@ impl AutoDriveSettingsView { let sanitized = Self::sanitize_routing_entries(updated_entries); if sanitized.is_empty() { - self.set_status_message("At least one valid gpt-* routing entry is required."); + self.set_status_message("At least one named routing entry is required."); return; } if self.model_routing_enabled && !sanitized.iter().any(|entry| entry.enabled) { @@ -1080,6 +1083,10 @@ impl AutoDriveSettingsView { let mut request_cancel = false; let has_models = !self.routing_model_options.is_empty(); let model_options_len = self.routing_model_options.len(); + let supported = if let AutoDriveSettingsMode::RoutingEditor(editor) = &self.mode { + self.routing_model_options.get(editor.model_cursor) + .and_then(|model| code_core::modern_models::reasoning_efforts(model)) + } else { None }; self.update_routing_editor(|editor| match editor.selected_field { RoutingEditorField::Model => match key_event.code { @@ -1117,15 +1124,23 @@ impl AutoDriveSettingsView { } else { editor.reasoning_cursor -= 1; } + while supported.is_some_and(|levels| !levels.contains(&ROUTING_REASONING_LEVELS[editor.reasoning_cursor])) { + editor.reasoning_cursor = (editor.reasoning_cursor + ROUTING_REASONING_LEVELS.len() - 1) % ROUTING_REASONING_LEVELS.len(); + } handled = true; } KeyCode::Right => { editor.reasoning_cursor = (editor.reasoning_cursor + 1) % ROUTING_REASONING_LEVELS.len(); + while supported.is_some_and(|levels| !levels.contains(&ROUTING_REASONING_LEVELS[editor.reasoning_cursor])) { + editor.reasoning_cursor = (editor.reasoning_cursor + 1) % ROUTING_REASONING_LEVELS.len(); + } handled = true; } KeyCode::Enter | KeyCode::Char(' ') => { - editor.toggle_reasoning_at_cursor(); + if supported.is_none_or(|levels| levels.contains(&ROUTING_REASONING_LEVELS[editor.reasoning_cursor])) { + editor.toggle_reasoning_at_cursor(); + } handled = true; } _ => {} @@ -1267,6 +1282,35 @@ mod tests { use super::*; use std::sync::mpsc::channel; + #[test] + fn routing_editor_preserves_max_and_explicit_empty_routes() { + let entry = AutoDriveModelRoutingEntry { + model: "gpt-6.1-sol".into(), enabled: true, + reasoning_levels: vec![ReasoningEffort::Max], description: String::new(), + }; + let editor = RoutingEditorState::from_entry(Some(0), Some(&entry), &[entry.model.clone()]); + assert_eq!(editor.selected_reasoning_levels(), vec![ReasoningEffort::Max]); + assert!(build_view(false, vec![]).model_routing_entries.is_empty()); + } + + #[test] + fn priority_routing_editor_skips_unsupported_efforts() { + let mut view = build_view(true, vec![AutoDriveModelRoutingEntry { + model: "gpt-6.1-sol".into(), enabled: true, + reasoning_levels: vec![ReasoningEffort::Max], description: String::new(), + }]); + view.open_routing_editor(Some(0)); + if let AutoDriveSettingsMode::RoutingEditor(editor) = &mut view.mode { + editor.selected_field = RoutingEditorField::Reasoning; + } + for _ in 0..10 { + view.handle_key_event_direct(key(KeyCode::Right)); + let AutoDriveSettingsMode::RoutingEditor(editor) = &view.mode else { panic!("editor"); }; + assert!(code_core::modern_models::reasoning_efforts("gpt-6.1-sol").unwrap() + .contains(&ROUTING_REASONING_LEVELS[editor.reasoning_cursor])); + } + } + fn key(code: KeyCode) -> KeyEvent { KeyEvent::new(code, KeyModifiers::NONE) } @@ -1388,7 +1432,7 @@ mod tests { view.handle_key_event_direct(key(KeyCode::Down)); view.handle_key_event_direct(key(KeyCode::Down)); - for _ in 0..3 { + for _ in 0..ROUTING_REASONING_LEVELS.iter().position(|level| *level == ReasoningEffort::High).unwrap() { view.handle_key_event_direct(key(KeyCode::Right)); } view.handle_key_event_direct(key(KeyCode::Enter)); diff --git a/code-rs/tui/src/bottom_pane/model_selection_view.rs b/code-rs/tui/src/bottom_pane/model_selection_view.rs index 52e5bc4b5341..afa0b12f4300 100644 --- a/code-rs/tui/src/bottom_pane/model_selection_view.rs +++ b/code-rs/tui/src/bottom_pane/model_selection_view.rs @@ -60,6 +60,8 @@ impl FlatPreset { ReasoningEffort::Medium => "Medium", ReasoningEffort::Low => "Low", ReasoningEffort::Minimal => "Minimal", + ReasoningEffort::Max => "Max", + ReasoningEffort::Disabled => "None", ReasoningEffort::None => "None", } } @@ -993,6 +995,8 @@ impl ModelSelectionView { ReasoningEffort::Medium => 2, ReasoningEffort::Low => 3, ReasoningEffort::Minimal => 4, + ReasoningEffort::Max => 5, + ReasoningEffort::Disabled => 0, ReasoningEffort::None => 5, } } @@ -1004,6 +1008,8 @@ impl ModelSelectionView { ReasoningEffort::Medium => "Medium", ReasoningEffort::Low => "Low", ReasoningEffort::Minimal => "Minimal", + ReasoningEffort::Max => "Max", + ReasoningEffort::Disabled => "None", ReasoningEffort::None => "None", } } diff --git a/code-rs/tui/src/bottom_pane/planning_settings_view.rs b/code-rs/tui/src/bottom_pane/planning_settings_view.rs index 237a9a17dafe..f58cba0babef 100644 --- a/code-rs/tui/src/bottom_pane/planning_settings_view.rs +++ b/code-rs/tui/src/bottom_pane/planning_settings_view.rs @@ -135,6 +135,8 @@ impl PlanningSettingsView { ReasoningEffort::Medium => "Medium", ReasoningEffort::Low => "Low", ReasoningEffort::Minimal => "Minimal", + ReasoningEffort::Max => "Max", + ReasoningEffort::Disabled => "None", ReasoningEffort::None => "None", } } diff --git a/code-rs/tui/src/bottom_pane/review_settings_view.rs b/code-rs/tui/src/bottom_pane/review_settings_view.rs index 83248b1d5fde..829aca187702 100644 --- a/code-rs/tui/src/bottom_pane/review_settings_view.rs +++ b/code-rs/tui/src/bottom_pane/review_settings_view.rs @@ -319,6 +319,8 @@ impl ReviewSettingsView { ReasoningEffort::Medium => "Medium", ReasoningEffort::Low => "Low", ReasoningEffort::Minimal => "Minimal", + ReasoningEffort::Max => "Max", + ReasoningEffort::Disabled => "None", ReasoningEffort::None => "None", } } diff --git a/code-rs/tui/src/chatwidget.rs b/code-rs/tui/src/chatwidget.rs index 26f2c3edfbc4..87ade5317d19 100644 --- a/code-rs/tui/src/chatwidget.rs +++ b/code-rs/tui/src/chatwidget.rs @@ -23089,10 +23089,15 @@ Have we met every part of this goal and is there no further work to do?"# } fn curated_model_presets(presets: Vec) -> Vec { - const MODEL_PICKER_ORDER: [&str; 5] = [ + const MODEL_PICKER_ORDER: [&str; 10] = [ + "gpt-6.1-sol", "gpt-5.5", "gpt-5.6-terra", "gpt-5.6-luna", + "gpt-5.6-sol", + "gpt-6-astra", + "gpt-6-sol", + "gpt-6-luna", "gpt-5.3-codex", "gpt-5.3-codex-spark", ]; @@ -23135,10 +23140,18 @@ Have we met every part of this goal and is there no further work to do?"# } else { Some(AuthMode::ApiKey) }; - builtin_model_presets( + let mut presets = builtin_model_presets( auth_mode, self.auth_manager.supports_pro_only_models(), - ) + ); + // Local first-party entries are not an advertisement by a custom + // endpoint. Users can configure an exact custom model explicitly. + let first_party = self.config.model_provider_id == "openai" + && self.config.model_provider.uses_official_openai_model_catalog(); + if !first_party { + presets.retain(|preset| code_core::modern_models::modern_model(&preset.model).is_none()); + } + presets } fn available_model_presets(&self) -> Vec { @@ -23523,11 +23536,20 @@ Have we met every part of this goal and is there no further work to do?"# } fn sync_session_context_selection_for_model(&mut self) -> bool { - let (next_context_window, next_auto_compact) = resolve_context_mode_limits( + let (mut next_context_window, mut next_auto_compact) = resolve_context_mode_limits( &self.config.model, - self.config.context_mode, + if self.config.model_provider_id == "openai" && self.config.model_provider.uses_official_openai_model_catalog() { + self.config.context_mode + } else { Some(ContextMode::Disabled) }, &self.config.model_family, ); + if self.config.using_chatgpt_auth && self.config.model_provider_id == "openai" + && self.config.model_provider.uses_official_openai_model_catalog() + && code_core::modern_models::modern_model(&self.config.model).is_some() + { + next_context_window = next_context_window.map(|limit| limit.min(code_core::modern_models::CODEX_MAX_CONTEXT_WINDOW)); + next_auto_compact = next_auto_compact.map(|limit| limit.min((code_core::modern_models::CODEX_MAX_CONTEXT_WINDOW * 9 / 10) as i64)); + } let changed = self.config.model_context_window != next_context_window || self.config.model_auto_compact_token_limit != next_auto_compact; self.config.model_context_window = next_context_window; @@ -23586,13 +23608,7 @@ Have we met every part of this goal and is there no further work to do?"# } self.config.context_mode = context_mode; - let (next_context_window, next_auto_compact) = resolve_context_mode_limits( - &self.config.model, - self.config.context_mode, - &self.config.model_family, - ); - self.config.model_context_window = next_context_window; - self.config.model_auto_compact_token_limit = next_auto_compact; + self.sync_session_context_selection_for_model(); self.submit_configure_session_op(); self.refresh_bottom_pane_token_usage(); @@ -23652,6 +23668,8 @@ Have we met every part of this goal and is there no further work to do?"# ReasoningEffort::Medium => 2, ReasoningEffort::High => 3, ReasoningEffort::XHigh => 4, + ReasoningEffort::Max => 5, + ReasoningEffort::Disabled => 0, ReasoningEffort::None => 5, } } @@ -23686,16 +23704,18 @@ Have we met every part of this goal and is there no further work to do?"# fn sanitize_auto_drive_routing_entries( entries: Vec, - available_models: &[String], + _available_models: &[String], ) -> Vec { fn normalize_reasoning(levels: &[ReasoningEffort]) -> Vec { let mut normalized = Vec::new(); for level in [ + ReasoningEffort::Disabled, ReasoningEffort::Minimal, ReasoningEffort::Low, ReasoningEffort::Medium, ReasoningEffort::High, ReasoningEffort::XHigh, + ReasoningEffort::Max, ] { if levels.contains(&level) { normalized.push(level); @@ -23707,21 +23727,7 @@ Have we met every part of this goal and is there no further work to do?"# let mut normalized_entries = Vec::new(); for entry in entries { let model = entry.model.trim().to_ascii_lowercase(); - if !model.starts_with("gpt-") { - continue; - } - - if !available_models - .iter() - .any(|available| available.eq_ignore_ascii_case(&model)) - { - continue; - } - let reasoning_levels = normalize_reasoning(&entry.reasoning_levels); - if reasoning_levels.is_empty() { - continue; - } normalized_entries.push(AutoDriveModelRoutingEntry { model, @@ -23731,19 +23737,6 @@ Have we met every part of this goal and is there no further work to do?"# }); } - if normalized_entries.is_empty() { - let mut defaults = code_core::config_types::default_auto_drive_model_routing_entries(); - defaults.retain(|entry| { - available_models - .iter() - .any(|available| available.eq_ignore_ascii_case(&entry.model)) - }); - if defaults.is_empty() { - return Vec::new(); - } - return defaults; - } - normalized_entries } @@ -23767,8 +23760,10 @@ Have we met every part of this goal and is there no further work to do?"# let mut updated = false; if !self.config.model.eq_ignore_ascii_case(trimmed) { self.config.model = trimmed.to_string(); - let family = find_family_for_model(&self.config.model) - .unwrap_or_else(|| derive_default_model_family(&self.config.model)); + let family = if code_core::modern_models::modern_model(&self.config.model).is_some() + && !(self.config.model_provider_id == "openai" && self.config.model_provider.uses_official_openai_model_catalog()) + { code_core::model_family::derive_provider_model_family(&self.config.model) } + else { find_family_for_model(&self.config.model).unwrap_or_else(|| derive_default_model_family(&self.config.model)) }; self.config.model_family = family; updated = true; } @@ -25621,6 +25616,8 @@ Have we met every part of this goal and is there no further work to do?"# fn format_reasoning_effort(effort: ReasoningEffort) -> &'static str { match effort { + ReasoningEffort::Disabled => "None", + ReasoningEffort::Max => "Max", ReasoningEffort::Minimal | ReasoningEffort::None => "Minimal", ReasoningEffort::Low => "Low", ReasoningEffort::Medium => "Medium", @@ -30958,6 +30955,17 @@ impl Drop for AutoReviewStubGuard { #[cfg(test)] mod tests { use super::*; + #[test] + fn routing_preserves_unavailable_models_and_max_effort() { + let entries = ChatWidget::sanitize_auto_drive_routing_entries(vec![AutoDriveModelRoutingEntry { + model: "gpt-6-unavailable".into(), enabled: true, + reasoning_levels: vec![ReasoningEffort::Max], description: String::new(), + }], &["gpt-6.1-sol".into()]); + assert_eq!(entries[0].model, "gpt-6-unavailable"); + assert_eq!(entries[0].reasoning_levels, vec![ReasoningEffort::Max]); + assert!(ChatWidget::sanitize_auto_drive_routing_entries(vec![], &[]).is_empty()); + } + use super::{ CAPTURE_AUTO_TURN_COMMIT_STUB, GIT_DIFF_NAME_ONLY_BETWEEN_STUB, @@ -31572,6 +31580,23 @@ use code_core::protocol::OrderMeta; assert_eq!(reloaded.service_tier, None); } + #[test] + fn priority_model_selection_preserves_exact_id_and_oauth_limits() { + let _runtime_guard = enter_test_runtime_guard(); + let mut harness = ChatWidgetHarness::new(); + let chat = harness.chat(); + chat.config.using_chatgpt_auth = true; + chat.config.model_provider_id = "openai".into(); + chat.config.model_provider.base_url = None; + chat.handle_model_command("gpt-6.1-sol".into()); + assert_eq!(chat.config.model, "gpt-6.1-sol"); + assert_eq!(chat.config.model_context_window, Some(872_000)); + assert_eq!(chat.config.model_auto_compact_token_limit, Some(784_800)); + chat.apply_session_context_mode_selection(Some(ContextMode::OneM)); + assert_eq!(chat.config.model_context_window, Some(872_000)); + assert_eq!(chat.config.model_auto_compact_token_limit, Some(784_800)); + } + #[test] fn apply_context_mode_selection_persists_disabled_override() { let _runtime_guard = enter_test_runtime_guard(); diff --git a/code-rs/tui/src/lib.rs b/code-rs/tui/src/lib.rs index 1a3d5fdf6507..1fb0ddfef0f3 100644 --- a/code-rs/tui/src/lib.rs +++ b/code-rs/tui/src/lib.rs @@ -1254,6 +1254,8 @@ fn reasoning_effort_to_str(effort: ReasoningEffort) -> &'static str { ReasoningEffort::Medium => "medium", ReasoningEffort::High => "high", ReasoningEffort::XHigh => "xhigh", + ReasoningEffort::Max => "max", + ReasoningEffort::Disabled => "none", ReasoningEffort::None => "none", } } diff --git a/code-rs/tui/src/remote_model_presets.rs b/code-rs/tui/src/remote_model_presets.rs index 51eed17c9bee..90abd56d9a14 100644 --- a/code-rs/tui/src/remote_model_presets.rs +++ b/code-rs/tui/src/remote_model_presets.rs @@ -18,6 +18,19 @@ const REMOTE_TEXT_VERBOSITY_ALL: &[TextVerbosityConfig] = &[ ]; const REMOTE_TEXT_VERBOSITY_MEDIUM: &[TextVerbosityConfig] = &[TextVerbosityConfig::Medium]; +pub(crate) fn merge_remote_models_for_provider( + remote_models: Vec, + mut local_presets: Vec, + auth_mode: Option, + supports_pro_only_models: bool, + first_party: bool, +) -> Vec { + if !first_party { + local_presets.retain(|preset| code_core::modern_models::modern_model(&preset.model).is_none()); + } + merge_remote_models(remote_models, local_presets, auth_mode, supports_pro_only_models) +} + pub(crate) fn merge_remote_models( remote_models: Vec, local_presets: Vec, @@ -88,9 +101,14 @@ fn model_info_to_preset(info: ModelInfo) -> ModelPreset { REMOTE_TEXT_VERBOSITY_MEDIUM }; + let modern = code_core::modern_models::modern_model(&info.slug).is_some(); let supported_reasoning_efforts = info .supported_reasoning_levels .into_iter() + // Modern presets expose only effort values the legacy execution enum + // preserves exactly. Do not relabel Ultra as XHigh. + .filter(|preset| !modern || !matches!(preset.effort, + RemoteReasoningEffort::Ultra | RemoteReasoningEffort::Persistent | RemoteReasoningEffort::Custom(_))) .map(|preset| ReasoningEffortPreset { effort: map_reasoning_effort(preset.effort), description: preset.description, @@ -123,6 +141,18 @@ fn model_info_to_preset(info: ModelInfo) -> ModelPreset { mod tests { use super::*; + #[test] + fn custom_discovery_does_not_advertise_unlisted_modern_models() { + let local = code_common::model_presets::builtin_model_presets(Some(AuthMode::ApiKey), false); + let presets = merge_remote_models_for_provider(vec![remote_model("proxy-model", 0)], local, + Some(AuthMode::ApiKey), false, false); + assert!(!presets.iter().any(|preset| preset.model == "gpt-6.1-sol")); + let local = code_common::model_presets::builtin_model_presets(Some(AuthMode::ApiKey), false); + let presets = merge_remote_models_for_provider(vec![remote_model("gpt-6.1-sol", 0)], local, + Some(AuthMode::ApiKey), false, false); + assert!(presets.iter().any(|preset| preset.model == "gpt-6.1-sol")); + } + fn remote_model(slug: &str, priority: i32) -> ModelInfo { serde_json::from_value(serde_json::json!({ "slug": slug, @@ -184,13 +214,13 @@ mod tests { fn map_reasoning_effort(effort: RemoteReasoningEffort) -> ProtocolReasoningEffort { match effort { - RemoteReasoningEffort::None => ProtocolReasoningEffort::Minimal, + RemoteReasoningEffort::None => ProtocolReasoningEffort::None, RemoteReasoningEffort::Minimal => ProtocolReasoningEffort::Minimal, RemoteReasoningEffort::Low => ProtocolReasoningEffort::Low, RemoteReasoningEffort::Medium => ProtocolReasoningEffort::Medium, RemoteReasoningEffort::High => ProtocolReasoningEffort::High, + RemoteReasoningEffort::Max => ProtocolReasoningEffort::Max, RemoteReasoningEffort::XHigh - | RemoteReasoningEffort::Max | RemoteReasoningEffort::Ultra | RemoteReasoningEffort::Persistent => { ProtocolReasoningEffort::XHigh diff --git a/docs/model-support-inventory.json b/docs/model-support-inventory.json new file mode 100644 index 000000000000..3e34e5aaa82d --- /dev/null +++ b/docs/model-support-inventory.json @@ -0,0 +1,2521 @@ +{ + "scope": "tracked runtime, read-only codex-rs mirror, JS/scripts, tests, configurations, prompts and docs; literal candidates include fixtures and non-model references and are not availability claims", + "models_by_file": { + ".github/ISSUE_TEMPLATE/3-cli.yml": [ + "gpt-5", + "o3" + ], + ".github/codex/home/config.toml": [ + "gpt-5.1" + ], + ".github/workflows/issue-triage.yml": [ + "gpt-4o-mini", + "gpt-5" + ], + "CHANGELOG.md": [ + "GPT-5", + "GPT-5.4", + "GPT-5.5", + "GPT-5.6", + "GPT-5.6-Terra", + "GPT-6-Astra", + "QWEN_MODEL", + "Qwen-coder", + "claude-sonnet-4.5", + "gemini-3-flash", + "gemini-3-pro", + "gpt-5-codex-mini", + "gpt-5.1", + "gpt-5.1-codex-max", + "gpt-5.2", + "gpt-5.2-codex", + "gpt-5.3", + "gpt-5.3-codex", + "gpt-5.3-codex-spark", + "gpt-5.4-mini" + ], + "README.md": [ + "GPT-5", + "claude-code", + "claude-opus-4.8", + "claude-sonnet-4.6", + "gemini-cli", + "gpt-5", + "gpt-5.1", + "gpt-5.4", + "gpt-5.4-mini", + "gpt-5.5", + "qwen-code" + ], + "code-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json": [ + "GPT-5" + ], + "code-rs/app-server-protocol/schema/json/v1/GetUserSavedConfigResponse.json": [ + "GPT-5" + ], + "code-rs/app-server-protocol/schema/json/v2/ConfigReadResponse.json": [ + "GPT-5" + ], + "code-rs/app-server-protocol/schema/typescript/Verbosity.ts": [ + "GPT-5" + ], + "code-rs/app-server-protocol/src/protocol/common.rs": [ + "gpt-5.1-codex-max" + ], + "code-rs/app-server/src/message_processor.rs": [ + "o3" + ], + "code-rs/apply-patch/src/lib.rs": [ + "gpt-4.1" + ], + "code-rs/apply-patch/src/parser.rs": [ + "GPT-4.1", + "gpt-4.1." + ], + "code-rs/arg0/src/lib.rs": [ + "QWEN_API_KEY" + ], + "code-rs/cli/src/llm.rs": [ + "gpt-4.1", + "gpt-5.1" + ], + "code-rs/cli/src/main.rs": [ + "gpt-5.1-test" + ], + "code-rs/code-auto-drive-core/src/auto_coordinator.rs": [ + "gpt-5.1", + "gpt-5.1-codex-max", + "gpt-5.3-codex", + "gpt-5.3-codex-experimental", + "gpt-5.3-codex-spark", + "gpt-5.5", + "gpt-5.6-luna", + "gpt-6.1-sol" + ], + "code-rs/code-version/src/lib.rs": [ + "GPT-6.1", + "gpt-5.4", + "gpt-5.5", + "gpt-5.6-luna", + "gpt-5.6-sol", + "gpt-5.6-terra", + "gpt-6-astra", + "gpt-6-luna", + "gpt-6-sol", + "gpt-6.1-sol" + ], + "code-rs/common/src/config_override.rs": [ + "o3" + ], + "code-rs/common/src/model_presets.rs": [ + "GPT-5", + "GPT-5.5", + "GPT-5.6", + "gpt-5", + "gpt-5-codex", + "gpt-5-codex-mini", + "gpt-5.1", + "gpt-5.1-codex", + "gpt-5.1-codex-max", + "gpt-5.1-codex-mini", + "gpt-5.2", + "gpt-5.2-codex", + "gpt-5.3-codex", + "gpt-5.3-codex-spark", + "gpt-5.5", + "gpt-5.6-luna", + "gpt-5.6-terra" + ], + "code-rs/config.md": [ + "QWEN_API_KEY", + "gpt-3.5-turbo", + "gpt-4o", + "gpt-5.1", + "o3", + "o4-mini" + ], + "code-rs/core/gpt-5.1-codex-max_prompt.md": [ + "GPT-5." + ], + "code-rs/core/gpt-5.2-codex_prompt.md": [ + "GPT-5." + ], + "code-rs/core/gpt_5_1_prompt.md": [ + "GPT-5.1" + ], + "code-rs/core/gpt_5_2_prompt.md": [ + "GPT-5.2" + ], + "code-rs/core/gpt_5_codex_prompt.md": [ + "GPT-5." + ], + "code-rs/core/prompt_coder.md": [ + "claude-sonnet-4.6", + "gemini-3.5-flash", + "gpt-5.5" + ], + "code-rs/core/src/agent_defaults.rs": [ + "Gemini 3.1 Pro (High)", + "QWEN_3_CODER_READ_ONLY", + "QWEN_3_CODER_WRITE", + "claude-haiku", + "claude-haiku-4-5-20251001", + "claude-haiku-4.5", + "claude-haiku-5-5", + "claude-haiku-5.5", + "claude-opus", + "claude-opus-4-5-20251101", + "claude-opus-4-6", + "claude-opus-4-7", + "claude-opus-4-8", + "claude-opus-4.5", + "claude-opus-4.6", + "claude-opus-4.7", + "claude-opus-4.8", + "claude-opus-5-5", + "claude-opus-5.5", + "claude-sonnet", + "claude-sonnet-4-5-20250929", + "claude-sonnet-4-6", + "claude-sonnet-4.5", + "claude-sonnet-4.6", + "claude-sonnet-5-5", + "claude-sonnet-5.5", + "gemini-2.5-flash", + "gemini-2.5-pro", + "gemini-3", + "gemini-3-flash", + "gemini-3-flash-preview", + "gemini-3-pro", + "gemini-3-pro-preview", + "gemini-3.1-pro", + "gemini-3.5-flash", + "gemini-flash", + "gemini-pro", + "gpt-5", + "gpt-5-codex", + "gpt-5-codex-mini", + "gpt-5.1", + "gpt-5.1-codex", + "gpt-5.1-codex-max", + "gpt-5.1-codex-mini", + "gpt-5.2", + "gpt-5.2-codex", + "gpt-5.3-codex", + "gpt-5.3-codex-spark", + "gpt-5.3-spark", + "gpt-5.4", + "gpt-5.4-mini", + "gpt-5.5", + "gpt-5.6-luna", + "gpt-5.6-sol", + "gpt-5.6-terra", + "gpt-6-terra", + "gpt-6.1-sol", + "qwen-3-coder", + "qwen3-coder-plus", + "qwen_uses_dashscope_model_id" + ], + "code-rs/core/src/agent_tool.rs": [ + "QWEN_API_KEY", + "QWEN_BASE_URL", + "claude-haiku-5-5", + "claude-opus-5-5", + "claude-sonnet-4.6", + "claude-sonnet-5-5", + "gemini-3.5-flash", + "gpt-5.3-codex", + "gpt-5.5", + "gpt-5.6-luna", + "gpt-6-luna", + "gpt-6-sol", + "gpt-6.1-sol", + "qwen_base", + "qwen_key" + ], + "code-rs/core/src/chat_completions.rs": [ + "gpt-4.1", + "kimi-k2" + ], + "code-rs/core/src/client.rs": [ + "GPT-5", + "gpt-5", + "gpt-5.1", + "gpt-5.1-codex-max", + "gpt-5.1.", + "gpt-5.3-codex-spark", + "gpt-5.6", + "gpt-6", + "gpt-test" + ], + "code-rs/core/src/client_common.rs": [ + "gpt-3.5", + "gpt-4.1", + "gpt-4o", + "gpt-5.1", + "gpt-5.1-codex", + "gpt-5.3-codex", + "gpt-5.3-codex-spark", + "gpt-oss" + ], + "code-rs/core/src/codex/streaming.rs": [ + "claude-opus-4.6", + "gpt-4.1", + "gpt-4.1-", + "gpt-5.2-codex", + "gpt-5.3-codex", + "gpt-5.3-codex-spark", + "gpt-5.4", + "gpt-5.4-2026-04-01", + "o3" + ], + "code-rs/core/src/config.rs": [ + "GPT-5.3-CODEX", + "GPT-5.3-CODEX-EXPERIMENTAL", + "claude-opus-4.6", + "claude-opus-4.8", + "claude-opus-5-5", + "claude-sonnet-4.5", + "claude-sonnet-4.6", + "gemini-2.5-pro", + "gemini-3-flash", + "gemini-3.1-pro", + "gemini-3.5-flash", + "gpt-3.5-turbo", + "gpt-4", + "gpt-4.1", + "gpt-5", + "gpt-5-codex", + "gpt-5.1", + "gpt-5.1-codex", + "gpt-5.1-codex-max", + "gpt-5.2", + "gpt-5.2-codex", + "gpt-5.2.4", + "gpt-5.3-codex", + "gpt-5.4", + "gpt-5.4-mini", + "gpt-5.5", + "gpt-5.6-luna", + "gpt-5.6-terra", + "gpt-6-astra", + "gpt-6.1-sol", + "o3", + "o4-high", + "o4-mini", + "qwen-3-coder" + ], + "code-rs/core/src/config/validation.rs": [ + "claude-opus-4.1", + "claude-opus-4.5", + "claude-opus-4.6", + "claude-opus-4.7", + "claude-opus-4.8", + "claude-sonnet-4.5", + "claude-sonnet-4.6", + "gemini-2.5-flash", + "gemini-2.5-pro", + "gemini-3-flash", + "gemini-3-flash-preview", + "gemini-3-pro", + "gemini-3-pro-preview", + "gemini-3.1-pro", + "gemini-3.5-flash", + "gpt-4.1", + "gpt-4.1-2024-04-09", + "gpt-4.1-mini", + "gpt-4.1-mini-2024-04-09", + "gpt-4.1-nano", + "gpt-4.1-nano-2024-04-09", + "gpt-5", + "gpt-5-codex", + "gpt-5.", + "gpt-5.1", + "gpt-5.1-codex", + "gpt-5.1-codex-max", + "gpt-5.2", + "gpt-5.2.4", + "gpt-5.4", + "gpt-5.6-terra" + ], + "code-rs/core/src/config_edit.rs": [ + "gpt-4", + "gpt-5.1-codex", + "o3", + "o4-mini" + ], + "code-rs/core/src/config_types.rs": [ + "gpt-4", + "gpt-5", + "gpt-5-mini", + "gpt-5.1", + "gpt-5.5", + "gpt-5.6-luna", + "gpt-6.1-sol" + ], + "code-rs/core/src/default_client.rs": [ + "gpt-5.5" + ], + "code-rs/core/src/external_agent_config.rs": [ + "claude-code" + ], + "code-rs/core/src/memories/mod.rs": [ + "gpt-5.1-codex-mini", + "gpt-5.3-codex" + ], + "code-rs/core/src/model_family.rs": [ + "GPT-5.", + "Qwen3-Coder-30B-A3B-Instruct-Q4_K_M", + "gpt-3.5", + "gpt-4.1", + "gpt-4.1-2025-04-14", + "gpt-4o", + "gpt-5", + "gpt-5-codex", + "gpt-5.1", + "gpt-5.1-codex", + "gpt-5.1-codex-max", + "gpt-5.1-codex-max.", + "gpt-5.1-codex-max_prompt.md", + "gpt-5.2", + "gpt-5.2-codex", + "gpt-5.2-codex.", + "gpt-5.2-codex_prompt.md", + "gpt-5.3", + "gpt-5.3-codex", + "gpt-5.4", + "gpt-5.5", + "gpt-5.6-luna", + "gpt-5.6-sol", + "gpt-5.6-terra", + "gpt-6.1-sol", + "gpt-oss", + "o3", + "o4-mini", + "qwen3-coder-30b-a3b-instruct", + "qwen3_coder_30b_quantized_slug_has_explicit_local_metadata" + ], + "code-rs/core/src/model_provider_info.rs": [ + "gpt-oss" + ], + "code-rs/core/src/openai_tools.rs": [ + "gpt-5.4", + "o3" + ], + "code-rs/core/src/reasoning.rs": [ + "gpt-5", + "gpt-5-codex", + "gpt-5-codex-mini", + "gpt-5.1", + "gpt-5.1-codex", + "gpt-5.1-codex-max", + "gpt-5.1-codex-mini", + "gpt-5.2", + "gpt-5.3", + "gpt-5.6-luna", + "gpt-5.6-sol", + "gpt-5.6-terra", + "gpt-6-astra", + "gpt-6-luna", + "gpt-6-sol", + "gpt-6.1-sol" + ], + "code-rs/core/src/remote_models/mod.rs": [ + "gpt-5.3-codex", + "gpt-5.4", + "gpt-6.1-sol" + ], + "code-rs/core/src/slash_commands.rs": [ + "claude-sonnet-4.5", + "claude-sonnet-5-5", + "gemini-3-flash", + "gpt-5.1-codex-max", + "gpt-5.5", + "gpt-5.6-terra", + "qwen-3-coder" + ], + "code-rs/core/src/tool_apply_patch.rs": [ + "GPT-5", + "gpt-oss" + ], + "code-rs/core/templates/model_instructions/gpt-5.2-codex_instructions_template.md": [ + "GPT-5." + ], + "code-rs/core/tests/agent_completion_wake.rs": [ + "gpt-5.1-codex" + ], + "code-rs/core/tests/gemini_model_args.rs": [ + "gemini-2.5-flash", + "gemini-2.5-pro", + "gemini-3.1-pro", + "gemini-3.5-flash" + ], + "code-rs/core/tests/remote_models_manager.rs": [ + "gpt-5.2" + ], + "code-rs/core/tests/repeated_tool_loop.rs": [ + "gpt-5.1-codex" + ], + "code-rs/core/tests/tool_hooks.rs": [ + "gpt-5.1-codex" + ], + "code-rs/core/tests/tool_selection.rs": [ + "gpt-5-codex", + "gpt-5.1", + "gpt-5.1-codex", + "o3" + ], + "code-rs/docs/codex_mcp_interface.md": [ + "gpt-5.1", + "gpt-5.1-codex", + "o3" + ], + "code-rs/exec/src/lib.rs": [ + "gpt-5.2", + "gpt-oss" + ], + "code-rs/mcp-server/src/code_tool_config.rs": [ + "o3", + "o4-mini" + ], + "code-rs/mcp-server/src/outgoing_message.rs": [ + "gpt-4o" + ], + "code-rs/mcp-types/schema/2025-03-26/schema.json": [ + "claude-3-5-sonnet", + "claude-3-5-sonnet-20241022", + "claude-3-haiku-20240307", + "claude-3-sonnet-20240229", + "gemini-1.5-flash" + ], + "code-rs/mcp-types/schema/2025-06-18/schema.json": [ + "claude-3-5-sonnet", + "claude-3-5-sonnet-20241022", + "claude-3-haiku-20240307", + "claude-3-sonnet-20240229", + "gemini-1.5-flash" + ], + "code-rs/ollama/src/client.rs": [ + "llama3.2" + ], + "code-rs/ollama/src/lib.rs": [ + "gpt-oss" + ], + "code-rs/protocol/src/config_types.rs": [ + "GPT-5", + "gpt-5.2-codex" + ], + "code-rs/protocol/src/mcp_protocol.rs": [ + "gpt-5.1-codex", + "o3", + "o4-mini" + ], + "code-rs/protocol/src/openai_models.rs": [ + "gpt-5" + ], + "code-rs/tui/src/agent_install_helpers.rs": [ + "claude-code", + "gemini-cli", + "qwen-code" + ], + "code-rs/tui/src/bottom_pane/auto_drive_settings_view.rs": [ + "gpt-5.5", + "gpt-5.6-luna" + ], + "code-rs/tui/src/bottom_pane/model_selection_view.rs": [ + "GPT-5.3-Codex", + "GPT-5.4", + "gpt-5.1", + "gpt-5.1-codex", + "gpt-5.1-codex-max", + "gpt-5.1-codex-mini", + "gpt-5.2", + "gpt-5.2-codex", + "gpt-5.3-codex", + "gpt-5.3-codex-spark", + "gpt-5.4", + "gpt-5.4-mini", + "gpt-5.5" + ], + "code-rs/tui/src/chatwidget.rs": [ + "GPT-4", + "GPT-5.1-Codex-Mini", + "GPT-5.5", + "GPT-5.6", + "claude-code", + "claude-sonnet-4.6", + "gemini-3.1-pro", + "gemini-cli", + "gpt-3.5", + "gpt-4", + "gpt-4.5", + "gpt-5.1-codex", + "gpt-5.1-codex-max", + "gpt-5.1-codex-mini", + "gpt-5.1-large", + "gpt-5.2", + "gpt-5.3-codex", + "gpt-5.3-codex-spark", + "gpt-5.4", + "gpt-5.5", + "gpt-5.6-luna", + "gpt-5.6-sol", + "gpt-5.6-terra", + "gpt-6-astra", + "gpt-6-luna", + "gpt-6-sol", + "gpt-6.1-sol", + "o3", + "qwen-3-coder", + "qwen-code" + ], + "code-rs/tui/src/chatwidget/agent_summary.rs": [ + "gpt-5.2-codex", + "gpt-5.3-codex", + "gpt-5.4" + ], + "code-rs/tui/src/lib.rs": [ + "gpt-5.1-codex", + "gpt-5.2-codex", + "gpt-oss" + ], + "code-rs/tui/src/model_migration.rs": [ + "GPT-5.2", + "GPT-5.3", + "gpt-5", + "gpt-5-1", + "gpt-5-1-codex-max", + "gpt-5-2", + "gpt-5.1", + "gpt-5.1-codex", + "gpt-5.1-codex-max", + "gpt-5.1-codex-mini.", + "gpt-5.3-codex." + ], + "code-rs/tui/src/onboarding/auth.rs": [ + "gpt-5.1" + ], + "code-rs/tui/src/remote_model_presets.rs": [ + "gpt-5.1-codex", + "gpt-5.2", + "gpt-5.2-codex", + "gpt-5.3-codex", + "gpt-5.3-codex-spark", + "gpt-5.4", + "gpt-5.5" + ], + "code-rs/tui/tests/ui_smoke.rs": [ + "gpt-5.1-codex" + ], + "code-rs/tui/tests/vt100_chatwidget_snapshot.rs": [ + "claude-3-opus", + "claude-opus-4.8", + "gpt-4o" + ], + "codex-cli/.pack/package/README.md": [ + "GPT-5", + "claude-code", + "gemini-cli", + "gpt-5", + "gpt-5.1", + "qwen-code" + ], + "codex-rs/analytics/src/client_tests.rs": [ + "gpt-5", + "gpt-5.1-codex" + ], + "codex-rs/analytics/src/tests/suite/approvals_tests.rs": [ + "gpt-5" + ], + "codex-rs/analytics/src/tests/suite/apps_tests.rs": [ + "gpt-5" + ], + "codex-rs/analytics/src/tests/suite/hooks_tests.rs": [ + "gpt-5" + ], + "codex-rs/analytics/src/tests/suite/plugins_tests.rs": [ + "gpt-5" + ], + "codex-rs/analytics/src/tests/suite/skills_tests.rs": [ + "gpt-5" + ], + "codex-rs/analytics/src/tests/suite/threads_tests.rs": [ + "gpt-5" + ], + "codex-rs/analytics/src/tests/suite/tools_tests.rs": [ + "gpt-5" + ], + "codex-rs/analytics/src/tests/suite/turns/accepted_lines_tests.rs": [ + "gpt-5", + "gpt-5.1-codex" + ], + "codex-rs/analytics/src/tests/suite/turns/compaction_tests.rs": [ + "gpt-5" + ], + "codex-rs/analytics/src/tests/suite/turns/events_tests.rs": [ + "gpt-5" + ], + "codex-rs/analytics/src/tests/suite/turns/requests_tests.rs": [ + "gpt-5" + ], + "codex-rs/analytics/src/tests/support.rs": [ + "gpt-5" + ], + "codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json": [ + "GPT-5" + ], + "codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json": [ + "GPT-5" + ], + "codex-rs/app-server-protocol/schema/json/v2/ConfigReadResponse.json": [ + "GPT-5" + ], + "codex-rs/app-server-protocol/schema/typescript/Verbosity.ts": [ + "GPT-5" + ], + "codex-rs/app-server-protocol/src/protocol/common.rs": [ + "gpt-5", + "gpt-5.4" + ], + "codex-rs/app-server-protocol/src/protocol/thread_history.rs": [ + "gpt-5.4-mini" + ], + "codex-rs/app-server-protocol/src/protocol/v2/tests.rs": [ + "gpt-5" + ], + "codex-rs/app-server-test-client/src/lib.rs": [ + "gpt-5" + ], + "codex-rs/app-server/src/config_manager_service_tests.rs": [ + "gpt-5.2", + "gpt-main", + "gpt-new", + "gpt-work" + ], + "codex-rs/app-server/src/main_tests.rs": [ + "gpt-5-codex" + ], + "codex-rs/app-server/src/outgoing_message.rs": [ + "gpt-5.2", + "gpt-5.3-codex" + ], + "codex-rs/app-server/src/request_processors/config_processor.rs": [ + "gpt-managed", + "gpt-protected" + ], + "codex-rs/app-server/src/request_processors/persisted_resume_settings_tests.rs": [ + "gpt-5" + ], + "codex-rs/app-server/src/request_processors/thread_processor_tests.rs": [ + "gpt-5", + "gpt-5.1-codex-max", + "gpt-5.2-codex" + ], + "codex-rs/app-server/src/turn_cost_worker_tests.rs": [ + "gpt-5.6" + ], + "codex-rs/app-server/tests/suite/v2/account.rs": [ + "gpt-5.6-sol", + "gpt-5.6-terra" + ], + "codex-rs/app-server/tests/suite/v2/account_thread_usage.rs": [ + "gpt-5.4" + ], + "codex-rs/app-server/tests/suite/v2/analytics.rs": [ + "gpt-5.5" + ], + "codex-rs/app-server/tests/suite/v2/config_rpc.rs": [ + "gpt-5.3-spark", + "gpt-5.5", + "gpt-existing", + "gpt-managed", + "gpt-new", + "gpt-old", + "gpt-protected", + "gpt-sensitive", + "gpt-system", + "gpt-test", + "gpt-user" + ], + "codex-rs/app-server/tests/suite/v2/cyber_access_program.rs": [ + "gpt-5.5" + ], + "codex-rs/app-server/tests/suite/v2/daemon_update_recovery.rs": [ + "gpt-5.2" + ], + "codex-rs/app-server/tests/suite/v2/executor_mcp.rs": [ + "gpt-5.4" + ], + "codex-rs/app-server/tests/suite/v2/external_agent_config.rs": [ + "claude-code", + "claude-code-sessions", + "gpt-5.4", + "gpt-concurrent" + ], + "codex-rs/app-server/tests/suite/v2/guardian_v2.rs": [ + "gpt-5.6-luna" + ], + "codex-rs/app-server/tests/suite/v2/guardian_v2_history_tests.rs": [ + "gpt-5.6-luna" + ], + "codex-rs/app-server/tests/suite/v2/imagegen_extension.rs": [ + "gpt-image-2" + ], + "codex-rs/app-server/tests/suite/v2/mcp_resource.rs": [ + "gpt-5.4" + ], + "codex-rs/app-server/tests/suite/v2/mcp_resource/plugin_metadata_refresh.rs": [ + "gpt-5.5" + ], + "codex-rs/app-server/tests/suite/v2/multi_agent_v2_developer_instructions.rs": [ + "gpt-5.4", + "gpt-5.5" + ], + "codex-rs/app-server/tests/suite/v2/plugin_list.rs": [ + "claude-plugin" + ], + "codex-rs/app-server/tests/suite/v2/realtime_conversation.rs": [ + "gpt-4o-mini-transcribe", + "gpt-live-1-codex", + "gpt-realtime-1.5" + ], + "codex-rs/app-server/tests/suite/v2/request_permissions.rs": [ + "gpt-5.4" + ], + "codex-rs/app-server/tests/suite/v2/residency.rs": [ + "gpt-5.4" + ], + "codex-rs/app-server/tests/suite/v2/safety_check_downgrade.rs": [ + "gpt-5.3-codex", + "gpt-5.5" + ], + "codex-rs/app-server/tests/suite/v2/skills_list.rs": [ + "gpt-5.4" + ], + "codex-rs/app-server/tests/suite/v2/thread_archive.rs": [ + "gpt-5.4" + ], + "codex-rs/app-server/tests/suite/v2/thread_goal_empty_responses.rs": [ + "gpt-5.4" + ], + "codex-rs/app-server/tests/suite/v2/thread_list.rs": [ + "gpt-5.2", + "gpt-5.4" + ], + "codex-rs/app-server/tests/suite/v2/thread_loaded_list.rs": [ + "gpt-5.2" + ], + "codex-rs/app-server/tests/suite/v2/thread_metadata_update.rs": [ + "gpt-5.2" + ], + "codex-rs/app-server/tests/suite/v2/thread_queue.rs": [ + "gpt-5.2" + ], + "codex-rs/app-server/tests/suite/v2/thread_resume.rs": [ + "gpt-5.2-codex", + "gpt-5.4" + ], + "codex-rs/app-server/tests/suite/v2/thread_start.rs": [ + "gpt-5.2", + "gpt-5.4-mini", + "gpt-5.5", + "gpt-5.6-sol", + "gpt-6-luna", + "gpt-6-sol", + "gpt-6.1-sol" + ], + "codex-rs/app-server/tests/suite/v2/turn_settings_update.rs": [ + "gpt-5.5" + ], + "codex-rs/app-server/tests/suite/v2/turn_start.rs": [ + "gpt-5.4", + "gpt-5.5" + ], + "codex-rs/app-server/tests/suite/v2/view_image.rs": [ + "gpt-5.4" + ], + "codex-rs/apply-patch/src/parser.rs": [ + "GPT-4.1", + "gpt-4.1." + ], + "codex-rs/aws-auth/src/lib.rs": [ + "gpt-oss-120b-1" + ], + "codex-rs/backend-client/src/client/chatgpt_turn_cost_tests.rs": [ + "gpt-5.6" + ], + "codex-rs/backend-client/src/client/task_usage_tests.rs": [ + "gpt-5.5" + ], + "codex-rs/backend-client/src/client/thread_usage_tests.rs": [ + "gpt-5.4" + ], + "codex-rs/backend-client/src/client/turn_usage.rs": [ + "gpt-5.6" + ], + "codex-rs/cli/src/doctor.rs": [ + "llama3.2" + ], + "codex-rs/cli/src/doctor/output.rs": [ + "gpt-5.5" + ], + "codex-rs/cli/src/main.rs": [ + "gpt-5.1-test" + ], + "codex-rs/cli/tests/app_server.rs": [ + "gpt-5" + ], + "codex-rs/cli/tests/daemon_startup.rs": [ + "GPT-5.6-Terra", + "gpt-5.6-terra", + "gpt-6-sol" + ], + "codex-rs/cli/tests/login.rs": [ + "gpt-5.6-sol" + ], + "codex-rs/cli/tests/mcp_add_remove.rs": [ + "gpt-5" + ], + "codex-rs/cloud-config/src/cache_tests.rs": [ + "gpt-5" + ], + "codex-rs/cloud-config/src/service_tests.rs": [ + "gpt-5", + "gpt-5-refreshed" + ], + "codex-rs/codex-api/src/endpoint/images.rs": [ + "gpt-image-1.5", + "gpt-image-2" + ], + "codex-rs/codex-api/src/endpoint/memories.rs": [ + "gpt-test" + ], + "codex-rs/codex-api/src/endpoint/models.rs": [ + "gpt-test" + ], + "codex-rs/codex-api/src/endpoint/realtime_call.rs": [ + "gpt-realtime" + ], + "codex-rs/codex-api/src/endpoint/realtime_websocket/methods.rs": [ + "gpt-4o-mini-transcribe" + ], + "codex-rs/codex-api/src/endpoint/realtime_websocket/methods_frameless_bidi_tests.rs": [ + "gpt-live" + ], + "codex-rs/codex-api/src/endpoint/realtime_websocket/methods_v2.rs": [ + "gpt-4o-mini-transcribe" + ], + "codex-rs/codex-api/src/endpoint/responses_websocket.rs": [ + "gpt-fast-header", + "gpt-fast-wire", + "gpt-test" + ], + "codex-rs/codex-api/src/endpoint/search.rs": [ + "gpt-test" + ], + "codex-rs/codex-api/src/files.rs": [ + "gpt-work" + ], + "codex-rs/codex-api/src/rate_limits.rs": [ + "gpt-5.2-codex-sonic" + ], + "codex-rs/codex-api/src/sse/responses.rs": [ + "gpt-5.1", + "gpt-5.3-codex", + "gpt-fast-header", + "gpt-fast-wire" + ], + "codex-rs/codex-api/src/sse/responses_error_parser_tests.rs": [ + "gpt-5.1" + ], + "codex-rs/codex-api/tests/clients.rs": [ + "gpt-test" + ], + "codex-rs/codex-api/tests/models_integration.rs": [ + "gpt-test" + ], + "codex-rs/config/src/config_toml.rs": [ + "GPT-5" + ], + "codex-rs/config/src/loader/mod.rs": [ + "gpt-1000" + ], + "codex-rs/config/src/loader/tests.rs": [ + "gpt-dev", + "gpt-main", + "gpt-work", + "gpt-work-v2" + ], + "codex-rs/config/src/strict_config_tests.rs": [ + "gpt-5" + ], + "codex-rs/config/src/types.rs": [ + "gpt-5.1-codex-max" + ], + "codex-rs/core-plugins/src/command_migration_tests.rs": [ + "claude-code" + ], + "codex-rs/core-plugins/src/executor_provider_tests.rs": [ + "claude-plugin" + ], + "codex-rs/core-plugins/src/manifest.rs": [ + "claude-plugin" + ], + "codex-rs/core-plugins/src/marketplace.rs": [ + "claude-plugin" + ], + "codex-rs/core-plugins/src/marketplace_tests.rs": [ + "claude-plugin" + ], + "codex-rs/core-plugins/src/store_tests.rs": [ + "claude-plugin" + ], + "codex-rs/core/config.schema.json": [ + "GPT-5", + "gpt-5.1-codex-max" + ], + "codex-rs/core/gpt-5.1-codex-max_prompt.md": [ + "GPT-5." + ], + "codex-rs/core/gpt-5.2-codex_prompt.md": [ + "GPT-5." + ], + "codex-rs/core/gpt_5_1_prompt.md": [ + "GPT-5.1" + ], + "codex-rs/core/gpt_5_2_prompt.md": [ + "GPT-5.2" + ], + "codex-rs/core/gpt_5_codex_prompt.md": [ + "GPT-5." + ], + "codex-rs/core/src/agent/control_tests.rs": [ + "gpt-5.5", + "gpt-5.6-luna", + "gpt-5.6-sol" + ], + "codex-rs/core/src/agent/role_tests.rs": [ + "gpt-5", + "gpt-5.4-mini" + ], + "codex-rs/core/src/client_common_tests.rs": [ + "gpt-5.4" + ], + "codex-rs/core/src/client_tests.rs": [ + "gpt-test" + ], + "codex-rs/core/src/compact_tests.rs": [ + "gpt-5.2", + "gpt-5.3-codex" + ], + "codex-rs/core/src/config/config_loader_tests.rs": [ + "gpt-4", + "gpt-5", + "gpt-main", + "gpt-work" + ], + "codex-rs/core/src/config/config_tests.rs": [ + "gpt-3.5-turbo", + "gpt-4", + "gpt-4.1", + "gpt-5", + "gpt-5-mini", + "gpt-5.2", + "gpt-5.4", + "gpt-5.6-terra", + "gpt-bar", + "gpt-foo", + "gpt-new", + "gpt-old", + "gpt-project-local", + "o3", + "o4-mini" + ], + "codex-rs/core/src/config/edit_tests.rs": [ + "gpt-4o", + "gpt-5.2", + "gpt-5.4", + "gpt-foo", + "o4-mini" + ], + "codex-rs/core/src/config/mod.rs": [ + "GPT-5", + "gpt-5.5" + ], + "codex-rs/core/src/context/world_state/model_tests.rs": [ + "gpt-new", + "gpt-old", + "gpt-test" + ], + "codex-rs/core/src/context_manager/history_tests.rs": [ + "gpt-test" + ], + "codex-rs/core/src/realtime_context_tests.rs": [ + "gpt-5.2" + ], + "codex-rs/core/src/realtime_conversation.rs": [ + "gpt-live-1-codex", + "gpt-realtime-1.5" + ], + "codex-rs/core/src/session/mod.rs": [ + "gpt-5.2", + "gpt-5.3-codex" + ], + "codex-rs/core/src/session/step_activation_tests.rs": [ + "gpt-5.5" + ], + "codex-rs/core/src/session/tests.rs": [ + "gpt-5.2", + "gpt-5.4", + "gpt-5.5" + ], + "codex-rs/core/src/session/turn_input_tests.rs": [ + "gpt-5.2", + "gpt-5.4" + ], + "codex-rs/core/src/tasks/mod_tests.rs": [ + "gpt-5.2", + "gpt-5.4" + ], + "codex-rs/core/src/tools/handlers/apply_patch_spec.rs": [ + "GPT-5" + ], + "codex-rs/core/src/tools/handlers/multi_agents_tests.rs": [ + "gpt-5-role-override", + "gpt-5.5" + ], + "codex-rs/core/src/tools/tool_dispatch_trace_tests.rs": [ + "gpt-test" + ], + "codex-rs/core/src/turn_metadata_tests.rs": [ + "gpt-5.4" + ], + "codex-rs/core/src/util.rs": [ + "gpt-5" + ], + "codex-rs/core/src/util_tests.rs": [ + "gpt-5.2" + ], + "codex-rs/core/templates/model_instructions/gpt-5.2-codex_instructions_template.md": [ + "GPT-5." + ], + "codex-rs/core/tests/common/apps_test_server.rs": [ + "gpt-5.5" + ], + "codex-rs/core/tests/common/streaming_sse.rs": [ + "gpt-5.4" + ], + "codex-rs/core/tests/common/test_codex.rs": [ + "gpt-5.1-codex", + "gpt-5.5" + ], + "codex-rs/core/tests/remote_env_windows/remote_env_windows_test.rs": [ + "gpt-5.2" + ], + "codex-rs/core/tests/responses_headers.rs": [ + "gpt-3.5-turbo", + "gpt-5.1-codex" + ], + "codex-rs/core/tests/suite/abort_tasks.rs": [ + "gpt-5.4" + ], + "codex-rs/core/tests/suite/agent_eviction_tests.rs": [ + "gpt-5.6-sol" + ], + "codex-rs/core/tests/suite/agent_execution.rs": [ + "gpt-5.6-sol" + ], + "codex-rs/core/tests/suite/agent_websocket.rs": [ + "gpt-5.2", + "gpt-5.4", + "gpt-5.6-sol" + ], + "codex-rs/core/tests/suite/app_tool_exposure.rs": [ + "gpt-5.5" + ], + "codex-rs/core/tests/suite/apply_patch_cli.rs": [ + "gpt-5.5" + ], + "codex-rs/core/tests/suite/approvals.rs": [ + "gpt-5.4", + "gpt-5.5" + ], + "codex-rs/core/tests/suite/audio_truncation.rs": [ + "gpt-5.5" + ], + "codex-rs/core/tests/suite/auto_review.rs": [ + "gpt-5.4" + ], + "codex-rs/core/tests/suite/bedrock_multi_agent_tests.rs": [ + "gpt-5.6-sol" + ], + "codex-rs/core/tests/suite/cli_stream.rs": [ + "gpt-5.5" + ], + "codex-rs/core/tests/suite/client.rs": [ + "gpt-5.4", + "gpt-5.5" + ], + "codex-rs/core/tests/suite/client_websockets.rs": [ + "gpt-5.2", + "gpt-5.4" + ], + "codex-rs/core/tests/suite/code_mode.rs": [ + "gpt-5.1-codex", + "gpt-5.4", + "gpt-5.5", + "gpt-5.6-luna" + ], + "codex-rs/core/tests/suite/code_mode_elicitation.rs": [ + "gpt-5.1-codex" + ], + "codex-rs/core/tests/suite/code_mode_model_messages.rs": [ + "gpt-5.5" + ], + "codex-rs/core/tests/suite/codex_apps_protocol.rs": [ + "gpt-5.5" + ], + "codex-rs/core/tests/suite/collaboration_instructions.rs": [ + "gpt-5.4", + "gpt-5.5" + ], + "codex-rs/core/tests/suite/compact.rs": [ + "gpt-5.2", + "gpt-5.4", + "gpt-5.5", + "gpt-5.6" + ], + "codex-rs/core/tests/suite/compact_program_tests.rs": [ + "gpt-5.5", + "gpt-5.6-cyber" + ], + "codex-rs/core/tests/suite/compact_resume_fork.rs": [ + "gpt-5.4" + ], + "codex-rs/core/tests/suite/context_annotations.rs": [ + "gpt-5.5" + ], + "codex-rs/core/tests/suite/current_time_reminder.rs": [ + "gpt-5.5" + ], + "codex-rs/core/tests/suite/cyber_access_program.rs": [ + "gpt-5.1", + "gpt-5.6-sol" + ], + "codex-rs/core/tests/suite/cyber_exec_policy.rs": [ + "gpt-5.2", + "gpt-5.4" + ], + "codex-rs/core/tests/suite/exec_policy.rs": [ + "gpt-5.2" + ], + "codex-rs/core/tests/suite/extension_sandbox.rs": [ + "gpt-5.4" + ], + "codex-rs/core/tests/suite/external_auth.rs": [ + "gpt-5.5" + ], + "codex-rs/core/tests/suite/git_enrichment.rs": [ + "gpt-5.1-codex" + ], + "codex-rs/core/tests/suite/guardian_authorization.rs": [ + "gpt-5.1-codex" + ], + "codex-rs/core/tests/suite/guardian_authorization_refresh.rs": [ + "gpt-5.1-codex" + ], + "codex-rs/core/tests/suite/guardian_cached_score_tests.rs": [ + "gpt-5.5", + "gpt-5.6-luna" + ], + "codex-rs/core/tests/suite/guardian_checkpoint_migration_tests.rs": [ + "gpt-5.5", + "gpt-5.6-luna" + ], + "codex-rs/core/tests/suite/guardian_context_budget.rs": [ + "gpt-5.5", + "gpt-5.6-luna" + ], + "codex-rs/core/tests/suite/guardian_environments_tests.rs": [ + "gpt-5.5", + "gpt-5.6-luna" + ], + "codex-rs/core/tests/suite/guardian_history.rs": [ + "gpt-5.5" + ], + "codex-rs/core/tests/suite/guardian_mcp_elicitation.rs": [ + "gpt-5.4", + "gpt-5.5", + "gpt-5.6-luna" + ], + "codex-rs/core/tests/suite/guardian_retained_context.rs": [ + "gpt-5.4" + ], + "codex-rs/core/tests/suite/guardian_review.rs": [ + "gpt-5.4", + "gpt-5.5", + "gpt-5.6-luna" + ], + "codex-rs/core/tests/suite/guardian_review_cancellation.rs": [ + "gpt-5.1-codex" + ], + "codex-rs/core/tests/suite/guardian_root_handoff.rs": [ + "gpt-5.5" + ], + "codex-rs/core/tests/suite/guardian_sender_messages_tests.rs": [ + "gpt-5.5", + "gpt-5.6-luna" + ], + "codex-rs/core/tests/suite/guardian_subagent_notification_tests.rs": [ + "gpt-5.1-codex" + ], + "codex-rs/core/tests/suite/hooks.rs": [ + "gpt-5.1-codex", + "gpt-5.4", + "gpt-5.5" + ], + "codex-rs/core/tests/suite/injected_models_cache.rs": [ + "gpt-5.4" + ], + "codex-rs/core/tests/suite/interrupt_hooks.rs": [ + "gpt-5.4" + ], + "codex-rs/core/tests/suite/json_result.rs": [ + "gpt-5.4" + ], + "codex-rs/core/tests/suite/mcp_optional_startup_grace.rs": [ + "gpt-5.4" + ], + "codex-rs/core/tests/suite/mcp_tool_cache.rs": [ + "gpt-5.4", + "gpt-5.5" + ], + "codex-rs/core/tests/suite/mcp_turn_metadata.rs": [ + "gpt-5.4", + "gpt-5.5" + ], + "codex-rs/core/tests/suite/model_overrides.rs": [ + "gpt-4o", + "o3" + ], + "codex-rs/core/tests/suite/model_request.rs": [ + "gpt-5.2" + ], + "codex-rs/core/tests/suite/model_switching.rs": [ + "gpt-5.5", + "gpt-5.6-terra" + ], + "codex-rs/core/tests/suite/model_visible_layout.rs": [ + "gpt-5.2", + "gpt-5.4", + "gpt-5.5" + ], + "codex-rs/core/tests/suite/models_cache_auth.rs": [ + "gpt-5.5" + ], + "codex-rs/core/tests/suite/models_cache_ttl.rs": [ + "gpt-5.2", + "gpt-5.5" + ], + "codex-rs/core/tests/suite/models_etag_responses.rs": [ + "gpt-5.2" + ], + "codex-rs/core/tests/suite/multi_agent_mode.rs": [ + "gpt-5.2", + "gpt-5.4" + ], + "codex-rs/core/tests/suite/multi_agent_resume.rs": [ + "gpt-5.6-sol" + ], + "codex-rs/core/tests/suite/multi_agent_tool_descriptions.rs": [ + "gpt-5.2" + ], + "codex-rs/core/tests/suite/network_approval.rs": [ + "gpt-5.5" + ], + "codex-rs/core/tests/suite/otel.rs": [ + "gpt-5.4", + "gpt-5.5" + ], + "codex-rs/core/tests/suite/override_updates.rs": [ + "gpt-5.4" + ], + "codex-rs/core/tests/suite/pending_input.rs": [ + "gpt-5.1-codex", + "gpt-5.4" + ], + "codex-rs/core/tests/suite/personality.rs": [ + "gpt-5.5" + ], + "codex-rs/core/tests/suite/plugins.rs": [ + "gpt-5.2" + ], + "codex-rs/core/tests/suite/prompt_caching.rs": [ + "gpt-5.2", + "gpt-5.4", + "gpt-5.5", + "o3" + ], + "codex-rs/core/tests/suite/realtime_conversation.rs": [ + "gpt-5.4", + "gpt-live-1-codex", + "gpt-realtime-1.5" + ], + "codex-rs/core/tests/suite/reasoning_effort_override.rs": [ + "gpt-5.2", + "gpt-5.4", + "gpt-5.5", + "gpt-6-astra" + ], + "codex-rs/core/tests/suite/remote_env.rs": [ + "gpt-5.4" + ], + "codex-rs/core/tests/suite/remote_models.rs": [ + "gpt-5-remote", + "gpt-5.2", + "gpt-5.3", + "gpt-5.3-codex", + "gpt-5.3-codex-test", + "gpt-5.4", + "gpt-5.4-test", + "gpt-5.5", + "gpt-5.5-codex" + ], + "codex-rs/core/tests/suite/request_plugin_install.rs": [ + "gpt-5.5" + ], + "codex-rs/core/tests/suite/request_user_input_async.rs": [ + "gpt-5.2" + ], + "codex-rs/core/tests/suite/responses_lite.rs": [ + "gpt-5.4" + ], + "codex-rs/core/tests/suite/resume.rs": [ + "gpt-5.2", + "gpt-5.4", + "gpt-5.5" + ], + "codex-rs/core/tests/suite/review.rs": [ + "gpt-4.1", + "gpt-5.2", + "gpt-5.4", + "gpt-5.6-sol" + ], + "codex-rs/core/tests/suite/rmcp_client.rs": [ + "gpt-5.2", + "gpt-5.4", + "gpt-5.5" + ], + "codex-rs/core/tests/suite/safety_check_downgrade.rs": [ + "gpt-5.2", + "gpt-5.3-codex", + "gpt-5.5" + ], + "codex-rs/core/tests/suite/scenarios.rs": [ + "gpt-6-astra" + ], + "codex-rs/core/tests/suite/scenarios_agent_message_board.rs": [ + "gpt-5.5" + ], + "codex-rs/core/tests/suite/scenarios_content_filter.rs": [ + "gpt-5.5" + ], + "codex-rs/core/tests/suite/scenarios_guardian_agent_messages_tests.rs": [ + "gpt-5.5" + ], + "codex-rs/core/tests/suite/scenarios_guardian_conversation_history_tests.rs": [ + "gpt-5.5" + ], + "codex-rs/core/tests/suite/scenarios_guardian_extra_policy.rs": [ + "gpt-5.5" + ], + "codex-rs/core/tests/suite/scenarios_guardian_heartbeat.rs": [ + "gpt-5.5" + ], + "codex-rs/core/tests/suite/scenarios_indirect_namespace_prefixes.rs": [ + "gpt-5.5" + ], + "codex-rs/core/tests/suite/scenarios_mailbox_preemption_tests.rs": [ + "gpt-5.4" + ], + "codex-rs/core/tests/suite/scenarios_mcp_resource_messages.rs": [ + "gpt-6-astra" + ], + "codex-rs/core/tests/suite/scenarios_skill_catalog_dedup.rs": [ + "gpt-6-astra" + ], + "codex-rs/core/tests/suite/search_tool.rs": [ + "gpt-5.5" + ], + "codex-rs/core/tests/suite/settings_commits.rs": [ + "gpt-5.2", + "gpt-5.4" + ], + "codex-rs/core/tests/suite/settings_constraints.rs": [ + "gpt-5.2", + "gpt-5.4" + ], + "codex-rs/core/tests/suite/skills_extension.rs": [ + "gpt-5.4", + "gpt-5.5", + "gpt-5.6-sol" + ], + "codex-rs/core/tests/suite/skills_extension/cloud_skill_tests.rs": [ + "gpt-5.5" + ], + "codex-rs/core/tests/suite/skills_extension/yielded_skill_tests.rs": [ + "gpt-5.5" + ], + "codex-rs/core/tests/suite/step_settings.rs": [ + "gpt-5.5" + ], + "codex-rs/core/tests/suite/step_settings_snapshots.rs": [ + "gpt-5.5" + ], + "codex-rs/core/tests/suite/subagent_notifications.rs": [ + "gpt-5.5", + "gpt-5.6-luna", + "gpt-5.6-sol", + "gpt-5.6-terra" + ], + "codex-rs/core/tests/suite/subagent_service_tier.rs": [ + "gpt-5.6-sol" + ], + "codex-rs/core/tests/suite/token_budget.rs": [ + "gpt-5.2", + "gpt-5.4" + ], + "codex-rs/core/tests/suite/tool_harness.rs": [ + "gpt-5-codex" + ], + "codex-rs/core/tests/suite/tool_lifecycle.rs": [ + "gpt-5.4" + ], + "codex-rs/core/tests/suite/tool_observation_tests.rs": [ + "gpt-5.1-codex" + ], + "codex-rs/core/tests/suite/tool_parallelism.rs": [ + "gpt-5.1-codex", + "gpt-5.4" + ], + "codex-rs/core/tests/suite/tools.rs": [ + "gpt-5-codex", + "gpt-5.4" + ], + "codex-rs/core/tests/suite/truncation.rs": [ + "gpt-5.2", + "gpt-5.5" + ], + "codex-rs/core/tests/suite/turn_error_details_tests.rs": [ + "gpt-5.5" + ], + "codex-rs/core/tests/suite/turn_input_submission.rs": [ + "gpt-5.4", + "gpt-5.6-sol" + ], + "codex-rs/core/tests/suite/unified_exec.rs": [ + "gpt-5.2", + "gpt-5.4", + "gpt-5.5" + ], + "codex-rs/core/tests/suite/user_shell_cmd.rs": [ + "gpt-5.4" + ], + "codex-rs/core/tests/suite/view_image.rs": [ + "gpt-5.2", + "gpt-5.4", + "gpt-5.5" + ], + "codex-rs/core/tests/suite/web_search.rs": [ + "gpt-5.2", + "gpt-5.4", + "gpt-5.5", + "gpt-5.6-sol" + ], + "codex-rs/exec-server-protocol/src/protocol.rs": [ + "claude-plugin" + ], + "codex-rs/exec-server/tests/capability_discovery.rs": [ + "claude-plugin" + ], + "codex-rs/exec/src/cli_tests.rs": [ + "gpt-5.2-codex" + ], + "codex-rs/exec/src/event_processor_with_human_output_tests.rs": [ + "gpt-5.4" + ], + "codex-rs/exec/src/lib_tests.rs": [ + "gpt-5.4" + ], + "codex-rs/exec/src/main_tests.rs": [ + "gpt-5.2-codex" + ], + "codex-rs/exec/tests/event_processor_with_json_output.rs": [ + "gpt-5", + "gpt-5-mini" + ], + "codex-rs/exec/tests/suite/output_schema.rs": [ + "gpt-5.1" + ], + "codex-rs/exec/tests/suite/prompt_stdin.rs": [ + "gpt-5.1" + ], + "codex-rs/exec/tests/suite/resume.rs": [ + "gpt-5.1", + "gpt-5.1-high", + "gpt-5.2-codex" + ], + "codex-rs/ext/goal/tests/goal_extension_backend.rs": [ + "gpt-5", + "gpt-test" + ], + "codex-rs/ext/guardian-reviewer/src/metrics.rs": [ + "gpt-5.4", + "gpt-5.4_guardian" + ], + "codex-rs/ext/guardian-v2/src/async_scorer/budget_tests.rs": [ + "gpt-5.5" + ], + "codex-rs/ext/guardian-v2/src/async_scorer/extension_tests.rs": [ + "gpt-5.5", + "gpt-5.6-luna" + ], + "codex-rs/ext/guardian-v2/src/async_scorer/sampler.rs": [ + "gpt-5.6-luna" + ], + "codex-rs/ext/guardian-v2/src/async_scorer/sampler_tests.rs": [ + "gpt-5.6-luna" + ], + "codex-rs/ext/guardian-v2/src/async_scorer/trusted_tools.rs": [ + "claude-plugin" + ], + "codex-rs/ext/guardian-v2/src/sync_reviewer/lifecycle_tests.rs": [ + "gpt-5" + ], + "codex-rs/ext/history-notes/tests/history_notes_extension.rs": [ + "gpt-test" + ], + "codex-rs/ext/history-notes/tests/image_model_input.rs": [ + "gpt-5.5" + ], + "codex-rs/ext/image-generation/src/tests.rs": [ + "gpt-image-2" + ], + "codex-rs/ext/image-generation/src/tool.rs": [ + "gpt-image-2" + ], + "codex-rs/ext/mcp/src/cloud_plugin_tests.rs": [ + "gpt-5.5" + ], + "codex-rs/ext/mcp/src/plugin/provider_tests.rs": [ + "claude-plugin" + ], + "codex-rs/ext/mcp/tests/plugin_mcp.rs": [ + "gpt-5.5" + ], + "codex-rs/ext/memories/src/tests.rs": [ + "gpt-test" + ], + "codex-rs/ext/skills/src/loader/environment_tests.rs": [ + "claude-name", + "claude-plugin" + ], + "codex-rs/ext/skills/tests/executor_file_system_authority.rs": [ + "claude-plugin" + ], + "codex-rs/ext/skills/tests/skills_extension.rs": [ + "gpt-test" + ], + "codex-rs/external-agent-migration/src/detect/sessions/connectors_cla.rs": [ + "claude-code-sessions" + ], + "codex-rs/external-agent-migration/src/lib_tests.rs": [ + "claude-code" + ], + "codex-rs/external-agent-migration/src/service_tests.rs": [ + "CLAUDE-CODE", + "claude-plugin" + ], + "codex-rs/external-agent-migration/src/service_tests/plugins/basics.rs": [ + "claude-code", + "claude-code-plugins" + ], + "codex-rs/external-agent-migration/src/source/cla.rs": [ + "claude-code" + ], + "codex-rs/external-agent-migration/src/source_cla.rs": [ + "claude-code", + "claude-code-plugins", + "claude-plugins-official" + ], + "codex-rs/features/src/lib.rs": [ + "Claude-style" + ], + "codex-rs/feedback/src/lib.rs": [ + "gpt-5" + ], + "codex-rs/history/src/tests.rs": [ + "gpt-5" + ], + "codex-rs/hooks/src/engine/mod_tests.rs": [ + "gpt-test" + ], + "codex-rs/hooks/src/events/compact.rs": [ + "gpt-test" + ], + "codex-rs/hooks/src/events/post_tool_use.rs": [ + "gpt-test" + ], + "codex-rs/hooks/src/events/pre_tool_use.rs": [ + "gpt-test" + ], + "codex-rs/hooks/src/schema.rs": [ + "gpt-test" + ], + "codex-rs/lmstudio/src/client.rs": [ + "gpt-oss-20b" + ], + "codex-rs/lmstudio/src/lib.rs": [ + "gpt-oss-20b" + ], + "codex-rs/memories/write/src/prompts_tests.rs": [ + "gpt-5.3-codex" + ], + "codex-rs/memories/write/src/startup_tests.rs": [ + "gpt-5.4-mini" + ], + "codex-rs/model-provider-info/src/lib.rs": [ + "gpt-5.5", + "gpt-5.6-luna", + "gpt-5.6-sol", + "gpt-5.6-terra", + "gpt-6-astra", + "gpt-6-luna", + "gpt-6-sol", + "gpt-6.1-sol", + "gpt-oss" + ], + "codex-rs/model-provider/src/amazon_bedrock/catalog.rs": [ + "GPT-5.5", + "GPT-5.6", + "GPT-6", + "GPT-6-Astra", + "GPT-6.1", + "gpt-5.5", + "gpt-5.6-luna", + "gpt-5.6-sol", + "gpt-5.6-terra", + "gpt-6-astra", + "gpt-6-luna", + "gpt-6-sol", + "gpt-6.1-sol" + ], + "codex-rs/model-provider/src/amazon_bedrock/runtime_catalog_tests.rs": [ + "GPT-5.6", + "GPT-6", + "GPT-6-Astra", + "GPT-6.1", + "gpt-5.6-luna", + "gpt-5.6-sol", + "gpt-5.6-terra", + "gpt-6-astra", + "gpt-6-luna", + "gpt-6-sol", + "gpt-6.1-sol" + ], + "codex-rs/model-provider/src/provider.rs": [ + "GPT-5.5", + "GPT-5.6", + "GPT-6", + "GPT-6-Astra", + "GPT-6.1", + "gpt-5.5", + "gpt-5.6-luna", + "gpt-5.6-sol", + "gpt-5.6-terra", + "gpt-6-astra", + "gpt-6-luna", + "gpt-6-sol", + "gpt-6.1-sol" + ], + "codex-rs/models-manager/models.json": [ + "GPT-5.", + "GPT-5.5", + "GPT-5.6-Luna", + "GPT-5.6-Sol", + "GPT-5.6-Terra", + "GPT-6", + "GPT-6-Astra", + "GPT-6-Luna", + "GPT-6-Sol", + "GPT-6.", + "GPT-6.1", + "GPT-6.1-Sol", + "gpt-5.5", + "gpt-5.6-luna", + "gpt-5.6-sol", + "gpt-5.6-terra", + "gpt-6-astra", + "gpt-6-luna", + "gpt-6-sol", + "gpt-6.1-sol", + "gpt-daybreak-blue-latest", + "gpt-daybreak-red-latest" + ], + "codex-rs/models-manager/src/manager.rs": [ + "gpt-5.3-codex" + ], + "codex-rs/models-manager/src/manager_tests.rs": [ + "gpt-5.6-luna", + "gpt-5.6-sol", + "gpt-5.6-terra", + "gpt-6-sol", + "gpt-6-sol-other", + "gpt-image", + "gpt-overlay", + "gpt-overlay-experiment" + ], + "codex-rs/models-manager/src/model_info_overrides_tests.rs": [ + "gpt-5.5" + ], + "codex-rs/network-proxy/src/network_policy.rs": [ + "gpt-5.3-codex" + ], + "codex-rs/ollama/src/client.rs": [ + "llama3.2" + ], + "codex-rs/ollama/src/lib.rs": [ + "gpt-oss" + ], + "codex-rs/otel/README.md": [ + "gpt-5.1" + ], + "codex-rs/otel/src/metrics/tags.rs": [ + "gpt-5.1" + ], + "codex-rs/otel/tests/suite/manager_metrics.rs": [ + "gpt-4o", + "gpt-5.1" + ], + "codex-rs/otel/tests/suite/otel_export_routing_policy.rs": [ + "gpt-5.1" + ], + "codex-rs/otel/tests/suite/runtime_summary.rs": [ + "gpt-5.1" + ], + "codex-rs/otel/tests/suite/send.rs": [ + "gpt-5.1" + ], + "codex-rs/otel/tests/suite/snapshot.rs": [ + "gpt-5.1", + "gpt-5.6" + ], + "codex-rs/protocol/src/config_types.rs": [ + "GPT-5", + "gpt-5.2-codex" + ], + "codex-rs/protocol/src/error.rs": [ + "gpt-reserve" + ], + "codex-rs/protocol/src/models.rs": [ + "gpt-5.2" + ], + "codex-rs/protocol/src/openai_models.rs": [ + "gpt-5" + ], + "codex-rs/protocol/src/protocol.rs": [ + "gpt-5" + ], + "codex-rs/responses-api-proxy/src/dump.rs": [ + "gpt-5.4" + ], + "codex-rs/rollout-trace/src/inference.rs": [ + "gpt-test" + ], + "codex-rs/rollout-trace/src/reducer/code_cell_tests.rs": [ + "gpt-test" + ], + "codex-rs/rollout-trace/src/reducer/conversation_tests.rs": [ + "gpt-test" + ], + "codex-rs/rollout-trace/src/reducer/test_support.rs": [ + "gpt-test" + ], + "codex-rs/rollout-trace/src/reducer/tool/agents_tests.rs": [ + "gpt-test" + ], + "codex-rs/rollout-trace/src/reducer/tool/terminal_tests.rs": [ + "gpt-test" + ], + "codex-rs/rollout-trace/src/thread_tests.rs": [ + "gpt-previous", + "gpt-selected", + "gpt-test" + ], + "codex-rs/rollout-trace/src/writer.rs": [ + "gpt-test" + ], + "codex-rs/skills/src/assets/samples/imagegen/SKILL.md": [ + "gpt-image-1.5", + "gpt-image-2" + ], + "codex-rs/skills/src/assets/samples/imagegen/references/cli.md": [ + "gpt-image-", + "gpt-image-1.5", + "gpt-image-2" + ], + "codex-rs/skills/src/assets/samples/imagegen/references/codex-network.md": [ + "gpt-image-1.5" + ], + "codex-rs/skills/src/assets/samples/imagegen/references/image-api.md": [ + "gpt-image-1", + "gpt-image-1-mini", + "gpt-image-1.5", + "gpt-image-2" + ], + "codex-rs/skills/src/assets/samples/imagegen/references/prompting.md": [ + "gpt-image-1.5", + "gpt-image-2" + ], + "codex-rs/skills/src/assets/samples/imagegen/references/sample-prompts.md": [ + "gpt-image-1.5", + "gpt-image-2" + ], + "codex-rs/skills/src/assets/samples/imagegen/scripts/image_gen.py": [ + "gpt-image-", + "gpt-image-1", + "gpt-image-1-mini", + "gpt-image-1.5", + "gpt-image-2" + ], + "codex-rs/skills/src/assets/samples/openai-docs/SKILL.md": [ + "GPT-6", + "gpt-6-astra.md" + ], + "codex-rs/skills/src/assets/samples/openai-docs/references/latest-model.md": [ + "GPT-6", + "gpt-4.1", + "gpt-5.4", + "gpt-5.6-luna", + "gpt-5.6-terra", + "gpt-6", + "gpt-6-astra", + "gpt-6-astra.md", + "gpt-6-pro" + ], + "codex-rs/skills/src/assets/samples/openai-docs/references/model-migration.md": [ + "GPT-5.5", + "GPT-5.6", + "GPT-6", + "gpt-6-astra.md" + ], + "codex-rs/skills/src/assets/samples/openai-docs/references/prompting-guide.md": [ + "GPT-5.6", + "GPT-6", + "gpt-6-astra.md" + ], + "codex-rs/skills/src/assets/samples/openai-docs/references/upgrade-guide.md": [ + "GPT-6", + "GPT-6-specific", + "gpt-6-astra.md" + ], + "codex-rs/skills/src/assets/samples/openai-docs/references/upgrading-to-gpt-6-astra.md": [ + "GPT-4.1", + "GPT-4o", + "GPT-5.5", + "GPT-5.6", + "GPT-6", + "GPT-6.", + "gpt-5.6-luna", + "gpt-5.6-terra", + "gpt-6", + "gpt-6-astra", + "gpt-6-astra.md" + ], + "codex-rs/state/src/extract.rs": [ + "gpt-5", + "gpt-5.2-codex" + ], + "codex-rs/state/src/model/thread_metadata.rs": [ + "gpt-5" + ], + "codex-rs/state/src/runtime/test_support.rs": [ + "gpt-5" + ], + "codex-rs/thread-manager-sample/README.md": [ + "gpt-5.2" + ], + "codex-rs/thread-store/src/local/rollout_migration/line_parser_tests.rs": [ + "gpt-test" + ], + "codex-rs/thread-store/src/thread_metadata_sync.rs": [ + "gpt-5.2-codex" + ], + "codex-rs/tui/src/analytics/account_layout_tests.rs": [ + "GPT-5.5" + ], + "codex-rs/tui/src/analytics/consumer_refresh_tests.rs": [ + "gpt-5.5" + ], + "codex-rs/tui/src/analytics/test_fixtures.rs": [ + "GPT-5.5", + "GPT-5.6-Sol", + "GPT-5.6-Terra" + ], + "codex-rs/tui/src/analytics/test_support.rs": [ + "GPT-5.5" + ], + "codex-rs/tui/src/analytics/tokens_tests.rs": [ + "gpt-5" + ], + "codex-rs/tui/src/analytics_tests.rs": [ + "GPT-5.5", + "GPT-5.6-Sol", + "GPT-5.6-Terra" + ], + "codex-rs/tui/src/app/agents_overview_actions_tests.rs": [ + "gpt-5.2" + ], + "codex-rs/tui/src/app/app_server_event_targets.rs": [ + "gpt-5.4" + ], + "codex-rs/tui/src/app/config_persistence.rs": [ + "gpt-5.4", + "gpt-5.5", + "gpt-test" + ], + "codex-rs/tui/src/app/owned_transcript_tests.rs": [ + "gpt-test" + ], + "codex-rs/tui/src/app/startup_prompts.rs": [ + "GPT-5.4", + "GPT-6", + "gpt-5.4", + "gpt-5.4-mini", + "gpt-6-luna", + "gpt-6-sol" + ], + "codex-rs/tui/src/app/tests.rs": [ + "gpt-5.4", + "gpt-5.5", + "gpt-agent", + "gpt-plan", + "gpt-replacement", + "gpt-restored", + "gpt-test" + ], + "codex-rs/tui/src/app/tests/active_reconnect_tests.rs": [ + "gpt-test" + ], + "codex-rs/tui/src/app/tests/advanced_reasoning_tests.rs": [ + "gpt-5.4" + ], + "codex-rs/tui/src/app/tests/backend_banner_fallback_tests.rs": [ + "gpt-5.5", + "gpt-5.6-terra", + "gpt-reserve" + ], + "codex-rs/tui/src/app/tests/backend_banner_startup_tests.rs": [ + "gpt-5.5", + "gpt-5.6-terra" + ], + "codex-rs/tui/src/app/tests/background_task_defaults_tests.rs": [ + "GPT-6-Astra", + "gpt-5.2", + "gpt-5.5", + "gpt-6-astra", + "gpt-local-choice" + ], + "codex-rs/tui/src/app/tests/daybreak_tests.rs": [ + "gpt-5.6-sol", + "gpt-6-astra", + "gpt-6-astra-wm" + ], + "codex-rs/tui/src/app/tests/fresh_sparkle_tests.rs": [ + "gpt-5.5", + "gpt-6-astra" + ], + "codex-rs/tui/src/app/tests/local_command_scroll_tests.rs": [ + "gpt-5.4", + "gpt-5.5" + ], + "codex-rs/tui/src/app/tests/luna_reserve_recovery_tests.rs": [ + "gpt-5.5", + "gpt-5.6-terra", + "gpt-reserve" + ], + "codex-rs/tui/src/app/tests/model_catalog.rs": [ + "GPT-5.4", + "GPT-5.4.", + "GPT-6", + "gpt-5.2", + "gpt-5.4", + "gpt-5.4-mini", + "gpt-5.5", + "gpt-5.6-luna", + "gpt-5.6-sol", + "gpt-5.6-terra", + "gpt-6-luna", + "gpt-6-sol" + ], + "codex-rs/tui/src/app/tests/model_defaults_tests.rs": [ + "gpt-5.5", + "gpt-5.6-terra" + ], + "codex-rs/tui/src/app/tests/navigation_reconnect_tests.rs": [ + "gpt-test" + ], + "codex-rs/tui/src/app/tests/projectless_tests.rs": [ + "gpt-5.2" + ], + "codex-rs/tui/src/app/tests/realtime_requests.rs": [ + "gpt-test" + ], + "codex-rs/tui/src/app/tests/recap_generation_tests.rs": [ + "gpt-5.2" + ], + "codex-rs/tui/src/app/tests/safety_buffering.rs": [ + "gpt-5.6-luna", + "gpt-5.6-terra" + ], + "codex-rs/tui/src/app/tests/session_lifecycle_requests.rs": [ + "gpt-5.2", + "gpt-5.4" + ], + "codex-rs/tui/src/app/tests/startup_defaults_tests.rs": [ + "GPT-6.1-Sol", + "gpt-5.5" + ], + "codex-rs/tui/src/app/tests/startup_warnings_tests.rs": [ + "gpt-test" + ], + "codex-rs/tui/src/app/thread_events.rs": [ + "gpt-test" + ], + "codex-rs/tui/src/app/thread_session_state.rs": [ + "gpt-test" + ], + "codex-rs/tui/src/app/thread_title.rs": [ + "gpt-5.6-luna" + ], + "codex-rs/tui/src/app/thread_title_tests.rs": [ + "gpt-5.2" + ], + "codex-rs/tui/src/app_server_session.rs": [ + "gpt-5", + "gpt-5.2", + "gpt-5.4", + "gpt-5.5" + ], + "codex-rs/tui/src/app_server_session/external_agent_config_tests.rs": [ + "claude-code" + ], + "codex-rs/tui/src/app_server_session/provider_selection_tests.rs": [ + "gpt-5.2" + ], + "codex-rs/tui/src/app_server_session/reasoning_defaults_tests.rs": [ + "gpt-5.5" + ], + "codex-rs/tui/src/app_server_session/workspace_roots_tests.rs": [ + "gpt-5.2" + ], + "codex-rs/tui/src/bottom_pane/chat_composer.rs": [ + "gpt-5.4" + ], + "codex-rs/tui/src/bottom_pane/chat_composer/sparkle_tests.rs": [ + "gpt-5.5", + "gpt-6-astra" + ], + "codex-rs/tui/src/bottom_pane/chat_composer_effort_tests.rs": [ + "gpt-5.4" + ], + "codex-rs/tui/src/bottom_pane/effort_status_line_tests.rs": [ + "gpt-5.4" + ], + "codex-rs/tui/src/bottom_pane/list_selection_view.rs": [ + "gpt-4.1-codex", + "gpt-5.1-codex", + "gpt-5.1-codex-mini" + ], + "codex-rs/tui/src/bottom_pane/status_line_setup.rs": [ + "gpt-5", + "gpt-5-codex" + ], + "codex-rs/tui/src/bottom_pane/status_line_style.rs": [ + "gpt-5" + ], + "codex-rs/tui/src/bottom_pane/status_surface_preview.rs": [ + "gpt-5.2-codex" + ], + "codex-rs/tui/src/chatwidget/model_popups.rs": [ + "gpt-5.1-codex", + "gpt-5.1-codex-max", + "gpt-5.2" + ], + "codex-rs/tui/src/chatwidget/tests/app_server.rs": [ + "GPT-5.6", + "gpt-5", + "gpt-5.2", + "gpt-5.4", + "gpt-5.5", + "gpt-5.6-luna", + "gpt-default", + "gpt-plan" + ], + "codex-rs/tui/src/chatwidget/tests/backend_banners_tests.rs": [ + "gpt-5" + ], + "codex-rs/tui/src/chatwidget/tests/bedrock_catalog_tests.rs": [ + "gpt-6-astra", + "gpt-6.1-sol" + ], + "codex-rs/tui/src/chatwidget/tests/collaboration_catalog_tests.rs": [ + "gpt-5.2" + ], + "codex-rs/tui/src/chatwidget/tests/composer_submission.rs": [ + "gpt-5", + "gpt-5.4" + ], + "codex-rs/tui/src/chatwidget/tests/helpers.rs": [ + "gpt-5.2", + "gpt-5.4", + "gpt-5.6-sol" + ], + "codex-rs/tui/src/chatwidget/tests/history_replay.rs": [ + "gpt-5.4" + ], + "codex-rs/tui/src/chatwidget/tests/luna_reserve_usage_tests.rs": [ + "gpt-5.6-sol", + "gpt-reserve" + ], + "codex-rs/tui/src/chatwidget/tests/model_display_name_tests.rs": [ + "GPT-5.6", + "gpt-5.5", + "gpt-5.6-luna" + ], + "codex-rs/tui/src/chatwidget/tests/model_picker_tests.rs": [ + "gpt-5.5" + ], + "codex-rs/tui/src/chatwidget/tests/permissions.rs": [ + "gpt-test" + ], + "codex-rs/tui/src/chatwidget/tests/plan_mode.rs": [ + "gpt-5", + "gpt-5.5", + "gpt-5.6-luna", + "gpt-5.6-terra" + ], + "codex-rs/tui/src/chatwidget/tests/popups_and_settings.rs": [ + "gpt-5.2", + "gpt-5.4", + "gpt-5.5", + "gpt-5.6-terra" + ], + "codex-rs/tui/src/chatwidget/tests/session_model_selection_tests.rs": [ + "gpt-5.5", + "gpt-6-astra" + ], + "codex-rs/tui/src/chatwidget/tests/slash_commands.rs": [ + "gpt-5.2", + "gpt-5.4", + "gpt-5.5", + "gpt-5.6-terra" + ], + "codex-rs/tui/src/chatwidget/tests/sparkle_submission_tests.rs": [ + "gpt-6-astra" + ], + "codex-rs/tui/src/chatwidget/tests/status_and_layout.rs": [ + "gpt-5", + "gpt-5.2", + "gpt-5.4", + "gpt-5.5", + "gpt-5.6-sol" + ], + "codex-rs/tui/src/chatwidget/tests/status_command_tests.rs": [ + "GPT-5", + "GPT-5.4", + "GPT-5.5", + "gpt-5-mini", + "gpt-5.4", + "gpt-5.5" + ], + "codex-rs/tui/src/chatwidget/thread_usage_tests.rs": [ + "gpt-5.4" + ], + "codex-rs/tui/src/daybreak.rs": [ + "gpt-5.6-sol", + "gpt-6-astra", + "gpt-6-astra-wm" + ], + "codex-rs/tui/src/debug_config.rs": [ + "gpt-5", + "gpt-5.6-terra" + ], + "codex-rs/tui/src/dynamic_tools_tests.rs": [ + "gpt-5.5" + ], + "codex-rs/tui/src/external_agent_config_migration/source.rs": [ + "claude-code" + ], + "codex-rs/tui/src/history_cell/tests.rs": [ + "gpt-3.5-turbo", + "gpt-5", + "gpt-5.6-sol" + ], + "codex-rs/tui/src/managed_new_thread_defaults.rs": [ + "gpt-5.4" + ], + "codex-rs/tui/src/model_catalog.rs": [ + "gpt-6-luna", + "gpt-reserve" + ], + "codex-rs/tui/src/model_migration.rs": [ + "gpt-5", + "gpt-5-codex", + "gpt-5-codex-mini", + "gpt-5.1", + "gpt-5.1-codex-max", + "gpt-5.1-codex-mini", + "gpt-5.2-codex", + "gpt-new", + "gpt-old" + ], + "codex-rs/tui/src/multi_agents.rs": [ + "gpt-5" + ], + "codex-rs/tui/src/startup_orchestration.rs": [ + "gpt-oss" + ], + "codex-rs/tui/src/status/tests.rs": [ + "gpt-5.1-codex", + "gpt-5.1-codex-max", + "gpt-5.5", + "gpt-5.6-sol" + ], + "codex-rs/tui/src/status/thread_usage_tests.rs": [ + "GPT-5", + "GPT-5.4", + "GPT-5.6", + "gpt-5-mini", + "gpt-5.4", + "gpt-5.6-sol" + ], + "codex-rs/tui/src/temporary_structured_request_tests.rs": [ + "gpt-5.2" + ], + "codex-rs/tui/src/transcript_view_tests.rs": [ + "gpt-test" + ], + "codex-rs/tui/tests/suite/directory_trust.rs": [ + "gpt-5.6-terra" + ], + "codex-rs/tui/tests/suite/external_editor_tests.rs": [ + "GPT-5.6-Terra" + ], + "codex-rs/tui/tests/suite/focus_palette.rs": [ + "GPT-5.6-Terra", + "gpt-5.6-terra", + "gpt-6-sol" + ], + "codex-rs/tui/tests/suite/reconnect.rs": [ + "gpt-5.6-terra" + ], + "codex-rs/tui/tests/suite/resize_reflow.rs": [ + "gpt-5.4", + "gpt-5.6-terra", + "gpt-6-sol" + ], + "codex-rs/utils/cli/src/config_override.rs": [ + "gpt-5.1", + "gpt-5.2", + "o3" + ], + "codex-rs/utils/plugins/src/plugin_namespace.rs": [ + "claude-plugin" + ], + "docs/agents.md": [ + "claude-haiku-4.5", + "claude-opus-4.8", + "claude-sonnet-4.6", + "gemini-3.1-pro", + "gemini-3.5-flash", + "gpt-5.1-codex-max", + "gpt-5.4", + "gpt-5.4-mini", + "gpt-5.5", + "qwen-3-coder" + ], + "docs/auto-drive.md": [ + "gpt-5.1" + ], + "docs/config.md": [ + "claude-haiku-4.5", + "claude-opus-4.8", + "claude-proxy", + "claude-sonnet-4.6", + "gemini-3.1-pro", + "gemini-3.5-flash", + "gpt-3.5-turbo", + "gpt-4o", + "gpt-5.1", + "gpt-5.1-codex", + "gpt-5.1-codex-max", + "gpt-5.4", + "gpt-5.4-mini", + "gpt-5.5", + "o3", + "o4-mini", + "qwen-3-coder" + ], + "docs/example-config.md": [ + "GPT-5", + "gpt-5.1-codex-max", + "gpt-5.2" + ], + "docs/exec.md": [ + "gpt-5.1", + "gpt-5.1-codex" + ], + "docs/faq.md": [ + "GPT-5.4", + "gpt-5.4", + "o3", + "o4-mini" + ], + "docs/integration-zed.md": [ + "gpt-5.1", + "gpt-5.1-codex" + ], + "docs/slash-commands.md": [ + "claude-haiku-4.5", + "claude-opus-4.8", + "claude-sonnet-4.6", + "gemini-3.1-pro", + "gemini-3.5-flash", + "gpt-5.1-codex-max", + "gpt-5.4", + "gpt-5.4-mini", + "gpt-5.5", + "qwen-3-coder" + ], + "scripts/code-hook-smoke.sh": [ + "gpt-5.1-codex" + ], + "scripts/mock_responses_websocket_server.py": [ + "gpt-5.2" + ], + "scripts/openai-proxy.js": [ + "gpt-4o-mini" + ], + "sdk/python/README.md": [ + "gpt-5" + ], + "sdk/python/docs/api-reference.md": [ + "gpt-5.4" + ], + "sdk/python/docs/getting-started.md": [ + "gpt-5.4" + ], + "sdk/python/examples/01_quickstart_constructor/async.py": [ + "gpt-5.4" + ], + "sdk/python/examples/01_quickstart_constructor/sync.py": [ + "gpt-5.4" + ], + "sdk/python/examples/02_turn_run/async.py": [ + "gpt-5.4" + ], + "sdk/python/examples/02_turn_run/sync.py": [ + "gpt-5.4" + ], + "sdk/python/examples/03_turn_stream_events/async.py": [ + "gpt-5.4" + ], + "sdk/python/examples/03_turn_stream_events/sync.py": [ + "gpt-5.4" + ], + "sdk/python/examples/05_existing_thread/async.py": [ + "gpt-5.4" + ], + "sdk/python/examples/05_existing_thread/sync.py": [ + "gpt-5.4" + ], + "sdk/python/examples/06_thread_lifecycle_and_controls/async.py": [ + "gpt-5.4" + ], + "sdk/python/examples/06_thread_lifecycle_and_controls/sync.py": [ + "gpt-5.4" + ], + "sdk/python/examples/07_image_and_text/async.py": [ + "gpt-5.4" + ], + "sdk/python/examples/07_image_and_text/sync.py": [ + "gpt-5.4" + ], + "sdk/python/examples/08_local_image_and_text/async.py": [ + "gpt-5.4" + ], + "sdk/python/examples/08_local_image_and_text/sync.py": [ + "gpt-5.4" + ], + "sdk/python/examples/09_async_parity/sync.py": [ + "gpt-5.4" + ], + "sdk/python/examples/10_error_handling_and_retry/async.py": [ + "gpt-5.4" + ], + "sdk/python/examples/10_error_handling_and_retry/sync.py": [ + "gpt-5.4" + ], + "sdk/python/examples/11_cli_mini_app/async.py": [ + "gpt-5.4" + ], + "sdk/python/examples/11_cli_mini_app/sync.py": [ + "gpt-5.4" + ], + "sdk/python/examples/12_turn_params_kitchen_sink/async.py": [ + "gpt-5.4" + ], + "sdk/python/examples/12_turn_params_kitchen_sink/sync.py": [ + "gpt-5.4" + ], + "sdk/python/examples/13_model_select_and_turn_params/async.py": [ + "gpt-5.4" + ], + "sdk/python/examples/13_model_select_and_turn_params/sync.py": [ + "gpt-5.4" + ], + "sdk/python/examples/14_turn_controls/async.py": [ + "gpt-5.4" + ], + "sdk/python/examples/14_turn_controls/sync.py": [ + "gpt-5.4" + ], + "sdk/python/tests/test_client_rpc_methods.py": [ + "gpt-5" + ], + "sdk/python/tests/test_real_app_server_integration.py": [ + "gpt-5.4" + ], + "sdk/typescript/tests/run.test.ts": [ + "gpt-test-1" + ], + "code-rs/core/src/modern_models.rs": [ + "GPT-5.6", + "GPT-6", + "GPT-6.1", + "gpt-5.6-luna", + "gpt-5.6-sol", + "gpt-5.6-terra", + "gpt-6-astra", + "gpt-6-luna", + "gpt-6-sol", + "gpt-6.1-sol" + ], + "code-rs/core/src/client_model_tests.rs": [ + "gpt-5.5", + "gpt-6-terra", + "gpt-6.1-sol" + ] + } +} diff --git a/docs/model-support.md b/docs/model-support.md new file mode 100644 index 000000000000..c70aadc10ba4 --- /dev/null +++ b/docs/model-support.md @@ -0,0 +1,232 @@ +# Modern model support + +Evidence checked on 2026-10-10 against the fork base +`e7d4a5d7c7183eaa6b433f7f9b5e9aa04336cc5c`. This change preserves its +Responses-Lite include fix. No production credentials or unconfigured defaults +were modified. Merge and deployment require operator approval. + +## Acceptance status + +Independent adversarial review verdict: **Claude Opus 5.5 executable resolution verified; overall interactive Claude gap remains as documented**. +The executable resolution discrepancy between older Homebrew Claude (`/opt/homebrew/bin/claude` 2.1.191) and modern npm Claude (`/Users/byte/.npm-global/bin/claude` 2.1.296) has been repaired through explicit agent command configuration without hardcoding operator paths in portable application defaults. +Claude Opus 5.5 authenticated execution, streaming, tool calling, and live child delegation from GPT-6.1 Sol are verified. Sonnet 5.5 and Haiku 5.5 remain verified. Opus 5.5 is enabled in the operator's agent configuration. +As previously audited, Claude execution is via CLI agents; a native Anthropic Messages transport for the interactive main agent remains an external gap outside CLI delegation. This branch is presented as a draft for PR #2 review, not approved for merge, installation or deployment. + +## Support matrix + +“Documented” means the identifier and capabilities appear in the provider's +own documentation; it does not imply every account has access. “Mocked” means +Every Code's real request builder/transport was exercised against a local server. + +| Exact ID | Public context / output tokens | Reasoning / effort | Execution evidence | +| --- | --- | --- | --- | +| `gpt-6.1-sol` | 1,050,000 / 128,000 | low, medium, high, xhigh, max | HTTP/WS Lite + public Responses mocked; account discovery; authenticated ModelClient and built CLI returned MODEL_OK; actual built-CLI child delegation returned DELEGATION_OK | +| `gpt-6-astra` | 1,050,000 / 128,000 | low, medium, high, xhigh, max | Official docs; account discovery; HTTP/WS Lite + public Responses mocked; inference not smoke tested | +| `gpt-6-sol` | 1,050,000 / 128,000 | none, low, medium, high, xhigh, max | Official docs; account discovery; HTTP/WS Lite + public Responses mocked; inference not smoke tested | +| `gpt-6-luna` | 1,050,000 / 128,000 | none, low, medium, high, xhigh, max | Official docs; account discovery; HTTP/WS Lite + public Responses mocked; inference not smoke tested | +| `gpt-5.6-sol` | 1,050,000 / 128,000 | none, low, medium, high, xhigh, max | Official docs; account discovery; HTTP/WS Lite + public Responses mocked; inference not smoke tested | +| `gpt-5.6-terra` | 1,050,000 / 128,000 | none, low, medium, high, xhigh, max | Official docs; account discovery; HTTP/WS Lite + public Responses mocked; inference not smoke tested | +| `gpt-5.6-luna` | 1,050,000 / 128,000 | none, low, medium, high, xhigh, max | Official docs; account discovery; HTTP/WS Lite + public Responses mocked; inference not smoke tested | +| `claude-opus-5-5` | 1,000,000 / 128,000 | low, medium, high, xhigh, max; always adaptive thinking | Official ID; exact delegation arguments mocked; authenticated modern CLI (2.1.296 via `/Users/byte/.npm-global/bin/claude`) returned MODEL_OK; streaming + tool calling passed (OPUS_TEST_OK); real child delegation from GPT-6.1 Sol returned OPUS_DELEGATION_OK; enabled in agent configuration | +| `claude-sonnet-5-5` | 1,000,000 / 128,000 | low, medium, high, xhigh, max; adaptive by default | Exact delegation mocked; authenticated installed CLI returned MODEL_OK under exact modelUsage ID; streaming + Read tool call returned MODEL_TOOL_OK; real child delegation from GPT-6.1 Sol returned SONNET_DELEGATION_OK | +| `claude-haiku-5-5` | 1,000,000 / 128,000 | low, medium, high, xhigh, max; adaptive by default | Exact delegation mocked; authenticated installed CLI returned MODEL_OK under exact modelUsage ID; real child delegation from GPT-6.1 Sol returned HAIKU_DELEGATION_OK | + +For public OpenAI models, documented maximum input is 922,000 tokens. OAuth is a +separate backend contract: observed normal context is 272,000 and maximum context +872,000; Every Code caps OAuth context at 872,000 and compaction at 784,800. These +limits were read from the backend/bundled catalog, not established with a costly +context saturation test. OAuth omits public `none` choices; its advertised `ultra` +is not newly enabled by this change. Public `minimal` is not an advertised modern +model effort. Extra third-party variants, `gpt-6-terra`, and unknown first-party +GPT-6/5.6 IDs are not advertised or substituted. + +GPT-6.1 Sol remains its exact ID everywhere. A live request using the bundled +0.153.0 client floor failed with model-not-supported. The same authorized account, +model and payload succeeded with the released 0.162.1 Codex protocol. The verified +floor applies only to OAuth backend requests and OAuth discovery; public API and +custom-provider headers retain their existing version behavior. Local account +discovery with 0.162.1 lists all seven exact OpenAI IDs. Discovery with the older +version omitted GPT-6.1, GPT-6 Sol and GPT-6 Luna. + +Claude execution is through the existing external Claude Code agent integration. +There is no native Anthropic Messages transport in this application. Claude Code +owns authentication, adaptive thinking, native request parameters, stream parsing, +and tool serialization. The change does not invent Anthropic thinking budgets or +send OpenAI reasoning fields to Claude. A Claude model selected for the native +OpenAI transport fails with guidance to use a Claude agent or configured compatible +proxy. Third-party Claude names are not treated as official Anthropic IDs. + +## Architecture and audit map + +The literal inventory in [model-support-inventory.json](model-support-inventory.json) +covers tracked Rust and JS source, scripts, tests, configuration, prompts, +documentation and the read-only mirror. +It includes historical fixtures and filename candidates, not just executable IDs; +these are not all availability claims. `codex-rs` is a read-only upstream mirror. + +| Concern | Actual owners and decisions | +| --- | --- | +| Catalog / interactive picker | `core/src/modern_models.rs`, `common/src/model_presets.rs`, `tui/src/{chatwidget,remote_model_presets}.rs`; one modern registry, exact IDs, GPT-6.1 first in curated order, no first-party modern advertisements on an undiscovered custom endpoint | +| Model metadata / limits | `core/src/{model_family,config,reasoning}.rs`; public specifications separated from observed OAuth limits; modern custom endpoints get conservative provider metadata rather than the OpenAI catalog | +| Authentication / transport | `core/src/{auth,model_provider_info,default_client,client}.rs`, `core/src/remote_models/mod.rs`, `code-version/src/lib.rs`; existing OAuth and API-key mechanisms retained, no new authentication integration | +| Lite request behavior | `core/src/client_common.rs` and `client.rs`; encrypted reasoning include only, store false, parallel false, all_turns reasoning context, normal HTTP and WS paths; disabling summary preserves effort/context | +| Agents / aliases / handoffs | `core/src/{agent_defaults,agent_tool,slash_commands}.rs`, `core/src/config/defaults.rs`; exact modern agent model args, explicit --model/-m overrides, distinct configured role names, supported Claude effort flags; older Claude version aliases retain exact older IDs | +| Coordinator / background routes | `code-auto-drive-core/src/auto_coordinator.rs`, `core/src/config_types.rs`; GPT-6.1 route with low through max; actual API IDs extracted from agent args instead of code-prefixed agent slugs; configured unavailable models diagnose; invalid-model errors do not switch models | +| Retry boundaries | `core/src/{error,client}.rs`, `core/src/codex/streaming.rs`; model/parameter 400 errors terminal in request, WS, and outer turn loops; WS 404/405 transport fallback remains available | +| Other providers | Existing Gemini, Antigravity, Copilot, Qwen, Ollama, Azure and OpenRouter adapters remain in their existing execution families; shared names do not establish modern OpenAI proxy capabilities | + +Existing main/review defaults (GPT-5.5) and coordinator default (GPT-5.1) are retained +to preserve established routing. For new configurations, GPT-6.1 Sol is the +recommended general coding, planning, investigation and review choice after +account validation. Sonnet 5.5 is the recommended available Claude delegation +choice; Opus 5.5 requires an operator-managed CLI update before use. + +Upstream investigation used GitHub read-only APIs, without adding or writing an +upstream remote: #615's Lite store=false behavior is already present; open #618's +removal of codex-lite was independently merged in the fork's base; #621's +provider-aware compatibility/catalog work is substantially present, with Terra +extended-context support corrected here; issue #625's unbounded invalid-request +loop is addressed across request and outer retry boundaries. No blind cherry-pick +was performed. The fork's embedded model minimum version was additionally found +stale by live execution; the read-only mirror was left untouched. + +## Role configuration + +Existing profiles, review/planning settings, Auto Drive model settings, named +`[[agents]]` and `[subagents]` commands remain configurable. For example: + +```toml +model = "gpt-6.1-sol" +review_model = "gpt-6.1-sol" +model_reasoning_effort = "max" + +[auto_drive] +model = "gpt-6.1-sol" +model_reasoning_effort = "high" + +[[agents]] +name = "investigation" +command = "coder" +args = ["--model", "gpt-6.1-sol"] +read-only = true + +[[agents]] +name = "review" +command = "claude" +args = ["--model", "claude-sonnet-5-5", "--effort", "high"] +read-only = true + +[[agents]] +name = "claude-opus-5-5" +command = "/Users/byte/.npm-global/bin/claude" +enabled = true + +[[agents]] +name = "claude-sonnet-5-5" +command = "/Users/byte/.npm-global/bin/claude" +enabled = true + +[[agents]] +name = "claude-haiku-5-5" +command = "/Users/byte/.npm-global/bin/claude" +enabled = true +``` + +When multiple installations of external agent CLIs exist on PATH (for example an +older Homebrew installation and a newer npm-global installation), specify the exact +binary path via `command` in `config.toml`. Application defaults remain portable +(`claude`) and do not embed operator-specific filesystem paths. Opus 5.5 requires +Claude Code >=2.1.280; after validation with an authorized compatible CLI, configure +and enable the agent with `enabled = true`. + +## macOS Apple Silicon build and installation + +Install Xcode Command Line Tools, Git, Node/pnpm if building JS components, and a +Rust toolchain satisfying the lockfile's MSRV. The checkout pins Rust 1.90 but the +current locked dependencies require newer Rust; this validation used existing +Homebrew Rust 1.96 through a repository-local rustup toolchain, without changing +an installed binary or global toolchain. Build from the fork feature branch: + +```sh +git clone https://github.com/ComputerByte/Every-code.git +cd Every-code +git checkout feat/modern-model-support +rustup toolchain install stable +RUSTUP_TOOLCHAIN=stable CARGO_NET_GIT_FETCH_WITH_CLI=true PROFILE=dev ./build-fast.sh +./code-rs/target/debug/code --version +./code-rs/target/debug/code exec -m gpt-6.1-sol -c model_reasoning_effort=low "Reply with MODEL_OK" +``` + +Select a compatible newer toolchain using `RUSTUP_TOOLCHAIN` if required. The build +prints the binary artifact path and creates repository-local links. For an optimized +build, use `PROFILE=release`. After operator approval, install the printed artifact +under the desired name/path (for example `~/.local/bin/coder`) and ensure delegated +`[[agents]].command` points to it. Do not overwrite an installed binary before +approval. Claude Opus 5.5 separately requires Claude Code >=2.1.280; this work did +not update it. Opus 5.5 is disabled in the default agent set; after verifying CLI +version and account access, configure a named Claude agent with +`args = ["--model", "claude-opus-5-5"]` and `enabled = true`. + +The ignored live regression can be run with an explicitly authorized existing +OAuth file, read into externally managed in-memory authentication with refresh and +credential writes disabled: + +```sh +EVERY_CODE_SMOKE_AUTH_PATH=/path/to/existing/auth.json \ + cargo test --manifest-path code-rs/Cargo.toml -p code-core --lib \ + authenticated_gpt_6_1_sol_executes_without_substitution -- --ignored +``` + +Do not enable request debug logging during authenticated validation. Public +OpenAI API-key inference, live OAuth WebSocket/tool round trips, parallel/background +cross-provider agent handoffs, and context saturation remain separately unverified. +Exact GPT-6.1 child create/wait/result cycle and real delegation to Claude Opus 5.5, +Sonnet 5.5, and Haiku 5.5 were verified live through the built CLI. + +## Validation + +| Check | Final result | +| --- | --- | +| `code-auto-drive-core` unit suite | 84 passed | +| `code-common` unit suite | 29 passed | +| `code-core` unit suite | 808 passed; 3 ignored | +| `code-tui` unit suite | 232 passed | +| `code-version` unit suite | 12 passed | +| Provider discovery, delegation wake, tool selection integration suites | 7 passed | +| Required `PROFILE=dev ./build-fast.sh` | Passed without warnings on Apple Silicon / Rust 1.96; includes Rust type checking | +| `git diff --check` | Passed; rustfmt was not run, as repository AGENTS.md prohibits it | +| Strict Clippy | Failed on two unchanged `git-tooling/src/ghost_commits.rs` redundant closures (lines 87/92); reproduced on the exact base commit | +| Authenticated GPT-6.1 | Exact ModelClient HTTP request, final built CLI MODEL_OK, and real child create/wait/result cycle passed; no credential changes | +| Authenticated Claude | Exact Sonnet/Haiku/Opus 5.5 passed; Sonnet and Opus streaming + tool use passed; child delegation from GPT-6.1 Sol to Opus 5.5, Sonnet 5.5, and Haiku 5.5 passed live; Opus 5.5 enabled in agent configuration | + +The three ignored core tests are the opt-in authenticated check and two existing +long-running unified-exec tests. The opt-in authenticated check was additionally +run successfully with authorized existing OAuth credentials. No JavaScript source +changed; JS-specific build/typecheck suites were not run. + +Tests were run serially to isolate existing tests that mutate process environment. +The global Git URL rewrite was excluded for unit fixtures expecting HTTPS URLs. +Initial stale expectations for context limits/routes, a test JWT fixture, and the +routing editor's fixed cursor index were repaired; final relevant suites have no +failures. The strict-Clippy baseline failure remains, and is not reported as green. + +The built binary validated after the Opus gate and resolution has SHA-256 +`a61f1b9318afc3bbad847d983f3a7d009952531e1530208baf3f800f936a0f1c`. +Sanitized local validation receipts remain outside tracked source; credentials, +account files and authenticated request logs are not included in this change. + +## Sources + +- [OpenAI GPT-6.1 Sol](https://developers.openai.com/api/docs/models/gpt-6.1-sol) +- [OpenAI GPT-6 Astra](https://developers.openai.com/api/docs/models/gpt-6-astra) +- [OpenAI GPT-6 Sol](https://developers.openai.com/api/docs/models/gpt-6-sol) +- [OpenAI GPT-6 Luna](https://developers.openai.com/api/docs/models/gpt-6-luna) +- [OpenAI GPT-5.6 Sol](https://developers.openai.com/api/docs/models/gpt-5.6-sol) +- [OpenAI GPT-5.6 Terra](https://developers.openai.com/api/docs/models/gpt-5.6-terra) +- [OpenAI GPT-5.6 Luna](https://developers.openai.com/api/docs/models/gpt-5.6-luna) +- [Released Codex 0.162.1](https://github.com/openai/codex/releases/tag/rust-v0.162.1) +- [Anthropic model overview](https://platform.claude.com/docs/en/models/overview) +- [Anthropic effort interface](https://platform.claude.com/docs/en/build-with-claude/effort) +- [Claude Code model configuration](https://code.claude.com/docs/en/model-config) +- [Claude Code CLI reference](https://code.claude.com/docs/en/cli-reference) +- [Upstream #615](https://github.com/just-every/code/pull/615), [#618](https://github.com/just-every/code/pull/618), [#621](https://github.com/just-every/code/pull/621), [#625](https://github.com/just-every/code/issues/625)