From df35189366d90df023ffa3b038701a8b7e18f927 Mon Sep 17 00:00:00 2001 From: Shijie Rao Date: Wed, 17 Dec 2025 12:13:16 -0800 Subject: [PATCH] feat: make list_models non-blocking (#8198) ### Summary * Make `app_server.list_models` to be non-blocking and consumers (i.e. extension) can manage the flow themselves. * Force config to use remote models and therefore fetch codex-auto model list. --- .../app-server/src/codex_message_processor.rs | 29 +++- .../app-server/tests/suite/v2/model_list.rs | 140 +++++++++--------- 2 files changed, 92 insertions(+), 77 deletions(-) diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 412b1c7d6..1c228a95c 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -393,7 +393,14 @@ impl CodexMessageProcessor { self.handle_list_conversations(request_id, params).await; } ClientRequest::ModelList { request_id, params } => { - self.list_models(request_id, params).await; + let outgoing = self.outgoing.clone(); + let conversation_manager = self.conversation_manager.clone(); + let config = self.config.clone(); + + tokio::spawn(async move { + Self::list_models(outgoing, conversation_manager, config, request_id, params) + .await; + }); } ClientRequest::McpServerOauthLogin { request_id, params } => { self.mcp_server_oauth_login(request_id, params).await; @@ -1896,9 +1903,17 @@ impl CodexMessageProcessor { Ok((items, next_cursor)) } - async fn list_models(&self, request_id: RequestId, params: ModelListParams) { + async fn list_models( + outgoing: Arc, + conversation_manager: Arc, + config: Arc, + request_id: RequestId, + params: ModelListParams, + ) { let ModelListParams { limit, cursor } = params; - let models = supported_models(self.conversation_manager.clone(), &self.config).await; + let mut config = (*config).clone(); + config.features.enable(Feature::RemoteModels); + let models = supported_models(conversation_manager, &config).await; let total = models.len(); if total == 0 { @@ -1906,7 +1921,7 @@ impl CodexMessageProcessor { data: Vec::new(), next_cursor: None, }; - self.outgoing.send_response(request_id, response).await; + outgoing.send_response(request_id, response).await; return; } @@ -1921,7 +1936,7 @@ impl CodexMessageProcessor { message: format!("invalid cursor: {cursor}"), data: None, }; - self.outgoing.send_error(request_id, error).await; + outgoing.send_error(request_id, error).await; return; } }, @@ -1934,7 +1949,7 @@ impl CodexMessageProcessor { message: format!("cursor {start} exceeds total models {total}"), data: None, }; - self.outgoing.send_error(request_id, error).await; + outgoing.send_error(request_id, error).await; return; } @@ -1949,7 +1964,7 @@ impl CodexMessageProcessor { data: items, next_cursor, }; - self.outgoing.send_response(request_id, response).await; + outgoing.send_response(request_id, response).await; } async fn mcp_server_oauth_login( diff --git a/codex-rs/app-server/tests/suite/v2/model_list.rs b/codex-rs/app-server/tests/suite/v2/model_list.rs index b406eda3f..5cf907f83 100644 --- a/codex-rs/app-server/tests/suite/v2/model_list.rs +++ b/codex-rs/app-server/tests/suite/v2/model_list.rs @@ -48,74 +48,32 @@ async fn list_models_returns_all_models_with_large_limit() -> Result<()> { let expected_models = vec![ Model { - id: "gpt-5.1-codex-max".to_string(), - model: "gpt-5.1-codex-max".to_string(), - display_name: "gpt-5.1-codex-max".to_string(), - description: "Latest Codex-optimized flagship for deep and fast reasoning.".to_string(), + id: "gpt-5.1".to_string(), + model: "gpt-5.1".to_string(), + display_name: "gpt-5.1".to_string(), + description: "Broad world knowledge with strong general reasoning.".to_string(), supported_reasoning_efforts: vec![ ReasoningEffortOption { reasoning_effort: ReasoningEffort::Low, - description: "Fast responses with lighter reasoning".to_string(), + description: "Balances speed with some reasoning; useful for straightforward \ + queries and short explanations" + .to_string(), }, ReasoningEffortOption { reasoning_effort: ReasoningEffort::Medium, - description: "Balances speed and reasoning depth for everyday tasks" + description: "Provides a solid balance of reasoning depth and latency for \ + general-purpose tasks" .to_string(), }, ReasoningEffortOption { reasoning_effort: ReasoningEffort::High, - description: "Greater reasoning depth for complex problems".to_string(), - }, - ReasoningEffortOption { - reasoning_effort: ReasoningEffort::XHigh, - description: "Extra high reasoning depth for complex problems".to_string(), + description: "Maximizes reasoning depth for complex or ambiguous problems" + .to_string(), }, ], default_reasoning_effort: ReasoningEffort::Medium, is_default: true, }, - Model { - id: "gpt-5.1-codex".to_string(), - model: "gpt-5.1-codex".to_string(), - display_name: "gpt-5.1-codex".to_string(), - description: "Optimized for codex.".to_string(), - supported_reasoning_efforts: vec![ - ReasoningEffortOption { - reasoning_effort: ReasoningEffort::Low, - description: "Fastest responses with limited reasoning".to_string(), - }, - ReasoningEffortOption { - reasoning_effort: ReasoningEffort::Medium, - description: "Dynamically adjusts reasoning based on the task".to_string(), - }, - ReasoningEffortOption { - reasoning_effort: ReasoningEffort::High, - description: "Maximizes reasoning depth for complex or ambiguous problems" - .to_string(), - }, - ], - default_reasoning_effort: ReasoningEffort::Medium, - is_default: false, - }, - Model { - id: "gpt-5.1-codex-mini".to_string(), - model: "gpt-5.1-codex-mini".to_string(), - display_name: "gpt-5.1-codex-mini".to_string(), - description: "Optimized for codex. Cheaper, faster, but less capable.".to_string(), - supported_reasoning_efforts: vec![ - ReasoningEffortOption { - reasoning_effort: ReasoningEffort::Medium, - description: "Dynamically adjusts reasoning based on the task".to_string(), - }, - ReasoningEffortOption { - reasoning_effort: ReasoningEffort::High, - description: "Maximizes reasoning depth for complex or ambiguous problems" - .to_string(), - }, - ], - default_reasoning_effort: ReasoningEffort::Medium, - is_default: false, - }, Model { id: "gpt-5.2".to_string(), model: "gpt-5.2".to_string(), @@ -150,22 +108,14 @@ async fn list_models_returns_all_models_with_large_limit() -> Result<()> { is_default: false, }, Model { - id: "gpt-5.1".to_string(), - model: "gpt-5.1".to_string(), - display_name: "gpt-5.1".to_string(), - description: "Broad world knowledge with strong general reasoning.".to_string(), + id: "gpt-5.1-codex-mini".to_string(), + model: "gpt-5.1-codex-mini".to_string(), + display_name: "gpt-5.1-codex-mini".to_string(), + description: "Optimized for codex. Cheaper, faster, but less capable.".to_string(), supported_reasoning_efforts: vec![ - ReasoningEffortOption { - reasoning_effort: ReasoningEffort::Low, - description: "Balances speed with some reasoning; useful for straightforward \ - queries and short explanations" - .to_string(), - }, ReasoningEffortOption { reasoning_effort: ReasoningEffort::Medium, - description: "Provides a solid balance of reasoning depth and latency for \ - general-purpose tasks" - .to_string(), + description: "Dynamically adjusts reasoning based on the task".to_string(), }, ReasoningEffortOption { reasoning_effort: ReasoningEffort::High, @@ -176,6 +126,56 @@ async fn list_models_returns_all_models_with_large_limit() -> Result<()> { default_reasoning_effort: ReasoningEffort::Medium, is_default: false, }, + Model { + id: "gpt-5.1-codex".to_string(), + model: "gpt-5.1-codex".to_string(), + display_name: "gpt-5.1-codex".to_string(), + description: "Optimized for codex.".to_string(), + supported_reasoning_efforts: vec![ + ReasoningEffortOption { + reasoning_effort: ReasoningEffort::Low, + description: "Fastest responses with limited reasoning".to_string(), + }, + ReasoningEffortOption { + reasoning_effort: ReasoningEffort::Medium, + description: "Dynamically adjusts reasoning based on the task".to_string(), + }, + ReasoningEffortOption { + reasoning_effort: ReasoningEffort::High, + description: "Maximizes reasoning depth for complex or ambiguous problems" + .to_string(), + }, + ], + default_reasoning_effort: ReasoningEffort::Medium, + is_default: false, + }, + Model { + id: "gpt-5.1-codex-max".to_string(), + model: "gpt-5.1-codex-max".to_string(), + display_name: "gpt-5.1-codex-max".to_string(), + description: "Latest Codex-optimized flagship for deep and fast reasoning.".to_string(), + supported_reasoning_efforts: vec![ + ReasoningEffortOption { + reasoning_effort: ReasoningEffort::Low, + description: "Fast responses with lighter reasoning".to_string(), + }, + ReasoningEffortOption { + reasoning_effort: ReasoningEffort::Medium, + description: "Balances speed and reasoning depth for everyday tasks" + .to_string(), + }, + ReasoningEffortOption { + reasoning_effort: ReasoningEffort::High, + description: "Greater reasoning depth for complex problems".to_string(), + }, + ReasoningEffortOption { + reasoning_effort: ReasoningEffort::XHigh, + description: "Extra high reasoning depth for complex problems".to_string(), + }, + ], + default_reasoning_effort: ReasoningEffort::Medium, + is_default: false, + }, ]; assert_eq!(items, expected_models); @@ -210,7 +210,7 @@ async fn list_models_pagination_works() -> Result<()> { } = to_response::(first_response)?; assert_eq!(first_items.len(), 1); - assert_eq!(first_items[0].id, "gpt-5.1-codex-max"); + assert_eq!(first_items[0].id, "gpt-5.1"); let next_cursor = first_cursor.ok_or_else(|| anyhow!("cursor for second page"))?; let second_request = mcp @@ -232,7 +232,7 @@ async fn list_models_pagination_works() -> Result<()> { } = to_response::(second_response)?; assert_eq!(second_items.len(), 1); - assert_eq!(second_items[0].id, "gpt-5.1-codex"); + assert_eq!(second_items[0].id, "gpt-5.2"); let third_cursor = second_cursor.ok_or_else(|| anyhow!("cursor for third page"))?; let third_request = mcp @@ -276,7 +276,7 @@ async fn list_models_pagination_works() -> Result<()> { } = to_response::(fourth_response)?; assert_eq!(fourth_items.len(), 1); - assert_eq!(fourth_items[0].id, "gpt-5.2"); + assert_eq!(fourth_items[0].id, "gpt-5.1-codex"); let fifth_cursor = fourth_cursor.ok_or_else(|| anyhow!("cursor for fifth page"))?; let fifth_request = mcp @@ -298,7 +298,7 @@ async fn list_models_pagination_works() -> Result<()> { } = to_response::(fifth_response)?; assert_eq!(fifth_items.len(), 1); - assert_eq!(fifth_items[0].id, "gpt-5.1"); + assert_eq!(fifth_items[0].id, "gpt-5.1-codex-max"); assert!(fifth_cursor.is_none()); Ok(()) }