mirror of
https://github.com/openai/codex.git
synced 2026-11-10 21:00:47 +00:00
allow extension-managed Codex Apps auth
This commit is contained in:
@@ -43,6 +43,7 @@ pub use mcp::codex_apps_mcp_server_config;
|
||||
pub use mcp::configured_mcp_servers;
|
||||
pub use mcp::effective_mcp_servers;
|
||||
pub use mcp::effective_mcp_servers_from_configured;
|
||||
pub use mcp::extension_managed_codex_apps_enabled;
|
||||
pub use mcp::host_owned_codex_apps_enabled;
|
||||
pub use mcp::hosted_plugin_runtime_mcp_server_config;
|
||||
pub use mcp::tool_plugin_provenance;
|
||||
|
||||
@@ -43,6 +43,7 @@ use rmcp::model::ReadResourceResult;
|
||||
use serde_json::Value;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::McpServerSource;
|
||||
use crate::ResolvedMcpCatalog;
|
||||
use crate::codex_apps_cache::CodexAppsToolsCache;
|
||||
use crate::codex_apps_cache::codex_apps_tools_cache_key;
|
||||
@@ -238,8 +239,22 @@ impl ToolPluginProvenance {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extension_managed_codex_apps_enabled(config: &McpConfig) -> bool {
|
||||
// Trusted host extensions may replace the ChatGPT-backed server with one
|
||||
// that supplies its own request authentication.
|
||||
config.apps_enabled
|
||||
&& config
|
||||
.mcp_server_catalog
|
||||
.server(CODEX_APPS_MCP_SERVER_NAME)
|
||||
.is_some_and(|server| {
|
||||
matches!(server.source(), McpServerSource::Extension { .. })
|
||||
&& matches!(&server.config().auth, &McpServerAuth::OAuth)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn host_owned_codex_apps_enabled(config: &McpConfig, auth: Option<&CodexAuth>) -> bool {
|
||||
config.apps_enabled && auth.is_some_and(CodexAuth::uses_codex_backend)
|
||||
extension_managed_codex_apps_enabled(config)
|
||||
|| (config.apps_enabled && auth.is_some_and(CodexAuth::uses_codex_backend))
|
||||
}
|
||||
|
||||
pub fn configured_mcp_servers(config: &McpConfig) -> HashMap<String, McpServerConfig> {
|
||||
|
||||
@@ -285,6 +285,88 @@ fn codex_apps_server_config_forwards_configured_product_sku_header() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn effective_mcp_servers_preserve_extension_owned_oauth_codex_apps_without_chatgpt_auth() {
|
||||
let mut config = test_mcp_config(PathBuf::new());
|
||||
config.apps_enabled = true;
|
||||
|
||||
let compatibility_config =
|
||||
codex_apps_mcp_server_config(&config.chatgpt_base_url, /*apps_mcp_product_sku*/ None);
|
||||
let mut extension_config = compatibility_config.clone();
|
||||
extension_config.auth = McpServerAuth::OAuth;
|
||||
let McpServerTransportConfig::StreamableHttp { http_headers, .. } =
|
||||
&mut extension_config.transport
|
||||
else {
|
||||
panic!("expected streamable http transport for codex apps");
|
||||
};
|
||||
*http_headers = Some(HashMap::from([(
|
||||
"X-OpenAI-Actor-Authorization".to_string(),
|
||||
"actor-biscuit".to_string(),
|
||||
)]));
|
||||
let expected = extension_config.clone();
|
||||
|
||||
let mut catalog = ResolvedMcpCatalog::builder();
|
||||
catalog.register(McpServerRegistration::from_compatibility(
|
||||
CODEX_APPS_MCP_SERVER_NAME.to_string(),
|
||||
"legacy_codex_apps".to_string(),
|
||||
compatibility_config,
|
||||
));
|
||||
catalog.register(McpServerRegistration::from_extension(
|
||||
CODEX_APPS_MCP_SERVER_NAME.to_string(),
|
||||
"host_managed_apps".to_string(),
|
||||
/*contribution_order*/ 0,
|
||||
extension_config,
|
||||
));
|
||||
config.mcp_server_catalog = catalog.build();
|
||||
|
||||
let effective = effective_mcp_servers(&config, /*auth*/ None);
|
||||
|
||||
assert_eq!(
|
||||
effective
|
||||
.get(CODEX_APPS_MCP_SERVER_NAME)
|
||||
.and_then(EffectiveMcpServer::configured_config),
|
||||
Some(&expected)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn effective_mcp_servers_hide_chatgpt_codex_apps_without_chatgpt_auth() {
|
||||
let mut config = test_mcp_config(PathBuf::new());
|
||||
config.apps_enabled = true;
|
||||
|
||||
let mut catalog = ResolvedMcpCatalog::builder();
|
||||
catalog.register(McpServerRegistration::from_compatibility(
|
||||
CODEX_APPS_MCP_SERVER_NAME.to_string(),
|
||||
"legacy_codex_apps".to_string(),
|
||||
codex_apps_mcp_server_config(&config.chatgpt_base_url, /*apps_mcp_product_sku*/ None),
|
||||
));
|
||||
config.mcp_server_catalog = catalog.build();
|
||||
|
||||
let effective = effective_mcp_servers(&config, /*auth*/ None);
|
||||
|
||||
assert!(!effective.contains_key(CODEX_APPS_MCP_SERVER_NAME));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn effective_mcp_servers_hide_config_owned_oauth_codex_apps_without_chatgpt_auth() {
|
||||
let mut config = test_mcp_config(PathBuf::new());
|
||||
config.apps_enabled = true;
|
||||
let mut server_config =
|
||||
codex_apps_mcp_server_config(&config.chatgpt_base_url, /*apps_mcp_product_sku*/ None);
|
||||
server_config.auth = McpServerAuth::OAuth;
|
||||
|
||||
let mut catalog = ResolvedMcpCatalog::builder();
|
||||
catalog.register(McpServerRegistration::from_config(
|
||||
CODEX_APPS_MCP_SERVER_NAME.to_string(),
|
||||
server_config,
|
||||
));
|
||||
config.mcp_server_catalog = catalog.build();
|
||||
|
||||
let effective = effective_mcp_servers(&config, /*auth*/ None);
|
||||
|
||||
assert!(!effective.contains_key(CODEX_APPS_MCP_SERVER_NAME));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn effective_mcp_servers_preserve_runtime_servers() {
|
||||
let codex_home = tempfile::tempdir().expect("tempdir");
|
||||
|
||||
@@ -111,6 +111,7 @@ pub(super) async fn spawn_review_thread(
|
||||
realtime_active: parent_turn_context.realtime_active,
|
||||
config: per_turn_config,
|
||||
auth_manager: auth_manager_for_context,
|
||||
extension_managed_apps_enabled: parent_turn_context.extension_managed_apps_enabled,
|
||||
model_info: model_info.clone(),
|
||||
session_telemetry: session_telemetry_for_context,
|
||||
provider: provider_for_context,
|
||||
|
||||
@@ -105,6 +105,7 @@ pub struct TurnContext {
|
||||
pub(crate) realtime_active: bool,
|
||||
pub config: Arc<Config>,
|
||||
pub(crate) auth_manager: Option<Arc<AuthManager>>,
|
||||
pub(crate) extension_managed_apps_enabled: bool,
|
||||
pub(crate) model_info: ModelInfo,
|
||||
pub(crate) session_telemetry: SessionTelemetry,
|
||||
pub(crate) provider: SharedModelProvider,
|
||||
@@ -201,7 +202,7 @@ impl TurnContext {
|
||||
.is_some_and(AuthManager::current_auth_uses_codex_backend);
|
||||
self.config
|
||||
.features
|
||||
.apps_enabled_for_auth(uses_codex_backend)
|
||||
.apps_enabled_for_auth(uses_codex_backend || self.extension_managed_apps_enabled)
|
||||
&& self.config.orchestrator_mcp_enabled
|
||||
}
|
||||
|
||||
@@ -253,6 +254,7 @@ impl TurnContext {
|
||||
realtime_active: self.realtime_active,
|
||||
config: Arc::new(config),
|
||||
auth_manager: self.auth_manager.clone(),
|
||||
extension_managed_apps_enabled: self.extension_managed_apps_enabled,
|
||||
model_info: model_info.clone(),
|
||||
session_telemetry: self
|
||||
.session_telemetry
|
||||
@@ -534,6 +536,7 @@ impl Session {
|
||||
realtime_active: false,
|
||||
config: per_turn_config,
|
||||
auth_manager: auth_manager_for_context,
|
||||
extension_managed_apps_enabled: false,
|
||||
model_info,
|
||||
session_telemetry: session_telemetry_for_context,
|
||||
provider: provider_for_context,
|
||||
@@ -763,6 +766,10 @@ impl Session {
|
||||
sub_id,
|
||||
skills_snapshot,
|
||||
);
|
||||
turn_context.extension_managed_apps_enabled =
|
||||
codex_mcp::extension_managed_codex_apps_enabled(
|
||||
self.services.latest_mcp_runtime().config(),
|
||||
);
|
||||
turn_context.realtime_active = self.conversation.running_state().await.is_some();
|
||||
|
||||
if let Some(final_schema) = final_output_json_schema {
|
||||
|
||||
Reference in New Issue
Block a user