modelmix 4.2.2 β†’ 4.2.6

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.
@@ -0,0 +1,77 @@
1
+ process.loadEnvFile();
2
+ import { ModelMix } from '../index.js';
3
+ import ivm from 'isolated-vm';
4
+
5
+ console.log('🧬 ModelMix - JavaScript REPL Tool Demo');
6
+
7
+ // Crear isolate una sola vez (reutilizable)
8
+ const isolate = new ivm.Isolate({ memoryLimit: 128 }); // 128MB mΓ‘ximo
9
+
10
+ // Ejemplo simple: REPL de JavaScript para calcular potencias de 2
11
+ async function replPowersExample() {
12
+ console.log('\n=== JavaScript REPL - Potencias de 2 ===\n');
13
+ const gptArgs = { options: { reasoning_effort: "none", verbosity: null } };
14
+ const mmix = ModelMix.new({ config: { debug: true, max_history: 10 } })
15
+ .gpt41nano()
16
+ .gpt52(gptArgs)
17
+ .gemini3flash()
18
+ .setSystem('You are a helpful assistant with access to a JavaScript REPL. When you use the REPL and get results, always show them to the user in your response.');
19
+
20
+ // Variable para capturar el resultado de la herramienta
21
+ let toolResult = null;
22
+
23
+ // Agregar herramienta REPL personalizada
24
+ mmix.addTool({
25
+ name: "javascript_repl",
26
+ description: "Execute JavaScript code in a REPL environment. You can run any valid JavaScript code and get the result.",
27
+ inputSchema: {
28
+ type: "object",
29
+ properties: {
30
+ code: {
31
+ type: "string",
32
+ description: "JavaScript code to execute"
33
+ }
34
+ },
35
+ required: ["code"]
36
+ }
37
+ }, async ({ code }) => {
38
+ console.log('πŸ”§ Ejecutando cΓ³digo JavaScript:');
39
+ console.log('─'.repeat(50));
40
+ console.log(code);
41
+ console.log('─'.repeat(50));
42
+
43
+ try {
44
+ const context = await isolate.createContext();
45
+ const result = await context.eval(`JSON.stringify(eval(${JSON.stringify(code)}))`, { timeout: 10000 });
46
+ toolResult = JSON.parse(result);
47
+ console.log('\nβœ… Resultado:', toolResult);
48
+ return result;
49
+ } catch (error) {
50
+ console.log('\n❌ Error:', error.message);
51
+ return `Error: ${error.message}`;
52
+ }
53
+ });
54
+
55
+ // Pedir al modelo que calcule 100 potencias de 2
56
+ mmix.addText('Calcular las primeras 100 potencias de 2 (2^0 hasta 2^99). DespuΓ©s de ejecutar el cΓ³digo, menciona algunos valores del resultado como las primeras 5 y las ΓΊltimas 5 potencias.');
57
+
58
+ const result = await mmix.message();
59
+ console.log('\nπŸ’¬ Respuesta del modelo:');
60
+ console.log(result);
61
+
62
+ // Mostrar muestra del resultado si estΓ‘ disponible
63
+ if (toolResult && Array.isArray(toolResult)) {
64
+ console.log('\nπŸ“Š Muestra de resultados (primeros 10 y ΓΊltimos 10):');
65
+ console.log('Primeros 10:', toolResult.slice(0, 10));
66
+ console.log('Últimos 10:', toolResult.slice(-10));
67
+ console.log(`\nTotal: ${toolResult.length} potencias calculadas`);
68
+ }
69
+ }
70
+
71
+ try {
72
+ await replPowersExample();
73
+ console.log('\nβœ… Ejemplo completado');
74
+ } catch (error) {
75
+ console.error('❌ Error:', error);
76
+ }
77
+
@@ -0,0 +1,103 @@
1
+ process.loadEnvFile();
2
+ import { ModelMix, MixOpenAI } from '../index.js';
3
+
4
+ const prompt = "Say 'Hello World' in exactly 2 words.";
5
+
6
+ console.log('═══════════════════════════════════════════════════════════════');
7
+ console.log('DEMO: Verbose Modes in ModelMix');
8
+ console.log('═══════════════════════════════════════════════════════════════\n');
9
+
10
+
11
+ // ===================================================================
12
+ // VERBOSE LEVEL 0 - Silent Mode
13
+ // ===================================================================
14
+ console.log('─────────────────────────────────────────────────────────────');
15
+ console.log('1. VERBOSE LEVEL 0 - Silent Mode');
16
+ console.log(' No output at all, only the result');
17
+ console.log('─────────────────────────────────────────────────────────────\n');
18
+
19
+ await ModelMix
20
+ .new({ config: { verbose: 0 } })
21
+ .gpt41nano()
22
+ .addText(prompt)
23
+ .message();
24
+
25
+
26
+ // ===================================================================
27
+ // VERBOSE LEVEL 1 - Minimal Mode
28
+ // ===================================================================
29
+ console.log('─────────────────────────────────────────────────────────────');
30
+ console.log('2. VERBOSE LEVEL 1 - Minimal Mode');
31
+ console.log(' Shows: β†’ [model] #N and βœ“ Success');
32
+ console.log('─────────────────────────────────────────────────────────────\n');
33
+
34
+ await ModelMix
35
+ .new({ config: { verbose: 1 } })
36
+ .gpt41nano()
37
+ .addText(prompt)
38
+ .message();
39
+
40
+ // ===================================================================
41
+ // VERBOSE LEVEL 2 - Readable Summary (DEFAULT)
42
+ // ===================================================================
43
+ console.log('─────────────────────────────────────────────────────────────');
44
+ console.log('3. VERBOSE LEVEL 2 - Readable Summary (DEFAULT)');
45
+ console.log(' Shows: model, system prompt, input, message count, output');
46
+ console.log(' Everything in compact format on 2 lines');
47
+ console.log('─────────────────────────────────────────────────────────────\n');
48
+
49
+ await ModelMix
50
+ .new({ config: { verbose: 2 } })
51
+ .gpt41nano()
52
+ .addText(prompt)
53
+ .json({ message: 'string' });
54
+
55
+ // ===================================================================
56
+ // VERBOSE LEVEL 3 - Full Debug
57
+ // ===================================================================
58
+ console.log('─────────────────────────────────────────────────────────────');
59
+ console.log('4. VERBOSE LEVEL 3 - Full Debug Mode');
60
+ console.log(' Shows: everything from level 2 + raw response, full message,');
61
+ console.log(' request details, config, and options');
62
+ console.log('─────────────────────────────────────────────────────────────\n');
63
+
64
+ await ModelMix
65
+ .new({ config: { verbose: 3 } })
66
+ .gpt41nano()
67
+ .addText(prompt)
68
+ .message();
69
+
70
+ // ===================================================================
71
+ // FALLBACK EXAMPLE (with verbose 2)
72
+ // ===================================================================
73
+ console.log('─────────────────────────────────────────────────────────────');
74
+ console.log('5. FALLBACK EXAMPLE (Verbose Level 2)');
75
+ console.log(' Shows how fallback models are displayed');
76
+ console.log('─────────────────────────────────────────────────────────────\n');
77
+
78
+ try {
79
+ const resultFallback = await ModelMix
80
+ .new({ config: { verbose: 2 } })
81
+ .attach('fake-model-that-will-fail', new MixOpenAI())
82
+ .gpt41nano() // This will be the fallback
83
+ .addText(prompt)
84
+ .message();
85
+
86
+ console.log(`Result: ${resultFallback}\n`);
87
+ } catch (error) {
88
+ console.log(`Error (should not happen): ${error.message}\n`);
89
+ }
90
+
91
+
92
+ // ===================================================================
93
+ // SUMMARY
94
+ // ===================================================================
95
+ console.log('═══════════════════════════════════════════════════════════════');
96
+ console.log('DEMO COMPLETED');
97
+ console.log('═══════════════════════════════════════════════════════════════\n');
98
+ console.log('Summary:');
99
+ console.log(' - Level 0: Silent, no logs');
100
+ console.log(' - Level 1: Minimal (β†’ model, βœ“ Success)');
101
+ console.log(' - Level 2: Readable (1 line input + 1 line output) [DEFAULT]');
102
+ console.log(' - Level 3: Full debug (includes raw responses and configs)');
103
+ console.log('');
package/index.js CHANGED
@@ -20,7 +20,7 @@ class ModelMix {
20
20
  this.mcp = {};
21
21
  this.mcpToolsManager = new MCPToolsManager();
22
22
  this.options = {
23
- max_tokens: 5000,
23
+ max_tokens: 8192,
24
24
  temperature: 1, // 1 --> More creative, 0 --> More deterministic.
25
25
  ...options
26
26
  };
@@ -35,6 +35,7 @@ class ModelMix {
35
35
  system: 'You are an assistant.',
36
36
  max_history: 1, // Default max history
37
37
  debug: false,
38
+ verbose: 2, // 0=silent, 1=minimal, 2=readable summary, 3=full details
38
39
  bottleneck: defaultBottleneckConfig,
39
40
  ...config
40
41
  }
@@ -45,7 +46,6 @@ class ModelMix {
45
46
 
46
47
  }
47
48
 
48
-
49
49
  replace(keyValues) {
50
50
  this.config.replace = { ...this.config.replace, ...keyValues };
51
51
  return this;
@@ -55,8 +55,8 @@ class ModelMix {
55
55
  return new ModelMix({ options, config, mix });
56
56
  }
57
57
 
58
- new() {
59
- return new ModelMix({ options: this.options, config: this.config });
58
+ new({ options = {}, config = {}, mix = {} } = {}) {
59
+ return new ModelMix({ options: { ...this.options, ...options }, config: { ...this.config, ...config }, mix: { ...this.mix, ...mix } });
60
60
  }
61
61
 
62
62
  static formatJSON(obj) {
@@ -79,6 +79,69 @@ class ModelMix {
79
79
  }
80
80
  }
81
81
 
82
+ // Verbose logging helpers
83
+ static truncate(str, maxLen = 100) {
84
+ if (!str || typeof str !== 'string') return str;
85
+ return str.length > maxLen ? str.substring(0, maxLen) + '...' : str;
86
+ }
87
+
88
+ static getVerboseLevel(config) {
89
+ // debug=true acts as verbose level 3
90
+ return config.verbose || 0;
91
+ }
92
+
93
+ static verboseLog(level, config, ...args) {
94
+ const verboseLevel = ModelMix.getVerboseLevel(config);
95
+ if (verboseLevel >= level) {
96
+ console.log(...args);
97
+ }
98
+ }
99
+
100
+ static formatInputSummary(messages, system) {
101
+ const lastMessage = messages[messages.length - 1];
102
+ let inputText = '';
103
+
104
+ if (lastMessage && Array.isArray(lastMessage.content)) {
105
+ const textContent = lastMessage.content.find(c => c.type === 'text');
106
+ if (textContent) inputText = textContent.text;
107
+ } else if (lastMessage && typeof lastMessage.content === 'string') {
108
+ inputText = lastMessage.content;
109
+ }
110
+
111
+ const systemStr = `System: ${ModelMix.truncate(system, 50)}`;
112
+ const inputStr = `Input: ${ModelMix.truncate(inputText, 120)}`;
113
+ const msgCount = `(${messages.length} msg${messages.length !== 1 ? 's' : ''})`;
114
+
115
+ return `${systemStr} \n| ${inputStr} ${msgCount}`;
116
+ }
117
+
118
+ static formatOutputSummary(result, verboseLevel = 2) {
119
+ const parts = [];
120
+ if (result.message) {
121
+ // Try to parse as JSON for better formatting
122
+ try {
123
+ const parsed = JSON.parse(result.message.trim());
124
+ // If it's valid JSON and verbose >= 2, show it formatted
125
+ if (verboseLevel >= 2) {
126
+ parts.push(`Output (JSON):\n${ModelMix.formatJSON(parsed)}`);
127
+ } else {
128
+ parts.push(`Output: ${ModelMix.truncate(result.message, 150)}`);
129
+ }
130
+ } catch (e) {
131
+ // Not JSON, show truncated as before
132
+ parts.push(`Output: ${ModelMix.truncate(result.message, 150)}`);
133
+ }
134
+ }
135
+ if (result.think) {
136
+ parts.push(`Thinking: ${ModelMix.truncate(result.think, 80)}`);
137
+ }
138
+ if (result.toolCalls && result.toolCalls.length > 0) {
139
+ const toolNames = result.toolCalls.map(t => t.function?.name || t.name).join(', ');
140
+ parts.push(`Tools: ${toolNames}`);
141
+ }
142
+ return parts.join(' | ');
143
+ }
144
+
82
145
  attach(key, provider) {
83
146
 
84
147
  if (this.models.some(model => model.key === key)) {
@@ -102,9 +165,6 @@ class ModelMix {
102
165
  gpt41nano({ options = {}, config = {} } = {}) {
103
166
  return this.attach('gpt-4.1-nano', new MixOpenAI({ options, config }));
104
167
  }
105
- gpt4o({ options = {}, config = {} } = {}) {
106
- return this.attach('gpt-4o', new MixOpenAI({ options, config }));
107
- }
108
168
  o4mini({ options = {}, config = {} } = {}) {
109
169
  return this.attach('o4-mini', new MixOpenAI({ options, config }));
110
170
  }
@@ -276,8 +336,11 @@ class ModelMix {
276
336
  return this.attach('MiniMax-M2', new MixMiniMax({ options, config }));
277
337
  }
278
338
 
279
- minimaxM21({ options = {}, config = {} } = {}) {
280
- return this.attach('MiniMax-M2.1', new MixMiniMax({ options, config }));
339
+ minimaxM21({ options = {}, config = {}, mix = { minimax: true } } = {}) {
340
+ mix = { ...this.mix, ...mix };
341
+ if (mix.minimax) this.attach('MiniMax-M2.1', new MixMiniMax({ options, config }));
342
+ if (mix.cerebras) this.attach('MiniMax-M2.1', new MixCerebras({ options, config }));
343
+ return this;
281
344
  }
282
345
 
283
346
  minimaxM2Stable({ options = {}, config = {} } = {}) {
@@ -292,8 +355,10 @@ class ModelMix {
292
355
  }
293
356
 
294
357
  GLM47({ options = {}, config = {}, mix = { fireworks: true } } = {}) {
358
+ mix = { ...this.mix, ...mix };
295
359
  if (mix.fireworks) this.attach('accounts/fireworks/models/glm-4p7', new MixFireworks({ options, config }));
296
360
  if (mix.openrouter) this.attach('z-ai/glm-4.7', new MixOpenRouter({ options, config }));
361
+ if (mix.cerebras) this.attach('zai-glm-4.7', new MixCerebras({ options, config }));
297
362
  return this;
298
363
  }
299
364
 
@@ -656,9 +721,19 @@ class ModelMix {
656
721
  ...config,
657
722
  };
658
723
 
659
- if (currentConfig.debug) {
724
+ const verboseLevel = ModelMix.getVerboseLevel(currentConfig);
725
+
726
+ if (verboseLevel >= 1) {
660
727
  const isPrimary = i === 0;
661
- log.debug(`[${currentModelKey}] Attempt #${i + 1}` + (isPrimary ? ' (Primary)' : ' (Fallback)'));
728
+ const prefix = isPrimary ? 'β†’' : '↻';
729
+ const suffix = isPrimary ? '' : ' (fallback)';
730
+ const header = `\n${prefix} [${currentModelKey}] #${i + 1}${suffix}`;
731
+
732
+ if (verboseLevel >= 2) {
733
+ console.log(`${header} | ${ModelMix.formatInputSummary(this.messages, currentConfig.system)}`);
734
+ } else {
735
+ console.log(header);
736
+ }
662
737
  }
663
738
 
664
739
  try {
@@ -692,27 +767,36 @@ class ModelMix {
692
767
  return this.execute();
693
768
  }
694
769
 
695
- if (currentConfig.debug) {
696
- console.log(`\nRequest successful: ${currentModelKey}`);
770
+ // Verbose level 1: Just success indicator
771
+ if (verboseLevel === 1) {
772
+ console.log(`βœ“ Success`);
773
+ }
774
+
775
+ // Verbose level 2: Readable summary of output
776
+ if (verboseLevel >= 2) {
777
+ console.log(`βœ“ ${ModelMix.formatOutputSummary(result, verboseLevel).trim()}`);
778
+ }
697
779
 
780
+ // Verbose level 3 (debug): Full response details
781
+ if (verboseLevel >= 3) {
698
782
  if (result.response) {
699
- console.log('\nRAW RESPONSE:');
783
+ console.log('\n[RAW RESPONSE]');
700
784
  console.log(ModelMix.formatJSON(result.response));
701
785
  }
702
786
 
703
787
  if (result.message) {
704
- console.log('\nMESSAGE:');
788
+ console.log('\n[FULL MESSAGE]');
705
789
  console.log(ModelMix.formatMessage(result.message));
706
790
  }
707
791
 
708
792
  if (result.think) {
709
- console.log('\nTHINKING:');
793
+ console.log('\n[FULL THINKING]');
710
794
  console.log(result.think);
711
795
  }
712
-
713
- console.log('');
714
796
  }
715
797
 
798
+ if (verboseLevel >= 1) console.log('');
799
+
716
800
  return result;
717
801
 
718
802
  } catch (error) {
@@ -940,15 +1024,19 @@ class MixCustom {
940
1024
 
941
1025
  options.messages = this.convertMessages(options.messages, config);
942
1026
 
943
- if (config.debug) {
944
- console.log('\nREQUEST:');
1027
+ const verboseLevel = ModelMix.getVerboseLevel(config);
945
1028
 
946
- console.log('\nCONFIG:');
1029
+ // Verbose level 3 (debug): Full request details
1030
+ if (verboseLevel >= 3) {
1031
+ console.log('\n[REQUEST DETAILS]');
1032
+
1033
+ console.log('\n[CONFIG]');
947
1034
  const configToLog = { ...config };
948
1035
  delete configToLog.debug;
1036
+ delete configToLog.verbose;
949
1037
  console.log(ModelMix.formatJSON(configToLog));
950
1038
 
951
- console.log('\nOPTIONS:');
1039
+ console.log('\n[OPTIONS]');
952
1040
  console.log(ModelMix.formatJSON(options));
953
1041
  }
954
1042
 
@@ -1680,7 +1768,8 @@ class MixGoogle extends MixCustom {
1680
1768
  functionCall: {
1681
1769
  name: toolCall.function.name,
1682
1770
  args: JSON.parse(toolCall.function.arguments)
1683
- }
1771
+ },
1772
+ thought_signature: toolCall.thought_signature || ""
1684
1773
  }))
1685
1774
  }
1686
1775
  }
@@ -1766,15 +1855,19 @@ class MixGoogle extends MixCustom {
1766
1855
  };
1767
1856
 
1768
1857
  try {
1769
- if (config.debug) {
1770
- console.log('\nREQUEST (GOOGLE):');
1858
+ const verboseLevel = ModelMix.getVerboseLevel(config);
1771
1859
 
1772
- console.log('\nCONFIG:');
1860
+ // Verbose level 3 (debug): Full request details
1861
+ if (verboseLevel >= 3) {
1862
+ console.log('\n[REQUEST DETAILS - GOOGLE]');
1863
+
1864
+ console.log('\n[CONFIG]');
1773
1865
  const configToLog = { ...config };
1774
1866
  delete configToLog.debug;
1867
+ delete configToLog.verbose;
1775
1868
  console.log(ModelMix.formatJSON(configToLog));
1776
1869
 
1777
- console.log('\nPAYLOAD:');
1870
+ console.log('\n[PAYLOAD]');
1778
1871
  console.log(ModelMix.formatJSON(payload));
1779
1872
  }
1780
1873
 
@@ -1808,7 +1901,8 @@ class MixGoogle extends MixCustom {
1808
1901
  function: {
1809
1902
  name: part.functionCall.name,
1810
1903
  arguments: JSON.stringify(part.functionCall.args)
1811
- }
1904
+ },
1905
+ thought_signature: part.thoughtSignature || ""
1812
1906
  };
1813
1907
  }
1814
1908
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "modelmix",
3
- "version": "4.2.2",
3
+ "version": "4.2.6",
4
4
  "description": "🧬 Reliable interface with automatic fallback for AI LLMs.",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -46,7 +46,7 @@
46
46
  },
47
47
  "homepage": "https://github.com/clasen/ModelMix#readme",
48
48
  "dependencies": {
49
- "@modelcontextprotocol/sdk": "^1.23.0",
49
+ "@modelcontextprotocol/sdk": "^1.25.2",
50
50
  "axios": "^1.12.2",
51
51
  "bottleneck": "^2.19.5",
52
52
  "file-type": "^16.5.4",
@@ -72,7 +72,7 @@ describe('Rate Limiting with Bottleneck Tests', () => {
72
72
  it('should enforce minimum time between requests', async () => {
73
73
  const startTimes = [];
74
74
 
75
- model.gpt4o();
75
+ model.gpt41();
76
76
 
77
77
  // Mock API responses
78
78
  nock('https://api.openai.com')
@@ -122,7 +122,7 @@ describe('Rate Limiting with Bottleneck Tests', () => {
122
122
  }
123
123
  });
124
124
 
125
- model.gpt4o();
125
+ model.gpt41();
126
126
 
127
127
  // Mock API with delay to simulate concurrent requests
128
128
  nock('https://api.openai.com')
@@ -184,7 +184,7 @@ describe('Rate Limiting with Bottleneck Tests', () => {
184
184
  it('should apply rate limiting to OpenAI requests', async () => {
185
185
  const requestTimes = [];
186
186
 
187
- model.gpt4o();
187
+ model.gpt41();
188
188
 
189
189
  nock('https://api.openai.com')
190
190
  .post('/v1/chat/completions')
@@ -267,7 +267,7 @@ describe('Rate Limiting with Bottleneck Tests', () => {
267
267
  });
268
268
 
269
269
  it('should handle rate limiting with API errors', async () => {
270
- model.gpt4o();
270
+ model.gpt41();
271
271
 
272
272
  nock('https://api.openai.com')
273
273
  .post('/v1/chat/completions')
@@ -289,7 +289,7 @@ describe('Rate Limiting with Bottleneck Tests', () => {
289
289
  it('should continue rate limiting after errors', async () => {
290
290
  const requestTimes = [];
291
291
 
292
- model.gpt4o();
292
+ model.gpt41();
293
293
 
294
294
  // First request fails
295
295
  nock('https://api.openai.com')
@@ -345,7 +345,7 @@ describe('Rate Limiting with Bottleneck Tests', () => {
345
345
  }
346
346
  });
347
347
 
348
- model.gpt4o();
348
+ model.gpt41();
349
349
 
350
350
  let requestCount = 0;
351
351
 
@@ -392,7 +392,7 @@ describe('Rate Limiting with Bottleneck Tests', () => {
392
392
  }
393
393
  });
394
394
 
395
- model.gpt4o();
395
+ model.gpt41();
396
396
 
397
397
  const results = [];
398
398
 
@@ -440,7 +440,7 @@ describe('Rate Limiting with Bottleneck Tests', () => {
440
440
  }
441
441
  });
442
442
 
443
- model.gpt4o();
443
+ model.gpt41();
444
444
 
445
445
  nock('https://api.openai.com')
446
446
  .post('/v1/chat/completions')
@@ -489,7 +489,7 @@ describe('Rate Limiting with Bottleneck Tests', () => {
489
489
  done();
490
490
  });
491
491
 
492
- model.gpt4o();
492
+ model.gpt41();
493
493
 
494
494
  nock('https://api.openai.com')
495
495
  .post('/v1/chat/completions')
@@ -25,7 +25,7 @@ describe('Image Processing and Multimodal Support Tests', () => {
25
25
  it('should handle base64 image data correctly', async () => {
26
26
  const base64Image = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAFUlEQVR42mP8z8BQz0AEYBxVSF+FABJADveWkH6oAAAAAElFTkSuQmCC';
27
27
 
28
- model.gpt4o()
28
+ model.gpt41()
29
29
  .addText('What do you see in this image?')
30
30
  .addImageFromUrl(base64Image);
31
31
 
package/test/json.test.js CHANGED
@@ -198,7 +198,7 @@ describe('JSON Schema and Structured Output Tests', () => {
198
198
  }]
199
199
  };
200
200
 
201
- model.gpt4o().addText('List 3 countries');
201
+ model.gpt41().addText('List 3 countries');
202
202
 
203
203
  // Mock the API response
204
204
  nock('https://api.openai.com')
@@ -270,7 +270,7 @@ describe('JSON Schema and Structured Output Tests', () => {
270
270
  });
271
271
 
272
272
  it('should handle JSON parsing errors gracefully', async () => {
273
- model.gpt4o().addText('Generate invalid JSON');
273
+ model.gpt41().addText('Generate invalid JSON');
274
274
 
275
275
  // Mock invalid JSON response
276
276
  nock('https://api.openai.com')
package/test/live.mcp.js CHANGED
@@ -110,8 +110,8 @@ describe('Live MCP Integration Tests', function () {
110
110
  }
111
111
  });
112
112
 
113
- it('should use custom MCP tools with Gemini 2.5 Flash', async function () {
114
- const model = ModelMix.new(setup).gemini25flash();
113
+ it('should use custom MCP tools with Gemini 3 Flash', async function () {
114
+ const model = ModelMix.new(setup).gemini3flash();
115
115
 
116
116
  // Add password generator tool
117
117
  model.addTool({
@@ -149,11 +149,13 @@ describe('Live MCP Integration Tests', function () {
149
149
  model.addText('Generate a secure password of 16 characters with symbols.');
150
150
 
151
151
  const response = await model.message();
152
- console.log(`Gemini 2.5 Flash with MCP tools: ${response}`);
152
+ console.log(`Gemini 3 Flash with MCP tools: ${response}`);
153
153
 
154
154
  expect(response).to.be.a('string');
155
- expect(response).to.include('password');
156
- expect(response).to.include('16');
155
+ // Check password is mentioned and a generated password string is present
156
+ expect(response.toLowerCase()).to.include('password');
157
+ // Verify a generated password is in the response (at least 12 chars with mix of alphanumeric/symbols)
158
+ expect(response).to.match(/[a-zA-Z0-9!@#$%^&*()_+\-=\[\]{}|;:,.<>?]{12,}/);
157
159
  });
158
160
 
159
161
  });
@@ -374,8 +376,8 @@ describe('Live MCP Integration Tests', function () {
374
376
  expect(result.factorial_result).to.equal(120);
375
377
  });
376
378
 
377
- it('should use MCP tools with JSON output using Gemini 2.5 Flash', async function () {
378
- const model = ModelMix.new(setup).gemini25flash();
379
+ it('should use MCP tools with JSON output using Gemini 3 Flash', async function () {
380
+ const model = ModelMix.new(setup).gemini3flash();
379
381
 
380
382
  // Add system info tool
381
383
  model.addTool({
@@ -414,7 +416,7 @@ describe('Live MCP Integration Tests', function () {
414
416
  generated_at: ""
415
417
  });
416
418
 
417
- console.log(`Gemini 2.5 Flash with MCP tools JSON result:`, result);
419
+ console.log(`Gemini 3 Flash with MCP tools JSON result:`, result);
418
420
 
419
421
  expect(result).to.be.an('object');
420
422
  expect(result.timestamp).to.be.a('number');