mirror of
https://github.com/openai/codex.git
synced 2026-11-06 20:20:34 +00:00
Expose model metadata from each thread's catalog and settings (#59958)
## What changed - Add `ThreadManager::models_manager_for_startup` to obtain the model catalog used when starting or cold-resuming a root thread, including its effective originator. - Add `CodexThread::selected_model_info` to resolve the model for future turns using the thread's current settings, catalog, and model overrides. - Share catalog selection logic between startup lookup and thread creation. ## Testing Add coverage verifying that startup and thread lookups use the thread-specific catalog, preserve the configured context window, and reflect model selection updates. GitOrigin-RevId: d7976caebb7459e1e9ca55f3c6f6bb2af92cf3de
This commit is contained in:
@@ -41,6 +41,7 @@ use codex_protocol::models::ActivePermissionProfile;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::models::ProfileWorkspaceRoot;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::EnvironmentConfig;
|
||||
@@ -1146,6 +1147,11 @@ impl CodexThread {
|
||||
self.session.state_db()
|
||||
}
|
||||
|
||||
/// Resolves the model selected for future turns using this thread's catalog and settings.
|
||||
pub async fn selected_model_info(&self) -> Arc<ModelInfo> {
|
||||
self.session.selected_model_info().await
|
||||
}
|
||||
|
||||
pub async fn config_snapshot(&self) -> ThreadConfigSnapshot {
|
||||
self.session.thread_config_snapshot().await
|
||||
}
|
||||
|
||||
@@ -2284,6 +2284,21 @@ impl Session {
|
||||
.await)
|
||||
}
|
||||
|
||||
pub(crate) async fn selected_model_info(&self) -> Arc<ModelInfo> {
|
||||
let (settings, overrides) = {
|
||||
let state = self.state.lock().await;
|
||||
let config = &state.session_configuration;
|
||||
(
|
||||
Arc::clone(&config.step_settings),
|
||||
config.model_info_overrides.clone(),
|
||||
)
|
||||
};
|
||||
settings
|
||||
.resolve_model_info(self.services.models_manager.as_ref(), &overrides)
|
||||
.await
|
||||
.model
|
||||
}
|
||||
|
||||
pub(crate) async fn thread_config_snapshot(&self) -> ThreadConfigSnapshot {
|
||||
self.resume_config_snapshot().await.0
|
||||
}
|
||||
|
||||
@@ -1077,6 +1077,32 @@ impl ThreadManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the catalog a root thread will use on start or cold resume.
|
||||
pub async fn models_manager_for_startup(
|
||||
&self,
|
||||
config: &Config,
|
||||
service_name: Option<&str>,
|
||||
history: &InitialHistory,
|
||||
) -> SharedModelsManager {
|
||||
let originator = self
|
||||
.state
|
||||
.effective_originator(
|
||||
history,
|
||||
service_name,
|
||||
&self.state.session_source,
|
||||
/*parent_thread_id*/ None,
|
||||
/*forked_from_thread_id*/ None,
|
||||
/*parent_originator*/ None,
|
||||
)
|
||||
.await;
|
||||
self.state.models_for_thread(
|
||||
config,
|
||||
&self.state.auth_manager,
|
||||
&self.state.runtime_auth,
|
||||
&originator,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn set_api_key_model_discovery_enabled(&self, enabled: bool) {
|
||||
self.state
|
||||
.model_catalog
|
||||
@@ -1872,6 +1898,28 @@ impl ThreadManager {
|
||||
}
|
||||
|
||||
impl ThreadManagerState {
|
||||
fn models_for_thread(
|
||||
&self,
|
||||
config: &Config,
|
||||
auth_manager: &Arc<AuthManager>,
|
||||
runtime_auth: &crate::runtime_auth::RuntimeAuthSlot,
|
||||
originator: &str,
|
||||
) -> SharedModelsManager {
|
||||
let runtime = crate::runtime_auth::runtime_for_config(
|
||||
runtime_auth,
|
||||
config,
|
||||
Arc::clone(auth_manager),
|
||||
&self.installation_id,
|
||||
);
|
||||
let manager = self.model_catalog.for_thread(
|
||||
config,
|
||||
Arc::clone(auth_manager),
|
||||
runtime,
|
||||
Arc::ptr_eq(auth_manager, &self.auth_manager),
|
||||
);
|
||||
manager.for_originator(originator).unwrap_or(manager)
|
||||
}
|
||||
|
||||
pub(crate) fn shared_thread_instructions_provider(
|
||||
&self,
|
||||
root_thread_id: ThreadId,
|
||||
@@ -2706,21 +2754,12 @@ impl ThreadManagerState {
|
||||
} else {
|
||||
Arc::new(mcp_manager.for_plugins_manager(Arc::clone(&plugins_manager)))
|
||||
};
|
||||
let runtime = crate::runtime_auth::runtime_for_config(
|
||||
let models_manager = self.models_for_thread(
|
||||
&config,
|
||||
&auth_manager,
|
||||
&runtime_auth,
|
||||
&config,
|
||||
Arc::clone(&auth_manager),
|
||||
&self.installation_id,
|
||||
&startup_context.originator,
|
||||
);
|
||||
let manager = self.model_catalog.for_thread(
|
||||
&config,
|
||||
Arc::clone(&auth_manager),
|
||||
runtime,
|
||||
Arc::ptr_eq(&auth_manager, &self.auth_manager),
|
||||
);
|
||||
let models_manager = manager
|
||||
.for_originator(&startup_context.originator)
|
||||
.unwrap_or(manager);
|
||||
let spawn_result = Session::spawn(SessionSpawnArgs {
|
||||
attached_thread,
|
||||
startup,
|
||||
|
||||
@@ -25,6 +25,7 @@ use codex_protocol::openai_models::ToolMode;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::InterAgentCommunication;
|
||||
use codex_protocol::protocol::Op;
|
||||
use codex_protocol::protocol::ThreadSettingsOverrides;
|
||||
use codex_protocol::user_input::UserInput;
|
||||
use core_test_support::responses;
|
||||
use core_test_support::responses::ev_completed;
|
||||
@@ -383,3 +384,75 @@ async fn auth_and_provider_switches_do_not_reuse_chatgpt_catalog() -> Result<()>
|
||||
assert_eq!(other_models.requests().len(), 1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn host_model_info_uses_the_thread_catalog() -> Result<()> {
|
||||
let slug = "gpt-5.5";
|
||||
let server = MockServer::start().await;
|
||||
let test = test_codex()
|
||||
.with_auth(header_auth("Bearer catalog-test"))
|
||||
.with_model_info_override(slug, |model| {
|
||||
model.comp_hash = Some("runtime-revision".into());
|
||||
})
|
||||
.build_with_auto_env(&server)
|
||||
.await?;
|
||||
|
||||
let mut options = test.start_thread_options().await;
|
||||
let catalog = options
|
||||
.config
|
||||
.model_catalog
|
||||
.take()
|
||||
.expect("runtime catalog");
|
||||
let mut model = catalog.models.into_iter().find(|m| m.slug == slug).unwrap();
|
||||
model.comp_hash = Some("thread-revision".into());
|
||||
let mut next_model = model.clone();
|
||||
next_model.slug = "after-update".into();
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/provider/models"))
|
||||
.and(header("authorization", "Bearer catalog-test"))
|
||||
.and(header("originator", "codex_work_cca"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(ModelsResponse {
|
||||
models: vec![model, next_model],
|
||||
}))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
options.config.model_provider.base_url = Some(format!("{}/provider", server.uri()));
|
||||
options.config.model_context_window = Some(32_000);
|
||||
options.metrics_service_name = Some("codex_work_cca".into());
|
||||
let overrides = options.config.to_models_manager_config();
|
||||
let default = test.thread_manager.get_models_manager();
|
||||
let default_model = default.get_model_info(slug, &overrides).await;
|
||||
assert_eq!(default_model.comp_hash.as_deref(), Some("runtime-revision"));
|
||||
|
||||
let startup = test
|
||||
.thread_manager
|
||||
.models_manager_for_startup(
|
||||
&options.config,
|
||||
options.metrics_service_name.as_deref(),
|
||||
&options.initial_history,
|
||||
)
|
||||
.await;
|
||||
startup
|
||||
.list_models(
|
||||
RefreshStrategy::OnlineIfUncached,
|
||||
options.config.http_client_factory(),
|
||||
)
|
||||
.await;
|
||||
let selected = startup.get_model_info(slug, &overrides).await;
|
||||
assert_eq!(selected.comp_hash.as_deref(), Some("thread-revision"));
|
||||
assert_eq!(selected.context_window, Some(32_000));
|
||||
|
||||
let thread = test.thread_manager.start_thread(options).await?.thread;
|
||||
assert_eq!(thread.selected_model_info().await, selected.model);
|
||||
thread
|
||||
.update_thread_settings(ThreadSettingsOverrides {
|
||||
model: Some("after-update".into()),
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
let next = thread.selected_model_info().await;
|
||||
assert_eq!(next.slug, "after-update");
|
||||
assert_eq!(next.comp_hash.as_deref(), Some("thread-revision"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user