From c8514ca6eb0714d60dce92b27b274837373820cc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Mar 2026 09:48:30 +0000 Subject: [PATCH 1/4] Initial plan From 7a69cf6a6678b53a463464b77e65b4d49cc5c123 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Mar 2026 09:53:57 +0000 Subject: [PATCH 2/4] Add wp ai is-supported command with tests and WP_Error handling Agent-Logs-Url: https://github.com/wp-cli/ai-command/sessions/6cc5b262-db12-4187-839d-1ffc38a9b333 Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- composer.json | 1 + features/generate.feature | 15 ++++++++++++++ features/is-supported.feature | 37 ++++++++++++++++++++++++++++++++++ src/AI_Command.php | 38 +++++++++++++++++++++++++++++++++++ 4 files changed, 91 insertions(+) create mode 100644 features/is-supported.feature diff --git a/composer.json b/composer.json index 2ce784e..149ea6b 100644 --- a/composer.json +++ b/composer.json @@ -31,6 +31,7 @@ "ai", "ai check", "ai generate", + "ai is-supported", "ai status", "connectors", "connectors get", diff --git a/features/generate.feature b/features/generate.feature index f9d2a42..8dfb038 100644 --- a/features/generate.feature +++ b/features/generate.feature @@ -196,3 +196,18 @@ Feature: Generate AI content """ Top-k must be a positive integer """ + + @require-wp-7.0 + Scenario: Generate fails when AI is disabled + Given a wp-content/mu-plugins/disable-ai.php file: + """ + get_error_message() ); + } + if ( isset( $assoc_args['provider'] ) ) { $builder = $builder->using_provider( $assoc_args['provider'] ); } @@ -185,6 +189,32 @@ public function generate( $args, $assoc_args ) { } } + /** + * Checks whether AI features are supported in the current environment. + * + * Exits with code 0 if AI features are supported, or code 1 if they are not. + * + * ## EXAMPLES + * + * # Check if AI is supported + * $ wp ai is-supported + * Success: AI features are supported. + * + * @subcommand is-supported + * + * @param string[] $args Positional arguments. Unused. + * @param string[] $assoc_args Associative arguments. Unused. + * @return void + */ + public function is_supported( $args, $assoc_args ) { + // @phpstan-ignore function.notFound + if ( wp_supports_ai() ) { + WP_CLI::success( 'AI features are supported.' ); + } else { + WP_CLI::error( 'AI features are not supported in this environment.' ); + } + } + /** * Checks if a prompt is supported for generation. * @@ -221,6 +251,10 @@ public function check( $args, $assoc_args ) { // @phpstan-ignore function.notFound $builder = wp_ai_client_prompt( $prompt ); + if ( is_wp_error( $builder ) ) { + WP_CLI::error( $builder->get_error_message() ); + } + if ( 'text' === $type ) { $supported = $builder->is_supported_for_text_generation(); if ( $supported ) { @@ -282,6 +316,10 @@ public function status( $args, $assoc_args ) { // @phpstan-ignore function.notFound $builder = wp_ai_client_prompt(); + if ( is_wp_error( $builder ) ) { + WP_CLI::error( $builder->get_error_message() ); + } + // Check each capability $capabilities = array( array( From 9d0e4d70142f5732eaa93623ec0f4a4f23a9446e Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Fri, 27 Mar 2026 11:02:12 +0100 Subject: [PATCH 3/4] Apply suggestion from @swissspidy --- src/AI_Command.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AI_Command.php b/src/AI_Command.php index b8e0d77..0666c62 100644 --- a/src/AI_Command.php +++ b/src/AI_Command.php @@ -209,9 +209,9 @@ public function generate( $args, $assoc_args ) { public function is_supported( $args, $assoc_args ) { // @phpstan-ignore function.notFound if ( wp_supports_ai() ) { - WP_CLI::success( 'AI features are supported.' ); + WP_CLI::halt( 0 ); } else { - WP_CLI::error( 'AI features are not supported in this environment.' ); + WP_CLI::halt( 1 ); } } From 492ee172a4f7052419fb3e2b807d0eb29d0e1b88 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Fri, 27 Mar 2026 12:34:19 +0100 Subject: [PATCH 4/4] Fix & expand tests --- features/generate.feature | 91 +++++++++++++++++++++++++++++------ features/is-supported.feature | 18 ++----- src/AI_Command.php | 44 +++++++++++++++-- 3 files changed, 121 insertions(+), 32 deletions(-) diff --git a/features/generate.feature b/features/generate.feature index 8dfb038..8bbb1ae 100644 --- a/features/generate.feature +++ b/features/generate.feature @@ -7,6 +7,9 @@ Feature: Generate AI content providerMetadata(), + $this->metadata(), + [ 'provider' => 'wp-cli-mock-provider' ] + ); + } + } class WP_CLI_Mock_Provider implements ProviderInterface { @@ -144,12 +185,10 @@ Feature: Generate AI content } } - WP_CLI::add_hook( - 'ai_client_init', + WP_CLI::add_wp_hook( + 'init', static function () { AiClient::defaultRegistry()->registerProvider( WP_CLI_Mock_Provider::class ); - - ( new API_Credentials_Manager() )->initialize(); } ); """ @@ -211,3 +250,27 @@ Feature: Generate AI content """ AI features are not supported in this environment. """ + + @require-wp-7.0 + Scenario: Generates text using mock provider + When I run `wp ai status` + Then STDOUT should be a table containing rows: + | capability | supported | + | Text Generation | Yes | + | Image Generation | Yes | + + @require-wp-7.0 + Scenario: Generates text using mock provider + When I run `wp ai generate text "Test prompt"` + Then STDOUT should be: + """ + This is mock-generated text + """ + + @require-wp-7.0 + Scenario: Generates image using mock provider + When I run `wp ai generate image "Test prompt"` + Then STDOUT should be: + """ + data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII= + """ diff --git a/features/is-supported.feature b/features/is-supported.feature index 3ecbe6f..806b647 100644 --- a/features/is-supported.feature +++ b/features/is-supported.feature @@ -6,20 +6,12 @@ Feature: Check if AI features are supported @less-than-wp-7.0 Scenario: Command not available on WP < 7.0 When I try `wp ai is-supported` - Then STDERR should contain: - """ - Requires WordPress 7.0 or greater. - """ - And the return code should be 1 + Then the return code should be 1 @require-wp-7.0 Scenario: AI is supported by default When I run `wp ai is-supported` - Then STDOUT should contain: - """ - AI features are supported. - """ - And the return code should be 0 + Then the return code should be 0 @require-wp-7.0 Scenario: AI is not supported when disabled via filter @@ -30,8 +22,4 @@ Feature: Check if AI features are supported """ When I try `wp ai is-supported` - Then STDERR should contain: - """ - AI features are not supported in this environment. - """ - And the return code should be 1 + Then the return code should be 1 diff --git a/src/AI_Command.php b/src/AI_Command.php index 0666c62..276af13 100644 --- a/src/AI_Command.php +++ b/src/AI_Command.php @@ -3,6 +3,7 @@ namespace WP_CLI\AI; use WP_CLI; +use WP_CLI\Utils; use WP_CLI_Command; use WordPress\AiClient\Results\DTO\TokenUsage; @@ -118,6 +119,11 @@ class AI_Command extends WP_CLI_Command { public function generate( $args, $assoc_args ) { list( $type, $prompt ) = $args; + // @phpstan-ignore function.notFound + if ( ! wp_supports_ai() ) { + WP_CLI::error( 'AI features are not supported in this environment.' ); + } + try { // @phpstan-ignore function.notFound $builder = wp_ai_client_prompt( $prompt ); @@ -247,6 +253,11 @@ public function check( $args, $assoc_args ) { list( $prompt ) = $args; $type = $assoc_args['type'] ?? 'text'; + // @phpstan-ignore function.notFound + if ( ! wp_supports_ai() ) { + WP_CLI::error( 'AI features are not supported in this environment.' ); + } + try { // @phpstan-ignore function.notFound $builder = wp_ai_client_prompt( $prompt ); @@ -301,7 +312,7 @@ public function check( $args, $assoc_args ) { * # Check AI status * $ wp ai status * +------------------+-----------+ - * | Capability | Supported | + * | capability | supported | * +------------------+-----------+ * | Text Generation | Yes | * | Image Generation | No | @@ -330,6 +341,26 @@ public function status( $args, $assoc_args ) { 'capability' => 'Image Generation', 'supported' => $builder->is_supported_for_image_generation() ? 'Yes' : 'No', ), + array( + 'capability' => 'Text to Speech Generation', + 'supported' => $builder->is_supported_for_text_to_speech_conversion() ? 'Yes' : 'No', + ), + array( + 'capability' => 'Video Generation', + 'supported' => $builder->is_supported_for_video_generation() ? 'Yes' : 'No', + ), + array( + 'capability' => 'Speech Generation', + 'supported' => $builder->is_supported_for_speech_generation() ? 'Yes' : 'No', + ), + array( + 'capability' => 'Music Generation', + 'supported' => $builder->is_supported_for_music_generation() ? 'Yes' : 'No', + ), + array( + 'capability' => 'Embedding Generation', + 'supported' => $builder->is_supported_for_embedding_generation() ? 'Yes' : 'No', + ), ); $format = $assoc_args['format'] ?? 'table'; @@ -359,6 +390,10 @@ private function generate_text( $builder, $assoc_args ) { // @phpstan-ignore class.notFound $text = $builder->generate_text_result(); + if ( is_wp_error( $text ) ) { + WP_CLI::error( $text ); + } + if ( 'json' === $format ) { $json = json_encode( array( 'text' => $text->toText() ) ); if ( false === $json ) { @@ -418,6 +453,10 @@ private function generate_image( $builder, $assoc_args ) { // @phpstan-ignore class.notFound $image_file = $builder->generate_image(); + if ( is_wp_error( $image_file ) ) { + WP_CLI::error( $image_file ); + } + if ( isset( $assoc_args['destination-file'] ) ) { $output_path = $assoc_args['destination-file']; $output_path = realpath( dirname( $output_path ) ) . DIRECTORY_SEPARATOR . basename( $output_path ); @@ -444,7 +483,7 @@ private function generate_image( $builder, $assoc_args ) { } WP_CLI::success( 'Image saved to ' . $output_path ); - } elseif ( $assoc_args['stdout'] ) { + } elseif ( Utils\get_flag_value( $assoc_args, 'stdout', false ) ) { $data_uri = $image_file->getDataUri(); $data_parts = $data_uri ? explode( ',', $data_uri, 2 ) : []; @@ -462,7 +501,6 @@ private function generate_image( $builder, $assoc_args ) { WP_CLI::log( $image_data ); } else { - WP_CLI::success( 'Image generated:' ); WP_CLI::line( (string) $image_file->getDataUri() ); } }