Skip to content

Commit ad1df1f

Browse files
committed
Connectors: Dynamically register providers from WP AI Client registry (#76014)
* 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]> * Connectors: Expose provider settings to the script module Pass provider data (name, description, credentials URL, setting keys) to the `connectors-wp-admin` script module via the `script_module_data` filter, making it available as inline JSON for the frontend to consume. Co-Authored-By: Claude Opus 4.6 <[email protected]> * Connectors: Register connectors dynamically from server-provided data Replace hardcoded per-provider connector components with a single dynamic loop that reads provider data from the script module data JSON tag. Known providers retain their SVG logos via a client-side map; third-party providers from the registry render without a logo. Co-Authored-By: Claude Opus 4.6 <[email protected]> * Update lib/experimental/connectors/default-connectors.php Co-authored-by: Felix Arntz <[email protected]> * Update lib/experimental/connectors/default-connectors.php Co-authored-by: Felix Arntz <[email protected]> * Update lib/experimental/connectors/default-connectors.php Co-authored-by: Felix Arntz <[email protected]> * Update lib/experimental/connectors/default-connectors.php Co-authored-by: Felix Arntz <[email protected]> * Connectors: Use ucwords fallback for empty provider name When a third-party provider from the AI Client registry has no name, fall back to ucwords( $provider_id ) for a reasonable display label. Co-Authored-By: Claude Opus 4.6 <[email protected]> * Connectors: Add authentication_method to provider data structure Expose authentication_method ('api_key' or 'none') in provider settings instead of silently filtering out non-API-key providers. This makes the public-facing interfaces extensible for future authentication methods while still only implementing api_key support for now. - Include all registered providers regardless of auth method - Conditionally generate settings sub-array only for api_key providers - Expose authenticationMethod in script module data for the frontend - Skip non-api_key providers in the frontend registration loop - Rename ProviderConnector to ApiKeyProviderConnector for clarity Co-Authored-By: Claude Opus 4.6 <[email protected]> * Connectors: Add type field to distinguish AI providers from other connectors Add a 'type' field ('ai_provider') to the provider data structure so credentials are only passed to the WP AI Client for AI providers. The frontend also filters by type, only rendering connectors for AI providers. This separates AI providers from future non-AI connectors and makes the intent of the API explicit. Co-Authored-By: Claude Opus 4.6 <[email protected]> * Connectors: Group authentication fields into sub-object and eliminate settings duplication Restructure provider data so credentials_url, setting_name, and method live together in an authentication sub-object rather than as flat top-level fields. Remove the redundant settings array from _gutenberg_get_provider_settings() and move register_setting logic (label, description, sanitize) into the consumer function. Update all PHP consumers to read from authentication directly. On the frontend, change ProviderAuthentication to a discriminated union type for type-safe access after narrowing on method. Co-Authored-By: Claude Opus 4.6 <[email protected]> * Connectors: Rename _gutenberg_get_provider_settings to _gutenberg_get_connector_settings Use the domain term "connector" consistently with the rest of the codebase (settings group, option names, script module filter). Co-Authored-By: Claude Opus 4.6 <[email protected]> * Connectors: Clarify authentication docblock for none method Document that credentials_url and setting_name are only present when method is 'api_key' and absent when method is 'none'. Co-Authored-By: Claude Opus 4.6 <[email protected]> * Connectors: Use URL hostname for help label instead of regex stripping Replace manual regex URL stripping with new URL().hostname for a cleaner, more robust extraction of the domain name used as the help link label. Co-Authored-By: Claude Opus 4.6 <[email protected]> * Connectors: Use type-based namespace for connector names Derive the connector name namespace from data.type instead of hardcoding 'core/'. Sanitize both parts to only allow letters, numbers, and hyphens. This produces names like 'ai-provider/google'. Co-Authored-By: Claude Opus 4.6 <[email protected]> * Connectors: Rename ApiKeyProviderConnector and derive helpLabel internally Rename ApiKeyProviderConnector to ApiKeyConnector and move helpLabel derivation from the registration loop into the component itself, since it already receives helpUrl. Co-Authored-By: Claude Opus 4.6 <[email protected]> * Update lib/experimental/connectors/default-connectors.php Co-authored-by: Felix Arntz <[email protected]> * Update lib/experimental/connectors/default-connectors.php Co-authored-by: Felix Arntz <[email protected]> * Update lib/experimental/connectors/default-connectors.php Co-authored-by: Felix Arntz <[email protected]> * Update lib/experimental/connectors/default-connectors.php Co-authored-by: Felix Arntz <[email protected]> * Update lib/experimental/connectors/default-connectors.php Co-authored-by: Felix Arntz <[email protected]> * Update lib/experimental/connectors/default-connectors.php Co-authored-by: Felix Arntz <[email protected]> * Update lib/experimental/connectors/default-connectors.php Co-authored-by: Felix Arntz <[email protected]> * Connectors: Rename _gutenberg_is_api_key_valid to _gutenberg_is_ai_api_key_valid The function is AI-provider-specific, so the name should reflect that. Co-Authored-By: Claude Opus 4.6 <[email protected]> * Connectors: Move sanitize helper outside the loop Co-Authored-By: Claude Opus 4.6 <[email protected]> * Connectors: Pass plugin data from server instead of deriving slug client-side Hardcode plugin slugs for the three featured AI providers in PHP within a `plugin` sub-object and pass them to the client via script module data. When no plugin data is provided (e.g. dynamically registered providers), the install/activate UI is skipped and the connector assumes the plugin is already active. Co-Authored-By: Claude Opus 4.6 <[email protected]> * Add backport changelog entry for Core PR #11080 Co-Authored-By: Claude Opus 4.6 <[email protected]> * Connectors: Add remove_filter for Core script module data function Ensures the Gutenberg version overrides the equivalent Core function (_wp_connectors_get_connector_script_module_data), consistent with the pattern used by the other connector functions. Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]> Co-authored-by: Felix Arntz <[email protected]> Co-authored-by: gziolo <[email protected]> Co-authored-by: raftaar1191 <[email protected]> Co-authored-by: jorgefilipecosta <[email protected]> Co-authored-by: felixarntz <[email protected]> Source: WordPress/gutenberg@3763f32
1 parent a256e60 commit ad1df1f

