mirror of
https://github.com/openai/codex.git
synced 2026-09-11 20:36:49 +00:00
## 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
216 lines
7.5 KiB
Rust
216 lines
7.5 KiB
Rust
use std::sync::Arc;
|
|
|
|
use codex_config::McpServerTransportConfig;
|
|
use codex_core::McpManager;
|
|
use codex_core::config::Config;
|
|
use codex_core::config::ConfigBuilder;
|
|
use codex_core::plugins_manager_for_config;
|
|
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;
|
|
|
|
type TestResult = Result<(), Box<dyn std::error::Error>>;
|
|
|
|
#[tokio::test]
|
|
async fn contributes_hosted_plugin_runtime_without_an_executor() -> TestResult {
|
|
let codex_home = tempfile::tempdir()?;
|
|
let config = ConfigBuilder::default()
|
|
.codex_home(codex_home.path().to_path_buf())
|
|
.fallback_cwd(Some(codex_home.path().to_path_buf()))
|
|
.cli_overrides(vec![
|
|
("features.apps".to_string(), true.into()),
|
|
("chatgpt_base_url".to_string(), "https://chatgpt.com".into()),
|
|
])
|
|
.build()
|
|
.await?;
|
|
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
|
|
let manager = installed_manager(&config, Some(auth.clone()));
|
|
|
|
let servers = manager.effective_servers(&config, Some(&auth)).await;
|
|
let server = servers
|
|
.get(CODEX_APPS_MCP_SERVER_NAME)
|
|
.ok_or("hosted plugin runtime should be contributed as a configured server")?
|
|
.config();
|
|
let McpServerTransportConfig::StreamableHttp { url, .. } = &server.transport else {
|
|
panic!("hosted plugin runtime should use streamable HTTP");
|
|
};
|
|
assert_eq!(url, "https://chatgpt.com/backend-api/ps/mcp");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn runtime_overlay_preserves_disabled_server() -> TestResult {
|
|
let codex_home = tempfile::tempdir()?;
|
|
let config = ConfigBuilder::default()
|
|
.codex_home(codex_home.path().to_path_buf())
|
|
.fallback_cwd(Some(codex_home.path().to_path_buf()))
|
|
.cli_overrides(vec![
|
|
("features.apps".to_string(), true.into()),
|
|
(
|
|
"mcp_servers.codex_apps.url".to_string(),
|
|
"https://example.com/mcp".into(),
|
|
),
|
|
("mcp_servers.codex_apps.enabled".to_string(), false.into()),
|
|
])
|
|
.build()
|
|
.await?;
|
|
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
|
|
let manager = installed_manager(&config, Some(auth.clone()));
|
|
|
|
let servers = manager.effective_servers(&config, Some(&auth)).await;
|
|
let server = servers
|
|
.get(CODEX_APPS_MCP_SERVER_NAME)
|
|
.ok_or("hosted plugin runtime should remain configured")?;
|
|
|
|
assert!(!server.enabled());
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn default_fallback_overwrites_reserved_config_without_an_extension() -> TestResult {
|
|
let codex_home = tempfile::tempdir()?;
|
|
let config = ConfigBuilder::default()
|
|
.codex_home(codex_home.path().to_path_buf())
|
|
.fallback_cwd(Some(codex_home.path().to_path_buf()))
|
|
.cli_overrides(vec![
|
|
("features.apps".to_string(), true.into()),
|
|
(
|
|
"mcp_servers.codex_apps.url".to_string(),
|
|
"https://example.com/mcp".into(),
|
|
),
|
|
])
|
|
.build()
|
|
.await?;
|
|
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
|
|
let manager = McpManager::new(Arc::new(plugins_manager_for_config(
|
|
&config,
|
|
AuthManager::from_auth_for_testing(auth.clone()),
|
|
)));
|
|
|
|
let servers = manager.effective_servers(&config, Some(&auth)).await;
|
|
let server = servers
|
|
.get(CODEX_APPS_MCP_SERVER_NAME)
|
|
.ok_or("default Apps MCP should be present")?
|
|
.config();
|
|
let McpServerTransportConfig::StreamableHttp { url, .. } = &server.transport else {
|
|
panic!("default Apps MCP should use streamable HTTP");
|
|
};
|
|
assert_eq!(url, "https://chatgpt.com/backend-api/ps/mcp");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn later_extension_can_remove_same_name_registration() -> TestResult {
|
|
let codex_home = tempfile::tempdir()?;
|
|
let config = ConfigBuilder::default()
|
|
.codex_home(codex_home.path().to_path_buf())
|
|
.fallback_cwd(Some(codex_home.path().to_path_buf()))
|
|
.cli_overrides(vec![("features.apps".to_string(), true.into())])
|
|
.build()
|
|
.await?;
|
|
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
|
|
let mut builder = ExtensionRegistryBuilder::new();
|
|
codex_mcp_extension::install(&mut builder);
|
|
builder.mcp_server_contributor(Arc::new(RemoveCodexApps));
|
|
let manager = McpManager::new_with_extensions(
|
|
Arc::new(plugins_manager_for_config(
|
|
&config,
|
|
AuthManager::from_auth_for_testing(auth.clone()),
|
|
)),
|
|
Arc::new(builder.build()),
|
|
codex_core::CodexAppsToolsCache::default(),
|
|
);
|
|
|
|
let servers = manager.effective_servers(&config, Some(&auth)).await;
|
|
|
|
assert!(!servers.contains_key(CODEX_APPS_MCP_SERVER_NAME));
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn hosted_apps_mcp_requires_chatgpt_auth() -> TestResult {
|
|
let codex_home = tempfile::tempdir()?;
|
|
let config = ConfigBuilder::default()
|
|
.codex_home(codex_home.path().to_path_buf())
|
|
.fallback_cwd(Some(codex_home.path().to_path_buf()))
|
|
.cli_overrides(vec![("features.apps".to_string(), true.into())])
|
|
.build()
|
|
.await?;
|
|
let auth = CodexAuth::from_api_key("test");
|
|
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));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn disabled_apps_remove_reserved_server_config_for_all_hosts() -> TestResult {
|
|
let codex_home = tempfile::tempdir()?;
|
|
let config = ConfigBuilder::default()
|
|
.codex_home(codex_home.path().to_path_buf())
|
|
.fallback_cwd(Some(codex_home.path().to_path_buf()))
|
|
.cli_overrides(vec![
|
|
("features.apps".to_string(), false.into()),
|
|
(
|
|
"mcp_servers.codex_apps.url".to_string(),
|
|
"https://example.com/mcp".into(),
|
|
),
|
|
])
|
|
.build()
|
|
.await?;
|
|
let managers = [
|
|
installed_manager(&config, /*auth*/ None),
|
|
McpManager::new(Arc::new(plugins_manager_for_config(
|
|
&config,
|
|
auth_manager_from_optional_auth(/*auth*/ None),
|
|
))),
|
|
];
|
|
for manager in managers {
|
|
let servers = manager.runtime_servers(&config).await;
|
|
assert!(!servers.contains_key(CODEX_APPS_MCP_SERVER_NAME));
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
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_manager_from_optional_auth(auth),
|
|
)),
|
|
Arc::new(builder.build()),
|
|
codex_core::CodexAppsToolsCache::default(),
|
|
)
|
|
}
|
|
|
|
struct RemoveCodexApps;
|
|
|
|
impl McpServerContributor<Config> for RemoveCodexApps {
|
|
fn id(&self) -> &'static str {
|
|
"remove_codex_apps"
|
|
}
|
|
|
|
fn contribute<'a>(
|
|
&'a self,
|
|
_context: McpServerContributionContext<'a, Config>,
|
|
) -> codex_extension_api::ExtensionFuture<'a, Vec<McpServerContribution>> {
|
|
Box::pin(async move {
|
|
vec![McpServerContribution::Remove {
|
|
name: CODEX_APPS_MCP_SERVER_NAME.to_string(),
|
|
}]
|
|
})
|
|
}
|
|
}
|