Support fetching remote models from azure with /model
Resolved 💬 2 comments Opened Jan 20, 2026 by PhracturedBlue Closed Feb 23, 2026
What feature would you like to see?
currently /model uses a predefined list of models if using Azure with an API key. My understanding is that features.remote_models only works when using openai login and not with Azure.
I would like /model to be able to query models from the Azure endpoint when using an API key
Additional information
The following is an LLM generated patch that implements this feature. The code has been tested to work, but I am not a Rust programmer, and am unable to validate its quality or overall correctness, so I will not file this as a pull-request. I only paste it here in case other users are interested in it.
diff --git a/codex-rs/codex-api/src/endpoint/models.rs b/codex-rs/codex-api/src/endpoint/models.rs
index 9f6083dc8..dff9cfbd6 100644
--- a/codex-rs/codex-api/src/endpoint/models.rs
+++ b/codex-rs/codex-api/src/endpoint/models.rs
@@ -7,6 +7,9 @@ use codex_client::HttpTransport;
use codex_client::RequestTelemetry;
use codex_protocol::openai_models::ModelInfo;
use codex_protocol::openai_models::ModelsResponse;
+use codex_protocol::openai_models::ConfigShellToolType;
+use codex_protocol::openai_models::ModelVisibility;
+use codex_protocol::openai_models::TruncationPolicyConfig;
use http::HeaderMap;
use http::Method;
use http::header::ETAG;
@@ -67,13 +70,67 @@ impl<T: HttpTransport, A: AuthProvider> ModelsClient<T, A> {
.and_then(|value| value.to_str().ok())
.map(ToString::to_string);
- let ModelsResponse { models } = serde_json::from_slice::<ModelsResponse>(&resp.body)
- .map_err(|e| {
- ApiError::Stream(format!(
- "failed to decode models response: {e}; body: {}",
- String::from_utf8_lossy(&resp.body)
- ))
- })?;
+ // Try the canonical Codex `/models` response first. If that fails,
+ // fall back to common OpenAI-style responses that return `{ "data": [...] }`.
+ let models: Vec<ModelInfo> = match serde_json::from_slice::<ModelsResponse>(&resp.body)
+ .map(|r| r.models)
+ {
+ Ok(models) => models,
+ Err(_) => {
+ // Attempt to parse as OpenAI-style list response: { data: [ { id, ... }, ... ] }
+ match serde_json::from_slice::<serde_json::Value>(&resp.body) {
+ Ok(val) => {
+ if let Some(data) = val.get("data").and_then(|d| d.as_array()) {
+ let mut parsed = Vec::new();
+ for item in data.iter() {
+ if let Some(id) = item.get("id").and_then(|v| v.as_str()) {
+ // Build a minimal ModelInfo from the OpenAI model entry.
+ parsed.push(ModelInfo {
+ slug: id.to_string(),
+ display_name: id.to_string(),
+ description: item
+ .get("description")
+ .and_then(|v| v.as_str())
+ .map(|s| s.to_string()),
+ default_reasoning_level: None,
+ supported_reasoning_levels: Vec::new(),
+ shell_type: ConfigShellToolType::ShellCommand,
+ visibility: ModelVisibility::List,
+ supported_in_api: true,
+ priority: 0,
+ upgrade: None,
+ base_instructions: String::new(),
+ supports_reasoning_summaries: false,
+ support_verbosity: false,
+ default_verbosity: None,
+ apply_patch_tool_type: None,
+ truncation_policy: TruncationPolicyConfig::tokens(10000),
+ supports_parallel_tool_calls: false,
+ context_window: None,
+ auto_compact_token_limit: None,
// default from protocol::openai_models::default_effective_context_window_percent()
+ effective_context_window_percent: 95,
+ experimental_supported_tools: Vec::new(),
+ });
+ }
+ }
+ parsed
+ } else {
+ return Err(ApiError::Stream(format!(
+ "failed to decode models response: missing `models` field and no `data` array; body: {}",
+ String::from_utf8_lossy(&resp.body)
+ )));
+ }
+ }
+ Err(e) => {
+ return Err(ApiError::Stream(format!(
+ "failed to decode models response: {e}; body: {}",
+ String::from_utf8_lossy(&resp.body)
+ )));
+ }
+ }
+ }
+ };
Ok((models, header_etag))
}
diff --git a/codex-rs/core/src/models_manager/manager.rs b/codex-rs/core/src/models_manager/manager.rs
index 206b4cab2..4d70e7165 100644
--- a/codex-rs/core/src/models_manager/manager.rs
+++ b/codex-rs/core/src/models_manager/manager.rs
@@ -175,9 +175,13 @@ impl ModelsManager {
config: &Config,
refresh_strategy: RefreshStrategy,
) -> CoreResult<()> {
- if !config.features.enabled(Feature::RemoteModels)
- || self.auth_manager.get_auth_mode() == Some(AuthMode::ApiKey)
- {
+ let remote_enabled = config.features.enabled(Feature::RemoteModels);
+ let auth_mode = self.auth_manager.get_auth_mode();
+ tracing::debug!(
+ "refresh_available_models called; remote_enabled = {remote_enabled:?}, auth_mode = {:?}",
+ auth_mode
+ );
+ if !remote_enabled {
return Ok(());
}
@@ -192,19 +196,32 @@ impl ModelsManager {
if self.try_load_cache().await {
return Ok(());
}
- self.fetch_and_update_models().await
+ self.fetch_and_update_models(config).await
}
RefreshStrategy::Online => {
// Always fetch from network
- self.fetch_and_update_models().await
+ self.fetch_and_update_models(config).await
}
}
}
- async fn fetch_and_update_models(&self) -> CoreResult<()> {
+ async fn fetch_and_update_models(&self, config: &Config) -> CoreResult<()> {
+ tracing::info!(
+ "fetch_and_update_models: starting (auth_mode = {:?})",
+ self.auth_manager.get_auth_mode()
+ );
let auth = self.auth_manager.auth().await;
- let api_provider = self.provider.to_api_provider(Some(AuthMode::ChatGPT))?;
- let api_auth = auth_provider_from_auth(auth.clone(), &self.provider)?;
+ // Prefer the configured provider from `config` so user-specified
+ // `model_provider.base_url` and headers are respected (e.g. Azure).
+ let provider = config.model_provider.clone();
+ tracing::debug!(
+ "fetch_and_update_models: using model_provider id={}, base_url={:?}",
+ provider.name,
+ provider.base_url
+ );
+ let api_provider = provider.to_api_provider(self.auth_manager.get_auth_mode())?;
+ tracing::debug!("fetch_and_update_models: api_provider.base_url={} ", api_provider.base_url);
+ let api_auth = auth_provider_from_auth(auth.clone(), &provider)?;
let transport = ReqwestTransport::new(build_reqwest_client());
let client = ModelsClient::new(transport, api_provider, api_auth);
@@ -216,7 +233,7 @@ impl ModelsManager {
.await
.map_err(|_| CodexErr::Timeout)?
.map_err(map_api_error)?;
-
+ tracing::info!("fetch_and_update_models: received {} models", models.len());
self.apply_remote_models(models.clone()).await;
*self.etag.write().await = etag.clone();
self.cache_manager.persist_cache(&models, etag).await;This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