Download codex-rs/core/src/connectors_tests.rs from SaylorTwift/codex: direct link, hf CLI and curl.
- Browser
- Download file 20.3 kB
-
https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/core/src/connectors_tests.rs
- Command line
-
hf download hf://SaylorTwift/codex/codex-rs/core/src/connectors_tests.rs
-
curl -L -o connectors_tests.rs https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/core/src/connectors_tests.rs
20.3 kB
| use super::*; | |
| use crate::config::CONFIG_TOML_FILE; | |
| use crate::config::ConfigBuilder; | |
| use crate::plugins::plugins_manager_for_config; | |
| use codex_config::test_support::CloudConfigBundleFixture; | |
| use codex_config::types::ApprovalsReviewer; | |
| 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; | |
| use pretty_assertions::assert_eq; | |
| use rmcp::model::JsonObject; | |
| use rmcp::model::MetaObject; | |
| use rmcp::model::Tool; | |
| use std::collections::HashSet; | |
| use std::sync::Arc; | |
| use tempfile::tempdir; | |
| fn plugin_names(names: &[&str]) -> Vec<String> { | |
| names.iter().map(ToString::to_string).collect() | |
| } | |
| fn test_tool_definition(tool_name: &str) -> Tool { | |
| Tool::new_with_raw(tool_name.to_string(), None, Arc::new(JsonObject::default())) | |
| } | |
| fn codex_app_tool( | |
| tool_name: &str, | |
| connector_id: &str, | |
| connector_name: Option<&str>, | |
| plugin_display_names: &[&str], | |
| ) -> ToolInfo { | |
| let tool_namespace = connector_name | |
| .map(sanitize_name) | |
| .map(|connector_name| format!("mcp__{CODEX_APPS_MCP_SERVER_NAME}__{connector_name}")) | |
| .unwrap_or_else(|| CODEX_APPS_MCP_SERVER_NAME.to_string()); | |
| ToolInfo { | |
| server_name: CODEX_APPS_MCP_SERVER_NAME.to_string(), | |
| supports_parallel_tool_calls: false, | |
| server_origin: None, | |
| callable_name: tool_name.to_string(), | |
| callable_namespace: tool_namespace, | |
| namespace_description: None, | |
| tool: test_tool_definition(tool_name), | |
| openai_file_input_optional_fields: Default::default(), | |
| connector_id: Some(connector_id.to_string()), | |
| connector_name: connector_name.map(ToOwned::to_owned), | |
| plugin_display_names: plugin_names(plugin_display_names), | |
| } | |
| } | |
| fn with_accessible_connectors_cache_cleared<R>(f: impl FnOnce() -> R) -> R { | |
| let previous = { | |
| let mut cache_guard = ACCESSIBLE_CONNECTORS_CACHE | |
| .lock() | |
| .unwrap_or_else(std::sync::PoisonError::into_inner); | |
| cache_guard.take() | |
| }; | |
| let result = f(); | |
| let mut cache_guard = ACCESSIBLE_CONNECTORS_CACHE | |
| .lock() | |
| .unwrap_or_else(std::sync::PoisonError::into_inner); | |
| *cache_guard = previous; | |
| result | |
| } | |
| fn accessible_connectors_from_mcp_tools_carries_plugin_display_names() { | |
| let tools = vec![ | |
| codex_app_tool( | |
| "calendar_list_events", | |
| "calendar", | |
| /*connector_name*/ None, | |
| &["sample", "sample"], | |
| ), | |
| codex_app_tool( | |
| "calendar_create_event", | |
| "calendar", | |
| Some("Google Calendar"), | |
| &["beta", "sample"], | |
| ), | |
| ToolInfo { | |
| server_name: "sample".to_string(), | |
| supports_parallel_tool_calls: false, | |
| server_origin: None, | |
| callable_name: "echo".to_string(), | |
| callable_namespace: "sample".to_string(), | |
| namespace_description: None, | |
| tool: test_tool_definition("echo"), | |
| openai_file_input_optional_fields: Default::default(), | |
| connector_id: None, | |
| connector_name: None, | |
| plugin_display_names: plugin_names(&["ignored"]), | |
| }, | |
| ]; | |
| let connectors = accessible_connectors_from_mcp_tools(&tools); | |
| assert_eq!( | |
| connectors, | |
| vec![AppInfo { | |
| id: "calendar".to_string(), | |
| name: "Google Calendar".to_string(), | |
| description: None, | |
| logo_url: None, | |
| logo_url_dark: None, | |
| icon_assets: None, | |
| icon_dark_assets: None, | |
| distribution_channel: None, | |
| install_url: Some(connector_install_url("Google Calendar", "calendar")), | |
| branding: None, | |
| app_metadata: None, | |
| labels: None, | |
| is_accessible: true, | |
| is_enabled: true, | |
| plugin_display_names: plugin_names(&["beta", "sample"]), | |
| }] | |
| ); | |
| } | |
| fn synthetic_links_are_exposed_to_the_agent_but_not_accessible_in_app_list() { | |
| let mut synthetic_tool = codex_app_tool("gmail_batch_read_email", "gmail", Some("Gmail"), &[]); | |
| synthetic_tool.tool.meta = Some(MetaObject( | |
| serde_json::json!({ | |
| "resource_name": "gmail.batch_read_email", | |
| "_codex_apps": { | |
| "resource_uri": "/connector/gmail/batch_read_email", | |
| "contains_mcp_source": false, | |
| "synthetic_link": true | |
| } | |
| }) | |
| .as_object() | |
| .expect("meta should be an object") | |
| .clone(), | |
| )); | |
| let tools = vec![ | |
| synthetic_tool, | |
| codex_app_tool("calendar_list_events", "calendar", Some("Calendar"), &[]), | |
| ]; | |
| let calendar = AppInfo { | |
| id: "calendar".to_string(), | |
| name: "Calendar".to_string(), | |
| description: None, | |
| logo_url: None, | |
| logo_url_dark: None, | |
| icon_assets: None, | |
| icon_dark_assets: None, | |
| distribution_channel: None, | |
| install_url: Some(connector_install_url("Calendar", "calendar")), | |
| branding: None, | |
| app_metadata: None, | |
| labels: None, | |
| is_accessible: true, | |
| is_enabled: true, | |
| plugin_display_names: Vec::new(), | |
| }; | |
| assert_eq!( | |
| accessible_connectors_for_app_list_from_mcp_tools(&tools), | |
| vec![calendar.clone()] | |
| ); | |
| assert_eq!( | |
| accessible_connectors_from_mcp_tools(&tools), | |
| vec![ | |
| calendar, | |
| AppInfo { | |
| id: "gmail".to_string(), | |
| name: "Gmail".to_string(), | |
| description: None, | |
| logo_url: None, | |
| logo_url_dark: None, | |
| icon_assets: None, | |
| icon_dark_assets: None, | |
| distribution_channel: None, | |
| install_url: Some(connector_install_url("Gmail", "gmail")), | |
| branding: None, | |
| app_metadata: None, | |
| labels: None, | |
| is_accessible: true, | |
| is_enabled: true, | |
| plugin_display_names: Vec::new(), | |
| } | |
| ] | |
| ); | |
| } | |
| async fn refresh_accessible_connectors_cache_from_mcp_tools_writes_latest_installed_apps() { | |
| let codex_home = tempdir().expect("tempdir should succeed"); | |
| let mut config = ConfigBuilder::default() | |
| .codex_home(codex_home.path().to_path_buf()) | |
| .build() | |
| .await | |
| .expect("config should load"); | |
| let _ = config.features.set_enabled(Feature::Apps, /*enabled*/ true); | |
| let cache_key = accessible_connectors_cache_key(&config, /*auth*/ None); | |
| let tools = vec![ | |
| codex_app_tool( | |
| "calendar_list_events", | |
| "calendar", | |
| Some("Google Calendar"), | |
| &["calendar-plugin"], | |
| ), | |
| codex_app_tool( | |
| "openai_hidden", | |
| "connector_openai_hidden", | |
| Some("Hidden"), | |
| &[], | |
| ), | |
| ]; | |
| let cached = with_accessible_connectors_cache_cleared(|| { | |
| refresh_accessible_connectors_cache_from_mcp_tools(&config, /*auth*/ None, &tools); | |
| read_cached_accessible_connectors(&cache_key).expect("cache should be populated") | |
| }); | |
| assert_eq!( | |
| cached, | |
| vec![ | |
| AppInfo { | |
| id: "calendar".to_string(), | |
| name: "Google Calendar".to_string(), | |
| description: None, | |
| logo_url: None, | |
| logo_url_dark: None, | |
| icon_assets: None, | |
| icon_dark_assets: None, | |
| distribution_channel: None, | |
| install_url: Some(connector_install_url("Google Calendar", "calendar")), | |
| branding: None, | |
| app_metadata: None, | |
| labels: None, | |
| is_accessible: true, | |
| is_enabled: true, | |
| plugin_display_names: plugin_names(&["calendar-plugin"]), | |
| }, | |
| AppInfo { | |
| id: "connector_openai_hidden".to_string(), | |
| name: "Hidden".to_string(), | |
| description: None, | |
| logo_url: None, | |
| logo_url_dark: None, | |
| icon_assets: None, | |
| icon_dark_assets: None, | |
| distribution_channel: None, | |
| install_url: Some(connector_install_url("Hidden", "connector_openai_hidden")), | |
| branding: None, | |
| app_metadata: None, | |
| labels: None, | |
| is_accessible: true, | |
| is_enabled: true, | |
| plugin_display_names: Vec::new(), | |
| } | |
| ] | |
| ); | |
| } | |
| fn accessible_connectors_from_mcp_tools_preserves_description() { | |
| let mcp_tools = vec![ToolInfo { | |
| server_name: CODEX_APPS_MCP_SERVER_NAME.to_string(), | |
| supports_parallel_tool_calls: false, | |
| server_origin: None, | |
| callable_name: "calendar_create_event".to_string(), | |
| callable_namespace: "mcp__codex_apps__calendar".to_string(), | |
| namespace_description: Some("Plan events".to_string()), | |
| tool: Tool::new( | |
| "calendar_create_event", | |
| "Create a calendar event", | |
| Arc::new(JsonObject::default()), | |
| ), | |
| openai_file_input_optional_fields: Default::default(), | |
| connector_id: Some("calendar".to_string()), | |
| connector_name: Some("Calendar".to_string()), | |
| plugin_display_names: Vec::new(), | |
| }]; | |
| assert_eq!( | |
| accessible_connectors_from_mcp_tools(&mcp_tools), | |
| vec![AppInfo { | |
| id: "calendar".to_string(), | |
| name: "Calendar".to_string(), | |
| description: Some("Plan events".to_string()), | |
| logo_url: None, | |
| logo_url_dark: None, | |
| icon_assets: None, | |
| icon_dark_assets: None, | |
| distribution_channel: None, | |
| branding: None, | |
| app_metadata: None, | |
| labels: None, | |
| install_url: Some(connector_install_url("Calendar", "calendar")), | |
| is_accessible: true, | |
| is_enabled: true, | |
| plugin_display_names: Vec::new(), | |
| }] | |
| ); | |
| } | |
| async fn app_approvals_reviewer_uses_link_then_app_then_default_then_global() { | |
| for (global, app_default, app, expected_global, expected_default, expected_app) in [ | |
| ( | |
| "user", | |
| "auto_review", | |
| "user", | |
| ApprovalsReviewer::User, | |
| ApprovalsReviewer::AutoReview, | |
| ApprovalsReviewer::User, | |
| ), | |
| ( | |
| "auto_review", | |
| "user", | |
| "auto_review", | |
| ApprovalsReviewer::AutoReview, | |
| ApprovalsReviewer::User, | |
| ApprovalsReviewer::AutoReview, | |
| ), | |
| ] { | |
| let codex_home = tempdir().expect("tempdir should succeed"); | |
| std::fs::write( | |
| codex_home.path().join(CONFIG_TOML_FILE), | |
| format!( | |
| r#" | |
| approvals_reviewer = "{global}" | |
| [apps._default] | |
| approvals_reviewer = "{app_default}" | |
| [apps.calendar] | |
| approvals_reviewer = "{app}" | |
| [apps.calendar.links.link_calendar] | |
| approvals_reviewer = "{app_default}" | |
| [apps.calendar.links.link_without_privacy] | |
| [apps.drive.links.link_drive] | |
| approvals_reviewer = "{app_default}" | |
| [apps.without_links] | |
| approvals_reviewer = "{app}" | |
| [apps.empty_links] | |
| approvals_reviewer = "{app}" | |
| [apps.empty_links.links] | |
| "# | |
| ), | |
| ) | |
| .expect("write config"); | |
| let config = ConfigBuilder::default() | |
| .codex_home(codex_home.path().to_path_buf()) | |
| .build() | |
| .await | |
| .expect("config should build"); | |
| for (link_id, expected) in [ | |
| (Some("link_calendar"), expected_default), | |
| (Some("link_without_privacy"), expected_app), | |
| (Some("link_drive"), expected_app), | |
| (None, expected_app), | |
| ] { | |
| assert_eq!( | |
| mcp_approvals_reviewer_from_layers( | |
| &config.config_layer_stack, | |
| config.approvals_reviewer, | |
| config.model.as_deref(), | |
| CODEX_APPS_MCP_SERVER_NAME, | |
| Some("calendar"), | |
| link_id, | |
| ), | |
| expected | |
| ); | |
| } | |
| for connector_id in ["without_links", "empty_links"] { | |
| assert_eq!( | |
| mcp_approvals_reviewer_from_layers( | |
| &config.config_layer_stack, | |
| config.approvals_reviewer, | |
| config.model.as_deref(), | |
| CODEX_APPS_MCP_SERVER_NAME, | |
| Some(connector_id), | |
| Some("link_calendar"), | |
| ), | |
| expected_app | |
| ); | |
| } | |
| assert_eq!( | |
| mcp_approvals_reviewer_from_layers( | |
| &config.config_layer_stack, | |
| config.approvals_reviewer, | |
| config.model.as_deref(), | |
| CODEX_APPS_MCP_SERVER_NAME, | |
| Some("drive"), | |
| /*link_id*/ None, | |
| ), | |
| expected_default | |
| ); | |
| assert_eq!( | |
| mcp_approvals_reviewer_from_layers( | |
| &config.config_layer_stack, | |
| config.approvals_reviewer, | |
| config.model.as_deref(), | |
| CODEX_APPS_MCP_SERVER_NAME, | |
| /*connector_id*/ None, | |
| /*link_id*/ None, | |
| ), | |
| expected_default | |
| ); | |
| assert_eq!( | |
| mcp_approvals_reviewer_from_layers( | |
| &config.config_layer_stack, | |
| config.approvals_reviewer, | |
| config.model.as_deref(), | |
| "custom_server", | |
| Some("calendar"), | |
| Some("link_calendar"), | |
| ), | |
| expected_global | |
| ); | |
| } | |
| } | |
| async fn default_app_approvals_reviewer_respects_global_reviewer_requirements() { | |
| let codex_home = tempdir().expect("tempdir should succeed"); | |
| std::fs::write( | |
| codex_home.path().join(CONFIG_TOML_FILE), | |
| r#" | |
| approvals_reviewer = "auto_review" | |
| [apps._default] | |
| approvals_reviewer = "user" | |
| "#, | |
| ) | |
| .expect("write config"); | |
| let config = ConfigBuilder::default() | |
| .codex_home(codex_home.path().to_path_buf()) | |
| .cloud_config_bundle( | |
| CloudConfigBundleFixture::loader_with_enterprise_requirement( | |
| r#"allowed_approvals_reviewers = ["auto_review"]"#, | |
| ), | |
| ) | |
| .build() | |
| .await | |
| .expect("config should build"); | |
| assert_eq!( | |
| mcp_approvals_reviewer_from_layers( | |
| &config.config_layer_stack, | |
| config.approvals_reviewer, | |
| config.model.as_deref(), | |
| CODEX_APPS_MCP_SERVER_NAME, | |
| Some("calendar"), | |
| /*link_id*/ None, | |
| ), | |
| ApprovalsReviewer::AutoReview | |
| ); | |
| } | |
| async fn app_approvals_reviewer_respects_global_reviewer_requirements() { | |
| let codex_home = tempdir().expect("tempdir should succeed"); | |
| std::fs::write( | |
| codex_home.path().join(CONFIG_TOML_FILE), | |
| r#" | |
| approvals_reviewer = "auto_review" | |
| [apps.calendar] | |
| approvals_reviewer = "user" | |
| [apps.calendar.links.link_calendar] | |
| approvals_reviewer = "user" | |
| "#, | |
| ) | |
| .expect("write config"); | |
| let config = ConfigBuilder::default() | |
| .codex_home(codex_home.path().to_path_buf()) | |
| .cloud_config_bundle( | |
| CloudConfigBundleFixture::loader_with_enterprise_requirement( | |
| r#"allowed_approvals_reviewers = ["auto_review"]"#, | |
| ), | |
| ) | |
| .build() | |
| .await | |
| .expect("config should build"); | |
| for link_id in [None, Some("link_calendar")] { | |
| assert_eq!( | |
| mcp_approvals_reviewer_from_layers( | |
| &config.config_layer_stack, | |
| config.approvals_reviewer, | |
| config.model.as_deref(), | |
| CODEX_APPS_MCP_SERVER_NAME, | |
| Some("calendar"), | |
| link_id, | |
| ), | |
| ApprovalsReviewer::AutoReview | |
| ); | |
| } | |
| } | |
| async fn tool_suggest_connector_ids_include_configured_tool_suggest_discoverables() { | |
| let codex_home = tempdir().expect("tempdir should succeed"); | |
| std::fs::write( | |
| codex_home.path().join(CONFIG_TOML_FILE), | |
| r#" | |
| [tool_suggest] | |
| discoverables = [ | |
| { type = "connector", id = "connector_2128aebfecb84f64a069897515042a44" }, | |
| { type = "plugin", id = "slack@openai-curated" }, | |
| { type = "connector", id = " " } | |
| ] | |
| "#, | |
| ) | |
| .expect("write config"); | |
| let config = ConfigBuilder::default() | |
| .codex_home(codex_home.path().to_path_buf()) | |
| .build() | |
| .await | |
| .expect("config should load"); | |
| assert_eq!( | |
| tool_suggest_connector_ids(&config, &[]), | |
| HashSet::from(["connector_2128aebfecb84f64a069897515042a44".to_string()]) | |
| ); | |
| } | |
| async fn tool_suggest_connector_ids_exclude_disabled_tool_suggestions() { | |
| let codex_home = tempdir().expect("tempdir should succeed"); | |
| std::fs::write( | |
| codex_home.path().join(CONFIG_TOML_FILE), | |
| r#" | |
| [tool_suggest] | |
| discoverables = [ | |
| { type = "connector", id = "connector_calendar" }, | |
| { type = "connector", id = "connector_gmail" } | |
| ] | |
| disabled_tools = [ | |
| { type = "connector", id = "connector_calendar" } | |
| ] | |
| "#, | |
| ) | |
| .expect("write config"); | |
| let config = ConfigBuilder::default() | |
| .codex_home(codex_home.path().to_path_buf()) | |
| .build() | |
| .await | |
| .expect("config should load"); | |
| assert_eq!( | |
| tool_suggest_connector_ids(&config, &[]), | |
| HashSet::from(["connector_gmail".to_string()]) | |
| ); | |
| } | |
| async fn tool_suggest_uses_connector_id_fallback_when_directory_cache_is_empty() { | |
| let codex_home = tempdir().expect("tempdir should succeed"); | |
| std::fs::write( | |
| codex_home.path().join(CONFIG_TOML_FILE), | |
| r#" | |
| [features] | |
| apps = true | |
| [tool_suggest] | |
| discoverables = [ | |
| { type = "connector", id = "connector_gmail" } | |
| ] | |
| "#, | |
| ) | |
| .expect("write config"); | |
| let config = ConfigBuilder::default() | |
| .codex_home(codex_home.path().to_path_buf()) | |
| .build() | |
| .await | |
| .expect("config should load"); | |
| let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing(); | |
| 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, | |
| &plugins_manager, | |
| Some(&auth), | |
| &[], | |
| &[], | |
| ) | |
| .await | |
| .expect("discoverable tools should load"); | |
| assert_eq!( | |
| discoverable_tools, | |
| vec![DiscoverableTool::from(plugin_connector_to_app_info( | |
| "connector_gmail".to_string(), | |
| ))] | |
| ); | |
| } | |
| async fn tool_suggest_includes_connectors_from_loaded_plugin_apps() { | |
| let codex_home = tempdir().expect("tempdir should succeed"); | |
| std::fs::write( | |
| codex_home.path().join(CONFIG_TOML_FILE), | |
| r#" | |
| [features] | |
| apps = true | |
| "#, | |
| ) | |
| .expect("write config"); | |
| let config = ConfigBuilder::default() | |
| .codex_home(codex_home.path().to_path_buf()) | |
| .build() | |
| .await | |
| .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, AuthManager::from_auth_for_testing(auth.clone())); | |
| let discoverable_tools = list_tool_suggest_discoverable_tools_with_auth( | |
| &config, | |
| &plugins_manager, | |
| Some(&auth), | |
| &[], | |
| &loaded_plugin_app_connector_ids, | |
| ) | |
| .await | |
| .expect("discoverable tools should load"); | |
| assert_eq!( | |
| discoverable_tools, | |
| vec![DiscoverableTool::from(plugin_connector_to_app_info( | |
| "asdk_app_databricks_workspace".to_string(), | |
| ))] | |
| ); | |
| } | |