modelmix 5.1.9 → 5.1.12

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.
@@ -77,6 +77,7 @@ describe('Unified effort scale', () => {
77
77
  expect(mapEffort('openai', 99, 'gpt-5.6-luna')).to.deep.equal({ reasoning_effort: 'xhigh' });
78
78
  for (const model of ['gpt-5.6-sol', 'gpt-5.6-terra', 'gpt-5.6-luna']) {
79
79
  expect(mapEffort('openai', 100, model)).to.deep.equal({ reasoning_effort: 'max' });
80
+ expect(mapEffort('openai', 100, `openai/${model}`)).to.deep.equal({ reasoning_effort: 'max' });
80
81
  }
81
82
  });
82
83
 
@@ -87,6 +88,8 @@ describe('Unified effort scale', () => {
87
88
 
88
89
  it('clamps OpenAI to model-supported levels', () => {
89
90
  expect(mapEffort('openai', 10, 'gpt-5.3-codex')).to.deep.equal({ reasoning_effort: 'low' });
91
+ expect(mapEffort('openai', 10, 'openai/gpt-5.3-codex')).to.deep.equal({ reasoning_effort: 'low' });
92
+ expect(mapEffort('openai', 0, 'openai/gpt-5')).to.deep.equal({ reasoning_effort: 'minimal' });
90
93
  expect(mapEffort('openai', 10, 'gpt-oss-120b')).to.deep.equal({ reasoning_effort: 'low' });
91
94
  });
92
95
 
@@ -132,6 +135,16 @@ describe('Unified effort scale', () => {
132
135
  });
133
136
  });
134
137
 
