getRegisteredProviderIds(); if ( ! is_array( $ids ) || array() === $ids ) { return false; } foreach ( $ids as $id ) { if ( is_string( $id ) && '' !== $id && AiClient::isConfigured( $id ) ) { return true; } } } catch ( Throwable $exception ) { unset( $exception ); return false; } return false; } /** * Test connectivity with a short prompt. * * @return true|WP_Error */ public static function test_connection() { if ( ! self::is_available() ) { return new WP_Error( 'aivexa_ai_unavailable', __( 'The WordPress AI Client is not available. Aivexa requires WordPress 7.0 or later.', 'aivexa' ) ); } if ( ! wp_supports_ai() ) { return new WP_Error( 'aivexa_ai_disabled', __( 'AI features are disabled on this site.', 'aivexa' ) ); } if ( ! self::is_provider_configured() ) { return new WP_Error( 'aivexa_no_provider', __( 'No AI provider is configured. Open Settings → Connectors, install an AI provider, and save an API key.', 'aivexa' ) ); } $builder = self::base_prompt( 'Reply with the single word: connected' ); $result = $builder->generate_text(); if ( is_wp_error( $result ) ) { return self::friendly_error( $result ); } return true; } /** * Build a configured prompt builder. * * @param string|Message|null $prompt Prompt. * @return WP_AI_Client_Prompt_Builder|WP_Error */ public static function base_prompt( $prompt = null ) { if ( ! self::is_available() ) { return new WP_Error( 'aivexa_ai_unavailable', __( 'The WordPress AI Client is not available.', 'aivexa' ) ); } $settings = Aivexa_Settings::get_all(); $builder = wp_ai_client_prompt( $prompt ); $builder = $builder ->using_system_instruction( (string) $settings['system_instructions'] ) ->using_max_tokens( max( 16, (int) $settings['max_output_tokens'] ) ) ->using_temperature( (float) $settings['temperature'] ); $model = trim( (string) $settings['model'] ); if ( '' !== $model && method_exists( $builder, 'using_model_preference' ) ) { $builder = $builder->using_model_preference( $model ); } return $builder; } /** * Convert stored conversation rows into AI Client Message objects. * * @param array> $rows Messages. * @return Message[] */ public static function messages_from_history( array $rows ): array { $messages = array(); foreach ( $rows as $row ) { $role = isset( $row['role'] ) ? (string) $row['role'] : 'user'; $content = isset( $row['content'] ) ? (string) $row['content'] : ''; if ( '' === trim( $content ) ) { continue; } $part = new MessagePart( $content ); if ( 'assistant' === $role || 'model' === $role ) { $messages[] = new ModelMessage( array( $part ) ); } else { $messages[] = new UserMessage( array( $part ) ); } } return $messages; } /** * Extract function calls from a generative result message. * * @param Message $message Message. * @return FunctionCall[] */ public static function extract_function_calls( Message $message ): array { $calls = array(); foreach ( $message->getParts() as $part ) { if ( $part->getType()->isFunctionCall() ) { $call = $part->getFunctionCall(); if ( $call instanceof FunctionCall ) { $calls[] = $call; } } } return $calls; } /** * Extract assistant text from a result. * * @param GenerativeAiResult $result Result. */ public static function extract_text( GenerativeAiResult $result ): string { try { $text = $result->toText(); return is_string( $text ) ? trim( $text ) : ''; } catch ( Throwable $exception ) { unset( $exception ); return ''; } } /** * Map AI Client / WP_Error into a visitor-safe error. * * @param WP_Error $error Error. */ public static function friendly_error( WP_Error $error ): WP_Error { $code = $error->get_error_code(); $message = Aivexa_Security::redact_sensitive( $error->get_error_message() ); if ( str_contains( strtolower( $message ), 'api key' ) || str_contains( strtolower( $message ), 'unauthorized' ) || str_contains( (string) $code, 'auth' ) ) { return new WP_Error( 'aivexa_ai_auth', __( 'AI authentication failed. Check Settings → Connectors and your provider API key.', 'aivexa' ), array( 'status' => 502 ) ); } if ( str_contains( strtolower( $message ), 'rate' ) || str_contains( (string) $code, 'rate' ) ) { return new WP_Error( 'aivexa_ai_rate_limited', __( 'The AI service is temporarily busy. Please try again later.', 'aivexa' ), array( 'status' => 429 ) ); } return new WP_Error( 'aivexa_ai_error', __( 'Sorry, the AI assistant is temporarily unavailable. Please try again later.', 'aivexa' ), array( 'status' => 502 ) ); } }