mirror of
https://github.com/openai/codex.git
synced 2026-10-20 00:18:49 +00:00
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:
committed by
copyberry
parent
2013e04354
commit
fc6268ad38
Generated
+1
@@ -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(¶ms.migration_items);
|
||||
let has_migration_items = !params.migration_items.is_empty();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user