modelmix 4.4.20 → 4.4.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -1334,7 +1334,34 @@ class MixCustom {
1334
1334
  }
1335
1335
 
1336
1336
  static extractMessage(data) {
1337
- const message = data.choices[0].message?.content?.trim() || '';
1337
+ const choice = data?.choices?.[0] || {};
1338
+ const messageObj = choice.message || {};
1339
+ const finishReason = choice.finish_reason;
1340
+
1341
+ if (typeof messageObj.refusal === 'string' && messageObj.refusal.trim().length > 0) {
1342
+ throw new Error(`OpenAI model refused to process this request: ${messageObj.refusal}`);
1343
+ }
1344
+
1345
+ if (finishReason === 'content_filter') {
1346
+ throw new Error('OpenAI response was blocked by content_filter.');
1347
+ }
1348
+
1349
+ let message = '';
1350
+ if (typeof messageObj.content === 'string') {
1351
+ message = messageObj.content.trim();
1352
+ } else if (Array.isArray(messageObj.content)) {
1353
+ const refusalPart = messageObj.content.find(part => part?.type === 'refusal' || (typeof part?.refusal === 'string' && part.refusal.trim().length > 0));
1354
+ if (refusalPart) {
1355
+ const refusalText = typeof refusalPart.refusal === 'string' ? refusalPart.refusal : 'No refusal text provided.';
1356
+ throw new Error(`OpenAI model refused to process this request: ${refusalText}`);
1357
+ }
1358
+ message = messageObj.content
1359
+ .filter(part => typeof part?.text === 'string')
1360
+ .map(part => part.text)
1361
+ .join('')
1362
+ .trim();
1363
+ }
1364
+
1338
1365
  const endTagIndex = message.indexOf('</think>');
1339
1366
  if (message.startsWith('<think>') && endTagIndex !== -1) {
1340
1367
  return message.substring(endTagIndex + 8).trim();
@@ -2020,10 +2047,25 @@ class MixAnthropic extends MixCustom {
2020
2047
  }
2021
2048
 
2022
2049
  static extractMessage(data) {
2023
- if (data.content?.[1]?.text) {
2024
- return data.content[1].text;
2050
+ const content = Array.isArray(data?.content) ? data.content : [];
2051
+
2052
+ // Anthropic can return text in different positions depending on thinking/tool blocks.
2053
+ const textBlock = content.find(block => typeof block?.text === 'string' && block.text.trim().length > 0);
2054
+ if (textBlock) {
2055
+ return textBlock.text;
2056
+ }
2057
+
2058
+ // Empty/non-text content is often due to safety refusal or token limits.
2059
+ const stopReason = data?.stop_reason;
2060
+ const contentTypes = content.map(block => block?.type || 'unknown').join(', ') || 'none';
2061
+
2062
+ if (stopReason === 'refusal') {
2063
+ throw new Error('Anthropic refused to process this request (content policy). Try different wording or a fallback model.');
2064
+ }
2065
+ if (!content.length) {
2066
+ throw new Error(`Anthropic returned empty content (stop_reason: ${stopReason ?? 'unknown'}).`);
2025
2067
  }
2026
- return data.content[0].text;
2068
+ throw new Error(`Anthropic content blocks are missing .text (stop_reason: ${stopReason ?? 'unknown'}, content_types: ${contentTypes}).`);
2027
2069
  }
2028
2070
 
2029
2071
  static extractThink(data) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "modelmix",
3
- "version": "4.4.20",
3
+ "version": "4.4.22",
4
4
  "description": "🧬 Reliable interface with automatic fallback for AI LLMs.",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -1,12 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(node:*)",
5
- "Bash(npm install:*)",
6
- "Bash(mkdir:*)",
7
- "Bash(npm test)",
8
- "Bash(npm test:*)"
9
- ],
10
- "deny": []
11
- }
12
- }