228 lines
7.1 KiB
PHP
228 lines
7.1 KiB
PHP
<?php
|
|
/**
|
|
* AI Provider Manager
|
|
*
|
|
* Routes AI requests to appropriate provider based on task type and configuration
|
|
*
|
|
* @package WP_Agentic_Writer
|
|
*/
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Result object containing provider instance plus selection metadata.
|
|
* Used to satisfy the DoD Provider Transparency contract.
|
|
*
|
|
* @since 0.2.0
|
|
*/
|
|
class WPAW_Provider_Selection_Result {
|
|
public $provider; // Provider instance
|
|
public $selected_provider; // Original requested provider name
|
|
public $actual_provider; // Actually used provider name (may differ if fallback)
|
|
public $fallback_used; // True if fallback occurred
|
|
public $warnings; // Array of warning messages
|
|
|
|
public function __construct( $provider, $selected, $actual, $fallback, $warnings = array() ) {
|
|
$this->provider = $provider;
|
|
$this->selected_provider = $selected;
|
|
$this->actual_provider = $actual;
|
|
$this->fallback_used = $fallback;
|
|
$this->warnings = $warnings;
|
|
}
|
|
}
|
|
|
|
class WP_Agentic_Writer_Provider_Manager {
|
|
/**
|
|
* Get provider instance for specific task type
|
|
*
|
|
* @param string $type Task type (chat, clarity, planning, writing, refinement, image).
|
|
* @return WPAW_Provider_Selection_Result Provider selection result with metadata.
|
|
*/
|
|
public static function get_provider_for_task( $type ) {
|
|
$settings = get_option( 'wp_agentic_writer_settings', array() );
|
|
$task_providers = $settings['task_providers'] ?? array();
|
|
$allow_openrouter_fallback = ! empty( $settings['allow_openrouter_fallback'] );
|
|
|
|
// Determine which provider to use for this task
|
|
$requested_provider = $task_providers[ $type ] ?? 'openrouter';
|
|
|
|
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
|
error_log( "WPAW Provider Manager: task={$type}, provider_name={$requested_provider}, task_providers=" . json_encode( $task_providers ) );
|
|
}
|
|
|
|
$warnings = array();
|
|
$fallback_used = false;
|
|
$actual_provider = $requested_provider;
|
|
|
|
// Get provider instance with fallback logic
|
|
$provider = self::get_provider_instance( $requested_provider, $type );
|
|
|
|
$can_fallback_to_openrouter = ( 'openrouter' === $requested_provider ) || $allow_openrouter_fallback;
|
|
|
|
// If provider not configured or unavailable.
|
|
if ( ! $provider || ! $provider->is_configured() ) {
|
|
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
|
error_log( "Provider '{$requested_provider}' not available for task '{$type}'" );
|
|
}
|
|
|
|
// Never silently spend OpenRouter credits when user selected another provider.
|
|
if ( ! $can_fallback_to_openrouter ) {
|
|
$warnings[] = "Provider '{$requested_provider}' unavailable. No automatic fallback was applied.";
|
|
return new WPAW_Provider_Selection_Result(
|
|
$provider,
|
|
$requested_provider,
|
|
$requested_provider,
|
|
false,
|
|
$warnings
|
|
);
|
|
}
|
|
|
|
$warnings[] = "Provider '{$requested_provider}' unavailable, fell back to OpenRouter";
|
|
$provider = WP_Agentic_Writer_OpenRouter_Provider::get_instance();
|
|
$actual_provider = 'openrouter';
|
|
$fallback_used = true;
|
|
}
|
|
|
|
// For local backend, verify it's actually reachable before using it
|
|
if ( 'local_backend' === $requested_provider && ! $fallback_used && method_exists( $provider, 'test_connection' ) ) {
|
|
$test_result = $provider->test_connection();
|
|
if ( is_wp_error( $test_result ) ) {
|
|
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
|
error_log( "Local Backend not reachable for task '{$type}'. Error: " . $test_result->get_error_message() );
|
|
}
|
|
if ( $can_fallback_to_openrouter ) {
|
|
$warnings[] = "Local Backend not reachable, fell back to OpenRouter.";
|
|
$provider = WP_Agentic_Writer_OpenRouter_Provider::get_instance();
|
|
$actual_provider = 'openrouter';
|
|
$fallback_used = true;
|
|
} else {
|
|
$warnings[] = "Local Backend not reachable. No automatic fallback was applied.";
|
|
}
|
|
}
|
|
}
|
|
|
|
return new WPAW_Provider_Selection_Result(
|
|
$provider,
|
|
$requested_provider,
|
|
$actual_provider,
|
|
$fallback_used,
|
|
$warnings
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get provider instance by name
|
|
*
|
|
* @param string $provider_name Provider identifier.
|
|
* @param string $task_type Task type for validation.
|
|
* @return WP_Agentic_Writer_AI_Provider_Interface|null Provider instance or null.
|
|
*/
|
|
private static function get_provider_instance( $provider_name, $task_type ) {
|
|
switch ( $provider_name ) {
|
|
case 'local_backend':
|
|
if ( ! class_exists( 'WP_Agentic_Writer_Local_Backend_Provider' ) ) {
|
|
require_once plugin_dir_path( __FILE__ ) . 'class-local-backend-provider.php';
|
|
}
|
|
$provider = new WP_Agentic_Writer_Local_Backend_Provider();
|
|
break;
|
|
|
|
case 'codex':
|
|
if ( ! class_exists( 'WP_Agentic_Writer_Codex_Provider' ) ) {
|
|
require_once plugin_dir_path( __FILE__ ) . 'class-codex-provider.php';
|
|
}
|
|
$provider = new WP_Agentic_Writer_Codex_Provider();
|
|
break;
|
|
|
|
case 'openrouter':
|
|
default:
|
|
$provider = WP_Agentic_Writer_OpenRouter_Provider::get_instance();
|
|
break;
|
|
}
|
|
|
|
// Validate provider supports this task type
|
|
if ( $provider && ! $provider->supports_task_type( $task_type ) ) {
|
|
error_log( "Provider '{$provider_name}' does not support task type '{$task_type}'" );
|
|
return null;
|
|
}
|
|
|
|
return $provider;
|
|
}
|
|
|
|
/**
|
|
* Get all available providers with their status
|
|
*
|
|
* @return array Array of provider info with name, status, supported tasks.
|
|
*/
|
|
public static function get_available_providers() {
|
|
$providers = array();
|
|
|
|
// OpenRouter (always available)
|
|
$openrouter = WP_Agentic_Writer_OpenRouter_Provider::get_instance();
|
|
$providers['openrouter'] = array(
|
|
'name' => 'OpenRouter',
|
|
'configured' => $openrouter->is_configured(),
|
|
'supports' => array( 'chat', 'clarity', 'planning', 'writing', 'refinement', 'image' ),
|
|
'icon' => '☁️',
|
|
);
|
|
|
|
// Local Backend
|
|
if ( class_exists( 'WP_Agentic_Writer_Local_Backend_Provider' ) ) {
|
|
$local = new WP_Agentic_Writer_Local_Backend_Provider();
|
|
$providers['local_backend'] = array(
|
|
'name' => 'Local Backend',
|
|
'configured' => $local->is_configured(),
|
|
'supports' => array( 'chat', 'clarity', 'planning', 'writing', 'refinement' ),
|
|
'icon' => '🏠',
|
|
);
|
|
}
|
|
|
|
// Codex
|
|
if ( class_exists( 'WP_Agentic_Writer_Codex_Provider' ) ) {
|
|
$codex = new WP_Agentic_Writer_Codex_Provider();
|
|
$providers['codex'] = array(
|
|
'name' => 'Codex (OpenAI)',
|
|
'configured' => $codex->is_configured(),
|
|
'supports' => array( 'chat', 'clarity', 'planning', 'writing', 'refinement' ),
|
|
'icon' => '🔗',
|
|
);
|
|
}
|
|
|
|
return $providers;
|
|
}
|
|
|
|
/**
|
|
* Test all configured providers
|
|
*
|
|
* @return array Results of connection tests.
|
|
*/
|
|
public static function test_all_providers() {
|
|
$results = array();
|
|
$providers = self::get_available_providers();
|
|
|
|
foreach ( $providers as $key => $info ) {
|
|
if ( ! $info['configured'] ) {
|
|
$results[ $key ] = array(
|
|
'success' => false,
|
|
'message' => 'Not configured',
|
|
);
|
|
continue;
|
|
}
|
|
|
|
$provider = self::get_provider_instance( $key, 'chat' );
|
|
if ( $provider ) {
|
|
$test_result = $provider->test_connection();
|
|
$results[ $key ] = is_wp_error( $test_result )
|
|
? array(
|
|
'success' => false,
|
|
'message' => $test_result->get_error_message(),
|
|
)
|
|
: $test_result;
|
|
}
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
}
|