modelmix 5.2.1 → 5.2.3

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.
package/README.md CHANGED
@@ -179,6 +179,7 @@ ModelMix provides convenient shorthand methods for quickly accessing different A
179
179
  | `gptOss()` | Multi-provider | gpt-oss-120B | [\$0.15][7] | [\$0.60][7] |
180
180
  | `fable51()` | Anthropic | claude-fable-5-1 | [\$10.00][2] | [\$50.00][2] |
181
181
  | `fable5()` | Anthropic | claude-fable-5 | [\$10.00][2] | [\$50.00][2] |
182
+ | `opus55()` | Anthropic | claude-opus-5-5 | [\$5.00][2] | [\$25.00][2] |
182
183
  | `opus5()` | Anthropic | claude-opus-5 | [\$5.00][2] | [\$25.00][2] |
183
184
  | `opus48()` | Anthropic | claude-opus-4-8 | [\$5.00][2] | [\$25.00][2] |
184
185
  | `opus47()` | Anthropic | claude-opus-4-7 | [\$5.00][2] | [\$25.00][2] |
@@ -193,6 +194,7 @@ ModelMix provides convenient shorthand methods for quickly accessing different A
193
194
  | `gemini35flash()` | Google | gemini-3.5-flash | [\$0.75][3] | [\$4.50][3] |
194
195
  | `gemini35flashLite()` | Google | gemini-3.5-flash-lite | [\$0.30][3] | [\$2.50][3] |
195
196
  | `gemini31flashLite()` | Google | gemini-3.1-flash-lite-preview | [\$0.25][3] | [\$1.50][3] |
197
+ | `grok47()` | Grok | grok-4.7 | — | — |
196
198
  | `grok46()` | Grok | grok-4.6 | [\$2.00][6] | [\$6.00][6] |
197
199
  | `grok45()` | Grok | grok-4.5 | [\$2.00][6] | [\$6.00][6] |
198
200
  | `grok43()` | Grok | grok-4.3 | [\$1.25][6] | [\$2.50][6] |
@@ -218,6 +220,7 @@ ModelMix provides convenient shorthand methods for quickly accessing different A
218
220
  | `GLM52()` | Together | zai-org/GLM-5.2 | [\$1.40][7] | [\$4.40][7] |
219
221
  | `minimaxM3()` | MiniMax | MiniMax-M3 | [\$0.30][9] | [\$1.20][9] |
220
222
  | `minimaxM27()` | MiniMax | MiniMax-M2.7 | [\$0.30][9] | [\$1.20][9] |
223
+ | `mimo26pro()` | MiMo | mimo-v2.6-pro | — | — |
221
224
  | `sonar()` | Perplexity | sonar | [\$1.00][4] | [\$1.00][4] |
222
225
  | `sonarPro()` | Perplexity | sonar-pro | [\$3.00][4] | [\$15.00][4] |
223
226
  | `hermes470b()` | OpenRouter | nousresearch/hermes-4-70b | [\$0.13][13] | [\$0.40][13] |
package/demo/grok.js CHANGED
@@ -13,7 +13,7 @@ const mmix = new ModelMix({
13
13
  });
14
14
 
15
15
 
16
- const r = await mmix.grok46()
16
+ const r = await mmix.grok47()
17
17
  .addText('hi there!')
18
18
  .addText('do you like cats?')
19
19
  .raw();
