mirror of
https://github.com/openai/codex.git
synced 2026-11-08 20:40:33 +00:00
Wire shared runtime authentication through threads and API clients (#59177)
## What changed - Share a lazily registered runtime across threads, model discovery, and extension clients when `runtime_agent_instances` is enabled, while honoring each thread's authentication mode and credential owner. - Bind model catalogs to the effective provider and authentication context while preserving cache policy and live API-key discovery settings. - Resolve credentials, routing, and transport policy together for each request. Preserve proxy and TLS settings, validate runtime account selectors, and prevent pooled connections from crossing runtime identities. - Accept supported ChatGPT subdomains when selecting the registration environment. ## Testing Add integration coverage for shared runtime registration, dual-header and assertion-only modes, disabled and third-party providers, credential replacement, subagents, and delegated review. Add coverage for catalog cache policy and runtime-aware connection reuse. GitOrigin-RevId: 99dd9d987c29c93597a648f6586f98ac6a932c3c
This commit is contained in:
@@ -393,6 +393,7 @@ pub async fn start(mut args: InProcessStartArgs) -> IoResult<InProcessClientHand
|
||||
});
|
||||
}
|
||||
let initialize = args.initialize.clone();
|
||||
// Keep embedded startup state off callers' stacks, which are smaller on Windows.
|
||||
let client = Box::pin(start_uninitialized(args)).await?;
|
||||
|
||||
let initialize_response = client
|
||||
|
||||
@@ -186,6 +186,7 @@ mod tests {
|
||||
use codex_home::CodexHomeUserInstructionsProvider;
|
||||
use codex_login::AuthManager;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_models_manager::manager::ModelCatalogSettings;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -534,8 +535,14 @@ enabled = false
|
||||
let thread_manager = Arc::new_cyclic(|thread_manager| {
|
||||
ThreadManager::new(
|
||||
&good_config,
|
||||
auth_manager.clone(),
|
||||
codex_core::build_models_manager(&good_config, auth_manager.clone()),
|
||||
codex_core::build_startup_models(
|
||||
&good_config,
|
||||
auth_manager.clone(),
|
||||
"11111111-1111-4111-8111-111111111111".to_string(),
|
||||
Arc::new(ModelCatalogSettings::disk(
|
||||
good_config.codex_home.to_path_buf(),
|
||||
)),
|
||||
),
|
||||
codex_core::CodexAppsToolsCache::default(),
|
||||
SessionSource::Exec,
|
||||
Arc::clone(&environment_manager),
|
||||
@@ -567,7 +574,6 @@ enabled = false
|
||||
codex_core::passthrough_image_store(),
|
||||
Arc::clone(&thread_store),
|
||||
codex_core::local_agent_graph_store_from_state_db(Some(&state_db)),
|
||||
"11111111-1111-4111-8111-111111111111".to_string(),
|
||||
/*attestation_provider*/ None,
|
||||
/*external_time_provider*/ None,
|
||||
)
|
||||
|
||||
@@ -90,6 +90,7 @@ use codex_feedback::CodexFeedback;
|
||||
use codex_goal_extension::GoalService;
|
||||
use codex_home::CodexHomeUserInstructionsProvider;
|
||||
use codex_login::AuthManager;
|
||||
use codex_models_manager::manager::ModelCatalogSettings;
|
||||
use codex_otel::auth_storage::AuthStorageOriginator;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::mcp::ClientMcpExtensions;
|
||||
@@ -372,10 +373,15 @@ impl MessageProcessor {
|
||||
Arc::clone(&extension_event_sink),
|
||||
))
|
||||
});
|
||||
let manager = ThreadManager::new(
|
||||
let startup = codex_core::build_startup_models(
|
||||
config.as_ref(),
|
||||
auth_manager.clone(),
|
||||
codex_core::build_models_manager(config.as_ref(), auth_manager.clone()),
|
||||
installation_id,
|
||||
Arc::new(ModelCatalogSettings::disk(config.codex_home.to_path_buf())),
|
||||
);
|
||||
let manager = ThreadManager::new(
|
||||
config.as_ref(),
|
||||
startup,
|
||||
codex_core::CodexAppsToolsCache::default(),
|
||||
session_source,
|
||||
environment_manager,
|
||||
@@ -405,7 +411,6 @@ impl MessageProcessor {
|
||||
codex_core::passthrough_image_store(),
|
||||
Arc::clone(&thread_store),
|
||||
codex_core::local_agent_graph_store_from_state_db(state_db.as_ref()),
|
||||
installation_id,
|
||||
Some(app_server_attestation_provider(
|
||||
outgoing.clone(),
|
||||
thread_state_manager.clone(),
|
||||
@@ -425,8 +430,7 @@ impl MessageProcessor {
|
||||
config_manager.clone(),
|
||||
startup_cwd,
|
||||
Arc::clone(&config),
|
||||
thread_manager.get_models_manager(),
|
||||
Arc::clone(&auth_manager),
|
||||
Arc::clone(&thread_manager),
|
||||
));
|
||||
let models_refresh_worker = crate::models_refresh_worker::spawn(&model_catalog);
|
||||
let turn_cost_worker =
|
||||
|
||||
@@ -4,9 +4,9 @@ use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_config::ConfigLayerStack;
|
||||
use codex_core::ThreadManager;
|
||||
use codex_core::config::Config;
|
||||
use codex_features::Feature;
|
||||
use codex_login::AuthManager;
|
||||
use codex_models_manager::manager::RefreshStrategy;
|
||||
use codex_models_manager::manager::SharedModelsManager;
|
||||
use codex_protocol::openai_models::ModelPreset;
|
||||
@@ -19,7 +19,7 @@ use crate::config_manager::StartupConfig;
|
||||
/// Refreshes catalog routing without changing the routes retained by existing threads.
|
||||
pub(crate) struct ModelCatalog {
|
||||
config_manager: ConfigManager,
|
||||
auth_manager: Arc<AuthManager>,
|
||||
thread_manager: Arc<ThreadManager>,
|
||||
startup_cwd: Option<PathBuf>,
|
||||
state: Mutex<CatalogState>,
|
||||
}
|
||||
@@ -35,12 +35,12 @@ impl ModelCatalog {
|
||||
config_manager: ConfigManager,
|
||||
startup_cwd: Option<PathBuf>,
|
||||
config: Arc<Config>,
|
||||
models_manager: SharedModelsManager,
|
||||
auth_manager: Arc<AuthManager>,
|
||||
thread_manager: Arc<ThreadManager>,
|
||||
) -> Self {
|
||||
let models_manager = thread_manager.get_models_manager();
|
||||
Self {
|
||||
config_manager,
|
||||
auth_manager,
|
||||
thread_manager,
|
||||
startup_cwd,
|
||||
state: Mutex::new(CatalogState {
|
||||
needs_reload: false,
|
||||
@@ -72,14 +72,6 @@ impl ModelCatalog {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn set_api_key_model_discovery_enabled(&self, enabled: bool) {
|
||||
self.state
|
||||
.lock()
|
||||
.await
|
||||
.models_manager
|
||||
.set_api_key_model_discovery_enabled(enabled);
|
||||
}
|
||||
|
||||
#[expect(
|
||||
clippy::await_holding_invalid_type,
|
||||
reason = "catalog reloads are serialized with account invalidation; model fetching holds no lock"
|
||||
@@ -100,11 +92,16 @@ impl ModelCatalog {
|
||||
if config.model_provider_id != state.config.model_provider_id
|
||||
|| config.model_provider != state.config.model_provider
|
||||
|| config.model_catalog != state.config.model_catalog
|
||||
|| config.features.enabled(Feature::RuntimeAgentInstances)
|
||||
!= state
|
||||
.config
|
||||
.features
|
||||
.enabled(Feature::RuntimeAgentInstances)
|
||||
|| config.runtime_auth_mode != state.config.runtime_auth_mode
|
||||
{
|
||||
state.models_manager =
|
||||
codex_core::build_models_manager(&config, Arc::clone(&self.auth_manager));
|
||||
state.models_manager = self.thread_manager.models_manager_for_config(&config);
|
||||
}
|
||||
state.models_manager.set_api_key_model_discovery_enabled(
|
||||
self.thread_manager.set_api_key_model_discovery_enabled(
|
||||
config.features.enabled(Feature::ApiKeyModelDiscovery),
|
||||
);
|
||||
state.config = Arc::new(config);
|
||||
|
||||
@@ -11,6 +11,7 @@ use codex_core::config::ConfigBuilder;
|
||||
use codex_core::config::ConfigOverrides;
|
||||
use codex_feedback::CodexFeedback;
|
||||
use codex_http_client::HttpClientFactory;
|
||||
use codex_models_manager::manager::ModelCatalogSettings;
|
||||
use codex_models_manager::manager::ModelsEndpointClient;
|
||||
use codex_models_manager::manager::ModelsEndpointFuture;
|
||||
use codex_models_manager::manager::ModelsEndpointResponse;
|
||||
@@ -28,6 +29,38 @@ use tracing_subscriber::layer::SubscriberExt;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn test_thread_manager(
|
||||
config: &codex_core::config::Config,
|
||||
models_manager: Option<SharedModelsManager>,
|
||||
auth_manager: Arc<codex_login::AuthManager>,
|
||||
) -> Arc<codex_core::ThreadManager> {
|
||||
let factory = match models_manager {
|
||||
Some(models) => codex_core::test_support::fixed_model_catalog(models),
|
||||
None => Arc::new(ModelCatalogSettings::disk(config.codex_home.to_path_buf())),
|
||||
};
|
||||
let startup = codex_core::build_startup_models(
|
||||
config,
|
||||
auth_manager,
|
||||
uuid::Uuid::new_v4().to_string(),
|
||||
factory,
|
||||
);
|
||||
Arc::new(codex_core::ThreadManager::new(
|
||||
config,
|
||||
startup,
|
||||
codex_core::CodexAppsToolsCache::default(),
|
||||
codex_protocol::protocol::SessionSource::Exec,
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
codex_extension_api::empty_extension_registry(),
|
||||
Arc::new(codex_core::test_support::EmptyUserInstructionsProvider),
|
||||
/*analytics_events_client*/ None,
|
||||
codex_core::passthrough_image_store(),
|
||||
codex_core::ThreadStoreBuilder::from_config(config, /*state_db*/ None).build(),
|
||||
/*agent_graph_store*/ None,
|
||||
/*attestation_provider*/ None,
|
||||
/*external_time_provider*/ None,
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TestModelsEndpoint {
|
||||
fetch_count: AtomicUsize,
|
||||
@@ -148,9 +181,8 @@ async fn refreshes_immediately_periodically_and_stops_when_dropped() {
|
||||
let catalog = Arc::new(ModelCatalog::new(
|
||||
config_manager,
|
||||
Some(config.cwd.to_path_buf()),
|
||||
config,
|
||||
Arc::clone(&models_manager),
|
||||
auth_manager,
|
||||
Arc::clone(&config),
|
||||
test_thread_manager(&config, Some(Arc::clone(&models_manager)), auth_manager),
|
||||
));
|
||||
let worker = spawn_with_interval(&catalog, Duration::from_millis(/*millis*/ 10));
|
||||
|
||||
@@ -232,9 +264,8 @@ async fn background_refresh_blocks_invalid_requirements_then_recovers() -> anyho
|
||||
let catalog = Arc::new(ModelCatalog::new(
|
||||
config_manager,
|
||||
Some(config.cwd.to_path_buf()),
|
||||
config,
|
||||
models_manager,
|
||||
auth_manager,
|
||||
Arc::clone(&config),
|
||||
test_thread_manager(&config, Some(models_manager), auth_manager),
|
||||
));
|
||||
let feedback = CodexFeedback::new();
|
||||
// This test uses Tokio's current-thread runtime, so spawned refreshes share the subscriber.
|
||||
@@ -319,9 +350,11 @@ async fn catalog_reload_recovers_after_cloud_failure() -> anyhow::Result<()> {
|
||||
/*enable_codex_api_key_env*/ false,
|
||||
)
|
||||
.await?;
|
||||
let models = codex_core::build_models_manager(&config, Arc::clone(&auth));
|
||||
let catalog = Arc::new(ModelCatalog::new(
|
||||
manager, /*startup_cwd*/ None, config, models, auth,
|
||||
manager,
|
||||
/*startup_cwd*/ None,
|
||||
Arc::clone(&config),
|
||||
test_thread_manager(&config, /*models_manager*/ None, auth),
|
||||
));
|
||||
failed.store(true, Ordering::SeqCst);
|
||||
catalog.invalidate().await;
|
||||
@@ -370,8 +403,12 @@ async fn catalog_reload_recovers_original_project_after_startup_fallback() -> an
|
||||
/*enable_codex_api_key_env*/ false,
|
||||
)
|
||||
.await?;
|
||||
let models = codex_core::build_models_manager(&config, Arc::clone(&auth));
|
||||
let catalog = ModelCatalog::new(manager, Some(project_path.clone()), config, models, auth);
|
||||
let catalog = ModelCatalog::new(
|
||||
manager,
|
||||
Some(project_path.clone()),
|
||||
Arc::clone(&config),
|
||||
test_thread_manager(&config, /*models_manager*/ None, auth),
|
||||
);
|
||||
let mut model = codex_models_manager::bundled_models_response()?
|
||||
.models
|
||||
.remove(0);
|
||||
|
||||
+1
-2
@@ -102,8 +102,7 @@ async fn background_routing_skips_config_until_chatgpt_sign_in(
|
||||
manager.clone(),
|
||||
Some(config.cwd.to_path_buf()),
|
||||
Arc::clone(&config),
|
||||
threads.get_models_manager(),
|
||||
Arc::clone(&auth_manager),
|
||||
Arc::clone(&threads),
|
||||
));
|
||||
let processor = AccountRequestProcessor::new(
|
||||
Arc::clone(&auth_manager),
|
||||
|
||||
@@ -357,16 +357,9 @@ impl ConfigRequestProcessor {
|
||||
.map_err(|_| internal_error("failed to update feature enablement"))?;
|
||||
|
||||
let config = self.load_latest_config(/*fallback_cwd*/ None).await?;
|
||||
self.thread_manager
|
||||
.get_models_manager()
|
||||
.set_api_key_model_discovery_enabled(
|
||||
config.features.enabled(Feature::ApiKeyModelDiscovery),
|
||||
);
|
||||
self.model_catalog
|
||||
.set_api_key_model_discovery_enabled(
|
||||
config.features.enabled(Feature::ApiKeyModelDiscovery),
|
||||
)
|
||||
.await;
|
||||
self.thread_manager.set_api_key_model_discovery_enabled(
|
||||
config.features.enabled(Feature::ApiKeyModelDiscovery),
|
||||
);
|
||||
if should_start_background_rollout_migration && config.features.enabled(feature) {
|
||||
self.thread_manager.start_background_rollout_migration();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user