modelmix 5.1.12 → 5.1.16

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,517 @@
1
+ const { expect } = require('chai');
2
+ const { EventEmitter } = require('events');
3
+ const sinon = require('sinon');
4
+ const {
5
+ MixCustom,
6
+ MixModeration,
7
+ MixOpenAI,
8
+ ModelMix
9
+ } = require('../index.js');
10
+ const createOpenAIProviders = require('../lib/providers/openai');
11
+ const { rejectsAnthropicSamplingParams } = require('../lib/providers/anthropic');
12
+ const {
13
+ fetchBinaryResponse,
14
+ fetchJsonResponse,
15
+ fetchStreamResponse
16
+ } = require('../http-client');
17
+
18
+ function deferred() {
19
+ let resolve;
20
+ let reject;
21
+ const promise = new Promise((resolvePromise, rejectPromise) => {
22
+ resolve = resolvePromise;
23
+ reject = rejectPromise;
24
+ });
25
+ return { promise, reject, resolve };
26
+ }
27
+
28
+ async function rejection(promise) {
29
+ try {
30
+ await promise;
31
+ } catch (error) {
32
+ return error;
33
+ }
34
+ throw new Error('Expected promise to reject.');
35
+ }
36
+
37
+ function createProvider(handler) {
38
+ const provider = new MixCustom();
39
+ provider.create = handler;
40
+ return provider;
41
+ }
42
+
43
+ function createModel(handler, config = {}) {
44
+ return ModelMix.new({
45
+ config: {
46
+ bottleneck: { maxConcurrent: 8, minTime: 0 },
47
+ ...config
48
+ }
49
+ }).attach('custom', createProvider(handler)).addText('test');
50
+ }
51
+
52
+ describe('AbortSignal execution contract', () => {
53
+ if (global.setupTestHooks) global.setupTestHooks();
54
+
55
+ it('passes the direct signal through every terminal method without storing it', async () => {
56
+ const controller = new AbortController();
57
+ const cases = [
58
+ ['message', model => model.message(controller.signal), 'plain'],
59
+ ['raw', model => model.raw(controller.signal), 'plain'],
60
+ ['stream', model => model.stream(() => {}, controller.signal), 'plain'],
61
+ ['block', model => model.block({}, controller.signal), '```plain```'],
62
+ ['json', model => model.json(null, {}, {}, controller.signal), '{"ok":true}'],
63
+ ['execute', model => model.execute({ signal: controller.signal }), 'plain']
64
+ ];
65
+
66
+ for (const [, invoke, message] of cases) {
67
+ let providerRequest;
68
+ const model = createModel(async request => {
69
+ providerRequest = request;
70
+ return { message, toolCalls: [] };
71
+ });
72
+
73
+ await invoke(model);
74
+
75
+ expect(providerRequest.signal).to.equal(controller.signal);
76
+ expect(providerRequest.config).to.not.have.property('signal');
77
+ expect(providerRequest.options).to.not.have.property('signal');
78
+ expect(model).to.not.have.property('signal');
79
+ expect(model.config).to.not.have.property('signal');
80
+ expect(model.options).to.not.have.property('signal');
81
+ }
82
+ });
83
+
84
+ it('rejects invalid and misplaced signals before provider work starts', async () => {
85
+ const signal = new AbortController().signal;
86
+ expect(() => ModelMix.new({ config: { signal } })).to.throw(TypeError, 'config.signal');
87
+ expect(() => ModelMix.new({ options: { signal } })).to.throw(TypeError, 'options.signal');
88
+ expect(() => new MixCustom({ config: { signal } })).to.throw(TypeError, 'config.signal');
89
+ expect(() => new MixCustom({ options: { signal } })).to.throw(TypeError, 'options.signal');
90
+
91
+ let providerCalls = 0;
92
+ const model = createModel(async () => {
93
+ providerCalls += 1;
94
+ return { message: 'unexpected', toolCalls: [] };
95
+ });
96
+
97
+ expect(await rejection(model.message({}))).to.be.instanceOf(TypeError);
98
+ expect(await rejection(model.execute({ config: { signal } }))).to.be.instanceOf(TypeError);
99
+ expect(await rejection(model.execute({ options: { signal } }))).to.be.instanceOf(TypeError);
100
+ expect(providerCalls).to.equal(0);
101
+
102
+ const pluginModel = createModel(async () => {
103
+ providerCalls += 1;
104
+ return { message: 'unexpected', toolCalls: [] };
105
+ }).use({
106
+ name: 'misplaced-signal',
107
+ execute(context, next) {
108
+ context.request.options.signal = signal;
109
+ return next();
110
+ }
111
+ });
112
+ const pluginError = await rejection(pluginModel.message());
113
+ expect(pluginError).to.be.instanceOf(TypeError);
114
+ expect(pluginError.message).to.include('options.signal');
115
+ expect(providerCalls).to.equal(0);
116
+ });
117
+
118
+ it('preserves a pre-aborted custom reason and leaves execution state untouched', async () => {
119
+ const reason = new Error('cancel before start');
120
+ const controller = new AbortController();
121
+ controller.abort(reason);
122
+ let providerCalls = 0;
123
+ const model = createModel(async () => {
124
+ providerCalls += 1;
125
+ return { message: 'unexpected', toolCalls: [] };
126
+ });
127
+ const originalMessages = model.messages.map(message => ({ ...message }));
128
+
129
+ expect(await rejection(model.message(controller.signal))).to.equal(reason);
130
+ expect(providerCalls).to.equal(0);
131
+ expect(model.lastRaw).to.equal(null);
132
+ expect(model.messages).to.deep.equal(originalMessages);
133
+ });
134
+
135
+ it('aborts remote image preparation without mutating messages or invoking a provider', async () => {
136
+ const reason = new Error('stop image download');
137
+ const controller = new AbortController();
138
+ const fetchStarted = deferred();
139
+ let fetchedSignal;
140
+ sinon.stub(global, 'fetch').callsFake((_url, { signal }) => {
141
+ fetchedSignal = signal;
142
+ fetchStarted.resolve();
143
+ return new Promise((_, rejectPromise) => {
144
+ signal.addEventListener('abort', () => rejectPromise(signal.reason), { once: true });
145
+ });
146
+ });
147
+ let providerCalls = 0;
148
+ const model = ModelMix.new({ config: { bottleneck: { maxConcurrent: 1, minTime: 0 } } })
149
+ .attach('custom', createProvider(async () => {
150
+ providerCalls += 1;
151
+ return { message: 'unexpected', toolCalls: [] };
152
+ }))
153
+ .addImageFromUrl('https://example.test/image.png')
154
+ .addText('describe');
155
+ const originalMessages = JSON.parse(JSON.stringify(model.messages));
156
+
157
+ const execution = model.raw(controller.signal);
158
+ await fetchStarted.promise;
159
+ controller.abort(reason);
160
+
161
+ expect(await rejection(execution)).to.equal(reason);
162
+ expect(fetchedSignal).to.equal(controller.signal);
163
+ expect(providerCalls).to.equal(0);
164
+ expect(model.messages).to.deep.equal(originalMessages);
165
+ });
166
+
167
+ it('aborts an in-flight custom provider without falling back or recording its late result', async () => {
168
+ const pending = deferred();
169
+ const primaryStarted = deferred();
170
+ const reason = new Error('stop provider');
171
+ const controller = new AbortController();
172
+ let primaryCalls = 0;
173
+ let fallbackCalls = 0;
174
+ const model = ModelMix.new({ config: { bottleneck: { maxConcurrent: 2, minTime: 0 } } })
175
+ .attach('primary', createProvider(async () => {
176
+ primaryCalls += 1;
177
+ primaryStarted.resolve();
178
+ return pending.promise;
179
+ }))
180
+ .attach('fallback', createProvider(async () => {
181
+ fallbackCalls += 1;
182
+ return { message: 'fallback', toolCalls: [] };
183
+ }))
184
+ .addText('test');
185
+
186
+ const execution = model.raw(controller.signal);
187
+ await primaryStarted.promise;
188
+ controller.abort(reason);
189
+
190
+ expect(await rejection(execution)).to.equal(reason);
191
+ pending.resolve({ message: 'late', toolCalls: [] });
192
+ await new Promise(resolve => setImmediate(resolve));
193
+ expect(primaryCalls).to.equal(1);
194
+ expect(fallbackCalls).to.equal(0);
195
+ expect(model.lastRaw).to.equal(null);
196
+ });
197
+
198
+ it('rejects while queued and prevents the queued provider invocation', async () => {
199
+ const first = deferred();
200
+ const firstStarted = deferred();
201
+ const reason = new Error('leave queue');
202
+ const controller = new AbortController();
203
+ let providerCalls = 0;
204
+ const model = createModel(async () => {
205
+ providerCalls += 1;
206
+ if (providerCalls === 1) {
207
+ firstStarted.resolve();
208
+ return first.promise;
209
+ }
210
+ return { message: 'unexpected', toolCalls: [] };
211
+ }, {
212
+ max_history: -1,
213
+ bottleneck: { maxConcurrent: 1, minTime: 0 }
214
+ });
215
+
216
+ const running = model.raw();
217
+ await firstStarted.promise;
218
+ const queued = model.raw(controller.signal);
219
+ controller.abort(reason);
220
+
221
+ expect(await rejection(queued)).to.equal(reason);
222
+ first.resolve({ message: 'first', toolCalls: [] });
223
+ await running;
224
+ await new Promise(resolve => setImmediate(resolve));
225
+ expect(providerCalls).to.equal(1);
226
+ });
227
+
228
+ it('aborts retry backoff without retrying or falling back', async () => {
229
+ const reason = new Error('stop retry');
230
+ const controller = new AbortController();
231
+ let primaryCalls = 0;
232
+ let fallbackCalls = 0;
233
+ const model = ModelMix.new({
234
+ config: {
235
+ bottleneck: { maxConcurrent: 1, minTime: 0 },
236
+ retry: {
237
+ enabled: true,
238
+ retries: 2,
239
+ baseDelayMs: 200,
240
+ maxDelayMs: 200,
241
+ retryableStatusCodes: [429]
242
+ }
243
+ }
244
+ })
245
+ .attach('primary', createProvider(async () => {
246
+ primaryCalls += 1;
247
+ throw { message: 'rate limited', statusCode: 429 };
248
+ }))
249
+ .attach('fallback', createProvider(async () => {
250
+ fallbackCalls += 1;
251
+ return { message: 'fallback', toolCalls: [] };
252
+ }))
253
+ .addText('test');
254
+
255
+ const execution = model.raw(controller.signal);
256
+ await new Promise(resolve => setTimeout(resolve, 20));
257
+ controller.abort(reason);
258
+
259
+ expect(await rejection(execution)).to.equal(reason);
260
+ expect(primaryCalls).to.equal(1);
261
+ expect(fallbackCalls).to.equal(0);
262
+ });
263
+
264
+ it('isolates signals across concurrent executions on one instance', async () => {
265
+ const first = deferred();
266
+ const second = deferred();
267
+ const started = deferred();
268
+ const controllerA = new AbortController();
269
+ const controllerB = new AbortController();
270
+ const reason = new Error('only A');
271
+ const requests = [];
272
+ const model = createModel(async request => {
273
+ requests.push(request);
274
+ if (requests.length === 2) started.resolve();
275
+ return requests.length === 1 ? first.promise : second.promise;
276
+ }, {
277
+ max_history: -1,
278
+ bottleneck: { maxConcurrent: 2, minTime: 0 }
279
+ });
280
+
281
+ const executionA = model.raw(controllerA.signal);
282
+ const executionB = model.raw(controllerB.signal);
283
+ await started.promise;
284
+ controllerA.abort(reason);
285
+ second.resolve({ message: 'B', toolCalls: [] });
286
+
287
+ expect(await rejection(executionA)).to.equal(reason);
288
+ expect((await executionB).message).to.equal('B');
289
+ first.resolve({ message: 'late A', toolCalls: [] });
290
+ await new Promise(resolve => setImmediate(resolve));
291
+ expect(requests.map(request => request.signal)).to.deep.equal([
292
+ controllerA.signal,
293
+ controllerB.signal
294
+ ]);
295
+ expect(model.lastRaw.message).to.equal('B');
296
+ });
297
+
298
+ it('exposes the signal to plugins and inherits it in child invocations', async () => {
299
+ const controller = new AbortController();
300
+ const contexts = [];
301
+ let providerSignal;
302
+ const provider = createProvider(async request => {
303
+ providerSignal = request.signal;
304
+ return { message: 'child', toolCalls: [] };
305
+ });
306
+ const model = ModelMix.new({ config: { bottleneck: { maxConcurrent: 2, minTime: 0 } } })
307
+ .attach('custom', provider)
308
+ .use({
309
+ name: 'child',
310
+ execute(context, next) {
311
+ contexts.push(context);
312
+ if (context.execution.depth > 0) return next();
313
+ return context.invoke({
314
+ messages: [{ role: 'user', content: 'child prompt' }],
315
+ history: false
316
+ });
317
+ }
318
+ })
319
+ .addText('parent prompt');
320
+
321
+ expect((await model.raw(controller.signal)).message).to.equal('child');
322
+ expect(contexts).to.have.length(2);
323
+ expect(contexts.every(context => context.signal === controller.signal)).to.equal(true);
324
+ expect(contexts.every(context => !Object.hasOwn(context.request.config, 'signal'))).to.equal(true);
325
+ expect(contexts.every(context => !Object.hasOwn(context.request.options, 'signal'))).to.equal(true);
326
+ expect(providerSignal).to.equal(controller.signal);
327
+ });
328
+
329
+ it('passes the signal to local and MCP tools and preserves abort through tool handling', async () => {
330
+ const controller = new AbortController();
331
+ let localSignal;
332
+ const model = ModelMix.new().addTool({
333
+ name: 'local',
334
+ description: 'local tool',
335
+ inputSchema: { type: 'object' }
336
+ }, async (_args, signal) => {
337
+ localSignal = signal;
338
+ return 'local result';
339
+ });
340
+
341
+ const localResult = await model.processToolCalls([
342
+ { id: 'local-1', name: 'local', input: {} }
343
+ ], controller.signal);
344
+ expect(localSignal).to.equal(controller.signal);
345
+ expect(localResult[0].content).to.equal('local result');
346
+
347
+ let mcpArguments;
348
+ model.toolClient.remote = {
349
+ async callTool(...args) {
350
+ mcpArguments = args;
351
+ return { content: [{ type: 'text', text: 'remote result' }] };
352
+ }
353
+ };
354
+ const remoteResult = await model.processToolCalls([
355
+ { id: 'remote-1', name: 'remote', input: { value: 1 } }
356
+ ], controller.signal);
357
+ expect(remoteResult[0].content).to.equal('remote result');
358
+ expect(mcpArguments[2]).to.deep.equal({ signal: controller.signal });
359
+
360
+ const reason = new Error('stop local tool');
361
+ const abortController = new AbortController();
362
+ const toolStarted = deferred();
363
+ let providerCalls = 0;
364
+ const toolModel = createModel(async () => {
365
+ providerCalls += 1;
366
+ return {
367
+ message: '',
368
+ toolCalls: [{ id: 'slow-1', name: 'slow', input: {} }]
369
+ };
370
+ }, { max_history: -1 }).addTool({
371
+ name: 'slow',
372
+ description: 'slow tool',
373
+ inputSchema: { type: 'object' }
374
+ }, async (_args, signal) => {
375
+ toolStarted.resolve(signal);
376
+ return new Promise((_, rejectPromise) => {
377
+ signal.addEventListener('abort', () => rejectPromise(signal.reason), { once: true });
378
+ });
379
+ });
380
+ const originalMessages = JSON.parse(JSON.stringify(toolModel.messages));
381
+ const execution = toolModel.raw(abortController.signal);
382
+ expect(await toolStarted.promise).to.equal(abortController.signal);
383
+ abortController.abort(reason);
384
+
385
+ expect(await rejection(execution)).to.equal(reason);
386
+ expect(providerCalls).to.equal(1);
387
+ expect(toolModel.messages).to.deep.equal(originalMessages);
388
+ });
389
+
390
+ it('forwards the signal through every fetch helper without serializing it', async () => {
391
+ const controller = new AbortController();
392
+ const calls = [];
393
+ sinon.stub(global, 'fetch').callsFake(async (_url, init) => {
394
+ calls.push(init);
395
+ if (calls.length === 1) {
396
+ return {
397
+ ok: true,
398
+ status: 200,
399
+ headers: new Headers(),
400
+ text: async () => '{"ok":true}'
401
+ };
402
+ }
403
+ if (calls.length === 2) {
404
+ return {
405
+ ok: true,
406
+ status: 200,
407
+ headers: new Headers(),
408
+ arrayBuffer: async () => Uint8Array.from([1, 2, 3]).buffer
409
+ };
410
+ }
411
+ return {
412
+ ok: true,
413
+ status: 200,
414
+ headers: new Headers(),
415
+ body: new ReadableStream({
416
+ start(streamController) {
417
+ streamController.close();
418
+ }
419
+ })
420
+ };
421
+ });
422
+
423
+ await fetchJsonResponse('https://example.test/json', { body: '{}', signal: controller.signal });
424
+ await fetchBinaryResponse('https://example.test/binary', { signal: controller.signal });
425
+ const stream = await fetchStreamResponse('https://example.test/stream', { body: '{}', signal: controller.signal });
426
+ stream.data.resume();
427
+
428
+ expect(calls).to.have.length(3);
429
+ expect(calls.every(call => call.signal === controller.signal)).to.equal(true);
430
+ expect(JSON.parse(calls[0].body)).to.not.have.property('signal');
431
+ });
432
+
433
+ it('keeps the signal outside a built-in provider payload', async () => {
434
+ const controller = new AbortController();
435
+ const reason = new Error('stop OpenAI request');
436
+ const fetchStarted = deferred();
437
+ let request;
438
+ sinon.stub(global, 'fetch').callsFake((_url, init) => {
439
+ request = init;
440
+ fetchStarted.resolve();
441
+ return new Promise((_, rejectPromise) => {
442
+ init.signal.addEventListener('abort', () => rejectPromise(init.signal.reason), { once: true });
443
+ });
444
+ });
445
+ const model = ModelMix.new({ config: { bottleneck: { maxConcurrent: 1, minTime: 0 } } })
446
+ .gpt51()
447
+ .addText('test');
448
+
449
+ const execution = model.raw(controller.signal);
450
+ await fetchStarted.promise;
451
+ expect(request.signal).to.equal(controller.signal);
452
+ expect(JSON.parse(request.body)).to.not.have.property('signal');
453
+ controller.abort(reason);
454
+ expect(await rejection(execution)).to.equal(reason);
455
+ });
456
+
457
+ it('aborts the realtime WebSocket and preserves the custom reason', async () => {
458
+ const created = deferred();
459
+ class FakeWebSocket extends EventEmitter {
460
+ constructor() {
461
+ super();
462
+ this.readyState = 0;
463
+ this.terminated = false;
464
+ created.resolve(this);
465
+ setImmediate(() => {
466
+ if (this.terminated) return;
467
+ this.readyState = 1;
468
+ this.emit('open');
469
+ });
470
+ }
471
+
472
+ send() {}
473
+
474
+ close() {
475
+ this.terminate();
476
+ }
477
+
478
+ terminate() {
479
+ if (this.terminated) return;
480
+ this.terminated = true;
481
+ this.readyState = 3;
482
+ this.emit('close');
483
+ }
484
+ }
485
+ const wsPath = require.resolve('ws');
486
+ const originalWebSocket = require.cache[wsPath].exports;
487
+ require.cache[wsPath].exports = FakeWebSocket;
488
+ try {
489
+ const { MixOpenAIWebSocket } = createOpenAIProviders({
490
+ ModelMix,
491
+ MixCustom,
492
+ MixOpenAI,
493
+ MixModeration,
494
+ rejectsAnthropicSamplingParams
495
+ });
496
+ const provider = new MixOpenAIWebSocket({
497
+ config: {
498
+ apiKey: 'test-key',
499
+ realtimeUrl: 'ws://example.test',
500
+ websocketTimeoutMs: 2000
501
+ }
502
+ });
503
+ const model = ModelMix.new({ config: { bottleneck: { maxConcurrent: 1, minTime: 0 } } })
504
+ .attach('realtime-test', provider)
505
+ .addText('test');
506
+ const controller = new AbortController();
507
+ const reason = new Error('close realtime');
508
+ const execution = model.raw(controller.signal);
509
+ const socket = await created.promise;
510
+ controller.abort(reason);
511
+ expect(await rejection(execution)).to.equal(reason);
512
+ expect(socket.terminated).to.equal(true);
513
+ } finally {
514
+ require.cache[wsPath].exports = originalWebSocket;
515
+ }
516
+ });
517
+ });
@@ -242,6 +242,19 @@ describe('Unified effort scale', () => {
242
242
  expect(mapEffort('google', -1, 'gemini-3.7-flash')).to.equal(null);
243
243
  });
