Read plugin authentication state from AuthManager (#39087)

## What changed

- Give `PluginsManager` a shared `AuthManager` instead of a separately mutable authentication-mode snapshot.
- Read the current authentication mode and credentials from that shared manager for plugin discovery, startup tasks, CLI commands, MCP setup, and external-agent migration.
- Update test helpers and coverage to exercise plugin projections and curated marketplace selection as authentication changes.

GitOrigin-RevId: 600c94de5130eda2da5727e1a0b4d39083fefc56
This commit is contained in:
willwang-openai
2026-08-17 22:26:27 +00:00
committed by copyberry
parent 2013e04354
commit fc6268ad38
37 changed files with 448 additions and 320 deletions
+1
View File
@@ -3142,6 +3142,7 @@ dependencies = [
"codex-core",
"codex-core-plugins",
"codex-hooks",
"codex-login",
"codex-memories-write",
"codex-otel",
"codex-plugin",
@@ -98,7 +98,7 @@ impl ExternalAgentConfigRequestProcessor {
} = args;
let migration_service = ExternalAgentConfigService::new(
codex_home.clone(),
thread_manager.plugins_manager().auth_mode(),
thread_manager.auth_manager(),
analytics_events_client.clone(),
state_db.clone(),
);
@@ -128,7 +128,6 @@ impl ExternalAgentConfigRequestProcessor {
) -> Result<ExternalAgentConfigDetectResponse, JSONRPCErrorError> {
let migration_service = self
.migration_service
.with_auth_mode(self.thread_manager.plugins_manager().auth_mode())
.with_migration_source(params.migration_source.as_deref());
let default_session_import_limits = ExternalAgentSessionImportLimits::default();
let migration_service =
@@ -200,7 +199,6 @@ impl ExternalAgentConfigRequestProcessor {
let provider_id = params.provider_id.clone();
let migration_service = self
.migration_service
.with_auth_mode(self.thread_manager.plugins_manager().auth_mode())
.with_migration_source(params.migration_source.as_deref());
let needs_runtime_refresh = migration_items_need_runtime_refresh(&params.migration_items);
let has_migration_items = !params.migration_items.is_empty();
+1 -2
View File
@@ -499,7 +499,7 @@ impl MessageProcessor {
config_warnings,
);
let turn_processor = TurnRequestProcessor::new(
auth_manager.clone(),
auth_manager,
Arc::clone(&thread_manager),
outgoing.clone(),
analytics_events_client.clone(),
@@ -521,7 +521,6 @@ impl MessageProcessor {
.plugins_manager()
.maybe_start_plugin_startup_tasks_for_config(
&config.plugins_config_input(),
auth_manager,
Some(on_effective_plugins_changed),
);
}
@@ -182,9 +182,6 @@ impl AccountRequestProcessor {
pub(crate) fn clear_external_auth(&self) {
self.auth_manager.clear_external_auth();
self.thread_manager
.plugins_manager()
.set_auth_mode(self.auth_manager.get_api_auth_mode());
}
fn current_account_updated_notification(&self) -> AccountUpdatedNotification {
@@ -217,9 +214,6 @@ impl AccountRequestProcessor {
thread_manager: &Arc<ThreadManager>,
auth: Option<CodexAuth>,
) {
thread_manager
.plugins_manager()
.set_auth_mode(auth.as_ref().map(CodexAuth::api_auth_mode));
thread_manager
.plugins_manager()
.clear_recommended_plugins_cache();
@@ -567,7 +567,6 @@ impl PluginRequestProcessor {
}
let auth = self.auth_manager.auth().await;
let auth_mode = auth.as_ref().map(CodexAuth::api_auth_mode);
plugins_manager.set_auth_mode(auth_mode);
let plugins_input = config.plugins_config_input();
if include_local
&& force_refetch
@@ -821,7 +820,6 @@ impl PluginRequestProcessor {
}
let auth = self.auth_manager.auth().await;
let auth_mode = auth.as_ref().map(CodexAuth::api_auth_mode);
plugins_manager.set_auth_mode(auth_mode);
let plugins_input = config.plugins_config_input();
let use_remote_global_catalog = config.features.enabled(Feature::RemotePlugin)
@@ -1011,7 +1009,6 @@ impl PluginRequestProcessor {
let config = self.load_latest_config(config_cwd).await?;
let plugins_input = config.plugins_config_input();
let auth = self.auth_manager.auth().await;
plugins_manager.set_auth_mode(auth.as_ref().map(CodexAuth::api_auth_mode));
let plugin = match read_source {
Ok(marketplace_path) => {
@@ -50,9 +50,6 @@ impl PluginRequestProcessor {
let auth = self.auth_manager.auth().await;
let auth_mode = auth.as_ref().map(CodexAuth::api_auth_mode);
self.thread_manager
.plugins_manager()
.set_auth_mode(auth_mode);
let remote_plugin_enabled = config.features.enabled(Feature::RemotePlugin);
let use_remote_global_catalog =
remote_plugin_enabled && auth_mode.is_some_and(DomainAuthMode::uses_codex_backend);
+3 -3
View File
@@ -26,7 +26,7 @@ use std::path::PathBuf;
use crate::plugin_cmd::JsonMarketplaceSource;
use crate::plugin_cmd::configured_marketplace_snapshot_issues;
use crate::plugin_cmd::configured_marketplace_sources;
use crate::plugin_cmd::load_cli_auth_mode;
use crate::plugin_cmd::load_cli_auth_manager;
#[derive(Debug, Parser)]
#[command(bin_name = "codex plugin marketplace")]
@@ -211,7 +211,7 @@ async fn run_list(overrides: Vec<(String, toml::Value)>, args: ListMarketplaceAr
let config = Config::load_with_cli_overrides(overrides)
.await
.context("failed to load configuration")?;
let manager = plugins_manager_for_config(&config, load_cli_auth_mode(&config).await?);
let manager = plugins_manager_for_config(&config, load_cli_auth_manager(&config).await?);
let plugins_input = config.plugins_config_input();
let marketplace_listing = manager
.discover_marketplaces_for_config(&plugins_input, &[])
@@ -377,7 +377,7 @@ async fn run_upgrade(
let config = Config::load_with_cli_overrides(overrides)
.await
.context("failed to load configuration")?;
let manager = plugins_manager_for_config(&config, load_cli_auth_mode(&config).await?);
let manager = plugins_manager_for_config(&config, load_cli_auth_manager(&config).await?);
let plugins_input = config.plugins_config_input();
let outcome = manager
.upgrade_configured_marketplaces_for_config(&plugins_input, marketplace_name.as_deref())
+2 -2
View File
@@ -41,7 +41,7 @@ use codex_utils_cli::CliConfigOverrides;
use codex_utils_cli::format_env_display;
use crate::cloud_config;
use crate::plugin_cmd::load_cli_auth_mode;
use crate::plugin_cmd::load_cli_auth_manager;
/// Subcommands:
/// - `list` — list configured servers (with `--json`)
@@ -523,7 +523,7 @@ async fn run_remove(config_overrides: &CliConfigOverrides, remove_args: RemoveAr
async fn load_mcp_manager(config: &Config) -> Result<McpManager> {
let plugins_manager = Arc::new(plugins_manager_for_config(
config,
load_cli_auth_mode(config).await?,
load_cli_auth_manager(config).await?,
));
Ok(McpManager::new(plugins_manager))
}
+4 -10
View File
@@ -22,12 +22,12 @@ use codex_core_plugins::marketplace::find_marketplace_manifest_path;
use codex_login::AuthManager;
use codex_plugin::PluginId;
use codex_plugin::validate_plugin_segment;
use codex_protocol::auth::AuthMode;
use codex_utils_cli::CliConfigOverrides;
use serde::Serialize;
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
use crate::marketplace_cmd::MarketplaceCli;
@@ -590,7 +590,7 @@ async fn load_plugin_command_context(
.await
.context("failed to load configuration")?;
let plugins_input = config.plugins_config_input();
let manager = plugins_manager_for_config(&config, load_cli_auth_mode(&config).await?);
let manager = plugins_manager_for_config(&config, load_cli_auth_manager(&config).await?);
Ok(PluginCommandContext {
codex_home: codex_home.to_path_buf(),
plugins_input,
@@ -598,14 +598,8 @@ async fn load_plugin_command_context(
})
}
pub(crate) async fn load_cli_auth_mode(config: &Config) -> Result<Option<AuthMode>> {
Ok(
AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ true)
.await?
.auth()
.await
.map(|auth| auth.api_auth_mode()),
)
pub(crate) async fn load_cli_auth_manager(config: &Config) -> Result<Arc<AuthManager>> {
Ok(AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ true).await?)
}
struct PluginSelection {
+68 -25
View File
@@ -10,7 +10,11 @@ use crate::remote::fetch_and_cache_global_remote_plugin_catalog;
use crate::startup_sync::curated_plugins_repo_path;
use crate::test_support::TEST_CURATED_PLUGIN_SHA;
use crate::test_support::load_plugins_config;
use crate::test_support::set_test_auth_mode;
use crate::test_support::test_auth_manager;
use crate::test_support::test_plugins_manager;
use crate::test_support::test_plugins_manager_with_auth_manager;
use crate::test_support::test_plugins_manager_with_options;
use crate::test_support::write_curated_plugin;
use crate::test_support::write_curated_plugin_sha_with;
use crate::test_support::write_file;
@@ -19,11 +23,13 @@ use crate::test_support::write_openai_curated_marketplace;
use codex_config::CONFIG_TOML_FILE;
use codex_login::CodexAuth;
use codex_protocol::auth::AuthMode;
use codex_protocol::protocol::Product;
use codex_utils_absolute_path::AbsolutePathBuf;
use pretty_assertions::assert_eq;
use serde_json::json;
use std::collections::HashSet;
use std::path::Path;
use std::sync::Arc;
use tempfile::tempdir;
use tracing::Level;
use tracing_subscriber::fmt::format::FmtSpan;
@@ -50,8 +56,11 @@ remote_plugin = false
write_openai_curated_marketplace(&curated_root, &["sample", "slack", "openai-developers"]);
let plugins = load_plugins_config(codex_home.path(), codex_home.path()).await;
let plugins_manager = test_plugins_manager(codex_home.path().to_path_buf());
plugins_manager.set_auth_mode(Some(AuthMode::Chatgpt));
let plugins_manager = test_plugins_manager_with_options(
codex_home.path().to_path_buf(),
Some(Product::Codex),
Some(AuthMode::Chatgpt),
);
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
let discoverable_plugins = list_discoverable_plugins(
&plugins_manager,
@@ -79,8 +88,11 @@ async fn returns_api_curated_fallback_plugins_for_direct_provider_auth() {
write_openai_api_curated_marketplace(&curated_root, &["sample", "slack", "openai-developers"]);
let plugins = load_plugins_config(codex_home.path(), codex_home.path()).await;
let plugins_manager = test_plugins_manager(codex_home.path().to_path_buf());
plugins_manager.set_auth_mode(Some(AuthMode::ApiKey));
let plugins_manager = test_plugins_manager_with_options(
codex_home.path().to_path_buf(),
Some(Product::Codex),
Some(AuthMode::ApiKey),
);
let auth = CodexAuth::from_api_key("test-api-key");
let discoverable_plugins = list_discoverable_plugins(
&plugins_manager,
@@ -112,8 +124,11 @@ async fn returns_microsoft_fallback_plugins() {
install_marketplace_plugin(codex_home.path(), curated_root.as_path(), "teams").await;
let plugins = load_plugins_config(codex_home.path(), codex_home.path()).await;
let plugins_manager = test_plugins_manager(codex_home.path().to_path_buf());
plugins_manager.set_auth_mode(Some(AuthMode::Chatgpt));
let plugins_manager = test_plugins_manager_with_options(
codex_home.path().to_path_buf(),
Some(Product::Codex),
Some(AuthMode::Chatgpt),
);
let discoverable_plugins = list_discoverable_plugins(
&plugins_manager,
discovery_input(plugins, &[], &[], &[]),
@@ -171,8 +186,11 @@ source = "/tmp/{bundled_marketplace_name}"
);
let plugins = load_plugins_config(codex_home.path(), codex_home.path()).await;
let plugins_manager = test_plugins_manager(codex_home.path().to_path_buf());
plugins_manager.set_auth_mode(Some(AuthMode::Chatgpt));
let plugins_manager = test_plugins_manager_with_options(
codex_home.path().to_path_buf(),
Some(Product::Codex),
Some(AuthMode::Chatgpt),
);
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
let discoverable_plugins = list_discoverable_plugins(
&plugins_manager,
@@ -255,8 +273,12 @@ source = "/tmp/{marketplace_name}"
),
);
let plugins = load_plugins_config(codex_home.path(), codex_home.path()).await;
let plugins_manager = test_plugins_manager(codex_home.path().to_path_buf());
assert!(plugins_manager.set_auth_mode(Some(AuthMode::Chatgpt)));
let auth_manager = test_auth_manager(Some(AuthMode::Chatgpt));
let plugins_manager = test_plugins_manager_with_auth_manager(
codex_home.path().to_path_buf(),
Some(Product::Codex),
Arc::clone(&auth_manager),
);
let chatgpt_projection = list_discoverable_plugins(
&plugins_manager,
discovery_input(plugins.clone(), &[plugin_id.as_str()], &[], &[]),
@@ -276,7 +298,7 @@ source = "/tmp/{marketplace_name}"
};
assert_eq!(chatgpt_projection, vec![expected.clone()]);
assert!(plugins_manager.set_auth_mode(Some(AuthMode::ApiKey)));
set_test_auth_mode(&auth_manager, Some(AuthMode::ApiKey)).await;
let api_key_projection = list_discoverable_plugins(
&plugins_manager,
discovery_input(plugins, &[plugin_id.as_str()], &[], &[]),
@@ -300,8 +322,11 @@ async fn reprojects_cached_skill_availability_for_current_config() {
write_openai_curated_marketplace(&curated_root, &["slack"]);
let plugins = load_plugins_config(codex_home.path(), codex_home.path()).await;
let plugins_manager = test_plugins_manager(codex_home.path().to_path_buf());
plugins_manager.set_auth_mode(Some(AuthMode::Chatgpt));
let plugins_manager = test_plugins_manager_with_options(
codex_home.path().to_path_buf(),
Some(Product::Codex),
Some(AuthMode::Chatgpt),
);
let expected = ToolSuggestDiscoverablePlugin {
id: "slack@openai-curated".to_string(),
remote_plugin_id: None,
@@ -355,8 +380,11 @@ async fn does_not_advertise_skills_when_skill_loading_fails() {
);
let plugins = load_plugins_config(codex_home.path(), codex_home.path()).await;
let plugins_manager = test_plugins_manager(codex_home.path().to_path_buf());
plugins_manager.set_auth_mode(Some(AuthMode::Chatgpt));
let plugins_manager = test_plugins_manager_with_options(
codex_home.path().to_path_buf(),
Some(Product::Codex),
Some(AuthMode::Chatgpt),
);
let discoverable_plugins = list_discoverable_plugins(
&plugins_manager,
discovery_input(plugins, &[], &[], &[]),
@@ -395,8 +423,11 @@ async fn clear_cache_invalidates_cached_tool_suggest_metadata() {
);
let plugins = load_plugins_config(codex_home.path(), codex_home.path()).await;
let plugins_manager = test_plugins_manager(codex_home.path().to_path_buf());
plugins_manager.set_auth_mode(Some(AuthMode::Chatgpt));
let plugins_manager = test_plugins_manager_with_options(
codex_home.path().to_path_buf(),
Some(Product::Codex),
Some(AuthMode::Chatgpt),
);
let input = discovery_input(plugins, &[], &[], &[]);
let expected_cached = vec![ToolSuggestDiscoverablePlugin {
id: "slack@openai-curated".to_string(),
@@ -468,8 +499,11 @@ source = "/tmp/{marketplace_name}"
install_marketplace_plugin(codex_home.path(), curated_root.as_path(), "installed").await;
let plugins = load_plugins_config(codex_home.path(), codex_home.path()).await;
let plugins_manager = test_plugins_manager(codex_home.path().to_path_buf());
plugins_manager.set_auth_mode(Some(AuthMode::Chatgpt));
let plugins_manager = test_plugins_manager_with_options(
codex_home.path().to_path_buf(),
Some(Product::Codex),
Some(AuthMode::Chatgpt),
);
let discoverable_plugins = list_discoverable_plugins(
&plugins_manager,
discovery_input(plugins, &[], &[], &[]),
@@ -496,8 +530,11 @@ async fn normalizes_description() {
install_marketplace_plugin(codex_home.path(), curated_root.as_path(), "installed").await;
let plugins = load_plugins_config(codex_home.path(), codex_home.path()).await;
let plugins_manager = test_plugins_manager(codex_home.path().to_path_buf());
plugins_manager.set_auth_mode(Some(AuthMode::Chatgpt));
let plugins_manager = test_plugins_manager_with_options(
codex_home.path().to_path_buf(),
Some(Product::Codex),
Some(AuthMode::Chatgpt),
);
let discoverable_plugins = list_discoverable_plugins(
&plugins_manager,
discovery_input(plugins, &[], &[], &[]),
@@ -581,8 +618,11 @@ async fn omits_not_available_curated_plugins() {
install_marketplace_plugin(codex_home.path(), curated_root.as_path(), "installed").await;
let plugins = load_plugins_config(codex_home.path(), codex_home.path()).await;
let plugins_manager = test_plugins_manager(codex_home.path().to_path_buf());
plugins_manager.set_auth_mode(Some(AuthMode::Chatgpt));
let plugins_manager = test_plugins_manager_with_options(
codex_home.path().to_path_buf(),
Some(Product::Codex),
Some(AuthMode::Chatgpt),
);
let discoverable_plugins = list_discoverable_plugins(
&plugins_manager,
discovery_input(plugins, &[], &[], &[]),
@@ -623,8 +663,11 @@ async fn does_not_reload_marketplace_per_plugin() {
}
let plugins = load_plugins_config(codex_home.path(), codex_home.path()).await;
let plugins_manager = test_plugins_manager(codex_home.path().to_path_buf());
plugins_manager.set_auth_mode(Some(AuthMode::Chatgpt));
let plugins_manager = test_plugins_manager_with_options(
codex_home.path().to_path_buf(),
Some(Product::Codex),
Some(AuthMode::Chatgpt),
);
let buffer: &'static std::sync::Mutex<Vec<u8>> =
Box::leak(Box::new(std::sync::Mutex::new(Vec::new())));
let subscriber = tracing_subscriber::fmt()
+8 -25
View File
@@ -448,7 +448,7 @@ pub struct PluginsManager {
remote_installed_plugins_cache_refresh_state: RwLock<RemoteInstalledPluginsCacheRefreshState>,
remote_catalog_cache_refresh_state: RwLock<RemoteCatalogCacheRefreshState>,
restriction_product: Option<Product>,
auth_mode: RwLock<Option<AuthMode>>,
auth_manager: Arc<AuthManager>,
analytics_events_client: RwLock<Option<AnalyticsEventsClient>>,
plugin_install_source: PluginInstallSource,
}
@@ -501,13 +501,13 @@ fn target_curated_marketplace(auth_mode: Option<AuthMode>) -> TargetCuratedMarke
impl PluginsManager {
pub fn new(
codex_home: PathBuf,
auth_mode: Option<AuthMode>,
auth_manager: Arc<AuthManager>,
skill_root_loader: Arc<dyn SkillRootLoader<PluginSkillRoot>>,
) -> Self {
Self::new_with_options(
codex_home,
Some(Product::Codex),
auth_mode,
auth_manager,
skill_root_loader,
)
}
@@ -515,7 +515,7 @@ impl PluginsManager {
pub fn new_with_options(
codex_home: PathBuf,
restriction_product: Option<Product>,
auth_mode: Option<AuthMode>,
auth_manager: Arc<AuthManager>,
skill_root_loader: Arc<dyn SkillRootLoader<PluginSkillRoot>>,
) -> Self {
// Product restrictions are enforced at marketplace admission time for a given CODEX_HOME:
@@ -552,7 +552,7 @@ impl PluginsManager {
RemoteCatalogCacheRefreshState::default(),
),
restriction_product,
auth_mode: RwLock::new(auth_mode),
auth_manager,
analytics_events_client: RwLock::new(None),
plugin_install_source: PluginInstallSource::Manual,
}
@@ -563,23 +563,8 @@ impl PluginsManager {
self
}
pub fn set_auth_mode(&self, auth_mode: Option<AuthMode>) -> bool {
let mut stored_auth_mode = match self.auth_mode.write() {
Ok(auth_mode_guard) => auth_mode_guard,
Err(err) => err.into_inner(),
};
if *stored_auth_mode == auth_mode {
return false;
}
*stored_auth_mode = auth_mode;
true
}
pub fn auth_mode(&self) -> Option<AuthMode> {
match self.auth_mode.read() {
Ok(auth_mode_guard) => *auth_mode_guard,
Err(err) => *err.into_inner(),
}
self.auth_manager.get_api_auth_mode()
}
fn remote_global_catalog_active(&self, config: &PluginsConfigInput) -> bool {
@@ -2118,7 +2103,6 @@ impl PluginsManager {
pub fn maybe_start_plugin_startup_tasks_for_config(
self: &Arc<Self>,
config: &PluginsConfigInput,
auth_manager: Arc<AuthManager>,
on_effective_plugins_changed: Option<EffectivePluginsChangedCallback>,
) {
if config.plugins_enabled {
@@ -2191,10 +2175,9 @@ impl PluginsManager {
}
let config_for_remote_sync = config.clone();
let manager = Arc::clone(self);
let auth_manager_for_remote_sync = auth_manager.clone();
let on_effective_plugins_changed = on_effective_plugins_changed.clone();
tokio::spawn(async move {
let auth = auth_manager_for_remote_sync.auth().await;
let auth = manager.auth_manager.auth().await;
manager.maybe_start_remote_plugin_caches_refresh(
&config_for_remote_sync,
auth.clone(),
@@ -2226,7 +2209,7 @@ impl PluginsManager {
let config_for_featured_plugins = config.clone();
let manager = Arc::clone(self);
tokio::spawn(async move {
let auth = auth_manager.auth().await;
let auth = manager.auth_manager.auth().await;
if let Err(err) = manager
.featured_plugin_ids_for_config(&config_for_featured_plugins, auth.as_ref())
.await
+52 -28
View File
@@ -20,8 +20,11 @@ use crate::startup_sync::curated_plugins_repo_path;
use crate::test_support::TEST_CURATED_PLUGIN_CACHE_VERSION;
use crate::test_support::TEST_CURATED_PLUGIN_SHA;
use crate::test_support::load_plugins_config as load_plugins_config_input;
use crate::test_support::set_test_auth_mode;
use crate::test_support::test_auth_manager;
use crate::test_support::test_http_client_factory;
use crate::test_support::test_plugins_manager;
use crate::test_support::test_plugins_manager_with_auth_manager;
use crate::test_support::test_plugins_manager_with_options;
use crate::test_support::test_skill_root_loader;
use crate::test_support::write_curated_plugin;
@@ -45,6 +48,7 @@ use codex_config::SkillConfigRules;
use codex_config::compose_requirements;
use codex_config::types::McpServerTransportConfig;
use codex_login::CodexAuth;
use codex_login::test_support::auth_manager_with_agent_identity;
use codex_model_provider::AMAZON_BEDROCK_PROVIDER_ID;
use codex_plugin::AppDeclaration;
use codex_plugin::PluginId;
@@ -118,18 +122,22 @@ fn plugins_config_input_with_requirements(
)
}
#[test]
fn plugins_manager_tracks_auth_mode() {
#[tokio::test]
async fn plugins_manager_reads_auth_mode_from_auth_manager() {
let tmp = TempDir::new().unwrap();
let manager = test_plugins_manager(tmp.path().to_path_buf());
let auth_manager = test_auth_manager(/*auth_mode*/ None);
let manager = test_plugins_manager_with_auth_manager(
tmp.path().to_path_buf(),
Some(Product::Codex),
Arc::clone(&auth_manager),
);
assert_eq!(manager.auth_mode(), None);
assert!(manager.set_auth_mode(Some(AuthMode::ApiKey)));
set_test_auth_mode(&auth_manager, Some(AuthMode::ApiKey)).await;
assert_eq!(manager.auth_mode(), Some(AuthMode::ApiKey));
assert!(!manager.set_auth_mode(Some(AuthMode::ApiKey)));
assert!(manager.set_auth_mode(Some(AuthMode::ChatgptAuthTokens)));
set_test_auth_mode(&auth_manager, Some(AuthMode::ChatgptAuthTokens)).await;
assert_eq!(manager.auth_mode(), Some(AuthMode::ChatgptAuthTokens));
assert!(manager.set_auth_mode(/*auth_mode*/ None));
set_test_auth_mode(&auth_manager, /*auth_mode*/ None).await;
assert_eq!(manager.auth_mode(), None);
let manager_with_auth = test_plugins_manager_with_options(
@@ -514,10 +522,12 @@ async fn plugin_auth_projection_hides_dual_surface_mcp_with_agent_identity_apps_
write_auth_projection_plugin(codex_home.path(), "sample", /*include_app*/ true);
write_auth_projection_plugin(codex_home.path(), "docs", /*include_app*/ false);
let config = auth_projection_config(codex_home.path()).await;
let manager = test_plugins_manager_with_options(
let manager = test_plugins_manager_with_auth_manager(
codex_home.path().to_path_buf(),
Some(Product::Codex),
Some(AuthMode::AgentIdentity),
auth_manager_with_agent_identity()
.await
.expect("create test Agent Identity auth manager"),
);
let outcome = manager.plugins_for_config(&config).await;
@@ -656,10 +666,12 @@ async fn plugin_auth_projection_reprojects_cached_plugins_when_auth_changes() {
write_auth_projection_plugin(codex_home.path(), "sample", /*include_app*/ true);
write_auth_projection_plugin(codex_home.path(), "docs", /*include_app*/ false);
let config = auth_projection_config(codex_home.path()).await;
let manager = test_plugins_manager_with_options(
let auth_manager = test_auth_manager(/*auth_mode*/ None);
set_test_auth_mode(&auth_manager, Some(AuthMode::Chatgpt)).await;
let manager = test_plugins_manager_with_auth_manager(
codex_home.path().to_path_buf(),
Some(Product::Codex),
Some(AuthMode::Chatgpt),
Arc::clone(&auth_manager),
);
let chatgpt_outcome = manager.plugins_for_config(&config).await;
@@ -695,7 +707,7 @@ async fn plugin_auth_projection_reprojects_cached_plugins_when_auth_changes() {
]
);
assert!(manager.set_auth_mode(Some(AuthMode::ApiKey)));
set_test_auth_mode(&auth_manager, Some(AuthMode::ApiKey)).await;
let api_key_outcome = manager.plugins_for_config(&config).await;
assert_eq!(
@@ -1625,10 +1637,12 @@ enabled = true
write_cached_plugin(codex_home.path(), "openai-curated-remote", "remote-only");
let mut config = load_config(codex_home.path(), codex_home.path()).await;
let manager = test_plugins_manager_with_options(
let auth_manager = test_auth_manager(/*auth_mode*/ None);
set_test_auth_mode(&auth_manager, Some(AuthMode::Chatgpt)).await;
let manager = test_plugins_manager_with_auth_manager(
codex_home.path().to_path_buf(),
Some(Product::Codex),
Some(AuthMode::Chatgpt),
Arc::clone(&auth_manager),
);
manager.write_remote_installed_plugins_cache(vec![remote_installed_plugin("remote-only")]);
assert_eq!(
@@ -1636,14 +1650,14 @@ enabled = true
vec!["remote-only@openai-curated-remote".to_string()]
);
manager.set_auth_mode(/*auth_mode*/ None);
set_test_auth_mode(&auth_manager, /*auth_mode*/ None).await;
assert_eq!(
loaded_plugin_names(&manager, &config).await,
vec!["linear@openai-api-curated".to_string()]
);
for auth_mode in [AuthMode::ApiKey, AuthMode::BedrockApiKey] {
manager.set_auth_mode(Some(auth_mode));
set_test_auth_mode(&auth_manager, Some(auth_mode)).await;
assert_eq!(
loaded_plugin_names(&manager, &config).await,
@@ -1651,7 +1665,7 @@ enabled = true
);
}
manager.set_auth_mode(/*auth_mode*/ None);
set_test_auth_mode(&auth_manager, /*auth_mode*/ None).await;
for model_provider_id in ["openai", AMAZON_BEDROCK_PROVIDER_ID, "ollama"] {
config.model_provider_id = model_provider_id.to_string();
assert_eq!(
@@ -1660,7 +1674,7 @@ enabled = true
);
}
manager.set_auth_mode(Some(AuthMode::Chatgpt));
set_test_auth_mode(&auth_manager, Some(AuthMode::Chatgpt)).await;
assert_eq!(
loaded_plugin_names(&manager, &config).await,
vec!["remote-only@openai-curated-remote".to_string()]
@@ -4804,8 +4818,11 @@ plugins = true
);
let config = load_config(tmp.path(), tmp.path()).await;
let manager = test_plugins_manager(tmp.path().to_path_buf());
manager.set_auth_mode(Some(AuthMode::ApiKey));
let manager = test_plugins_manager_with_options(
tmp.path().to_path_buf(),
Some(Product::Codex),
Some(AuthMode::ApiKey),
);
let marketplaces = manager
.list_marketplaces_for_config(&config, &[], /*include_openai_curated*/ true)
.unwrap()
@@ -4939,8 +4956,11 @@ plugins = true
write_openai_api_curated_marketplace(&curated_root, &["api-plugin"]);
let config = load_config(tmp.path(), tmp.path()).await;
let manager = test_plugins_manager(tmp.path().to_path_buf());
manager.set_auth_mode(Some(AuthMode::Chatgpt));
let manager = test_plugins_manager_with_options(
tmp.path().to_path_buf(),
Some(Product::Codex),
Some(AuthMode::Chatgpt),
);
let marketplaces = manager
.list_marketplaces_for_config(&config, &[], /*include_openai_curated*/ true)
.unwrap()
@@ -4975,8 +4995,11 @@ plugins = true
);
let config = load_config(tmp.path(), tmp.path()).await;
let manager = test_plugins_manager(tmp.path().to_path_buf());
manager.set_auth_mode(Some(AuthMode::BedrockApiKey));
let manager = test_plugins_manager_with_options(
tmp.path().to_path_buf(),
Some(Product::Codex),
Some(AuthMode::BedrockApiKey),
);
let outcome = manager
.list_marketplaces_for_config(&config, &[], /*include_openai_curated*/ true)
.unwrap();
@@ -6635,10 +6658,11 @@ enabled = true
);
}
let config = load_config(codex_home.path(), codex_home.path()).await;
let manager = test_plugins_manager_with_options(
let auth_manager = test_auth_manager(Some(AuthMode::Chatgpt));
let manager = test_plugins_manager_with_auth_manager(
codex_home.path().to_path_buf(),
Some(Product::Codex),
Some(AuthMode::Chatgpt),
Arc::clone(&auth_manager),
);
let chatgpt_hooks = manager
@@ -6653,7 +6677,7 @@ enabled = true
vec!["linear@openai-curated"]
);
assert!(manager.set_auth_mode(Some(AuthMode::ApiKey)));
set_test_auth_mode(&auth_manager, Some(AuthMode::ApiKey)).await;
let api_hooks = manager
.plugin_hooks_for_layer_stack(&config.config_layer_stack, &config)
.await;
@@ -6666,7 +6690,7 @@ enabled = true
vec!["linear@openai-api-curated"]
);
assert!(manager.set_auth_mode(/*auth_mode*/ None));
set_test_auth_mode(&auth_manager, /*auth_mode*/ None).await;
let bedrock_hooks = manager
.plugin_hooks_for_layer_stack(&config.config_layer_stack, &config)
.await;
+71 -2
View File
@@ -21,6 +21,14 @@ use codex_http_client::HttpClientFactory;
use codex_http_client::OutboundProxyPolicy;
use codex_http_client::RouteAwareClientPool;
use codex_http_client::RouteAwareRequestBuilder;
use codex_login::AuthHeaders;
use codex_login::AuthManager;
use codex_login::CodexAuth;
use codex_login::ExternalAuth;
use codex_login::ExternalAuthFuture;
use codex_login::ExternalAuthRefreshContext;
use codex_login::auth::BedrockApiKeyAuth;
use codex_login::test_support::auth_manager_from_optional_auth;
use codex_protocol::auth::AuthMode;
use codex_protocol::protocol::Product;
use codex_protocol::protocol::SkillScope;
@@ -45,7 +53,7 @@ pub(crate) const TEST_CURATED_PLUGIN_CACHE_VERSION: &str = "01234567";
pub(crate) fn test_plugins_manager(codex_home: PathBuf) -> PluginsManager {
PluginsManager::new(
codex_home,
/*auth_mode*/ None,
test_auth_manager(/*auth_mode*/ None),
test_skill_root_loader(),
)
}
@@ -58,11 +66,72 @@ pub(crate) fn test_plugins_manager_with_options(
PluginsManager::new_with_options(
codex_home,
restriction_product,
auth_mode,
test_auth_manager(auth_mode),
test_skill_root_loader(),
)
}
pub(crate) fn test_plugins_manager_with_auth_manager(
codex_home: PathBuf,
restriction_product: Option<Product>,
auth_manager: Arc<AuthManager>,
) -> PluginsManager {
PluginsManager::new_with_options(
codex_home,
restriction_product,
auth_manager,
test_skill_root_loader(),
)
}
pub(crate) fn test_auth_manager(auth_mode: Option<AuthMode>) -> Arc<AuthManager> {
auth_manager_from_optional_auth(test_codex_auth(auth_mode))
}
pub(crate) async fn set_test_auth_mode(auth_manager: &AuthManager, auth_mode: Option<AuthMode>) {
let Some(auth) = test_codex_auth(auth_mode) else {
auth_manager.clear_external_auth();
return;
};
auth_manager
.set_external_auth(Arc::new(StaticExternalAuth(auth)))
.await
.expect("test auth should update");
}
fn test_codex_auth(auth_mode: Option<AuthMode>) -> Option<CodexAuth> {
auth_mode.map(|auth_mode| match auth_mode {
AuthMode::ApiKey => CodexAuth::from_api_key("test-api-key"),
AuthMode::Chatgpt => CodexAuth::create_dummy_chatgpt_auth_for_testing(),
AuthMode::ChatgptAuthTokens => CodexAuth::from_external_chatgpt_tokens(
"header.e30.test",
"test-account",
/*chatgpt_plan_type*/ None,
)
.expect("test ChatGPT tokens should parse"),
AuthMode::Headers => CodexAuth::Headers(AuthHeaders::new(http::HeaderMap::new())),
AuthMode::BedrockApiKey => CodexAuth::BedrockApiKey(BedrockApiKeyAuth {
api_key: "test-api-key".to_string(),
region: "us-east-1".to_string(),
}),
AuthMode::AgentIdentity | AuthMode::PersonalAccessToken => {
panic!("test auth mode requires a purpose-built CodexAuth")
}
})
}
struct StaticExternalAuth(CodexAuth);
impl ExternalAuth for StaticExternalAuth {
fn resolve(&self) -> ExternalAuthFuture<'_, CodexAuth> {
Box::pin(async { Ok(self.0.clone()) })
}
fn refresh(&self, _context: ExternalAuthRefreshContext) -> ExternalAuthFuture<'_, CodexAuth> {
Box::pin(async { Ok(self.0.clone()) })
}
}
pub(crate) fn test_skill_root_loader() -> Arc<dyn SkillRootLoader<PluginSkillRoot>> {
Arc::new(TestSkillRootLoader)
}
+5 -1
View File
@@ -2,6 +2,7 @@ use super::*;
use crate::config::ConfigBuilder;
use crate::plugins::plugins_manager_for_config;
use crate::skills_load_input_from_config;
use codex_login::test_support::auth_manager_from_optional_auth;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::models::BaseInstructionsProvenance;
use codex_protocol::openai_models::ReasoningEffort;
@@ -463,7 +464,10 @@ enabled = false
.await
.expect("custom role should apply");
let plugins_manager = Arc::new(plugins_manager_for_config(&config, /*auth_mode*/ None));
let plugins_manager = Arc::new(plugins_manager_for_config(
&config,
auth_manager_from_optional_auth(/*auth*/ None),
));
let skills_service =
HostSkillsService::new(home.path().abs(), /*bundled_skills_enabled*/ true);
let plugins_input = config.plugins_config_input();
+19 -9
View File
@@ -73,6 +73,7 @@ use codex_config::types::WindowsToml;
use codex_exec_server::LOCAL_FS;
use codex_features::Feature;
use codex_features::FeaturesToml;
use codex_login::test_support::auth_manager_from_optional_auth;
use codex_model_provider::ProviderCapabilities;
use codex_model_provider_info::LMSTUDIO_OSS_PROVIDER_ID;
use codex_model_provider_info::OLLAMA_OSS_PROVIDER_ID;
@@ -529,7 +530,8 @@ async fn load_config_resolves_non_prefixed_mcp_tool_servers() -> std::io::Result
assert_eq!(config.non_prefixed_mcp_tool_servers, expected_servers);
assert_eq!(config.prefix_mcp_tool_names(), expected_prefix);
let plugins_manager = plugins_manager_for_config(&config, /*auth_mode*/ None);
let plugins_manager =
plugins_manager_for_config(&config, auth_manager_from_optional_auth(/*auth*/ None));
let mcp_config = config.to_mcp_config(&plugins_manager).await;
assert_eq!(mcp_config.prefix_mcp_tool_names, expected_prefix);
assert_eq!(
@@ -5243,7 +5245,8 @@ async fn rebuild_preserving_session_layers_refreshes_plugin_derived_mcp_config()
let config = thread_config
.rebuild_preserving_session_layers(&refreshed_config)
.await?;
let plugins_manager = plugins_manager_for_config(&config, /*auth_mode*/ None);
let plugins_manager =
plugins_manager_for_config(&config, auth_manager_from_optional_auth(/*auth*/ None));
let mcp_config = config.to_mcp_config(&plugins_manager).await;
let configured_servers = mcp_config.mcp_server_catalog.configured_servers();
@@ -5305,7 +5308,8 @@ enabled = true
.codex_home(codex_home.path().to_path_buf())
.build()
.await?;
let plugins_manager = plugins_manager_for_config(&config, /*auth_mode*/ None);
let plugins_manager =
plugins_manager_for_config(&config, auth_manager_from_optional_auth(/*auth*/ None));
let mcp_config = config.to_mcp_config(&plugins_manager).await;
let configured_servers = mcp_config.mcp_server_catalog.configured_servers();
@@ -5373,7 +5377,8 @@ url = "https://sample.example/mcp"
)
.build()
.await?;
let plugins_manager = plugins_manager_for_config(&config, /*auth_mode*/ None);
let plugins_manager =
plugins_manager_for_config(&config, auth_manager_from_optional_auth(/*auth*/ None));
let mcp_config = config.to_mcp_config(&plugins_manager).await;
let configured_servers = mcp_config.mcp_server_catalog.configured_servers();
@@ -5475,7 +5480,8 @@ enabled = true
)
.build()
.await?;
let plugins_manager = plugins_manager_for_config(&config, /*auth_mode*/ None);
let plugins_manager =
plugins_manager_for_config(&config, auth_manager_from_optional_auth(/*auth*/ None));
let mcp_config = config.to_mcp_config(&plugins_manager).await;
let configured_servers = mcp_config.mcp_server_catalog.configured_servers();
@@ -6539,7 +6545,8 @@ async fn to_mcp_config_preserves_apps_feature_from_config() -> std::io::Result<(
codex_home.abs(),
)
.await?;
let plugins_manager = plugins_manager_for_config(&config, /*auth_mode*/ None);
let plugins_manager =
plugins_manager_for_config(&config, auth_manager_from_optional_auth(/*auth*/ None));
config.apps_mcp_product_sku = Some("tpp".to_string());
let mcp_config = config.to_mcp_config(&plugins_manager).await;
@@ -6566,7 +6573,8 @@ async fn to_mcp_config_flows_mcp_tool_prefix_from_feature() -> std::io::Result<(
codex_home.abs(),
)
.await?;
let plugins_manager = plugins_manager_for_config(&config, /*auth_mode*/ None);
let plugins_manager =
plugins_manager_for_config(&config, auth_manager_from_optional_auth(/*auth*/ None));
let mcp_config = config.to_mcp_config(&plugins_manager).await;
assert!(mcp_config.prefix_mcp_tool_names);
@@ -6602,7 +6610,8 @@ async fn to_mcp_config_flows_mcp_2026_feature_from_config() -> std::io::Result<(
codex_home.abs(),
)
.await?;
let plugins_manager = plugins_manager_for_config(&config, /*auth_mode*/ None);
let plugins_manager =
plugins_manager_for_config(&config, auth_manager_from_optional_auth(/*auth*/ None));
let mcp_config = config.to_mcp_config(&plugins_manager).await;
assert_eq!(mcp_config.protocol_mode, codex_mcp::McpProtocolMode::Legacy);
@@ -6626,7 +6635,8 @@ async fn to_mcp_config_preserves_auth_elicitation_feature_from_config() -> std::
codex_home.abs(),
)
.await?;
let plugins_manager = plugins_manager_for_config(&config, /*auth_mode*/ None);
let plugins_manager =
plugins_manager_for_config(&config, auth_manager_from_optional_auth(/*auth*/ None));
let mcp_config = config.to_mcp_config(&plugins_manager).await;
assert_eq!(
+1 -2
View File
@@ -191,10 +191,9 @@ pub async fn list_accessible_connectors_from_mcp_tools_with_environment_manager(
) -> anyhow::Result<AccessibleConnectorsStatus> {
let auth_manager =
AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false).await?;
let auth = auth_manager.auth().await;
let plugins_manager = Arc::new(plugins_manager_for_config(
config,
auth.as_ref().map(CodexAuth::api_auth_mode),
Arc::clone(&auth_manager),
));
let mcp_manager = Arc::new(McpManager::new(plugins_manager));
list_accessible_connectors_from_mcp_tools_with_mcp_manager(
+5 -2
View File
@@ -8,6 +8,7 @@ use codex_connectors::merge::plugin_connector_to_app_info;
use codex_connectors::metadata::connector_install_url;
use codex_connectors::metadata::sanitize_name;
use codex_features::Feature;
use codex_login::AuthManager;
use codex_login::CodexAuth;
use codex_mcp::CODEX_APPS_MCP_SERVER_NAME;
use codex_mcp::ToolInfo;
@@ -537,7 +538,8 @@ discoverables = [
.await
.expect("config should load");
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
let plugins_manager = plugins_manager_for_config(&config, Some(auth.api_auth_mode()));
let plugins_manager =
plugins_manager_for_config(&config, AuthManager::from_auth_for_testing(auth.clone()));
let discoverable_tools = list_tool_suggest_discoverable_tools_with_auth(
&config,
@@ -575,7 +577,8 @@ apps = true
.expect("config should load");
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
let loaded_plugin_app_connector_ids = vec!["asdk_app_databricks_workspace".to_string()];
let plugins_manager = plugins_manager_for_config(&config, Some(auth.api_auth_mode()));
let plugins_manager =
plugins_manager_for_config(&config, AuthManager::from_auth_for_testing(auth.clone()));
let discoverable_tools = list_tool_suggest_discoverable_tools_with_auth(
&config,
@@ -3,6 +3,7 @@ use crate::plugins::test_support::load_plugins_config;
use crate::plugins::test_support::write_file;
use crate::plugins::test_support::write_openai_api_curated_marketplace;
use codex_core_plugins::startup_sync::curated_plugins_repo_path;
use codex_login::test_support::auth_manager_from_optional_auth;
use codex_tools::DiscoverablePluginInfo;
use pretty_assertions::assert_eq;
use tempfile::tempdir;
@@ -11,7 +12,8 @@ async fn list_discoverable_plugins(
config: &crate::config::Config,
loaded_plugin_app_connector_ids: &[String],
) -> anyhow::Result<Vec<DiscoverablePluginInfo>> {
let plugins_manager = plugins_manager_for_config(config, /*auth_mode*/ None);
let plugins_manager =
plugins_manager_for_config(config, auth_manager_from_optional_auth(/*auth*/ None));
super::list_tool_suggest_discoverable_plugins(
config,
&plugins_manager,
+6 -3
View File
@@ -11,7 +11,7 @@ pub(crate) mod test_support;
use crate::config::Config;
use codex_core_plugins::PluginsManager;
use codex_protocol::auth::AuthMode;
use codex_login::AuthManager;
use codex_skills_extension::HostSkillsService;
use std::sync::Arc;
@@ -29,14 +29,17 @@ pub(crate) use mentions::collect_tool_mentions_from_messages;
/// Constructs a standalone plugin manager with extension-owned plugin skill loading.
///
/// Callers that already own a host skills service should inject that existing service instead.
pub fn plugins_manager_for_config(config: &Config, auth_mode: Option<AuthMode>) -> PluginsManager {
pub fn plugins_manager_for_config(
config: &Config,
auth_manager: Arc<AuthManager>,
) -> PluginsManager {
let skill_root_loader = Arc::new(HostSkillsService::new(
config.codex_home.clone(),
/*bundled_skills_enabled*/ false,
));
PluginsManager::new(
config.codex_home.to_path_buf(),
auth_mode,
auth_manager,
skill_root_loader,
)
}
@@ -1,7 +1,8 @@
use codex_core_plugins::PluginsManager;
use codex_core_plugins::store::PluginStore;
use codex_login::AuthManager;
use codex_login::CodexAuth;
use codex_plugin::PluginId;
use codex_protocol::auth::AuthMode;
use codex_protocol::protocol::Product;
use codex_skills_extension::HostSkillsLoadInput;
use codex_skills_extension::HostSkillsService;
@@ -55,7 +56,7 @@ enabled = false
let plugins_manager = PluginsManager::new_with_options(
codex_home.path().to_path_buf(),
Some(Product::Codex),
Some(AuthMode::Chatgpt),
AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing()),
skills_service.clone(),
);
let plugin_outcome = plugins_manager.plugins_for_config(&plugins_input).await;
+3 -16
View File
@@ -160,14 +160,10 @@ impl Session {
};
loop {
let auth = self.services.auth_manager.auth_cached();
if self
if !self
.services
.plugins_manager
.set_auth_mode(auth.as_ref().map(CodexAuth::api_auth_mode))
|| !self
.services
.mcp_runtime
.current_auth_matches(auth.as_ref())
.mcp_runtime
.current_auth_matches(auth.as_ref())
{
self.mark_mcp_runtime_dirty();
}
@@ -180,9 +176,6 @@ impl Session {
published: false,
};
let auth = self.services.auth_manager.auth().await;
self.services
.plugins_manager
.set_auth_mode(auth.as_ref().map(CodexAuth::api_auth_mode));
let desired = self.latest_mcp_desired_state(auth).await;
let selected_capability_roots = self
.resolve_selected_capability_roots_for_step(&desired.environments)
@@ -237,9 +230,6 @@ impl Session {
.await
.map_err(|_| anyhow::anyhow!("MCP runtime refresh semaphore closed"))?;
let auth = self.services.auth_manager.auth().await;
self.services
.plugins_manager
.set_auth_mode(auth.as_ref().map(CodexAuth::api_auth_mode));
let desired = self.latest_mcp_desired_state(auth).await;
let selected_capability_roots = self
.resolve_selected_capability_roots_for_step(&desired.environments)
@@ -623,9 +613,6 @@ impl Session {
return;
};
let auth = self.services.auth_manager.auth().await;
self.services
.plugins_manager
.set_auth_mode(auth.as_ref().map(CodexAuth::api_auth_mode));
{
let mut state = self.state.lock().await;
let mut config = (*state.session_configuration.original_config_do_not_use).clone();
-3
View File
@@ -1408,9 +1408,6 @@ impl Session {
let mcp_projection = if startup_auth_changed
|| mcp_auth_changes.has_changed().unwrap_or(false)
{
sess.services
.plugins_manager
.set_auth_mode(latest_auth.as_ref().map(CodexAuth::api_auth_mode));
sess.services
.mcp_manager
.runtime_config_for_step(
+32 -42
View File
@@ -5672,7 +5672,7 @@ async fn session_new_fails_when_zsh_fork_enabled_without_packaged_zsh() {
let (agent_status_tx, _agent_status_rx) = watch::channel(AgentStatus::PendingInit);
let plugins_manager = Arc::new(plugins_manager_for_config(
&config,
auth_manager.get_api_auth_mode(),
Arc::clone(&auth_manager),
));
let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager)));
let skills_service = Arc::new(HostSkillsService::new(
@@ -5843,7 +5843,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
);
let plugins_manager = Arc::new(plugins_manager_for_config(
&config,
auth_manager.get_api_auth_mode(),
Arc::clone(&auth_manager),
));
let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager)));
let skills_service = Arc::new(HostSkillsService::new(
@@ -6103,7 +6103,7 @@ async fn make_session_with_config_and_rx(
let (agent_status_tx, _agent_status_rx) = watch::channel(AgentStatus::PendingInit);
let plugins_manager = Arc::new(plugins_manager_for_config(
&config,
auth_manager.get_api_auth_mode(),
Arc::clone(&auth_manager),
));
let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager)));
let skills_service = Arc::new(HostSkillsService::new(
@@ -6224,7 +6224,7 @@ async fn make_session_with_history_source_and_agent_control_and_rx(
let (agent_status_tx, _agent_status_rx) = watch::channel(AgentStatus::PendingInit);
let plugins_manager = Arc::new(plugins_manager_for_config(
&config,
auth_manager.get_api_auth_mode(),
Arc::clone(&auth_manager),
));
let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager)));
let skills_service = Arc::new(HostSkillsService::new(
@@ -7962,7 +7962,7 @@ where
let state_db = None;
let config = Arc::new(config);
let thread_id = ThreadId::default();
let auth_manager = AuthManager::from_auth_for_testing(auth);
let auth_manager = AuthManager::from_auth_for_testing_with_home(auth, codex_home.to_path_buf());
let models_manager = models_manager_with_provider(
config.codex_home.to_path_buf(),
auth_manager.clone(),
@@ -8048,7 +8048,7 @@ where
);
let plugins_manager = Arc::new(plugins_manager_for_config(
&config,
auth_manager.get_api_auth_mode(),
Arc::clone(&auth_manager),
));
let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager)));
let skills_service = Arc::new(HostSkillsService::new(
@@ -8569,26 +8569,38 @@ async fn mcp_policy_changes_schedule_runtime_refresh() {
}
#[tokio::test]
async fn mcp_refresh_updates_plugin_auth_mode_before_checking_pending_state() {
let codex_home = tempfile::tempdir().expect("create auth test directory");
let (mut session, _turn_context) = make_session_and_context().await;
session.services.auth_manager = AuthManager::from_auth_for_testing_with_home(
CodexAuth::from_api_key("old-api-key"),
codex_home.path().to_path_buf(),
async fn mcp_refresh_detects_shared_auth_manager_changes() {
let (session, _turn_context) = make_session_and_context().await;
let session = Arc::new(session);
assert_eq!(
session.services.plugins_manager.auth_mode(),
Some(codex_protocol::auth::AuthMode::ApiKey)
);
session.refresh_mcp_if_dirty().await;
assert!(
session
.services
.mcp_runtime
.current_auth_matches(session.services.auth_manager.auth_cached().as_ref())
);
session
.services
.plugins_manager
.set_auth_mode(/*auth_mode*/ None);
let session = Arc::new(session);
let auth_mode = session.services.auth_manager.get_api_auth_mode();
assert_ne!(session.services.plugins_manager.auth_mode(), auth_mode);
session.mcp_refresh.claim();
.auth_manager
.logout()
.await
.expect("logout should succeed");
assert_eq!(session.services.plugins_manager.auth_mode(), None);
assert!(
!session
.services
.mcp_runtime
.current_auth_matches(session.services.auth_manager.auth_cached().as_ref())
);
session.refresh_mcp_if_dirty().await;
assert_eq!(session.services.plugins_manager.auth_mode(), auth_mode);
assert!(
session
.services
@@ -8597,28 +8609,6 @@ async fn mcp_refresh_updates_plugin_auth_mode_before_checking_pending_state() {
.await
.is_some()
);
codex_login::login_with_api_key(
codex_home.path(),
"new-api-key",
codex_login::AuthCredentialsStoreMode::File,
codex_login::AuthKeyringBackendKind::default(),
)
.expect("store replacement API key");
session.services.auth_manager.reload().await;
assert_eq!(
session
.services
.auth_manager
.auth_cached()
.and_then(|auth| auth.get_token().ok()),
Some("new-api-key".to_string())
);
assert_eq!(session.services.plugins_manager.auth_mode(), auth_mode);
session.mcp_refresh.claim();
session.refresh_mcp_if_dirty().await;
assert!(
session
.services
@@ -786,7 +786,7 @@ async fn guardian_subagent_does_not_inherit_parent_exec_policy_rules() {
);
let plugins_manager = Arc::new(plugins_manager_for_config(
&config,
auth_manager.get_api_auth_mode(),
Arc::clone(&auth_manager),
));
let skills_service = Arc::new(HostSkillsService::new(
config.codex_home.clone(),
+2 -2
View File
@@ -443,7 +443,7 @@ impl ThreadManager {
let plugins_manager = Arc::new(PluginsManager::new_with_options(
codex_home.to_path_buf(),
restriction_product,
auth_manager.get_api_auth_mode(),
Arc::clone(&auth_manager),
skills_service.clone(),
));
let mcp_manager = Arc::new(McpManager::new_with_extensions(
@@ -590,7 +590,7 @@ impl ThreadManager {
let plugins_manager = Arc::new(PluginsManager::new_with_options(
codex_home.clone(),
restriction_product,
auth_manager.get_api_auth_mode(),
Arc::clone(&auth_manager),
skills_service.clone(),
));
let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager)));
@@ -12,6 +12,7 @@ use codex_config::types::ToolSuggestDiscoverable;
use codex_config::types::ToolSuggestDiscoverableType;
use codex_core_plugins::PluginInstallRequest;
use codex_core_plugins::startup_sync::curated_plugins_repo_path;
use codex_login::test_support::auth_manager_from_optional_auth;
use codex_rmcp_client::ElicitationResponse;
use codex_tools::DiscoverablePluginInfo;
use codex_utils_absolute_path::AbsolutePathBuf;
@@ -40,7 +41,8 @@ async fn verified_plugin_install_completed_requires_installed_plugin() {
write_plugins_feature_config(codex_home.path());
let config = load_plugins_config(codex_home.path()).await;
let plugins_manager = plugins_manager_for_config(&config, /*auth_mode*/ None);
let plugins_manager =
plugins_manager_for_config(&config, auth_manager_from_optional_auth(/*auth*/ None));
assert!(!verified_plugin_install_completed(
"sample@openai-api-curated",
+12 -3
View File
@@ -674,7 +674,10 @@ impl TestCodexBuilder {
config.codex_home.clone(),
))
});
let auth_manager = codex_core::test_support::auth_manager_from_auth(auth.clone());
let auth_manager = codex_core::test_support::auth_manager_from_auth_with_home(
auth.clone(),
config.codex_home.to_path_buf(),
);
let models_manager = self
.models_manager
.clone()
@@ -721,7 +724,10 @@ impl TestCodexBuilder {
let new_conversation = match (resume_from, user_shell_override) {
(Some(path), Some(user_shell_override)) => {
let auth_manager = codex_core::test_support::auth_manager_from_auth(auth);
let auth_manager = codex_core::test_support::auth_manager_from_auth_with_home(
auth,
config.codex_home.to_path_buf(),
);
Box::pin(
codex_core::test_support::resume_thread_from_rollout_with_user_shell_override(
thread_manager.as_ref(),
@@ -735,7 +741,10 @@ impl TestCodexBuilder {
.await?
}
(Some(path), None) => {
let auth_manager = codex_core::test_support::auth_manager_from_auth(auth);
let auth_manager = codex_core::test_support::auth_manager_from_auth_with_home(
auth,
config.codex_home.to_path_buf(),
);
Box::pin(thread_manager.resume_thread_from_rollout(
config.clone(),
path,
@@ -88,7 +88,7 @@ async fn hosted_plugin_runtime_ps_mcp_tool_calls_use_current_auth_manager_token(
.build()
.await?;
config.permissions.approval_policy = Constrained::allow_any(AskForApproval::Never);
let plugins_manager = plugins_manager_for_config(&config, auth_manager.get_api_auth_mode());
let plugins_manager = plugins_manager_for_config(&config, Arc::clone(&auth_manager));
let mcp_config = Arc::new(config.to_mcp_config(&plugins_manager).await);
let runtime = McpRuntime::new(McpRuntimeInput {
startup_policy: McpStartupPolicy::Eager,
+66 -89
View File
@@ -25,6 +25,7 @@ use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::ThreadSettingsOverrides;
use codex_protocol::user_input::UserInput;
use codex_skills_extension::HostSkillsLoadInput;
use codex_skills_extension::SkillsExtensionConfig;
use codex_skills_extension::install;
use core_test_support::apps_test_server::AppsTestServer;
@@ -794,7 +795,7 @@ async fn agent_plugin_root_mcp_stdio_tool_round_trip_expands_reserved_paths() ->
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn agent_turns_route_curated_plugin_skills_after_auth_switch() -> Result<()> {
async fn curated_plugin_skills_follow_auth_switch() -> Result<()> {
const CHATGPT_CURATED_PLUGIN_SKILL: &str = "chatgpt-plugin:chatgpt-skill";
const API_CURATED_PLUGIN_SKILL: &str = "api-plugin:api-skill";
const CURATED_PLUGIN_SKILLS: &[&str] =
@@ -813,7 +814,6 @@ async fn agent_turns_route_curated_plugin_skills_after_auth_switch() -> Result<(
name: &'static str,
target_auth: TargetAuth,
target_model_provider_id: &'static str,
target_prompt: &'static str,
expected_target_loaded_plugin_skills: &'static [&'static str],
expected_target_skill_description: &'static str,
}
@@ -823,7 +823,6 @@ async fn agent_turns_route_curated_plugin_skills_after_auth_switch() -> Result<(
name: "ChatGPT",
target_auth: TargetAuth::Chatgpt,
target_model_provider_id: OPENAI_PROVIDER_ID,
target_prompt: "chatgpt target turn",
expected_target_loaded_plugin_skills: &[CHATGPT_CURATED_PLUGIN_SKILL],
expected_target_skill_description: "chatgpt description",
},
@@ -831,7 +830,6 @@ async fn agent_turns_route_curated_plugin_skills_after_auth_switch() -> Result<(
name: "ChatGPT with a custom provider",
target_auth: TargetAuth::Chatgpt,
target_model_provider_id: "ollama",
target_prompt: "chatgpt custom provider target turn",
expected_target_loaded_plugin_skills: &[CHATGPT_CURATED_PLUGIN_SKILL],
expected_target_skill_description: "chatgpt description",
},
@@ -839,7 +837,6 @@ async fn agent_turns_route_curated_plugin_skills_after_auth_switch() -> Result<(
name: "API key",
target_auth: TargetAuth::ApiKey,
target_model_provider_id: OPENAI_PROVIDER_ID,
target_prompt: "api key target turn",
expected_target_loaded_plugin_skills: &[API_CURATED_PLUGIN_SKILL],
expected_target_skill_description: "api description before",
},
@@ -847,7 +844,6 @@ async fn agent_turns_route_curated_plugin_skills_after_auth_switch() -> Result<(
name: "Bedrock API key",
target_auth: TargetAuth::BedrockApiKey,
target_model_provider_id: AMAZON_BEDROCK_PROVIDER_ID,
target_prompt: "bedrock key target turn",
expected_target_loaded_plugin_skills: &[API_CURATED_PLUGIN_SKILL],
expected_target_skill_description: "api description before",
},
@@ -855,7 +851,6 @@ async fn agent_turns_route_curated_plugin_skills_after_auth_switch() -> Result<(
name: "ambient Bedrock",
target_auth: TargetAuth::NoCodexAuth,
target_model_provider_id: AMAZON_BEDROCK_PROVIDER_ID,
target_prompt: "ambient bedrock target turn",
expected_target_loaded_plugin_skills: &[API_CURATED_PLUGIN_SKILL],
expected_target_skill_description: "api description before",
},
@@ -863,7 +858,6 @@ async fn agent_turns_route_curated_plugin_skills_after_auth_switch() -> Result<(
name: "unauthenticated OpenAI",
target_auth: TargetAuth::NoCodexAuth,
target_model_provider_id: OPENAI_PROVIDER_ID,
target_prompt: "unauthenticated openai target turn",
expected_target_loaded_plugin_skills: &[API_CURATED_PLUGIN_SKILL],
expected_target_skill_description: "api description before",
},
@@ -871,47 +865,44 @@ async fn agent_turns_route_curated_plugin_skills_after_auth_switch() -> Result<(
name: "unauthenticated custom provider",
target_auth: TargetAuth::NoCodexAuth,
target_model_provider_id: "ollama",
target_prompt: "unauthenticated custom provider target turn",
expected_target_loaded_plugin_skills: &[API_CURATED_PLUGIN_SKILL],
expected_target_skill_description: "api description before",
},
];
async fn skills_for_agent_turn(
test_codex: &TestCodex,
response: &ResponseMock,
model_provider_id: &str,
prompt: &str,
expected_request_count: usize,
) -> Result<String> {
let mut config = test_codex.config.clone();
config.model_provider_id = model_provider_id.to_string();
let thread = test_codex
async fn loaded_plugin_skills_for_config(test_codex: &TestCodex, config: &Config) -> String {
let plugins_input = config.plugins_config_input();
let plugins_manager = test_codex.thread_manager.plugins_manager();
let plugin_outcome = plugins_manager.plugins_for_config(&plugins_input).await;
let skills_input = HostSkillsLoadInput::new(
config.cwd.clone(),
plugin_outcome.effective_plugin_skill_roots(),
config.config_layer_stack.clone(),
)
.with_plugin_skill_snapshots(
plugins_manager.plugin_skill_snapshots_for_config(&plugins_input),
);
let skills_snapshot = test_codex
.thread_manager
.start_thread(codex_core::StartThreadOptions::new(config))
.await?
.thread;
thread
.start_or_steer_turn(TurnInputRequest::user_input(vec![
codex_protocol::user_input::UserInput::Text {
text: prompt.to_string(),
text_elements: Vec::new(),
},
]))
.await?;
wait_for_event(&thread, |event| matches!(event, EventMsg::TurnComplete(_))).await;
let requests = response.requests();
assert_eq!(requests.len(), expected_request_count);
Ok(requests
.last()
.expect("agent turn should send a request")
.message_input_text_groups("developer")
.into_iter()
.rev()
.find(|texts| texts.iter().any(|text| text.contains("## Skills")))
.expect("agent turn should include a skills developer message")
.join("\n"))
.skills_service()
.snapshot_for_config(&skills_input, /*fs*/ None)
.await;
skills_snapshot
.outcome()
.skills
.iter()
.filter_map(|skill| {
let plugin_id = skill.plugin_id.as_deref()?;
let plugin_name = plugin_id
.split_once('@')
.map_or(plugin_id, |(plugin_name, _)| plugin_name);
Some(format!(
"{plugin_name}:{}\n{}",
skill.name, skill.description
))
})
.collect::<Vec<_>>()
.join("\n")
}
skip_if_no_network!(Ok(()));
@@ -931,20 +922,6 @@ async fn agent_turns_route_curated_plugin_skills_after_auth_switch() -> Result<(
for fixture in FIXTURES {
let server = start_mock_server().await;
let response = mount_sse_sequence(
&server,
vec![
sse(vec![
ev_response_created("resp-initial"),
ev_completed("resp-initial"),
]),
sse(vec![
ev_response_created("resp-target"),
ev_completed("resp-target"),
]),
],
)
.await;
let codex_home = Arc::new(TempDir::new()?);
std::fs::write(
@@ -998,20 +975,10 @@ enabled = true
.with_extensions(skills_extensions())
.with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing());
let test_codex = builder.build_with_auto_env(&server).await?;
let plugins_manager = test_codex.thread_manager.plugins_manager();
let skills_service = test_codex.thread_manager.skills_service();
let initial_skills = skills_for_agent_turn(
&test_codex,
&response,
OPENAI_PROVIDER_ID,
"initial chatgpt turn",
/*expected_request_count*/ 1,
)
.await?;
let initial_skills = loaded_plugin_skills_for_config(&test_codex, &test_codex.config).await;
assert_loaded_plugin_skills(
fixture.name,
"initial ChatGPT turn",
"initial ChatGPT config",
&initial_skills,
&[CHATGPT_CURATED_PLUGIN_SKILL],
);
@@ -1024,35 +991,45 @@ enabled = true
"---\ndescription: api description after\n---\n\n# body\n",
)?;
match fixture.target_auth {
TargetAuth::Chatgpt => {}
let expected_auth_mode = match fixture.target_auth {
TargetAuth::Chatgpt => Some(AuthMode::Chatgpt),
TargetAuth::ApiKey => {
plugins_manager.set_auth_mode(Some(AuthMode::ApiKey));
codex_login::login_with_api_key(
codex_home.path(),
"test-api-key",
codex_login::AuthCredentialsStoreMode::File,
codex_login::AuthKeyringBackendKind::default(),
)?;
test_codex.thread_manager.auth_manager().reload().await;
Some(AuthMode::ApiKey)
}
TargetAuth::BedrockApiKey => {
plugins_manager.set_auth_mode(Some(AuthMode::BedrockApiKey));
codex_login::login_with_bedrock_api_key(
codex_home.path(),
"test-bedrock-api-key",
"us-east-1",
codex_login::AuthCredentialsStoreMode::File,
codex_login::AuthKeyringBackendKind::default(),
)?;
test_codex.thread_manager.auth_manager().reload().await;
Some(AuthMode::BedrockApiKey)
}
TargetAuth::NoCodexAuth => {
test_codex.thread_manager.auth_manager().logout().await?;
assert_eq!(
test_codex.thread_manager.auth_manager().get_api_auth_mode(),
None
);
plugins_manager.set_auth_mode(/*auth_mode*/ None);
None
}
}
skills_service.clear_cache();
let target_skills = skills_for_agent_turn(
&test_codex,
&response,
fixture.target_model_provider_id,
fixture.target_prompt,
/*expected_request_count*/ 2,
)
.await?;
};
assert_eq!(
test_codex.thread_manager.auth_manager().get_api_auth_mode(),
expected_auth_mode
);
test_codex.thread_manager.skills_service().clear_cache();
let mut target_config = test_codex.config.clone();
target_config.model_provider_id = fixture.target_model_provider_id.to_string();
let target_skills = loaded_plugin_skills_for_config(&test_codex, &target_config).await;
assert_loaded_plugin_skills(
fixture.name,
"target turn",
"target config",
&target_skills,
fixture.expected_target_loaded_plugin_skills,
);
+15 -12
View File
@@ -9,7 +9,9 @@ use codex_extension_api::ExtensionRegistryBuilder;
use codex_extension_api::McpServerContribution;
use codex_extension_api::McpServerContributionContext;
use codex_extension_api::McpServerContributor;
use codex_login::AuthManager;
use codex_login::CodexAuth;
use codex_login::test_support::auth_manager_from_optional_auth;
use codex_mcp::CODEX_APPS_MCP_SERVER_NAME;
use pretty_assertions::assert_eq;
@@ -28,7 +30,7 @@ async fn contributes_hosted_plugin_runtime_without_an_executor() -> TestResult {
.build()
.await?;
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
let manager = installed_manager(&config, Some(auth.api_auth_mode()));
let manager = installed_manager(&config, Some(auth.clone()));
let servers = manager.effective_servers(&config, Some(&auth)).await;
let server = servers
@@ -60,7 +62,7 @@ async fn runtime_overlay_preserves_disabled_server() -> TestResult {
.build()
.await?;
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
let manager = installed_manager(&config, Some(auth.api_auth_mode()));
let manager = installed_manager(&config, Some(auth.clone()));
let servers = manager.effective_servers(&config, Some(&auth)).await;
let server = servers
@@ -89,7 +91,7 @@ async fn default_fallback_overwrites_reserved_config_without_an_extension() -> T
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
let manager = McpManager::new(Arc::new(plugins_manager_for_config(
&config,
Some(auth.api_auth_mode()),
AuthManager::from_auth_for_testing(auth.clone()),
)));
let servers = manager.effective_servers(&config, Some(&auth)).await;
@@ -121,7 +123,7 @@ async fn later_extension_can_remove_same_name_registration() -> TestResult {
let manager = McpManager::new_with_extensions(
Arc::new(plugins_manager_for_config(
&config,
Some(auth.api_auth_mode()),
AuthManager::from_auth_for_testing(auth.clone()),
)),
Arc::new(builder.build()),
codex_core::CodexAppsToolsCache::default(),
@@ -143,7 +145,7 @@ async fn hosted_apps_mcp_requires_chatgpt_auth() -> TestResult {
.build()
.await?;
let auth = CodexAuth::from_api_key("test");
let manager = installed_manager(&config, Some(auth.api_auth_mode()));
let manager = installed_manager(&config, Some(auth.clone()));
let servers = manager.effective_servers(&config, Some(&auth)).await;
assert!(!servers.contains_key(CODEX_APPS_MCP_SERVER_NAME));
@@ -167,9 +169,10 @@ async fn disabled_apps_remove_reserved_server_config_for_all_hosts() -> TestResu
.build()
.await?;
let managers = [
installed_manager(&config, /*auth_mode*/ None),
installed_manager(&config, /*auth*/ None),
McpManager::new(Arc::new(plugins_manager_for_config(
&config, /*auth_mode*/ None,
&config,
auth_manager_from_optional_auth(/*auth*/ None),
))),
];
for manager in managers {
@@ -179,14 +182,14 @@ async fn disabled_apps_remove_reserved_server_config_for_all_hosts() -> TestResu
Ok(())
}
fn installed_manager(
config: &Config,
auth_mode: Option<codex_protocol::auth::AuthMode>,
) -> McpManager {
fn installed_manager(config: &Config, auth: Option<CodexAuth>) -> McpManager {
let mut builder = ExtensionRegistryBuilder::new();
codex_mcp_extension::install(&mut builder);
McpManager::new_with_extensions(
Arc::new(plugins_manager_for_config(config, auth_mode)),
Arc::new(plugins_manager_for_config(
config,
auth_manager_from_optional_auth(auth),
)),
Arc::new(builder.build()),
codex_core::CodexAppsToolsCache::default(),
)
@@ -19,6 +19,7 @@ codex-config = { workspace = true }
codex-core = { workspace = true }
codex-core-plugins = { workspace = true }
codex-hooks = { workspace = true }
codex-login = { workspace = true }
codex-memories-write = { workspace = true }
codex-otel = { workspace = true }
codex-plugin = { workspace = true }
@@ -30,6 +30,7 @@ use std::collections::HashMap;
use std::collections::HashSet;
use std::fs;
use std::io;
use std::sync::Arc;
use toml::Value as TomlValue;
const EXTERNAL_AGENT_CONFIG_DETECT_METRIC: &str = "codex.external_agent_config.detect";
@@ -351,7 +352,7 @@ impl ExternalAgentConfigService {
.unwrap_or_default();
let configured_marketplace_plugins = configured_marketplace_plugins(
&config,
&plugins_manager_for_config(&config, self.auth_mode),
&plugins_manager_for_config(&config, Arc::clone(&self.auth_manager)),
)?;
let source_root = repo_root.unwrap_or(self.external_agent_home.as_path());
if let Some(detected) =
@@ -11,6 +11,7 @@ use codex_core_plugins::marketplace_add::is_local_marketplace_source;
use std::collections::BTreeMap;
use std::io;
use std::path::Path;
use std::sync::Arc;
use crate::migration_source::MarketplaceImportSource;
use crate::model::MigrationDetails;
@@ -101,7 +102,7 @@ impl ExternalAgentConfigService {
.map_err(|err| io::Error::other(format!("failed to load config: {err}")))?;
let requirements = config.config_layer_stack.requirements().clone();
let mut outcome = PluginImportOutcome::default();
let plugins_manager = plugins_manager_for_config(&config, self.auth_mode)
let plugins_manager = plugins_manager_for_config(&config, Arc::clone(&self.auth_manager))
.with_plugin_install_source(PluginInstallSource::ExternalAgentMigration);
if let Some(analytics_events_client) = self.analytics_events_client.clone() {
plugins_manager.set_analytics_events_client(analytics_events_client);
@@ -43,7 +43,7 @@ use codex_analytics::AnalyticsEventsClient;
use codex_core::config::Config;
use codex_core_plugins::PluginsManager;
use codex_core_plugins::marketplace::MarketplacePluginInstallPolicy;
use codex_protocol::auth::AuthMode;
use codex_login::AuthManager;
use codex_protocol::protocol::Product;
use codex_rollout::StateDbHandle;
use serde_json::Value as JsonValue;
@@ -54,6 +54,7 @@ use std::fs;
use std::io;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
use toml::Value as TomlValue;
#[cfg(test)]
@@ -69,7 +70,7 @@ pub struct ExternalAgentConfigService {
pub(super) connector_metadata_roots: Vec<PathBuf>,
pub(crate) external_agent_home: PathBuf,
pub(crate) analytics_events_client: Option<AnalyticsEventsClient>,
pub(crate) auth_mode: Option<AuthMode>,
pub(crate) auth_manager: Arc<AuthManager>,
pub(crate) source: ExternalAgentSource,
pub(crate) session_import_limits: ExternalAgentSessionImportLimits,
state_db: Option<StateDbHandle>,
@@ -78,7 +79,7 @@ pub struct ExternalAgentConfigService {
impl ExternalAgentConfigService {
pub fn new(
codex_home: PathBuf,
auth_mode: Option<AuthMode>,
auth_manager: Arc<AuthManager>,
analytics_events_client: AnalyticsEventsClient,
state_db: Option<StateDbHandle>,
) -> Self {
@@ -90,7 +91,7 @@ impl ExternalAgentConfigService {
connector_metadata_roots,
external_agent_home,
analytics_events_client: Some(analytics_events_client),
auth_mode,
auth_manager,
source,
session_import_limits: ExternalAgentSessionImportLimits::default(),
state_db,
@@ -106,7 +107,7 @@ impl ExternalAgentConfigService {
connector_metadata_roots,
external_agent_home,
analytics_events_client: self.analytics_events_client.clone(),
auth_mode: self.auth_mode,
auth_manager: Arc::clone(&self.auth_manager),
source,
session_import_limits: self.session_import_limits,
state_db: self.state_db.clone(),
@@ -119,12 +120,6 @@ impl ExternalAgentConfigService {
service
}
pub fn with_auth_mode(&self, auth_mode: Option<AuthMode>) -> Self {
let mut service = self.clone();
service.auth_mode = auth_mode;
service
}
pub fn session_metadata_mode(&self) -> SessionMetadataMode {
self.source.session_metadata_mode()
}
@@ -168,7 +163,9 @@ impl ExternalAgentConfigService {
connector_metadata_roots,
external_agent_home,
analytics_events_client: None,
auth_mode: None,
auth_manager: codex_login::test_support::auth_manager_from_optional_auth(
/*auth*/ None,
),
source,
session_import_limits: ExternalAgentSessionImportLimits::default(),
state_db: None,
@@ -1,7 +1,8 @@
use super::super::*;
use crate::migration_source::MarketplaceImportSource;
use crate::source_cla;
use codex_protocol::auth::AuthMode;
use codex_login::AuthManager;
use codex_login::CodexAuth;
use pretty_assertions::assert_eq;
#[tokio::test]
@@ -45,8 +46,9 @@ async fn authenticated_plugin_migration_uses_chatgpt_curated_marketplace() {
)
.expect("write curated plugin manifest");
let service = service_for_paths(external_agent_home.clone(), codex_home)
.with_auth_mode(Some(AuthMode::Chatgpt));
let mut service = service_for_paths(external_agent_home.clone(), codex_home);
service.auth_manager =
AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing());
let items = service
.detect(ExternalAgentConfigDetectOptions {
include_home: true,
+6 -1
View File
@@ -2145,8 +2145,13 @@ impl AuthManager {
/// Create an AuthManager with a specific CodexAuth, for testing only.
pub fn from_auth_for_testing(auth: CodexAuth) -> Arc<Self> {
Self::from_optional_auth_for_testing(Some(auth))
}
/// Create an AuthManager with an optional CodexAuth, for testing only.
pub(crate) fn from_optional_auth_for_testing(auth: Option<CodexAuth>) -> Arc<Self> {
let cached = CachedAuth {
auth: Some(auth),
auth,
permanent_refresh_failure: None,
};
let (auth_change_tx, _auth_change_rx) = watch::channel(0);
+35
View File
@@ -3,9 +3,44 @@
//! Production code should receive an [`AuthRouteConfig`](crate::AuthRouteConfig) adapted from the
//! application's resolved HTTP client factory instead of depending on this module.
use crate::AuthManager;
use crate::AuthRouteConfig;
use crate::CodexAuth;
use crate::auth::AgentIdentityAuth;
use crate::auth::AgentIdentityAuthRecord;
use codex_http_client::HttpClientFactory;
use codex_http_client::OutboundProxyPolicy;
use codex_protocol::account::PlanType as AccountPlanType;
use std::sync::Arc;
/// Creates an authentication manager with optional credentials for cross-crate tests.
pub fn auth_manager_from_optional_auth(auth: Option<CodexAuth>) -> Arc<AuthManager> {
AuthManager::from_optional_auth_for_testing(auth)
}
/// Creates an authentication manager with initialized Agent Identity credentials for tests.
pub async fn auth_manager_with_agent_identity() -> std::io::Result<Arc<AuthManager>> {
let key_material =
codex_agent_identity::generate_agent_key_material().map_err(std::io::Error::other)?;
let auth = AgentIdentityAuth::from_record(
AgentIdentityAuthRecord {
agent_runtime_id: "test-agent-runtime-id".to_string(),
agent_private_key: key_material.private_key_pkcs8_base64,
account_id: "test-account-id".to_string(),
chatgpt_user_id: "test-user-id".to_string(),
email: Some("test-user@example.com".to_string()),
plan_type: AccountPlanType::Pro,
chatgpt_account_is_fedramp: false,
task_id: Some("test-task-id".to_string()),
},
"https://unused.invalid",
&transport_default_auth_route_config(),
)
.await?;
Ok(auth_manager_from_optional_auth(Some(
CodexAuth::AgentIdentity(auth),
)))
}
/// Returns auth routing that preserves the transport's built-in proxy behavior.
pub fn transport_default_auth_route_config() -> AuthRouteConfig {