modelmix 5.1.6 → 5.1.9

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
@@ -177,9 +177,11 @@ ModelMix provides convenient shorthand methods for quickly accessing different A
177
177
  | `qwen37plus()` | Fireworks | models/qwen3p7-plus | [\$0.40][10] | [\$1.60][10] |
178
178
  | `qwen38max()` | Fireworks | qwen3p8-2p4t-a95b | [\$2.00][10] | [\$6.00][10] |
179
179
  | `qwen3827b()` | OpenRouter | qwen/qwen3.8-27b | [\$0.45][15] | [\$3.20][15] |
180
+ | `qwen38flash()` | OpenRouter | qwen/qwen3.8-flash | [\$0.16][19] | [\$0.47][19] |
180
181
  | `deepseekV4Flash()` | Fireworks | models/deepseek-v4-flash | [\$0.14][10] | [\$0.28][10] |
181
182
  | `deepseekV4Pro()` | Fireworks | models/deepseek-v4-pro-0813 | [\$1.32][12] | [\$3.96][12] |
182
183
  | `GLM53()` | OpenRouter | z-ai/glm-5.3 | [\$1.40][16] | [\$4.40][16] |
184
+ | `GLM53Flash()` | OpenRouter | z-ai/glm-5.3-flash | [\$0.075][20] | [\$0.25][20] |
183
185
  | `GLM52()` | Together | zai-org/GLM-5.2 | [\$1.40][7] | [\$4.40][7] |
184
186
  | `minimaxM3()` | MiniMax | MiniMax-M3 | [\$0.30][9] | [\$1.20][9] |
185
187
  | `minimaxM27()` | MiniMax | MiniMax-M2.7 | [\$0.30][9] | [\$1.20][9] |