244
244
 
245
+ it('clamps Gemini 3.8 Flash to low, medium, and high', () => {
246
+ expect(mapEffort('google', 0, 'gemini-3.8-flash')).to.deep.equal({
247
+ thinkingConfig: { thinkingLevel: 'low' }
248
+ });
249
+ expect(mapEffort('google', 50, 'gemini-3.8-flash')).to.deep.equal({
250
+ thinkingConfig: { thinkingLevel: 'medium' }
251
+ });
252
+ expect(mapEffort('google', 100, 'gemini-3.8-flash')).to.deep.equal({
253
+ thinkingConfig: { thinkingLevel: 'high' }
254
+ });
255
+ expect(mapEffort('google', -1, 'gemini-3.8-flash')).to.equal(null);
256
+ });
257
+
245
258
  it('clamps Gemini levels for models with fewer steps', () => {
246
259
  expect(mapEffort('google', 10, 'gemini-3-pro-preview')).to.deep.equal({
247
260
  thinkingConfig: { thinkingLevel: 'low' }
@@ -34,12 +34,12 @@ describe('Provider Fallback Chain Tests', () => {
34
34
  });
35
35
 
36
36
  it('should attach chain shortcuts from arguments with optional per-model effort', () => {
37
- model.chain('sonnet5', 'gpt56luna@20', 'gemini37flash@-1');
37
+ model.chain('sonnet5', 'gpt56luna@20', 'gemini38flash@-1');
38
38
 
39
39
  expect(model.models.map(({ key }) => key)).to.deep.equal([
40
40
  'claude-sonnet-5',
41
41
  'gpt-5.6-luna',
42
- 'gemini-3.7-flash'
42
+ 'gemini-3.8-flash'
43
43
  ]);
44
44
  expect(model.models[0].provider.config).to.not.have.property('effort');
45
45
  expect(model.models[1].provider.config.effort).to.equal(20);
package/test/live.mcp.js CHANGED
@@ -181,8 +181,8 @@ describe('Live MCP Integration Tests', function () {
181
181
  }
182
182
  });
183
183
 
184
- it('should use custom MCP tools with Gemini 3.7 Flash', async function () {
185
- const model = ModelMix.new(setup).gemini37flash();
184
+ it('should use custom MCP tools with Gemini 3.8 Flash', async function () {
185
+ const model = ModelMix.new(setup).gemini38flash();
186
186
 
187
187
  // Add password generator tool
188
188
  model.addTool({
@@ -220,7 +220,7 @@ describe('Live MCP Integration Tests', function () {
220
220
  model.addText('Generate a secure password of 16 characters with symbols.');
221
221
 
222
222
  const response = await model.message();
223
- console.log(`Gemini 3.7 Flash with MCP tools: ${response}`);
223
+ console.log(`Gemini 3.8 Flash with MCP tools: ${response}`);
224
224
 
225
225
  expect(response).to.be.a('string');
226
226
  // Check password is mentioned and a generated password string is present
@@ -407,8 +407,8 @@ describe('Live MCP Integration Tests', function () {
407
407
  expect(result.factorial_result).to.equal(120);
408
408
  });
409
409
 
410
- it('should use MCP tools with JSON output using Gemini 3.7 Flash', async function () {
411
- const model = ModelMix.new(setup).gemini37flash();
410
+ it('should use MCP tools with JSON output using Gemini 3.8 Flash', async function () {
411
+ const model = ModelMix.new(setup).gemini38flash();
412
412
 
413
413
  // Add system info tool
414
414
  model.addTool({
@@ -447,7 +447,7 @@ describe('Live MCP Integration Tests', function () {
447
447
  generated_at: ""
448
448
  });
449
449
 
450
- console.log(`Gemini 3.7 Flash with MCP tools JSON result:`, result);
450
+ console.log(`Gemini 3.8 Flash with MCP tools JSON result:`, result);
451
451
 
452
452
  expect(result).to.be.an('object');
453
453
  expect(result.timestamp).to.be.a('number');
package/test/live.test.js CHANGED
@@ -59,7 +59,7 @@ describe('Live Integration Tests', function () {
59
59
  });
60
60
 
61
61
  it('should process images with Google Gemini', async function () {
62
- const model = ModelMix.new(setup).gemini37flash();
62
+ const model = ModelMix.new(setup).gemini38flash();
63
63
 
64
64
  model.addImageFromUrl(blueSquareBase64)
65
65
  .addText('What color is this image? Answer in one word only.');
@@ -120,7 +120,7 @@ describe('Live Integration Tests', function () {
120
120
  });
121
121
 
122
122
  it('should return structured JSON with Google Gemini', async function () {
123
- const model = ModelMix.new(setup).gemini37flash();
123
+ const model = ModelMix.new(setup).gemini38flash();
124
124
 
125
125
  model.addText('Generate information about a fictional city.');
126
126
 
@@ -181,7 +181,9 @@ describe('Live Integration Tests', function () {
181
181
  describe('Additional Model Tests', function () {
182
182
 
183
183
  it('should work with GPT-OSS model', async function () {
184
- const model = ModelMix.new(setup).gptOss();
184
+ const model = ModelMix.new(setup).gptOss({
185
+ options: { max_tokens: 128 }
186
+ });
185
187
 
186
188
  model.addText('Say "gptoss test successful" and nothing else.');
187
189
 
@@ -241,8 +243,11 @@ describe('Live Integration Tests', function () {
241
243
 
242
244
  describe('JSON Structured Output for New Models', function () {
243
245
 
244
- it('should return structured JSON with KimiK25 Thinking', async function () {
245
- const model = ModelMix.new(setup).kimiK25();
246
+ it('should return structured JSON with Kimi K3', async function () {
247
+ const model = ModelMix.new(setup).kimiK3({
248
+ options: { max_tokens: 512 },
249
+ mix: { moonshot: false, together: true }
250
+ });
246
251
 
247
252
  model.addText('Generate information about a fictional vehicle.');
248
253
 
@@ -253,7 +258,7 @@ describe('Live Integration Tests', function () {
253
258
  manufacturer: "Future Motors"
254
259
  });
255
260
 
256
- console.log(`KimiK25 JSON result:`, result);
261
+ console.log(`Kimi K3 JSON result:`, result);
257
262
 
258
263
  expect(result).to.be.an('object');
259
264
  expect(result).to.have.property('name');