Connectors: Add connector registry for extensibility
Introduces `WP_Connector_Registry` class and a `wp_connectors_init` action hook so plugins can register their own connectors alongside the built-in defaults (Anthropic, Google, OpenAI). Key changes: * `WP_Connector_Registry` — A `final` singleton class managing connector registration and lookup, with validation for IDs, required fields, and authentication methods. * `wp_connectors_init` action — Fired during `init` after built-in connectors are registered. Passes the registry instance so plugins call `$registry->register()` directly. * `_wp_connectors_init()` — Private function that creates the registry, merges hardcoded defaults with AI Client registry data, registers them, then fires the action. * Public read-only functions — `wp_is_connector_registered()`, `wp_get_connector()`, `wp_get_connectors()` for querying the registry after initialization. * Logo URL support — Connectors can include an optional `logo_url` field resolved from plugin directories via `_wp_connectors_resolve_ai_provider_logo_url()`. * Timing guards — `set_instance()` rejects calls after `init` completes. Registration is only possible during `wp_connectors_init`. * Connector API key settings are now only registered when the provider exists in the AI Client registry. * Refactors `_wp_connectors_get_connector_settings()` to read from the registry via `wp_get_connectors()`. Developed in https://github.com/WordPress/wordpress-develop/pull/11175 Props gziolo, flixos90, mukesh27, westonruter. Fixes #64791. Built from https://develop.svn.wordpress.org/trunk@61943 git-svn-id: http://core.svn.wordpress.org/trunk@61225 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
289
wp-includes/class-wp-connector-registry.php
Normal file
289
wp-includes/class-wp-connector-registry.php
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Connectors API
|
||||||
|
*
|
||||||
|
* Defines WP_Connector_Registry class.
|
||||||
|
*
|
||||||
|
* @package WordPress
|
||||||
|
* @subpackage Connectors
|
||||||
|
* @since 7.0.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages the registration and lookup of connectors.
|
||||||
|
*
|
||||||
|
* @since 7.0.0
|
||||||
|
* @access private
|
||||||
|
*
|
||||||
|
* @phpstan-type Connector array{
|
||||||
|
* name: string,
|
||||||
|
* description: string,
|
||||||
|
* logo_url?: string|null,
|
||||||
|
* type: string,
|
||||||
|
* authentication: array{
|
||||||
|
* method: string,
|
||||||
|
* credentials_url?: string|null,
|
||||||
|
* setting_name?: string
|
||||||
|
* },
|
||||||
|
* plugin?: array{
|
||||||
|
* slug: string
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
final class WP_Connector_Registry {
|
||||||
|
/**
|
||||||
|
* The singleton instance of the registry.
|
||||||
|
*
|
||||||
|
* @since 7.0.0
|
||||||
|
*/
|
||||||
|
private static ?WP_Connector_Registry $instance = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds the registered connectors.
|
||||||
|
*
|
||||||
|
* Each connector is stored as an associative array with keys:
|
||||||
|
* name, description, type, authentication, and optionally plugin.
|
||||||
|
*
|
||||||
|
* @since 7.0.0
|
||||||
|
* @var array<string, array>
|
||||||
|
* @phpstan-var array<string, Connector>
|
||||||
|
*/
|
||||||
|
private array $registered_connectors = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a new connector.
|
||||||
|
*
|
||||||
|
* @since 7.0.0
|
||||||
|
*
|
||||||
|
* @param string $id The unique connector identifier. Must contain only lowercase
|
||||||
|
* alphanumeric characters and underscores.
|
||||||
|
* @param array $args {
|
||||||
|
* An associative array of arguments for the connector.
|
||||||
|
*
|
||||||
|
* @type string $name Required. The connector's display name.
|
||||||
|
* @type string $description Optional. The connector's description. Default empty string.
|
||||||
|
* @type string|null $logo_url Optional. URL to the connector's logo image. Default null.
|
||||||
|
* @type string $type Required. The connector type. Currently, only 'ai_provider' is supported.
|
||||||
|
* @type array $authentication {
|
||||||
|
* Required. Authentication configuration.
|
||||||
|
*
|
||||||
|
* @type string $method Required. The authentication method: 'api_key' or 'none'.
|
||||||
|
* @type string|null $credentials_url Optional. URL where users can obtain API credentials.
|
||||||
|
* }
|
||||||
|
* @type array $plugin {
|
||||||
|
* Optional. Plugin data for install/activate UI.
|
||||||
|
*
|
||||||
|
* @type string $slug The WordPress.org plugin slug.
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* @return array|null The registered connector data on success, null on failure.
|
||||||
|
*
|
||||||
|
* @phpstan-param Connector $args
|
||||||
|
* @phpstan-return Connector|null
|
||||||
|
*/
|
||||||
|
public function register( string $id, array $args ): ?array {
|
||||||
|
if ( ! preg_match( '/^[a-z0-9_]+$/', $id ) ) {
|
||||||
|
_doing_it_wrong(
|
||||||
|
__METHOD__,
|
||||||
|
__(
|
||||||
|
'Connector ID must contain only lowercase alphanumeric characters and underscores.'
|
||||||
|
),
|
||||||
|
'7.0.0'
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $this->is_registered( $id ) ) {
|
||||||
|
_doing_it_wrong(
|
||||||
|
__METHOD__,
|
||||||
|
/* translators: %s: Connector ID. */
|
||||||
|
sprintf( __( 'Connector "%s" is already registered.' ), esc_html( $id ) ),
|
||||||
|
'7.0.0'
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate required fields.
|
||||||
|
if ( empty( $args['name'] ) || ! is_string( $args['name'] ) ) {
|
||||||
|
_doing_it_wrong(
|
||||||
|
__METHOD__,
|
||||||
|
/* translators: %s: Connector ID. */
|
||||||
|
sprintf( __( 'Connector "%s" requires a non-empty "name" string.' ), esc_html( $id ) ),
|
||||||
|
'7.0.0'
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( empty( $args['type'] ) || ! is_string( $args['type'] ) ) {
|
||||||
|
_doing_it_wrong(
|
||||||
|
__METHOD__,
|
||||||
|
/* translators: %s: Connector ID. */
|
||||||
|
sprintf( __( 'Connector "%s" requires a non-empty "type" string.' ), esc_html( $id ) ),
|
||||||
|
'7.0.0'
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! isset( $args['authentication'] ) || ! is_array( $args['authentication'] ) ) {
|
||||||
|
_doing_it_wrong(
|
||||||
|
__METHOD__,
|
||||||
|
/* translators: %s: Connector ID. */
|
||||||
|
sprintf( __( 'Connector "%s" requires an "authentication" array.' ), esc_html( $id ) ),
|
||||||
|
'7.0.0'
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( empty( $args['authentication']['method'] ) || ! in_array( $args['authentication']['method'], array( 'api_key', 'none' ), true ) ) {
|
||||||
|
_doing_it_wrong(
|
||||||
|
__METHOD__,
|
||||||
|
/* translators: %s: Connector ID. */
|
||||||
|
sprintf( __( 'Connector "%s" authentication method must be "api_key" or "none".' ), esc_html( $id ) ),
|
||||||
|
'7.0.0'
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$connector = array(
|
||||||
|
'name' => $args['name'],
|
||||||
|
'description' => isset( $args['description'] ) && is_string( $args['description'] ) ? $args['description'] : '',
|
||||||
|
'type' => $args['type'],
|
||||||
|
'authentication' => array(
|
||||||
|
'method' => $args['authentication']['method'],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( ! empty( $args['logo_url'] ) && is_string( $args['logo_url'] ) ) {
|
||||||
|
$connector['logo_url'] = $args['logo_url'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( 'api_key' === $args['authentication']['method'] ) {
|
||||||
|
$connector['authentication']['credentials_url'] = $args['authentication']['credentials_url'] ?? null;
|
||||||
|
$connector['authentication']['setting_name'] = "connectors_ai_{$id}_api_key";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! empty( $args['plugin'] ) && is_array( $args['plugin'] ) ) {
|
||||||
|
$connector['plugin'] = $args['plugin'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->registered_connectors[ $id ] = $connector;
|
||||||
|
return $connector;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregisters a connector.
|
||||||
|
*
|
||||||
|
* @since 7.0.0
|
||||||
|
*
|
||||||
|
* @param string $id The connector identifier.
|
||||||
|
* @return array|null The unregistered connector data on success, null on failure.
|
||||||
|
*
|
||||||
|
* @phpstan-return Connector|null
|
||||||
|
*/
|
||||||
|
public function unregister( string $id ): ?array {
|
||||||
|
if ( ! $this->is_registered( $id ) ) {
|
||||||
|
_doing_it_wrong(
|
||||||
|
__METHOD__,
|
||||||
|
/* translators: %s: Connector ID. */
|
||||||
|
sprintf( __( 'Connector "%s" not found.' ), esc_html( $id ) ),
|
||||||
|
'7.0.0'
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$unregistered = $this->registered_connectors[ $id ];
|
||||||
|
unset( $this->registered_connectors[ $id ] );
|
||||||
|
|
||||||
|
return $unregistered;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the list of all registered connectors.
|
||||||
|
*
|
||||||
|
* Do not use this method directly. Instead, use the `wp_get_connectors()` function.
|
||||||
|
*
|
||||||
|
* @since 7.0.0
|
||||||
|
*
|
||||||
|
* @see wp_get_connectors()
|
||||||
|
*
|
||||||
|
* @return array<string, array> The array of registered connectors keyed by connector ID.
|
||||||
|
* @phpstan-return array<string, Connector>
|
||||||
|
*/
|
||||||
|
public function get_all_registered(): array {
|
||||||
|
return $this->registered_connectors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a connector is registered.
|
||||||
|
*
|
||||||
|
* Do not use this method directly. Instead, use the `wp_is_connector_registered()` function.
|
||||||
|
*
|
||||||
|
* @since 7.0.0
|
||||||
|
*
|
||||||
|
* @see wp_is_connector_registered()
|
||||||
|
*
|
||||||
|
* @param string $id The connector identifier.
|
||||||
|
* @return bool True if the connector is registered, false otherwise.
|
||||||
|
*/
|
||||||
|
public function is_registered( string $id ): bool {
|
||||||
|
return isset( $this->registered_connectors[ $id ] );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a registered connector.
|
||||||
|
*
|
||||||
|
* Do not use this method directly. Instead, use the `wp_get_connector()` function.
|
||||||
|
*
|
||||||
|
* @since 7.0.0
|
||||||
|
*
|
||||||
|
* @see wp_get_connector()
|
||||||
|
*
|
||||||
|
* @param string $id The connector identifier.
|
||||||
|
* @return array|null The registered connector data, or null if it is not registered.
|
||||||
|
* @phpstan-return Connector|null
|
||||||
|
*/
|
||||||
|
public function get_registered( string $id ): ?array {
|
||||||
|
if ( ! $this->is_registered( $id ) ) {
|
||||||
|
_doing_it_wrong(
|
||||||
|
__METHOD__,
|
||||||
|
/* translators: %s: Connector ID. */
|
||||||
|
sprintf( __( 'Connector "%s" not found.' ), esc_html( $id ) ),
|
||||||
|
'7.0.0'
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return $this->registered_connectors[ $id ];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the main instance of the registry class.
|
||||||
|
*
|
||||||
|
* @since 7.0.0
|
||||||
|
*
|
||||||
|
* @return WP_Connector_Registry|null The main registry instance, or null if not yet initialized.
|
||||||
|
*/
|
||||||
|
public static function get_instance(): ?self {
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the main instance of the registry class.
|
||||||
|
*
|
||||||
|
* @since 7.0.0
|
||||||
|
* @access private
|
||||||
|
*
|
||||||
|
* @param WP_Connector_Registry $registry The registry instance.
|
||||||
|
*/
|
||||||
|
public static function set_instance( WP_Connector_Registry $registry ): void {
|
||||||
|
if ( ! doing_action( 'init' ) ) {
|
||||||
|
_doing_it_wrong(
|
||||||
|
__METHOD__,
|
||||||
|
__( 'The connector registry instance must be set during the <code>init</code> action.' ),
|
||||||
|
'7.0.0'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self::$instance = $registry;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,244 @@
|
|||||||
use WordPress\AiClient\AiClient;
|
use WordPress\AiClient\AiClient;
|
||||||
use WordPress\AiClient\Providers\Http\DTO\ApiKeyRequestAuthentication;
|
use WordPress\AiClient\Providers\Http\DTO\ApiKeyRequestAuthentication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a connector is registered.
|
||||||
|
*
|
||||||
|
* @since 7.0.0
|
||||||
|
*
|
||||||
|
* @see WP_Connector_Registry::is_registered()
|
||||||
|
*
|
||||||
|
* @param string $id The connector identifier.
|
||||||
|
* @return bool True if the connector is registered, false otherwise.
|
||||||
|
*/
|
||||||
|
function wp_is_connector_registered( string $id ): bool {
|
||||||
|
$registry = WP_Connector_Registry::get_instance();
|
||||||
|
if ( null === $registry ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $registry->is_registered( $id );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a registered connector.
|
||||||
|
*
|
||||||
|
* @since 7.0.0
|
||||||
|
*
|
||||||
|
* @see WP_Connector_Registry::get_registered()
|
||||||
|
*
|
||||||
|
* @param string $id The connector identifier.
|
||||||
|
* @return array|null The registered connector data, or null if not registered.
|
||||||
|
*/
|
||||||
|
function wp_get_connector( string $id ): ?array {
|
||||||
|
$registry = WP_Connector_Registry::get_instance();
|
||||||
|
if ( null === $registry ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $registry->get_registered( $id );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves all registered connectors.
|
||||||
|
*
|
||||||
|
* @since 7.0.0
|
||||||
|
*
|
||||||
|
* @see WP_Connector_Registry::get_all_registered()
|
||||||
|
*
|
||||||
|
* @return array[] An array of registered connectors keyed by connector ID.
|
||||||
|
*/
|
||||||
|
function wp_get_connectors(): array {
|
||||||
|
$registry = WP_Connector_Registry::get_instance();
|
||||||
|
if ( null === $registry ) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $registry->get_all_registered();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves an AI provider logo file path to a URL.
|
||||||
|
*
|
||||||
|
* Converts an absolute file path to a plugin URL. The path must reside within
|
||||||
|
* the plugins or must-use plugins directory.
|
||||||
|
*
|
||||||
|
* @since 7.0.0
|
||||||
|
* @access private
|
||||||
|
*
|
||||||
|
* @param string $path Absolute path to the logo file.
|
||||||
|
* @return string|null The URL to the logo file, or null if the path is invalid.
|
||||||
|
*/
|
||||||
|
function _wp_connectors_resolve_ai_provider_logo_url( string $path ): ?string {
|
||||||
|
if ( ! $path ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$path = wp_normalize_path( $path );
|
||||||
|
|
||||||
|
if ( ! file_exists( $path ) ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mu_plugin_dir = wp_normalize_path( WPMU_PLUGIN_DIR );
|
||||||
|
if ( str_starts_with( $path, $mu_plugin_dir . '/' ) ) {
|
||||||
|
return plugins_url( substr( $path, strlen( $mu_plugin_dir ) ), WPMU_PLUGIN_DIR . '/.' );
|
||||||
|
}
|
||||||
|
|
||||||
|
$plugin_dir = wp_normalize_path( WP_PLUGIN_DIR );
|
||||||
|
if ( str_starts_with( $path, $plugin_dir . '/' ) ) {
|
||||||
|
return plugins_url( substr( $path, strlen( $plugin_dir ) ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
_doing_it_wrong(
|
||||||
|
__FUNCTION__,
|
||||||
|
__( 'Provider logo path must be located within the plugins or must-use plugins directory.' ),
|
||||||
|
'7.0.0'
|
||||||
|
);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the connector registry with default connectors and fires the registration action.
|
||||||
|
*
|
||||||
|
* Creates the registry instance, registers built-in connectors (which cannot be unhooked),
|
||||||
|
* and then fires the `wp_connectors_init` action for plugins to register their own connectors.
|
||||||
|
*
|
||||||
|
* @since 7.0.0
|
||||||
|
* @access private
|
||||||
|
*/
|
||||||
|
function _wp_connectors_init(): void {
|
||||||
|
$registry = new WP_Connector_Registry();
|
||||||
|
WP_Connector_Registry::set_instance( $registry );
|
||||||
|
// Built-in connectors.
|
||||||
|
$defaults = array(
|
||||||
|
'anthropic' => array(
|
||||||
|
'name' => 'Anthropic',
|
||||||
|
'description' => __( 'Text generation with Claude.' ),
|
||||||
|
'type' => 'ai_provider',
|
||||||
|
'plugin' => array(
|
||||||
|
'slug' => 'ai-provider-for-anthropic',
|
||||||
|
),
|
||||||
|
'authentication' => array(
|
||||||
|
'method' => 'api_key',
|
||||||
|
'credentials_url' => 'https://platform.claude.com/settings/keys',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'google' => array(
|
||||||
|
'name' => 'Google',
|
||||||
|
'description' => __( 'Text and image generation with Gemini and Imagen.' ),
|
||||||
|
'type' => 'ai_provider',
|
||||||
|
'plugin' => array(
|
||||||
|
'slug' => 'ai-provider-for-google',
|
||||||
|
),
|
||||||
|
'authentication' => array(
|
||||||
|
'method' => 'api_key',
|
||||||
|
'credentials_url' => 'https://aistudio.google.com/api-keys',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'openai' => array(
|
||||||
|
'name' => 'OpenAI',
|
||||||
|
'description' => __( 'Text and image generation with GPT and Dall-E.' ),
|
||||||
|
'type' => 'ai_provider',
|
||||||
|
'plugin' => array(
|
||||||
|
'slug' => 'ai-provider-for-openai',
|
||||||
|
),
|
||||||
|
'authentication' => array(
|
||||||
|
'method' => 'api_key',
|
||||||
|
'credentials_url' => 'https://platform.openai.com/api-keys',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Merge AI Client registry data on top of defaults.
|
||||||
|
// Registry values (from provider plugins) take precedence over hardcoded fallbacks.
|
||||||
|
$ai_registry = AiClient::defaultRegistry();
|
||||||
|
|
||||||
|
foreach ( $ai_registry->getRegisteredProviderIds() as $connector_id ) {
|
||||||
|
$provider_class_name = $ai_registry->getProviderClassName( $connector_id );
|
||||||
|
$provider_metadata = $provider_class_name::metadata();
|
||||||
|
|
||||||
|
$auth_method = $provider_metadata->getAuthenticationMethod();
|
||||||
|
$is_api_key = null !== $auth_method && $auth_method->isApiKey();
|
||||||
|
|
||||||
|
if ( $is_api_key ) {
|
||||||
|
$credentials_url = $provider_metadata->getCredentialsUrl();
|
||||||
|
$authentication = array(
|
||||||
|
'method' => 'api_key',
|
||||||
|
'credentials_url' => $credentials_url ? $credentials_url : null,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$authentication = array( 'method' => 'none' );
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = $provider_metadata->getName();
|
||||||
|
$description = $provider_metadata->getDescription();
|
||||||
|
$logo_url = $provider_metadata->getLogoPath()
|
||||||
|
? _wp_connectors_resolve_ai_provider_logo_url( $provider_metadata->getLogoPath() )
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if ( isset( $defaults[ $connector_id ] ) ) {
|
||||||
|
// Override fields with non-empty registry values.
|
||||||
|
if ( $name ) {
|
||||||
|
$defaults[ $connector_id ]['name'] = $name;
|
||||||
|
}
|
||||||
|
if ( $description ) {
|
||||||
|
$defaults[ $connector_id ]['description'] = $description;
|
||||||
|
}
|
||||||
|
if ( $logo_url ) {
|
||||||
|
$defaults[ $connector_id ]['logo_url'] = $logo_url;
|
||||||
|
}
|
||||||
|
// Always update auth method; keep existing credentials_url as fallback.
|
||||||
|
$defaults[ $connector_id ]['authentication']['method'] = $authentication['method'];
|
||||||
|
if ( ! empty( $authentication['credentials_url'] ) ) {
|
||||||
|
$defaults[ $connector_id ]['authentication']['credentials_url'] = $authentication['credentials_url'];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$defaults[ $connector_id ] = array(
|
||||||
|
'name' => $name ? $name : ucwords( $connector_id ),
|
||||||
|
'description' => $description ? $description : '',
|
||||||
|
'type' => 'ai_provider',
|
||||||
|
'authentication' => $authentication,
|
||||||
|
'logo_url' => $logo_url,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register all default connectors directly on the registry.
|
||||||
|
foreach ( $defaults as $id => $args ) {
|
||||||
|
$registry->register( $id, $args );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fires when the connector registry is ready for plugins to register connectors.
|
||||||
|
*
|
||||||
|
* Default connectors have already been registered at this point and cannot be
|
||||||
|
* unhooked. Use `$registry->register()` within this action to add new connectors.
|
||||||
|
*
|
||||||
|
* Example usage:
|
||||||
|
*
|
||||||
|
* add_action( 'wp_connectors_init', function ( WP_Connector_Registry $registry ) {
|
||||||
|
* $registry->register(
|
||||||
|
* 'my_custom_ai',
|
||||||
|
* array(
|
||||||
|
* 'name' => __( 'My Custom AI', 'my-plugin' ),
|
||||||
|
* 'description' => __( 'Custom AI provider integration.', 'my-plugin' ),
|
||||||
|
* 'type' => 'ai_provider',
|
||||||
|
* 'authentication' => array(
|
||||||
|
* 'method' => 'api_key',
|
||||||
|
* 'credentials_url' => 'https://example.com/api-keys',
|
||||||
|
* ),
|
||||||
|
* )
|
||||||
|
* );
|
||||||
|
* } );
|
||||||
|
*
|
||||||
|
* @since 7.0.0
|
||||||
|
*
|
||||||
|
* @param WP_Connector_Registry $registry Connector registry instance.
|
||||||
|
*/
|
||||||
|
do_action( 'wp_connectors_init', $registry );
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Masks an API key, showing only the last 4 characters.
|
* Masks an API key, showing only the last 4 characters.
|
||||||
@@ -116,99 +354,8 @@ function _wp_connectors_get_real_api_key( string $option_name, callable $mask_ca
|
|||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
function _wp_connectors_get_connector_settings(): array {
|
function _wp_connectors_get_connector_settings(): array {
|
||||||
$connectors = array(
|
$connectors = wp_get_connectors();
|
||||||
'anthropic' => array(
|
|
||||||
'name' => 'Anthropic',
|
|
||||||
'description' => __( 'Text generation with Claude.' ),
|
|
||||||
'type' => 'ai_provider',
|
|
||||||
'plugin' => array(
|
|
||||||
'slug' => 'ai-provider-for-anthropic',
|
|
||||||
),
|
|
||||||
'authentication' => array(
|
|
||||||
'method' => 'api_key',
|
|
||||||
'credentials_url' => 'https://platform.claude.com/settings/keys',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
'google' => array(
|
|
||||||
'name' => 'Google',
|
|
||||||
'description' => __( 'Text and image generation with Gemini and Imagen.' ),
|
|
||||||
'type' => 'ai_provider',
|
|
||||||
'plugin' => array(
|
|
||||||
'slug' => 'ai-provider-for-google',
|
|
||||||
),
|
|
||||||
'authentication' => array(
|
|
||||||
'method' => 'api_key',
|
|
||||||
'credentials_url' => 'https://aistudio.google.com/api-keys',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
'openai' => array(
|
|
||||||
'name' => 'OpenAI',
|
|
||||||
'description' => __( 'Text and image generation with GPT and Dall-E.' ),
|
|
||||||
'type' => 'ai_provider',
|
|
||||||
'plugin' => array(
|
|
||||||
'slug' => 'ai-provider-for-openai',
|
|
||||||
),
|
|
||||||
'authentication' => array(
|
|
||||||
'method' => 'api_key',
|
|
||||||
'credentials_url' => 'https://platform.openai.com/api-keys',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
$registry = AiClient::defaultRegistry();
|
|
||||||
|
|
||||||
foreach ( $registry->getRegisteredProviderIds() as $connector_id ) {
|
|
||||||
$provider_class_name = $registry->getProviderClassName( $connector_id );
|
|
||||||
$provider_metadata = $provider_class_name::metadata();
|
|
||||||
|
|
||||||
$auth_method = $provider_metadata->getAuthenticationMethod();
|
|
||||||
$is_api_key = null !== $auth_method && $auth_method->isApiKey();
|
|
||||||
|
|
||||||
if ( $is_api_key ) {
|
|
||||||
$credentials_url = $provider_metadata->getCredentialsUrl();
|
|
||||||
$authentication = array(
|
|
||||||
'method' => 'api_key',
|
|
||||||
'credentials_url' => $credentials_url ? $credentials_url : null,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$authentication = array( 'method' => 'none' );
|
|
||||||
}
|
|
||||||
|
|
||||||
$name = $provider_metadata->getName();
|
|
||||||
$description = $provider_metadata->getDescription();
|
|
||||||
|
|
||||||
if ( isset( $connectors[ $connector_id ] ) ) {
|
|
||||||
// Override fields with non-empty registry values.
|
|
||||||
if ( $name ) {
|
|
||||||
$connectors[ $connector_id ]['name'] = $name;
|
|
||||||
}
|
|
||||||
if ( $description ) {
|
|
||||||
$connectors[ $connector_id ]['description'] = $description;
|
|
||||||
}
|
|
||||||
// Always update auth method; keep existing credentials_url as fallback.
|
|
||||||
$connectors[ $connector_id ]['authentication']['method'] = $authentication['method'];
|
|
||||||
if ( ! empty( $authentication['credentials_url'] ) ) {
|
|
||||||
$connectors[ $connector_id ]['authentication']['credentials_url'] = $authentication['credentials_url'];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$connectors[ $connector_id ] = array(
|
|
||||||
'name' => $name ? $name : ucwords( $connector_id ),
|
|
||||||
'description' => $description ? $description : '',
|
|
||||||
'type' => 'ai_provider',
|
|
||||||
'authentication' => $authentication,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ksort( $connectors );
|
ksort( $connectors );
|
||||||
|
|
||||||
// Add setting_name for connectors that use API key authentication.
|
|
||||||
foreach ( $connectors as $connector_id => $connector ) {
|
|
||||||
if ( 'api_key' === $connector['authentication']['method'] ) {
|
|
||||||
$connectors[ $connector_id ]['authentication']['setting_name'] = "connectors_ai_{$connector_id}_api_key";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $connectors;
|
return $connectors;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,12 +429,19 @@ add_filter( 'rest_post_dispatch', '_wp_connectors_validate_keys_in_rest', 10, 3
|
|||||||
* @access private
|
* @access private
|
||||||
*/
|
*/
|
||||||
function _wp_register_default_connector_settings(): void {
|
function _wp_register_default_connector_settings(): void {
|
||||||
|
$ai_registry = AiClient::defaultRegistry();
|
||||||
|
|
||||||
foreach ( _wp_connectors_get_connector_settings() as $connector_id => $connector_data ) {
|
foreach ( _wp_connectors_get_connector_settings() as $connector_id => $connector_data ) {
|
||||||
$auth = $connector_data['authentication'];
|
$auth = $connector_data['authentication'];
|
||||||
if ( 'ai_provider' !== $connector_data['type'] || 'api_key' !== $auth['method'] || empty( $auth['setting_name'] ) ) {
|
if ( 'ai_provider' !== $connector_data['type'] || 'api_key' !== $auth['method'] || empty( $auth['setting_name'] ) ) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip registering the setting if the provider is not in the registry.
|
||||||
|
if ( ! $ai_registry->hasProvider( $connector_id ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$setting_name = $auth['setting_name'];
|
$setting_name = $auth['setting_name'];
|
||||||
register_setting(
|
register_setting(
|
||||||
'connectors',
|
'connectors',
|
||||||
@@ -330,7 +484,7 @@ add_action( 'init', '_wp_register_default_connector_settings', 20 );
|
|||||||
*/
|
*/
|
||||||
function _wp_connectors_pass_default_keys_to_ai_client(): void {
|
function _wp_connectors_pass_default_keys_to_ai_client(): void {
|
||||||
try {
|
try {
|
||||||
$registry = AiClient::defaultRegistry();
|
$ai_registry = AiClient::defaultRegistry();
|
||||||
foreach ( _wp_connectors_get_connector_settings() as $connector_id => $connector_data ) {
|
foreach ( _wp_connectors_get_connector_settings() as $connector_id => $connector_data ) {
|
||||||
if ( 'ai_provider' !== $connector_data['type'] ) {
|
if ( 'ai_provider' !== $connector_data['type'] ) {
|
||||||
continue;
|
continue;
|
||||||
@@ -341,18 +495,22 @@ function _wp_connectors_pass_default_keys_to_ai_client(): void {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$api_key = _wp_connectors_get_real_api_key( $auth['setting_name'], '_wp_connectors_mask_api_key' );
|
if ( ! $ai_registry->hasProvider( $connector_id ) ) {
|
||||||
if ( '' === $api_key || ! $registry->hasProvider( $connector_id ) ) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$registry->setProviderRequestAuthentication(
|
$api_key = _wp_connectors_get_real_api_key( $auth['setting_name'], '_wp_connectors_mask_api_key' );
|
||||||
|
if ( '' === $api_key ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ai_registry->setProviderRequestAuthentication(
|
||||||
$connector_id,
|
$connector_id,
|
||||||
new ApiKeyRequestAuthentication( $api_key )
|
new ApiKeyRequestAuthentication( $api_key )
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch ( Exception $e ) {
|
} catch ( Exception $e ) {
|
||||||
wp_trigger_error( __FUNCTION__, $e->getMessage() );
|
wp_trigger_error( __FUNCTION__, $e->getMessage() );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
add_action( 'init', '_wp_connectors_pass_default_keys_to_ai_client', 20 );
|
add_action( 'init', '_wp_connectors_pass_default_keys_to_ai_client', 20 );
|
||||||
|
|||||||
@@ -539,6 +539,9 @@ add_action( 'parse_request', 'rest_api_loaded' );
|
|||||||
add_action( 'wp_abilities_api_categories_init', 'wp_register_core_ability_categories' );
|
add_action( 'wp_abilities_api_categories_init', 'wp_register_core_ability_categories' );
|
||||||
add_action( 'wp_abilities_api_init', 'wp_register_core_abilities' );
|
add_action( 'wp_abilities_api_init', 'wp_register_core_abilities' );
|
||||||
|
|
||||||
|
// Connectors API.
|
||||||
|
add_action( 'init', '_wp_connectors_init' );
|
||||||
|
|
||||||
// Sitemaps actions.
|
// Sitemaps actions.
|
||||||
add_action( 'init', 'wp_sitemaps_get_server' );
|
add_action( 'init', 'wp_sitemaps_get_server' );
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
*
|
*
|
||||||
* @global string $wp_version
|
* @global string $wp_version
|
||||||
*/
|
*/
|
||||||
$wp_version = '7.0-beta4-61942';
|
$wp_version = '7.0-beta4-61943';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.
|
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.
|
||||||
|
|||||||
@@ -294,6 +294,7 @@ require ABSPATH . WPINC . '/ai-client/adapters/class-wp-ai-client-event-dispatch
|
|||||||
require ABSPATH . WPINC . '/ai-client/class-wp-ai-client-ability-function-resolver.php';
|
require ABSPATH . WPINC . '/ai-client/class-wp-ai-client-ability-function-resolver.php';
|
||||||
require ABSPATH . WPINC . '/ai-client/class-wp-ai-client-prompt-builder.php';
|
require ABSPATH . WPINC . '/ai-client/class-wp-ai-client-prompt-builder.php';
|
||||||
require ABSPATH . WPINC . '/ai-client.php';
|
require ABSPATH . WPINC . '/ai-client.php';
|
||||||
|
require ABSPATH . WPINC . '/class-wp-connector-registry.php';
|
||||||
require ABSPATH . WPINC . '/connectors.php';
|
require ABSPATH . WPINC . '/connectors.php';
|
||||||
require ABSPATH . WPINC . '/class-wp-icons-registry.php';
|
require ABSPATH . WPINC . '/class-wp-icons-registry.php';
|
||||||
require ABSPATH . WPINC . '/widgets.php';
|
require ABSPATH . WPINC . '/widgets.php';
|
||||||
|
|||||||
Reference in New Issue
Block a user