modelmix 3.8.2 → 3.8.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,356 @@
1
+ const { expect } = require('chai');
2
+ const { ModelMix, MixOpenAI, MixAnthropic, MixGoogle } = require('../index.js');
3
+ const path = require('path');
4
+ const fixturesPath = path.join(__dirname, 'fixtures');
5
+
6
+ const blueSquareBase64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAMAAABHPGVmAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDkuMS1jMDAzIDc5Ljk2OTBhODdmYywgMjAyNS8wMy8wNi0yMDo1MDoxNiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDI2LjkgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6REM2QzQ3NEQ2Q0I5MTFGMDlBRTVGQzcwQjMyMkY4MDciIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6REM2QzQ3NEU2Q0I5MTFGMDlBRTVGQzcwQjMyMkY4MDciPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpEQzZDNDc0QjZDQjkxMUYwOUFFNUZDNzBCMzIyRjgwNyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpEQzZDNDc0QzZDQjkxMUYwOUFFNUZDNzBCMzIyRjgwNyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PvArxh0AAAAGUExURQAA/wAAAHtivz4AAAAjSURBVHja7MGBAAAAAMOg+VNf4QBVAQAAAAAAAAAAAI8JMAAndAABi7SX2gAAAABJRU5ErkJggg==';
7
+
8
+ const setup = {
9
+ options: { temperature: 0 },
10
+ config: { debug: false, max_history: 2 }
11
+ };
12
+
13
+ describe('Live Integration Tests', function () {
14
+ // Increase timeout for real API calls
15
+ this.timeout(30000);
16
+
17
+ describe('Image Processing', function () {
18
+
19
+ it('should process images with OpenAI GPT-5nano', async function () {
20
+ const model = ModelMix.new(setup).gpt5nano();
21
+
22
+ model.addImageFromUrl(blueSquareBase64)
23
+ .addText('What color is this image? Answer in one word only.');
24
+
25
+ const response = await model.message();
26
+
27
+ console.log(`OpenAI GPT-4o response: ${response}`);
28
+
29
+ expect(response).to.be.a('string');
30
+ expect(response.toLowerCase()).to.include('blue');
31
+ });
32
+
33
+ it('should process images with Anthropic Claude', async function () {
34
+ const model = ModelMix.new(setup).sonnet35();
35
+
36
+ model.addImageFromUrl(blueSquareBase64)
37
+ .addText('What color is this image? Answer in one word only.');
38
+
39
+ const response = await model.message();
40
+ console.log(`Anthropic Claude response: ${response}`);
41
+
42
+ expect(response).to.be.a('string');
43
+ expect(response.toLowerCase()).to.include('blue');
44
+ });
45
+
46
+ it('should process images with Google Gemini', async function () {
47
+ const model = ModelMix.new(setup).gemini25flash();
48
+
49
+ model.addImageFromUrl(blueSquareBase64)
50
+ .addText('What color is this image? Answer in one word only.');
51
+
52
+ const response = await model.message();
53
+ console.log(`Google Gemini response: ${response}`);
54
+
55
+ expect(response).to.be.a('string');
56
+ expect(response.toLowerCase()).to.include('blue');
57
+ });
58
+
59
+ });
60
+
61
+ describe('JSON Structured Output', function () {
62
+
63
+ it('should return structured JSON with OpenAI', async function () {
64
+ const model = ModelMix.new(setup).gpt5mini();
65
+
66
+ model.addText('Generate information about a fictional character.');
67
+
68
+ const result = await model.json({
69
+ name: "John Doe",
70
+ age: 30,
71
+ occupation: "Engineer",
72
+ skills: ["JavaScript", "Python"]
73
+ }, {}, { addNote: true });
74
+
75
+ console.log(`OpenAI JSON result:`, result);
76
+
77
+ expect(result).to.be.an('object');
78
+ expect(result).to.have.property('name');
79
+ expect(result).to.have.property('age');
80
+ expect(result).to.have.property('occupation');
81
+ expect(result).to.have.property('skills');
82
+ expect(result.skills).to.be.an('array');
83
+ });
84
+
85
+ it('should return structured JSON with Sonnet Think', async function () {
86
+ const model = ModelMix.new(setup).sonnet4think();
87
+
88
+ model.addText('Generate information about a fictional city.');
89
+
90
+ const result = await model.json({
91
+ name: "Springfield",
92
+ population: 50000,
93
+ country: "USA",
94
+ attractions: ["Museum", "Park"]
95
+ });
96
+
97
+ console.log(`Anthropic JSON result:`, result);
98
+
99
+ expect(result).to.be.an('object');
100
+ expect(result).to.have.property('name');
101
+ expect(result).to.have.property('population');
102
+ expect(result).to.have.property('country');
103
+ expect(result).to.have.property('attractions');
104
+ expect(result.attractions).to.be.an('array');
105
+ });
106
+
107
+ it('should return structured JSON with Google Gemini', async function () {
108
+ const model = ModelMix.new(setup).gemini25flash();
109
+
110
+ model.addText('Generate information about a fictional city.');
111
+
112
+ const result = await model.json({
113
+ name: "Springfield",
114
+ population: 50000,
115
+ country: "USA",
116
+ attractions: ["Museum", "Park"]
117
+ });
118
+
119
+ console.log(`Google Gemini JSON result:`, result);
120
+
121
+ expect(result).to.be.an('object');
122
+ expect(result).to.have.property('name');
123
+ expect(result).to.have.property('population');
124
+ expect(result).to.have.property('country');
125
+ expect(result).to.have.property('attractions');
126
+ expect(result.attractions).to.be.an('array');
127
+ });
128
+
129
+
130
+ });
131
+
132
+ describe('Model Fallback', function () {
133
+
134
+ it('should fallback from OpenAI to Anthropic', async function () {
135
+ // Create a model chain: non-existent model -> Claude
136
+ const model = ModelMix.new(setup)
137
+ .attach('non-existent-model', new MixOpenAI())
138
+ .sonnet4();
139
+
140
+ model.addText('Say "fallback test successful" and nothing else.');
141
+
142
+ const response = await model.message();
143
+ console.log(`Fallback test result: ${response}`);
144
+
145
+ expect(response).to.be.a('string');
146
+ expect(response.toLowerCase()).to.include('fallback test successful');
147
+ });
148
+
149
+ it('should fallback from Anthropic to OpenAI', async function () {
150
+ // Create a model chain: non-existent model -> Claude
151
+ const model = ModelMix.new(setup)
152
+ .attach('non-existent-model', new MixAnthropic())
153
+ .gpt41nano();
154
+
155
+ model.addText('Say "fallback test successful" and nothing else.');
156
+
157
+ const response = await model.message();
158
+ console.log(`Fallback test result: ${response}`);
159
+
160
+ expect(response).to.be.a('string');
161
+ expect(response.toLowerCase()).to.include('fallback test successful');
162
+ });
163
+
164
+ });
165
+
166
+ describe('Additional Model Tests', function () {
167
+
168
+ it('should work with Scout model', async function () {
169
+ const model = ModelMix.new(setup).scout();
170
+
171
+ model.addText('Say "scout test successful" and nothing else.');
172
+
173
+ const response = await model.message();
174
+ console.log(`Scout response: ${response}`);
175
+
176
+ expect(response).to.be.a('string');
177
+ expect(response.toLowerCase()).to.include('scout test successful');
178
+ });
179
+
180
+ it('should work with KimiK2 model', async function () {
181
+ const model = ModelMix.new(setup).kimiK2();
182
+
183
+ model.addText('Say "kimik2 test successful" and nothing else.');
184
+
185
+ const response = await model.message();
186
+ console.log(`KimiK2 response: ${response}`);
187
+
188
+ expect(response).to.be.a('string');
189
+ expect(response.toLowerCase()).to.include('kimik2 test successful');
190
+ });
191
+
192
+ it('should work with GPT-OSS model', async function () {
193
+ const model = ModelMix.new(setup).gptOss();
194
+
195
+ model.addText('Say "gptoss test successful" and nothing else.');
196
+
197
+ const response = await model.message();
198
+ console.log(`GPT-OSS response: ${response}`);
199
+
200
+ expect(response).to.be.a('string');
201
+ expect(response.toLowerCase()).to.include('gptoss test successful');
202
+ });
203
+
204
+ it('should work with Grok3Mini model', async function () {
205
+ const model = ModelMix.new(setup).grok3mini();
206
+
207
+ model.addText('Say "grok3mini test successful" and nothing else.');
208
+
209
+ const response = await model.message();
210
+ console.log(`Grok3Mini response: ${response}`);
211
+
212
+ expect(response).to.be.a('string');
213
+ expect(response.toLowerCase()).to.include('grok3mini test successful');
214
+ });
215
+
216
+ });
217
+
218
+ describe('Image Processing with JSON Output', function () {
219
+
220
+ it('should process images and return JSON with Maverick', async function () {
221
+ const model = ModelMix.new(setup).maverick();
222
+ model.addImage(path.join(fixturesPath, 'img.png'))
223
+ .addText('Analyze this image and provide details in JSON format.');
224
+
225
+ const result = await model.json({
226
+ color: "string",
227
+ shape: "string",
228
+ description: "string"
229
+ });
230
+
231
+ console.log(`Scout image JSON result:`, result);
232
+
233
+ expect(result).to.be.an('object');
234
+ expect(result).to.have.property('color');
235
+ expect(result).to.have.property('shape');
236
+ expect(result).to.have.property('description');
237
+ expect(result.color.toLowerCase()).to.include('blue');
238
+ });
239
+
240
+ it('should process images and return JSON with Grok 4', async function () {
241
+ const model = ModelMix.new(setup).grok4();
242
+
243
+ model.addImageFromUrl(blueSquareBase64)
244
+ .addText('Analyze this image and provide details in JSON format.');
245
+
246
+ const result = await model.json({
247
+ color: "string",
248
+ shape: "string",
249
+ description: "string"
250
+ });
251
+
252
+ console.log(`Grok3Mini image JSON result:`, result);
253
+
254
+ expect(result).to.be.an('object');
255
+ expect(result).to.have.property('color');
256
+ expect(result).to.have.property('shape');
257
+ expect(result).to.have.property('description');
258
+ expect(result.color.toLowerCase()).to.include('blue');
259
+ });
260
+
261
+ });
262
+
263
+ describe('JSON Structured Output for New Models', function () {
264
+
265
+ it('should return structured JSON with Scout', async function () {
266
+ const model = ModelMix.new(setup).scout();
267
+
268
+ model.addText('Generate information about a fictional animal.');
269
+
270
+ const result = await model.json({
271
+ name: "Dragon",
272
+ type: "Mythical",
273
+ abilities: ["Fire breathing", "Flight"],
274
+ habitat: "Mountains"
275
+ });
276
+
277
+ console.log(`Scout JSON result:`, result);
278
+
279
+ expect(result).to.be.an('object');
280
+ expect(result).to.have.property('name');
281
+ expect(result).to.have.property('type');
282
+ expect(result).to.have.property('abilities');
283
+ expect(result).to.have.property('habitat');
284
+ expect(result.abilities).to.be.an('array');
285
+ });
286
+
287
+ it('should return structured JSON with KimiK2', async function () {
288
+ const model = ModelMix.new(setup).kimiK2();
289
+
290
+ model.addText('Generate information about a fictional vehicle.');
291
+
292
+ const result = await model.json({
293
+ name: "Flying Car",
294
+ type: "Transportation",
295
+ features: ["Anti-gravity", "Auto-pilot"],
296
+ manufacturer: "Future Motors"
297
+ });
298
+
299
+ console.log(`KimiK2 JSON result:`, result);
300
+
301
+ expect(result).to.be.an('object');
302
+ expect(result).to.have.property('name');
303
+ expect(result).to.have.property('type');
304
+ expect(result).to.have.property('features');
305
+ expect(result).to.have.property('manufacturer');
306
+ expect(result.features).to.be.an('array');
307
+ });
308
+
309
+ it('should return structured JSON with GPT-OSS', async function () {
310
+ const model = ModelMix.new(setup).gptOss();
311
+
312
+ model.addText('Generate information about a fictional planet.');
313
+
314
+ const result = await model.json({
315
+ name: "Nova Prime",
316
+ type: "Gas Giant",
317
+ moons: ["Alpha", "Beta", "Gamma"],
318
+ atmosphere: "Hydrogen and Helium"
319
+ });
320
+
321
+ console.log(`GPT-OSS JSON result:`, result);
322
+
323
+ expect(result).to.be.an('object');
324
+ expect(result).to.have.property('name');
325
+ expect(result).to.have.property('type');
326
+ expect(result).to.have.property('moons');
327
+ expect(result).to.have.property('atmosphere');
328
+ expect(result.moons).to.be.an('array');
329
+ });
330
+
331
+ it('should return structured JSON with Grok3Mini', async function () {
332
+ const model = ModelMix.new(setup).grok3mini();
333
+
334
+ model.addText('Generate information about a fictional technology.');
335
+
336
+ const result = await model.json({
337
+ name: "Quantum Computer",
338
+ type: "Computing",
339
+ applications: ["Cryptography", "Simulation"],
340
+ power: "1000 qubits"
341
+ });
342
+
343
+ console.log(`Grok3Mini JSON result:`, result);
344
+
345
+ expect(result).to.be.an('object');
346
+ expect(result).to.have.property('name');
347
+ expect(result).to.have.property('type');
348
+ expect(result).to.have.property('applications');
349
+ expect(result).to.have.property('power');
350
+ expect(result.applications).to.be.an('array');
351
+ });
352
+
353
+ });
354
+
355
+
356
+ });
@@ -0,0 +1,5 @@
1
+ --timeout 10000
2
+ --recursive
3
+ --require test/setup.js
4
+ --reporter spec
5
+ --bail false
package/test/setup.js ADDED
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Global test setup for ModelMix
3
+ *
4
+ * This file contains shared configuration and utilities for all test files.
5
+ * It runs before each test suite and sets up the testing environment.
6
+ */
7
+
8
+ // Set dummy environment variables for testing to prevent library errors
9
+
10
+ const chai = require('chai');
11
+ const sinon = require('sinon');
12
+ const nock = require('nock');
13
+
14
+ // Set up chai
15
+ chai.config.includeStack = true;
16
+ chai.config.showDiff = true;
17
+
18
+ // Global test timeout
19
+ const TIMEOUT = 10000;
20
+
21
+ // Set dummy environment variables for testing to prevent library initialization errors
22
+ process.env.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY || 'sk-ant-test-dummy-key-for-testing-purposes';
23
+ process.env.OPENAI_API_KEY = process.env.OPENAI_API_KEY || 'sk-proj-test-dummy-key-for-testing-purposes';
24
+ process.env.PPLX_API_KEY = process.env.PPLX_API_KEY || 'pplx-test-dummy-key-for-testing-purposes';
25
+ process.env.GROQ_API_KEY = process.env.GROQ_API_KEY || 'gsk_test-dummy-key-for-testing-purposes';
26
+ process.env.TOGETHER_API_KEY = process.env.TOGETHER_API_KEY || '49a96test-dummy-key-for-testing-purposes';
27
+ process.env.XAI_API_KEY = process.env.XAI_API_KEY || 'xai-test-dummy-key-for-testing-purposes';
28
+ process.env.CEREBRAS_API_KEY = process.env.CEREBRAS_API_KEY || 'csk-test-dummy-key-for-testing-purposes';
29
+ process.env.GOOGLE_API_KEY = process.env.GOOGLE_API_KEY || 'AIzatest-dummy-key-for-testing-purposes';
30
+ process.env.LAMBDA_API_KEY = process.env.LAMBDA_API_KEY || 'secret_test-dummy-key-for-testing-purposes';
31
+ process.env.BRAVE_API_KEY = process.env.BRAVE_API_KEY || 'BSA0test-dummy-key-for-testing-purposes_fm';
32
+
33
+ // Global test configuration
34
+ global.TEST_CONFIG = {
35
+ TIMEOUT,
36
+ MOCK_APIS: true,
37
+ DEBUG: false
38
+ };
39
+
40
+ // Global cleanup function
41
+ global.cleanup = function() {
42
+ nock.cleanAll();
43
+ sinon.restore();
44
+ };
45
+
46
+ // Console override for testing (suppress debug logs unless needed)
47
+ if (!process.env.DEBUG_TESTS) {
48
+ const originalConsole = {
49
+ log: console.log,
50
+ warn: console.warn,
51
+ error: console.error,
52
+ info: console.info,
53
+ debug: console.debug
54
+ };
55
+
56
+ // Suppress most console output during tests
57
+ console.log = () => {};
58
+ console.info = () => {};
59
+ console.debug = () => {};
60
+
61
+ // Keep warnings and errors visible
62
+ console.warn = originalConsole.warn;
63
+ console.error = originalConsole.error;
64
+
65
+ // Restore original console for specific test debugging
66
+ global.restoreConsole = () => {
67
+ Object.assign(console, originalConsole);
68
+ };
69
+ }
70
+
71
+ // Utility functions for tests
72
+ global.testUtils = {
73
+ /**
74
+ * Create a mock API response for different providers
75
+ */
76
+ createMockResponse: (provider, content, options = {}) => {
77
+ switch (provider) {
78
+ case 'openai':
79
+ return {
80
+ choices: [{
81
+ message: {
82
+ role: 'assistant',
83
+ content: content,
84
+ ...options
85
+ }
86
+ }]
87
+ };
88
+
89
+ case 'anthropic':
90
+ return {
91
+ content: [{
92
+ type: 'text',
93
+ text: content,
94
+ ...options
95
+ }]
96
+ };
97
+
98
+ case 'google':
99
+ return {
100
+ candidates: [{
101
+ content: {
102
+ parts: [{
103
+ text: content,
104
+ ...options
105
+ }]
106
+ }
107
+ }]
108
+ };
109
+
110
+ default:
111
+ throw new Error(`Unknown provider: ${provider}`);
112
+ }
113
+ },
114
+
115
+ /**
116
+ * Create a mock error response
117
+ */
118
+ createMockError: (statusCode, message) => ({
119
+ error: {
120
+ message: message,
121
+ type: 'test_error',
122
+ code: statusCode
123
+ }
124
+ }),
125
+
126
+ /**
127
+ * Wait for a specified amount of time
128
+ */
129
+ wait: (ms) => new Promise(resolve => setTimeout(resolve, ms)),
130
+
131
+ /**
132
+ * Generate test image data
133
+ */
134
+ generateTestImage: (format = 'jpeg') => {
135
+ const formats = {
136
+ jpeg: '/9j/4AAQSkZJRgABAQAAAQABAAD//2Q==',
137
+ png: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
138
+ gif: 'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
139
+ };
140
+
141
+ const mimeTypes = {
142
+ jpeg: 'image/jpeg',
143
+ png: 'image/png',
144
+ gif: 'image/gif'
145
+ };
146
+
147
+ return `data:${mimeTypes[format]};base64,${formats[format]}`;
148
+ }
149
+ };
150
+
151
+ // Export hooks for tests to use
152
+ global.setupTestHooks = function() {
153
+ beforeEach(() => {
154
+ // Disable real HTTP requests
155
+ if (global.TEST_CONFIG.MOCK_APIS) {
156
+ nock.disableNetConnect();
157
+ // Allow localhost connections for local servers if needed
158
+ nock.enableNetConnect('localhost');
159
+ }
160
+ });
161
+
162
+ afterEach(() => {
163
+ global.cleanup();
164
+
165
+ if (global.TEST_CONFIG.MOCK_APIS) {
166
+ nock.enableNetConnect();
167
+ }
168
+ });
169
+ };
170
+
171
+ // Handle unhandled promise rejections in tests
172
+ process.on('unhandledRejection', (reason, promise) => {
173
+ console.error('Unhandled Rejection at:', promise, 'reason:', reason);
174
+ });
175
+
176
+ console.log('✅ ModelMix test setup complete');