Add codex debug models to show model catalog (#18625)

This commit is contained in:
Andrey Mishchenko
2026-04-19 22:42:22 -07:00
committed by GitHub
Unverified
parent 87fc21ff60
commit ab65fbbdd6
7 changed files with 140 additions and 11 deletions
+1
View File
@@ -1699,6 +1699,7 @@ dependencies = [
"codex-mcp",
"codex-mcp-server",
"codex-model-provider",
"codex-models-manager",
"codex-protocol",
"codex-responses-api-proxy",
"codex-rmcp-client",
+1
View File
@@ -37,6 +37,7 @@ codex-features = { workspace = true }
codex-login = { workspace = true }
codex-mcp = { workspace = true }
codex-mcp-server = { workspace = true }
codex-models-manager = { workspace = true }
codex-model-provider = { workspace = true }
codex-protocol = { workspace = true }
codex-responses-api-proxy = { workspace = true }
+63
View File
@@ -49,6 +49,7 @@ use crate::mcp_cmd::McpCli;
use crate::responses_cmd::ResponsesCommand;
use crate::responses_cmd::run_responses_command;
use codex_core::build_models_manager;
use codex_core::clear_memory_roots_contents;
use codex_core::config::Config;
use codex_core::config::ConfigOverrides;
@@ -57,6 +58,10 @@ use codex_core::config::find_codex_home;
use codex_features::FEATURES;
use codex_features::Stage;
use codex_features::is_known_feature_key;
use codex_models_manager::AuthManager;
use codex_models_manager::bundled_models_response;
use codex_models_manager::collaboration_mode_presets::CollaborationModesConfig;
use codex_models_manager::manager::RefreshStrategy;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::user_input::UserInput;
use codex_terminal_detection::TerminalName;
@@ -200,6 +205,9 @@ struct DebugCommand {
#[derive(Debug, clap::Subcommand)]
enum DebugSubcommand {
/// Render the raw model catalog as JSON.
Models(DebugModelsCommand),
/// Tooling: helps debug the app server.
AppServer(DebugAppServerCommand),
@@ -240,6 +248,13 @@ struct DebugPromptInputCommand {
images: Vec<PathBuf>,
}
#[derive(Debug, Parser)]
struct DebugModelsCommand {
/// Skip refresh and dump only the bundled catalog shipped with this binary.
#[arg(long = "bundled", default_value_t = false)]
bundled: bool,
}
#[derive(Debug, Parser)]
struct ResumeCommand {
/// Conversation/session id (UUID) or thread name. UUIDs take precedence if it parses.
@@ -990,6 +1005,14 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
}
},
Some(Subcommand::Debug(DebugCommand { subcommand })) => match subcommand {
DebugSubcommand::Models(cmd) => {
reject_remote_mode_for_subcommand(
root_remote.as_deref(),
root_remote_auth_token_env.as_deref(),
"debug models",
)?;
run_debug_models_command(cmd, root_config_overrides).await?;
}
DebugSubcommand::AppServer(cmd) => {
reject_remote_mode_for_subcommand(
root_remote.as_deref(),
@@ -1279,6 +1302,31 @@ async fn run_debug_prompt_input_command(
Ok(())
}
async fn run_debug_models_command(
cmd: DebugModelsCommand,
root_config_overrides: CliConfigOverrides,
) -> anyhow::Result<()> {
let catalog = if cmd.bundled {
bundled_models_response()?
} else {
let cli_overrides = root_config_overrides
.parse_overrides()
.map_err(anyhow::Error::msg)?;
let config = Config::load_with_cli_overrides(cli_overrides).await?;
let auth_manager =
AuthManager::shared_from_config(&config, /*enable_codex_api_key_env*/ true);
let models_manager =
build_models_manager(&config, auth_manager, CollaborationModesConfig::default());
models_manager
.raw_model_catalog(RefreshStrategy::OnlineIfUncached)
.await
};
serde_json::to_writer(std::io::stdout(), &catalog)?;
println!();
Ok(())
}
async fn run_debug_clear_memories_command(
root_config_overrides: &CliConfigOverrides,
interactive: &TuiCli,
@@ -1701,6 +1749,21 @@ mod tests {
);
}
#[test]
fn debug_models_parses_bundled_flag() {
let cli =
MultitoolCli::try_parse_from(["codex", "debug", "models", "--bundled"]).expect("parse");
let Some(Subcommand::Debug(DebugCommand {
subcommand: DebugSubcommand::Models(cmd),
})) = cli.subcommand
else {
panic!("expected debug models subcommand");
};
assert!(cmd.bundled);
}
#[test]
fn responses_subcommand_is_hidden_from_help_but_parses() {
let help = MultitoolCli::command().render_help().to_string();
+40
View File
@@ -0,0 +1,40 @@
use std::path::Path;
use anyhow::Result;
use tempfile::TempDir;
fn codex_command(codex_home: &Path) -> Result<assert_cmd::Command> {
let mut cmd = assert_cmd::Command::new(codex_utils_cargo_bin::cargo_bin("codex")?);
cmd.env("CODEX_HOME", codex_home);
Ok(cmd)
}
#[test]
fn debug_models_bundled_prints_json() -> Result<()> {
let codex_home = TempDir::new()?;
let mut cmd = codex_command(codex_home.path())?;
let output = cmd.args(["debug", "models", "--bundled"]).output()?;
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout)?;
let value: serde_json::Value = serde_json::from_str(&stdout)?;
assert!(value["models"].is_array());
assert!(!value["models"].as_array().unwrap_or(&Vec::new()).is_empty());
Ok(())
}
#[test]
fn debug_models_default_prints_json_without_auth() -> Result<()> {
let codex_home = TempDir::new()?;
let mut cmd = codex_command(codex_home.path())?;
let output = cmd.args(["debug", "models"]).output()?;
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout)?;
let value: serde_json::Value = serde_json::from_str(&stdout)?;
assert!(value["models"].is_array());
assert!(!value["models"].as_array().unwrap_or(&Vec::new()).is_empty());
Ok(())
}
+1
View File
@@ -122,6 +122,7 @@ pub(crate) mod windows_sandbox_read_grants;
pub use thread_manager::ForkSnapshot;
pub use thread_manager::NewThread;
pub use thread_manager::ThreadManager;
pub use thread_manager::build_models_manager;
pub use web_search::web_search_action_detail;
pub use web_search::web_search_detail;
pub use windows_sandbox_read_grants::grant_read_root_non_elevated;
+24 -11
View File
@@ -217,6 +217,26 @@ pub(crate) struct ThreadManagerState {
ops_log: Option<SharedCapturedOps>,
}
pub fn build_models_manager(
config: &Config,
auth_manager: Arc<AuthManager>,
collaboration_modes_config: CollaborationModesConfig,
) -> Arc<ModelsManager> {
let openai_models_provider = config
.model_providers
.get(OPENAI_PROVIDER_ID)
.cloned()
.unwrap_or_else(|| ModelProviderInfo::create_openai_provider(/*base_url*/ None));
Arc::new(ModelsManager::new_with_provider(
config.codex_home.to_path_buf(),
auth_manager,
config.model_catalog.clone(),
collaboration_modes_config,
openai_models_provider,
))
}
impl ThreadManager {
pub fn new(
config: &Config,
@@ -228,11 +248,6 @@ impl ThreadManager {
) -> Self {
let codex_home = config.codex_home.clone();
let restriction_product = session_source.restriction_product();
let openai_models_provider = config
.model_providers
.get(OPENAI_PROVIDER_ID)
.cloned()
.unwrap_or_else(|| ModelProviderInfo::create_openai_provider(/*base_url*/ None));
let (thread_created_tx, _) = broadcast::channel(THREAD_CREATED_CHANNEL_CAPACITY);
let plugins_manager = Arc::new(PluginsManager::new_with_restriction_product(
codex_home.to_path_buf(),
@@ -240,7 +255,7 @@ impl ThreadManager {
));
let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager)));
let skills_manager = Arc::new(SkillsManager::new_with_restriction_product(
codex_home.clone(),
codex_home,
config.bundled_skills_enabled(),
restriction_product,
));
@@ -249,13 +264,11 @@ impl ThreadManager {
state: Arc::new(ThreadManagerState {
threads: Arc::new(RwLock::new(HashMap::new())),
thread_created_tx,
models_manager: Arc::new(ModelsManager::new_with_provider(
codex_home.to_path_buf(),
models_manager: build_models_manager(
config,
auth_manager.clone(),
config.model_catalog.clone(),
collaboration_modes_config,
openai_models_provider,
)),
),
environment_manager,
skills_manager,
plugins_manager,
+10
View File
@@ -253,6 +253,16 @@ impl ModelsManager {
self.build_available_models(remote_models)
}
/// Return the active raw model catalog, refreshing according to the specified strategy.
pub async fn raw_model_catalog(&self, refresh_strategy: RefreshStrategy) -> ModelsResponse {
if let Err(err) = self.refresh_available_models(refresh_strategy).await {
error!("failed to refresh available models: {err}");
}
ModelsResponse {
models: self.get_remote_models().await,
}
}
/// List collaboration mode presets.
///
/// Returns a static set of presets seeded with the configured model.