[wp-trac] [WordPress Trac] #64938: wp_ai_client_prompt() does not pass the event dispatcher to the SDK PromptBuilder — before/after generate actions never fire

WordPress Trac noreply at wordpress.org
Tue Mar 24 09:16:23 UTC 2026


#64938: wp_ai_client_prompt() does not pass the event dispatcher to the SDK
PromptBuilder — before/after generate actions never fire
-------------------------+-------------------------------------------------
 Reporter:  PerS         |      Owner:  (none)
     Type:  defect       |     Status:  new
  (bug)                  |
 Priority:  normal       |  Milestone:  Awaiting Review
Component:  AI           |    Version:  trunk
 Severity:  normal       |   Keywords:  event-dispatcher has-patch ai-
  Focuses:               |  client
-------------------------+-------------------------------------------------
 === Summary ===

 `wp_ai_client_prompt()` in `wp-includes/ai-client.php` creates
 `WP_AI_Client_Prompt_Builder` without passing the global event dispatcher.
 As a result, the `wp_ai_client_before_generate_result` and
 `wp_ai_client_after_generate_result` actions **never fire**, even though
 `wp-settings.php` correctly configures a dispatcher via
 `AiClient::setEventDispatcher()`.

 === Steps to Reproduce ===

  1. Register callbacks on the before/after actions:

 {{{#!php
 add_action( 'wp_ai_client_before_generate_result', function ( $event ) {
     error_log( 'before_generate fired' );
 }, 10, 1 );

 add_action( 'wp_ai_client_after_generate_result', function ( $event ) {
     $usage = $event->getResult()->getTokenUsage();
     error_log( sprintf( 'after_generate fired — %d tokens',
 $usage->getTotalTokens() ) );
 }, 10, 1 );
 }}}

  2. Trigger an AI prompt:

 {{{#!php
 $result = wp_ai_client_prompt( 'Summarize this text.' )
     ->set_model( 'gpt-4.1' )
     ->as_text()
     ->get();
 }}}

  3. Check the error log.

 === Expected Result ===

 Both `before_generate` and `after_generate` should fire. The log should
 show:

 {{{
 before_generate fired
 after_generate fired — 42 tokens
 }}}

 === Actual Result ===

 Neither action fires. The AI request itself completes successfully (the
 return value is correct), but no events are dispatched.

 The `wp_ai_client_prevent_prompt` filter **does** fire correctly, because
 it is called directly in `wp_ai_client_prompt()` via `apply_filters()`.

 === Root Cause ===

 In `wp-includes/ai-client.php`, the builder is constructed with only two
 arguments:

 {{{#!php
 // wp-includes/ai-client.php — wp_ai_client_prompt()
 $builder = new WP_AI_Client_Prompt_Builder( AiClient::defaultRegistry(),
 $prompt );
 }}}

 The underlying SDK `PromptBuilder` constructor accepts an optional third
 parameter:

 {{{#!php
 // vendor/wordpress/ai-client/src/PromptBuilder.php
 public function __construct(
     ProviderRegistryInterface $registry,
     string $prompt,
     ?EventDispatcherInterface $eventDispatcher = null,  // <-- never
 passed
 )
 }}}

 Because `$eventDispatcher` stays `null`, the SDK's internal
 `dispatchEvent()` method silently skips all event dispatching:

 {{{#!php
 // PromptBuilder.php
 private function dispatchEvent( object $event ): void {
     if ( null !== $this->eventDispatcher ) {
         $this->eventDispatcher->dispatch( $event );
     }
 }
 }}}

 Meanwhile, `wp-settings.php` **does** configure a global dispatcher:

 {{{#!php
 // wp-settings.php
 AiClient::setEventDispatcher( new WP_AI_Client_Event_Dispatcher() );
 }}}

 This dispatcher is available via `AiClient::getEventDispatcher()`, but it
 is never plumbed into the `PromptBuilder`.

 === Proposed Fix ===

 Pass the event dispatcher as the third argument to
 `WP_AI_Client_Prompt_Builder` (which should forward it to the SDK
 `PromptBuilder`):

 {{{#!diff
 --- a/wp-includes/ai-client.php
 +++ b/wp-includes/ai-client.php
 @@ wp_ai_client_prompt()
 - $builder = new WP_AI_Client_Prompt_Builder( AiClient::defaultRegistry(),
 $prompt );
 + $builder = new WP_AI_Client_Prompt_Builder( AiClient::defaultRegistry(),
 $prompt, AiClient::getEventDispatcher() );
 }}}

 If `WP_AI_Client_Prompt_Builder.__construct()` does not yet accept or
 forward the dispatcher, it needs to be updated to pass it through to the
 SDK `PromptBuilder`.

 === Current Workaround ===

 Plugins can inject the dispatcher via reflection at an early priority on
 `wp_ai_client_prevent_prompt`. The `prevent_prompt` filter receives a
 clone of the builder, but since `WP_AI_Client_Prompt_Builder` has no
 `__clone()` method, the clone shares the same inner SDK `PromptBuilder`
 object — modifying it propagates to the original.

 {{{#!php
 use WordPress\AiClient\AiClient;

 add_filter( 'wp_ai_client_prevent_prompt', function ( bool $prevent,
 WP_AI_Client_Prompt_Builder $builder ): bool {
     try {
         $wp_ref       = new ReflectionClass( $builder );
         $builder_prop = $wp_ref->getProperty( 'builder' );
         $sdk_builder  = $builder_prop->getValue( $builder );

         $sdk_ref   = new ReflectionClass( $sdk_builder );
         $disp_prop = $sdk_ref->getProperty( 'eventDispatcher' );
         $current   = $disp_prop->getValue( $sdk_builder );

         if ( null === $current ) {
             $dispatcher = AiClient::getEventDispatcher();
             if ( null !== $dispatcher ) {
                 $disp_prop->setValue( $sdk_builder, $dispatcher );
             }
         }
     } catch ( \Throwable ) {
         // Reflection failed — don't break the request.
     }

     return $prevent;
 }, 5, 2 );
 }}}

 This workaround is safe: once core passes the dispatcher properly, the
 `null === $current` check causes the reflection to be skipped entirely.

 === Impact ===

 Any plugin relying on `wp_ai_client_before_generate_result` or
 `wp_ai_client_after_generate_result` to track token usage, log AI
 requests, enforce budgets, or meter AI traffic is completely non-
 functional in WP 7.0 without the reflection workaround.

 === Environment ===

  * WordPress 7.0 (trunk as of 2026-03-24)
  * PHP 8.3
  * Tested with `azure_openai` provider, `gpt-4.1` model

-- 
Ticket URL: <https://core.trac.wordpress.org/ticket/64938>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform


More information about the wp-trac mailing list