modelmix 5.2.3 → 5.3.1

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.
@@ -115,13 +115,11 @@ describe('Provider Fallback Chain Tests', () => {
115
115
  expect(ModelMix.new().mix.openrouter).to.equal(false);
116
116
  const shortcuts = [
117
117
  ['gpt6astra', 'gpt-6-astra', 'openai/gpt-6-astra', MixOpenAIResponses],
118
- ['gpt5', 'gpt-5', 'openai/gpt-5', MixOpenAI],
118
+ ['gpt6sol', 'gpt-6-sol', 'openai/gpt-6-sol', MixOpenAIResponses],
119
+ ['gpt6luna', 'gpt-6-luna', 'openai/gpt-6-luna', MixOpenAIResponses],
119
120
  ['gpt5mini', 'gpt-5-mini', 'openai/gpt-5-mini', MixOpenAI],
120
121
  ['gpt5nano', 'gpt-5-nano', 'openai/gpt-5-nano', MixOpenAI],
121
- ['gpt51', 'gpt-5.1', 'openai/gpt-5.1', MixOpenAIResponses],
122
122
  ['gpt52', 'gpt-5.2', 'openai/gpt-5.2', MixOpenAIResponses],
123
- ['gpt53codex', 'gpt-5.3-codex', 'openai/gpt-5.3-codex', MixOpenAIResponses],
124
- ['gpt53chat', 'gpt-5.3-chat-latest', 'openai/gpt-5.3-chat', MixOpenAIResponses],
125
123
  ['gpt54', 'gpt-5.4', 'openai/gpt-5.4', MixOpenAIResponses],
126
124
  ['gpt54mini', 'gpt-5.4-mini', 'openai/gpt-5.4-mini', MixOpenAIResponses],
127
125
  ['gpt54nano', 'gpt-5.4-nano', 'openai/gpt-5.4-nano', MixOpenAIResponses],
@@ -151,10 +149,10 @@ describe('Provider Fallback Chain Tests', () => {
151
149
  .gpt56sol({ mix: { openrouter: false } });
152
150
  expect(locallyDisabled.models.map(({ key }) => key)).to.deep.equal(['gpt-5.6-sol']);
153
151
 
154
- const inherited = ModelMix.new({ mix: { openrouter: true } }).new().gpt5();
152
+ const inherited = ModelMix.new({ mix: { openrouter: true } }).new().gpt5mini();
155
153
  expect(inherited.models.map(({ key }) => key)).to.deep.equal([
156
- 'gpt-5',
157
- 'openai/gpt-5'
154
+ 'gpt-5-mini',
155
+ 'openai/gpt-5-mini'
158
156
  ]);
159
157
  });
160
158
 
@@ -232,6 +230,48 @@ describe('Provider Fallback Chain Tests', () => {
232
230
  expect(openRouterRequest).to.not.have.property('temperature');
233
231
  });
234
232
 
233
+ it('should fallback from the official GPT-6 Sol and Luna endpoints to OpenRouter', async () => {
234
+ const cases = [
235
+ ['gpt6sol', 'gpt-6-sol', 'Hello from GPT-6 Sol through OpenRouter!'],
236
+ ['gpt6luna', 'gpt-6-luna', 'Hello from GPT-6 Luna through OpenRouter!']
237
+ ];
238
+
239
+ for (const [shortcut, officialKey, content] of cases) {
240
+ let openRouterRequest;
241
+ const instance = ModelMix.new().effort(100);
242
+ instance[shortcut]({ mix: { openrouter: true } }).addText('Hello');
243
+
244
+ nock('https://api.openai.com')
245
+ .post('/v1/responses', body => {
246
+ expect(body.model).to.equal(officialKey);
247
+ expect(body.reasoning).to.deep.equal({ effort: 'max' });
248
+ expect(body.max_output_tokens).to.equal(8192);
249
+ expect(body).to.not.have.property('temperature');
250
+ return true;
251
+ })
252
+ .reply(503, { error: 'Service unavailable' });
253
+
254
+ nock('https://openrouter.ai')
255
+ .post('/api/v1/chat/completions', body => {
256
+ openRouterRequest = body;
257
+ return true;
258
+ })
259
+ .reply(200, {
260
+ choices: [{
261
+ message: {
262
+ role: 'assistant',
263
+ content
264
+ }
265
+ }]
266
+ });
267
+
268
+ expect(await instance.message()).to.equal(content);
269
+ expect(openRouterRequest.model).to.equal(`openai/${officialKey}`);
270
+ expect(openRouterRequest.max_completion_tokens).to.equal(8192);
271
+ expect(openRouterRequest).to.not.have.property('temperature');
272
+ }
273
+ });
274
+
235
275
  it('should keep the default fable51 chain on Anthropic', () => {
236
276
  model.chain('fable51@80');
237
277
 
@@ -265,7 +305,7 @@ describe('Provider Fallback Chain Tests', () => {
265
305
  });
266
306
 
267
307
  it('should use primary provider when available', async () => {
268
- model.gpt5mini().sonnet46().addText('Hello');
308
+ model.gpt5mini().sonnet5().addText('Hello');
269
309
 
270
310
  // Mock successful OpenAI response
271
311
  nock('https://api.openai.com')
@@ -285,7 +325,7 @@ describe('Provider Fallback Chain Tests', () => {
285
325
  });
286
326
 
287
327
  it('should fallback to secondary provider when primary fails', async () => {
288
- model.gpt5mini().sonnet46().addText('Hello');
328
+ model.gpt5mini().sonnet5().addText('Hello');
289
329
 
290
330
  // Mock failed OpenAI response (GPT-5 mini)
291
331
  nock('https://api.openai.com')
@@ -399,7 +439,7 @@ describe('Provider Fallback Chain Tests', () => {
399
439
  });
400
440
 
401
441
  it('should cascade through multiple fallbacks', async () => {
402
- model.gpt5mini().sonnet46().gemini37flash().addText('Hello');
442
+ model.gpt5mini().sonnet5().gemini37flash().addText('Hello');
403
443
 
404
444
  // Mock failed OpenAI response
405
445
  nock('https://api.openai.com')
@@ -430,7 +470,7 @@ describe('Provider Fallback Chain Tests', () => {
430
470
  });
431
471
 
432
472
  it('should throw error when all providers fail', async () => {
433
- model.gpt5mini().sonnet46().addText('Hello');
473
+ model.gpt5mini().sonnet5().addText('Hello');
434
474
 
435
475
  // Mock all providers failing
436
476
  nock('https://api.openai.com')
@@ -460,7 +500,7 @@ describe('Provider Fallback Chain Tests', () => {
460
500
  });
461
501
 
462
502
  it('should fallback from OpenAI to Anthropic', async () => {
463
- model.gpt5mini().sonnet46().addText('Test message');
503
+ model.gpt5mini().sonnet5().addText('Test message');
464
504
 
465
505
  // Mock OpenAI failure
466
506
  nock('https://api.openai.com')
@@ -483,7 +523,7 @@ describe('Provider Fallback Chain Tests', () => {
483
523
  });
484
524
 
485
525
  it('should fallback from Anthropic to Google', async () => {
486
- model.sonnet46().gemini37flash().addText('Test message');
526
+ model.sonnet5().gemini37flash().addText('Test message');
487
527
 
488
528
  // Mock Anthropic failure
489
529
  nock('https://api.anthropic.com')
@@ -509,7 +549,7 @@ describe('Provider Fallback Chain Tests', () => {
509
549
  });
510
550
 
511
551
  it('should handle network timeout fallback', async () => {
512
- model.gpt5mini().sonnet46().addText('Hello');
552
+ model.gpt5mini().sonnet5().addText('Hello');
513
553
 
514
554
  // Mock timeout error on first provider (using 408 Request Timeout)
515
555
  nock('https://api.openai.com')
@@ -543,7 +583,7 @@ describe('Provider Fallback Chain Tests', () => {
543
583
 
544
584
  it('should handle JSON fallback correctly', async () => {
545
585
  const schema = { name: 'Alice', age: 30 };
546
- model.gpt5mini().sonnet46().addText('Generate user data');
586
+ model.gpt5mini().sonnet5().addText('Generate user data');
547
587
 
548
588
  // Mock OpenAI failure
549
589
  nock('https://api.openai.com')
@@ -569,7 +609,7 @@ describe('Provider Fallback Chain Tests', () => {
569
609
  });
570
610
 
571
611
  it('should preserve message history through fallbacks', async () => {
572
- model.gpt5mini().sonnet46()
612
+ model.gpt5mini().sonnet5()
573
613
  .addText('First message')
574
614
  .addText('Second message');
575
615
 
@@ -600,7 +640,7 @@ describe('Provider Fallback Chain Tests', () => {
600
640
  config: { debug: false, retry: { enabled: false } }
601
641
  });
602
642
 
603
- model.gpt5mini().sonnet46().addText('Hello');
643
+ model.gpt5mini().sonnet5().addText('Hello');
604
644
 
605
645
  nock('https://api.openai.com')
606
646
  .post('/v1/chat/completions')
@@ -648,7 +688,7 @@ describe('Provider Fallback Chain Tests', () => {
648
688
  config: { debug: false, retry: { enabled: true, retries: 2, baseDelayMs: 0, maxDelayMs: 0 } }
649
689
  });
650
690
 
651
- model.gpt5mini().sonnet46().addText('Hello');
691
+ model.gpt5mini().sonnet5().addText('Hello');
652
692
 
653
693
  nock('https://api.openai.com')
654
694
  .post('/v1/chat/completions')
@@ -672,7 +712,7 @@ describe('Provider Fallback Chain Tests', () => {
672
712
  config: { debug: false, retry: { enabled: true, retries: 1, baseDelayMs: 0, maxDelayMs: 0 } }
673
713
  });
674
714
 
675
- model.gpt5mini().sonnet46().addText('Hello');
715
+ model.gpt5mini().sonnet5().addText('Hello');
676
716
 
677
717
  nock('https://api.openai.com')
678
718
  .post('/v1/chat/completions')
@@ -702,7 +742,7 @@ describe('Provider Fallback Chain Tests', () => {
702
742
 
703
743
  // Configure with custom temperature for fallback
704
744
  model.gpt5mini({ options: { temperature: 0.6 } })
705
- .sonnet46({ options: { temperature: 0.7 } })
745
+ .sonnet45({ options: { temperature: 0.7 } })
706
746
  .addText('Creative response');
707
747
 
708
748
  // Mock first provider failure
@@ -735,7 +775,7 @@ describe('Provider Fallback Chain Tests', () => {
735
775
  });
736
776
 
737
777
  model.gpt5mini({ options: { max_tokens: 100 } })
738
- .sonnet46({ options: { max_tokens: 200 } })
778
+ .sonnet5({ options: { max_tokens: 200 } })
739
779
  .addText('Generate text');
740
780
 
741
781
  // Mock OpenAI failure
@@ -869,7 +909,7 @@ describe('Provider Fallback Chain Tests', () => {
869
909
  });
870
910
 
871
911
  it('should provide detailed error information when all fallbacks fail', async () => {
872
- model.gpt5mini().sonnet46().gemini37flash().addText('Test');
912
+ model.gpt5mini().sonnet5().gemini37flash().addText('Test');
873
913
 
874
914
  // Mock all providers failing with different errors
875
915
  nock('https://api.openai.com')
@@ -894,7 +934,7 @@ describe('Provider Fallback Chain Tests', () => {
894
934
  });
895
935
 
896
936
  it('should handle malformed responses in fallback', async () => {
897
- model.gpt5mini().sonnet46().addText('Test');
937
+ model.gpt5mini().sonnet5().addText('Test');
898
938
 
899
939
  // Mock malformed response from first provider
900
940
  nock('https://api.openai.com')
package/test/grok.test.js CHANGED
@@ -13,10 +13,7 @@ describe('Grok Model Registration Tests', () => {
13
13
  const grokModels = [
14
14
  { method: 'grok47', key: 'grok-4.7' },
15
15
  { method: 'grok46', key: 'grok-4.6' },
16
- { method: 'grok45', key: 'grok-4.5' },
17
- { method: 'grok43', key: 'grok-4.3' },
18
- { method: 'grok420multiAgent', key: 'grok-4.20-multi-agent-0309' },
19
- { method: 'grok420', key: GROK420_ALIAS }
16
+ { method: 'grok43', key: 'grok-4.3' }
20
17
  ];
21
18
 
22
19
  for (const grokModel of grokModels) {
@@ -47,7 +47,7 @@ describe('Conversation History Tests', () => {
47
47
  });
48
48
 
49
49
  it('should add assistant response to message history after raw()', async () => {
50
- model.sonnet46().addText('Hello');
50
+ model.sonnet5().addText('Hello');
51
51
 
52
52
  nock('https://api.anthropic.com')
53
53
  .post('/v1/messages')
@@ -128,7 +128,7 @@ describe('Conversation History Tests', () => {
128
128
  const model = ModelMix.new({
129
129
  config: { debug: false, max_history: 10 }
130
130
  });
131
- model.sonnet46();
131
+ model.sonnet5();
132
132
 
133
133
  // First turn
134
134
  model.addText('Capital of France?');
@@ -51,10 +51,10 @@ describe('Image Processing and Multimodal Support Tests', () => {
51
51
  expect(response).to.include('I can see a small test image');
52
52
  });
53
53
 
54
- it('should support multimodal with sonnet46()', async () => {
54
+ it('should support multimodal with sonnet5()', async () => {
55
55
  const base64Image = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAFUlEQVR42mP8z8BQz0AEYBxVSF+FABJADveWkH6oAAAAAElFTkSuQmCC';
56
56
 
57
- model.sonnet46()
57
+ model.sonnet5()
58
58
  .addText('Describe this image')
59
59
  .addImageFromUrl(base64Image);
60
60
 
@@ -87,7 +87,7 @@ describe('Image Processing and Multimodal Support Tests', () => {
87
87
  const pngBase64 = 'iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAFUlEQVR42mP8z8BQz0AEYBxVSF+FABJADveWkH6oAAAAAElFTkSuQmCC';
88
88
  const pngBuffer = Buffer.from(pngBase64, 'base64');
89
89
 
90
- model.sonnet46()
90
+ model.sonnet5()
91
91
  .addText('Describe this image')
92
92
  .addImageFromUrl(imageUrl);
93
93
 
package/test/json.test.js CHANGED
@@ -412,7 +412,7 @@ describe('JSON Schema and Structured Output Tests', () => {
412
412
  }]
413
413
  };
414
414
 
415
- model.gpt51().addText('Name and capital of 3 South American countries.');
415
+ model.gpt52().addText('Name and capital of 3 South American countries.');
416
416
 
417
417
  let capturedBody;
418
418
  nock('https://api.openai.com')
@@ -462,7 +462,7 @@ describe('JSON Schema and Structured Output Tests', () => {
462
462
  }
463
463
  };
464
464
 
465
- model.sonnet46().addText('Generate user data');
465
+ model.sonnet5().addText('Generate user data');
466
466
 
467
467
  // Mock the API response
468
468
  nock('https://api.anthropic.com')
package/test/live.test.js CHANGED
@@ -180,24 +180,22 @@ describe('Live Integration Tests', function () {
180
180
 
181
181
  describe('Additional Model Tests', function () {
182
182
 
183
- it('should work with GPT-OSS model', async function () {
184
- const model = ModelMix.new(setup).gptOss({
183
+ it('should work with Kimi K2.6 model', async function () {
184
+ const model = ModelMix.new(setup).kimiK26({
185
185
  options: { max_tokens: 128 }
186
186
  });
187
187
 
188
- model.addText('Say "gptoss test successful" and nothing else.');
188
+ model.addText('Say "Kimik26 test successful" and nothing else.');
189
189
 
190
190
  const response = await model.message();
191
- console.log(`GPT-OSS response: ${response}`);
191
+ console.log(`Kimi K2.6 response: ${response}`);
192
192
 
193
193
  expect(response).to.be.a('string');
194
- expect(response.toLowerCase()).to.include('gptoss test successful');
194
+ expect(response.toLowerCase()).to.include('kimik26 test successful');
195
195
  });
196
196
 
197
197
  const grokSeriesTests = [
198
- { name: 'Grok 4.6', factory: (m) => m.grok46(), token: 'grok46' },
199
- { name: 'Grok 4.20 reasoning', factory: (m) => m.effort(50).grok420(), token: 'grok420' },
200
- { name: 'Grok 4.20 non-reasoning', factory: (m) => m.grok420(), token: 'grok420nr' }
198
+ { name: 'Grok 4.6', factory: (m) => m.grok46(), token: 'grok46' }
201
199
  ];
202
200
 
203
201
  for (const grokModel of grokSeriesTests) {
@@ -16,10 +16,10 @@ describe('Provider expansion regressions', () => {
16
16
  process.env.MINIMAX_API_KEY = 'test-minimax-key';
17
17
 
18
18
  try {
19
- const gptOss = ModelMix.new().gptOss();
19
+ const kimi = ModelMix.new().kimiK26();
20
20
  const minimax = ModelMix.new().minimaxM27();
21
21
 
22
- expect(gptOss.models.some(({ provider }) => provider instanceof MixOpenRouter)).to.equal(false);
22
+ expect(kimi.models.some(({ provider }) => provider instanceof MixOpenRouter)).to.equal(false);
23
23
  expect(minimax.models.map(({ key }) => key)).to.deep.equal(['MiniMax-M2.7']);
24
24
  expect(minimax.models[0].provider).to.be.instanceOf(MixMiniMax);
25
25
  } finally {
@@ -28,33 +28,26 @@ describe('Provider expansion regressions', () => {
28
28
  }
29
29
  });
30
30
 
31
- it('should retain every enabled GPT OSS provider, including shared model keys', () => {
32
- const model = ModelMix.new().gptOss({
31
+ it('should retain every enabled Muse Glimmer 30B provider, including shared model keys', () => {
32
+ const model = ModelMix.new().museGlimmer30b({
33
33
  mix: {
34
34
  nvidia: true,
35
35
  fireworks: true,
36
36
  together: true,
37
- cerebras: true,
38
- groq: true,
39
37
  openrouter: true
40
38
  }
41
39
  });
42
40
 
43
41
  expect(model.models.map(({ key }) => key)).to.deep.equal([
44
- 'openai/gpt-oss-120b',
45
- 'accounts/fireworks/models/gpt-oss-120b',
46
- 'openai/gpt-oss-120b',
47
- 'gpt-oss-120b',
48
- 'openai/gpt-oss-120b',
49
- 'openai/gpt-oss-120b'
42
+ 'meta/muse-glimmer-30b',
43
+ 'accounts/fireworks/models/muse-glimmer-30b',
44
+ 'meta/muse-glimmer-30b',
45
+ 'meta-models/Muse-Glimmer-30B'
50
46
  ]);
51
47
  expect(model.models[0].provider).to.be.instanceOf(MixNVIDIA);
52
48
  expect(model.models[1].provider).to.be.instanceOf(MixFireworks);
53
- expect(ModelMix.calculateCost('accounts/fireworks/models/gpt-oss-120b', {
54
- input: 1_000_000,
55
- cached: 500_000,
56
- output: 1_000_000
57
- })).to.equal(0.682);
49
+ expect(model.models[2].provider).to.be.instanceOf(MixOpenRouter);
50
+ expect(model.models[3].provider).to.be.instanceOf(MixTogether);
58
51
  });
59
52
 
60
53
  it('should retain every enabled MiniMax M2.7 provider in fallback order', () => {
@@ -38,7 +38,7 @@ describe('EJS Template and File Operations Tests', () => {
38
38
  describe('EJS rendering', () => {
39
39
  it('renders inline variables with plain data keys', async () => {
40
40
  const model = ModelMix.new()
41
- .gpt51()
41
+ .gpt52()
42
42
  .assign({ name: 'Alice', age: 30, city: 'New York' })
43
43
  .addText('Hello <%- name %>, you are <%- age %> years old and live in <%- city %>.');
44
44
 
@@ -53,7 +53,7 @@ describe('EJS Template and File Operations Tests', () => {
53
53
 
54
54
  it('assigns one template data key', async () => {
55
55
  const model = ModelMix.new()
56
- .gpt51()
56
+ .gpt52()
57
57
  .assignKey('name', 'Martin')
58
58
  .addText('Hello <%- name %>.');
59
59
 
@@ -66,7 +66,7 @@ describe('EJS Template and File Operations Tests', () => {
66
66
 
67
67
  it('supports nested data, conditionals, and loops', async () => {
68
68
  const model = ModelMix.new()
69
- .gpt51()
69
+ .gpt52()
70
70
  .assign({
71
71
  user: {
72
72
  name: 'Charlie',
@@ -86,7 +86,7 @@ describe('EJS Template and File Operations Tests', () => {
86
86
  it('keeps raw and XML-escaped output distinct', async () => {
87
87
  const value = 'Hello & "World" <test>';
88
88
  const model = ModelMix.new()
89
- .gpt51()
89
+ .gpt52()
90
90
  .assign({ value })
91
91
  .addText('Escaped: <%= value %>\nRaw: <%- value %>');
92
92
 
@@ -101,7 +101,7 @@ describe('EJS Template and File Operations Tests', () => {
101
101
 
102
102
  it('does not execute EJS received through template data', async () => {
103
103
  const model = ModelMix.new()
104
- .gpt51()
104
+ .gpt52()
105
105
  .assign({ payload: '<%- secret %>', secret: 'must-not-render' })
106
106
  .addText('Payload: <%- payload %>');
107
107
 
@@ -114,7 +114,7 @@ describe('EJS Template and File Operations Tests', () => {
114
114
 
115
115
  it('selects uniformly when choice options omit weights', async () => {
116
116
  const model = ModelMix.new()
117
- .gpt51()
117
+ .gpt52()
118
118
  .addText(`<% choice %>
119
119
  <% option %>
120
120
  Use emojis.
@@ -134,7 +134,7 @@ Do not use emojis.
134
134
 
135
135
  it('selects weighted options using relative weights', async () => {
136
136
  const model = ModelMix.new()
137
- .gpt51()
137
+ .gpt52()
138
138
  .assign({ language: 'Spanish' })
139
139
  .addText(`<% choice %>
140
140
  <% option 20 %>
@@ -155,7 +155,7 @@ Do not use emojis in <%- language %>.
155
155
 
156
156
  it('supports nested choices', async () => {
157
157
  const model = ModelMix.new()
158
- .gpt51()
158
+ .gpt52()
159
159
  .addText(`<% choice %>
160
160
  <% option %>
161
161
  Tone:
@@ -187,7 +187,7 @@ first
187
187
  <% option %>
188
188
  second
189
189
  <% /choice %>`;
190
- const model = ModelMix.new().gpt51().addText(template);
190
+ const model = ModelMix.new().gpt52().addText(template);
191
191
  const random = sinon.stub(model, '_choiceRandom');
192
192
  random.onFirstCall().returns(0.1);
193
193
  random.onSecondCall().returns(0.9);
@@ -208,7 +208,7 @@ second
208
208
 
209
209
  it('fails before the request when a variable is missing', async () => {
210
210
  const model = ModelMix.new()
211
- .gpt51()
211
+ .gpt52()
212
212
  .assign({ name: 'David' })
213
213
  .addText('Hello <%- name %>, status: <%- status %>');
214
214
 
@@ -225,7 +225,7 @@ second
225
225
  });
226
226
 
227
227
  it('rejects invalid template data immediately', () => {
228
- const model = ModelMix.new().gpt51();
228
+ const model = ModelMix.new().gpt52();
229
229
 
230
230
  expect(() => model.assign(null)).to.throw(TypeError, 'Template data must be a plain non-null object.');
231
231
  expect(() => model.assign(undefined)).to.throw(TypeError, 'Template data must be a plain non-null object.');
@@ -278,7 +278,7 @@ second
278
278
  ];
279
279
 
280
280
  for (const testCase of cases) {
281
- const model = ModelMix.new().gpt51().addText(testCase.source);
281
+ const model = ModelMix.new().gpt52().addText(testCase.source);
282
282
  let error;
283
283
  try {
284
284
  await model.message();
@@ -293,7 +293,7 @@ second
293
293
 
294
294
  it('rerolls earlier choices after a later template fails to render', async () => {
295
295
  const model = ModelMix.new()
296
- .gpt51()
296
+ .gpt52()
297
297
  .addText(`<% choice %>
298
298
  <% option %>
299
299
  A
@@ -333,7 +333,7 @@ A
333
333
  <% option %>
334
334
  B
335
335
  <% /choice %>`;
336
- const model = ModelMix.new().gpt51().addText(template);
336
+ const model = ModelMix.new().gpt52().addText(template);
337
337
  const random = sinon.stub(model, '_choiceRandom');
338
338
  random.onFirstCall().returns(0.1);
339
339
  random.onSecondCall().returns(0.9);
@@ -370,7 +370,7 @@ B
370
370
  max_history: 10,
371
371
  bottleneck: { maxConcurrent: 2, minTime: 0 }
372
372
  }
373
- }).gpt51().addText(template);
373
+ }).gpt52().addText(template);
374
374
  const content = model.messages[0].content[0];
375
375
  const random = sinon.stub(model, '_choiceRandom');
376
376
  random.onFirstCall().returns(0.1);
@@ -396,7 +396,7 @@ B
396
396
  describe('File templates and data', () => {
397
397
  it('renders a file template with a relative include', async () => {
398
398
  const model = ModelMix.new()
399
- .gpt51()
399
+ .gpt52()
400
400
  .assign({
401
401
  name: 'Eve',
402
402
  platform: 'ModelMix',
@@ -423,7 +423,7 @@ B
423
423
 
424
424
  it('resolves a dynamic include path relative to its template', async () => {
425
425
  const model = ModelMix.new()
426
- .gpt51()
426
+ .gpt52()
427
427
  .assign({ rulesFile: 'system-rules.txt', language: 'Spanish' })
428
428
  .addTextFromFile(path.join(fixturesPath, 'dynamic-include.txt'));
429
429
 
@@ -436,7 +436,7 @@ B
436
436
 
437
437
  it('processes choice directives inside relative includes', async () => {
438
438
  const model = ModelMix.new()
439
- .gpt51()
439
+ .gpt52()
440
440
  .addTextFromFile(path.join(fixturesPath, 'choice-template.txt'));
441
441
  sinon.stub(model, '_choiceRandom').returns(0.75);
442
442
 
@@ -449,7 +449,7 @@ B
449
449
 
450
450
  it('supports recursive includes with an explicit depth limit', async () => {
451
451
  const model = ModelMix.new()
452
- .gpt51()
452
+ .gpt52()
453
453
  .assign({
454
454
  node: {
455
455
  text: 'Root',
@@ -481,7 +481,7 @@ B
481
481
  const base = ModelMix.new()
482
482
  .setSystemFromFile(path.join(fixturesPath, 'system-template.txt'))
483
483
  .assign({ role: 'data analyst', language: 'Spanish' });
484
- const model = base.new().gpt51().addText('Analyze this.');
484
+ const model = base.new().gpt52().addText('Analyze this.');
485
485
 
486
486
  mockOpenAI(body => {
487
487
  const system = body.input.find(message => message.role === 'developer');
@@ -494,7 +494,7 @@ B
494
494
 
495
495
  it('renders assigned files through EJS includes, including their relative includes', async () => {
496
496
  const model = ModelMix.new()
497
- .gpt51()
497
+ .gpt52()
498
498
  .assign({
499
499
  name: 'Eve',
500
500
  platform: 'ModelMix',
@@ -523,7 +523,7 @@ B
523
523
  const base = ModelMix.new()
524
524
  .assign({ language: 'Spanish' })
525
525
  .assignKeyFromFile('rules', path.join(fixturesPath, 'system-rules.txt'));
526
- const model = base.new().gpt51().addText('Rules:\n<%- rules %>');
526
+ const model = base.new().gpt52().addText('Rules:\n<%- rules %>');
527
527
 
528
528
  mockOpenAI(body => {
529
529
  expect(userTexts(body)[0].trim()).to.equal('Rules:\nAlways respond in Spanish.');
@@ -534,7 +534,7 @@ B
534
534
 
535
535
  it('uses the latest assignment when a plain value and a file share a key', async () => {
536
536
  const plainValue = ModelMix.new()
537
- .gpt51()
537
+ .gpt52()
538
538
  .assign({ language: 'Spanish' })
539
539
  .assignKeyFromFile('rules', path.join(fixturesPath, 'system-rules.txt'))
540
540
  .assignKey('rules', 'Use the plain value.')
@@ -546,7 +546,7 @@ B
546
546
  await plainValue.message();
547
547
 
548
548
  const fileValue = ModelMix.new()
549
- .gpt51()
549
+ .gpt52()
550
550
  .assign({ language: 'Spanish', rules: 'Ignore this value.' })
551
551
  .assignKeyFromFile('rules', path.join(fixturesPath, 'system-rules.txt'))
552
552
  .addText('<%- rules %>');
@@ -559,7 +559,7 @@ B
559
559
 
560
560
  it('injects JSON file contents without XML escaping', async () => {
561
561
  const model = ModelMix.new()
562
- .gpt51()
562
+ .gpt52()
563
563
  .assignKeyFromFile('data', path.join(fixturesPath, 'data.json'))
564
564
  .addText('Process this data:\n<%- data %>');
565
565
 
@@ -580,7 +580,7 @@ B
580
580
  try {
581
581
  fs.writeFileSync(assignedFile, 'Version one for <%- name %>.');
582
582
  const model = ModelMix.new()
583
- .gpt51()
583
+ .gpt52()
584
584
  .assign({ name: 'Eve' })
585
585
  .assignKeyFromFile('content', assignedFile)
586
586
  .addText('<%- content %>');
@@ -602,7 +602,7 @@ B
602
602
  });
603
603
 
604
604
  it('throws immediately when a template or data file is missing', () => {
605
- const model = ModelMix.new().gpt51();
605
+ const model = ModelMix.new().gpt52();
606
606
  const missingPath = path.join(fixturesPath, 'nonexistent.txt');
607
607
 
608
608
  expect(() => model.addTextFromFile(missingPath)).to.throw(`File not found: ${missingPath}`);
@@ -622,7 +622,7 @@ B
622
622
  it('renders system and message templates for JSON output', async () => {
623
623
  const schema = { summary: 'Analysis summary', userCount: 0 };
624
624
  const model = ModelMix.new()
625
- .gpt51()
625
+ .gpt52()
626
626
  .setSystem('You are a <%- role %>.')
627
627
  .assign({ role: 'data analyst', instruction: 'Count active users' })
628
628
  .assignKeyFromFile('data', path.join(fixturesPath, 'data.json'))
@@ -653,7 +653,7 @@ B
653
653
 
654
654
  it('renders the system before adding block instructions', async () => {
655
655
  const model = ModelMix.new()
656
- .gpt51()
656
+ .gpt52()
657
657
  .setSystem('Act as <%- role %>.')
658
658
  .assign({ role: 'reviewer' })
659
659
  .addText('Review this.');
@@ -670,7 +670,7 @@ B
670
670
 
671
671
  it('keeps rendered history snapshots when template data changes', async () => {
672
672
  const model = ModelMix.new({ config: { max_history: 10 } })
673
- .gpt51()
673
+ .gpt52()
674
674
  .assign({ name: 'Alice' })
675
675
  .addText('Hello <%- name %>.');
676
676
 
@@ -689,8 +689,8 @@ B
689
689
  it('keeps a system choice stable across provider fallback', async () => {
690
690
  const systems = [];
691
691
  const model = ModelMix.new()
692
- .gpt51()
693
- .sonnet46()
692
+ .gpt52()
693
+ .sonnet5()
694
694
  .setSystem(`<% choice %>
695
695
  <% option %>
696
696
  First system.