8 files changed

Lines changed: 402 additions & 251 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ This is a duplicate of the [WordPress/Gutenberg](https://github.com/WordPress/gu
55
You are currently viewing the version build from the source:
66

77
* Branch: `wp/7.0`
8-
* Commit: [WordPress/Gutenberg@`e1d2ea74b04`](https://github.com/WordPress/gutenberg/commit/e1d2ea74b04e8bb87620a84379b833c564f03f37), [Browse files](https://github.com/WordPress/gutenberg/tree/e1d2ea74b04e8bb87620a84379b833c564f03f37)
8+
* Commit: [WordPress/Gutenberg@`3763f32faee`](https://github.com/WordPress/gutenberg/commit/3763f32faee3ede110074e6539b9be970d1f4983), [Browse files](https://github.com/WordPress/gutenberg/tree/3763f32faee3ede110074e6539b9be970d1f4983)
99

1010
> [!NOTE]
1111
> This readme file has replaced the original version included in the upstream repository.
1212
>
13-
> The [original `README.md` file](https://github.com/WordPress/gutenberg/blob/e1d2ea74b04e8bb87620a84379b833c564f03f37/README.md) file for this commit can be found in the upstream repository.
13+
> The [original `README.md` file](https://github.com/WordPress/gutenberg/blob/3763f32faee3ede110074e6539b9be970d1f4983/README.md) file for this commit can be found in the upstream repository.
1414
1515
## Pupose
1616

build/modules/registry.php

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,16 @@
2222
'path' => 'latex-to-mathml/loader',
2323
'asset' => 'latex-to-mathml/loader.min.asset.php',
2424
),
25-
array(
26-
'id' => '@wordpress/vips/worker',
27-
'path' => 'vips/worker',
28-
'asset' => 'vips/worker.min.asset.php',
29-
),
30-
array(
31-
'id' => '@wordpress/vips/loader',
32-
'path' => 'vips/loader',
33-
'asset' => 'vips/loader.min.asset.php',
34-
),
3525
array(
3626
'id' => '@wordpress/a11y',
3727
'path' => 'a11y/index',
3828
'asset' => 'a11y/index.min.asset.php',
3929
),
30+
array(
31+
'id' => '@wordpress/abilities',
32+
'path' => 'abilities/index',
33+
'asset' => 'abilities/index.min.asset.php',
34+
),
4035
array(
4136
'id' => '@wordpress/interactivity-router',
4237
'path' => 'interactivity-router/index',
@@ -48,9 +43,14 @@
4843
'asset' => 'interactivity-router/full-page.min.asset.php',
4944
),
5045
array(
51-
'id' => '@wordpress/abilities',
52-
'path' => 'abilities/index',
53-
'asset' => 'abilities/index.min.asset.php',
46+
'id' => '@wordpress/vips/worker',
47+
'path' => 'vips/worker',
48+
'asset' => 'vips/worker.min.asset.php',
49+
),
50+
array(
51+
'id' => '@wordpress/vips/loader',
52+
'path' => 'vips/loader',
53+
'asset' => 'vips/loader.min.asset.php',
5454
),
5555
array(
5656
'id' => '@wordpress/core-abilities',

build/routes/connectors-home/content.js

Lines changed: 63 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,11 @@ function useConnectorPlugin({
239239
}, [settingName]);
240240
(0, import_element2.useEffect)(() => {
241241
const checkPluginStatus = async () => {
242+
if (!pluginSlug) {
243+
await fetchApiKey();
244+
setPluginStatus("active");
245+
return;
246+
}
242247
try {
243248
const plugins = await (0, import_api_fetch.default)({
244249
path: "/wp/v2/plugins"
@@ -261,6 +266,9 @@ function useConnectorPlugin({
261266
checkPluginStatus();
262267
}, [pluginSlug, fetchApiKey]);
263268
const installPlugin = async () => {
269+
if (!pluginSlug) {
270+
return;
271+
}
264272
setIsBusy(true);
265273
try {
266274
await (0, import_api_fetch.default)({
@@ -277,6 +285,9 @@ function useConnectorPlugin({
277285
}
278286
};
279287
const activatePlugin = async () => {
288+
if (!pluginSlug) {
289+
return;
290+
}
280291
setIsBusy(true);
281292
try {
282293
await (0, import_api_fetch.default)({
@@ -483,6 +494,23 @@ var GeminiLogo = () => /* @__PURE__ */ React.createElement(
483494
);
484495

485496
// routes/connectors-home/default-connectors.tsx
497+
function getConnectorData() {
498+
try {
499+
const parsed = JSON.parse(
500+
document.getElementById(
501+
"wp-script-module-data-connectors-wp-admin"
502+
)?.textContent ?? ""
503+
);
504+
return parsed?.connectors ?? {};
505+
} catch {
506+
return {};
507+
}
508+
}
509+
var CONNECTOR_LOGOS = {
510+
google: GeminiLogo,
511+
openai: OpenAILogo,
512+
anthropic: ClaudeLogo
513+
};
486514
var ConnectedBadge = () => /* @__PURE__ */ React.createElement(
487515
"span",
488516
{
@@ -498,15 +526,21 @@ var ConnectedBadge = () => /* @__PURE__ */ React.createElement(
498526
},
499527
(0, import_i18n2.__)("Connected")
500528
);
501-
function ProviderConnector({
529+
function ApiKeyConnector({
502530
label,
503531
description,
504532
pluginSlug,
505533
settingName,
506534
helpUrl,
507-
helpLabel,
508535
Logo
509536
}) {
537+
let helpLabel;
538+
try {
539+
if (helpUrl) {
540+
helpLabel = new URL(helpUrl).hostname;
541+
}
542+
} catch {
543+
}
510544
const {
511545
pluginStatus,
512546
isExpanded,
@@ -525,8 +559,8 @@ function ProviderConnector({
525559
return /* @__PURE__ */ React.createElement(
526560
ConnectorItem,
527561
{
528-
className: `connector-item--${pluginSlug}`,
529-
icon: /* @__PURE__ */ React.createElement(Logo, null),
562+
className: pluginSlug ? `connector-item--${pluginSlug}` : void 0,
563+
icon: Logo ? /* @__PURE__ */ React.createElement(Logo, null) : void 0,
530564
name: label,
531565
description,
532566
actionArea: /* @__PURE__ */ React.createElement(import_components3.__experimentalHStack, { spacing: 3, expanded: false }, isConnected && /* @__PURE__ */ React.createElement(ConnectedBadge, null), /* @__PURE__ */ React.createElement(
@@ -559,65 +593,32 @@ function ProviderConnector({
559593
)
560594
);
561595
}
562-
function OpenAIConnector(props) {
563-
return /* @__PURE__ */ React.createElement(
564-
ProviderConnector,
565-
{
566-
...props,
567-
pluginSlug: "ai-provider-for-openai",
568-
settingName: "connectors_ai_openai_api_key",
569-
helpUrl: "https://platform.openai.com",
570-
helpLabel: "platform.openai.com",
571-
Logo: OpenAILogo
572-
}
573-
);
574-
}
575-
function ClaudeConnector(props) {
576-
return /* @__PURE__ */ React.createElement(
577-
ProviderConnector,
578-
{
579-
...props,
580-
pluginSlug: "ai-provider-for-anthropic",
581-
settingName: "connectors_ai_anthropic_api_key",
582-
helpUrl: "https://console.anthropic.com",
583-
helpLabel: "console.anthropic.com",
584-
Logo: ClaudeLogo
585-
}
586-
);
587-
}
588-
function GeminiConnector(props) {
589-
return /* @__PURE__ */ React.createElement(
590-
ProviderConnector,
591-
{
592-
...props,
593-
pluginSlug: "ai-provider-for-google",
594-
settingName: "connectors_ai_google_api_key",
595-
helpUrl: "https://aistudio.google.com",
596-
helpLabel: "aistudio.google.com",
597-
Logo: GeminiLogo
598-
}
599-
);
600-
}
601596
function registerDefaultConnectors() {
602-
registerConnector("core/openai", {
603-
label: (0, import_i18n2.__)("OpenAI"),
604-
description: (0, import_i18n2.__)(
605-
"Text, image, and code generation with GPT and DALL-E."
606-
),
607-
render: OpenAIConnector
608-
});
609-
registerConnector("core/claude", {
610-
label: (0, import_i18n2.__)("Claude"),
611-
description: (0, import_i18n2.__)("Writing, research, and analysis with Claude."),
612-
render: ClaudeConnector
613-
});
614-
registerConnector("core/gemini", {
615-
label: (0, import_i18n2.__)("Gemini"),
616-
description: (0, import_i18n2.__)(
617-
"Content generation, translation, and vision with Google's Gemini."
618-
),
619-
render: GeminiConnector
620-
});
597+
const connectors = getConnectorData();
598+
const sanitize = (s) => s.replace(/[^a-z0-9-]/gi, "-");
599+
for (const [connectorId, data] of Object.entries(connectors)) {
600+
const { authentication } = data;
601+
if (data.type !== "ai_provider" || authentication.method !== "api_key") {
602+
continue;
603+
}
604+
const connectorName = `${sanitize(data.type)}/${sanitize(
605+
connectorId
606+
)}`;
607+
registerConnector(connectorName, {
608+
label: data.name,
609+
description: data.description,
610+
render: (props) => /* @__PURE__ */ React.createElement(
611+
ApiKeyConnector,
612+
{
613+
...props,
614+
pluginSlug: data.plugin?.slug,
615+
settingName: authentication.settingName,
616+
helpUrl: authentication.credentialsUrl ?? void 0,
617+
Logo: CONNECTOR_LOGOS[connectorId]
618+
}
619+
)
620+
});
621+
}
621622
}
622623

623624
// routes/lock-unlock.ts
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<?php return array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-private-apis'), 'module_dependencies' => array(array('id' => '@wordpress/connectors', 'import' => 'static'), array('id' => '@wordpress/route', 'import' => 'static')), 'version' => '20dbb1bd0fa68fa7cd2a');
1+
<?php return array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-private-apis'), 'module_dependencies' => array(array('id' => '@wordpress/connectors', 'import' => 'static'), array('id' => '@wordpress/route', 'import' => 'static')), 'version' => 'd6eeeed2a53ac1142e3c');

0 commit comments

Comments
 (0)