Changeset 62067
- Timestamp:
- 03/19/2026 01:55:45 PM (8 days ago)
- Location:
- trunk
- Files:
-
- 1 added
- 6 edited
-
src/wp-includes/ai-client.php (modified) (1 diff)
-
src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php (modified) (6 diffs)
-
src/wp-includes/class-wp-connector-registry.php (modified) (1 diff)
-
src/wp-includes/connectors.php (modified) (9 diffs)
-
tests/phpunit/tests/ai-client/wpAiClientPromptBuilder.php (modified) (2 diffs)
-
tests/phpunit/tests/ai-client/wpSupportsAI.php (added)
-
tests/phpunit/tests/connectors/wpConnectorRegistry.php (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/ai-client.php
r61700 r62067 9 9 10 10 use WordPress\AiClient\AiClient; 11 12 /** 13 * Returns whether AI features are supported in the current environment. 14 * 15 * @since 7.0.0 16 * 17 * @return bool Whether AI features are supported. 18 */ 19 function wp_supports_ai(): bool { 20 $is_enabled = defined( 'WP_AI_SUPPORT' ) ? WP_AI_SUPPORT : true; 21 22 /** 23 * Filters whether the current request should use AI. 24 * 25 * This allows plugins and 3rd-party code to disable AI features on a per-request basis, or to even override explicit 26 * preferences defined by the site owner. 27 * 28 * @since 7.0.0 29 * 30 * @param bool $is_enabled Whether the current request should use AI. Default to WP_AI_SUPPORT constant, or true if 31 * the constant is not defined. 32 */ 33 return (bool) apply_filters( 'wp_supports_ai', $is_enabled ); 34 } 11 35 12 36 /** -
trunk/src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php
r62037 r62067 46 46 * 47 47 * @since 7.0.0 48 * 49 * @phpstan-import-type Prompt from PromptBuilder 48 50 * 49 51 * @method self with_text(string $text) Adds text to the current message. … … 171 173 * @since 7.0.0 172 174 * 173 * @param ProviderRegistry $registry The provider registry for finding suitable models.174 * @param string|MessagePart|Message|array|list<string|MessagePart|array>|list<Message>|null$prompt Optional. Initial prompt content.175 * A string for simple text prompts,176 * a MessagePart or Message object for177 * structured content, an array for a178 * message array shape, or a list of179 * parts or messages for multi-turn180 * conversations. Default null.175 * @param ProviderRegistry $registry The provider registry for finding suitable models. 176 * @param Prompt $prompt Optional. Initial prompt content. 177 * A string for simple text prompts, 178 * a MessagePart or Message object for 179 * structured content, an array for a 180 * message array shape, or a list of 181 * parts or messages for multi-turn 182 * conversations. Default null. 181 183 */ 182 184 public function __construct( ProviderRegistry $registry, $prompt = null ) { … … 290 292 // Check if the prompt should be prevented for is_supported* and generate_*/convert_text_to_speech* methods. 291 293 if ( self::is_support_check_method( $name ) || self::is_generating_method( $name ) ) { 292 /** 293 * Filters whether to prevent the prompt from being executed. 294 * 295 * @since 7.0.0 296 * 297 * @param bool $prevent Whether to prevent the prompt. Default false. 298 * @param WP_AI_Client_Prompt_Builder $builder A clone of the prompt builder instance (read-only). 299 */ 300 $prevent = (bool) apply_filters( 'wp_ai_client_prevent_prompt', false, clone $this ); 294 // If AI is not supported, then there's no need to apply the filter as the prompt will be prevented anyway. 295 $is_ai_disabled = ! wp_supports_ai(); 296 $prevent = $is_ai_disabled; 297 if ( ! $prevent ) { 298 /** 299 * Filters whether to prevent the prompt from being executed. 300 * 301 * @since 7.0.0 302 * 303 * @param bool $prevent Whether to prevent the prompt. Default false. 304 * @param WP_AI_Client_Prompt_Builder $builder A clone of the prompt builder instance (read-only). 305 */ 306 $prevent = (bool) apply_filters( 'wp_ai_client_prevent_prompt', false, clone $this ); 307 } 301 308 302 309 if ( $prevent ) { … … 306 313 } 307 314 315 $error_message = $is_ai_disabled 316 ? __( 'AI features are not supported in this environment.' ) 317 : __( 'Prompt execution was prevented by a filter.' ); 318 308 319 // For generate_* and convert_text_to_speech* methods, create a WP_Error. 309 320 $this->error = new WP_Error( 310 321 'prompt_prevented', 311 __( 'Prompt execution was prevented by a filter.' ),322 $error_message, 312 323 array( 313 324 'status' => 503, … … 424 435 $camel_case_name = $this->snake_to_camel_case( $name ); 425 436 426 if ( ! is_callable( array( $this->builder, $camel_case_name ) ) ) { 437 $method = array( $this->builder, $camel_case_name ); 438 if ( ! is_callable( $method ) ) { 427 439 throw new BadMethodCallException( 428 440 sprintf( … … 435 447 } 436 448 437 return array( $this->builder, $camel_case_name );449 return $method; 438 450 } 439 451 -
trunk/src/wp-includes/class-wp-connector-registry.php
r62056 r62067 171 171 } 172 172 173 if ( 'ai_provider' === $args['type'] && ! wp_supports_ai() ) { 174 // No need for a `doing_it_wrong` as AI support is disabled intentionally. 175 return null; 176 } 177 173 178 $connector = array( 174 179 'name' => $args['name'], -
trunk/src/wp-includes/connectors.php
r62032 r62067 1 1 <?php 2 2 /** 3 * Connectors API: core functions for registering and managing connectors. 4 * 5 * The Connectors API provides a unified framework for registering and managing 6 * external service integrations within WordPress. A "connector" represents a 7 * connection to an external service — currently focused on AI providers — with 8 * standardized metadata, authentication configuration, and plugin association. 9 * 10 * ## Overview 11 * 12 * The Connectors API enables developers to: 13 * 14 * - Register AI provider connectors with standardized interfaces. 15 * - Define authentication methods and credential sources. 16 * - Associate connectors with WordPress.org plugins for install/activate UI. 17 * - Expose connector settings through the REST API with automatic key masking. 18 * 19 * ## AI Provider Plugins 20 * 21 * AI provider plugins that register with the WP AI Client's `ProviderRegistry` 22 * get automatic connector integration — no explicit connector registration is 23 * needed. The system discovers providers from the WP AI Client registry and 24 * creates connectors with the correct name, description, logo, authentication 25 * method, and setting name derived from the provider's configuration. 26 * 27 * The authentication method (`api_key` or `none`) is determined by the provider's 28 * metadata in the WP AI Client. For `api_key` providers, a `setting_name` is 29 * automatically generated following the same naming convention used for environment 30 * variables and PHP constants (e.g., provider `openai` maps to `OPENAI_API_KEY` 31 * for env/constant lookup). 32 * 33 * @see WordPress\AiClient\Providers\ProviderRegistry 34 * 35 * ## Admin UI Integration 36 * 37 * Registered `ai_provider` connectors appear on the Settings → Connectors 38 * admin screen. The screen renders each connector as a card using the 39 * registry data: 40 * 41 * - `name`, `description`, and `logo_url` are displayed on the card. 42 * - `plugin.slug` enables install/activate controls — the screen checks 43 * whether the plugin is installed and active, and shows the appropriate 44 * action button. 45 * - `authentication.credentials_url` is rendered as a link directing users 46 * to the provider's site to obtain API credentials. 47 * - For `api_key` connectors, the screen shows the current key source 48 * (environment variable, PHP constant, or database) and connection status. 49 * 50 * On the backend, `api_key` connectors also receive automatic settings 51 * registration via the Settings API (`show_in_rest`), API key masking in 52 * REST API responses, and key validation against the provider on update. 53 * 54 * Connectors with other authentication methods or types are registered in the PHP 55 * registry and exposed via the script module data, but require a client-side 56 * JavaScript registration for custom frontend UI. Support for additional 57 * authentication methods and connector types is planned for future releases. 58 * 59 * ## Custom Connectors 60 * 61 * The `wp_connectors_init` action hook allows plugins to override metadata on 62 * existing connectors. AI provider connectors are auto-discovered from the WP 63 * AI Client registry and should not be manually registered here. 64 * 65 * Example — overriding the description of an auto-discovered connector: 66 * 67 * add_action( 'wp_connectors_init', function ( WP_Connector_Registry $registry ) { 68 * if ( $registry->is_registered( 'openai' ) ) { 69 * $connector = $registry->unregister( 'openai' ); 70 * $connector['description'] = __( 'Custom description for OpenAI.', 'my-plugin' ); 71 * $registry->register( 'openai', $connector ); 72 * } 73 * } ); 74 * 75 * Non-AI-provider connector types are not yet fully supported. The PHP registry 76 * accepts any connector type, but only `ai_provider` connectors with `api_key` 77 * authentication receive automatic admin UI. Support for additional connector 78 * types with dedicated frontend integration is planned for future releases. 79 * When available, this action will be the primary hook for registering those 80 * new connector types. 81 * 82 * ## Initialization Lifecycle 83 * 84 * During `init`, the system: 85 * 86 * 1. Creates the `WP_Connector_Registry` singleton. 87 * 2. Registers built-in connectors (Anthropic, Google, OpenAI) with hardcoded defaults. 88 * 3. Auto-discovers providers from the WP AI Client registry and merges their 89 * metadata (name, description, logo, authentication) on top of defaults, 90 * with registry values taking precedence. 91 * 4. Fires the `wp_connectors_init` action so plugins can override metadata 92 * on existing connectors or register additional connectors. 93 * 5. Registers settings and passes stored API keys to the WP AI Client. 94 * 95 * ## Authentication 96 * 97 * Connectors support two authentication methods: 98 * 99 * - `api_key`: Requires an API key, which can be provided via environment variable, 100 * PHP constant, or the database (checked in that order). 101 * - `none`: No authentication required. 102 * 103 * API keys stored in the database are automatically masked in REST API responses 104 * and validated against the provider on update. 3 * Connectors API. 105 4 * 106 5 * @package WordPress … … 115 14 * Checks if a connector is registered. 116 15 * 117 * Example:118 *119 * if ( wp_is_connector_registered( 'openai' ) ) {120 * // The OpenAI connector is available.121 * }122 *123 16 * @since 7.0.0 124 17 * 125 18 * @see WP_Connector_Registry::is_registered() 126 * @see wp_get_connector()127 * @see wp_get_connectors()128 19 * 129 20 * @param string $id The connector identifier. … … 142 33 * Retrieves a registered connector. 143 34 * 144 * Example:145 *146 * $connector = wp_get_connector( 'openai' );147 * if ( $connector ) {148 * echo $connector['name']; // 'OpenAI'149 * }150 *151 35 * @since 7.0.0 152 36 * 153 37 * @see WP_Connector_Registry::get_registered() 154 * @see wp_is_connector_registered()155 * @see wp_get_connectors()156 38 * 157 39 * @param string $id The connector identifier. … … 204 86 * Retrieves all registered connectors. 205 87 * 206 * Example:207 *208 * $connectors = wp_get_connectors();209 * foreach ( $connectors as $id => $connector ) {210 * printf( '%s: %s', $connector['name'], $connector['description'] );211 * }212 *213 88 * @since 7.0.0 214 89 * 215 90 * @see WP_Connector_Registry::get_all_registered() 216 * @see wp_is_connector_registered()217 * @see wp_get_connector()218 91 * 219 92 * @return array { … … 311 184 * Initializes the connector registry with default connectors and fires the registration action. 312 185 * 313 * This function orchestrates the full connector initialization sequence: 314 * 315 * 1. Creates the `WP_Connector_Registry` singleton instance. 316 * 2. Defines built-in connectors (Anthropic, Google, OpenAI) with hardcoded defaults 317 * including name, description, type, plugin slug, and authentication configuration. 318 * 3. Merges metadata from the WP AI Client provider registry on top of defaults. 319 * Registry values (from provider plugins) take precedence over hardcoded fallbacks 320 * for name, description, logo URL, and authentication method. 321 * 4. Registers all connectors (built-in and AI Client-discovered) on the registry. 322 * 5. Fires the `wp_connectors_init` action for plugins to override metadata 323 * on existing connectors or register additional connectors. 324 * 325 * Built-in connectors are registered before the action fires and cannot be unhooked. 326 * Plugins should use the `wp_connectors_init` action to override metadata or 327 * register new connectors via `$registry->register()`. 186 * Creates the registry instance, registers built-in connectors (which cannot be unhooked), 187 * and then fires the `wp_connectors_init` action for plugins to register their own connectors. 328 188 * 329 189 * @since 7.0.0 … … 333 193 $registry = new WP_Connector_Registry(); 334 194 WP_Connector_Registry::set_instance( $registry ); 195 196 // Only register default AI providers if AI support is enabled. 197 if ( wp_supports_ai() ) { 198 _wp_connectors_register_default_ai_providers( $registry ); 199 } 200 201 /** 202 * Fires when the connector registry is ready for plugins to register connectors. 203 * 204 * Built-in connectors and any AI providers auto-discovered from the WP AI Client 205 * registry have already been registered at this point and cannot be unhooked. 206 * 207 * AI provider plugins that register with the WP AI Client do not need to use 208 * this action — their connectors are created automatically. This action is 209 * primarily for registering non-AI-provider connectors or overriding metadata 210 * on existing connectors. 211 * 212 * Use `$registry->register()` within this action to add new connectors. 213 * To override an existing connector, unregister it first, then re-register 214 * with updated data. 215 * 216 * Example — overriding metadata on an auto-discovered connector: 217 * 218 * add_action( 'wp_connectors_init', function ( WP_Connector_Registry $registry ) { 219 * if ( $registry->is_registered( 'openai' ) ) { 220 * $connector = $registry->unregister( 'openai' ); 221 * $connector['description'] = __( 'Custom description for OpenAI.', 'my-plugin' ); 222 * $registry->register( 'openai', $connector ); 223 * } 224 * } ); 225 * 226 * @since 7.0.0 227 * 228 * @param WP_Connector_Registry $registry Connector registry instance. 229 */ 230 do_action( 'wp_connectors_init', $registry ); 231 } 232 233 /** 234 * Registers connectors for the built-in AI providers. 235 * 236 * @since 7.0.0 237 * @access private 238 * 239 * @param WP_Connector_Registry $registry The connector registry instance. 240 */ 241 function _wp_connectors_register_default_ai_providers( WP_Connector_Registry $registry ): void { 335 242 // Built-in connectors. 336 243 $defaults = array( … … 431 338 $registry->register( $id, $args ); 432 339 } 433 434 /**435 * Fires when the connector registry is ready for plugins to register connectors.436 *437 * Built-in connectors and any AI providers auto-discovered from the WP AI Client438 * registry have already been registered at this point and cannot be unhooked.439 *440 * AI provider plugins that register with the WP AI Client do not need to use441 * this action — their connectors are created automatically. This action is442 * primarily for registering non-AI-provider connectors or overriding metadata443 * on existing connectors.444 *445 * Use `$registry->register()` within this action to add new connectors.446 * To override an existing connector, unregister it first, then re-register447 * with updated data.448 *449 * Example — overriding metadata on an auto-discovered connector:450 *451 * add_action( 'wp_connectors_init', function ( WP_Connector_Registry $registry ) {452 * if ( $registry->is_registered( 'openai' ) ) {453 * $connector = $registry->unregister( 'openai' );454 * $connector['description'] = __( 'Custom description for OpenAI.', 'my-plugin' );455 * $registry->register( 'openai', $connector );456 * }457 * } );458 *459 * @since 7.0.0460 *461 * @param WP_Connector_Registry $registry Connector registry instance.462 */463 do_action( 'wp_connectors_init', $registry );464 340 } 465 341 … … 628 504 /** 629 505 * Registers default connector settings. 630 *631 * Only registers settings for `ai_provider` connectors with `api_key`632 * authentication whose provider is present in the WP AI Client registry.633 * Each setting is registered with `show_in_rest` enabled, making it634 * accessible through the `/wp/v2/settings` REST endpoint.635 506 * 636 507 * @since 7.0.0 … … 721 592 722 593 /** 723 * Provides connector data to the Settings → Connectors admin screen. 724 * 725 * This function is the bridge between the PHP connector registry and the 726 * frontend admin UI. It transforms each registered connector into the data 727 * structure consumed by the `options-connectors-wp-admin` script module, 728 * enriching registry data with runtime state: 729 * 730 * - Plugin install/activate status (via `get_plugins()` and `is_plugin_active()`). 731 * - API key source detection (`env`, `constant`, `database`, or `none`). 732 * - Connection status for `api_key` connectors (via the WP AI Client registry). 733 * 734 * Hooked to the `script_module_data_options-connectors-wp-admin` filter. 735 * 736 * @since 7.0.0 737 * @access private 738 * 739 * @see _wp_connectors_get_api_key_source() 594 * Exposes connector settings to the connectors-wp-admin script module. 595 * 596 * @since 7.0.0 597 * @access private 740 598 * 741 599 * @param array<string, mixed> $data Existing script module data. 742 * @return array<string, mixed> Script module data with a `connectors` key added, 743 * keyed by connector ID and sorted alphabetically. 600 * @return array<string, mixed> Script module data with connectors added. 744 601 */ 745 602 function _wp_connectors_get_connector_script_module_data( array $data ): array { -
trunk/tests/phpunit/tests/ai-client/wpAiClientPromptBuilder.php
r62037 r62067 2406 2406 * @ticket 64591 2407 2407 */ 2408 public function test_is_supported_returns_false_when_ai_not_supported() { 2409 add_filter( 'wp_supports_ai', '__return_false' ); 2410 2411 $builder = new WP_AI_Client_Prompt_Builder( AiClient::defaultRegistry(), 'Test prompt' ); 2412 2413 $this->assertFalse( $builder->is_supported() ); 2414 } 2415 2416 /** 2417 * Tests that is_supported returns false when prevent prompt filter returns true. 2418 * 2419 * @ticket 64591 2420 */ 2408 2421 public function test_is_supported_returns_false_when_filter_prevents_prompt() { 2409 2422 add_filter( 'wp_ai_client_prevent_prompt', '__return_true' ); … … 2413 2426 $this->assertFalse( $builder->is_supported() ); 2414 2427 } 2415 2416 2428 /** 2417 2429 * Tests that generate_result returns WP_Error when prevent prompt filter returns true. -
trunk/tests/phpunit/tests/connectors/wpConnectorRegistry.php
r62056 r62067 398 398 $this->assertSame( $instance1, $instance2 ); 399 399 } 400 401 /** 402 * Test registration skips AI connectors when AI is not supported. 403 */ 404 public function test_register_skips_when_ai_not_supported() { 405 add_filter( 'wp_supports_ai', '__return_false' ); 406 407 $this->registry->register( 'first', self::$default_args ); 408 409 $all = $this->registry->get_all_registered(); 410 $this->assertCount( 0, $all ); 411 } 400 412 }
Note: See TracChangeset
for help on using the changeset viewer.