package/index.d.ts CHANGED
@@ -470,6 +470,7 @@ export declare class ModelMix {
470
470
  fable51(args?: ModelAttachArgs): this;
471
471
  fable50(args?: ModelAttachArgs): this;
472
472
  fable5(args?: ModelAttachArgs): this;
473
+ opus55(args?: ModelAttachArgs): this;
473
474
  opus50(args?: ModelAttachArgs): this;
474
475
  opus5(args?: ModelAttachArgs): this;
475
476
  opus48(args?: ModelAttachArgs): this;
@@ -495,6 +496,7 @@ export declare class ModelMix {
495
496
  sonar(args?: ModelAttachArgs): this;
496
497
 
497
498
  // Grok
499
+ grok47(args?: ModelAttachArgs): this;
498
500
  grok46(args?: ModelAttachArgs): this;
499
501
  grok45(args?: ModelAttachArgs): this;
500
502
  grok43(args?: ModelAttachArgs): this;
@@ -526,6 +528,7 @@ export declare class ModelMix {
526
528
  minimaxM3(args?: ModelAttachArgs): this;
527
529
  mimo25(args?: ModelAttachArgs): this;
528
530
  mimo25pro(args?: ModelAttachArgs): this;
531
+ mimo26pro(args?: ModelAttachArgs): this;
529
532
  deepseekV4Pro(args?: ModelAttachArgs): this;
530
533
  /** Uses deepseek/deepseek-v4-pro-0813 through OpenRouter. */
531
534
  deepseekPro(args?: ModelAttachArgs): this;
package/index.js CHANGED
@@ -545,6 +545,9 @@ class ModelMix {
545
545
  if (mix.openrouter) this.attach('anthropic/claude-fable-5.1', new MixOpenRouter({ options, config }));
546
546
  return this;
547
547
  }
548
+ opus55({ options = {}, config = {} } = {}) {
549
+ return this.attach('claude-opus-5-5', new MixAnthropic({ options, config }));
550
+ }
548
551
  opus50({ options = {}, config = {} } = {}) {
549
552
  return this.attach('claude-opus-5', new MixAnthropic({ options, config }));
550
553
  }
@@ -603,6 +606,9 @@ class ModelMix {
603
606
  return this.attach('sonar', new MixPerplexity({ options, config }));
604
607
  }
605
608
 
609
+ grok47({ options = {}, config = {} } = {}) {
610
+ return this.attach('grok-4.7', new MixGrok({ options, config }));
611
+ }
606
612
  grok46({ options = {}, config = {} } = {}) {
607
613
  return this.attach('grok-4.6', new MixGrok({ options, config }));
608
614
  }
@@ -766,6 +772,13 @@ class ModelMix {
766
772
  return this;
767
773
  }
768
774
 
775
+ mimo26pro({ options = {}, config = {}, mix = { openrouter: true } } = {}) {
776
+ mix = { ...this.mix, ...mix };
777
+ if (mix.mimo) this.attach('mimo-v2.6-pro', new MixMiMo({ options, config }));
778
+ if (mix.openrouter) this.attach('xiaomi/mimo-v2.6-pro', new MixOpenRouter({ options, config }));
779
+ return this;
780
+ }
781
+
769
782
  deepseekPro({ options = {}, config = {} } = {}) {
770
783
  return this.attach('deepseek/deepseek-v4-pro-0813', new MixOpenRouter({ options, config }));
771
784
  }
@@ -5,16 +5,16 @@ const CHAIN_MODEL_SHORTCUTS = new Set([
5
5
  'gpt51', 'gpt52', 'gpt54', 'gpt54mini', 'gpt54nano', 'gpt54pro',
6
6
  'gpt6astra', 'gpt55', 'gpt55pro', 'gpt56sol', 'gpt56terra', 'gpt56luna',
7
7
  'gptRealtime', 'gptRealtimeMini', 'gpt53codex', 'gpt53chat', 'gptOss',
8
- 'fable51', 'fable50', 'fable5', 'opus50', 'opus5', 'opus48', 'opus47', 'opus46',
8
+ 'fable51', 'fable50', 'fable5', 'opus55', 'opus50', 'opus5', 'opus48', 'opus47', 'opus46',
9
9
  'sonnet50', 'sonnet5', 'sonnet46', 'sonnet45', 'haiku45',
10
10
  'gemini31pro', 'gemini38flash', 'gemini37flash', 'gemini36flash', 'gemini35flash',
11
11
  'gemini35flashLite', 'gemini31flashLite', 'sonarPro', 'sonar',
12
- 'grok46', 'grok45', 'grok43', 'grok420multiAgent', 'grok420',
12
+ 'grok47', 'grok46', 'grok45', 'grok43', 'grok420multiAgent', 'grok420',
13
13
  'museGlimmer30b', 'museSpark12', 'museSpark12c', 'museSpark13', 'museSpark13c',
14
14
  'qwen35397b', 'qwen36plus', 'qwen37plus', 'qwen38max', 'qwen3827b', 'qwen38flash',
15
15
  'hermes470b', 'hermes4405b', 'hermes3',
16
16
  'kimiK26', 'kimiK27Code', 'kimiK3', 'kimiK25',
17
- 'minimaxM27', 'minimaxM3', 'mimo25', 'mimo25pro',
17
+ 'minimaxM27', 'minimaxM3', 'mimo25', 'mimo25pro', 'mimo26pro',
18
18
  'deepseekV4Pro', 'deepseekPro', 'deepseekV4Flash', 'deepseekV41Flash', 'GLM52', 'GLM53', 'GLM53Flash'
19
19
  ]);
20
20
 
@@ -53,6 +53,7 @@ const MODEL_PRICING = {
53
53
  'claude-fable-5-1': { input: 10.00, cachedInput: 0.25, cacheWrite: 12.50, cacheWrite1h: 20.00, output: 50.00 },
54
54
  'anthropic/claude-fable-5.1': { input: 10.00, cachedInput: 0.25, cacheWrite: 12.50, cacheWrite1h: 20.00, output: 50.00 },
55
55
  'claude-fable-5': { input: 10.00, cachedInput: 1.00, cacheWrite: 12.50, cacheWrite1h: 20.00, output: 50.00 },
56
+ 'claude-opus-5-5': { input: 5.00, cachedInput: 0.50, cacheWrite: 6.25, cacheWrite1h: 10.00, output: 25.00 },
56
57
  'claude-opus-5': { input: 5.00, cachedInput: 0.50, cacheWrite: 6.25, cacheWrite1h: 10.00, output: 25.00 },
57
58
  'claude-sonnet-5': { input: 3.00, cachedInput: 0.30, cacheWrite: 3.75, cacheWrite1h: 6.00, output: 15.00 },
58
59
  'claude-opus-4-8': { input: 5.00, cachedInput: 0.50, cacheWrite: 6.25, cacheWrite1h: 10.00, output: 25.00 },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "modelmix",
3
- "version": "5.2.1",
3
+ "version": "5.2.3",
4
4
  "description": "🧬 Reliable interface with automatic fallback for AI LLMs.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -177,7 +177,7 @@ Use `ModerationMix.new().openai()` with `.raw()` to classify text and images thr
177
177
  Every textual GPT-5 and GPT-6 shortcut registers only the official OpenAI model by default. Pass `mix: { openrouter: true }` to `ModelMix.new()` or to an individual shortcut to append its `openai/*` OpenRouter route as a fallback. `gpt53chat()` uses `gpt-5.3-chat-latest` officially and `openai/gpt-5.3-chat` through OpenRouter. Both API keys are required when that fallback is enabled. Realtime shortcuts remain official-only.
178
178
 
179
179
  ### Anthropic
180
- `fable51()` `fable50()` `opus50()` `opus48()` `opus47()` `opus46()` `sonnet5()` `sonnet46()` `sonnet45()` `haiku45()`
180
+ `fable51()` `fable50()` `opus55()` `opus50()` `opus48()` `opus47()` `opus46()` `sonnet5()` `sonnet46()` `sonnet45()` `haiku45()`
181
181
 
182
182
  Use `.effort(n)` (or `config.effort`) to enable Anthropic thinking — e.g. `.effort(100).opus50()`. `fable5()` and `opus5()` remain available as compatibility aliases.
183
183
 
@@ -187,7 +187,7 @@ Use `.effort(n)` (or `config.effort`) to enable Anthropic thinking — e.g. `.ef
187
187
  `gemini31pro()` `gemini38flash()` `gemini37flash()` `gemini36flash()` `gemini35flash()` `gemini35flashLite()` `gemini31flashLite()`
188
188
 
189
189
  ### Grok
190
- `grok46()` `grok45()` `grok43()` `grok420multiAgent()` `grok420()`
190
+ `grok47()` `grok46()` `grok45()` `grok43()` `grok420multiAgent()` `grok420()`
191
191
 
192
192
  ### Perplexity
193
193
  `sonar()` `sonarPro()`
@@ -204,6 +204,9 @@ Use `.effort(n)` (or `config.effort`) to enable Anthropic thinking — e.g. `.ef
204
204
  ### MiniMax
205
205
  `minimaxM27()` `minimaxM3()`
206
206
 
207
+ ### MiMo
208
+ `mimo26pro()` — uses OpenRouter (`xiaomi/mimo-v2.6-pro`) by default; the native API requires `MIMO_API_KEY` and `mix: { mimo: true }`.
209
+
207
210
  ### DeepSeek
208
211
  `deepseekV41Flash({ mix: { deepseek: true, openrouter: false } })` uses the native API at `https://api.deepseek.com/chat/completions` with model `deepseek-flash` (currently V4.1 Flash). Requires `DEEPSEEK_API_KEY`; `MixDeepSeek` supports explicit `.attach()` calls. Unified effort and native cache usage are supported, and assistant reasoning is preserved for tool continuations. Cost estimates use peak rates per 1M tokens: $0.30 input, $0.006 cached input, $1.20 output; off-peak charges are half. Enabling all three providers orders them DeepSeek → Fireworks → OpenRouter.
209
212
 
@@ -122,6 +122,15 @@ describe('Anthropic Model Registration Tests', () => {
122
122
  expect(model.models[0].provider).to.be.instanceOf(MixAnthropic);
123
123
  });
124
124
 
125
+ it('should register Claude Opus 5.5', () => {
126
+ const model = ModelMix.new();
127
+ model.opus55();
128
+
129
+ expect(model.models).to.have.length(1);
130
+ expect(model.models[0].key).to.equal('claude-opus-5-5');
131
+ expect(model.models[0].provider).to.be.instanceOf(MixAnthropic);
132
+ });
133
+
125
134
  it('should keep opus5() as an alias for opus50()', () => {
126
135
  const model = ModelMix.new();
127
136
 
@@ -147,9 +156,21 @@ describe('Anthropic Model Registration Tests', () => {
147
156
  expect(options.thinking).to.deep.equal({ type: 'adaptive', display: 'summarized' });
148
157
  });
149
158
 
159
+ it('should apply max effort thinking via .effort(100).opus55()', () => {
160
+ const model = ModelMix.new().effort(100).opus55();
161
+ const { applyUnifiedEffort } = require('../effort.js');
162
+
163
+ expect(model.config.effort).to.equal(100);
164
+ const options = { model: 'claude-opus-5-5' };
165
+ applyUnifiedEffort(options, model.config, 'anthropic', 'claude-opus-5-5');
166
+ expect(options.output_config).to.deep.equal({ effort: 'max' });
167
+ expect(options.thinking).to.deep.equal({ type: 'adaptive', display: 'summarized' });
168
+ });
169
+
150
170
  describe('Sampling params (temperature/top_p/top_k)', () => {
151
171
  it('should detect models that reject sampling params', () => {
152
172
  expect(MixAnthropic.rejectsSamplingParams('claude-opus-5')).to.equal(true);
173
+ expect(MixAnthropic.rejectsSamplingParams('claude-opus-5-5')).to.equal(true);
153
174
  expect(MixAnthropic.rejectsSamplingParams('claude-opus-4-8')).to.equal(true);
154
175
  expect(MixAnthropic.rejectsSamplingParams('claude-opus-4-7')).to.equal(true);
155
176
  expect(MixAnthropic.rejectsSamplingParams('claude-sonnet-5')).to.equal(true);
@@ -197,6 +197,10 @@ describe('Unified effort scale', () => {
197
197
  thinking: { type: 'adaptive', display: 'summarized' },
198
198
  output_config: { effort: 'low' }
199
199
  });
200
+ expect(mapEffort('anthropic', 50, 'claude-opus-5-5')).to.deep.equal({
201
+ thinking: { type: 'adaptive', display: 'summarized' },
202
+ output_config: { effort: 'high' }
203
+ });
200
204
  expect(mapEffort('anthropic', 90, 'claude-fable-5-1')).to.deep.equal({
201
205
  thinking: { type: 'adaptive', display: 'summarized' },
202
206
  output_config: { effort: 'max' }
package/test/grok.test.js CHANGED
@@ -11,6 +11,7 @@ const {
11
11
 
12
12
  describe('Grok Model Registration Tests', () => {
13
13
  const grokModels = [
14
+ { method: 'grok47', key: 'grok-4.7' },
14
15
  { method: 'grok46', key: 'grok-4.6' },
15
16
  { method: 'grok45', key: 'grok-4.5' },
16
17
  { method: 'grok43', key: 'grok-4.3' },
@@ -38,6 +39,38 @@ describe('Grok Model Registration Tests', () => {
38
39
  expect(model.models[0].provider.config).to.include(config);
39
40
  });
40
41
 
42
+ it('supports Grok 4.7 in chain() and preserves attachment options', () => {
43
+ const options = { temperature: 0.5 };
44
+ const config = { max_history: 3 };
45
+ const model = ModelMix.new().grok47({ options, config });
46
+
47
+ expect(model.models[0].provider).to.be.instanceOf(MixGrok);
48
+ expect(model.models[0].provider.options).to.deep.equal(options);
49
+ expect(model.models[0].provider.config).to.include(config);
50
+ expect(ModelMix.new().chain('grok47').models[0].key).to.equal('grok-4.7');
51
+ });
52
+
53
+ it('sends Grok 4.7 requests to xAI', async () => {
54
+ const api = nock('https://api.x.ai')
55
+ .post('/v1/chat/completions', body => {
56
+ expect(body.model).to.equal('grok-4.7');
57
+ expect(body.temperature).to.equal(0.5);
58
+ return true;
59
+ })
60
+ .reply(200, {
61
+ choices: [{ message: { content: 'ok' } }],
62
+ usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }
63
+ });
64
+
65
+ const response = await ModelMix.new()
66
+ .grok47({ options: { temperature: 0.5 }, config: { apiKey: 'test-key' } })
67
+ .addText('Hi')
68
+ .message();
69
+
70
+ expect(response).to.equal('ok');
71
+ api.done();
72
+ });
73
+
41
74
  it('maps unified effort to Grok 4.6 supported levels', () => {
42
75
  expect(mapEffort('openai', 0, 'grok-4.6')).to.deep.equal({ reasoning_effort: 'low' });
43
76
  expect(mapEffort('openai', 39, 'grok-4.6')).to.deep.equal({ reasoning_effort: 'low' });
@@ -1,7 +1,9 @@
1
1
  const { expect } = require('chai');
2
+ const nock = require('nock');
2
3
  const {
3
4
  ModelMix,
4
5
  MixFireworks,
6
+ MixMiMo,
5
7
  MixMiniMax,
6
8
  MixNVIDIA,
7
9
  MixOpenRouter,
@@ -97,4 +99,29 @@ describe('Provider expansion regressions', () => {
97
99
  expect(model.models[0].key).to.equal('accounts/fireworks/models/minimax-m3');
98
100
  expect(model.models[0].provider).to.be.instanceOf(MixFireworks);
99
101
  });
102
+
103
+ it('should send MiMo 2.6 Pro requests to the native MiMo API', async () => {
104
+ const api = nock('https://api.xiaomimimo.com')
105
+ .post('/v1/chat/completions', body => {
106
+ expect(body.model).to.equal('mimo-v2.6-pro');
107
+ return true;
108
+ })
109
+ .reply(200, {
110
+ choices: [{ message: { content: 'ok' } }],
111
+ usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }
112
+ });
113
+
114
+ const model = ModelMix.new().mimo26pro({
115
+ mix: { mimo: true, openrouter: false },
116
+ config: { apiKey: 'test-mimo-key' }
117
+ });
118
+
119
+ expect(model.models).to.have.length(1);
120
+ expect(model.models[0].provider).to.be.instanceOf(MixMiMo);
121
+
122
+ const response = await model.addText('Hi').message();
123
+
124
+ expect(response).to.equal('ok');
125
+ api.done();
126
+ });
100
127
  });
@@ -674,6 +674,38 @@ describe('Token Usage Tracking', () => {
674
674
  }
675
675
  });
676
676
 
677
+ it('should register MiMo 2.6 Pro with native and OpenRouter providers', function () {
678
+ const originalMimoApiKey = process.env.MIMO_API_KEY;
679
+ const originalOpenRouterApiKey = process.env.OPENROUTER_API_KEY;
680
+
681
+ process.env.MIMO_API_KEY = 'test-mimo-key';
682
+ process.env.OPENROUTER_API_KEY = 'test-openrouter-key';
683
+
684
+ try {
685
+ const model = ModelMix.new().mimo26pro();
686
+ const withNative = ModelMix.new()
687
+ .mimo26pro({ mix: { mimo: true, openrouter: true } });
688
+
689
+ expect(model.models.map(({ key }) => key)).to.deep.equal(['xiaomi/mimo-v2.6-pro']);
690
+ expect(model.models[0].provider).to.be.instanceOf(MixOpenRouter);
691
+
692
+ expect(withNative.models.map(({ key }) => key)).to.deep.equal([
693
+ 'mimo-v2.6-pro',
694
+ 'xiaomi/mimo-v2.6-pro'
695
+ ]);
696
+ expect(withNative.models[0].provider).to.be.instanceOf(MixMiMo);
697
+ expect(withNative.models[1].provider).to.be.instanceOf(MixOpenRouter);
698
+
699
+ expect(ModelMix.new().chain('mimo26pro').models[0].key).to.equal('xiaomi/mimo-v2.6-pro');
700
+ } finally {
701
+ if (originalMimoApiKey === undefined) delete process.env.MIMO_API_KEY;
702
+ else process.env.MIMO_API_KEY = originalMimoApiKey;
703
+
704
+ if (originalOpenRouterApiKey === undefined) delete process.env.OPENROUTER_API_KEY;
705
+ else process.env.OPENROUTER_API_KEY = originalOpenRouterApiKey;
706
+ }
707
+ });
708
+
677
709
  it('should register GPT-OSS 120B through the current OpenRouter model ID', function () {
678
710
  const originalOpenRouterApiKey = process.env.OPENROUTER_API_KEY;
679
711
  process.env.OPENROUTER_API_KEY = 'test-openrouter-key';