PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.2.25
MxChat – AI Chatbot & Content Generation for WordPress v3.2.25
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-content-generator.php +74 -8 3.2.16 → 3.2.25 View file →
@@ -2242,8 +2242,22 @@
2242 2242
2243 2243 /**
2244 2244 * Call the configured content model
2245 2245 */
2246 + /**
2247 + * Provider slugs (MxChat_Model_Catalog keys) that call_content_model() can
2248 + * dispatch to. MUST mirror the if/elseif chain below — the Content Model
2249 + * picker derives its provider set from this list (plan ccb751), so a
2250 + * provider added to the dispatch without updating this is never offered,
2251 + * and one added here without dispatch support would break generation.
2252 + * OpenRouter and the custom OpenAI-compatible provider are absent on
2253 + * purpose: their pseudo-model ids would fall through to the OpenAI default
2254 + * branch with the wrong key and endpoint.
2255 + */
2256 + public static function supported_content_providers() {
2257 + return array('openai', 'claude', 'gemini', 'xai', 'deepseek');
2258 + }
2259 +
2246 2260 private function call_content_model($system_prompt, $messages, $max_tokens = 4096) {
2247 2261 $options = get_option('mxchat_options', array());
2248 2262 $model = $options['content_model'] ?? $options['model'] ?? 'gpt-5.6-sol';
2249 2263
@@ -2270,13 +2284,14 @@
2270 2284 * used only when the core model catalog is unavailable. Byte-identical to the
2271 2285 * old inline if/elseif at the call site.
2272 2286 */
2273 2287 private function mxchat_cg_reasoning_effort_fallback($model) {
2274 - if (strpos($model, 'gpt-5') !== 0) return null;
2288 + if (strpos($model, 'gpt-5') !== 0 && strpos($model, 'gpt-6') !== 0) return null; // gpt-6 too (plan 45c22d)
2275 2289 if ($model === 'gpt-5.2' || $model === 'gpt-5.1-chat-latest') return null;
2276 2290 if ($model === 'gpt-5.1-2025-11-13') return 'low';
2277 2291 if ($model === 'gpt-5.5') return 'low';
2278 2292 if ($model === 'gpt-5.4') return 'low';
2293 + if (in_array($model, array('gpt-6-astra', 'gpt-6-sol', 'gpt-6-luna'), true)) return 'low'; // plan 45c22d
2279 2294 if (in_array($model, array('gpt-5.6-sol', 'gpt-5.6-terra', 'gpt-5.6-luna'), true)) return 'low';
2280 2295 return 'minimal';
2281 2296 }
2282 2297
@@ -2297,9 +2312,9 @@
2297 2312 // GPT-5.x models require max_completion_tokens and only support temperature=1.
2298 2313 // Token key, temperature gate, and reasoning_effort are sourced from the
2299 2314 // core model catalog (plan-dcb71c); frozen fallbacks preserve exact
2300 2315 // pre-dcb71c behavior if the catalog class is unavailable.
2301 - $is_gpt5 = strpos($model, 'gpt-5') === 0;
2316 + $is_gpt5 = strpos($model, 'gpt-5') === 0 || strpos($model, 'gpt-6') === 0; // gpt-6 too (plan 45c22d)
2302 2317 $catalog = class_exists('MxChat_Model_Catalog');
2303 2318 $token_key = ($catalog && method_exists('MxChat_Model_Catalog', 'openai_token_param'))
2304 2319 ? MxChat_Model_Catalog::openai_token_param($model)
2305 2320 : ($is_gpt5 ? 'max_completion_tokens' : 'max_tokens');
@@ -2351,8 +2366,38 @@
2351 2366
2352 2367 $status_code = wp_remote_retrieve_response_code($response);
2353 2368 $decoded = json_decode(wp_remote_retrieve_body($response), true);
2354 2369
2370 + // plan-25b972 self-heal: supported reasoning_effort VALUES are per-model;
2371 + // a 400 rejecting the value we sent (stale catalog entry / provider
2372 + // drift) is deterministic — strip the param and retry ONCE.
2373 + if ($status_code === 400
2374 + && isset($body['reasoning_effort'])
2375 + && isset($decoded['error']['message'])
2376 + && is_string($decoded['error']['message'])
2377 + && preg_match('/Unsupported value:.*reasoning_effort/i', $decoded['error']['message'])) {
2378 + if (defined('WP_DEBUG') && WP_DEBUG) {
2379 + error_log(sprintf(
2380 + '[MxChat] content-generator: model %s rejected reasoning_effort \'%s\' — retrying once without the param (plan-25b972).',
2381 + $model, $body['reasoning_effort']
2382 + ));
2383 + }
2384 + unset($body['reasoning_effort']);
2385 + $response = wp_remote_post($endpoint, array(
2386 + 'headers' => array(
2387 + 'Authorization' => 'Bearer ' . $api_key,
2388 + 'Content-Type' => 'application/json',
2389 + ),
2390 + 'body' => wp_json_encode($body),
2391 + 'timeout' => $timeout,
2392 + ));
2393 + if (is_wp_error($response)) {
2394 + return $response;
2395 + }
2396 + $status_code = wp_remote_retrieve_response_code($response);
2397 + $decoded = json_decode(wp_remote_retrieve_body($response), true);
2398 + }
2399 +
2355 2400 if ($status_code !== 200) {
2356 2401 $error_msg = $decoded['error']['message'] ?? __('API request failed with status ', 'mxchat') . $status_code;
2357 2402 return new WP_Error('api_error', $error_msg);
2358 2403 }
@@ -2368,11 +2413,10 @@
2368 2413 * Call Claude (Anthropic) API
2369 2414 */
2370 2415 private function call_claude($model, $api_key, $system_prompt, $messages, $max_tokens) {
2371 2416 // Anthropic retired claude-opus-4-20250514 / claude-sonnet-4-20250514 on 2026-06-15.
2372 - // Read-time rescue: remap a saved dead ID to the current equivalent before the API call.
2373 - if ($model === 'claude-opus-4-20250514') { $model = 'claude-opus-4-8'; }
2374 - elseif ($model === 'claude-sonnet-4-20250514') { $model = 'claude-sonnet-4-6'; }
2417 + // Read-time rescue: a saved retired id is remapped to the catalog's replacement before the API call.
2418 + $model = MxChat_Model_Catalog::resolve_retired($model); // plan 8c4eb4: the catalog's one map
2375 2419 if (empty($api_key)) {
2376 2420 return new WP_Error('no_api_key', __('Claude API key not configured.', 'mxchat'));
2377 2421 }
2378 2422
@@ -2392,9 +2436,14 @@
2392 2436 'model' => $model,
2393 2437 'max_tokens' => $max_tokens,
2394 2438 'temperature' => 0.7,
2395 2439 'messages' => $formatted,
2396 - 'system' => $system_prompt,
2440 + // Prompt-cache breakpoint (plan 1ff43b): generation pipelines reuse
2441 + // the same system prompt across consecutive requests within the
2442 + // 5-minute cache window. Silently no-ops below the model minimum.
2443 + 'system' => trim((string) $system_prompt) === '' ? $system_prompt : array(
2444 + array('type' => 'text', 'text' => $system_prompt, 'cache_control' => array('type' => 'ephemeral')),
2445 + ),
2397 2446 );
2398 2447
2399 2448 // Anthropic removed temperature on Opus 4.7+ flagships (400 if sent) —
2400 2449 // sourced from the core model catalog (plan-dcb71c); frozen list fallback.
@@ -2506,12 +2555,29 @@
2506 2555 $error_msg = $decoded['error']['message'] ?? __('Gemini API error ', 'mxchat') . $status_code;
2507 2556 return new WP_Error('gemini_error', $error_msg);
2508 2557 }
2509 2558
2510 - if (isset($decoded['candidates'][0]['content']['parts'][0]['text'])) {
2511 - return trim($decoded['candidates'][0]['content']['parts'][0]['text']);
2559 + // plan 781afd: join every text part (skip thought parts), not only parts[0].
2560 + $text = '';
2561 + if (isset($decoded['candidates'][0]['content']['parts']) && is_array($decoded['candidates'][0]['content']['parts'])) {
2562 + foreach ($decoded['candidates'][0]['content']['parts'] as $part) {
2563 + if (is_array($part) && isset($part['text']) && is_string($part['text']) && empty($part['thought'])) {
2564 + $text .= $part['text'];
2565 + }
2566 + }
2512 2567 }
2568 + $text = trim($text);
2569 + if ($text !== '') {
2570 + return $text;
2571 + }
2513 2572
2573 + $no_text_reason = isset($decoded['candidates'][0]['finishReason']) && is_string($decoded['candidates'][0]['finishReason'])
2574 + ? $decoded['candidates'][0]['finishReason']
2575 + : (isset($decoded['promptFeedback']['blockReason']) && is_string($decoded['promptFeedback']['blockReason']) ? $decoded['promptFeedback']['blockReason'] : '');
2576 + if ($no_text_reason !== '') {
2577 + /* translators: %s: Gemini's finish or block reason, e.g. SAFETY */
2578 + return new WP_Error('gemini_no_text', sprintf(__('Gemini returned no text (%s).', 'mxchat'), $no_text_reason));
2579 + }
2514 2580 return new WP_Error('unexpected_response', __('Unexpected Gemini response format.', 'mxchat'));
2515 2581 }
2516 2582
2517 2583 // ─── Model Detection Helpers ────────────────────────────────────────