Introduce ModelsManager and migrate app-server to use it. (#7552)

This commit is contained in:
Ahmed Ibrahim
2025-12-03 17:17:56 -08:00
committed by GitHub
Unverified
parent 70b97790be
commit 00cc00ead8
14 changed files with 56 additions and 71 deletions
@@ -1862,8 +1862,7 @@ impl CodexMessageProcessor {
async fn list_models(&self, request_id: RequestId, params: ModelListParams) {
let ModelListParams { limit, cursor } = params;
let auth_mode = self.auth_manager.auth().map(|auth| auth.mode);
let models = supported_models(auth_mode);
let models = supported_models(self.conversation_manager.clone()).await;
let total = models.len();
if total == 0 {
+7 -4
View File
@@ -1,12 +1,15 @@
use codex_app_server_protocol::AuthMode;
use std::sync::Arc;
use codex_app_server_protocol::Model;
use codex_app_server_protocol::ReasoningEffortOption;
use codex_core::openai_models::model_presets::builtin_model_presets;
use codex_core::ConversationManager;
use codex_protocol::openai_models::ModelPreset;
use codex_protocol::openai_models::ReasoningEffortPreset;
pub fn supported_models(auth_mode: Option<AuthMode>) -> Vec<Model> {
builtin_model_presets(auth_mode)
pub async fn supported_models(conversation_manager: Arc<ConversationManager>) -> Vec<Model> {
conversation_manager
.list_models()
.await
.into_iter()
.map(model_from_preset)
.collect()
+4
View File
@@ -1201,4 +1201,8 @@ impl AuthManager {
self.reload();
Ok(removed)
}
pub fn get_auth_mode(&self) -> Option<AuthMode> {
self.auth().map(|a| a.mode)
}
}
-24
View File
@@ -23,7 +23,6 @@ use crate::user_notification::UserNotifier;
use crate::util::error_or_panic;
use async_channel::Receiver;
use async_channel::Sender;
use codex_app_server_protocol::AuthMode;
use codex_protocol::ConversationId;
use codex_protocol::items::TurnItem;
use codex_protocol::protocol::FileChange;
@@ -643,14 +642,6 @@ impl Session {
Ok(sess)
}
pub(crate) fn get_auth_mode(&self) -> AuthMode {
self.services
.auth_manager
.auth()
.map(|a| a.mode)
.unwrap_or(AuthMode::ApiKey)
}
pub(crate) fn get_tx_event(&self) -> Sender<Event> {
self.tx_event.clone()
}
@@ -1486,9 +1477,6 @@ async fn submission_loop(sess: Arc<Session>, config: Arc<Config>, rx_sub: Receiv
Op::Review { review_request } => {
handlers::review(&sess, &config, sub.id.clone(), review_request).await;
}
Op::ListModels => {
handlers::list_models(&sess, sub.id.clone(), Some(sess.get_auth_mode())).await;
}
_ => {} // Ignore unknown ops; enum is non_exhaustive to allow extensions.
}
}
@@ -1505,15 +1493,12 @@ mod handlers {
use crate::config::Config;
use crate::mcp::auth::compute_auth_statuses;
use crate::mcp::collect_mcp_snapshot_from_manager;
use crate::openai_models::model_presets::builtin_model_presets;
use crate::review_prompts::resolve_review_request;
use crate::tasks::CompactTask;
use crate::tasks::RegularTask;
use crate::tasks::UndoTask;
use crate::tasks::UserShellCommandTask;
use codex_app_server_protocol::AuthMode;
use codex_protocol::custom_prompts::CustomPrompt;
use codex_protocol::openai_models::AvailableModelsEvent;
use codex_protocol::protocol::CodexErrorInfo;
use codex_protocol::protocol::ErrorEvent;
use codex_protocol::protocol::Event;
@@ -1828,15 +1813,6 @@ mod handlers {
}
}
}
pub async fn list_models(sess: &Arc<Session>, sub_id: String, auth_mode: Option<AuthMode>) {
let models = builtin_model_presets(auth_mode);
let event = Event {
id: sub_id,
msg: EventMsg::ListModelsResponse(AvailableModelsEvent { models }),
};
sess.send_event_raw(event).await;
}
}
/// Spawn a review thread using the given prompt.
+9 -1
View File
@@ -7,6 +7,7 @@ use crate::codex_conversation::CodexConversation;
use crate::config::Config;
use crate::error::CodexErr;
use crate::error::Result as CodexResult;
use crate::openai_models::models_manager::ModelsManager;
use crate::protocol::Event;
use crate::protocol::EventMsg;
use crate::protocol::SessionConfiguredEvent;
@@ -14,6 +15,7 @@ use crate::rollout::RolloutRecorder;
use codex_protocol::ConversationId;
use codex_protocol::items::TurnItem;
use codex_protocol::models::ResponseItem;
use codex_protocol::openai_models::ModelPreset;
use codex_protocol::protocol::InitialHistory;
use codex_protocol::protocol::RolloutItem;
use codex_protocol::protocol::SessionSource;
@@ -35,6 +37,7 @@ pub struct NewConversation {
pub struct ConversationManager {
conversations: Arc<RwLock<HashMap<ConversationId, Arc<CodexConversation>>>>,
auth_manager: Arc<AuthManager>,
models_manager: Arc<ModelsManager>,
session_source: SessionSource,
}
@@ -42,8 +45,9 @@ impl ConversationManager {
pub fn new(auth_manager: Arc<AuthManager>, session_source: SessionSource) -> Self {
Self {
conversations: Arc::new(RwLock::new(HashMap::new())),
auth_manager,
auth_manager: auth_manager.clone(),
session_source,
models_manager: Arc::new(ModelsManager::new(auth_manager.get_auth_mode())),
}
}
@@ -193,6 +197,10 @@ impl ConversationManager {
self.finalize_spawn(codex, conversation_id).await
}
pub async fn list_models(&self) -> Vec<ModelPreset> {
self.models_manager.available_models.read().await.clone()
}
}
/// Return a prefix of `items` obtained by cutting strictly before the nth user message
+1
View File
@@ -1 +1,2 @@
pub mod model_presets;
pub mod models_manager;
@@ -0,0 +1,26 @@
use codex_app_server_protocol::AuthMode;
use codex_protocol::openai_models::ModelPreset;
use tokio::sync::RwLock;
use crate::openai_models::model_presets::builtin_model_presets;
pub struct ModelsManager {
pub available_models: RwLock<Vec<ModelPreset>>,
pub etag: String,
pub auth_mode: Option<AuthMode>,
}
impl ModelsManager {
pub fn new(auth_mode: Option<AuthMode>) -> Self {
Self {
available_models: RwLock::new(builtin_model_presets(auth_mode)),
etag: String::new(),
auth_mode,
}
}
pub async fn refresh_available_models(&self) {
let models = builtin_model_presets(self.auth_mode);
*self.available_models.write().await = models;
}
}
-1
View File
@@ -51,7 +51,6 @@ pub(crate) fn should_persist_event_msg(ev: &EventMsg) -> bool {
| EventMsg::Warning(_)
| EventMsg::TaskStarted(_)
| EventMsg::TaskComplete(_)
| EventMsg::ListModelsResponse(_)
| EventMsg::AgentMessageDelta(_)
| EventMsg::AgentReasoningDelta(_)
| EventMsg::AgentReasoningRawContentDelta(_)
+8 -29
View File
@@ -1,51 +1,30 @@
use anyhow::Result;
use codex_core::CodexAuth;
use codex_core::protocol::EventMsg;
use codex_core::protocol::Op;
use codex_core::ConversationManager;
use codex_protocol::openai_models::ModelPreset;
use codex_protocol::openai_models::ReasoningEffort;
use codex_protocol::openai_models::ReasoningEffortPreset;
use core_test_support::responses::start_mock_server;
use core_test_support::test_codex::test_codex;
use core_test_support::wait_for_event_match;
use pretty_assertions::assert_eq;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn list_models_returns_api_key_models() -> Result<()> {
let server = start_mock_server().await;
let mut builder = test_codex().with_auth(CodexAuth::from_api_key("sk-test"));
let test = builder.build(&server).await?;
test.codex.submit(Op::ListModels).await?;
let event = wait_for_event_match(&test.codex, |event| match event {
EventMsg::ListModelsResponse(models) => Some(models.clone()),
_ => None,
})
.await;
let manager = ConversationManager::with_auth(CodexAuth::from_api_key("sk-test"));
let models = manager.list_models().await;
let expected_models = expected_models_for_api_key();
assert_eq!(expected_models, event.models);
assert_eq!(expected_models, models);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn list_models_returns_chatgpt_models() -> Result<()> {
let server = start_mock_server().await;
let mut builder = test_codex().with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing());
let test = builder.build(&server).await?;
test.codex.submit(Op::ListModels).await?;
let event = wait_for_event_match(&test.codex, |event| match event {
EventMsg::ListModelsResponse(models) => Some(models.clone()),
_ => None,
})
.await;
let manager =
ConversationManager::with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing());
let models = manager.list_models().await;
let expected_models = expected_models_for_chatgpt();
assert_eq!(expected_models, event.models);
assert_eq!(expected_models, models);
Ok(())
}
@@ -583,7 +583,6 @@ impl EventProcessor for EventProcessorWithHumanOutput {
| EventMsg::ReasoningContentDelta(_)
| EventMsg::ReasoningRawContentDelta(_)
| EventMsg::UndoCompleted(_)
| EventMsg::ListModelsResponse(_)
| EventMsg::UndoStarted(_) => {}
}
CodexStatus::Running
@@ -307,7 +307,6 @@ async fn run_codex_tool_session_inner(
| EventMsg::UndoCompleted(_)
| EventMsg::ExitedReviewMode(_)
| EventMsg::ContextCompacted(_)
| EventMsg::ListModelsResponse(_)
| EventMsg::DeprecationNotice(_) => {
// For now, we do not do anything extra for these
// events. Note that
-5
View File
@@ -35,11 +35,6 @@ pub enum ReasoningEffort {
XHigh,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema)]
pub struct AvailableModelsEvent {
pub models: Vec<ModelPreset>,
}
/// A reasoning effort option that can be surfaced for a model.
#[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema, PartialEq)]
pub struct ReasoningEffortPreset {
-2
View File
@@ -19,7 +19,6 @@ use crate::message_history::HistoryEntry;
use crate::models::ContentItem;
use crate::models::ResponseItem;
use crate::num_format::format_with_separators;
use crate::openai_models::AvailableModelsEvent;
use crate::openai_models::ReasoningEffort as ReasoningEffortConfig;
use crate::parse_command::ParsedCommand;
use crate::plan_tool::UpdatePlanArgs;
@@ -582,7 +581,6 @@ pub enum EventMsg {
AgentMessageContentDelta(AgentMessageContentDeltaEvent),
ReasoningContentDelta(ReasoningContentDeltaEvent),
ReasoningRawContentDelta(ReasoningRawContentDeltaEvent),
ListModelsResponse(AvailableModelsEvent),
}
/// Codex errors that we expose to clients.
-1
View File
@@ -1842,7 +1842,6 @@ impl ChatWidget {
| EventMsg::ItemCompleted(_)
| EventMsg::AgentMessageContentDelta(_)
| EventMsg::ReasoningContentDelta(_)
| EventMsg::ListModelsResponse(_)
| EventMsg::ReasoningRawContentDelta(_) => {}
}
}