138
+ it('maps Muse Spark 1.2 Contributor to every supported reasoning level', () => {
139
+ const key = 'meta/muse-spark-1.2-contributor';
140
+ expect(mapEffort('openai', 0, key)).to.deep.equal({ reasoning_effort: 'minimal' });
141
+ expect(mapEffort('openai', 20, key)).to.deep.equal({ reasoning_effort: 'low' });
142
+ expect(mapEffort('openai', 40, key)).to.deep.equal({ reasoning_effort: 'medium' });
143
+ expect(mapEffort('openai', 60, key)).to.deep.equal({ reasoning_effort: 'high' });
144
+ expect(mapEffort('openai', 80, key)).to.deep.equal({ reasoning_effort: 'xhigh' });
145
+ expect(mapEffort('openai', -1, key)).to.equal(null);
146
+ });
147
+
135
148
  it('maps Qwen 3.8 27B to its supported reasoning levels', () => {
136
149
  const key = 'qwen/qwen3.8-27b';
137
150
  expect(mapEffort('openai', 0, key)).to.deep.equal({ reasoning_effort: 'low' });
@@ -171,7 +184,7 @@ describe('Unified effort scale', () => {
171
184
  thinking: { type: 'adaptive', display: 'summarized' },
172
185
  output_config: { effort: 'low' }
173
186
  });
174
- expect(mapEffort('anthropic', 90, 'claude-fable-5')).to.deep.equal({
187
+ expect(mapEffort('anthropic', 90, 'claude-fable-5-1')).to.deep.equal({
175
188
  thinking: { type: 'adaptive', display: 'summarized' },
176
189
  output_config: { effort: 'max' }
177
190
  });
@@ -181,6 +194,16 @@ describe('Unified effort scale', () => {
181
194
  });
182
195
  });
183
196
 
197
+ it('maps OpenRouter Claude Fable 5.1 to Anthropic effort levels', () => {
198
+ const key = 'anthropic/claude-fable-5.1';
199
+ expect(mapEffort('openai', 0, key)).to.deep.equal({ reasoning_effort: 'low' });
200
+ expect(mapEffort('openai', 20, key)).to.deep.equal({ reasoning_effort: 'medium' });
201
+ expect(mapEffort('openai', 40, key)).to.deep.equal({ reasoning_effort: 'high' });
202
+ expect(mapEffort('openai', 60, key)).to.deep.equal({ reasoning_effort: 'xhigh' });
203
+ expect(mapEffort('openai', 80, key)).to.deep.equal({ reasoning_effort: 'max' });
204
+ expect(mapEffort('openai', -1, key)).to.equal(null);
205
+ });
206
+
184
207
  it('maps Anthropic manual models to thinking.type=enabled + budget_tokens', () => {
185
208
  expect(mapEffort('anthropic', 50, 'claude-sonnet-4-5-20250929')).to.deep.equal({
186
209
  thinking: { type: 'enabled', budget_tokens: 8192 }
@@ -3,7 +3,14 @@ const sinon = require('sinon');
3
3
  const nock = require('nock');
4
4
  const { EventEmitter } = require('events');
5
5
  const Module = require('module');
6
- const { MixCustom, MixGoogle, ModelMix } = require('../index.js');
6
+ const {
7
+ MixCustom,
8
+ MixGoogle,
9
+ MixOpenAI,
10
+ MixOpenAIResponses,
11
+ MixOpenRouter,
12
+ ModelMix
13
+ } = require('../index.js');
7
14
 
8
15
  describe('Provider Fallback Chain Tests', () => {
9
16
 
@@ -47,6 +54,99 @@ describe('Provider Fallback Chain Tests', () => {
47
54
  expect(model.models[1].provider.config.effort).to.equal(20);
48
55
  });
49
56
 
57
+ it('should register every supported OpenAI text shortcut with its OpenRouter fallback', () => {
58
+ expect(ModelMix.new().mix.openrouter).to.equal(false);
59
+ const shortcuts = [
60
+ ['gpt5', 'gpt-5', 'openai/gpt-5', MixOpenAI],
61
+ ['gpt5mini', 'gpt-5-mini', 'openai/gpt-5-mini', MixOpenAI],
62
+ ['gpt5nano', 'gpt-5-nano', 'openai/gpt-5-nano', MixOpenAI],
63
+ ['gpt51', 'gpt-5.1', 'openai/gpt-5.1', MixOpenAIResponses],
64
+ ['gpt52', 'gpt-5.2', 'openai/gpt-5.2', MixOpenAIResponses],
65
+ ['gpt53codex', 'gpt-5.3-codex', 'openai/gpt-5.3-codex', MixOpenAIResponses],
66
+ ['gpt53chat', 'gpt-5.3-chat-latest', 'openai/gpt-5.3-chat', MixOpenAIResponses],
67
+ ['gpt54', 'gpt-5.4', 'openai/gpt-5.4', MixOpenAIResponses],
68
+ ['gpt54mini', 'gpt-5.4-mini', 'openai/gpt-5.4-mini', MixOpenAIResponses],
69
+ ['gpt54nano', 'gpt-5.4-nano', 'openai/gpt-5.4-nano', MixOpenAIResponses],
70
+ ['gpt54pro', 'gpt-5.4-pro', 'openai/gpt-5.4-pro', MixOpenAIResponses],
71
+ ['gpt55', 'gpt-5.5', 'openai/gpt-5.5', MixOpenAIResponses],
72
+ ['gpt55pro', 'gpt-5.5-pro', 'openai/gpt-5.5-pro', MixOpenAIResponses],
73
+ ['gpt56sol', 'gpt-5.6-sol', 'openai/gpt-5.6-sol', MixOpenAIResponses],
74
+ ['gpt56terra', 'gpt-5.6-terra', 'openai/gpt-5.6-terra', MixOpenAIResponses],
75
+ ['gpt56luna', 'gpt-5.6-luna', 'openai/gpt-5.6-luna', MixOpenAIResponses]
76
+ ];
77
+
78
+ for (const [shortcut, officialKey, openRouterKey, OfficialProvider] of shortcuts) {
79
+ const official = ModelMix.new()[shortcut]();
80
+ expect(official.models.map(({ key }) => key)).to.deep.equal([officialKey]);
81
+ expect(official.models[0].provider).to.be.instanceOf(OfficialProvider);
82
+
83
+ const routed = ModelMix.new()[shortcut]({ mix: { openrouter: true } });
84
+ expect(routed.models.map(({ key }) => key)).to.deep.equal([officialKey, openRouterKey]);
85
+ expect(routed.models[0].provider).to.be.instanceOf(OfficialProvider);
86
+ expect(routed.models[1].provider).to.be.instanceOf(MixOpenRouter);
87
+
88
+ const globallyRouted = ModelMix.new({ mix: { openrouter: true } })[shortcut]();
89
+ expect(globallyRouted.models.map(({ key }) => key)).to.deep.equal([officialKey, openRouterKey]);
90
+ }
91
+
92
+ const locallyDisabled = ModelMix.new({ mix: { openrouter: true } })
93
+ .gpt56sol({ mix: { openrouter: false } });
94
+ expect(locallyDisabled.models.map(({ key }) => key)).to.deep.equal(['gpt-5.6-sol']);
95
+
96
+ const inherited = ModelMix.new({ mix: { openrouter: true } }).new().gpt5();
97
+ expect(inherited.models.map(({ key }) => key)).to.deep.equal([
98
+ 'gpt-5',
99
+ 'openai/gpt-5'
100
+ ]);
101
+ });
102
+
103
+ it('should append OpenRouter GPT fallbacks in chain() only when enabled globally', () => {
104
+ const official = ModelMix.new().chain('gpt56sol@100');
105
+ expect(official.models.map(({ key }) => key)).to.deep.equal(['gpt-5.6-sol']);
106
+
107
+ const routed = ModelMix.new({ mix: { openrouter: true } }).chain('gpt56sol@100');
108
+ expect(routed.models.map(({ key }) => key)).to.deep.equal([
109
+ 'gpt-5.6-sol',
110
+ 'openai/gpt-5.6-sol'
111
+ ]);
112
+ expect(routed.models.map(({ provider }) => provider.config.effort)).to.deep.equal([100, 100]);
113
+ });
114
+
115
+ it('should fallback from the official GPT-5.6 Sol endpoint to OpenRouter', async () => {
116
+ let openRouterRequest;
117
+ model.gpt56sol({ mix: { openrouter: true } }).addText('Hello');
118
+
119
+ nock('https://api.openai.com')
120
+ .post('/v1/responses')
121
+ .reply(503, { error: 'Service unavailable' });
122
+
123
+ nock('https://openrouter.ai')
124
+ .post('/api/v1/chat/completions', body => {
125
+ openRouterRequest = body;
126
+ return true;
127
+ })
128
+ .reply(200, {
129
+ choices: [{
130
+ message: {
131
+ role: 'assistant',
132
+ content: 'Hello from GPT-5.6 Sol through OpenRouter!'
133
+ }
134
+ }]
135
+ });
136
+
137
+ expect(await model.message()).to.equal('Hello from GPT-5.6 Sol through OpenRouter!');
138
+ expect(openRouterRequest.model).to.equal('openai/gpt-5.6-sol');
139
+ expect(openRouterRequest.max_completion_tokens).to.equal(8192);
140
+ expect(openRouterRequest).to.not.have.property('temperature');
141
+ });
142
+
143
+ it('should keep the default fable51 chain on Anthropic', () => {
144
+ model.chain('fable51@80');
145
+
146
+ expect(model.models.map(({ key }) => key)).to.deep.equal(['claude-fable-5-1']);
147
+ expect(model.models[0].provider.config.effort).to.equal(80);
148
+ });
149
+
50
150
  it('should reject invalid chains before attaching any model', () => {
51
151
  expect(() => model.chain('gpt56luna', 'unknownModel'))
52
152
  .to.throw('Unknown model shortcut "unknownModel" in chain().');
package/test/glm.test.js CHANGED
@@ -2,16 +2,12 @@ const { expect } = require('chai');
2
2
  const { ModelMix, MixFireworks, MixOpenRouter, MixTogether } = require('../index.js');
3
3
 
4
4
  describe('GLM Model Registration Tests', () => {
5
- it('should register Together GLM 5.2 before the OpenRouter fallback by default', () => {
5
+ it('should register only Together GLM 5.2 by default', () => {
6
6
  const model = ModelMix.new();
7
7
  model.GLM52();
8
8
 
9
- expect(model.models.map(({ key }) => key)).to.deep.equal([
10
- 'zai-org/GLM-5.2',
11
- 'z-ai/glm-5.2'
12
- ]);
9
+ expect(model.models.map(({ key }) => key)).to.deep.equal(['zai-org/GLM-5.2']);
13
10
  expect(model.models[0].provider).to.be.instanceOf(MixTogether);
14
- expect(model.models[1].provider).to.be.instanceOf(MixOpenRouter);
15
11
  });
16
12
 
17
13
  it('should register every requested GLM 5.2 provider', () => {
package/test/kimi.test.js CHANGED
@@ -3,16 +3,12 @@ const nock = require('nock');
3
3
  const { ModelMix, MixFireworks, MixKimi, MixOpenRouter, MixTogether } = require('../index.js');
4
4
 
5
5
  describe('Kimi Model Registration Tests', () => {
6
- it('should register Together Kimi K2.7 Code before the OpenRouter fallback by default', () => {
6
+ it('should register only Together Kimi K2.7 Code by default', () => {
7
7
  const model = ModelMix.new();
8
8
  model.kimiK27Code();
9
9
 
10
- expect(model.models.map(({ key }) => key)).to.deep.equal([
11
- 'moonshotai/Kimi-K2.7-Code',
12
- 'moonshotai/kimi-k2.7-code'
13
- ]);
10
+ expect(model.models.map(({ key }) => key)).to.deep.equal(['moonshotai/Kimi-K2.7-Code']);
14
11
  expect(model.models[0].provider).to.be.instanceOf(MixTogether);
15
- expect(model.models[1].provider).to.be.instanceOf(MixOpenRouter);
16
12
  });
17
13
 
18
14
  it('should register every requested Kimi K2.7 Code provider', () => {
package/test/live.test.js CHANGED
@@ -1,5 +1,5 @@
1
1
  const { expect } = require('chai');
2
- const { ModelMix, MixOpenAI, MixAnthropic, MixGoogle } = require('../index.js');
2
+ const { ModelMix, MixOpenAI, MixAnthropic } = require('../index.js');
3
3
  const nock = require('nock');
4
4
 
5
5
  const blueSquareBase64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAMAAABHPGVmAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDkuMS1jMDAzIDc5Ljk2OTBhODdmYywgMjAyNS8wMy8wNi0yMDo1MDoxNiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDI2LjkgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6REM2QzQ3NEQ2Q0I5MTFGMDlBRTVGQzcwQjMyMkY4MDciIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6REM2QzQ3NEU2Q0I5MTFGMDlBRTVGQzcwQjMyMkY4MDciPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpEQzZDNDc0QjZDQjkxMUYwOUFFNUZDNzBCMzIyRjgwNyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpEQzZDNDc0QzZDQjkxMUYwOUFFNUZDNzBCMzIyRjgwNyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PvArxh0AAAAGUExURQAA/wAAAHtivz4AAAAjSURBVHja7MGBAAAAAMOg+VNf4QBVAQAAAAAAAAAAAI8JMAAndAABi7SX2gAAAABJRU5ErkJggg==';
package/test/muse.test.js CHANGED
@@ -7,16 +7,14 @@ const {
7
7
  MixTogether
8
8
  } = require('../index.js');
9
9
 
10
- describe('Muse Glimmer Model Registration Tests', () => {
11
- it('registers Fireworks before the OpenRouter fallback by default', () => {
10
+ describe('Muse Model Registration Tests', () => {
11
+ it('registers only Fireworks by default', () => {
12
12
  const model = ModelMix.new().museGlimmer30b();
13
13
 
14
14
  expect(model.models.map(({ key }) => key)).to.deep.equal([
15
- 'accounts/fireworks/models/muse-glimmer-30b',
16
- 'meta/muse-glimmer-30b'
15
+ 'accounts/fireworks/models/muse-glimmer-30b'
17
16
  ]);
18
17
  expect(model.models[0].provider).to.be.instanceOf(MixFireworks);
19
- expect(model.models[1].provider).to.be.instanceOf(MixOpenRouter);
20
18
  });
21
19
 
22
20
  it('registers every supported provider in fallback order', () => {
@@ -54,8 +52,27 @@ describe('Muse Glimmer Model Registration Tests', () => {
54
52
  const model = ModelMix.new().chain('museGlimmer30b');
55
53
 
56
54
  expect(model.models.map(({ key }) => key)).to.deep.equal([
57
- 'accounts/fireworks/models/muse-glimmer-30b',
58
- 'meta/muse-glimmer-30b'
55
+ 'accounts/fireworks/models/muse-glimmer-30b'
59
56
  ]);
60
57
  });
58
+
59
+ it('registers Muse Spark 1.2 Contributor through OpenRouter', () => {
60
+ const model = ModelMix.new().museSpark12Contributor();
61
+
62
+ expect(model.models).to.have.length(1);
63
+ expect(model.models[0].key).to.equal('meta/muse-spark-1.2-contributor');
64
+ expect(model.models[0].provider).to.be.instanceOf(MixOpenRouter);
65
+ expect(ModelMix.calculateCost('meta/muse-spark-1.2-contributor', {
66
+ input: 1_000_000,
67
+ cached: 250_000,
68
+ output: 1_000_000
69
+ })).to.equal(0.2755);
70
+ });
71
+
72
+ it('supports Muse Spark 1.2 Contributor in chain()', () => {
73
+ const model = ModelMix.new().chain('museSpark12Contributor');
74
+
75
+ expect(model.models).to.have.length(1);
76
+ expect(model.models[0].key).to.equal('meta/muse-spark-1.2-contributor');
77
+ });
61
78
  });
@@ -9,6 +9,23 @@ const {
9
9
  } = require('../index.js');
10
10
 
11
11
  describe('Provider expansion regressions', () => {
12
+ it('should keep OpenRouter fallbacks disabled by default', () => {
13
+ const originalMiniMaxApiKey = process.env.MINIMAX_API_KEY;
14
+ process.env.MINIMAX_API_KEY = 'test-minimax-key';
15
+
16
+ try {
17
+ const gptOss = ModelMix.new().gptOss();
18
+ const minimax = ModelMix.new().minimaxM27();
19
+
20
+ expect(gptOss.models.some(({ provider }) => provider instanceof MixOpenRouter)).to.equal(false);
21
+ expect(minimax.models.map(({ key }) => key)).to.deep.equal(['MiniMax-M2.7']);
22
+ expect(minimax.models[0].provider).to.be.instanceOf(MixMiniMax);
23
+ } finally {
24
+ if (originalMiniMaxApiKey === undefined) delete process.env.MINIMAX_API_KEY;
25
+ else process.env.MINIMAX_API_KEY = originalMiniMaxApiKey;
26
+ }
27
+ });
28
+
12
29
  it('should retain every enabled GPT OSS provider, including shared model keys', () => {
13
30
  const model = ModelMix.new().gptOss({
14
31
  mix: {
@@ -14,6 +14,7 @@ describe('public module boundary', () => {
14
14
  'MixGroq',
15
15
  'MixKimi',
16
16
  'MixLMStudio',
17
+ 'MixLambda',
17
18
  'MixMiMo',
18
19
  'MixMiniMax',
19
20
  'MixModeration',
@@ -57,8 +58,7 @@ describe('public module boundary', () => {
57
58
  ['MiMo', config => new api.MixMiMo({ config })],
58
59
  ['Perplexity', config => new api.MixPerplexity({ config })],
59
60
  ['Grok', config => new api.MixGrok({ config })],
60
- ['Lambda', config => api.ModelMix.new({ mix: { openrouter: false, lambda: true } })
61
- .hermes3({ config }).models[0].provider],
61
+ ['Lambda', config => new api.MixLambda({ config })],
62
62
  ['Groq', config => new api.MixGroq({ config })],
63
63
  ['Together', config => new api.MixTogether({ config })],
64
64
  ['Cerebras', config => new api.MixCerebras({ config })],
package/test/qwen.test.js CHANGED
@@ -67,13 +67,12 @@ describe('Qwen Model Registration Tests', () => {
67
67
  })).to.equal(4.08576);
68
68
  });
69
69
 
70
- it('should register Fireworks Qwen 3.8 Max before the OpenRouter fallback by default', () => {
70
+ it('should register only Fireworks Qwen 3.8 Max by default', () => {
71
71
  const model = ModelMix.new();
72
72
  model.qwen38max();
73
73
 
74
74
  expect(model.models.map(({ key }) => key)).to.deep.equal([
75
- 'accounts/fireworks/models/qwen3p8-2p4t-a95b',
76
- 'qwen/qwen3.8-max'
75
+ 'accounts/fireworks/models/qwen3p8-2p4t-a95b'
77
76
  ]);
78
77
  expect(ModelMix.calculateCost('accounts/fireworks/models/qwen3p8-2p4t-a95b', {
79
78
  input: 1_000_000,
@@ -321,32 +321,47 @@ describe('Token Usage Tracking', () => {
321
321
  });
322
322
 
323
323
  it('should register GPT-5.5 shortcuts with OpenAI Responses provider', function () {
324
- const model = ModelMix.new()
324
+ const model = ModelMix.new({ mix: { openrouter: true } })
325
325
  .gpt55()
326
326
  .gpt55pro();
327
327
 
328
- expect(model.models).to.have.length(2);
329
- expect(model.models[0].key).to.equal('gpt-5.5');
330
- expect(model.models[1].key).to.equal('gpt-5.5-pro');
328
+ expect(model.models.map(({ key }) => key)).to.deep.equal([
329
+ 'gpt-5.5',
330
+ 'openai/gpt-5.5',
331
+ 'gpt-5.5-pro',
332
+ 'openai/gpt-5.5-pro'
333
+ ]);
331
334
  expect(model.models[0].provider).to.be.instanceOf(MixOpenAIResponses);
332
- expect(model.models[1].provider).to.be.instanceOf(MixOpenAIResponses);
335
+ expect(model.models[1].provider).to.be.instanceOf(MixOpenRouter);
336
+ expect(model.models[2].provider).to.be.instanceOf(MixOpenAIResponses);
337
+ expect(model.models[3].provider).to.be.instanceOf(MixOpenRouter);
333
338
  });
334
339
 
335
340
  it('should register GPT-5.6 shortcuts with OpenAI Responses provider', function () {
336
- const model = ModelMix.new()
341
+ const model = ModelMix.new({ mix: { openrouter: true } })
337
342
  .gpt56sol()
338
343
  .gpt56terra()
339
344
  .gpt56luna();
340
345
 
341
346
  expect(model.models.map(({ key }) => key)).to.deep.equal([
342
347
  'gpt-5.6-sol',
348
+ 'openai/gpt-5.6-sol',
343
349
  'gpt-5.6-terra',
344
- 'gpt-5.6-luna'
350
+ 'openai/gpt-5.6-terra',
351
+ 'gpt-5.6-luna',
352
+ 'openai/gpt-5.6-luna'
345
353
  ]);
346
- expect(model.models.every(({ provider }) => provider instanceof MixOpenAIResponses)).to.equal(true);
354
+ expect(model.models[0].provider).to.be.instanceOf(MixOpenAIResponses);
355
+ expect(model.models[1].provider).to.be.instanceOf(MixOpenRouter);
356
+ expect(model.models[2].provider).to.be.instanceOf(MixOpenAIResponses);
357
+ expect(model.models[3].provider).to.be.instanceOf(MixOpenRouter);
358
+ expect(model.models[4].provider).to.be.instanceOf(MixOpenAIResponses);
359
+ expect(model.models[5].provider).to.be.instanceOf(MixOpenRouter);
347
360
  expect(ModelMix.calculateCost('gpt-5.6-sol', { input: 1_000_000, output: 1_000_000 })).to.equal(55);
361
+ expect(ModelMix.calculateCost('openai/gpt-5.6-sol', { input: 1_000_000, output: 1_000_000 })).to.equal(55);
348
362
  expect(ModelMix.calculateCost('gpt-5.6-terra', { input: 1_000_000, output: 1_000_000 })).to.equal(22);
349
363
  expect(ModelMix.calculateCost('gpt-5.6-luna', { input: 1_000_000, output: 1_000_000 })).to.equal(2.2);
364
+ expect(ModelMix.calculateCost('openai/gpt-5.3-chat', { input: 1_000_000, output: 1_000_000 })).to.equal(15.75);
350
365
  });
351
366
 
352
367
  it('should calculate GPT-5.6 cache reads and writes at their actual rates', function () {