* @author Maximum Software Inc * @license GPL-3.0-only * @link https://agenistrator.com/ * * SPDX-FileCopyrightText: 2025-2026 Alex Ruban * SPDX-FileCopyrightText: 2025-2026 Maximum Software Inc * SPDX-License-Identifier: GPL-3.0-only */ namespace Agenistrator; // Direct access prevention if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Provider_Manager class. */ class Provider_Manager extends Manager { /** * List of registered provider types * * IMPORTANT: Provider type keys MUST follow the lowercase kebab-case convention * documented on Provider_Type::get_type_id() (e.g., 'openai', 'ollama-cloud'). * Lowercase is required because WordPress sanitize_key() lowercases input in * AJAX handlers. * * @var array */ private static $provider_types = array( 'openai' => Provider_OpenAI::class, 'anthropic' => Provider_Anthropic::class, 'openrouter' => Provider_OpenRouter::class, 'ollama-cloud' => Provider_Ollama_Cloud::class, ); /** * Provider instances cache * @var array */ private $instances = array(); /** * Constructor - initializes the manager * @since 1.0.0 */ protected function __construct() { // Call parent constructor with option suffix parent::__construct( 'providers' ); } /** * Get default configuration * * No default providers are created on activation. * Users will be guided to set up providers through the UI. * * @return array Default configuration with empty providers array * @since 1.0.0 */ protected function get_default_config() { // Return configuration with empty providers array return array( 'providers' => array(), ); } /** * Uninstall plugin * * Clears provider model cache transients and removes configuration. * @since 1.0.0 */ public function uninstall_plugin() { // Clear model cache transients for all providers before config is deleted foreach ( $this->get_all_providers() as $provider ) { $provider->clear_models_cache(); } // Call parent to delete option parent::uninstall_plugin(); } /** * Get all registered provider types * @return array Array of provider types * @since 1.0.0 */ public function get_provider_types() { return array_keys( self::$provider_types ); } /** * Get all registered provider types with their human-readable display names * * Returns a map of type ID to localized display name for use in UI surfaces * such as the provider type dropdown on the Add New Provider form. * * @return array Associative array of type_id => display_name * @since 1.0.1 */ public function get_provider_type_display_names() { $names = array(); foreach ( $this->get_provider_types() as $type_id ) { $provider = $this->create_temp_provider( $type_id ); $names[ $type_id ] = $provider->get_type_display_name(); } return $names; } /** * Check if a provider is enabled * @param string $provider_id The provider ID * @return bool True if enabled, false otherwise * @since 1.0.0 */ public function is_provider_enabled( $provider_id ) { return $this->get_config( array( 'providers', $provider_id, 'enabled' ), false ); } /** * Enable a provider * @param string $provider_id The provider ID * @return bool Success or failure * @since 1.0.0 */ public function enable_provider( $provider_id ) { // Check if provider exists if ( ! $this->get_config( array( 'providers', $provider_id ) ) ) { return false; } // Check if provider is already enabled if ( ! $this->get_config( array( 'providers', $provider_id, 'enabled' ), false ) ) { // Update the config and save return $this->set_config_and_save( array( 'providers', $provider_id, 'enabled' ), true ); } return true; } /** * Disable a provider * @param string $provider_id The provider ID * @return bool Success or failure * @since 1.0.0 */ public function disable_provider( $provider_id ) { // Check if provider exists if ( ! $this->get_config( array( 'providers', $provider_id ) ) ) { return false; } // Check if provider is already disabled if ( $this->get_config( array( 'providers', $provider_id, 'enabled' ), false ) ) { // Update the config and save return $this->set_config_and_save( array( 'providers', $provider_id, 'enabled' ), false ); } return true; } /** * Get a provider by ID * @param string $provider_id The provider ID * @return Provider_Type Provider instance * @since 1.0.0 */ public function get_provider( $provider_id ) { // Check if the provider exists in configuration $provider_config = $this->get_config( array( 'providers', $provider_id ) ); if ( ! $provider_config ) { // phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception messages are not HTML output throw new \Exception( String_Utils::replace_tags( __( 'Unknown provider: {provider_id}', 'agenistrator' ), array( 'provider_id' => $provider_id ) ) ); // phpcs:enable WordPress.Security.EscapeOutput.ExceptionNotEscaped } // Return from cache if available if ( isset( $this->instances[ $provider_id ] ) ) { return $this->instances[ $provider_id ]; } // Get provider type $type = $provider_config['type']; // Check if the provider type exists if ( ! isset( self::$provider_types[ $type ] ) ) { // phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception messages are not HTML output throw new \Exception( String_Utils::replace_tags( __( 'Unknown provider type: {type}', 'agenistrator' ), array( 'type' => $type ) ) ); // phpcs:enable WordPress.Security.EscapeOutput.ExceptionNotEscaped } $provider_class = self::$provider_types[ $type ]; // Check if the class exists if ( ! class_exists( $provider_class ) ) { // phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception messages are not HTML output throw new \Exception( String_Utils::replace_tags( __( 'Provider class not found: {provider_class}', 'agenistrator' ), array( 'provider_class' => $provider_class ) ) ); // phpcs:enable WordPress.Security.EscapeOutput.ExceptionNotEscaped } // Create provider instance with configuration $provider = new $provider_class( $provider_id, $provider_config['display_name'], $provider_config['parameters'] ); // Cache the instance $this->instances[ $provider_id ] = $provider; return $provider; } /** * Create a new provider * * @param string $type The provider type (must be lowercase) * @param string $display_name The display name * @param array $parameters The parameters * @return string The new provider ID (lowercase) * @since 1.0.0 */ public function create_provider( $type, $display_name, $parameters = array() ) { // Normalize type to lowercase for consistent lookups (sanitize_key() lowercases input) $type = strtolower( $type ); // Check if the provider type exists if ( ! isset( self::$provider_types[ $type ] ) ) { // phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception messages are not HTML output throw new \Exception( String_Utils::replace_tags( __( 'Unknown provider type: {type}', 'agenistrator' ), array( 'type' => $type ) ) ); // phpcs:enable WordPress.Security.EscapeOutput.ExceptionNotEscaped } // Generate a unique provider ID (lowercase for consistent lookups) $provider_id = $type . '_' . uniqid(); // Add to configurations and save $config = array( 'type' => $type, 'display_name' => $display_name, 'parameters' => $parameters, 'enabled' => true, // New providers are enabled by default ); $this->set_config_and_save( array( 'providers', $provider_id ), $config ); return $provider_id; } /** * Update a provider configuration * @param string $provider_id The provider ID * @param string $display_name The display name * @param array $parameters The parameters * @return bool Success or failure * @since 1.0.0 */ public function update_provider( $provider_id, $display_name, $parameters = null ) { // Check if the provider exists if ( ! $this->get_config( array( 'providers', $provider_id ) ) ) { return false; } // Update display name $this->set_config( array( 'providers', $provider_id, 'display_name' ), $display_name ); // Update parameters if provided if ( null !== $parameters ) { $this->set_config( array( 'providers', $provider_id, 'parameters' ), $parameters ); } // Clear instance from cache if ( isset( $this->instances[ $provider_id ] ) ) { unset( $this->instances[ $provider_id ] ); } // Save configurations $this->save_config(); return true; } /** * Delete a provider * @param string $provider_id The provider ID * @return bool Success or failure * @since 1.0.0 */ public function delete_provider( $provider_id ) { // Check if the provider exists if ( ! $this->get_config( array( 'providers', $provider_id ) ) ) { return false; } // Get a copy of the providers config $providers = $this->get_config( array( 'providers' ), array() ); // Remove from configurations unset( $providers[ $provider_id ] ); // Clear instance from cache if ( isset( $this->instances[ $provider_id ] ) ) { unset( $this->instances[ $provider_id ] ); } // Update the providers config and save return $this->set_config_and_save( array( 'providers' ), $providers ); } /** * Get all providers that are enabled and properly configured * @return array Array of enabled and properly configured Provider_Type instances * @since 1.0.0 */ public function get_enabled_providers() { $providers = $this->get_config( array( 'providers' ), array() ); $enabled_providers = array(); foreach ( $providers as $provider_id => $config ) { // Skip disabled providers if ( ! isset( $config['enabled'] ) || ! $config['enabled'] ) { continue; } try { $provider = $this->get_provider( $provider_id ); // Check if the provider has all required parameters set if ( $provider->is_configured() ) { $enabled_providers[ $provider_id ] = $provider; } } catch ( \Throwable $e ) { // Skip providers with errors continue; } } return $enabled_providers; } /** * Get all providers (including disabled ones) * @return array Array of all Provider_Type instances * @since 1.0.0 */ public function get_all_providers() { $providers_config = $this->get_config( array( 'providers' ), array() ); $providers = array(); foreach ( array_keys( $providers_config ) as $provider_id ) { try { $providers[ $provider_id ] = $this->get_provider( $provider_id ); } catch ( \Throwable $e ) { // Skip providers with errors continue; } } return $providers; } /** * Create a temporary provider for testing * * @param string $type The provider type (must be lowercase) * @param array $parameters The parameters to test * @return Provider_Type A temporary provider instance * @since 1.0.0 */ public function create_temp_provider( $type, $parameters = array() ) { // Normalize type to lowercase for consistent lookups (sanitize_key() lowercases input) $type = strtolower( $type ); // Check if the provider type exists if ( ! isset( self::$provider_types[ $type ] ) ) { // phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception messages are not HTML output throw new \Exception( String_Utils::replace_tags( __( 'Unknown provider type: {type}', 'agenistrator' ), array( 'type' => $type ) ) ); // phpcs:enable WordPress.Security.EscapeOutput.ExceptionNotEscaped } $provider_class = self::$provider_types[ $type ]; // Check if the class exists if ( ! class_exists( $provider_class ) ) { // phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception messages are not HTML output throw new \Exception( String_Utils::replace_tags( __( 'Provider class not found: {provider_class}', 'agenistrator' ), array( 'provider_class' => $provider_class ) ) ); // phpcs:enable WordPress.Security.EscapeOutput.ExceptionNotEscaped } // Create a temporary provider instance $provider = new $provider_class( 'temp_' . uniqid(), 'Temporary ' . $type, $parameters ); return $provider; } }