Skip to content

Commit cd56c68

Browse files
gzioloclaude
andcommitted
Connectors: Dynamically register providers from WP AI Client registry
Polyfills WordPress/wordpress-develop#11080 for the Gutenberg plugin: - Expand `_gutenberg_get_provider_settings()` to dynamically fetch registered providers from the AI Client registry, in addition to the three hardcoded featured providers (Gemini, OpenAI, Claude). - Restructure the return value to be keyed by provider ID with `name`, `description`, `credentials_url` at the top level and `settings` as a nested array. - Filter out providers whose authentication method is not `api_key`. - Update all consumer functions to use the new structure. Co-Authored-By: Claude Opus 4.6 <[email protected]>
1 parent 7a891e8 commit cd56c68

1 file changed

Lines changed: 129 additions & 59 deletions

File tree

lib/experimental/connectors/default-connectors.php

Lines changed: 129 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -82,50 +82,114 @@ function _gutenberg_get_real_api_key( string $option_name, callable $mask_callba
8282
*
8383
* @access private
8484
*
85-
* @return array<string, array{provider: string, label: string, description: string, mask: callable, sanitize: callable}> Provider settings keyed by setting name.
85+
* @return array {
86+
* Provider settings keyed by provider ID.
87+
*
88+
* @type array ...$0 {
89+
* Data for a single provider.
90+
*
91+
* @type string $name The provider's display name.
92+
* @type string $description The provider's description.
93+
* @type string|null $credentials_url URL where users can obtain API credentials.
94+
* @type array $settings {
95+
* Settings keyed by setting name.
96+
*
97+
* @type array ...$0 {
98+
* Data for a single setting.
99+
*
100+
* @type string $label The setting label.
101+
* @type string $description The setting description.
102+
* @type callable $sanitize Callback to sanitize the input value.
103+
* }
104+
* }
105+
* }
106+
* }
86107
*/
87108
function _gutenberg_get_provider_settings(): array {
88109
$providers = array(
89110
'google' => array(
90-
'name' => 'Google',
111+
'name' => 'Gemini',
112+
'description' => __( 'Content generation, translation, and vision with Google\'s Gemini.', 'gutenberg' ),
113+
'credentials_url' => 'https://aistudio.google.com/',
91114
),
92115
'openai' => array(
93-
'name' => 'OpenAI',
116+
'name' => 'OpenAI',
117+
'description' => __( 'Text, image, and code generation with GPT and DALL-E.', 'gutenberg' ),
118+
'credentials_url' => 'https://platform.openai.com/',
94119
),
95120
'anthropic' => array(
96-
'name' => 'Anthropic',
121+
'name' => 'Claude',
122+
'description' => __( 'Writing, research, and analysis with Claude.', 'gutenberg' ),
123+
'credentials_url' => 'https://console.anthropic.com/',
97124
),
98125
);
99126

127+
$registry = \WordPress\AiClient\AiClient::defaultRegistry();
128+
129+
foreach ( $registry->getRegisteredProviderIds() as $provider_id ) {
130+
$provider_class_name = $registry->getProviderClassName( $provider_id );
131+
$provider_metadata = $provider_class_name::metadata();
132+
133+
$auth_method = $provider_metadata->getAuthenticationMethod();
134+
if ( null === $auth_method || ! $auth_method->isApiKey() ) {
135+
continue;
136+
}
137+
138+
$registry_data = array_filter(
139+
array(
140+
'name' => $provider_metadata->getName(),
141+
'credentials_url' => $provider_metadata->getCredentialsUrl(),
142+
)
143+
);
144+
145+
if ( isset( $providers[ $provider_id ] ) ) {
146+
// Merge non-empty registry data over hardcoded fallbacks.
147+
$providers[ $provider_id ] = array_merge( $providers[ $provider_id ], $registry_data );
148+
} else {
149+
$providers[ $provider_id ] = array_merge(
150+
array(
151+
'name' => '',
152+
'description' => '',
153+
'credentials_url' => null,
154+
),
155+
$registry_data
156+
);
157+
}
158+
}
159+
100160
$provider_settings = array();
101161
foreach ( $providers as $provider => $data ) {
102162
$setting_name = "connectors_ai_{$provider}_api_key";
103163

104-
$provider_settings[ $setting_name ] = array(
105-
'provider' => $provider,
106-
'label' => sprintf(
107-
/* translators: %s: AI provider name. */
108-
__( '%s API Key', 'gutenberg' ),
109-
$data['name']
110-
),
111-
'description' => sprintf(
112-
/* translators: %s: AI provider name. */
113-
__( 'API key for the %s AI provider.', 'gutenberg' ),
114-
$data['name']
115-
),
116-
'mask' => '_gutenberg_mask_api_key',
117-
'sanitize' => static function ( string $value ) use ( $provider ): string {
118-
$value = sanitize_text_field( $value );
119-
if ( '' === $value ) {
120-
return $value;
121-
}
164+
$provider_settings[ $provider ] = array(
165+
'name' => $data['name'],
166+
'description' => $data['description'],
167+
'credentials_url' => $data['credentials_url'],
168+
'settings' => array(
169+
$setting_name => array(
170+
'label' => sprintf(
171+
/* translators: %s: AI provider name. */
172+
__( '%s API Key', 'gutenberg' ),
173+
$data['name']
174+
),
175+
'description' => sprintf(
176+
/* translators: %s: AI provider name. */
177+
__( 'API key for the %s AI provider.', 'gutenberg' ),
178+
$data['name']
179+
),
180+
'sanitize' => static function ( string $value ) use ( $provider ): string {
181+
$value = sanitize_text_field( $value );
182+
if ( '' === $value ) {
183+
return $value;
184+
}
122185

123-
$valid = _gutenberg_is_api_key_valid( $value, $provider );
124-
return true === $valid ? $value : '';
125-
},
186+
$valid = _gutenberg_is_api_key_valid( $value, $provider );
187+
return true === $valid ? $value : '';
188+
},
189+
),
190+
),
126191
);
127192
}
128-
129193
return $provider_settings;
130194
}
131195

@@ -169,18 +233,20 @@ function _gutenberg_validate_connector_keys_in_rest( WP_REST_Response $response,
169233
return $response;
170234
}
171235

172-
foreach ( _gutenberg_get_provider_settings() as $setting_name => $config ) {
173-
if ( ! in_array( $setting_name, $requested, true ) ) {
174-
continue;
175-
}
236+
foreach ( _gutenberg_get_provider_settings() as $provider => $provider_data ) {
237+
foreach ( $provider_data['settings'] as $setting_name => $config ) {
238+
if ( ! in_array( $setting_name, $requested, true ) ) {
239+
continue;
240+
}
176241

177-
$real_key = _gutenberg_get_real_api_key( $setting_name, $config['mask'] );
178-
if ( '' === $real_key ) {
179-
continue;
180-
}
242+
$real_key = _gutenberg_get_real_api_key( $setting_name, '_gutenberg_mask_api_key' );
243+
if ( '' === $real_key ) {
244+
continue;
245+
}
181246

182-
if ( true !== _gutenberg_is_api_key_valid( $real_key, $config['provider'] ) ) {
183-
$data[ $setting_name ] = 'invalid_key';
247+
if ( true !== _gutenberg_is_api_key_valid( $real_key, $provider ) ) {
248+
$data[ $setting_name ] = 'invalid_key';
249+
}
184250
}
185251
}
186252

@@ -200,20 +266,22 @@ function _gutenberg_register_default_connector_settings(): void {
200266
return;
201267
}
202268

203-
foreach ( _gutenberg_get_provider_settings() as $setting_name => $config ) {
204-
register_setting(
205-
'connectors',
206-
$setting_name,
207-
array(
208-
'type' => 'string',
209-
'label' => $config['label'],
210-
'description' => $config['description'],
211-
'default' => '',
212-
'show_in_rest' => true,
213-
'sanitize_callback' => $config['sanitize'],
214-
)
215-
);
216-
add_filter( "option_{$setting_name}", $config['mask'] );
269+
foreach ( _gutenberg_get_provider_settings() as $provider_data ) {
270+
foreach ( $provider_data['settings'] as $setting_name => $config ) {
271+
register_setting(
272+
'connectors',
273+
$setting_name,
274+
array(
275+
'type' => 'string',
276+
'label' => $config['label'],
277+
'description' => $config['description'],
278+
'default' => '',
279+
'show_in_rest' => true,
280+
'sanitize_callback' => $config['sanitize'],
281+
)
282+
);
283+
add_filter( "option_{$setting_name}", '_gutenberg_mask_api_key' );
284+
}
217285
}
218286
}
219287
remove_action( 'init', '_wp_register_default_connector_settings' );
@@ -231,16 +299,18 @@ function _gutenberg_pass_default_connector_keys_to_ai_client(): void {
231299

232300
try {
233301
$registry = \WordPress\AiClient\AiClient::defaultRegistry();
234-
foreach ( _gutenberg_get_provider_settings() as $setting_name => $config ) {
235-
$api_key = _gutenberg_get_real_api_key( $setting_name, $config['mask'] );
236-
if ( '' === $api_key || ! $registry->hasProvider( $config['provider'] ) ) {
237-
continue;
238-
}
302+
foreach ( _gutenberg_get_provider_settings() as $provider => $provider_data ) {
303+
foreach ( $provider_data['settings'] as $setting_name => $config ) {
304+
$api_key = _gutenberg_get_real_api_key( $setting_name, '_gutenberg_mask_api_key' );
305+
if ( '' === $api_key || ! $registry->hasProvider( $provider ) ) {
306+
continue;
307+
}
239308

240-
$registry->setProviderRequestAuthentication(
241-
$config['provider'],
242-
new \WordPress\AiClient\Providers\Http\DTO\ApiKeyRequestAuthentication( $api_key )
243-
);
309+
$registry->setProviderRequestAuthentication(
310+
$provider,
311+
new \WordPress\AiClient\Providers\Http\DTO\ApiKeyRequestAuthentication( $api_key )
312+
);
313+
}
244314
}
245315
} catch ( Exception $e ) {
246316
wp_trigger_error( __FUNCTION__, $e->getMessage() );

0 commit comments

Comments
 (0)