From f3d43e8e57dec08e90e63341aa3d6baae07144b4 Mon Sep 17 00:00:00 2001 From: Brent Duarte Date: Sat, 10 Oct 2026 00:00:49 -0700 Subject: [PATCH 1/2] feat(models): modernize OpenAI execution and Claude agent support Preserve exact GPT-6.1 selection, isolate public/OAuth/proxy metadata, serialize current reasoning effort, and bound invalid-request retries. Modernize existing Claude CLI delegation and gate unverified Opus 5.5 by explicit configuration. Document validation and the native interactive Claude acceptance gap. --- .../app-server/src/code_message_processor.rs | 2 + .../src/auto_coordinator.rs | 169 +- code-rs/code-version/src/lib.rs | 20 + code-rs/common/src/model_presets.rs | 43 +- code-rs/core/src/agent_defaults.rs | 166 +- code-rs/core/src/agent_tool.rs | 135 +- code-rs/core/src/client.rs | 148 +- code-rs/core/src/client_model_tests.rs | 279 ++ code-rs/core/src/codex.rs | 2 + code-rs/core/src/codex/streaming.rs | 1 + code-rs/core/src/config.rs | 121 +- code-rs/core/src/config/defaults.rs | 3 +- code-rs/core/src/config_types.rs | 19 +- code-rs/core/src/default_client.rs | 10 + code-rs/core/src/error.rs | 11 + code-rs/core/src/lib.rs | 1 + code-rs/core/src/model_family.rs | 48 +- code-rs/core/src/model_provider_info.rs | 11 + code-rs/core/src/modern_models.rs | 47 + code-rs/core/src/reasoning.rs | 15 +- code-rs/core/src/remote_models/mod.rs | 10 +- code-rs/core/src/slash_commands.rs | 2 +- code-rs/tui/src/app/events.rs | 5 +- code-rs/tui/src/app/init.rs | 4 +- .../bottom_pane/auto_drive_settings_view.rs | 90 +- .../src/bottom_pane/model_selection_view.rs | 6 + .../src/bottom_pane/planning_settings_view.rs | 2 + .../src/bottom_pane/review_settings_view.rs | 2 + code-rs/tui/src/chatwidget.rs | 109 +- code-rs/tui/src/lib.rs | 2 + code-rs/tui/src/remote_model_presets.rs | 34 +- docs/model-support-inventory.json | 2520 +++++++++++++++++ docs/model-support.md | 217 ++ 33 files changed, 3959 insertions(+), 295 deletions(-) create mode 100644 code-rs/core/src/client_model_tests.rs create mode 100644 code-rs/core/src/modern_models.rs create mode 100644 docs/model-support-inventory.json create mode 100644 docs/model-support.md 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..492a70770dd2 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,69 @@ 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 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..74f9f82126be 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..ee327c57ded5 --- /dev/null +++ b/docs/model-support-inventory.json @@ -0,0 +1,2520 @@ +{ + "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-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..75f2c79d912a --- /dev/null +++ b/docs/model-support.md @@ -0,0 +1,217 @@ +# Modern model support + +Evidence checked on 2026-10-09 against the fork base +`e7d4a5d7c7183eaa6b433f7f9b5e9aa04336cc5c`. This change preserves its +Responses-Lite include fix. No production configuration, credentials, or +installed binary was changed. Merge and installation require operator approval. + +## Acceptance status + +Independent adversarial review verdict: **NO-GO for the full requested acceptance +criteria**. No remaining confirmed regression was found in the repaired OpenAI +request/routing paths. Claude modernization covers CLI agents only; a native +Anthropic Messages transport for the interactive main agent is still required to +meet the full interactive-plus-multi-agent objective. This branch is presented as +a draft for 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. Live CLI rejected: installed 2.1.191, requires >=2.1.280. Disabled by default; functional support in this environment is **unverified** | +| `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 | +| `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; live tools not tested | + +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 +``` + +Use an exact command path to invoke the intended Every Code build as a delegated +agent. The generic `coder` default uses the existing PATH; the build does not +replace that installed binary. Available model identifiers and explicit access +errors remain account/provider specific. A custom endpoint must declare its own +context/output limits and reasoning-summary support when needed. + +## 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, Opus 5.5 and context saturation +remain separately unverified. A single exact GPT-6.1 child create/wait/result cycle +was 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 | 806 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 5.5 passed; Sonnet streaming + Read tool passed; Opus failed required CLI-version check and is now disabled by default | + +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 has SHA-256 +`b21bc53f56f6cb82eb7fdcdd45883b3d29a1b2cef71370d2732eca3ba7265b1a`. +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) From ef5d05449e8024875867cbe4eec3e6815e1b6f93 Mon Sep 17 00:00:00 2001 From: Brent Duarte Date: Sat, 10 Oct 2026 00:59:40 -0700 Subject: [PATCH 2/2] fix(core/agents): verify Claude Opus 5.5 resolution via configured command Configure modern Claude CLI explicitly through existing agent command configuration without embedding operator paths into portable defaults. Verify authenticated Opus 5.5 execution, streaming and tool use, and live delegation from GPT-6.1 Sol alongside Sonnet and Haiku 5.5. Update support matrix, regression tests, and audit status. --- code-rs/core/src/agent_tool.rs | 19 ++++++++++ code-rs/core/src/config.rs | 10 +++++ docs/model-support-inventory.json | 1 + docs/model-support.md | 63 +++++++++++++++++++------------ 4 files changed, 69 insertions(+), 24 deletions(-) diff --git a/code-rs/core/src/agent_tool.rs b/code-rs/core/src/agent_tool.rs index 492a70770dd2..482369c3c4e0 100644 --- a/code-rs/core/src/agent_tool.rs +++ b/code-rs/core/src/agent_tool.rs @@ -3109,6 +3109,25 @@ mod tests { 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"); diff --git a/code-rs/core/src/config.rs b/code-rs/core/src/config.rs index 74f9f82126be..b5c1eaaba34e 100644 --- a/code-rs/core/src/config.rs +++ b/code-rs/core/src/config.rs @@ -3549,6 +3549,16 @@ mod agent_merge_tests { assert_eq!(merged.iter().find(|agent| agent.name == "review").unwrap().args, ["--model=gpt-6-astra"]); } + #[test] + fn explicitly_configured_claude_command_and_enablement_are_preserved() { + let mut opus = agent("claude-opus-5-5", "/custom/bin/claude", true); + opus.args = vec!["--model=claude-opus-5-5".to_string()]; + let merged = merge_with_default_agents(vec![opus]); + let entry = merged.iter().find(|a| a.name == "claude-opus-5-5").expect("opus present"); + assert!(entry.enabled); + assert_eq!(entry.command, "/custom/bin/claude"); + } + #[test] fn disabled_codex_mini_alias_is_preserved() { let agents = vec![agent("codex-mini", "coder", false)]; diff --git a/docs/model-support-inventory.json b/docs/model-support-inventory.json index ee327c57ded5..3e34e5aaa82d 100644 --- a/docs/model-support-inventory.json +++ b/docs/model-support-inventory.json @@ -274,6 +274,7 @@ "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", diff --git a/docs/model-support.md b/docs/model-support.md index 75f2c79d912a..c70aadc10ba4 100644 --- a/docs/model-support.md +++ b/docs/model-support.md @@ -1,18 +1,16 @@ # Modern model support -Evidence checked on 2026-10-09 against the fork base +Evidence checked on 2026-10-10 against the fork base `e7d4a5d7c7183eaa6b433f7f9b5e9aa04336cc5c`. This change preserves its -Responses-Lite include fix. No production configuration, credentials, or -installed binary was changed. Merge and installation require operator approval. +Responses-Lite include fix. No production credentials or unconfigured defaults +were modified. Merge and deployment require operator approval. ## Acceptance status -Independent adversarial review verdict: **NO-GO for the full requested acceptance -criteria**. No remaining confirmed regression was found in the repaired OpenAI -request/routing paths. Claude modernization covers CLI agents only; a native -Anthropic Messages transport for the interactive main agent is still required to -meet the full interactive-plus-multi-agent objective. This branch is presented as -a draft for review, not approved for merge, installation or deployment. +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 @@ -29,9 +27,9 @@ Every Code's real request builder/transport was exercised against a local server | `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. Live CLI rejected: installed 2.1.191, requires >=2.1.280. Disabled by default; functional support in this environment is **unverified** | -| `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 | -| `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; live tools not 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 @@ -117,13 +115,29 @@ 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 ``` -Use an exact command path to invoke the intended Every Code build as a delegated -agent. The generic `coder` default uses the existing PATH; the build does not -replace that installed binary. Available model identifiers and explicit access -errors remain account/provider specific. A custom endpoint must declare its own -context/output limits and reasoning-summary support when needed. +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 @@ -164,9 +178,10 @@ EVERY_CODE_SMOKE_AUTH_PATH=/path/to/existing/auth.json \ ``` 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, Opus 5.5 and context saturation -remain separately unverified. A single exact GPT-6.1 child create/wait/result cycle -was verified live through the built CLI. +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 @@ -174,7 +189,7 @@ was verified live through the built CLI. | --- | --- | | `code-auto-drive-core` unit suite | 84 passed | | `code-common` unit suite | 29 passed | -| `code-core` unit suite | 806 passed; 3 ignored | +| `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 | @@ -182,7 +197,7 @@ was verified live through the built CLI. | `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 5.5 passed; Sonnet streaming + Read tool passed; Opus failed required CLI-version check and is now disabled by default | +| 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 @@ -195,8 +210,8 @@ Initial stale expectations for context limits/routes, a test JWT fixture, and th 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 has SHA-256 -`b21bc53f56f6cb82eb7fdcdd45883b3d29a1b2cef71370d2732eca3ba7265b1a`. +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.