From 39e7d7d38d1bc94d05088d48d6dc4be82ec3b1be Mon Sep 17 00:00:00 2001 From: Grzegorz Ziolkowski Date: Mon, 2 Mar 2026 15:18:44 +0100 Subject: [PATCH 1/2] AI: Wrap SDK constructor exceptions into WP_Error in prompt builder. Trac ticket: https://core.trac.wordpress.org/ticket/64591 When invalid input is passed to `wp_ai_client_prompt()`, the SDK's `PromptBuilder` constructor throws an `InvalidArgumentException` directly, bypassing the `__call()` exception wrapping. This catches the exception in the constructor and stores it as a `WP_Error` in `$this->error`, so it surfaces consistently through the fluent chain. Follow-up to https://github.com/WordPress/wordpress-develop/pull/10881#issuecomment-3934854584. Co-Authored-By: Claude Opus 4.6 --- .../class-wp-ai-client-prompt-builder.php | 13 +++- .../ai-client/wpAiClientPromptBuilder.php | 64 +++++++++++-------- 2 files changed, 49 insertions(+), 28 deletions(-) diff --git a/src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php b/src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php index adfde12eb1b51..b952fcc9bf6a1 100644 --- a/src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php +++ b/src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php @@ -165,7 +165,18 @@ class WP_AI_Client_Prompt_Builder { * conversations. Default null. */ public function __construct( ProviderRegistry $registry, $prompt = null ) { - $this->builder = new PromptBuilder( $registry, $prompt ); + try { + $this->builder = new PromptBuilder( $registry, $prompt ); + } catch ( \Exception $e ) { + $this->builder = new PromptBuilder( $registry ); + $this->error = new \WP_Error( + 'prompt_builder_error', + $e->getMessage(), + array( + 'exception_class' => get_class( $e ), + ) + ); + } /** * Filters the default request timeout in seconds for AI Client HTTP requests. diff --git a/tests/phpunit/tests/ai-client/wpAiClientPromptBuilder.php b/tests/phpunit/tests/ai-client/wpAiClientPromptBuilder.php index bfd02fd3ebb15..a8c8ec58e7037 100644 --- a/tests/phpunit/tests/ai-client/wpAiClientPromptBuilder.php +++ b/tests/phpunit/tests/ai-client/wpAiClientPromptBuilder.php @@ -1347,22 +1347,15 @@ public function test_validate_messages_non_user_last_returns_wp_error() { /** * Tests parseMessage with empty string returns WP_Error on termination. * - * The SDK constructor throws immediately for empty strings, so the exception - * is caught in the constructor and stored. - * * @ticket 64591 */ public function test_parse_message_empty_string_returns_wp_error() { - // The empty string exception is thrown by the SDK's PromptBuilder constructor, - // which happens before our __call() error handling. We must catch it manually. - try { - $builder = new WP_AI_Client_Prompt_Builder( $this->registry, ' ' ); - // If we get here, the SDK didn't throw. Test would need adjusting. - $result = $builder->generate_result(); - $this->assertWPError( $result ); - } catch ( InvalidArgumentException $e ) { - $this->assertStringContainsString( 'Cannot create a message from an empty string', $e->getMessage() ); - } + $builder = new WP_AI_Client_Prompt_Builder( $this->registry, ' ' ); + $result = $builder->generate_result(); + + $this->assertWPError( $result ); + $this->assertSame( 'prompt_builder_error', $result->get_error_code() ); + $this->assertStringContainsString( 'Cannot create a message from an empty string', $result->get_error_message() ); } /** @@ -1371,13 +1364,12 @@ public function test_parse_message_empty_string_returns_wp_error() { * @ticket 64591 */ public function test_parse_message_empty_array_returns_wp_error() { - try { - $builder = new WP_AI_Client_Prompt_Builder( $this->registry, array() ); - $result = $builder->generate_result(); - $this->assertWPError( $result ); - } catch ( InvalidArgumentException $e ) { - $this->assertStringContainsString( 'Cannot create a message from an empty array', $e->getMessage() ); - } + $builder = new WP_AI_Client_Prompt_Builder( $this->registry, array() ); + $result = $builder->generate_result(); + + $this->assertWPError( $result ); + $this->assertSame( 'prompt_builder_error', $result->get_error_code() ); + $this->assertStringContainsString( 'Cannot create a message from an empty array', $result->get_error_message() ); } /** @@ -1386,13 +1378,31 @@ public function test_parse_message_empty_array_returns_wp_error() { * @ticket 64591 */ public function test_parse_message_invalid_type_returns_wp_error() { - try { - $builder = new WP_AI_Client_Prompt_Builder( $this->registry, 123 ); - $result = $builder->generate_result(); - $this->assertWPError( $result ); - } catch ( InvalidArgumentException $e ) { - $this->assertStringContainsString( 'Input must be a string, MessagePart, MessagePartArrayShape', $e->getMessage() ); - } + $builder = new WP_AI_Client_Prompt_Builder( $this->registry, 123 ); + $result = $builder->generate_result(); + + $this->assertWPError( $result ); + $this->assertSame( 'prompt_builder_error', $result->get_error_code() ); + $this->assertStringContainsString( 'Input must be a string, MessagePart, MessagePartArrayShape', $result->get_error_message() ); + } + + /** + * Tests that wp_ai_client_prompt() with an empty string does not throw. + * + * Constructor exceptions are caught and surfaced as WP_Error from + * generating methods, consistent with the __call() wrapping behavior. + * + * @ticket 64591 + */ + public function test_wp_ai_client_prompt_empty_string_returns_wp_error() { + $builder = wp_ai_client_prompt( ' ' ); + + $this->assertInstanceOf( WP_AI_Client_Prompt_Builder::class, $builder ); + + $result = $builder->generate_text(); + + $this->assertWPError( $result ); + $this->assertSame( 'prompt_builder_error', $result->get_error_code() ); } /** From bbbff31a5a95ead2a71dd96814d5656aa9ead361 Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Mon, 2 Mar 2026 20:27:30 -0600 Subject: [PATCH 2/2] Remove unnecessary backslashes. --- .../ai-client/class-wp-ai-client-prompt-builder.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php b/src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php index b952fcc9bf6a1..3ed311915d158 100644 --- a/src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php +++ b/src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php @@ -167,9 +167,9 @@ class WP_AI_Client_Prompt_Builder { public function __construct( ProviderRegistry $registry, $prompt = null ) { try { $this->builder = new PromptBuilder( $registry, $prompt ); - } catch ( \Exception $e ) { + } catch ( Exception $e ) { $this->builder = new PromptBuilder( $registry ); - $this->error = new \WP_Error( + $this->error = new WP_Error( 'prompt_builder_error', $e->getMessage(), array(