Skip to content

Add WP AI Client#10881

Closed
JasonTheAdams wants to merge 43 commits intoWordPress:trunkfrom
JasonTheAdams:add/wp-ai-client
Closed

Add WP AI Client#10881
JasonTheAdams wants to merge 43 commits intoWordPress:trunkfrom
JasonTheAdams:add/wp-ai-client

Conversation

@JasonTheAdams
Copy link
Member

@JasonTheAdams JasonTheAdams commented Feb 7, 2026

Trac ticket: https://core.trac.wordpress.org/ticket/64591
Merge Proposal: https://make.wordpress.org/core/2026/02/03/proposal-for-merging-wp-ai-client-into-wordpress-7-0

Summary

Adds a provider-agnostic AI Client, enabling developers to interact with generative AI services through a single, fluent API — without needing to know which provider is configured.

This PR includes three layers:

  • PHP AI Client SDK (php-ai-client): The upstream SDK from WordPress/php-ai-client, bundled into wp-includes/php-ai-client/ with all third-party dependencies (PSR interfaces, HTTPlug) scoped to WordPress\AiClientDependencies\* to avoid conflicts with plugins shipping their own versions.

  • Import tooling (tools/php-ai-client/): An installer script and PHP-Scoper configuration that fetches, scopes, and reorganizes the SDK for bundling. Running bash tools/php-ai-client/installer.sh reproduces the bundled output deterministically.

  • WP AI Client (ai-client-utils/, ai-client.php): The WordPress integration layer. This provides minimal PSR-7/PSR-17 implementations backed by string buffers and wp_parse_url(), an HTTP client adapter that routes requests through wp_remote_request(), a discovery strategy so the SDK automatically finds these implementations, and an event dispatcher that bridges PSR-14 events to WordPress hooks.

The public API is a single function:

$summary = wp_ai_client_prompt( 'Summarize this post' )
    ->with_text( $post->post_content )
    ->generate_text();

WP_AI_Client_Prompt_Builder wraps the SDK's fluent builder with WordPress conventions — snake_case methods, WP_Error returns instead of exceptions, and using_abilities() for connecting the Abilities API to AI function calling.

The wp_ai_client_prevent_prompt filter gives site owners and plugins centralized control over AI availability. When a prompt is prevented, generating methods return WP_Error while is_supported_* methods return false — giving plugin developers a graceful way to hide AI features entirely when AI is not available.

This gives plugin and theme developers a stable, provider-neutral way to add AI features without bundling their own HTTP clients or managing provider-specific SDKs.

Testing Instructions

This is strictly the core work and doesn't include a built-in UI for setting up provider credentials. While it is possible to add a provider and set up the credentials, the easiest way to test this is using the stacked settings PR: #10904. That PR has testing instructions and links to provider plugins you can install.

Use of AI Tools

This is a compilation of work from the PHP AI Client and WP AI Client repositories, with some changes made in porting to core. Claude Code was used in both the original development of those packages as well as the porting over and creation of the tooling. All code was generated by Claude Code and reviewed by myself and @felixarntz.


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

@github-actions
Copy link

github-actions bot commented Feb 7, 2026

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props jason_the_adams, desrosj, flixos90, dkotter, jorgefilipecosta, peterwilsoncc, johnbillion, swissspidy.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@github-actions
Copy link

github-actions bot commented Feb 7, 2026

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • The Plugin and Theme Directories cannot be accessed within Playground.
  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

@johnbillion
Copy link
Member

Might any of these 3p libraries potentially be used outside of the AI client in the future? ie. they might benefit from being in the higher level WordPress namespace rather than WordPress\AiClient.

Extenders might refer to that 3p code as soon as it's in, so we can't change the namespace at a later date.

@swissspidy
Copy link
Member

FYI there are 50+ Method ReflectionProperty::setAccessible() is deprecated since 8.5, as it has no effect since PHP 8.1 deprecation notices introduced by this PR.

@JasonTheAdams
Copy link
Member Author

I was fixing that as you posted the comment, @swissspidy. 😄

Copy link

