mirror of
https://github.com/openai/codex.git
synced 2026-02-02 15:03:38 +00:00
Compare commits
1 Commits
input-vali
...
pr1291
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2422660594 |
1
codex-rs/Cargo.lock
generated
1
codex-rs/Cargo.lock
generated
@@ -600,6 +600,7 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"codex-core",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"toml",
|
||||
]
|
||||
|
||||
@@ -18,7 +18,7 @@ workspace = true
|
||||
anyhow = "1"
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
codex-core = { path = "../core" }
|
||||
codex-common = { path = "../common", features = ["cli"] }
|
||||
codex-common = { path = "../common", features = ["cli", "model-list"] }
|
||||
codex-exec = { path = "../exec" }
|
||||
codex-login = { path = "../login" }
|
||||
codex-linux-sandbox = { path = "../linux-sandbox" }
|
||||
|
||||
55
codex-rs/cli/src/list_models.rs
Normal file
55
codex-rs/cli/src/list_models.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use clap::Parser;
|
||||
|
||||
use codex_common::CliConfigOverrides;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::config::ConfigOverrides;
|
||||
|
||||
/// Print the list of models available for the configured (or overridden)
|
||||
/// provider.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct ListModelsCli {
|
||||
/// Optional provider override. When set this value is used instead of the
|
||||
/// `model_provider_id` configured in `~/.codex/config.toml`.
|
||||
#[clap(long)]
|
||||
pub provider: Option<String>,
|
||||
|
||||
/// Arbitrary `-c key=value` overrides that apply **in addition** to the
|
||||
/// `--provider` flag.
|
||||
#[clap(flatten)]
|
||||
pub config_overrides: CliConfigOverrides,
|
||||
}
|
||||
|
||||
impl ListModelsCli {
|
||||
pub async fn run(self) -> anyhow::Result<()> {
|
||||
// Compose strongly-typed overrides. The provider flag, if specified,
|
||||
// is translated into the corresponding field inside `ConfigOverrides`.
|
||||
let overrides = ConfigOverrides {
|
||||
model: None,
|
||||
config_profile: None,
|
||||
approval_policy: None,
|
||||
sandbox_policy: None,
|
||||
cwd: None,
|
||||
model_provider: self.provider.clone(),
|
||||
codex_linux_sandbox_exe: None,
|
||||
};
|
||||
|
||||
// Parse the raw `-c` overrides early so we can bail with a useful
|
||||
// error message if the user supplied an invalid value.
|
||||
let cli_kv_overrides = self
|
||||
.config_overrides
|
||||
.parse_overrides()
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
|
||||
// Load the merged configuration.
|
||||
let cfg = Config::load_with_cli_overrides(cli_kv_overrides, overrides)?;
|
||||
|
||||
// Retrieve the model list.
|
||||
let models = codex_common::fetch_available_models(cfg.model_provider).await?;
|
||||
|
||||
for m in models {
|
||||
println!("{m}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ use codex_tui::Cli as TuiCli;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::proto::ProtoCli;
|
||||
mod list_models;
|
||||
|
||||
/// Codex CLI
|
||||
///
|
||||
@@ -43,6 +44,10 @@ enum Subcommand {
|
||||
/// Experimental: run Codex as an MCP server.
|
||||
Mcp,
|
||||
|
||||
/// List models for the configured or specified provider.
|
||||
#[clap(name = "list-models", visible_alias = "lm")]
|
||||
ListModels(crate::list_models::ListModelsCli),
|
||||
|
||||
/// Run the Protocol stream via stdin/stdout
|
||||
#[clap(visible_alias = "p")]
|
||||
Proto(ProtoCli),
|
||||
@@ -121,6 +126,13 @@ async fn cli_main(codex_linux_sandbox_exe: Option<PathBuf>) -> anyhow::Result<()
|
||||
.await?;
|
||||
}
|
||||
},
|
||||
Some(Subcommand::ListModels(list_cli)) => {
|
||||
// Combine root-level overrides with subcommand-specific ones so
|
||||
// that the latter take precedence.
|
||||
let mut list_cli = list_cli;
|
||||
prepend_config_flags(&mut list_cli.config_overrides, cli.config_overrides);
|
||||
list_cli.run().await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -11,8 +11,15 @@ clap = { version = "4", features = ["derive", "wrap_help"], optional = true }
|
||||
codex-core = { path = "../core" }
|
||||
toml = { version = "0.8", optional = true }
|
||||
serde = { version = "1", optional = true }
|
||||
reqwest = { version = "0.12", features = ["json"], optional = true }
|
||||
|
||||
[features]
|
||||
# Separate feature so that `clap` is not a mandatory dependency.
|
||||
cli = ["clap", "toml", "serde"]
|
||||
elapsed = []
|
||||
|
||||
# Helper functionality for querying the list of available models from a model
|
||||
# provider. This is intentionally behind a separate opt-in feature so that
|
||||
# downstream crates that do not need it avoid pulling in the additional heavy
|
||||
# dependencies (`reqwest`, etc.).
|
||||
model-list = ["reqwest"]
|
||||
|
||||
@@ -14,3 +14,13 @@ mod config_override;
|
||||
|
||||
#[cfg(feature = "cli")]
|
||||
pub use config_override::CliConfigOverrides;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Optional helpers for querying the list of available models.
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
#[cfg(feature = "model-list")]
|
||||
mod model_list;
|
||||
|
||||
#[cfg(feature = "model-list")]
|
||||
pub use model_list::fetch_available_models;
|
||||
|
||||
73
codex-rs/common/src/model_list.rs
Normal file
73
codex-rs/common/src/model_list.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
//! Helper for fetching the list of models that are available for a given
|
||||
//! [`ModelProviderInfo`] instance.
|
||||
//!
|
||||
//! The implementation is intentionally lightweight and only covers the subset
|
||||
//! of the OpenAI-compatible REST API that is required to discover available
|
||||
//! model *identifiers*. At the time of writing all providers supported by
|
||||
//! Codex expose a `GET /models` endpoint that returns a JSON payload in the
|
||||
//! following canonical form:
|
||||
//!
|
||||
//! ```json
|
||||
//! {
|
||||
//! "object": "list",
|
||||
//! "data": [
|
||||
//! { "id": "o3", "object": "model" },
|
||||
//! { "id": "o4-mini", "object": "model" }
|
||||
//! ]
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! We purposefully parse *only* the `id` fields that callers care about and
|
||||
//! ignore any additional metadata so that the function keeps working even if
|
||||
//! upstream providers add new attributes.
|
||||
|
||||
use codex_core::ModelProviderInfo;
|
||||
use codex_core::error::CodexErr;
|
||||
use codex_core::error::Result;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ModelsResponse {
|
||||
data: Vec<ModelId>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ModelId {
|
||||
id: String,
|
||||
}
|
||||
|
||||
/// Fetch the list of available model identifiers from the given provider.
|
||||
///
|
||||
/// The caller must ensure that the provider's API key can be resolved via
|
||||
/// [`ModelProviderInfo::api_key`] – if this fails the function returns a
|
||||
/// [`CodexErr::EnvVar`]. Any network or JSON parsing failures are forwarded
|
||||
/// to the caller.
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub async fn fetch_available_models(provider: ModelProviderInfo) -> Result<Vec<String>> {
|
||||
let api_key = provider.api_key()?;
|
||||
|
||||
let base_url = provider.base_url.trim_end_matches('/');
|
||||
let url = format!("{base_url}/models");
|
||||
|
||||
// Build the request. For providers that require authentication we send
|
||||
// the token via the standard Bearer mechanism. Providers like Ollama do
|
||||
// not require a token – in that case we just omit the header.
|
||||
let client = reqwest::Client::new();
|
||||
let mut req = client.get(&url);
|
||||
if let Some(token) = api_key {
|
||||
req = req.bearer_auth(token);
|
||||
}
|
||||
|
||||
let resp = req.send().await?;
|
||||
|
||||
match resp.error_for_status() {
|
||||
Ok(ok_resp) => {
|
||||
// Guaranteed 2xx
|
||||
let json: ModelsResponse = ok_resp.json().await?;
|
||||
let mut models: Vec<String> = json.data.into_iter().map(|m| m.id).collect();
|
||||
models.sort();
|
||||
Ok(models)
|
||||
}
|
||||
Err(err) => Err(CodexErr::Reqwest(err)),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user