@@ -217,6 +219,8 @@ The multi-provider shortcuts also expose the current catalog alternatives: `gptO
217
219
  [16]: https://openrouter.ai/z-ai/glm-5.3 "GLM 5.3 on OpenRouter"
218
220
  [17]: https://fireworks.ai/models/fireworks/muse-glimmer-30b "Muse Glimmer 30B on Fireworks"
219
221
  [18]: https://openrouter.ai/qwen/qwen3.6-plus "Qwen 3.6 Plus on OpenRouter"
222
+ [19]: https://openrouter.ai/qwen/qwen3.8-flash "Qwen3.8 Flash on OpenRouter"
223
+ [20]: https://openrouter.ai/z-ai/glm-5.3-flash "GLM 5.3 Flash on OpenRouter"
220
224
 
221
225
  Each method accepts optional `options`, `config`, and (for multi-provider methods) `mix` parameters to customize behavior.
222
226
 
@@ -257,8 +261,8 @@ ModelMix.new().effort(-1).minimaxM3().addText('...').message();
257
261
 
258
262
  - **Gemini:** Gemini 3+ uses bands 0–24 / 25–49 / 50–74 / 75–100. Gemini 3.7 Flash clamps these bands to `low` / `low` / `medium` / `high`; `-1` leaves its native `medium` default unchanged. Gemini 2.5 maps 0–100 to `thinkingBudget`.
259
263
  - **GPT-5.6:** `100` maps to `max`; 80–99 remains `xhigh`.
260
- - **Qwen 3.8 27B:** 0–39 / 40–79 / 80–100 map to `low` / `medium` / `xhigh`; `-1` leaves the native `xhigh` default unchanged.
261
- - **GLM 5.3:** reasoning is mandatory; 0–39 / 40–79 / 80–100 map to `low` / `high` / `max`; `-1` leaves the native `max` default unchanged.
264
+ - **Qwen 3.8 27B and Flash:** 0–39 / 40–79 / 80–100 map to `low` / `medium` / `xhigh`; `-1` leaves the native `xhigh` default unchanged. Qwen 3.8 Flash is the managed production version based on the open-weight Flash-Next architecture.
265
+ - **GLM 5.3 and GLM 5.3 Flash:** reasoning is mandatory; 0–39 / 40–79 / 80–100 map to `low` / `high` / `max`; `-1` leaves the native `max` default unchanged.
262
266
  - **DeepSeek:** `↑` means thinking is enabled; `off` means it is disabled.
263
267
  - **MiniMax:** `off` maps to `thinking.disabled`; `adaptive` maps to `thinking.type=adaptive`.
264
268
  - **Anthropic:** Claude 5, Fable, Opus 4.6+, and Sonnet 4.6+ use adaptive thinking with `output_config.effort`. Sonnet 4.5 and Haiku 4.5 use `thinking.type=enabled` with `budget_tokens`.
package/demo/short.js CHANGED
@@ -16,10 +16,12 @@ const mmix = await ModelMix.new(setup)
16
16
  .grok46() // (fallback 4) Grok grok-4.6
17
17
  .qwen35397b() // (fallback 5) OpenRouter qwen/qwen3.5-397b-a17b
18
18
  .qwen3827b() // (fallback 6) OpenRouter qwen/qwen3.8-27b
19
- .GLM53() // (fallback 7) OpenRouter z-ai/glm-5.3
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
19
+ .qwen38flash() // (fallback 7) OpenRouter qwen/qwen3.8-flash
20
+ .GLM53() // (fallback 8) OpenRouter z-ai/glm-5.3
21
+ .GLM53Flash() // (fallback 9) OpenRouter z-ai/glm-5.3-flash
22
+ .museGlimmer30b({ mix: { fireworks: false, openrouter: true } }) // (fallback 10) OpenRouter meta/muse-glimmer-30b
23
+ .hermes470b() // (fallback 11) OpenRouter nousresearch/hermes-4-70b
24
+ .hermes4405b() // (fallback 12) OpenRouter nousresearch/hermes-4-405b
23
25
  .addText("What's your name?");
24
26
 
25
27
  console.log(await mmix.message());
package/effort.js CHANGED
@@ -50,7 +50,9 @@ const OPENAI_MODEL_LEVELS = {
50
50
  'meta-models/Muse-Glimmer-30B': ['low', 'medium', 'high', 'xhigh'],
51
51
  'accounts/fireworks/models/qwen3p8-2p4t-a95b': ['none', 'low', 'medium', 'high'],
52
52
  'qwen/qwen3.8-27b': ['low', 'medium', 'xhigh'],
53
+ 'qwen/qwen3.8-flash': ['low', 'medium', 'xhigh'],
53
54
  'z-ai/glm-5.3': ['low', 'high', 'max'],
55
+ 'z-ai/glm-5.3-flash': ['low', 'high', 'max'],
54
56
  'grok-4.6': ['low', 'medium', 'high', 'xhigh'],
55
57
  'gpt-5': ['minimal', 'low', 'medium', 'high'],
56
58
  'gpt-5-mini': ['minimal', 'low', 'medium', 'high'],
@@ -396,7 +398,7 @@ function mapEffort(providerFamily, effort, modelKey) {
396
398
  return null;
397
399
  }
398
400
  const supported = supportedOpenAILevels(modelKey);
399
- const desired = modelKey === 'z-ai/glm-5.3'
401
+ const desired = modelKey === 'z-ai/glm-5.3' || modelKey === 'z-ai/glm-5.3-flash'
400
402
  ? levelFromBands(normalized, GLM53_BANDS)
401
403
  : normalized === 100 && supported.includes('max')
402
404
  ? 'max'
package/index.d.ts CHANGED
@@ -502,6 +502,7 @@ export declare class ModelMix {
502
502
  qwen37plus(args?: ModelAttachArgs): this;
503
503
  qwen38max(args?: ModelAttachArgs): this;
504
504
  qwen3827b(args?: ModelAttachArgs): this;
505
+ qwen38flash(args?: ModelAttachArgs): this;
505
506
  hermes470b(args?: ModelAttachArgs): this;
506
507
  hermes4405b(args?: ModelAttachArgs): this;
507
508
  hermes3(args?: ModelAttachArgs): this;
@@ -518,6 +519,7 @@ export declare class ModelMix {
518
519
  deepseekV4Flash(args?: ModelAttachArgs): this;
519
520
  GLM52(args?: ModelAttachArgs): this;
520
521
  GLM53(args?: ModelAttachArgs): this;
522
+ GLM53Flash(args?: ModelAttachArgs): this;
521
523
 
522
524
  addText(text: string, options?: RoleOptions): this;
523
525
  addTextFromFile(filePath: string, options?: RoleOptions): this;
package/index.js CHANGED
@@ -616,6 +616,10 @@ class ModelMix {
616
616
  return this.attach('qwen/qwen3.8-27b', new MixOpenRouter({ options, config }));
617
617
  }
618
618
 
619
+ qwen38flash({ options = {}, config = {} } = {}) {
620
+ return this.attach('qwen/qwen3.8-flash', new MixOpenRouter({ options, config }));
621
+ }
622
+
619
623
  hermes470b({ options = {}, config = {} } = {}) {
620
624
  return this.attach('nousresearch/hermes-4-70b', new MixOpenRouter({ options, config }));
621
625
  }
@@ -732,6 +736,10 @@ class ModelMix {
732
736
  return this.attach('z-ai/glm-5.3', new MixOpenRouter({ options, config }));
733
737
  }
734
738
 
739
+ GLM53Flash({ options = {}, config = {} } = {}) {
740
+ return this.attach('z-ai/glm-5.3-flash', new MixOpenRouter({ options, config }));
741
+ }
742
+
735
743
  addText(text, { role = "user", cache } = {}) {
736
744
  return this._addText(text, {
737
745
  role,
@@ -11,11 +11,11 @@ const CHAIN_MODEL_SHORTCUTS = new Set([
11
11
  'gemini35flashLite', 'gemini31flashLite', 'sonarPro', 'sonar',
12
12
  'grok46', 'grok45', 'grok43', 'grok420multiAgent', 'grok420',
13
13
  'museGlimmer30b',
14
- 'qwen35397b', 'qwen36plus', 'qwen37plus', 'qwen38max', 'qwen3827b',
14
+ 'qwen35397b', 'qwen36plus', 'qwen37plus', 'qwen38max', 'qwen3827b', 'qwen38flash',
15
15
  'hermes470b', 'hermes4405b', 'hermes3',
16
16
  'kimiK26', 'kimiK27Code', 'kimiK3', 'kimiK25',
17
17
  'minimaxM27', 'minimaxM3', 'mimo25', 'mimo25pro',
18
- 'deepseekV4Pro', 'deepseekV4Flash', 'GLM52', 'GLM53'
18
+ 'deepseekV4Pro', 'deepseekV4Flash', 'GLM52', 'GLM53', 'GLM53Flash'
19
19
  ]);
20
20
 
21
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
  }
@@ -91,6 +91,7 @@ const MODEL_PRICING = {
91
91
  'accounts/fireworks/models/glm-5p2': { input: 1.40, cachedInput: 0.14, output: 4.40 },
92
92
  'z-ai/glm-5.2': { input: 0.966, cachedInput: 0.1932, output: 3.036 },
93
93
  'z-ai/glm-5.3': { input: 1.40, cachedInput: 0.26, output: 4.40 },
94
+ 'z-ai/glm-5.3-flash': { input: 0.075, cachedInput: 0.015, output: 0.25 },
94
95
  'accounts/fireworks/models/kimi-k2p5': { input: 0.50, output: 2.80 },
95
96
  'qwen/qwen3.5-397b-a17b': { input: 0.385, output: 2.45 },
96
97
  'accounts/fireworks/models/qwen3p6-plus': { input: 0.50, output: 3.00 },
@@ -113,6 +114,7 @@ const MODEL_PRICING = {
113
114
  'accounts/fireworks/models/qwen3p8-2p4t-a95b': { input: 2.00, cachedInput: 0.25, output: 6.00 },
114
115
  'qwen/qwen3.8-max': { input: 2.00, output: 6.00 },
115
116
  'qwen/qwen3.8-27b': { input: 0.45, cachedInput: 0.05, output: 3.20 },
117
+ 'qwen/qwen3.8-flash': { input: 0.16, cachedInput: 0.016, cacheWrite: 0.20, output: 0.47 },
116
118
  // MiniMax
117
119
  'MiniMax-M2.5': { input: 0.30, output: 1.20 },
118
120
  'MiniMax-M2.7': { input: 0.30, output: 1.20 },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "modelmix",
3
- "version": "5.1.6",
3
+ "version": "5.1.9",
4
4
  "description": "🧬 Reliable interface with automatic fallback for AI LLMs.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -143,7 +143,7 @@ ModelMix.new({ config: { effort: 80 } })
143
143
  | DeepSeek V4 | off | `low`↑ | `high`↑ | `high`↑ | `max`↑ | — |
144
144
  | MiniMax M3 | off | adaptive | adaptive | adaptive | adaptive | adaptive |
145
145
 
146
- \* GPT-5.6 maps `100` to `max`; 80–99 remains `xhigh`. Qwen 3.8 27B maps 0–39 / 40–79 / 80–100 to `low` / `medium` / `xhigh`. GLM 5.3 requires reasoning and maps those bands to `low` / `high` / `max`. Gemini bands: 0–24 / 25–49 / 50–74 / 75–100. Gemini 3.7 Flash supports only `low` / `medium` / `high`, so the first two bands clamp to `low`; `-1` keeps its native `medium` default. DeepSeek `↑` = thinking on; `off` = thinking disabled. MiniMax `off`/`adaptive` = `thinking.disabled` / `thinking.type=adaptive`. Gemini 2.5 maps 0–100 to `thinkingBudget`. Anthropic: adaptive + `output_config.effort` on Claude 5 / Fable / Opus 4.6+ / Sonnet 4.6+; Sonnet 4.5 / Haiku 4.5 use `thinking.type=enabled` + `budget_tokens`. Grok 4.6 maps 0–39 / 40–59 / 60–79 / 80–100 to `low` / `medium` / `high` / `xhigh`; without effort it uses native `high`. `-1` = adaptive/dynamic when available, else no-op. Levels clamp per model. Former `*think()` methods are removed — use `.effort(n).<model>()`. Kimi: `kimiK25()` / `kimiK26()`. Grok 4.20: `.grok420()` non-reasoning; `.effort(20+|-1).grok420()` selects reasoning.
146
+ \* GPT-5.6 maps `100` to `max`; 80–99 remains `xhigh`. Qwen 3.8 27B and Flash map 0–39 / 40–79 / 80–100 to `low` / `medium` / `xhigh`; Qwen 3.8 Flash is the managed production version based on Flash-Next. GLM 5.3 and GLM 5.3 Flash require reasoning and map those bands to `low` / `high` / `max`. Gemini bands: 0–24 / 25–49 / 50–74 / 75–100. Gemini 3.7 Flash supports only `low` / `medium` / `high`, so the first two bands clamp to `low`; `-1` keeps its native `medium` default. DeepSeek `↑` = thinking on; `off` = thinking disabled. MiniMax `off`/`adaptive` = `thinking.disabled` / `thinking.type=adaptive`. Gemini 2.5 maps 0–100 to `thinkingBudget`. Anthropic: adaptive + `output_config.effort` on Claude 5 / Fable / Opus 4.6+ / Sonnet 4.6+; Sonnet 4.5 / Haiku 4.5 use `thinking.type=enabled` + `budget_tokens`. Grok 4.6 maps 0–39 / 40–59 / 60–79 / 80–100 to `low` / `medium` / `high` / `xhigh`; without effort it uses native `high`. `-1` = adaptive/dynamic when available, else no-op. Levels clamp per model. Former `*think()` methods are removed — use `.effort(n).<model>()`. Kimi: `kimiK25()` / `kimiK26()`. Grok 4.20: `.grok420()` non-reasoning; `.effort(20+|-1).grok420()` selects reasoning.
147
147
 
148
148
  ## Available Model Shorthands
149
149
 
@@ -186,7 +186,7 @@ Use `.effort(n)` (or `config.effort`) to enable Anthropic thinking — e.g. `.ef
186
186
  `GLM46()`
