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 +6 -2
- package/demo/short.js +6 -4
- package/effort.js +3 -1
- package/index.d.ts +2 -0
- package/index.js +8 -0
- package/lib/model-chain.js +2 -2
- package/lib/providers/anthropic.js +3 -3
- package/lib/providers/base.js +3 -3
- package/lib/providers/google.js +2 -1
- package/lib/providers/openai-compatible.js +31 -30
- package/lib/providers/openai.js +6 -5
- package/lib/token-usage.js +2 -0
- package/package.json +1 -1
- package/skills/modelmix/SKILL.md +2 -2
- package/test/effort.test.js +17 -0
- package/test/glm.test.js +20 -0
- package/test/public-api.test.js +42 -0
- package/test/qwen.test.js +21 -0
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
|
-
.
|
|
20
|
-
.
|
|
21
|
-
.
|
|
22
|
-
.
|
|
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,
|
package/lib/model-chain.js
CHANGED
|
@@ -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 (!
|
|
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
|
|
58
|
+
apiKey,
|
|
59
59
|
...customConfig
|
|
60
60
|
});
|
|
61
61
|
}
|
package/lib/providers/base.js
CHANGED
|
@@ -274,14 +274,14 @@ function createBaseProviders({ ModelMix }) {
|
|
|
274
274
|
}
|
|
275
275
|
|
|
276
276
|
getDefaultConfig(customConfig) {
|
|
277
|
-
|
|
278
|
-
if (!
|
|
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
|
|
284
|
+
apiKey,
|
|
285
285
|
...customConfig
|
|
286
286
|
});
|
|
287
287
|
}
|
package/lib/providers/google.js
CHANGED
|
@@ -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
|
|
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 (!
|
|
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
|
|
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
|
-
|
|
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
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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
|
|
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 (!
|
|
276
|
-
throw new Error('
|
|
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
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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
|
|
317
|
+
apiKey,
|
|
317
318
|
...customConfig
|
|
318
319
|
});
|
|
319
320
|
}
|
package/lib/providers/openai.js
CHANGED
|
@@ -556,14 +556,14 @@ function createOpenAIProviders({
|
|
|
556
556
|
|
|
557
557
|
class MixOpenRouter extends MixOpenAI {
|
|
558
558
|
getDefaultConfig(customConfig) {
|
|
559
|
-
|
|
560
|
-
if (!
|
|
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
|
|
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
|
-
|
|
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
|
|
581
|
+
apiKey,
|
|
581
582
|
...customConfig
|
|
582
583
|
});
|
|
583
584
|
}
|
package/lib/token-usage.js
CHANGED
|
@@ -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
package/skills/modelmix/SKILL.md
CHANGED
|
@@ -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
|
|
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()`
|
package/test/effort.test.js
CHANGED
|
@@ -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
|
});
|
package/test/public-api.test.js
CHANGED
|
@@ -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
|
|