PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.2.24
MxChat – AI Chatbot & Content Generation for WordPress v3.2.24
3.2.25 3.2.24 3.2.23 3.2.22 3.2.21 3.2.20 3.2.19 3.2.18 3.2.17 3.2.16 3.2.15 3.2.14 3.2.12 3.2.13 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 All 156 releases
← All changes | includes/class-mxchat-model-catalog.php +165 -10 3.2.19 → 3.2.24 View file →
@@ -55,11 +55,15 @@
55 55 'label' => __('Google Gemini', 'mxchat'),
56 56 'key_option' => 'gemini_api_key',
57 57 'requires_key_to_load_models' => false,
58 58 'models' => array(
59 - 'gemini-3.5-flash' => array('label' => 'Gemini 3.5 Flash', 'description' => __('Stable — newest Flash generation, recommended default', 'mxchat')),
59 + 'gemini-3.8-flash' => array('label' => 'Gemini 3.8 Flash', 'description' => __('Stable — newest Flash generation, recommended default', 'mxchat')),
60 + 'gemini-3.7-flash' => array('label' => 'Gemini 3.7 Flash', 'description' => __('Stable — efficiency-first Flash', 'mxchat')),
61 + 'gemini-3.6-flash' => array('label' => 'Gemini 3.6 Flash', 'description' => __('Stable — previous Flash generation', 'mxchat')),
62 + 'gemini-3.5-flash' => array('label' => 'Gemini 3.5 Flash', 'description' => __('Stable — older Flash, priced above 3.8 Flash on every token', 'mxchat')),
60 63 'gemini-3.1-pro-preview' => array('label' => 'Gemini 3.1 Pro', 'description' => __('Preview — most intelligent, multimodal & agentic', 'mxchat')),
61 64 'gemini-3-flash-preview' => array('label' => 'Gemini 3 Flash', 'description' => __('Preview — balanced speed and scale', 'mxchat')),
65 + 'gemini-3.5-flash-lite' => array('label' => 'Gemini 3.5 Flash-Lite', 'description' => __('Stable — cheapest Gemini, no thinking', 'mxchat')),
62 66 'gemini-3.1-flash-lite' => array('label' => 'Gemini 3.1 Flash-Lite', 'description' => __('Stable — cost-efficient, high throughput', 'mxchat')),
63 67 'gemini-3.1-flash-lite-preview' => array('label' => 'Gemini 3.1 Flash-Lite (Preview)', 'description' => __('Preview — latest cost-efficient model', 'mxchat')),
64 68 'gemini-2.5-pro' => array('label' => 'Gemini 2.5 Pro', 'description' => __('Stable — advanced thinking, code, math & long context', 'mxchat')),
65 69 'gemini-2.5-flash' => array('label' => 'Gemini 2.5 Flash', 'description' => __('Stable — strong price-performance with thinking', 'mxchat')),
@@ -79,9 +83,18 @@
79 83 // 'deprecated_on' (Y-m-d) + 'replacement' are optional per-model
80 84 // fields: pickers label a future date as "retires <date>" and hide
81 85 // the entry once the date passes (a saved value keeps rendering via
82 86 // settings_dropdown_groups($current_model)).
83 - 'gpt-5.6-sol' => array('label' => 'GPT-5.6 Sol', 'description' => __('Recommended — newest OpenAI flagship for reasoning, coding and chat', 'mxchat')),
87 + // GPT-6 (announced 2026-09-22; plan 45c22d). Probed live on
88 + // 2026-09-27: all three need max_completion_tokens, reject any
89 + // temperature but 1 and reject reasoning_effort 'minimal';
90 + // Astra also rejects 'none'. Every "modern OpenAI" gate goes
91 + // through is_modern_openai() so these ids are not sent legacy
92 + // parameters (which 400 on the first message).
93 + 'gpt-6-astra' => array('label' => 'GPT-6 Astra', 'description' => __('Recommended — newest OpenAI flagship; strongest reasoning, coding and agentic work', 'mxchat')),
94 + 'gpt-6-sol' => array('label' => 'GPT-6 Sol', 'description' => __('Strong reasoning on demanding tasks at mid-tier cost', 'mxchat')),
95 + 'gpt-6-luna' => array('label' => 'GPT-6 Luna', 'description' => __('Fastest and cheapest GPT-6 for high-volume, repeatable work', 'mxchat')),
96 + 'gpt-5.6-sol' => array('label' => 'GPT-5.6 Sol', 'description' => __('Previous OpenAI flagship for reasoning, coding and chat', 'mxchat')),
84 97 'gpt-5.6-terra' => array('label' => 'GPT-5.6 Terra', 'description' => __('Balanced GPT-5.6 — strong capability at lower cost', 'mxchat')),
85 98 'gpt-5.6-luna' => array('label' => 'GPT-5.6 Luna', 'description' => __('Fastest and cheapest GPT-5.6 for lightweight tasks', 'mxchat')),
86 99 'gpt-5.5' => array('label' => 'GPT-5.5', 'description' => __('Previous flagship — powerful reasoning and coding model', 'mxchat')),
87 100 'gpt-5.4' => array('label' => 'GPT-5.4', 'description' => __('Flagship reasoning and coding model', 'mxchat')),
@@ -202,8 +215,64 @@
202 215 );
203 216 }
204 217
205 218 /**
219 + * Canonical retired-model map: every chat model id a provider has retired
220 + * (or hard-deprecated) that stored settings may still carry, mapped to its
221 + * current replacement (plan-mxchat-20260822-202df5).
222 + *
223 + * THE ONLY PLACE THIS MAPPING LIVES. mxchat_migrate_deprecated_models()
224 + * consumes it for the main chat + content models, and add-ons that store
225 + * their own model ids (mxchat-multi-bot's per-bot overrides) consume it for
226 + * theirs — a second hardcoded list in any consumer is how a stranded-model
227 + * bug recurs, so route every new retirement through this map.
228 + *
229 + * Claude ids map BY TIER so a rescued site lands on the current generation,
230 + * not an older intermediate (plan dc91bd). Never map onto a model that is
231 + * itself on a deprecation list (the e46b8f rule).
232 + *
233 + * Entry shape: retired_id => array(
234 + * 'to' => replacement model id (must be current, never itself retired),
235 + * 'label' => replacement display name for user-facing notices,
236 + * 'reason' => notice clause explaining the retirement; deliberately NOT
237 + * translated — migration messages are stored in an option and
238 + * rendered later, matching the existing migration notices.
239 + * )
240 + */
241 + public static function retired_model_map() {
242 + return array(
243 + // Anthropic Claude — Opus tier.
244 + 'claude-3-opus-20240229' => array('to' => 'claude-opus-5', 'label' => 'Claude Opus 5', 'reason' => 'because Anthropic retired the older Claude model'), // Retired Jan 5, 2026
245 + 'claude-opus-4-20250514' => array('to' => 'claude-opus-5', 'label' => 'Claude Opus 5', 'reason' => 'because Anthropic retired the older Claude model'), // Deprecated
246 + 'claude-opus-4-1-20250805' => array('to' => 'claude-opus-5', 'label' => 'Claude Opus 5', 'reason' => 'because Anthropic retired the older Claude model'), // Retired Aug 5, 2026 (first-party API)
247 + // Anthropic Claude — Sonnet tier.
248 + 'claude-3-5-sonnet-20240620' => array('to' => 'claude-sonnet-5', 'label' => 'Claude Sonnet 5', 'reason' => 'because Anthropic retired the older Claude model'), // Retired Oct 28, 2025
249 + 'claude-3-5-sonnet-20241022' => array('to' => 'claude-sonnet-5', 'label' => 'Claude Sonnet 5', 'reason' => 'because Anthropic retired the older Claude model'), // Retired Oct 28, 2025
250 + 'claude-3-7-sonnet-20250219' => array('to' => 'claude-sonnet-5', 'label' => 'Claude Sonnet 5', 'reason' => 'because Anthropic retired the older Claude model'), // Retired Feb 19, 2026
251 + 'claude-3-sonnet-20240229' => array('to' => 'claude-sonnet-5', 'label' => 'Claude Sonnet 5', 'reason' => 'because Anthropic retired the older Claude model'), // Legacy
252 + 'claude-sonnet-4-20250514' => array('to' => 'claude-sonnet-5', 'label' => 'Claude Sonnet 5', 'reason' => 'because Anthropic retired the older Claude model'), // Deprecated
253 + // Anthropic Claude — Haiku tier.
254 + 'claude-3-haiku-20240307' => array('to' => 'claude-haiku-4-5-20251001', 'label' => 'Claude Haiku 4.5', 'reason' => 'because Anthropic retired the older Claude model'), // Legacy
255 + 'claude-3-5-haiku-20241022' => array('to' => 'claude-haiku-4-5-20251001', 'label' => 'Claude Haiku 4.5', 'reason' => 'because Anthropic retired the older Claude model'), // Deprecated
256 + // OpenAI — GPT-4 series + GPT-3.5 Turbo (deprecated 2026-02-17).
257 + 'gpt-4o' => array('to' => 'gpt-5.6-sol', 'label' => 'GPT-5.6 Sol', 'reason' => 'due to OpenAI deprecating older models'),
258 + 'gpt-4.1-2025-04-14' => array('to' => 'gpt-5.6-sol', 'label' => 'GPT-5.6 Sol', 'reason' => 'due to OpenAI deprecating older models'),
259 + 'gpt-4-turbo' => array('to' => 'gpt-5.6-sol', 'label' => 'GPT-5.6 Sol', 'reason' => 'due to OpenAI deprecating older models'),
260 + 'gpt-4' => array('to' => 'gpt-5.6-sol', 'label' => 'GPT-5.6 Sol', 'reason' => 'due to OpenAI deprecating older models'),
261 + 'gpt-3.5-turbo' => array('to' => 'gpt-5.6-sol', 'label' => 'GPT-5.6 Sol', 'reason' => 'due to OpenAI deprecating older models'),
262 + // OpenAI — the gpt-5.x-chat-latest aliases retired 2026-08-10 (e46b8f).
263 + 'gpt-5.1-chat-latest' => array('to' => 'gpt-5.6-sol', 'label' => 'GPT-5.6 Sol', 'reason' => 'because OpenAI retires the GPT-5.x Chat Latest models on August 10, 2026'),
264 + 'gpt-5.3-chat-latest' => array('to' => 'gpt-5.6-sol', 'label' => 'GPT-5.6 Sol', 'reason' => 'because OpenAI retires the GPT-5.x Chat Latest models on August 10, 2026'),
265 + // OpenAI — mini tier.
266 + 'gpt-4o-mini' => array('to' => 'gpt-5-mini', 'label' => 'GPT-5 Mini', 'reason' => 'due to OpenAI deprecating GPT-4 series models'),
267 + 'gpt-4.1-mini' => array('to' => 'gpt-5-mini', 'label' => 'GPT-5 Mini', 'reason' => 'due to OpenAI deprecating GPT-4 series models'),
268 + // DeepSeek — removed at the vendor 2026-07-24 (hard cutoff, every request 400s).
269 + 'deepseek-chat' => array('to' => 'deepseek-v4-flash', 'label' => 'DeepSeek V4 Flash', 'reason' => 'because DeepSeek retired its older API models on July 24, 2026'),
270 + 'deepseek-reasoner' => array('to' => 'deepseek-v4-flash', 'label' => 'DeepSeek V4 Flash', 'reason' => 'because DeepSeek retired its older API models on July 24, 2026'),
271 + );
272 + }
273 +
274 + /**
206 275 * Returns the display label for the given chat-or-embedding provider slug.
207 276 * Chat providers checked first; embedding-only providers fall through.
208 277 */
209 278 public static function provider_label($slug) {
@@ -423,13 +492,22 @@
423 492 * Retired models (past 'deprecated_on') are hidden — EXCEPT the install's
424 493 * currently saved model, which is re-injected with a "retired" marker so
425 494 * the settings screen never renders a blank select (plan e46b8f).
426 495 *
427 - * @param string $current_model Optionally, the install's saved model id.
496 + * @param string $current_model Optionally, the install's saved model id.
497 + * @param array|null $provider_slugs Optional allowlist of provider slugs
498 + * (catalog keys, e.g. 'openai', 'claude').
499 + * null = every provider. Lets a consumer
500 + * that can only dispatch to some providers
501 + * (the content generator — plan ccb751)
502 + * derive its picker from the same catalog.
428 503 */
429 - public static function settings_dropdown_groups($current_model = '') {
504 + public static function settings_dropdown_groups($current_model = '', $provider_slugs = null) {
430 505 $out = array();
431 506 foreach (self::chat_models() as $provider_slug => $provider) {
507 + if (is_array($provider_slugs) && !in_array($provider_slug, $provider_slugs, true)) {
508 + continue;
509 + }
432 510 $out[$provider['label']] = array();
433 511 foreach ($provider['models'] as $id => $entry) {
434 512 $label = self::picker_label($entry);
435 513 if ($label === null && $id !== $current_model) {
@@ -606,9 +684,9 @@
606 684 $map = array(
607 685 'openai' => 'gpt-5.6-sol',
608 686 'claude' => 'claude-haiku-4-5-20251001',
609 687 'xai' => 'grok-4.6',
610 - 'gemini' => 'gemini-3.5-flash',
688 + 'gemini' => 'gemini-3.8-flash',
611 689 );
612 690 $model = isset($map[$provider]) ? $map[$provider] : '';
613 691 return (string) apply_filters('mxchat_default_vision_model', $model, $provider);
614 692 }
@@ -653,10 +731,29 @@
653 731 */
654 732 public static function reasoning_effort_for($model, $context = 'chat') {
655 733 $model = (string) $model;
656 734
657 - // Only the gpt-5 family carries reasoning_effort. Everything else omits.
658 - if (strpos($model, 'gpt-5') !== 0) {
735 + // plan a9774b: every Gemini 3.x output price is "including thinking
736 + // tokens" and the model thinks at its default level unless told
737 + // otherwise — measured on this site's own support prompt, 71% of
738 + // billed output on 3.8 Flash was hidden thinking, with indistinguishable
739 + // replies at 'low' (0 hidden tokens). So the chat surface sends 'low':
740 + // as reasoning_effort on the OpenAI-compatible endpoint, and as
741 + // generationConfig.thinkingConfig.thinkingLevel on the native one (see
742 + // gemini_thinking_config()). Flash-Lite does not think and returns 400
743 + // on any reasoning parameter — it must send nothing.
744 + if (strpos($model, 'gemini-3') === 0) {
745 + if (strpos($model, 'flash-lite') !== false) {
746 + return null;
747 + }
748 + return ($context === 'chat') ? 'low' : null;
749 + }
750 +
751 + // Only the modern OpenAI family (gpt-5*, gpt-6*) carries
752 + // reasoning_effort. Everything else omits. plan 45c22d widened this
753 + // from a literal gpt-5 prefix: a gpt-6 id used to fall through all
754 + // three maps below and be sent legacy parameters.
755 + if (!self::is_modern_openai($model)) {
659 756 return null;
660 757 }
661 758
662 759 switch ($context) {
@@ -663,8 +760,11 @@
663 760 case 'websearch':
664 761 // integrator web-search (Responses API). Explicit allowlist, no
665 762 // "else" — anything not listed omits reasoning entirely.
666 763 $map = array(
764 + 'gpt-6-astra' => 'low', // plan 45c22d, probed 2026-09-27 on /v1/responses
765 + 'gpt-6-sol' => 'low',
766 + 'gpt-6-luna' => 'low',
667 767 'gpt-5.6-sol' => 'low',
668 768 'gpt-5.6-terra' => 'low',
669 769 'gpt-5.6-luna' => 'low',
670 770 'gpt-5.5' => 'low',
@@ -683,8 +783,14 @@
683 783 // 400'd on every call); the original gpt-5/-mini/-nano still
684 784 // REQUIRE 'minimal' and reject 'none'. Do not "harmonize" these
685 785 // — the two generations genuinely differ.
686 786 $map = array(
787 + // plan 45c22d (probed 2026-09-27): gpt-6-astra accepts only
788 + // low|medium|high; gpt-6-sol/-luna accept none|low|medium|high.
789 + // 'low' for all three, as for their 5.6 siblings.
790 + 'gpt-6-astra' => 'low',
791 + 'gpt-6-sol' => 'low',
792 + 'gpt-6-luna' => 'low',
687 793 'gpt-5.6-sol' => 'low',
688 794 'gpt-5.6-terra' => 'low',
689 795 'gpt-5.6-luna' => 'low',
690 796 'gpt-5.5' => 'low',
@@ -718,8 +824,14 @@
718 824 }
719 825 if ($model === 'gpt-5.4') {
720 826 return 'none';
721 827 }
828 + // plan 45c22d (probed 2026-09-27): gpt-6-astra accepts only
829 + // low|medium|high; -sol/-luna accept none|low|medium|high.
830 + // 'low' for all three, as for their 5.6 siblings.
831 + if (in_array($model, array('gpt-6-astra', 'gpt-6-sol', 'gpt-6-luna'), true)) {
832 + return 'low';
833 + }
722 834 if (in_array($model, array('gpt-5.6-sol', 'gpt-5.6-terra', 'gpt-5.6-luna'), true)) {
723 835 return 'low';
724 836 }
725 837 // plan-25b972: the original gpt-5 generation REQUIRES 'minimal'
@@ -750,13 +862,35 @@
750 862 *
751 863 * @param string $model Chat model id.
752 864 * @return bool true if a custom temperature may be sent.
753 865 */
866 + /**
867 + * generationConfig.thinkingConfig for the native Gemini endpoint, or null
868 + * to send none (plan a9774b). Derived from reasoning_effort_for() so the
869 + * two Gemini paths — native generateContent and the OpenAI-compatible
870 + * endpoint — always agree on the thinking level. Overridable via
871 + * mxchat_gemini_thinking_level (return '' to send none).
872 + *
873 + * @param string $model Chat model id.
874 + * @return array|null e.g. array('thinkingLevel' => 'low').
875 + */
876 + public static function gemini_thinking_config($model) {
877 + $level = self::reasoning_effort_for($model, 'chat');
878 + if (strpos((string) $model, 'gemini-') !== 0) {
879 + return null;
880 + }
881 + $level = apply_filters('mxchat_gemini_thinking_level', $level, $model);
882 + if (!is_string($level) || $level === '' || !in_array($level, array('minimal', 'low', 'medium', 'high'), true)) {
883 + return null;
884 + }
885 + return array('thinkingLevel' => $level);
886 + }
887 +
754 888 public static function supports_temperature($model) {
755 889 $model = (string) $model;
756 890
757 - // gpt-5 family accepts only the default temperature (1).
758 - if (strpos($model, 'gpt-5') === 0) {
891 + // gpt-5 / gpt-6 families accept only the default temperature (1).
892 + if (self::is_modern_openai($model)) {
759 893 return false;
760 894 }
761 895
762 896 // Anthropic flagships that reject the temperature param outright.
@@ -785,7 +919,28 @@
785 919 * @param string $model Chat model id.
786 920 * @return string 'max_completion_tokens' | 'max_tokens'
787 921 */
788 922 public static function openai_token_param($model) {
789 - return strpos((string) $model, 'gpt-5') === 0 ? 'max_completion_tokens' : 'max_tokens';
923 + return self::is_modern_openai($model) ? 'max_completion_tokens' : 'max_tokens';
924 + }
925 +
926 + /**
927 + * plan 45c22d: ONE predicate for "a modern OpenAI chat model" — the
928 + * generations that take max_completion_tokens, only the default
929 + * temperature, and reasoning_effort. Every gate that used to test the
930 + * literal prefix gpt-5 routes through here, so the next generation is a
931 + * one-line change instead of nine edits. Before this, gpt-6-* ids fell
932 + * through all nine and were sent legacy parameters: a 400 on the first
933 + * message while looking fine in the picker.
934 + *
935 + * The frozen partial-upgrade fallbacks in the integrator, the content
936 + * generator and the editor assistant carry the same two-prefix test
937 + * inline (they exist for the window where this class is not loaded).
938 + *
939 + * @param string $model Chat model id.
940 + * @return bool
941 + */
942 + public static function is_modern_openai($model) {
943 + $model = (string) $model;
944 + return strpos($model, 'gpt-5') === 0 || strpos($model, 'gpt-6') === 0;
790 945 }
791 946 }