Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@
* interface. As soon as any exception is caught in a chain of method calls,
* the returned instance will be in an error state, and all subsequent method
* calls will be no-ops that just return the same error state instance. Only
* when a generating method is called, the WP_Error will be returned.
* when a generating method is called, the WP_Error will be returned. The
* support check methods are the exception to the no-op behavior: they return
* false rather than the error state instance.
*
* @since 7.0.0
*
Expand Down Expand Up @@ -79,7 +81,7 @@
* @method self as_output_media_aspect_ratio(string $aspectRatio) Sets the output media aspect ratio.
* @method self as_output_speech_voice(string $voice) Sets the output speech voice.
* @method self as_json_response(?array<string, mixed> $schema = null) Configures the prompt for JSON response output.
* @method bool|WP_Error is_supported(?CapabilityEnum $capability = null) Checks if the prompt is supported for the given capability.
* @method bool is_supported(?CapabilityEnum $capability = null) Checks if the prompt is supported for the given capability.
* @method bool is_supported_for_text_generation() Checks if the prompt is supported for text generation.
* @method bool is_supported_for_image_generation() Checks if the prompt is supported for image generation.
* @method bool is_supported_for_text_to_speech_conversion() Checks if the prompt is supported for text to speech conversion.
Expand Down Expand Up @@ -282,7 +284,7 @@ public function using_abilities( ...$abilities ): self {
*
* This allows WordPress developers to use snake_case naming conventions. It catches
* any exceptions thrown, stores them, and returns a WP_Error when a terminate method
* is called.
* is called, or false when a support check method is called.
*
* @since 7.0.0
*
Expand Down Expand Up @@ -364,6 +366,9 @@ public function __call( string $name, array $arguments ) {
if ( self::is_generating_method( $name ) ) {
return $this->error;
}
if ( self::is_support_check_method( $name ) ) {
return false;
}
return $this;
}
}
Expand Down
82 changes: 82 additions & 0 deletions tests/phpunit/tests/ai-client/wpAiClientPromptBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -2648,6 +2648,88 @@ public function test_support_check_methods_return_false_in_error_state() {
$this->assertFalse( $prompt_builder->is_supported_for_text_generation(), 'is_supported_for_text_generation should return false when in error state' );
}

/**
* Tests that support check methods return false when the wrapped builder throws.
*
* @ticket 65781
*
* @dataProvider data_support_check_methods
*
* @param string $method The support check method on the wrapper.
* @param string $wrapped_method The matching method on the wrapped builder.
*/
public function test_support_check_methods_return_false_when_builder_throws( $method, $wrapped_method ) {
$registry = AiClient::defaultRegistry();
$prompt_builder = new WP_AI_Client_Prompt_Builder( $registry, 'Test text' );

$wrapped_builder = $this->createMock( PromptBuilder::class );
$wrapped_builder->method( $wrapped_method )
->willThrowException( new RuntimeException( 'Thrown by the wrapped builder.' ) );

$builder_property = new ReflectionProperty( WP_AI_Client_Prompt_Builder::class, 'builder' );
self::set_accessible( $builder_property );
$builder_property->setValue( $prompt_builder, $wrapped_builder );

$result = $prompt_builder->{$method}();

$this->assertIsBool( $result, $method . ' should return a boolean' );
$this->assertFalse( $result, $method . ' should return false when the wrapped builder throws' );

// The error is still recorded, so a generating method returns it.
$error = $prompt_builder->generate_text();
$this->assertWPError( $error, 'The caught error should still be returned by generating methods' );
$this->assertSame(
'prompt_builder_error',
$error->get_error_code(),
'The recorded error should be the one thrown by the wrapped builder'
);
}

/**
* Data provider.
*
* @return array<string, array{0: string, 1: string}> Support check method names, keyed by the method on the wrapper.
*/
public static function data_support_check_methods(): array {
return array(
'is_supported' => array( 'is_supported', 'isSupported' ),
'is_supported_for_text_generation' => array( 'is_supported_for_text_generation', 'isSupportedForTextGeneration' ),
'is_supported_for_image_generation' => array( 'is_supported_for_image_generation', 'isSupportedForImageGeneration' ),
'is_supported_for_text_to_speech_conversion' => array( 'is_supported_for_text_to_speech_conversion', 'isSupportedForTextToSpeechConversion' ),
'is_supported_for_video_generation' => array( 'is_supported_for_video_generation', 'isSupportedForVideoGeneration' ),
'is_supported_for_speech_generation' => array( 'is_supported_for_speech_generation', 'isSupportedForSpeechGeneration' ),
'is_supported_for_music_generation' => array( 'is_supported_for_music_generation', 'isSupportedForMusicGeneration' ),
'is_supported_for_embedding_generation' => array( 'is_supported_for_embedding_generation', 'isSupportedForEmbeddingGeneration' ),
);
}

/**
* Tests that a throw from the bundled client during a support check is handled.
*
* This covers the route reported on the ticket. The document modality has no
* capability to infer, so the wrapped builder throws while determining whether
* the prompt is supported.
*
* @ticket 65781
*/
public function test_is_supported_returns_false_when_the_bundled_builder_throws() {
$registry = AiClient::defaultRegistry();
$prompt_builder = new WP_AI_Client_Prompt_Builder( $registry, 'Test text' );

$result = $prompt_builder->as_output_modalities( ModalityEnum::document() )->is_supported();

$this->assertIsBool( $result, 'is_supported should return a boolean' );
$this->assertFalse( $result, 'is_supported should return false when the wrapped builder throws' );

$error = $prompt_builder->generate_text();
$this->assertWPError( $error, 'The caught error should still be returned by generating methods' );
$this->assertSame(
'prompt_builder_error',
$error->get_error_code(),
'The document modality should still throw from the wrapped builder. If it no longer does, this test needs another route to a throw'
);
}

/**
* Tests that generating methods return WP_Error when in error state.
*
Expand Down
Loading