diff --git a/presshub-ai-editor/includes/class-api-client.php b/presshub-ai-editor/includes/class-api-client.php index 615e3a4..f8ea1d0 100644 --- a/presshub-ai-editor/includes/class-api-client.php +++ b/presshub-ai-editor/includes/class-api-client.php @@ -104,9 +104,30 @@ class PressHub_AI_API_Client { * provider default). */ public static function current_request_meta(): string { - $provider = (string) get_option( 'presshub_ai_provider', 'openai' ); - $model = (string) get_option( 'presshub_ai_model_' . $provider, '' ); - $max_tokens = (int) get_option( 'presshub_ai_max_tokens_' . $provider, PressHub_AI_Provider_Defaults::default_max_tokens() ); + $provider = ''; + $model = ''; + $max_tokens = 0; + + if ( class_exists( 'PressHub_AI_Provider_Store' ) ) { + $enabled = PressHub_AI_Provider_Store::get_all( true ); + if ( ! empty( $enabled ) && is_array( $enabled ) ) { + $prov = $enabled[0]; + $provider = (string) ( $prov['type'] ?? 'openai' ); + $model = (string) ( $prov['default_model'] ?? '' ); + $max_tokens = (int) ( $prov['max_tokens'] ?? PressHub_AI_Provider_Defaults::default_max_tokens() ); + } + } + + if ( '' === $provider ) { + $provider = (string) get_option( 'presshub_ai_provider', 'openai' ); + } + if ( '' === $model ) { + $model = (string) get_option( 'presshub_ai_model_' . $provider, 'default' ); + } + if ( $max_tokens <= 0 ) { + $max_tokens = (int) get_option( 'presshub_ai_max_tokens_' . $provider, PressHub_AI_Provider_Defaults::default_max_tokens() ); + } + return 'provider=' . $provider . ' model=' . ( '' !== $model ? $model : 'default' ) . ' max_tokens=' . $max_tokens; @@ -296,18 +317,11 @@ public static function resolve_module_config( string $module ): array { ); } - // 6. Temperature resolution + // 6. Temperature resolution: Module override -> Provider Store record -> Default $opt_temp = ( 'tts' === $module ) ? get_option( 'presshub_ai_briefing_tts_temperature', null ) : get_option( "presshub_ai_{$module}_temperature", null ); - if ( null === $opt_temp || '' === $opt_temp ) { - $opt_temp = get_option( 'presshub_ai_temperature_' . $provider_id, null ); - if ( ( null === $opt_temp || '' === $opt_temp ) && $provider_id !== $type ) { - $opt_temp = get_option( 'presshub_ai_temperature_' . $type, null ); - } - } - if ( null !== $opt_temp && '' !== $opt_temp ) { $temperature = max( 0.0, min( 2.0, (float) $opt_temp ) ); } elseif ( isset( $provider_record['temperature'] ) ) { @@ -316,18 +330,11 @@ public static function resolve_module_config( string $module ): array { $temperature = PressHub_AI_Provider_Defaults::default_temperature(); } - // 7. Max Tokens resolution + // 7. Max Tokens resolution: Module override -> Provider Store record -> Default $opt_tokens = ( 'tts' === $module ) ? get_option( 'presshub_ai_briefing_tts_max_tokens', null ) : get_option( "presshub_ai_{$module}_max_tokens", null ); - if ( null === $opt_tokens || '' === $opt_tokens ) { - $opt_tokens = get_option( 'presshub_ai_max_tokens_' . $provider_id, null ); - if ( ( null === $opt_tokens || '' === $opt_tokens ) && $provider_id !== $type ) { - $opt_tokens = get_option( 'presshub_ai_max_tokens_' . $type, null ); - } - } - if ( null !== $opt_tokens && '' !== $opt_tokens ) { $max_tokens = max( 1, (int) $opt_tokens ); } elseif ( isset( $provider_record['max_tokens'] ) ) { @@ -336,18 +343,11 @@ public static function resolve_module_config( string $module ): array { $max_tokens = PressHub_AI_Provider_Defaults::default_max_tokens(); } - // 8. Timeout resolution + // 8. Timeout resolution: Module override -> Provider Store record -> Default $opt_timeout = ( 'tts' === $module ) ? get_option( 'presshub_ai_briefing_tts_timeout', null ) : get_option( "presshub_ai_{$module}_timeout", null ); - if ( null === $opt_timeout || '' === $opt_timeout ) { - $opt_timeout = get_option( 'presshub_ai_timeout_' . $provider_id, null ); - if ( ( null === $opt_timeout || '' === $opt_timeout ) && $provider_id !== $type ) { - $opt_timeout = get_option( 'presshub_ai_timeout_' . $type, null ); - } - } - if ( null !== $opt_timeout && '' !== $opt_timeout ) { $timeout = max( 1, (int) $opt_timeout ); } elseif ( isset( $provider_record['timeout'] ) ) { @@ -524,6 +524,18 @@ public function __construct( $module_or_provider = null ) { if ( ! empty( $configured['api_key'] ) ) { $this->api_key = $configured['api_key']; } + if ( isset( $configured['temperature'] ) ) { + $this->temperature = (float) $configured['temperature']; + } + if ( isset( $configured['max_tokens'] ) ) { + $this->max_tokens = (int) $configured['max_tokens']; + } + if ( isset( $configured['timeout'] ) ) { + $this->timeout = (int) $configured['timeout']; + } + if ( ! empty( $configured['default_model'] ) ) { + $this->model = $configured['default_model']; + } } } @@ -861,6 +873,15 @@ public function test_connection( $provider = null ) { $this->current_action = 'custom_test'; if ( ! empty( $provider ) ) { $prov_record = PressHub_AI_Provider_Store::get( $provider ); + if ( null === $prov_record ) { + $all_provs = PressHub_AI_Provider_Store::get_all( false ); + foreach ( $all_provs as $p ) { + if ( ( $p['type'] ?? '' ) === $provider || ( $p['id'] ?? '' ) === $provider ) { + $prov_record = $p; + break; + } + } + } if ( $prov_record ) { $this->set_provider_config( $prov_record ); } else { diff --git a/presshub-ai-editor/presshub-ai-editor.php b/presshub-ai-editor/presshub-ai-editor.php index dee9483..e94d99d 100644 --- a/presshub-ai-editor/presshub-ai-editor.php +++ b/presshub-ai-editor/presshub-ai-editor.php @@ -2,7 +2,7 @@ /** * Plugin Name: PressHub AI Co-Pilot * Description: AI Co-Authoring and Editorial Workflow for PressHub. - * Version: 2.3.4 + * Version: 2.3.5 * Requires at least: 6.0 * Requires PHP: 7.4 * Tested up to: 6.7 @@ -42,7 +42,43 @@ function presshub_ai_migrate_max_tokens_defaults() { } add_action( 'admin_init', 'presshub_ai_migrate_max_tokens_defaults' ); -define( 'PRESSHUB_AI_VERSION', '2.3.4' ); +/** + * Purge legacy flat provider tuning options (max_tokens, temperature, timeout) + * from wp_options so they no longer shadow Provider Store records. + * Also migrate any uncustomized 16384 provider records in Provider Store to 20000. + */ +if ( ! function_exists( 'presshub_ai_migrate_purge_legacy_provider_tuning' ) ) { + function presshub_ai_migrate_purge_legacy_provider_tuning() { + if ( '1' === get_option( 'presshub_ai_migrated_provider_store_precedence', '0' ) ) { + return; + } + + foreach ( array( 'openai', 'anthropic', 'gemini' ) as $provider ) { + delete_option( 'presshub_ai_max_tokens_' . $provider ); + delete_option( 'presshub_ai_temperature_' . $provider ); + delete_option( 'presshub_ai_timeout_' . $provider ); + } + + if ( class_exists( 'PressHub_AI_Provider_Store' ) ) { + $providers = PressHub_AI_Provider_Store::get_all( false ); + $mutated = false; + foreach ( $providers as $i => $prov ) { + if ( isset( $prov['max_tokens'] ) && 16384 === (int) $prov['max_tokens'] ) { + $providers[ $i ]['max_tokens'] = 20000; + $mutated = true; + } + } + if ( $mutated ) { + update_option( PressHub_AI_Provider_Store::OPTION_CONFIGURED_PROVIDERS, $providers, false ); + } + } + + update_option( 'presshub_ai_migrated_provider_store_precedence', '1', false ); + } +} +add_action( 'admin_init', 'presshub_ai_migrate_purge_legacy_provider_tuning' ); + +define( 'PRESSHUB_AI_VERSION', '2.3.5' ); define( 'PRESSHUB_AI_DIR', plugin_dir_path( __FILE__ ) ); define( 'PRESSHUB_AI_URL', plugin_dir_url( __FILE__ ) ); diff --git a/presshub-ai-editor/tests/APIClientModularTest.php b/presshub-ai-editor/tests/APIClientModularTest.php index 5017145..3522ecd 100644 --- a/presshub-ai-editor/tests/APIClientModularTest.php +++ b/presshub-ai-editor/tests/APIClientModularTest.php @@ -126,6 +126,41 @@ public static function run(): void { $failures[] = "tts resolution failed. Got: " . var_export( $tts_cfg, true ); } + // ================================================================== + // 2b. Provider Store tuning precedence: $provider_record['max_tokens'] + // beats legacy flat options in wp_options. + // ================================================================== + PressHub_AI_Provider_Store::save_provider( [ + 'id' => 'tuning-precedence-prov', + 'type' => 'gemini', + 'name' => 'Tuning Precedence Gemini', + 'max_tokens' => 28000, + 'temperature' => 0.35, + 'timeout' => 150, + 'enabled' => true, + ] ); + $GLOBALS['OPTIONS_STORE']['presshub_ai_briefing_text_provider'] = 'tuning-precedence-prov'; + // Simulate stale legacy options from an upgraded install + $GLOBALS['OPTIONS_STORE']['presshub_ai_max_tokens_gemini'] = '10000'; + $GLOBALS['OPTIONS_STORE']['presshub_ai_temperature_gemini'] = '0.9'; + $GLOBALS['OPTIONS_STORE']['presshub_ai_timeout_gemini'] = '45'; + + $precedence_cfg = PressHub_AI_API_Client::resolve_module_config( 'briefing_text' ); + if ( $precedence_cfg['max_tokens'] !== 28000 ) { + $failures[] = "Provider Store max_tokens (28000) not honored; got: " . var_export( $precedence_cfg['max_tokens'], true ); + } + if ( $precedence_cfg['temperature'] !== 0.35 ) { + $failures[] = "Provider Store temperature (0.35) not honored; got: " . var_export( $precedence_cfg['temperature'], true ); + } + if ( $precedence_cfg['timeout'] !== 150 ) { + $failures[] = "Provider Store timeout (150) not honored; got: " . var_export( $precedence_cfg['timeout'], true ); + } + + // Clean up legacy options + unset( $GLOBALS['OPTIONS_STORE']['presshub_ai_max_tokens_gemini'] ); + unset( $GLOBALS['OPTIONS_STORE']['presshub_ai_temperature_gemini'] ); + unset( $GLOBALS['OPTIONS_STORE']['presshub_ai_timeout_gemini'] ); + // ================================================================== // 3. Fallback when module options are empty: first enabled provider in store // ================================================================== diff --git a/presshub-ai-editor/tests/MaxTokensMigrationTest.php b/presshub-ai-editor/tests/MaxTokensMigrationTest.php index a327651..6fffe40 100644 --- a/presshub-ai-editor/tests/MaxTokensMigrationTest.php +++ b/presshub-ai-editor/tests/MaxTokensMigrationTest.php @@ -53,16 +53,45 @@ function mtm_check( $label, $condition ) { presshub_ai_migrate_max_tokens_defaults(); mtm_check( 'second run is a no-op', $before === $GLOBALS['OPTIONS_STORE'] ); -// --- Case 3: migration flag already set → untouched stale values ------------ +// --- Case 4: purge legacy tuning options & migrate 16384 store records ------ $GLOBALS['OPTIONS_STORE'] = [ - 'presshub_ai_migrated_max_tokens' => '1', - 'presshub_ai_max_tokens_openai' => '2000', + 'presshub_ai_max_tokens_openai' => '10000', + 'presshub_ai_max_tokens_anthropic' => '4000', + 'presshub_ai_max_tokens_gemini' => '16384', + 'presshub_ai_temperature_openai' => '0.7', + 'presshub_ai_timeout_gemini' => '120', + 'presshub_ai_configured_providers' => [ + [ + 'id' => 'prov-1', + 'type' => 'gemini', + 'max_tokens' => 16384, // uncustomized old default + ], + [ + 'id' => 'prov-2', + 'type' => 'openai', + 'max_tokens' => 32000, // customized value + ], + ], ]; -presshub_ai_migrate_max_tokens_defaults(); -mtm_check( 'flag-set run leaves values alone', isset( $GLOBALS['OPTIONS_STORE']['presshub_ai_max_tokens_openai'] ) ); + +presshub_ai_migrate_purge_legacy_provider_tuning(); + +mtm_check( 'legacy max_tokens_openai purged', ! isset( $GLOBALS['OPTIONS_STORE']['presshub_ai_max_tokens_openai'] ) ); +mtm_check( 'legacy max_tokens_anthropic purged', ! isset( $GLOBALS['OPTIONS_STORE']['presshub_ai_max_tokens_anthropic'] ) ); +mtm_check( 'legacy max_tokens_gemini purged', ! isset( $GLOBALS['OPTIONS_STORE']['presshub_ai_max_tokens_gemini'] ) ); +mtm_check( 'legacy temperature_openai purged', ! isset( $GLOBALS['OPTIONS_STORE']['presshub_ai_temperature_openai'] ) ); +mtm_check( 'legacy timeout_gemini purged', ! isset( $GLOBALS['OPTIONS_STORE']['presshub_ai_timeout_gemini'] ) ); +mtm_check( 'uncustomized 16384 migrated to 20000', 20000 === ( $GLOBALS['OPTIONS_STORE']['presshub_ai_configured_providers'][0]['max_tokens'] ?? 0 ) ); +mtm_check( 'customized 32000 left unchanged', 32000 === ( $GLOBALS['OPTIONS_STORE']['presshub_ai_configured_providers'][1]['max_tokens'] ?? 0 ) ); +mtm_check( 'purge migration flag set', ( $GLOBALS['OPTIONS_STORE']['presshub_ai_migrated_provider_store_precedence'] ?? '' ) === '1' ); + +// Case 4b: Idempotent +$before_purge = $GLOBALS['OPTIONS_STORE']; +presshub_ai_migrate_purge_legacy_provider_tuning(); +mtm_check( 'purge migration is idempotent', $before_purge === $GLOBALS['OPTIONS_STORE'] ); if ( $failures > 0 ) { fwrite( STDERR, "MaxTokensMigrationTest: {$failures} failure(s)\n" ); exit( 1 ); } -echo "MaxTokensMigrationTest: OK (8 checks)\n"; +echo "MaxTokensMigrationTest: OK (17 checks)\n";