From be17a67f2e7bb2d997026abf052b0a95f2d20203 Mon Sep 17 00:00:00 2001 From: raftaar1191 Date: Thu, 26 Mar 2026 13:54:09 +0530 Subject: [PATCH 1/7] Fix: restrict Namer model dropdown to text-generation capable models Filters get_filtered_ai_models() to skip models that do not support the text_generation capability, preventing embedding, vision-only, and audio models from appearing in the Namer tool's model dropdown. Adds model_supports_text_generation() helper that uses the getSupportedCapabilities() API from ModelMetadata (WP 7.0 AI Client). Fixes #1219 --- includes/Traits/AI_Utils.php | 37 ++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/includes/Traits/AI_Utils.php b/includes/Traits/AI_Utils.php index 18a9fd6f4..bc3731f72 100644 --- a/includes/Traits/AI_Utils.php +++ b/includes/Traits/AI_Utils.php @@ -231,6 +231,11 @@ protected function get_filtered_ai_models() { $provider_meta = $class_name::metadata(); foreach ( $class_name::modelMetadataDirectory()->listModelMetadata() as $model_meta ) { + + if ( ! $this->model_supports_text_generation( $model_meta ) ) { + continue; + } + $models[] = array( 'provider' => (string) $provider_id, 'provider_label' => (string) $provider_meta->getName(), @@ -247,6 +252,38 @@ protected function get_filtered_ai_models() { return is_array( $models ) ? $models : array(); } + /** + * Determines whether a model metadata object supports text generation. + * + * @since 1.9.1 + * + * @param object $model_meta Model metadata object. + * @return bool True when the model supports text generation, otherwise false. + */ + protected function model_supports_text_generation( $model_meta ) { + + if ( ! method_exists( $model_meta, 'getSupportedCapabilities' ) ) { + return true; + } + + $capabilities = $model_meta->getSupportedCapabilities(); + if ( ! is_array( $capabilities ) ) { + return false; + } + + foreach ( $capabilities as $capability ) { + if ( is_string( $capability ) && 'text_generation' === $capability ) { + return true; + } + + if ( is_object( $capability ) && method_exists( $capability, 'equals' ) && $capability->equals( 'text_generation' ) ) { + return true; + } + } + + return false; + } + /** * Returns whether there are no active AI connectors. * From ae19e93e382f041c54fbb9daf74c86aa5f527c40 Mon Sep 17 00:00:00 2001 From: raftaar1191 Date: Thu, 26 Mar 2026 14:22:03 +0530 Subject: [PATCH 2/7] Fix: exclude models with non-text output modalities from Namer dropdown --- includes/Traits/AI_Utils.php | 58 ++++++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/includes/Traits/AI_Utils.php b/includes/Traits/AI_Utils.php index bc3731f72..00a3af859 100644 --- a/includes/Traits/AI_Utils.php +++ b/includes/Traits/AI_Utils.php @@ -255,8 +255,13 @@ protected function get_filtered_ai_models() { /** * Determines whether a model metadata object supports text generation. * + * Both input and output must be text-based; models that support non-text output + * modalities (e.g. audio, image) are excluded. + * * @since 1.9.1 * + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * * @param object $model_meta Model metadata object. * @return bool True when the model supports text generation, otherwise false. */ @@ -271,17 +276,64 @@ protected function model_supports_text_generation( $model_meta ) { return false; } + $has_text_generation = false; foreach ( $capabilities as $capability ) { if ( is_string( $capability ) && 'text_generation' === $capability ) { - return true; + $has_text_generation = true; + break; } if ( is_object( $capability ) && method_exists( $capability, 'equals' ) && $capability->equals( 'text_generation' ) ) { - return true; + $has_text_generation = true; + break; + } + } + + if ( ! $has_text_generation ) { + return false; + } + + // Also ensure all supported output modality combinations are text-only. + if ( method_exists( $model_meta, 'getSupportedOptions' ) ) { + $options = $model_meta->getSupportedOptions(); + if ( is_array( $options ) ) { + foreach ( $options as $option ) { + if ( ! is_object( $option ) || ! method_exists( $option, 'getName' ) ) { + continue; + } + $option_name = $option->getName(); + if ( + ! is_object( $option_name ) + || ! method_exists( $option_name, 'isOutputModalities' ) + || ! $option_name->isOutputModalities() + ) { + continue; + } + if ( ! method_exists( $option, 'getSupportedValues' ) ) { + continue; + } + $supported_values = $option->getSupportedValues(); + if ( ! is_array( $supported_values ) ) { + continue; + } + foreach ( $supported_values as $combination ) { + if ( ! is_array( $combination ) ) { + continue; + } + foreach ( $combination as $modality ) { + $is_text = ( is_string( $modality ) && 'text' === $modality ) + || ( is_object( $modality ) && method_exists( $modality, 'isText' ) && $modality->isText() ) + || ( is_object( $modality ) && isset( $modality->value ) && 'text' === $modality->value ); + if ( ! $is_text ) { + return false; + } + } + } + } } } - return false; + return true; } /** From 534efd53f3b4b22cb5ff054409a5c864ac1a4d7c Mon Sep 17 00:00:00 2001 From: raftaar1191 Date: Sun, 29 Mar 2026 14:03:48 +0530 Subject: [PATCH 3/7] Update code --- includes/Traits/AI_Utils.php | 228 ++++++++++++++++++++++------------- 1 file changed, 143 insertions(+), 85 deletions(-) diff --git a/includes/Traits/AI_Utils.php b/includes/Traits/AI_Utils.php index 00a3af859..d3cb440c9 100644 --- a/includes/Traits/AI_Utils.php +++ b/includes/Traits/AI_Utils.php @@ -205,6 +205,148 @@ protected function find_json_bounds( $text ) { ); } + /** + * Returns whether a model metadata object supports text for both input and output. + * + * Checks supported capabilities for text_generation, then inspects input/output + * modality options. Falls back to name-based inference when metadata is insufficient. + * + * @since 2.0.0 + * + * @param object $model_meta Model metadata object. + * @return bool True when the model supports text input and text output. + */ + protected function supports_text_io_from_metadata( $model_meta ) { + if ( ! is_object( $model_meta ) ) { + return false; + } + + // If capabilities metadata is present, require text_generation capability. + if ( method_exists( $model_meta, 'getSupportedCapabilities' ) ) { + $supported = $model_meta->getSupportedCapabilities(); + if ( is_array( $supported ) ) { + $has_text_gen = false; + foreach ( $supported as $cap ) { + $val = ''; + if ( is_object( $cap ) && method_exists( $cap, '__get' ) && 'text_generation' === strtolower( (string) $cap->value ) ) { + $has_text_gen = true; + break; + } + if ( is_string( $cap ) && 'text_generation' === strtolower( $cap ) ) { + $has_text_gen = true; + break; + } + } + if ( ! $has_text_gen ) { + return false; + } + } + } + + // Check input/output modality options for text support. + if ( method_exists( $model_meta, 'getSupportedOptions' ) ) { + $options = $model_meta->getSupportedOptions(); + if ( is_array( $options ) ) { + $input_has_text = null; + $output_has_text = null; + + foreach ( $options as $option ) { + if ( ! is_object( $option ) || ! method_exists( $option, 'getName' ) ) { + continue; + } + + $name = $option->getName(); + $is_input = false; + $is_output = false; + + if ( is_object( $name ) ) { + if ( method_exists( $name, '__get' ) ) { + $raw = strtolower( (string) $name->value ); + $is_input = 'input_modalities' === $raw; + $is_output = 'output_modalities' === $raw; + } + } elseif ( is_string( $name ) ) { + $raw = strtolower( $name ); + $is_input = 'inputmodalities' === $raw || 'input_modalities' === $raw; + $is_output = 'outputmodalities' === $raw || 'output_modalities' === $raw; + } + + if ( ! $is_input && ! $is_output ) { + continue; + } + + if ( ! method_exists( $option, 'getSupportedValues' ) ) { + continue; + } + + $values = $option->getSupportedValues(); + $has_text = $this->modality_values_include_text( $values ); + + if ( $is_input ) { + $input_has_text = $has_text; + } + if ( $is_output ) { + $output_has_text = $has_text; + } + } + + // If modality options were present, use them as the authority. + if ( null !== $input_has_text || null !== $output_has_text ) { + return (bool) $input_has_text && (bool) $output_has_text; + } + } + } + + // Fall back to name-based inference. + if ( method_exists( $model_meta, 'getId' ) ) { + $model = strtolower( (string) $model_meta->getId() ); + if ( + false !== strpos( $model, 'transcribe' ) + || false !== strpos( $model, 'tts' ) + || false !== strpos( $model, 'realtime' ) + ) { + return false; + } + } + + return true; + } + + /** + * Returns whether a supported-values matrix contains a text modality. + * + * @since 2.0.0 + * + * @param mixed $values Supported values from a SupportedOption (array of combinations). + * @return bool True when at least one item resolves to text. + */ + protected function modality_values_include_text( $values ) { + if ( ! is_array( $values ) ) { + return false; + } + + foreach ( $values as $combination ) { + if ( ! is_array( $combination ) ) { + continue; + } + foreach ( $combination as $modality ) { + $text = ''; + if ( is_string( $modality ) ) { + $text = strtolower( $modality ); + } elseif ( is_object( $modality ) ) { + if ( method_exists( $modality, '__get' ) && 'text' === strtolower( (string) $modality->value ) ) { + return true; + } + } + if ( 'text' === $text ) { + return true; + } + } + } + + return false; + } + /** * Returns models from active/configured providers using the official AI Client registry flow. * @@ -232,7 +374,7 @@ protected function get_filtered_ai_models() { foreach ( $class_name::modelMetadataDirectory()->listModelMetadata() as $model_meta ) { - if ( ! $this->model_supports_text_generation( $model_meta ) ) { + if ( ! $this->supports_text_io_from_metadata( $model_meta ) ) { continue; } @@ -252,90 +394,6 @@ protected function get_filtered_ai_models() { return is_array( $models ) ? $models : array(); } - /** - * Determines whether a model metadata object supports text generation. - * - * Both input and output must be text-based; models that support non-text output - * modalities (e.g. audio, image) are excluded. - * - * @since 1.9.1 - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * - * @param object $model_meta Model metadata object. - * @return bool True when the model supports text generation, otherwise false. - */ - protected function model_supports_text_generation( $model_meta ) { - - if ( ! method_exists( $model_meta, 'getSupportedCapabilities' ) ) { - return true; - } - - $capabilities = $model_meta->getSupportedCapabilities(); - if ( ! is_array( $capabilities ) ) { - return false; - } - - $has_text_generation = false; - foreach ( $capabilities as $capability ) { - if ( is_string( $capability ) && 'text_generation' === $capability ) { - $has_text_generation = true; - break; - } - - if ( is_object( $capability ) && method_exists( $capability, 'equals' ) && $capability->equals( 'text_generation' ) ) { - $has_text_generation = true; - break; - } - } - - if ( ! $has_text_generation ) { - return false; - } - - // Also ensure all supported output modality combinations are text-only. - if ( method_exists( $model_meta, 'getSupportedOptions' ) ) { - $options = $model_meta->getSupportedOptions(); - if ( is_array( $options ) ) { - foreach ( $options as $option ) { - if ( ! is_object( $option ) || ! method_exists( $option, 'getName' ) ) { - continue; - } - $option_name = $option->getName(); - if ( - ! is_object( $option_name ) - || ! method_exists( $option_name, 'isOutputModalities' ) - || ! $option_name->isOutputModalities() - ) { - continue; - } - if ( ! method_exists( $option, 'getSupportedValues' ) ) { - continue; - } - $supported_values = $option->getSupportedValues(); - if ( ! is_array( $supported_values ) ) { - continue; - } - foreach ( $supported_values as $combination ) { - if ( ! is_array( $combination ) ) { - continue; - } - foreach ( $combination as $modality ) { - $is_text = ( is_string( $modality ) && 'text' === $modality ) - || ( is_object( $modality ) && method_exists( $modality, 'isText' ) && $modality->isText() ) - || ( is_object( $modality ) && isset( $modality->value ) && 'text' === $modality->value ); - if ( ! $is_text ) { - return false; - } - } - } - } - } - } - - return true; - } - /** * Returns whether there are no active AI connectors. * From bb14f4600030561a73f8b7daa51aa813e7b57cbc Mon Sep 17 00:00:00 2001 From: raftaar1191 Date: Sun, 29 Mar 2026 14:09:49 +0530 Subject: [PATCH 4/7] Fix PHPStan: narrow AbstractEnum $value access via instanceof check --- includes/Traits/AI_Utils.php | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/includes/Traits/AI_Utils.php b/includes/Traits/AI_Utils.php index d3cb440c9..421d23c6f 100644 --- a/includes/Traits/AI_Utils.php +++ b/includes/Traits/AI_Utils.php @@ -8,6 +8,7 @@ namespace WordPress\Plugin_Check\Traits; use WordPress\AiClient\AiClient; +use WordPress\AiClient\Common\AbstractEnum; use WP_Error; /** @@ -228,7 +229,7 @@ protected function supports_text_io_from_metadata( $model_meta ) { $has_text_gen = false; foreach ( $supported as $cap ) { $val = ''; - if ( is_object( $cap ) && method_exists( $cap, '__get' ) && 'text_generation' === strtolower( (string) $cap->value ) ) { + if ( $cap instanceof AbstractEnum && 'text_generation' === strtolower( (string) $cap->value ) ) { $has_text_gen = true; break; } @@ -259,12 +260,10 @@ protected function supports_text_io_from_metadata( $model_meta ) { $is_input = false; $is_output = false; - if ( is_object( $name ) ) { - if ( method_exists( $name, '__get' ) ) { - $raw = strtolower( (string) $name->value ); - $is_input = 'input_modalities' === $raw; - $is_output = 'output_modalities' === $raw; - } + if ( $name instanceof AbstractEnum ) { + $raw = strtolower( (string) $name->value ); + $is_input = 'input_modalities' === $raw; + $is_output = 'output_modalities' === $raw; } elseif ( is_string( $name ) ) { $raw = strtolower( $name ); $is_input = 'inputmodalities' === $raw || 'input_modalities' === $raw; @@ -333,10 +332,8 @@ protected function modality_values_include_text( $values ) { $text = ''; if ( is_string( $modality ) ) { $text = strtolower( $modality ); - } elseif ( is_object( $modality ) ) { - if ( method_exists( $modality, '__get' ) && 'text' === strtolower( (string) $modality->value ) ) { - return true; - } + } elseif ( $modality instanceof AbstractEnum && 'text' === strtolower( (string) $modality->value ) ) { + return true; } if ( 'text' === $text ) { return true; From 91c9f05f8efbb696a523f1ea24dfd49b90d82a8c Mon Sep 17 00:00:00 2001 From: raftaar1191 Date: Sun, 29 Mar 2026 14:14:41 +0530 Subject: [PATCH 5/7] Fix PHPStan: use string cast instead of AbstractEnum instanceof (class unavailable in CI) --- includes/Traits/AI_Utils.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/includes/Traits/AI_Utils.php b/includes/Traits/AI_Utils.php index 421d23c6f..82e6f2a98 100644 --- a/includes/Traits/AI_Utils.php +++ b/includes/Traits/AI_Utils.php @@ -8,7 +8,6 @@ namespace WordPress\Plugin_Check\Traits; use WordPress\AiClient\AiClient; -use WordPress\AiClient\Common\AbstractEnum; use WP_Error; /** @@ -229,7 +228,7 @@ protected function supports_text_io_from_metadata( $model_meta ) { $has_text_gen = false; foreach ( $supported as $cap ) { $val = ''; - if ( $cap instanceof AbstractEnum && 'text_generation' === strtolower( (string) $cap->value ) ) { + if ( is_object( $cap ) && 'text_generation' === strtolower( (string) $cap ) ) { $has_text_gen = true; break; } @@ -260,8 +259,8 @@ protected function supports_text_io_from_metadata( $model_meta ) { $is_input = false; $is_output = false; - if ( $name instanceof AbstractEnum ) { - $raw = strtolower( (string) $name->value ); + if ( is_object( $name ) ) { + $raw = strtolower( (string) $name ); $is_input = 'input_modalities' === $raw; $is_output = 'output_modalities' === $raw; } elseif ( is_string( $name ) ) { @@ -332,7 +331,7 @@ protected function modality_values_include_text( $values ) { $text = ''; if ( is_string( $modality ) ) { $text = strtolower( $modality ); - } elseif ( $modality instanceof AbstractEnum && 'text' === strtolower( (string) $modality->value ) ) { + } elseif ( is_object( $modality ) && 'text' === strtolower( (string) $modality ) ) { return true; } if ( 'text' === $text ) { From d284cddff3b92cc84f93062dcaa773f93f7f32a0 Mon Sep 17 00:00:00 2001 From: raftaar1191 Date: Sun, 29 Mar 2026 14:48:56 +0530 Subject: [PATCH 6/7] Refactor: extract helper methods to fix PHPMD complexity violations --- includes/Traits/AI_Utils.php | 163 +++++++++++++++++++---------------- 1 file changed, 90 insertions(+), 73 deletions(-) diff --git a/includes/Traits/AI_Utils.php b/includes/Traits/AI_Utils.php index 82e6f2a98..125f287dd 100644 --- a/includes/Traits/AI_Utils.php +++ b/includes/Traits/AI_Utils.php @@ -224,22 +224,8 @@ protected function supports_text_io_from_metadata( $model_meta ) { // If capabilities metadata is present, require text_generation capability. if ( method_exists( $model_meta, 'getSupportedCapabilities' ) ) { $supported = $model_meta->getSupportedCapabilities(); - if ( is_array( $supported ) ) { - $has_text_gen = false; - foreach ( $supported as $cap ) { - $val = ''; - if ( is_object( $cap ) && 'text_generation' === strtolower( (string) $cap ) ) { - $has_text_gen = true; - break; - } - if ( is_string( $cap ) && 'text_generation' === strtolower( $cap ) ) { - $has_text_gen = true; - break; - } - } - if ( ! $has_text_gen ) { - return false; - } + if ( is_array( $supported ) && ! $this->meta_has_text_generation_cap( $supported ) ) { + return false; } } @@ -247,67 +233,14 @@ protected function supports_text_io_from_metadata( $model_meta ) { if ( method_exists( $model_meta, 'getSupportedOptions' ) ) { $options = $model_meta->getSupportedOptions(); if ( is_array( $options ) ) { - $input_has_text = null; - $output_has_text = null; - - foreach ( $options as $option ) { - if ( ! is_object( $option ) || ! method_exists( $option, 'getName' ) ) { - continue; - } - - $name = $option->getName(); - $is_input = false; - $is_output = false; - - if ( is_object( $name ) ) { - $raw = strtolower( (string) $name ); - $is_input = 'input_modalities' === $raw; - $is_output = 'output_modalities' === $raw; - } elseif ( is_string( $name ) ) { - $raw = strtolower( $name ); - $is_input = 'inputmodalities' === $raw || 'input_modalities' === $raw; - $is_output = 'outputmodalities' === $raw || 'output_modalities' === $raw; - } - - if ( ! $is_input && ! $is_output ) { - continue; - } - - if ( ! method_exists( $option, 'getSupportedValues' ) ) { - continue; - } - - $values = $option->getSupportedValues(); - $has_text = $this->modality_values_include_text( $values ); - - if ( $is_input ) { - $input_has_text = $has_text; - } - if ( $is_output ) { - $output_has_text = $has_text; - } + $modality = $this->meta_get_modality_text_support( $options ); + if ( null !== $modality['input'] || null !== $modality['output'] ) { + return (bool) $modality['input'] && (bool) $modality['output']; } - - // If modality options were present, use them as the authority. - if ( null !== $input_has_text || null !== $output_has_text ) { - return (bool) $input_has_text && (bool) $output_has_text; - } - } - } - - // Fall back to name-based inference. - if ( method_exists( $model_meta, 'getId' ) ) { - $model = strtolower( (string) $model_meta->getId() ); - if ( - false !== strpos( $model, 'transcribe' ) - || false !== strpos( $model, 'tts' ) - || false !== strpos( $model, 'realtime' ) - ) { - return false; } } - return true; + return ! $this->meta_is_audio_model_by_name( $model_meta ); } /** @@ -343,6 +276,90 @@ protected function modality_values_include_text( $values ) { return false; } + /** + * Returns whether a capabilities array contains text_generation. + * + * @since 2.0.0 + * + * @param array $supported Capabilities from getSupportedCapabilities(). + * @return bool + */ + private function meta_has_text_generation_cap( array $supported ): bool { + foreach ( $supported as $cap ) { + if ( is_object( $cap ) && 'text_generation' === strtolower( (string) $cap ) ) { + return true; + } + if ( is_string( $cap ) && 'text_generation' === strtolower( $cap ) ) { + return true; + } + } + return false; + } + + /** + * Returns input/output text-modality support derived from getSupportedOptions(). + * + * @since 2.0.0 + * + * @param array $options Options from getSupportedOptions(). + * @return array + */ + private function meta_get_modality_text_support( array $options ): array { + $input_has_text = null; + $output_has_text = null; + + foreach ( $options as $option ) { + if ( ! is_object( $option ) || ! method_exists( $option, 'getName' ) ) { + continue; + } + $name = $option->getName(); + $is_input = false; + $is_output = false; + if ( is_object( $name ) ) { + $raw = strtolower( (string) $name ); + $is_input = 'input_modalities' === $raw; + $is_output = 'output_modalities' === $raw; + } elseif ( is_string( $name ) ) { + $raw = strtolower( $name ); + $is_input = 'inputmodalities' === $raw || 'input_modalities' === $raw; + $is_output = 'outputmodalities' === $raw || 'output_modalities' === $raw; + } + if ( ( ! $is_input && ! $is_output ) || ! method_exists( $option, 'getSupportedValues' ) ) { + continue; + } + $has_text = $this->modality_values_include_text( $option->getSupportedValues() ); + if ( $is_input ) { + $input_has_text = $has_text; + } + if ( $is_output ) { + $output_has_text = $has_text; + } + } + + return array( + 'input' => $input_has_text, + 'output' => $output_has_text, + ); + } + + /** + * Returns true when model name suggests audio-only (transcription / TTS / realtime). + * + * @since 2.0.0 + * + * @param object $model_meta Model metadata object. + * @return bool + */ + private function meta_is_audio_model_by_name( $model_meta ): bool { + if ( ! method_exists( $model_meta, 'getId' ) ) { + return false; + } + $model = strtolower( (string) $model_meta->getId() ); + return false !== strpos( $model, 'transcribe' ) + || false !== strpos( $model, 'tts' ) + || false !== strpos( $model, 'realtime' ); + } + /** * Returns models from active/configured providers using the official AI Client registry flow. * From 84e075c5a78b3129f199eaf01d61a6b553410586 Mon Sep 17 00:00:00 2001 From: raftaar1191 Date: Sun, 29 Mar 2026 15:22:31 +0530 Subject: [PATCH 7/7] Update code as copilot suggested --- includes/Traits/AI_Utils.php | 1 - 1 file changed, 1 deletion(-) diff --git a/includes/Traits/AI_Utils.php b/includes/Traits/AI_Utils.php index 125f287dd..5cca432fe 100644 --- a/includes/Traits/AI_Utils.php +++ b/includes/Traits/AI_Utils.php @@ -386,7 +386,6 @@ protected function get_filtered_ai_models() { $provider_meta = $class_name::metadata(); foreach ( $class_name::modelMetadataDirectory()->listModelMetadata() as $model_meta ) { - if ( ! $this->supports_text_io_from_metadata( $model_meta ) ) { continue; }