@dkotter dkotter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did a fairly quick review here (mostly ignoring everything in the php-ai-client directory as I know that's already been reviewed and tested upstream) and have left a few minor comments.

I was mostly curious to see what, if any, changes would need to be made for those that have already been building on top of the WP AI Client (like we're doing in the AI Experiments plugin). Or if there's anything here that's missing that we'd need to ensure those integrations work (beyond the obvious exclusions in this PR like the provider credentials settings screen).

Not surprisingly, seems there's only super minimal changes we'd make, namely:

  1. Removing the inclusion of the WP AI Client
  2. Remove the loading and initialization of the client
  3. Changing from AI_Client::prompt_with_wp_error to wp_ai_client_prompt

But looks like everything else we're doing will continue to work, which is great.

We've built many features on top of the (WP) AI Client now in AI Experiments and it's all been working great, so would love to see this merged into core to make it even easier to build on.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm assuming this is probably due to the scoping tool we run here but noticing all the extra line breaks are removed from the php-ai-client files. This may be expected/desired, just makes it a bit harder to read through the code

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's due to the tool indeed. Potentially something we can fix in the future, though this code should never be edited in Core anyway, similar to e.g. the Requests library or all the block PHP files that are controlled via Gutenberg.

@JasonTheAdams
Copy link
Member Author

Might any of these 3p libraries potentially be used outside of the AI client in the future? ie. they might benefit from being in the higher level WordPress namespace rather than WordPress\AiClient.

Extenders might refer to that 3p code as soon as it's in, so we can't change the namespace at a later date.

I'm certainly open to this! What libraries are you thinking of, specifically?

@JasonTheAdams
Copy link
Member Author

Thanks for looking over this, @dkotter! I've resolved most of what you raised. I'm temporarily shifting to get the settings screen PR in place so we can start testing!

@JasonTheAdams JasonTheAdams mentioned this pull request Feb 11, 2026
5 tasks
This adjusts the test coverage workflow to run for any change to a PHP file in a pull request.

This is not meant to be included in the final merge, but aims to help gauge the test coverage changes this merge would result in.
@desrosj
Copy link
Member

desrosj commented Feb 12, 2026

So I updated the test coverage workflow to run every time this PR is updated (please revert this before a final commit to SVN. In the status checks, you'll find two codecov lines.

Surprisingly, it seems that 96%+ of this PR is actually covered by tests (which excludes the bundled library parts of this PR based on the PHPUnit configuration file changes), and this PR actually increases overall test coverages by roughly 2-tenths of a percent. For easier reference, you can view the coverage report for the PR here.

Chatting it through with @aaronjorbin, @jeffpaul, and @felixarntz just now, this seems to make sense. The wp-ai-client is essentially a pass through, so it's not difficult to have "coverage". But it's unlikely that a high percentage of the underlying PHP SDK is being tested through the wp-ai-client tests here.

I do feel a bit better about the test coverage part of this. I think this will need to be a blended approach. There should be a high level of coverage for the non-bundled library code here combined with the test coverage within the PHP SDK library. @aaronjorbin has created an issue to follow up with adding test coverage reporting to the php-ai-client repository to track and confirm this.

@peterwilsoncc
Copy link
Contributor

@JasonTheAdams Are you able to add some testing notes to the PR description. I'm having trouble finguring out what needs to be done to add providers to the settings screen with the AI Experiments plugin installed. The menu page is available but without any form fields.

Screenshot 2026-02-13 at 8 03 56 am

@jorgefilipecosta
Copy link
Member

jorgefilipecosta commented Feb 20, 2026

@jorgefilipecosta

left a small note as a reminder that probably should be changed before merging).

Not sure which one you mean - can you clarify? Apologies if I missed it.

Sorry it was just the note you left #10881 (comment), I should have linked.

@jorgefilipecosta
Copy link
Member

jorgefilipecosta commented Feb 20, 2026

@jorgefilipecosta

left a small note as a reminder that probably should be changed before merging).

Not sure which one you mean - can you clarify? Apologies if I missed it.

Sorry it was just the note you left #10881 (comment), I should have linked.

It said: TODO: Revert before commit (just leaving this here as a reminder and was on file github/workflows/test-coverage.yml.

@felixarntz
Copy link
Member

Based on some of the feedback so far, @JasonTheAdams and I found a way to simplify the WordPress-owned code in terms of those PSR7 and PSR-17 classes - they are boilerplate code needed to work with PSR-7 and PSR-17 standards, but they can be part of WordPress/php-ai-client itself, as there's nothing WordPress-y about them.

This is being handled in WordPress/php-ai-client#208. We'll publish a new release after and pull that in here.

Copy link
Member

@felixarntz felixarntz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JasonTheAdams Awesome work on this - and extra thanks for going through this with me to push it over the finish line!

@gziolo
Copy link
Member

gziolo commented Feb 20, 2026

Awesome work. I'm catching up after a longer break and I would like to offer my help to bring the remaining work to the finish line. I see that this PR was committed by @felixarntz in 52e3c30 (svn changeset https://core.trac.wordpress.org/changeset/61700).

@gziolo gziolo closed this Feb 20, 2026
@gziolo
Copy link
Member

gziolo commented Feb 20, 2026

I have minor additional feedback from my reviews sessions with Claude Code:

src/wp-includes/php-ai-client/src/polyfills.php appears to be dead code. It is never loaded — the generated autoloader only handles class autoloading, and neither wp-settings.php nor any other file requires it. The four functions it polyfills (array_is_list, str_starts_with, str_contains, str_ends_with) are already provided by WordPress Core in src/wp-includes/compat.php, which loads during bootstrap. I suggest adding rm -f "$TARGET_DIR/src/polyfills.php" (or equivalent) to tools/php-ai-client/installer.sh after the third-party cleanup step so it's stripped during the library update.

@gziolo
Copy link
Member

gziolo commented Feb 20, 2026

Really like how using_abilities() on the prompt builder integrates the Abilities API into function calling — the conversion from WP_Ability to FunctionDeclaration via the wpab__ naming convention is clean, and it makes exposing WordPress capabilities to AI models feel natural and consistent.

On the response side, my understanding from studying the code is that execute_ability(), execute_abilities(), and has_ability_calls() on WP_AI_Client_Ability_Function_Resolver are not referenced anywhere in Core outside their own test file. The unit test coverage is solid, so the logic is well-validated — but I'm curious about the intended usage pattern. Is the expectation that the consumer (plugin or Core feature) explicitly builds the tool-calling loop, something like (AI generated example):

$result = $builder->using_abilities( 'core/get-site-info' )->generate_text_result();

while ( WP_AI_Client_Ability_Function_Resolver::has_ability_calls( $result->getMessage() ) ) {
    $response = WP_AI_Client_Ability_Function_Resolver::execute_abilities( $result->getMessage() );
    $result = $builder->with_history( $result->getMessage(), $response )->generate_text_result();
}

Or is there a plan to provide a higher-level helper that handles this loop automatically? Would be great to understand the design vision here.

@gziolo
Copy link
Member

gziolo commented Feb 20, 2026

One more minor finding for consideration. The WP_AI_Client_Prompt_Builder wraps SDK exceptions into WP_Error through the __call() magic method, which is great for WordPress developers. However, this wrapping doesn't cover the constructor. When invalid input is passed to wp_ai_client_prompt(), the SDK's PromptBuilder constructor throws an InvalidArgumentException directly:

// This throws InvalidArgumentException instead of returning a WP_Error from generate_text().
wp_ai_client_prompt( '   ' )->generate_text();

The same applies to other invalid constructor inputs like wp_ai_client_prompt( array() ) or wp_ai_client_prompt( 123 ) — these are already covered by tests in wpAiClientPromptBuilder.php in test_parse_message_empty_string_returns_wp_error which acknowledge the gap with try/catch blocks.

A possible fix would be wrapping the new prompt builder call in the constructor with a try/catch, storing the exception as a WP_Error in $this->error, so it surfaces consistently through the fluent chain when a generating method is called.

Here's a test case that documents the current behavior:

/**
 * Test that wp_ai_client_prompt() with an empty string throws an InvalidArgumentException.
 *
 * The SDK's PromptBuilder constructor throws immediately for empty strings,
 * bypassing the WP_Error wrapping provided by __call(). This means invalid
 * input to wp_ai_client_prompt() surfaces as an uncaught exception rather
 * than a WP_Error from a generating method.
 *
 * @ticket 64591
 */
public function test_empty_string_throws_exception() {
      $this->expectException( InvalidArgumentException::class );
      $this->expectExceptionMessage( 'Cannot create a message from an empty string' );

      wp_ai_client_prompt( '   ' );
}

@jorgefilipecosta
Copy link
Member

Awesome work moving this big project forward @JasonTheAdams and @felixarntz! Thank you for the way you iterated fast and addressed the comments during the review process.

@peterwilsoncc
Copy link
Contributor

@felixarntz

This is not quite correct - not sure if it's a misunderstanding or you just pasted the wrong link :)

Yeah, wrong link. I was referring to WordPress/php-ai-client -- I had a lot of links open doing archeology.

Mainly posting this for logging purposes, even though the PR has been merged.

@JasonTheAdams
Copy link
Member Author

JasonTheAdams commented Feb 23, 2026

Thanks, @gziolo!

Or is there a plan to provide a higher-level helper that handles this loop automatically? Would be great to understand the design vision here.

To start we decided not to get too deep into agent-like behavior, as we'd tossed around an ->auto_resolve_abiliites() type of method, which could automatically handle AI function calls tied to abilities. I still think it's a cool idea, but for now we decided to expose the system that would handle that, giving advanced dev cases a clear path forward when using using_abilities().

One more minor finding for consideration. The WP_AI_Client_Prompt_Builder wraps SDK exceptions into WP_Error through the __call() magic method, which is great for WordPress developers. However, this wrapping doesn't cover the constructor. When invalid input is passed to wp_ai_client_prompt(), the SDK's PromptBuilder constructor throws an InvalidArgumentException directly:

That's a fair point! Typically, I'd say this is appropriate behavior, but given that all the constructor's parameter does is the same as with_text(), but that method stores a WP_Error, then it's odd not to be consistent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants