mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Add codex debug models to show model catalog (#18625)
This commit is contained in:
committed by
GitHub
Unverified
parent
87fc21ff60
commit
ab65fbbdd6
Generated
+1
@@ -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",
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user