diff --git a/src/wp-includes/ai-client.php b/src/wp-includes/ai-client.php index 4fc20166fb8bb..b38c7b721416d 100644 --- a/src/wp-includes/ai-client.php +++ b/src/wp-includes/ai-client.php @@ -8,6 +8,8 @@ */ use WordPress\AiClient\AiClient; +use WordPress\AiClient\Messages\DTO\Message; +use WordPress\AiClient\Messages\DTO\MessagePart; /** * Returns whether AI features are supported in the current environment. @@ -55,6 +57,6 @@ function wp_supports_ai(): bool { * conversations. Default null. * @return WP_AI_Client_Prompt_Builder The prompt builder instance. */ -function wp_ai_client_prompt( $prompt = null ) { +function wp_ai_client_prompt( $prompt = null ): WP_AI_Client_Prompt_Builder { return new WP_AI_Client_Prompt_Builder( AiClient::defaultRegistry(), $prompt ); } diff --git a/src/wp-includes/ai-client/adapters/class-wp-ai-client-cache.php b/src/wp-includes/ai-client/adapters/class-wp-ai-client-cache.php index 18d85eee6c9e6..45504897485f7 100644 --- a/src/wp-includes/ai-client/adapters/class-wp-ai-client-cache.php +++ b/src/wp-includes/ai-client/adapters/class-wp-ai-client-cache.php @@ -104,7 +104,7 @@ public function clear(): bool { * @param mixed $default_value Default value to return for keys that do not exist. * @return array A list of key => value pairs. */ - public function getMultiple( $keys, $default_value = null ) { + public function getMultiple( $keys, $default_value = null ): array { /** * Keys array. * diff --git a/src/wp-includes/ai-client/adapters/class-wp-ai-client-http-client.php b/src/wp-includes/ai-client/adapters/class-wp-ai-client-http-client.php index f1827db0e437c..f6c6dea441d1c 100644 --- a/src/wp-includes/ai-client/adapters/class-wp-ai-client-http-client.php +++ b/src/wp-includes/ai-client/adapters/class-wp-ai-client-http-client.php @@ -32,17 +32,15 @@ class WP_AI_Client_HTTP_Client implements ClientInterface, ClientWithOptionsInte * Response factory instance. * * @since 7.0.0 - * @var ResponseFactoryInterface */ - private $response_factory; + private ResponseFactoryInterface $response_factory; /** * Stream factory instance. * * @since 7.0.0 - * @var StreamFactoryInterface */ - private $stream_factory; + private StreamFactoryInterface $stream_factory; /** * Constructor. 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 d1f2271bd47d3..da7858dd76555 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 @@ -190,14 +190,29 @@ public function __construct( ProviderRegistry $registry, $prompt = null ) { $this->error = $this->exception_to_wp_error( $e ); } + $default_timeout = 30.0; + /** * Filters the default request timeout in seconds for AI Client HTTP requests. * * @since 7.0.0 * - * @param int $default_timeout The default timeout in seconds. + * @param float $default_timeout The default timeout in seconds. */ - $default_timeout = (int) apply_filters( 'wp_ai_client_default_request_timeout', 30 ); + $filtered_default_timeout = apply_filters( 'wp_ai_client_default_request_timeout', $default_timeout ); + if ( is_numeric( $filtered_default_timeout ) && (float) $filtered_default_timeout >= 0.0 ) { + $default_timeout = (float) $filtered_default_timeout; + } else { + _doing_it_wrong( + __METHOD__, + sprintf( + /* translators: %s: wp_ai_client_default_request_timeout */ + __( 'The %s filter must return a non-negative number.' ), + 'wp_ai_client_default_request_timeout' + ), + '7.0.0' + ); + } $this->builder->usingRequestOptions( RequestOptions::fromArray( diff --git a/tests/phpunit/tests/ai-client/wpAiClientPromptBuilder.php b/tests/phpunit/tests/ai-client/wpAiClientPromptBuilder.php index 3a781ccc73751..e758a6868aa42 100644 --- a/tests/phpunit/tests/ai-client/wpAiClientPromptBuilder.php +++ b/tests/phpunit/tests/ai-client/wpAiClientPromptBuilder.php @@ -192,15 +192,64 @@ public function test_constructor_sets_default_request_timeout() { } /** - * Test that the constructor allows overriding the default request timeout. + * Test that the constructor allows overriding the default request timeout with a valid value. * * @ticket 64591 + * @ticket 65094 + * + * @dataProvider data_valid_request_timeout_overrides + * + * @param mixed $input The timeout value returned by the filter. + * @param float $expected The expected timeout stored on the request options. + */ + public function test_constructor_allows_overriding_request_timeout_with_valid_timeout( $input, float $expected ) { + add_filter( + 'wp_ai_client_default_request_timeout', + static function () use ( $input ) { + return $input; + } + ); + + $builder = new WP_AI_Client_Prompt_Builder( AiClient::defaultRegistry() ); + + /** @var RequestOptions $request_options */ + $request_options = $this->get_wrapped_prompt_builder_property_value( $builder, 'requestOptions' ); + + $this->assertInstanceOf( RequestOptions::class, $request_options ); + $this->assertSame( $expected, $request_options->getTimeout() ); + } + + /** + * Data provider for {@see self::test_constructor_allows_overriding_request_timeout_with_valid_timeout()}. + * + * @return array */ - public function test_constructor_allows_overriding_request_timeout() { + public function data_valid_request_timeout_overrides(): array { + return array( + 'float' => array( 45.5, 45.5 ), + 'integer' => array( 67, 67.0 ), + 'string' => array( '20', 20.0 ), + 'infinity' => array( INF, INF ), + 'zero' => array( 0.0, 0.0 ), + ); + } + + /** + * Test that the constructor disallows overriding the default request timeout with an invalid value. + * + * @ticket 65094 + * + * @dataProvider data_invalid_request_timeouts + * + * @expectedIncorrectUsage WP_AI_Client_Prompt_Builder::__construct + * + * @param mixed $timeout The invalid timeout value returned by the filter. + */ + public function test_constructor_disallows_overriding_with_invalid_request_timeout( $timeout ) { add_filter( 'wp_ai_client_default_request_timeout', - static function () { - return 45; + static function () use ( $timeout ) { + return $timeout; } ); @@ -210,7 +259,20 @@ static function () { $request_options = $this->get_wrapped_prompt_builder_property_value( $builder, 'requestOptions' ); $this->assertInstanceOf( RequestOptions::class, $request_options ); - $this->assertSame( 45.0, $request_options->getTimeout() ); + $this->assertSame( 30.0, $request_options->getTimeout() ); + } + + /** + * Data provider for {@see self::test_constructor_disallows_overriding_with_invalid_request_timeout()}. + * + * @return array + */ + public function data_invalid_request_timeouts(): array { + return array( + 'negative number' => array( -1 ), + 'array' => array( array() ), + 'null' => array( null ), + ); } /**