| @@ -2413,11 +2413,10 @@ | ||
| 2413 | 2413 | * Call Claude (Anthropic) API |
| 2414 | 2414 | */ |
| 2415 | 2415 | private function call_claude($model, $api_key, $system_prompt, $messages, $max_tokens) { |
| 2416 | 2416 | // Anthropic retired claude-opus-4-20250514 / claude-sonnet-4-20250514 on 2026-06-15. |
| 2417 | - // Read-time rescue: remap a saved dead ID to the current equivalent before the API call. | |
| 2418 | - if ($model === 'claude-opus-4-20250514') { $model = 'claude-opus-4-8'; } | |
| 2419 | - 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 | |
| 2420 | 2419 | if (empty($api_key)) { |
| 2421 | 2420 | return new WP_Error('no_api_key', __('Claude API key not configured.', 'mxchat')); |
| 2422 | 2421 | } |
| 2423 | 2422 | |
| @@ -2556,12 +2555,29 @@ | ||
| 2556 | 2555 | $error_msg = $decoded['error']['message'] ?? __('Gemini API error ', 'mxchat') . $status_code; |
| 2557 | 2556 | return new WP_Error('gemini_error', $error_msg); |
| 2558 | 2557 | } |
| 2559 | 2558 | |
| 2560 | - if (isset($decoded['candidates'][0]['content']['parts'][0]['text'])) { | |
| 2561 | - 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 | + } | |
| 2562 | 2567 | } |
| 2568 | + $text = trim($text); | |
| 2569 | + if ($text !== '') { | |
| 2570 | + return $text; | |
| 2571 | + } | |
| 2563 | 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 | + } | |
| 2564 | 2580 | return new WP_Error('unexpected_response', __('Unexpected Gemini response format.', 'mxchat')); |
| 2565 | 2581 | } |
| 2566 | 2582 | |
| 2567 | 2583 | // ─── Model Detection Helpers ──────────────────────────────────────── |