187
187
 
188
188
  ### OpenRouter
189
- `museGlimmer30b()` `gptOss()` `qwen35397b()` `qwen36plus()` `qwen37plus()` `qwen3827b()` `hermes470b()` `hermes4405b()` `qwen38max()` `kimiK27Code()` `kimiK3()` `minimaxM27()` `minimaxM3()` `GLM45()` `GLM52()` `GLM53()`
189
+ `museGlimmer30b()` `gptOss()` `qwen35397b()` `qwen36plus()` `qwen37plus()` `qwen3827b()` `qwen38flash()` `hermes470b()` `hermes4405b()` `qwen38max()` `kimiK27Code()` `kimiK3()` `minimaxM27()` `minimaxM3()` `GLM45()` `GLM52()` `GLM53()` `GLM53Flash()`
190
190
 
191
191
  ### Multi-provider (auto-fallback across free/paid tiers)
192
192
  `hermes3()` `kimiK25()`
@@ -140,6 +140,14 @@ describe('Unified effort scale', () => {
140
140
  expect(mapEffort('openai', -1, key)).to.equal(null);
141
141
  });
142
142
 
143
+ it('maps Qwen 3.8 Flash to its supported reasoning levels', () => {
144
+ const key = 'qwen/qwen3.8-flash';
145
+ expect(mapEffort('openai', 0, key)).to.deep.equal({ reasoning_effort: 'low' });
146
+ expect(mapEffort('openai', 50, key)).to.deep.equal({ reasoning_effort: 'medium' });
147
+ expect(mapEffort('openai', 100, key)).to.deep.equal({ reasoning_effort: 'xhigh' });
148
+ expect(mapEffort('openai', -1, key)).to.equal(null);
149
+ });
150
+
143
151
  it('maps GLM 5.3 to mandatory low, high, and max reasoning', () => {
144
152
  const key = 'z-ai/glm-5.3';
145
153
  expect(mapEffort('openai', 39, key)).to.deep.equal({ reasoning_effort: 'low' });
@@ -149,6 +157,15 @@ describe('Unified effort scale', () => {
149
157
  expect(mapEffort('openai', -1, key)).to.equal(null);
150
158
  });
151
159
 
160
+ it('maps GLM 5.3 Flash to mandatory low, high, and max reasoning', () => {
161
+ const key = 'z-ai/glm-5.3-flash';
162
+ expect(mapEffort('openai', 39, key)).to.deep.equal({ reasoning_effort: 'low' });
163
+ expect(mapEffort('openai', 40, key)).to.deep.equal({ reasoning_effort: 'high' });
164
+ expect(mapEffort('openai', 79, key)).to.deep.equal({ reasoning_effort: 'high' });
165
+ expect(mapEffort('openai', 80, key)).to.deep.equal({ reasoning_effort: 'max' });
166
+ expect(mapEffort('openai', -1, key)).to.equal(null);
167
+ });
168
+
152
169
  it('maps Anthropic adaptive models to thinking + output_config.effort', () => {
153
170
  expect(mapEffort('anthropic', 10, 'claude-opus-5')).to.deep.equal({
154
171
  thinking: { type: 'adaptive', display: 'summarized' },
package/test/glm.test.js CHANGED
@@ -53,4 +53,24 @@ describe('GLM Model Registration Tests', () => {
53
53
  expect(model.models).to.have.length(1);
54
54
  expect(model.models[0].key).to.equal('z-ai/glm-5.3');
55
55
  });
56
+
57
+ it('should register GLM 5.3 Flash through OpenRouter', () => {
58
+ const model = ModelMix.new().GLM53Flash();
59
+
60
+ expect(model.models).to.have.length(1);
61
+ expect(model.models[0].key).to.equal('z-ai/glm-5.3-flash');
62
+ expect(model.models[0].provider).to.be.instanceOf(MixOpenRouter);
63
+ expect(ModelMix.calculateCost('z-ai/glm-5.3-flash', {
64
+ input: 1_000_000,
65
+ cached: 500_000,
66
+ output: 1_000_000
67
+ })).to.equal(0.295);
68
+ });
69
+
70
+ it('should support GLM 5.3 Flash in chain()', () => {
71
+ const model = ModelMix.new().chain('GLM53Flash');
72
+
73
+ expect(model.models).to.have.length(1);
74
+ expect(model.models[0].key).to.equal('z-ai/glm-5.3-flash');
75
+ });
56
76
  });
@@ -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
@@ -110,6 +110,27 @@ describe('Qwen Model Registration Tests', () => {
110
110
  expect(model.models[0].key).to.equal('qwen/qwen3.8-27b');
111
111
  });
112
112
 
113
+ it('should register Qwen 3.8 Flash through OpenRouter', () => {
114
+ const model = ModelMix.new().qwen38flash();
115
+
116
+ expect(model.models).to.have.length(1);
117
+ expect(model.models[0].key).to.equal('qwen/qwen3.8-flash');
118
+ expect(model.models[0].provider).to.be.instanceOf(MixOpenRouter);
119
+ expect(ModelMix.calculateCost('qwen/qwen3.8-flash', {
120
+ input: 1_000_000,
121
+ cached: 250_000,
122
+ cacheWrite: 100_000,
123
+ output: 1_000_000
124
+ })).to.equal(0.598);
125
+ });
126
+
127
+ it('should support Qwen 3.8 Flash in chain()', () => {
128
+ const model = ModelMix.new().chain('qwen38flash');
129
+
130
+ expect(model.models).to.have.length(1);
131
+ expect(model.models[0].key).to.equal('qwen/qwen3.8-flash');
132
+ });
133
+
113
134
  it('should register Qwen 3.5 397B A17B through OpenRouter', () => {
114
135
  const model = ModelMix.new().qwen35397b();
115
136