modelmix 5.1.4 → 5.1.8

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
@@ -151,7 +151,7 @@ ModelMix provides convenient shorthand methods for quickly accessing different A
151
151
  | `gpt51()` | OpenAI | gpt-5.1 | [\$1.25][1] | [\$10.00][1] |
152
152
  | `gpt5mini()` | OpenAI | gpt-5-mini | [\$0.25][1] | [\$2.00][1] |
153
153
  | `gpt5nano()` | OpenAI | gpt-5-nano | [\$0.05][1] | [\$0.40][1] |
154
- | `gptOss()` | Together | gpt-oss-120B | [\$0.15][7] | [\$0.60][7] |
154
+ | `gptOss()` | Cerebras/Groq/OpenRouter | gpt-oss-120B | [\$0.15][7] | [\$0.60][7] |
155
155
  | `fable5()` | Anthropic | claude-fable-5 | [\$10.00][2] | [\$50.00][2] |
156
156
  | `opus5()` | Anthropic | claude-opus-5 | [\$5.00][2] | [\$25.00][2] |
157
157
  | `opus48()` | Anthropic | claude-opus-4-8 | [\$5.00][2] | [\$25.00][2] |
@@ -171,8 +171,9 @@ ModelMix provides convenient shorthand methods for quickly accessing different A
171
171
  | `grok43()` | Grok | grok-4.3 | [\$1.25][6] | [\$2.50][6] |
172
172
  | `grok420multiAgent()` | Grok | grok-4.20-multi-agent-0309 | [\$1.25][6] | [\$2.50][6] |
173
173
  | `grok420()` | Grok | grok-4.20-0309 (†) | [\$1.25][6] | [\$2.50][6] |
174
+ | `museGlimmer30b()` | Fireworks | models/muse-glimmer-30b | [\$0.35][17] | [\$1.50][17] |
174
175
  | `qwen35397b()` | OpenRouter | qwen/qwen3.5-397b-a17b | [\$0.385][14] | [\$2.45][14] |
175
- | `qwen36plus()` | Fireworks | qwen3p6-plus | [\$0.50][10] | [\$3.00][10] |
176
+ | `qwen36plus()` | OpenRouter | qwen/qwen3.6-plus | [\$0.325][18] | [\$1.95][18] |
176
177
  | `qwen37plus()` | Fireworks | models/qwen3p7-plus | [\$0.40][10] | [\$1.60][10] |
177
178
  | `qwen38max()` | Fireworks | qwen3p8-2p4t-a95b | [\$2.00][10] | [\$6.00][10] |
178
179
  | `qwen3827b()` | OpenRouter | qwen/qwen3.8-27b | [\$0.45][15] | [\$3.20][15] |
@@ -180,7 +181,6 @@ ModelMix provides convenient shorthand methods for quickly accessing different A
180
181
  | `deepseekV4Pro()` | Fireworks | models/deepseek-v4-pro-0813 | [\$1.32][12] | [\$3.96][12] |
181
182
  | `GLM53()` | OpenRouter | z-ai/glm-5.3 | [\$1.40][16] | [\$4.40][16] |
182
183
  | `GLM52()` | Together | zai-org/GLM-5.2 | [\$1.40][7] | [\$4.40][7] |
183
- | `GLM51()` | Fireworks | models/glm-5p1 | [\$1.05][10] | [\$3.50][10] |
184
184
  | `minimaxM3()` | MiniMax | MiniMax-M3 | [\$0.30][9] | [\$1.20][9] |
185
185
  | `minimaxM27()` | MiniMax | MiniMax-M2.7 | [\$0.30][9] | [\$1.20][9] |
186
186
  | `sonar()` | Perplexity | sonar | [\$1.00][4] | [\$1.00][4] |
@@ -189,11 +189,16 @@ ModelMix provides convenient shorthand methods for quickly accessing different A
189
189
  | `hermes4405b()` | OpenRouter | nousresearch/hermes-4-405b | [\$1.00][13] | [\$3.00][13] |
190
190
  | `hermes3()` | Lambda | Hermes-3-Llama-3.1-405B-FP8 | [\$0.80][8] | [\$0.80][8] |
191
191
  | `kimiK3()` | Moonshot | kimi-k3 | [\$3.00][11] | [\$15.00][11] |
192
+ | `kimiK27Code()` | Together | Kimi-K2.7-Code | [\$0.95][7] | [\$4.00][7] |
192
193
  | `kimiK25()` | Together | Kimi-K2.5 | [\$0.50][7] | [\$2.80][7] |
193
194
  | `kimiK26()` | Fireworks | models/kimi-k2p6 | [\$0.95][10] | [\$4.00][10] |
194
195
 
195
196
  Gemini 3.7 Flash and 3.6 Flash use Google's introductory standard pricing through December 31, 2026; standard rates double on January 1, 2027.
196
197
 
198
+ `museGlimmer30b()` uses Fireworks first and OpenRouter as its default fallback. NVIDIA NIM and Together are also available through `mix.nvidia` and `mix.together`; disable either default provider with `mix.fireworks: false` or `mix.openrouter: false`.
199
+
200
+ The multi-provider shortcuts also expose the current catalog alternatives: `gptOss()` supports NVIDIA and Fireworks; `qwen37plus()` supports Together; `kimiK27Code()` supports Fireworks and OpenRouter; `kimiK3()` supports Fireworks, OpenRouter, and Together; `GLM52()` supports Fireworks and OpenRouter; and both MiniMax shortcuts support Fireworks. `minimaxM27()` keeps every enabled provider in its fallback chain. `qwen36plus()` now defaults to OpenRouter because Fireworks retired its serverless deployment; Fireworks remains available explicitly for private or on-demand deployments.
201
+
197
202
  [1]: https://platform.openai.com/docs/pricing "Pricing | OpenAI"
198
203
  [2]: https://docs.anthropic.com/en/docs/about-claude/pricing "Pricing - Anthropic"
199
204
  [3]: https://ai.google.dev/gemini-api/docs/pricing "Google AI for Developers"
@@ -210,6 +215,8 @@ Gemini 3.7 Flash and 3.6 Flash use Google's introductory standard pricing throug
210
215
  [14]: https://openrouter.ai/qwen/qwen3.5-397b-a17b "Qwen3.5 397B A17B on OpenRouter"
211
216
  [15]: https://openrouter.ai/qwen/qwen3.8-27b "Qwen3.8 27B on OpenRouter"
212
217
  [16]: https://openrouter.ai/z-ai/glm-5.3 "GLM 5.3 on OpenRouter"
218
+ [17]: https://fireworks.ai/models/fireworks/muse-glimmer-30b "Muse Glimmer 30B on Fireworks"
219
+ [18]: https://openrouter.ai/qwen/qwen3.6-plus "Qwen 3.6 Plus on OpenRouter"
213
220
 
214
221
  Each method accepts optional `options`, `config`, and (for multi-provider methods) `mix` parameters to customize behavior.
215
222
 
package/demo/custom.js CHANGED
@@ -50,8 +50,8 @@ class MixTogether extends MixCustom {
50
50
  }
51
51
  }
52
52
 
53
- mmix.attach('Qwen/Qwen3-235B-A22B-fp8-tput', new MixTogether());
53
+ mmix.attach('Qwen/Qwen3.6-Plus', new MixTogether());
54
54
 
55
55
  let r = mmix.addText('hi there');
56
56
  r = await r.addText('do you like cats?').message();
57
- console.log(r);
57
+ console.log(r);
package/demo/short.js CHANGED
@@ -17,8 +17,9 @@ const mmix = await ModelMix.new(setup)
17
17
  .qwen35397b() // (fallback 5) OpenRouter qwen/qwen3.5-397b-a17b
18
18
  .qwen3827b() // (fallback 6) OpenRouter qwen/qwen3.8-27b
19
19
  .GLM53() // (fallback 7) OpenRouter z-ai/glm-5.3
