Skip to content
159 changes: 159 additions & 0 deletions includes/Traits/AI_Utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,161 @@ 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 ) && ! $this->meta_has_text_generation_cap( $supported ) ) {
return false;
}
}

// Check input/output modality options for text support.
if ( method_exists( $model_meta, 'getSupportedOptions' ) ) {
$options = $model_meta->getSupportedOptions();
if ( is_array( $options ) ) {
$modality = $this->meta_get_modality_text_support( $options );
if ( null !== $modality['input'] || null !== $modality['output'] ) {
return (bool) $modality['input'] && (bool) $modality['output'];
}
}
}

return ! $this->meta_is_audio_model_by_name( $model_meta );
}

/**
* 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 ) && 'text' === strtolower( (string) $modality ) ) {
return true;
}
if ( 'text' === $text ) {
return true;
}
}
}

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.
*
Expand All @@ -231,6 +386,10 @@ 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;
}

$models[] = array(
'provider' => (string) $provider_id,
'provider_label' => (string) $provider_meta->getName(),
Expand Down
Loading