20
- .hermes470b() // (fallback 8) OpenRouter nousresearch/hermes-4-70b
21
- .hermes4405b() // (fallback 9) OpenRouter nousresearch/hermes-4-405b
20
+ .museGlimmer30b({ mix: { fireworks: false, openrouter: true } }) // (fallback 8) OpenRouter meta/muse-glimmer-30b
21
+ .hermes470b() // (fallback 9) OpenRouter nousresearch/hermes-4-70b
22
+ .hermes4405b() // (fallback 10) OpenRouter nousresearch/hermes-4-405b
22
23
  .addText("What's your name?");
23
24
 
24
25
  console.log(await mmix.message());
package/effort.js CHANGED
@@ -45,6 +45,9 @@ const OPENAI_MODEL_LEVELS = {
45
45
  'gpt-5.6-sol': ['none', 'low', 'medium', 'high', 'xhigh', 'max'],
46
46
  'gpt-5.6-terra': ['none', 'low', 'medium', 'high', 'xhigh', 'max'],
47
47
  'gpt-5.6-luna': ['none', 'low', 'medium', 'high', 'xhigh', 'max'],
48
+ 'meta/muse-glimmer-30b': ['low', 'medium', 'high'],
49
+ 'accounts/fireworks/models/muse-glimmer-30b': ['low', 'medium', 'high', 'xhigh'],
50
+ 'meta-models/Muse-Glimmer-30B': ['low', 'medium', 'high', 'xhigh'],
48
51
  'accounts/fireworks/models/qwen3p8-2p4t-a95b': ['none', 'low', 'medium', 'high'],
49
52
  'qwen/qwen3.8-27b': ['low', 'medium', 'xhigh'],
50
53
  'z-ai/glm-5.3': ['low', 'high', 'max'],
@@ -55,6 +58,14 @@ const OPENAI_MODEL_LEVELS = {
55
58
  'gpt-5.3-codex': ['low', 'medium', 'high', 'xhigh'],
56
59
  'gpt-oss-120b': ['low', 'medium', 'high'],
57
60
  'openai/gpt-oss-120b': ['low', 'medium', 'high'],
61
+ 'accounts/fireworks/models/gpt-oss-120b': ['low', 'medium', 'high'],
62
+ 'kimi-k3': ['low', 'high', 'max'],
63
+ 'moonshotai/kimi-k3': ['low', 'high', 'max'],
64
+ 'accounts/fireworks/models/kimi-k3': ['low', 'high', 'max'],
65
+ 'moonshotai/Kimi-K3': ['low', 'high', 'max'],
66
+ 'zai-org/GLM-5.2': ['high', 'xhigh'],
67
+ 'accounts/fireworks/models/glm-5p2': ['high', 'xhigh'],
68
+ 'z-ai/glm-5.2': ['high', 'xhigh'],
58
69
  };
59
70
 
60
71
  /**
package/index.d.ts CHANGED
@@ -496,7 +496,7 @@ export declare class ModelMix {
496
496
  grok420(args?: ModelAttachArgs): this;
497
497
 
498
498
  // Multi-provider
499
- qwen3(args?: ModelAttachArgs): this;
499
+ museGlimmer30b(args?: ModelAttachArgs): this;
500
500
  qwen35397b(args?: ModelAttachArgs): this;
501
501
  qwen36plus(args?: ModelAttachArgs): this;
502
502
  qwen37plus(args?: ModelAttachArgs): this;
@@ -516,7 +516,6 @@ export declare class ModelMix {
516
516
  mimo25pro(args?: ModelAttachArgs): this;
517
517
  deepseekV4Pro(args?: ModelAttachArgs): this;
518
518
  deepseekV4Flash(args?: ModelAttachArgs): this;
519
- GLM51(args?: ModelAttachArgs): this;
520
519
  GLM52(args?: ModelAttachArgs): this;
521
520
  GLM53(args?: ModelAttachArgs): this;
522
521
 
package/index.js CHANGED
@@ -490,6 +490,8 @@ class ModelMix {
490
490
  }
491
491
  gptOss({ options = {}, config = {}, mix = {} } = {}) {
492
492
  mix = { ...this.mix, ...mix };
493
+ if (mix.nvidia) this.attach('openai/gpt-oss-120b', new MixNVIDIA({ options, config }));
494
+ if (mix.fireworks) this.attach('accounts/fireworks/models/gpt-oss-120b', new MixFireworks({ options, config }));
493
495
  if (mix.together) this.attach('openai/gpt-oss-120b', new MixTogether({ options, config }));
494
496
  if (mix.cerebras) this.attach('gpt-oss-120b', new MixCerebras({ options, config }));
495
497
  if (mix.groq) this.attach('openai/gpt-oss-120b', new MixGroq({ options, config }));
@@ -574,9 +576,12 @@ class ModelMix {
574
576
  return this.attach('grok-4.20-0309', new MixGrok({ options, config }));
575
577
  }
576
578
 
577
- qwen3({ options = {}, config = {}, mix = { together: true, cerebras: false } } = {}) {
578
- if (mix.together) this.attach('Qwen/Qwen3-235B-A22B-fp8-tput', new MixTogether({ options, config }));
579
- if (mix.cerebras) this.attach('qwen-3-32b', new MixCerebras({ options, config }));
579
+ museGlimmer30b({ options = {}, config = {}, mix = { fireworks: true } } = {}) {
580
+ mix = { ...this.mix, ...mix };
581
+ if (mix.nvidia) this.attach('meta/muse-glimmer-30b', new MixNVIDIA({ options, config }));
582
+ if (mix.fireworks) this.attach('accounts/fireworks/models/muse-glimmer-30b', new MixFireworks({ options, config }));
583
+ if (mix.openrouter) this.attach('meta/muse-glimmer-30b', new MixOpenRouter({ options, config }));
584
+ if (mix.together) this.attach('meta-models/Muse-Glimmer-30B', new MixTogether({ options, config }));
580
585
  return this;
581
586
  }
582
587
 
@@ -584,9 +589,10 @@ class ModelMix {
584
589
  return this.attach('qwen/qwen3.5-397b-a17b', new MixOpenRouter({ options, config }));
585
590
  }
586
591
 
587
- qwen36plus({ options = {}, config = {}, mix = { fireworks: true } } = {}) {
592
+ qwen36plus({ options = {}, config = {}, mix = { fireworks: false, openrouter: true } } = {}) {
588
593
  mix = { ...this.mix, ...mix };
589
594
  if (mix.fireworks) this.attach('accounts/fireworks/models/qwen3p6-plus', new MixFireworks({ options, config }));
595
+ if (mix.openrouter) this.attach('qwen/qwen3.6-plus', new MixOpenRouter({ options, config }));
590
596
  if (mix.together) this.attach('Qwen/Qwen3.6-Plus', new MixTogether({ options, config }));
591
597
  return this;
592
598
  }
@@ -595,6 +601,7 @@ class ModelMix {
595
601
  mix = { ...this.mix, ...mix };
596
602
  if (mix.fireworks) this.attach('accounts/fireworks/models/qwen3p7-plus', new MixFireworks({ options, config }));
597
603
  if (mix.openrouter) this.attach('qwen/qwen3.7-plus', new MixOpenRouter({ options, config }));
604
+ if (mix.together) this.attach('Qwen/Qwen3.7-Plus', new MixTogether({ options, config }));
598
605
  return this;
599
606
  }
600
607
 
@@ -635,13 +642,17 @@ class ModelMix {
635
642
  kimiK27Code({ options = {}, config = {}, mix = { together: true } } = {}) {
636
643
  mix = { ...this.mix, ...mix };
637
644
  if (mix.together) this.attach('moonshotai/Kimi-K2.7-Code', new MixTogether({ options, config }));
645
+ if (mix.fireworks) this.attach('accounts/fireworks/models/kimi-k2p7-code', new MixFireworks({ options, config }));
646
+ if (mix.openrouter) this.attach('moonshotai/kimi-k2.7-code', new MixOpenRouter({ options, config }));
638
647
  return this;
639
648
  }
640
649
 
641
650
  kimiK3({ options = {}, config = {}, mix = { moonshot: true, openrouter: false } } = {}) {
642
651
  mix = { ...this.mix, ...mix };
643
652
  if (mix.moonshot) this.attach('kimi-k3', new MixKimi({ options, config }));
653
+ if (mix.fireworks) this.attach('accounts/fireworks/models/kimi-k3', new MixFireworks({ options, config }));
644
654
  if (mix.openrouter) this.attach('moonshotai/kimi-k3', new MixOpenRouter({ options, config }));
655
+ if (mix.together) this.attach('moonshotai/Kimi-K3', new MixTogether({ options, config }));
645
656
  return this;
646
657
  }
647
658
 
@@ -661,14 +672,16 @@ class ModelMix {
661
672
  minimaxM27({ options = {}, config = {}, mix = { openrouter: true, minimax: true } } = {}) {
662
673
  mix = { ...this.mix, ...mix };
663
674
  if (mix.nvidia) this.attach('minimaxai/minimax-m2.7', new MixNVIDIA({ options, config }));
664
- if (mix.openrouter) return this.attach('minimax/minimax-m2.7', new MixOpenRouter({ options, config }));
665
- if (mix.minimax) return this.attach('MiniMax-M2.7', new MixMiniMax({ options, config }));
666
- if (mix.together) return this.attach('MiniMaxAI/MiniMax-M2.7', new MixTogether({ options, config }));
675
+ if (mix.fireworks) this.attach('accounts/fireworks/models/minimax-m2p7', new MixFireworks({ options, config }));
676
+ if (mix.openrouter) this.attach('minimax/minimax-m2.7', new MixOpenRouter({ options, config }));
677
+ if (mix.minimax) this.attach('MiniMax-M2.7', new MixMiniMax({ options, config }));
678
+ if (mix.together) this.attach('MiniMaxAI/MiniMax-M2.7', new MixTogether({ options, config }));
667
679
  return this;
668
680
  }
669
681
 
670
682
  minimaxM3({ options = {}, config = {}, mix = { minimax: true, openrouter: false } } = {}) {
671
683
  mix = { ...this.mix, ...mix };
684
+ if (mix.fireworks) this.attach('accounts/fireworks/models/minimax-m3', new MixFireworks({ options, config }));
672
685
  if (mix.openrouter) this.attach('minimax/minimax-m3', new MixOpenRouter({ options, config }));
673
686
  if (mix.minimax) this.attach('MiniMax-M3', new MixMiniMax({ options, config }));
674
687
  if (mix.together) this.attach('MiniMaxAI/MiniMax-M3', new MixTogether({ options, config }));
@@ -707,18 +720,11 @@ class ModelMix {
707
720
  return this;
708
721
  }
709
722
 
710
- GLM51({ options = {}, config = {}, mix = { fireworks: true } } = {}) {
711
- mix = { ...this.mix, ...mix };
712
- if (mix.nvidia) this.attach('z-ai/glm-5.1', new MixNVIDIA({ options, config }));
713
- if (mix.fireworks) this.attach('accounts/fireworks/models/glm-5p1', new MixFireworks({ options, config }));
714
- if (mix.openrouter) this.attach('z-ai/glm-5.1', new MixOpenRouter({ options, config }));
715
- if (mix.together) this.attach('zai-org/GLM-5.1', new MixTogether({ options, config }));
716
- return this;
717
- }
718
-
719
723
  GLM52({ options = {}, config = {}, mix = { together: true } } = {}) {
720
724
  mix = { ...this.mix, ...mix };
721
725
  if (mix.together) this.attach('zai-org/GLM-5.2', new MixTogether({ options, config }));
726
+ if (mix.fireworks) this.attach('accounts/fireworks/models/glm-5p2', new MixFireworks({ options, config }));
727
+ if (mix.openrouter) this.attach('z-ai/glm-5.2', new MixOpenRouter({ options, config }));
722
728
  return this;
723
729
  }
724
730
 
@@ -10,11 +10,12 @@ const CHAIN_MODEL_SHORTCUTS = new Set([
10
10
  'gemini31pro', 'gemini37flash', 'gemini36flash', 'gemini35flash',
11
11
  'gemini35flashLite', 'gemini31flashLite', 'sonarPro', 'sonar',
12
12
  'grok46', 'grok45', 'grok43', 'grok420multiAgent', 'grok420',
13
- 'qwen3', 'qwen35397b', 'qwen36plus', 'qwen37plus', 'qwen38max', 'qwen3827b',
13
+ 'museGlimmer30b',
14
+ 'qwen35397b', 'qwen36plus', 'qwen37plus', 'qwen38max', 'qwen3827b',
14
15
  'hermes470b', 'hermes4405b', 'hermes3',
15
16
  'kimiK26', 'kimiK27Code', 'kimiK3', 'kimiK25',
16
17
  'minimaxM27', 'minimaxM3', 'mimo25', 'mimo25pro',
17
- 'deepseekV4Pro', 'deepseekV4Flash', 'GLM51', 'GLM52', 'GLM53'
18
+ 'deepseekV4Pro', 'deepseekV4Flash', 'GLM52', 'GLM53'
18
19
  ]);
19
20
 
20
21
  function parseChainModels(modelSpecs) {
@@ -48,14 +48,14 @@ function createAnthropicProviders({ ModelMix, MixCustom, log }) {
48
48
  }
49
49
 
50
50
  getDefaultConfig(customConfig) {
51
-
52
- if (!process.env.ANTHROPIC_API_KEY) {
51
+ const apiKey = customConfig.apiKey || process.env.ANTHROPIC_API_KEY;
52
+ if (!apiKey) {
53
53
  throw new Error('Anthropic API key not found. Please provide it in config or set ANTHROPIC_API_KEY environment variable.');
54
54
  }
55
55
 
56
56
  return super.getDefaultConfig({
57
57
  url: 'https://api.anthropic.com/v1/messages',
58
- apiKey: process.env.ANTHROPIC_API_KEY,
58
+ apiKey,
59
59
  ...customConfig
60
60
  });
61
61
  }
@@ -274,14 +274,14 @@ function createBaseProviders({ ModelMix }) {
274
274
  }
275
275
 
276
276
  getDefaultConfig(customConfig) {
277
-
278
- if (!process.env.OPENAI_API_KEY) {
277
+ const apiKey = customConfig.apiKey || process.env.OPENAI_API_KEY;
278
+ if (!apiKey) {
279
279
  throw new Error('OpenAI API key not found. Please provide it in config or set OPENAI_API_KEY environment variable.');
280
280
  }
281
281
 
282
282
  return super.getDefaultConfig({
283
283
  url: 'https://api.openai.com/v1/chat/completions',
284
- apiKey: process.env.OPENAI_API_KEY,
284
+ apiKey,
285
285
  ...customConfig
286
286
  });
287
287
  }
@@ -4,9 +4,10 @@ const { configForDebug } = require('../provider-debug');
4
4
  function createGoogleProviders({ ModelMix, MixCustom }) {
5
5
  class MixGoogle extends MixCustom {
6
6
  getDefaultConfig(customConfig) {
7
+ const apiKey = customConfig.apiKey || process.env.GEMINI_API_KEY;
7
8
  return super.getDefaultConfig({
8
9
  url: 'https://generativelanguage.googleapis.com/v1beta/models',
9
- apiKey: process.env.GEMINI_API_KEY,
10
+ apiKey,
10
11
  ...customConfig
11
12
  });
12
13
  }
@@ -6,14 +6,14 @@ const {
6
6
  function createCompatibleProviders({ MixCustom, MixOpenAI }) {
7
7
  class MixMiniMax extends MixOpenAI {
8
8
  getDefaultConfig(customConfig) {
9
-
10
- if (!process.env.MINIMAX_API_KEY) {
9
+ const apiKey = customConfig.apiKey || process.env.MINIMAX_API_KEY;
10
+ if (!apiKey) {
11
11
  throw new Error('MiniMax API key not found. Please provide it in config or set MINIMAX_API_KEY environment variable.');
12
12
  }
13
13
 
14
14
  return MixCustom.prototype.getDefaultConfig.call(this, {
15
15
  url: 'https://api.minimax.io/v1/chat/completions',
16
- apiKey: process.env.MINIMAX_API_KEY,
16
+ apiKey,
17
17
  ...customConfig
18
18
  });
19
19
  }
@@ -29,13 +29,14 @@ function createCompatibleProviders({ MixCustom, MixOpenAI }) {
29
29
 
30
30
  class MixMiMo extends MixOpenAI {
31
31
  getDefaultConfig(customConfig) {
32
- if (!process.env.MIMO_API_KEY) {
32
+ const apiKey = customConfig.apiKey || process.env.MIMO_API_KEY;
33
+ if (!apiKey) {
33
34
  throw new Error('MiMo API key not found. Please provide it in config or set MIMO_API_KEY environment variable.');
34
35
  }
35
36
 
36
37
  return MixCustom.prototype.getDefaultConfig.call(this, {
37
38
  url: 'https://api.xiaomimimo.com/v1/chat/completions',
38
- apiKey: process.env.MIMO_API_KEY,
39
+ apiKey,
39
40
  ...customConfig
40
41
  });
41
42
  }
@@ -52,14 +53,14 @@ function createCompatibleProviders({ MixCustom, MixOpenAI }) {
52
53
 
53
54
  class MixPerplexity extends MixCustom {
54
55
  getDefaultConfig(customConfig) {
55
-
56
- if (!process.env.PPLX_API_KEY) {
56
+ const apiKey = customConfig.apiKey || process.env.PPLX_API_KEY;
57
+ if (!apiKey) {
57
58
  throw new Error('Perplexity API key not found. Please provide it in config or set PPLX_API_KEY environment variable.');
58
59
  }
59
60
 
60
61
  return super.getDefaultConfig({
61
62
  url: 'https://api.perplexity.ai/chat/completions',
62
- apiKey: process.env.PPLX_API_KEY,
63
+ apiKey,
63
64
  ...customConfig
64
65
  });
65
66
  }
@@ -132,14 +133,14 @@ function createCompatibleProviders({ MixCustom, MixOpenAI }) {
132
133
 
133
134
  class MixGrok extends MixOpenAI {
134
135
  getDefaultConfig(customConfig) {
135
-
136
- if (!process.env.XAI_API_KEY) {
136
+ const apiKey = customConfig.apiKey || process.env.XAI_API_KEY;
137
+ if (!apiKey) {
137
138
  throw new Error('Grok API key not found. Please provide it in config or set XAI_API_KEY environment variable.');
138
139
  }
139
140
 
140
141
  return super.getDefaultConfig({
141
142
  url: 'https://api.x.ai/v1/chat/completions',
142
- apiKey: process.env.XAI_API_KEY,
143
+ apiKey,
143
144
  ...customConfig
144
145
  });
145
146
  }
@@ -154,14 +155,14 @@ function createCompatibleProviders({ MixCustom, MixOpenAI }) {
154
155
 
155
156
  class MixLambda extends MixCustom {
156
157
  getDefaultConfig(customConfig) {
157
-
158
- if (!process.env.LAMBDA_API_KEY) {
158
+ const apiKey = customConfig.apiKey || process.env.LAMBDA_API_KEY;
159
+ if (!apiKey) {
159
160
  throw new Error('Lambda API key not found. Please provide it in config or set LAMBDA_API_KEY environment variable.');
160
161
  }
161
162
 
162
163
  return super.getDefaultConfig({
163
164
  url: 'https://api.lambda.ai/v1/chat/completions',
164
- apiKey: process.env.LAMBDA_API_KEY,
165
+ apiKey,
165
166
  ...customConfig
166
167
  });
167
168
  }
@@ -234,14 +235,14 @@ function createCompatibleProviders({ MixCustom, MixOpenAI }) {
234
235
 
235
236
  class MixGroq extends MixCustom {
236
237
  getDefaultConfig(customConfig) {
237
-
238
- if (!process.env.GROQ_API_KEY) {
238
+ const apiKey = customConfig.apiKey || process.env.GROQ_API_KEY;
239
+ if (!apiKey) {
239
240
  throw new Error('Groq API key not found. Please provide it in config or set GROQ_API_KEY environment variable.');
240
241
  }
241
242
 
242
243
  return super.getDefaultConfig({
243
244
  url: 'https://api.groq.com/openai/v1/chat/completions',
244
- apiKey: process.env.GROQ_API_KEY,
245
+ apiKey,
245
246
  ...customConfig
246
247
  });
247
248
  }
@@ -249,14 +250,14 @@ function createCompatibleProviders({ MixCustom, MixOpenAI }) {
249
250
 
250
251
  class MixTogether extends MixCustom {
251
252
  getDefaultConfig(customConfig) {
252
-
253
- if (!process.env.TOGETHER_API_KEY) {
253
+ const apiKey = customConfig.apiKey || process.env.TOGETHER_API_KEY;
254
+ if (!apiKey) {
254
255
  throw new Error('Together API key not found. Please provide it in config or set TOGETHER_API_KEY environment variable.');
255
256
  }
256
257
 
257
258
  return super.getDefaultConfig({
258
259
  url: 'https://api.together.xyz/v1/chat/completions',
259
- apiKey: process.env.TOGETHER_API_KEY,
260
+ apiKey,
260
261
  ...customConfig
261
262
  });
262
263
  }
@@ -271,14 +272,14 @@ function createCompatibleProviders({ MixCustom, MixOpenAI }) {
271
272
 
272
273
  class MixCerebras extends MixCustom {
273
274
  getDefaultConfig(customConfig) {
274
-
275
- if (!process.env.CEREBRAS_API_KEY) {
276
- throw new Error('Together API key not found. Please provide it in config or set CEREBRAS_API_KEY environment variable.');
275
+ const apiKey = customConfig.apiKey || process.env.CEREBRAS_API_KEY;
276
+ if (!apiKey) {
277
+ throw new Error('Cerebras API key not found. Please provide it in config or set CEREBRAS_API_KEY environment variable.');
277
278
  }
278
279
 
279
280
  return super.getDefaultConfig({
280
281
  url: 'https://api.cerebras.ai/v1/chat/completions',
281
- apiKey: process.env.CEREBRAS_API_KEY,
282
+ apiKey,
282
283
  ...customConfig
283
284
  });
284
285
  }
@@ -291,14 +292,14 @@ function createCompatibleProviders({ MixCustom, MixOpenAI }) {
291
292
 
292
293
  class MixFireworks extends MixCustom {
293
294
  getDefaultConfig(customConfig) {
294
-
295
- if (!process.env.FIREWORKS_API_KEY) {
295
+ const apiKey = customConfig.apiKey || process.env.FIREWORKS_API_KEY;
296
+ if (!apiKey) {
296
297
  throw new Error('Fireworks API key not found. Please provide it in config or set FIREWORKS_API_KEY environment variable.');
297
298
  }
298
299
 
299
300
  return super.getDefaultConfig({
300
301
  url: 'https://api.fireworks.ai/inference/v1/chat/completions',
301
- apiKey: process.env.FIREWORKS_API_KEY,
302
+ apiKey,
302
303
  ...customConfig
303
304
  });
304
305
  }
@@ -306,14 +307,14 @@ function createCompatibleProviders({ MixCustom, MixOpenAI }) {
306
307
 
307
308
  class MixNVIDIA extends MixCustom {
308
309
  getDefaultConfig(customConfig) {
309
-
310
- if (!process.env.NVIDIA_API_KEY) {
310
+ const apiKey = customConfig.apiKey || process.env.NVIDIA_API_KEY;
311
+ if (!apiKey) {
311
312
  throw new Error('NVIDIA API key not found. Please provide it in config or set NVIDIA_API_KEY environment variable.');
312
313
  }
313
314
 
314
315
  return super.getDefaultConfig({
315
316
  url: 'https://integrate.api.nvidia.com/v1/chat/completions',
316
- apiKey: process.env.NVIDIA_API_KEY,
317
+ apiKey,
317
318
  ...customConfig
318
319
  });
319
320
  }
@@ -556,14 +556,14 @@ function createOpenAIProviders({
556
556
 
557
557
  class MixOpenRouter extends MixOpenAI {
558
558
  getDefaultConfig(customConfig) {
559
-
560
- if (!process.env.OPENROUTER_API_KEY) {
559
+ const apiKey = customConfig.apiKey || process.env.OPENROUTER_API_KEY;
560
+ if (!apiKey) {
561
561
  throw new Error('OpenRouter API key not found. Please provide it in config or set OPENROUTER_API_KEY environment variable.');
562
562
  }
563
563
 
564
564
  return MixCustom.prototype.getDefaultConfig.call(this, {
565
565
  url: 'https://openrouter.ai/api/v1/chat/completions',
566
- apiKey: process.env.OPENROUTER_API_KEY,
566
+ apiKey,
567
567
  ...customConfig
568
568
  });
569
569
  }
@@ -571,13 +571,14 @@ function createOpenAIProviders({
571
571
 
572
572
  class MixKimi extends MixOpenAI {
573
573
  getDefaultConfig(customConfig) {
574
- if (!process.env.MOONSHOT_API_KEY) {
574
+ const apiKey = customConfig.apiKey || process.env.MOONSHOT_API_KEY;
575
+ if (!apiKey) {
575
576
  throw new Error('Moonshot API key not found. Please provide it in config or set MOONSHOT_API_KEY environment variable.');
576
577
  }
577
578
 
578
579
  return MixCustom.prototype.getDefaultConfig.call(this, {
579
580
  url: 'https://api.moonshot.ai/v1/chat/completions',
580
- apiKey: process.env.MOONSHOT_API_KEY,
581
+ apiKey,
581
582
  ...customConfig
582
583
  });
583
584
  }
@@ -42,9 +42,10 @@ const MODEL_PRICING = {
42
42
  'gpt-4.1': { input: 2.00, cachedInput: 0.50, output: 8.00 },
43
43
  'gpt-4.1-mini': { input: 0.40, cachedInput: 0.10, output: 1.60 },
44
44
  'gpt-4.1-nano': { input: 0.10, cachedInput: 0.025, output: 0.40 },
45
- // gptOss (Together/Groq/Cerebras/OpenRouter)
45
+ // gptOss (NVIDIA/Fireworks/Together/Groq/Cerebras/OpenRouter)
46
46
  'openai/gpt-oss-120b': { input: 0.15, output: 0.60 },
47
47
  'gpt-oss-120b': { input: 0.15, output: 0.60 },
48
+ 'accounts/fireworks/models/gpt-oss-120b': { input: 0.15, cachedInput: 0.014, output: 0.60 },
48
49
  // Anthropic
49
50
  'claude-fable-5': { input: 10.00, cachedInput: 1.00, cacheWrite: 12.50, cacheWrite1h: 20.00, output: 50.00 },
50
51
  'claude-opus-5': { input: 5.00, cachedInput: 0.50, cacheWrite: 6.25, cacheWrite1h: 10.00, output: 25.00 },
@@ -74,6 +75,10 @@ const MODEL_PRICING = {
74
75
  'grok-4.20-0309': { input: 1.25, output: 2.50 },
75
76
  'grok-4.20-0309-reasoning': { input: 1.25, output: 2.50 },
76
77
  'grok-4.20-0309-non-reasoning': { input: 1.25, output: 2.50 },
78
+ // Muse Glimmer 30B (NVIDIA/OpenRouter, Fireworks, Together)
79
+ 'meta/muse-glimmer-30b': { input: 0.35, cachedInput: 0.04, output: 1.50 },
80
+ 'accounts/fireworks/models/muse-glimmer-30b': { input: 0.35, cachedInput: 0.04, output: 1.50 },
81
+ 'meta-models/Muse-Glimmer-30B': { input: 0.35, cachedInput: 0.04, output: 1.50 },
77
82
  // Fireworks
78
83
  'accounts/fireworks/models/deepseek-v4-flash': { input: 0.14, output: 0.28 },
79
84
  'accounts/fireworks/models/deepseek-v4-pro': { input: 1.74, output: 3.48 },
@@ -82,15 +87,29 @@ const MODEL_PRICING = {
82
87
  'deepseek-ai/DeepSeek-V4-Pro': { input: 2.10, output: 4.40 },
83
88
  'deepseek/deepseek-v4-flash': { input: 0.09, output: 0.18 },
84
89
  'accounts/fireworks/models/glm-4p7': { input: 0.55, output: 2.19 },
85
- 'accounts/fireworks/models/glm-5p1': { input: 1.05, output: 3.50 },
86
- 'zai-org/GLM-5.2': { input: 1.40, output: 4.40 },
90
+ 'zai-org/GLM-5.2': { input: 1.40, cachedInput: 0.26, output: 4.40 },
91
+ 'accounts/fireworks/models/glm-5p2': { input: 1.40, cachedInput: 0.14, output: 4.40 },
92
+ 'z-ai/glm-5.2': { input: 0.966, cachedInput: 0.1932, output: 3.036 },
87
93
  'z-ai/glm-5.3': { input: 1.40, cachedInput: 0.26, output: 4.40 },
88
94
  'accounts/fireworks/models/kimi-k2p5': { input: 0.50, output: 2.80 },
89
95
  'qwen/qwen3.5-397b-a17b': { input: 0.385, output: 2.45 },
90
96
  'accounts/fireworks/models/qwen3p6-plus': { input: 0.50, output: 3.00 },
91
97
  'Qwen/Qwen3.6-Plus': { input: 0.50, output: 3.00 },
98
+ 'qwen/qwen3.6-plus': {
99
+ input: 0.325,
100
+ cacheWrite: 0.40625,
101
+ output: 1.95,
102
+ longContext: { inputThreshold: 256_000, inputMultiplier: 4, outputMultiplier: 2, inclusive: true }
103
+ },
92
104
  'accounts/fireworks/models/qwen3p7-plus': { input: 0.40, output: 1.60 },
93
- 'qwen/qwen3.7-plus': { input: 0.32, output: 1.28 },
105
+ 'qwen/qwen3.7-plus': {
106
+ input: 0.32,
107
+ cachedInput: 0.064,
108
+ cacheWrite: 0.40,
109
+ output: 1.28,
110
+ longContext: { inputThreshold: 256_000, inputMultiplier: 3, outputMultiplier: 3, inclusive: true }
111
+ },
112
+ 'Qwen/Qwen3.7-Plus': { input: 0.32, output: 1.28 },
94
113
  'accounts/fireworks/models/qwen3p8-2p4t-a95b': { input: 2.00, cachedInput: 0.25, output: 6.00 },
95
114
  'qwen/qwen3.8-max': { input: 2.00, output: 6.00 },
96
115
  'qwen/qwen3.8-27b': { input: 0.45, cachedInput: 0.05, output: 3.20 },
@@ -100,7 +119,11 @@ const MODEL_PRICING = {
100
119
  'MiniMax-M3': { input: 0.30, output: 1.20 },
101
120
  'minimax/minimax-m2.7': { input: 0.30, output: 1.20 },
102
121
  'minimax/minimax-m3': { input: 0.30, output: 1.20 },
122
+ 'minimaxai/minimax-m2.7': { input: 0.30, output: 1.20 },
123
+ 'MiniMaxAI/MiniMax-M2.7': { input: 0.30, output: 1.20 },
103
124
  'MiniMaxAI/MiniMax-M3': { input: 0.30, output: 1.20 },
125
+ 'accounts/fireworks/models/minimax-m2p7': { input: 0.30, cachedInput: 0.059, output: 1.20 },
126
+ 'accounts/fireworks/models/minimax-m3': { input: 0.30, cachedInput: 0.059, output: 1.20 },
104
127
  // Perplexity
105
128
  'sonar': { input: 1.00, output: 1.00 },
106
129
  'sonar-pro': { input: 3.00, output: 15.00 },
@@ -110,15 +133,18 @@ const MODEL_PRICING = {
110
133
  // Hermes 3 (Lambda/OpenRouter)
111
134
  'Hermes-3-Llama-3.1-405B-FP8': { input: 0.80, output: 0.80 },
112
135
  'nousresearch/hermes-3-llama-3.1-405b:free': { input: 0, output: 0 },
113
- // Qwen3 (Together/Cerebras)
114
- 'Qwen/Qwen3-235B-A22B-fp8-tput': { input: 0.20, output: 0.60 },
115
- 'qwen-3-32b': { input: 0.20, output: 0.60 },
116
136
  // Kimi K2.5 (Together/Fireworks/OpenRouter)
117
137
  'moonshotai/Kimi-K2.5': { input: 0.50, output: 2.80 },
118
138
  'moonshotai/kimi-k2.5': { input: 0.50, output: 2.80 },
119
139
  // Kimi K3
120
- 'kimi-k3': { input: 3.00, output: 15.00 },
121
- 'moonshotai/kimi-k3': { input: 3.00, output: 15.00 },
140
+ 'kimi-k3': { input: 3.00, cachedInput: 0.30, output: 15.00 },
141
+ 'moonshotai/kimi-k3': { input: 3.00, cachedInput: 0.30, output: 15.00 },
142
+ 'accounts/fireworks/models/kimi-k3': { input: 3.00, cachedInput: 0.30, output: 15.00 },
143
+ 'moonshotai/Kimi-K3': { input: 3.00, cachedInput: 0.30, output: 15.00 },
144
+ // Kimi K2.7 Code
145
+ 'moonshotai/Kimi-K2.7-Code': { input: 0.95, cachedInput: 0.19, output: 4.00 },
146
+ 'accounts/fireworks/models/kimi-k2p7-code': { input: 0.95, cachedInput: 0.19, output: 4.00 },
147
+ 'moonshotai/kimi-k2.7-code': { input: 0.71, cachedInput: 0.15, output: 3.50 },
122
148
  // GLM 4.7 (OpenRouter/Cerebras)
123
149
  'z-ai/glm-4.7': { input: 0.55, output: 2.19 },
124
150
  'zai-glm-4.7': { input: 0.55, output: 2.19 },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "modelmix",
3
- "version": "5.1.4",
3
+ "version": "5.1.8",
4
4
  "description": "🧬 Reliable interface with automatic fallback for AI LLMs.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -168,22 +168,25 @@ Use `.effort(n)` (or `config.effort`) to enable Anthropic thinking — e.g. `.ef
168
168
  `sonar()` `sonarPro()`
169
169
 
170
170
  ### Together
171
- `qwen36plus()` `GLM52()` `kimiK25()` `kimiK26()` `gptOss()`
171
+ `museGlimmer30b()` `qwen36plus()` `qwen37plus()` `GLM52()` `kimiK25()` `kimiK26()` `kimiK27Code()` `kimiK3()` `minimaxM27()` `minimaxM3()` `gptOss()`
172
+
173
+ ### NVIDIA
174
+ `museGlimmer30b()` `gptOss()` `minimaxM27()`
172
175
 
173
176
  ### Moonshot
174
- `kimiK3()` — requires `MOONSHOT_API_KEY`; use `{ mix: { moonshot: false, openrouter: true } }` for OpenRouter.
177
+ `kimiK3()` — requires `MOONSHOT_API_KEY`; Fireworks, OpenRouter, and Together are available through `mix`.
175
178
 
176
179
  ### MiniMax
177
180
  `minimaxM27()` `minimaxM3()`
178
181
 
179
182
  ### Fireworks
180
- `qwen36plus()` `qwen37plus()` `qwen38max()` `deepseekV4Flash()` `deepseekV4Pro()` `kimiK26()`
183
+ `museGlimmer30b()` `gptOss()` `qwen36plus()` (private/on-demand only) `qwen37plus()` `qwen38max()` `deepseekV4Flash()` `deepseekV4Pro()` `kimiK26()` `kimiK27Code()` `kimiK3()` `minimaxM27()` `minimaxM3()` `GLM52()`
181
184
 
182
185
  ### Cerebras
183
186
  `GLM46()`
184
187
 
185
188
  ### OpenRouter
186
- `qwen35397b()` `qwen3827b()` `hermes470b()` `hermes4405b()` `qwen38max()` `GLM45()` `GLM53()`
189
+ `museGlimmer30b()` `gptOss()` `qwen35397b()` `qwen36plus()` `qwen37plus()` `qwen3827b()` `hermes470b()` `hermes4405b()` `qwen38max()` `kimiK27Code()` `kimiK3()` `minimaxM27()` `minimaxM3()` `GLM45()` `GLM52()` `GLM53()`
187
190
 
188
191
  ### Multi-provider (auto-fallback across free/paid tiers)
189
192
  `hermes3()` `kimiK25()`
@@ -96,6 +96,42 @@ describe('Unified effort scale', () => {
96
96
  expect(mapEffort('openai', 100, key)).to.deep.equal({ reasoning_effort: 'high' });
97
97
  });
98
98
 
99
+ it('maps the new GPT OSS providers to low, medium, and high reasoning', () => {
100
+ expect(mapEffort('openai', 0, 'accounts/fireworks/models/gpt-oss-120b')).to.deep.equal({
101
+ reasoning_effort: 'low'
102
+ });
103
+ expect(mapEffort('openai', 50, 'openai/gpt-oss-120b')).to.deep.equal({
104
+ reasoning_effort: 'medium'
105
+ });
106
+ expect(mapEffort('openai', 100, 'accounts/fireworks/models/gpt-oss-120b')).to.deep.equal({
107
+ reasoning_effort: 'high'
108
+ });
109
+ });
110
+
111
+ it('maps Kimi K3 and GLM 5.2 to their provider-supported levels', () => {
112
+ expect(mapEffort('openai', 100, 'moonshotai/Kimi-K3')).to.deep.equal({
113
+ reasoning_effort: 'max'
114
+ });
115
+ expect(mapEffort('openai', 0, 'accounts/fireworks/models/glm-5p2')).to.deep.equal({
116
+ reasoning_effort: 'high'
117
+ });
118
+ expect(mapEffort('openai', 100, 'z-ai/glm-5.2')).to.deep.equal({
119
+ reasoning_effort: 'xhigh'
120
+ });
121
+ });
122
+
123
+ it('maps Muse Glimmer 30B to provider-supported reasoning levels', () => {
124
+ expect(mapEffort('openai', 0, 'accounts/fireworks/models/muse-glimmer-30b')).to.deep.equal({
125
+ reasoning_effort: 'low'
126
+ });
127
+ expect(mapEffort('openai', 100, 'accounts/fireworks/models/muse-glimmer-30b')).to.deep.equal({
128
+ reasoning_effort: 'xhigh'
129
+ });
130
+ expect(mapEffort('openai', 100, 'meta/muse-glimmer-30b')).to.deep.equal({
131
+ reasoning_effort: 'high'
132
+ });
133
+ });
134
+
99
135
  it('maps Qwen 3.8 27B to its supported reasoning levels', () => {
100
136
  const key = 'qwen/qwen3.8-27b';
101
137
  expect(mapEffort('openai', 0, key)).to.deep.equal({ reasoning_effort: 'low' });
@@ -64,7 +64,7 @@ describe('Provider Fallback Chain Tests', () => {
64
64
  for (const shortcut of [
65
65
  'gpt41', 'gpt41mini', 'gpt41nano',
66
66
  'gemini25flash', 'gemini25pro', 'gemini3pro', 'gemini3flash',
67
- 'minimaxM25'
67
+ 'minimaxM25', 'qwen3', 'GLM51'
68
68
  ]) {
69
69
  expect(model[shortcut]).to.equal(undefined);
70
70
  expect(() => model.chain(shortcut))
package/test/glm.test.js CHANGED
@@ -1,13 +1,37 @@
1
1
  const { expect } = require('chai');
2
- const { ModelMix, MixOpenRouter } = require('../index.js');
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 by default', () => {
5
+ it('should register Together GLM 5.2 before the OpenRouter fallback by default', () => {
6
6
  const model = ModelMix.new();
7
7
  model.GLM52();
8
8
 
9
- expect(model.models).to.have.length(1);
10
- expect(model.models[0].key).to.equal('zai-org/GLM-5.2');
9
+ expect(model.models.map(({ key }) => key)).to.deep.equal([
10
+ 'zai-org/GLM-5.2',
11
+ 'z-ai/glm-5.2'
12
+ ]);
13
+ expect(model.models[0].provider).to.be.instanceOf(MixTogether);
14
+ expect(model.models[1].provider).to.be.instanceOf(MixOpenRouter);
15
+ });
16
+
17
+ it('should register every requested GLM 5.2 provider', () => {
18
+ const model = ModelMix.new().GLM52({
19
+ mix: { together: true, fireworks: true, openrouter: true }
20
+ });
21
+
22
+ expect(model.models.map(({ key }) => key)).to.deep.equal([
23
+ 'zai-org/GLM-5.2',
24
+ 'accounts/fireworks/models/glm-5p2',
25
+ 'z-ai/glm-5.2'
26
+ ]);
27
+ expect(model.models[0].provider).to.be.instanceOf(MixTogether);
28
+ expect(model.models[1].provider).to.be.instanceOf(MixFireworks);
29
+ expect(model.models[2].provider).to.be.instanceOf(MixOpenRouter);
30
+ expect(ModelMix.calculateCost('accounts/fireworks/models/glm-5p2', {
31
+ input: 1_000_000,
32
+ cached: 500_000,
33
+ output: 1_000_000
34
+ })).to.equal(5.17);
11
35
  });
12
36
 
13
37
  it('should register GLM 5.3 through OpenRouter', () => {
package/test/kimi.test.js CHANGED
@@ -1,15 +1,32 @@
1
1
  const { expect } = require('chai');
2
2
  const nock = require('nock');
3
- const { ModelMix, MixKimi, MixOpenRouter, MixTogether } = require('../index.js');
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 by default', () => {
6
+ it('should register Together Kimi K2.7 Code before the OpenRouter fallback by default', () => {
7
7
  const model = ModelMix.new();
8
8
  model.kimiK27Code();
9
9
 
10
- expect(model.models).to.have.length(1);
11
- expect(model.models[0].key).to.equal('moonshotai/Kimi-K2.7-Code');
10
+ expect(model.models.map(({ key }) => key)).to.deep.equal([
11
+ 'moonshotai/Kimi-K2.7-Code',
12
+ 'moonshotai/kimi-k2.7-code'
13
+ ]);
12
14
  expect(model.models[0].provider).to.be.instanceOf(MixTogether);
15
+ expect(model.models[1].provider).to.be.instanceOf(MixOpenRouter);
16
+ });
17
+
18
+ it('should register every requested Kimi K2.7 Code provider', () => {
19
+ const model = ModelMix.new().kimiK27Code({
20
+ mix: { together: true, fireworks: true, openrouter: true }
21
+ });
22
+
23
+ expect(model.models.map(({ key }) => key)).to.deep.equal([
24
+ 'moonshotai/Kimi-K2.7-Code',
25
+ 'accounts/fireworks/models/kimi-k2p7-code',
26
+ 'moonshotai/kimi-k2.7-code'
27
+ ]);
28
+ expect(model.models[1].provider).to.be.instanceOf(MixFireworks);
29
+ expect(model.models[2].provider).to.be.instanceOf(MixOpenRouter);
13
30
  });
14
31
 
15
32
  it('should register Kimi K3 with the native Moonshot provider by default', () => {
@@ -45,6 +62,19 @@ describe('Kimi Model Registration Tests', () => {
45
62
  }
46
63
  });
47
64
 
65
+ it('should register Fireworks and Together Kimi K3 when requested', () => {
66
+ const model = ModelMix.new().kimiK3({
67
+ mix: { moonshot: false, fireworks: true, openrouter: false, together: true }
68
+ });
69
+
70
+ expect(model.models.map(({ key }) => key)).to.deep.equal([
71
+ 'accounts/fireworks/models/kimi-k3',
72
+ 'moonshotai/Kimi-K3'
73
+ ]);
74
+ expect(model.models[0].provider).to.be.instanceOf(MixFireworks);
75
+ expect(model.models[1].provider).to.be.instanceOf(MixTogether);
76
+ });
77
+
48
78
  it('should adapt Kimi K3 requests to its fixed sampling API', async () => {
49
79
  const originalMoonshotApiKey = process.env.MOONSHOT_API_KEY;
50
80
  process.env.MOONSHOT_API_KEY = 'test-moonshot-key';
@@ -0,0 +1,61 @@
1
+ const { expect } = require('chai');
2
+ const {
3
+ ModelMix,
4
+ MixFireworks,
5
+ MixNVIDIA,
6
+ MixOpenRouter,
7
+ MixTogether
8
+ } = require('../index.js');
9
+
10
+ describe('Muse Glimmer Model Registration Tests', () => {
11
+ it('registers Fireworks before the OpenRouter fallback by default', () => {
12
+ const model = ModelMix.new().museGlimmer30b();
13
+
14
+ expect(model.models.map(({ key }) => key)).to.deep.equal([
15
+ 'accounts/fireworks/models/muse-glimmer-30b',
16
+ 'meta/muse-glimmer-30b'
17
+ ]);
18
+ expect(model.models[0].provider).to.be.instanceOf(MixFireworks);
19
+ expect(model.models[1].provider).to.be.instanceOf(MixOpenRouter);
20
+ });
21
+
22
+ it('registers every supported provider in fallback order', () => {
23
+ const model = ModelMix.new().museGlimmer30b({
24
+ mix: { nvidia: true, fireworks: true, openrouter: true, together: true }
25
+ });
26
+
27
+ expect(model.models.map(({ key }) => key)).to.deep.equal([
28
+ 'meta/muse-glimmer-30b',
29
+ 'accounts/fireworks/models/muse-glimmer-30b',
30
+ 'meta/muse-glimmer-30b',
31
+ 'meta-models/Muse-Glimmer-30B'
32
+ ]);
33
+ expect(model.models.map(({ provider }) => provider.constructor)).to.deep.equal([
34
+ MixNVIDIA,
35
+ MixFireworks,
36
+ MixOpenRouter,
37
+ MixTogether
38
+ ]);
39
+ });
40
+
41
+ it('calculates cached input pricing for every provider model ID', () => {
42
+ const usage = { input: 1_000_000, cached: 250_000, output: 1_000_000 };
43
+
44
+ for (const key of [
45
+ 'meta/muse-glimmer-30b',
46
+ 'accounts/fireworks/models/muse-glimmer-30b',
47
+ 'meta-models/Muse-Glimmer-30B'
48
+ ]) {
49
+ expect(ModelMix.calculateCost(key, usage)).to.equal(1.7725);
50
+ }
51
+ });
52
+
53
+ it('supports Muse Glimmer 30B in chain()', () => {
54
+ const model = ModelMix.new().chain('museGlimmer30b');
55
+
56
+ expect(model.models.map(({ key }) => key)).to.deep.equal([
57
+ 'accounts/fireworks/models/muse-glimmer-30b',
58
+ 'meta/muse-glimmer-30b'
59
+ ]);
60
+ });
61
+ });
@@ -0,0 +1,83 @@
1
+ const { expect } = require('chai');
2
+ const {
3
+ ModelMix,
4
+ MixFireworks,
5
+ MixMiniMax,
6
+ MixNVIDIA,
7
+ MixOpenRouter,
8
+ MixTogether
9
+ } = require('../index.js');
10
+
11
+ describe('Provider expansion regressions', () => {
12
+ it('should retain every enabled GPT OSS provider, including shared model keys', () => {
13
+ const model = ModelMix.new().gptOss({
14
+ mix: {
15
+ nvidia: true,
16
+ fireworks: true,
17
+ together: true,
18
+ cerebras: true,
19
+ groq: true,
20
+ openrouter: true
21
+ }
22
+ });
23
+
24
+ expect(model.models.map(({ key }) => key)).to.deep.equal([
25
+ 'openai/gpt-oss-120b',
26
+ 'accounts/fireworks/models/gpt-oss-120b',
27
+ 'openai/gpt-oss-120b',
28
+ 'gpt-oss-120b',
29
+ 'openai/gpt-oss-120b',
30
+ 'openai/gpt-oss-120b'
31
+ ]);
32
+ expect(model.models[0].provider).to.be.instanceOf(MixNVIDIA);
33
+ expect(model.models[1].provider).to.be.instanceOf(MixFireworks);
34
+ expect(ModelMix.calculateCost('accounts/fireworks/models/gpt-oss-120b', {
35
+ input: 1_000_000,
36
+ cached: 500_000,
37
+ output: 1_000_000
38
+ })).to.equal(0.682);
39
+ });
40
+
41
+ it('should retain every enabled MiniMax M2.7 provider in fallback order', () => {
42
+ const originalMiniMaxApiKey = process.env.MINIMAX_API_KEY;
43
+ process.env.MINIMAX_API_KEY = 'test-minimax-key';
44
+
45
+ try {
46
+ const model = ModelMix.new().minimaxM27({
47
+ mix: {
48
+ nvidia: true,
49
+ fireworks: true,
50
+ openrouter: true,
51
+ minimax: true,
52
+ together: true
53
+ }
54
+ });
55
+
56
+ expect(model.models.map(({ key }) => key)).to.deep.equal([
57
+ 'minimaxai/minimax-m2.7',
58
+ 'accounts/fireworks/models/minimax-m2p7',
59
+ 'minimax/minimax-m2.7',
60
+ 'MiniMax-M2.7',
61
+ 'MiniMaxAI/MiniMax-M2.7'
62
+ ]);
63
+ expect(model.models[0].provider).to.be.instanceOf(MixNVIDIA);
64
+ expect(model.models[1].provider).to.be.instanceOf(MixFireworks);
65
+ expect(model.models[2].provider).to.be.instanceOf(MixOpenRouter);
66
+ expect(model.models[3].provider).to.be.instanceOf(MixMiniMax);
67
+ expect(model.models[4].provider).to.be.instanceOf(MixTogether);
68
+ } finally {
69
+ if (originalMiniMaxApiKey === undefined) delete process.env.MINIMAX_API_KEY;
70
+ else process.env.MINIMAX_API_KEY = originalMiniMaxApiKey;
71
+ }
72
+ });
73
+
74
+ it('should register Fireworks for MiniMax M3 without changing its default', () => {
75
+ const model = ModelMix.new().minimaxM3({
76
+ mix: { fireworks: true, openrouter: false, minimax: false, together: false }
77
+ });
78
+
79
+ expect(model.models).to.have.length(1);
80
+ expect(model.models[0].key).to.equal('accounts/fireworks/models/minimax-m3');
81
+ expect(model.models[0].provider).to.be.instanceOf(MixFireworks);
82
+ });
83
+ });
@@ -48,6 +48,48 @@ describe('public module boundary', () => {
48
48
  expect(model.models[0].provider.constructor).to.equal(api.MixOpenRouter);
49
49
  });
50
50
 
51
+ const explicitApiKeyCases = [
52
+ ['OpenAI', config => new api.MixOpenAIResponses({ config })],
53
+ ['OpenAI moderation', config => new api.MixOpenAIModeration({ config })],
54
+ ['Anthropic', config => new api.MixAnthropic({ config })],
55
+ ['Gemini', config => new api.MixGoogle({ config })],
56
+ ['MiniMax', config => new api.MixMiniMax({ config })],
57
+ ['MiMo', config => new api.MixMiMo({ config })],
58
+ ['Perplexity', config => new api.MixPerplexity({ config })],
59
+ ['Grok', config => new api.MixGrok({ config })],
60
+ ['Lambda', config => api.ModelMix.new({ mix: { openrouter: false, lambda: true } })
61
+ .hermes3({ config }).models[0].provider],
62
+ ['Groq', config => new api.MixGroq({ config })],
63
+ ['Together', config => new api.MixTogether({ config })],
64
+ ['Cerebras', config => new api.MixCerebras({ config })],
65
+ ['Fireworks', config => new api.MixFireworks({ config })],
66
+ ['NVIDIA', config => new api.MixNVIDIA({ config })],
67
+ ['OpenRouter', config => new api.MixOpenRouter({ config })],
68
+ ['Moonshot', config => new api.MixKimi({ config })]
69
+ ];
70
+
71
+ for (const [providerName, createProvider] of explicitApiKeyCases) {
72
+ it(`uses an explicit API key for ${providerName} without reading environment credentials`, () => {
73
+ const originalEnv = process.env;
74
+ process.env = new Proxy(originalEnv, {
75
+ get(target, property, receiver) {
76
+ if (typeof property === 'string' && property.endsWith('_API_KEY')) {
77
+ throw new Error(`${property} should not be read`);
78
+ }
79
+ return Reflect.get(target, property, receiver);
80
+ }
81
+ });
82
+
83
+ try {
84
+ const provider = createProvider({ apiKey: 'explicit-key' });
85
+
86
+ expect(provider.config.apiKey).to.equal('explicit-key');
87
+ } finally {
88
+ process.env = originalEnv;
89
+ }
90
+ });
91
+ }
92
+
51
93
  it('keeps the mutable pricing catalog private', () => {
52
94
  expect(require('../lib/token-usage')).to.not.have.property('MODEL_PRICING');
53
95
  });
package/test/qwen.test.js CHANGED
@@ -1,13 +1,22 @@
1
1
  const { expect } = require('chai');
2
- const { ModelMix, MixOpenRouter } = require('../index.js');
2
+ const { ModelMix, MixFireworks, MixOpenRouter, MixTogether } = require('../index.js');
3
3
 
4
4
  describe('Qwen Model Registration Tests', () => {
5
- it('should register Fireworks Qwen 3.6 Plus by default', () => {
5
+ it('should register OpenRouter Qwen 3.6 Plus by default', () => {
6
+ const model = ModelMix.new().qwen36plus();
7
+
8
+ expect(model.models).to.have.length(1);
9
+ expect(model.models[0].key).to.equal('qwen/qwen3.6-plus');
10
+ expect(model.models[0].provider).to.be.instanceOf(MixOpenRouter);
11
+ });
12
+
13
+ it('should retain Fireworks Qwen 3.6 Plus as an explicit deployment option', () => {
6
14
  const model = ModelMix.new();
7
- model.qwen36plus({ mix: { fireworks: true, together: false } });
15
+ model.qwen36plus({ mix: { fireworks: true, openrouter: false, together: false } });
8
16
 
9
17
  expect(model.models).to.have.length(1);
10
18
  expect(model.models[0].key).to.equal('accounts/fireworks/models/qwen3p6-plus');
19
+ expect(model.models[0].provider).to.be.instanceOf(MixFireworks);
11
20
  });
12
21
 
13
22
  it('should register Together Qwen 3.6 Plus when together mix is enabled', () => {
@@ -38,6 +47,26 @@ describe('Qwen Model Registration Tests', () => {
38
47
  expect(model.models[0].key).to.equal('qwen/qwen3.7-plus');
39
48
  });
40
49
 
50
+ it('should register Together Qwen 3.7 Plus when together mix is enabled', () => {
51
+ const model = ModelMix.new();
52
+ model.qwen37plus({ mix: { fireworks: false, openrouter: false, together: true } });
53
+
54
+ expect(model.models).to.have.length(1);
55
+ expect(model.models[0].key).to.equal('Qwen/Qwen3.7-Plus');
56
+ expect(model.models[0].provider).to.be.instanceOf(MixTogether);
57
+ });
58
+
59
+ it('should apply OpenRouter long-context pricing to Qwen 3.6 and 3.7 Plus', () => {
60
+ expect(ModelMix.calculateCost('qwen/qwen3.6-plus', {
61
+ input: 256_000,
62
+ output: 1_000_000
63
+ })).to.equal(4.2328);
64
+ expect(ModelMix.calculateCost('qwen/qwen3.7-plus', {
65
+ input: 256_000,
66
+ output: 1_000_000
67
+ })).to.equal(4.08576);
68
+ });
69
+
41
70
  it('should register Fireworks Qwen 3.8 Max before the OpenRouter fallback by default', () => {
42
71
  const model = ModelMix.new();
43
72
  model.qwen38max();