modelmix 4.6.15 → 4.7.2
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 +16 -14
- package/demo/free.js +1 -1
- package/demo/opus5.js +2 -2
- package/demo/short.js +3 -3
- package/demo/stream.js +1 -1
- package/demo/tokens.js +1 -1
- package/effort.js +101 -3
- package/index.d.ts +3 -20
- package/index.js +20 -99
- package/package.json +2 -2
- package/skills/modelmix/SKILL.md +12 -12
- package/test/anthropic.test.js +31 -36
- package/test/effort.test.js +50 -7
- package/test/grok.test.js +38 -2
- package/test/history.test.js +1 -1
- package/test/live.test.js +7 -19
package/README.md
CHANGED
|
@@ -89,7 +89,7 @@ console.log(ETH.price);
|
|
|
89
89
|
```javascript
|
|
90
90
|
ModelMix.new()
|
|
91
91
|
.gptOss()
|
|
92
|
-
.
|
|
92
|
+
.kimiK25()
|
|
93
93
|
.hermes3()
|
|
94
94
|
.addText('What is the capital of France?');
|
|
95
95
|
```
|
|
@@ -124,7 +124,9 @@ ModelMix.new().effort(-1).minimaxM3().addText('...').message();
|
|
|
124
124
|
| DeepSeek V4 | off | `low`↑ | `high`↑ | `high`↑ | `max`↑ | — |
|
|
125
125
|
| MiniMax M3 | off | adaptive | adaptive | adaptive | adaptive | adaptive |
|
|
126
126
|
|
|
127
|
-
\* Gemini bands: 0–24 / 25–49 / 50–74 / 75–100. DeepSeek `↑` = thinking on; `off` = thinking disabled. MiniMax `off`/`adaptive` = `thinking.disabled` / `thinking.type=adaptive`. Gemini 2.5 maps 0–100 to `thinkingBudget`. `-1` = provider adaptive/dynamic when available, else no-op. Levels clamp to what each model supports.
|
|
127
|
+
\* Gemini bands: 0–24 / 25–49 / 50–74 / 75–100. DeepSeek `↑` = thinking on; `off` = thinking disabled. MiniMax `off`/`adaptive` = `thinking.disabled` / `thinking.type=adaptive`. Gemini 2.5 maps 0–100 to `thinkingBudget`. Anthropic maps adaptive thinking + `output_config.effort` on Claude 5 / Fable / Opus 4.6+ / Sonnet 4.6+; older models (Sonnet 4.5, Haiku 4.5) get `thinking.type=enabled` + `budget_tokens`. `-1` = provider adaptive/dynamic when available, else no-op. Levels clamp to what each model supports.
|
|
128
|
+
|
|
129
|
+
Migration: former `*think()` shorthands are removed — use `.effort(n).<model>()` (or any 0–100 / `-1`). Kimi: `kimiK25()` / `kimiK26()`. Grok 4.20: `.grok420()` is non-reasoning; `.effort(20+).grok420()` (or `-1`) selects the reasoning model.
|
|
128
130
|
|
|
129
131
|
## 🔧 Model Context Protocol (MCP) Integration
|
|
130
132
|
|
|
@@ -177,14 +179,14 @@ Here's a comprehensive list of available methods:
|
|
|
177
179
|
| `gpt41mini()` | OpenAI | gpt-4.1-mini | [\$0.40 / \$1.60][1] |
|
|
178
180
|
| `gpt41nano()` | OpenAI | gpt-4.1-nano | [\$0.10 / \$0.40][1] |
|
|
179
181
|
| `gptOss()` | Together | gpt-oss-120B | [\$0.15 / \$0.60][7] |
|
|
180
|
-
| `fable5
|
|
181
|
-
| `opus5
|
|
182
|
-
| `opus48
|
|
183
|
-
| `opus47
|
|
184
|
-
| `opus46
|
|
185
|
-
| `sonnet5
|
|
186
|
-
| `sonnet46
|
|
187
|
-
| `haiku45
|
|
182
|
+
| `fable5()` | Anthropic | claude-fable-5 | [\$10.00 / \$50.00][2] |
|
|
183
|
+
| `opus5()` | Anthropic | claude-opus-5 | [\$5.00 / \$25.00][2] |
|
|
184
|
+
| `opus48()` | Anthropic | claude-opus-4-8 | [\$5.00 / \$25.00][2] |
|
|
185
|
+
| `opus47()` | Anthropic | claude-opus-4-7 | [\$5.00 / \$25.00][2] |
|
|
186
|
+
| `opus46()` | Anthropic | claude-opus-4-6 | [\$5.00 / \$25.00][2] |
|
|
187
|
+
| `sonnet5()` | Anthropic | claude-sonnet-5 | [\$3.00 / \$15.00][2] |
|
|
188
|
+
| `sonnet46()` | Anthropic | claude-sonnet-4-6 | [\$3.00 / \$15.00][2] |
|
|
189
|
+
| `haiku45()` | Anthropic | claude-haiku-4-5-20251001 | [\$1.00 / \$5.00][2] |
|
|
188
190
|
| `gemini31pro()` | Google | gemini-3.1-pro-preview | [\$2.00 / \$12.00][3] |
|
|
189
191
|
| `gemini36flash()` | Google | gemini-3.6-flash | [\$1.50 / \$7.50][3] |
|
|
190
192
|
| `gemini35flash()` | Google | gemini-3.5-flash | [\$0.75 / \$4.50][3] |
|
|
@@ -192,8 +194,7 @@ Here's a comprehensive list of available methods:
|
|
|
192
194
|
| `grok45()` | Grok | grok-4.5 | [\$2.00 / \$6.00][6] |
|
|
193
195
|
| `grok43()` | Grok | grok-4.3 | [\$1.25 / \$2.50][6] |
|
|
194
196
|
| `grok420multiAgent()`| Grok | grok-4.20-multi-agent-0309 | [\$1.25 / \$2.50][6] |
|
|
195
|
-
| `grok420
|
|
196
|
-
| `grok41[think]()` | Grok | grok-4-1-fast | [\$0.20 / \$0.50][6] |
|
|
197
|
+
| `grok420()` | Grok | grok-4.20-0309 (†) | [\$1.25 / \$2.50][6] |
|
|
197
198
|
| `qwen36plus()` | Fireworks/Together | qwen3p6-plus / Qwen3.6-Plus | [\$0.50 / \$3.00][10] |
|
|
198
199
|
| `qwen37plus()` | Fireworks | models/qwen3p7-plus | [\$0.40 / \$1.60][10] |
|
|
199
200
|
| `qwen38max()` | OpenRouter | qwen/qwen3.8-max | [\$2.00 / \$6.00][12] |
|
|
@@ -207,8 +208,8 @@ Here's a comprehensive list of available methods:
|
|
|
207
208
|
| `sonarPro()` | Perplexity | sonar-pro | [\$3.00 / \$15.00][4] |
|
|
208
209
|
| `hermes3()` | Lambda | Hermes-3-Llama-3.1-405B-FP8 | [\$0.80 / \$0.80][8] |
|
|
209
210
|
| `kimiK3()` | Moonshot | kimi-k3 | [\$3.00 / \$15.00][11] |
|
|
210
|
-
| `
|
|
211
|
-
| `
|
|
211
|
+
| `kimiK25()` | Together | Kimi-K2.5 | [\$0.50 / \$2.80][7] |
|
|
212
|
+
| `kimiK26()` | Fireworks | models/kimi-k2p6 | [\$0.95 / \$4.00][10] |
|
|
212
213
|
|
|
213
214
|
[1]: https://platform.openai.com/docs/pricing "Pricing | OpenAI"
|
|
214
215
|
[2]: https://docs.anthropic.com/en/docs/about-claude/pricing "Pricing - Anthropic"
|
|
@@ -224,6 +225,7 @@ Here's a comprehensive list of available methods:
|
|
|
224
225
|
[12]: https://openrouter.ai/qwen/qwen3.8-max "OpenRouter Pricing"
|
|
225
226
|
|
|
226
227
|
Each method accepts optional `options`, `config`, and (for multi-provider methods) `mix` parameters to customize behavior.
|
|
228
|
+
† `grok420()` resolves to `grok-4.20-0309-non-reasoning` by default, or `grok-4.20-0309-reasoning` when `.effort(20+)` / `-1` (or native non-`none` `reasoning_effort`) is set.
|
|
227
229
|
For NVIDIA on DeepSeek V4 Flash/Pro, use `deepseekV4Flash({ mix: { nvidia: true } })` or `deepseekV4Pro({ mix: { nvidia: true } })`.
|
|
228
230
|
For Together on Qwen 3.6 Plus, use `qwen36plus({ mix: { fireworks: false, together: true } })`.
|
|
229
231
|
For OpenRouter on Qwen 3.7 Plus, use `qwen37plus({ mix: { fireworks: false, openrouter: true } })`.
|
package/demo/free.js
CHANGED
package/demo/opus5.js
CHANGED
|
@@ -15,9 +15,9 @@ opus.addText("Explain quantum entanglement in simple terms.");
|
|
|
15
15
|
const response = await opus.message();
|
|
16
16
|
console.log(response);
|
|
17
17
|
|
|
18
|
-
console.log("\n" + '--------|
|
|
18
|
+
console.log("\n" + '--------| effort(100).opus5() |--------');
|
|
19
19
|
|
|
20
|
-
const opusThink = mmix.new().
|
|
20
|
+
const opusThink = mmix.new().effort(100).opus5();
|
|
21
21
|
opusThink.addText("A bat and a ball cost $1.10 in total. The bat costs $1.00 more than the ball. How much does the ball cost?");
|
|
22
22
|
const thinkResponse = await opusThink.raw();
|
|
23
23
|
console.log(thinkResponse);
|
package/demo/short.js
CHANGED
|
@@ -9,9 +9,9 @@ const setup = {
|
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
const mmix = await ModelMix.new(setup)
|
|
12
|
-
.
|
|
13
|
-
.
|
|
14
|
-
.
|
|
12
|
+
.sonnet46() // (main model) Anthropic claude-sonnet-4-6
|
|
13
|
+
.gpt56luna() // (fallback 1) OpenAI gpt-5.6-luna
|
|
14
|
+
.gemini36flash({ config: { temperature: 0 } }) // (fallback 2) Google gemini-3.6-flash
|
|
15
15
|
.gpt41nano() // (fallback 3) OpenAI gpt-4.1-nano
|
|
16
16
|
.grok43() // (fallback 4) Grok grok-4.3
|
|
17
17
|
.addText("What's your name?");
|
package/demo/stream.js
CHANGED
|
@@ -6,7 +6,7 @@ await ModelMix.new().gpt41nano()
|
|
|
6
6
|
.addText('describe')
|
|
7
7
|
.stream((data) => { console.log(data.message); });
|
|
8
8
|
|
|
9
|
-
await ModelMix.new().
|
|
9
|
+
await ModelMix.new().haiku45()
|
|
10
10
|
.addImageFromUrl('https://pbs.twimg.com/media/F6-GsjraAAADDGy?format=jpg')
|
|
11
11
|
.addText('describe')
|
|
12
12
|
.stream((data) => { console.log(data.message); });
|
package/demo/tokens.js
CHANGED
|
@@ -25,7 +25,7 @@ console.log('-'.repeat(60));
|
|
|
25
25
|
|
|
26
26
|
const providers = [
|
|
27
27
|
{ name: 'OpenAI GPT-5-nano', fn: (m) => m.gpt5nano() },
|
|
28
|
-
{ name: 'Anthropic Haiku', fn: (m) => m.
|
|
28
|
+
{ name: 'Anthropic Haiku', fn: (m) => m.haiku45() },
|
|
29
29
|
{ name: 'Google Gemini', fn: (m) => m.gemini25flash() }
|
|
30
30
|
];
|
|
31
31
|
|
package/effort.js
CHANGED
|
@@ -105,6 +105,39 @@ const PROVIDER_FAMILY_BY_CLASS = {
|
|
|
105
105
|
MixCustom: null,
|
|
106
106
|
};
|
|
107
107
|
|
|
108
|
+
/** Logical alias from `.grok420()` — resolved to reasoning / non-reasoning at request time. */
|
|
109
|
+
const GROK420_ALIAS = 'grok-4.20-0309';
|
|
110
|
+
const GROK420_REASONING = 'grok-4.20-0309-reasoning';
|
|
111
|
+
const GROK420_NON_REASONING = 'grok-4.20-0309-non-reasoning';
|
|
112
|
+
|
|
113
|
+
function isGrok420Alias(modelKey) {
|
|
114
|
+
return modelKey === GROK420_ALIAS;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Pick Grok 4.20 concrete model from unified effort (and native reasoning_effort).
|
|
119
|
+
* - no effort / OpenAI band `none` (0–19) / native `none` → non-reasoning
|
|
120
|
+
* - effort -1 or 20–100 / native non-none reasoning_effort → reasoning
|
|
121
|
+
*/
|
|
122
|
+
function resolveGrok420ModelKey(modelKey, effort, options = {}) {
|
|
123
|
+
if (!isGrok420Alias(modelKey)) return modelKey;
|
|
124
|
+
|
|
125
|
+
const native = options.reasoning_effort;
|
|
126
|
+
if (native != null && native !== '') {
|
|
127
|
+
return native === 'none' ? GROK420_NON_REASONING : GROK420_REASONING;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (effort === undefined || effort === null) {
|
|
131
|
+
return GROK420_NON_REASONING;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const normalized = normalizeEffort(effort);
|
|
135
|
+
if (normalized === -1) return GROK420_REASONING;
|
|
136
|
+
|
|
137
|
+
const level = levelFromBands(normalized, OPENAI_BANDS);
|
|
138
|
+
return level === 'none' ? GROK420_NON_REASONING : GROK420_REASONING;
|
|
139
|
+
}
|
|
140
|
+
|
|
108
141
|
function normalizeEffort(value) {
|
|
109
142
|
if (typeof value !== 'number' || !Number.isFinite(value) || !Number.isInteger(value)) {
|
|
110
143
|
throw new Error(`Invalid effort: expected integer -1 or 0..100, got ${JSON.stringify(value)}`);
|
|
@@ -170,6 +203,62 @@ function isMiniMax(modelKey) {
|
|
|
170
203
|
return typeof modelKey === 'string' && modelKey.toLowerCase().includes('minimax');
|
|
171
204
|
}
|
|
172
205
|
|
|
206
|
+
/** Max budget_tokens when mapping unified effort onto manual Anthropic thinking. */
|
|
207
|
+
const ANTHROPIC_MANUAL_BUDGET_MAX = 16384;
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Models that use adaptive thinking + output_config.effort (Claude 5 / Fable /
|
|
211
|
+
* Opus 4.6+ / Sonnet 4.6+). Older ones (Sonnet 4.5, Haiku 4.5, Opus 4.5) use
|
|
212
|
+
* thinking.type=enabled + budget_tokens.
|
|
213
|
+
*/
|
|
214
|
+
function usesAnthropicAdaptiveThinking(modelKey) {
|
|
215
|
+
const id = String(modelKey || '').toLowerCase();
|
|
216
|
+
if (!id) return true;
|
|
217
|
+
if (!id.includes('claude')) return true;
|
|
218
|
+
if (id.includes('fable') || id.includes('mythos')) return true;
|
|
219
|
+
|
|
220
|
+
const opus = id.match(/claude-opus-(\d+)(?:-(\d+))?/);
|
|
221
|
+
if (opus) {
|
|
222
|
+
const major = Number(opus[1]);
|
|
223
|
+
const minor = opus[2] !== undefined ? Number(opus[2]) : 0;
|
|
224
|
+
return major > 4 || (major === 4 && minor >= 6);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const sonnet = id.match(/claude-sonnet-(\d+)(?:-(\d+))?/);
|
|
228
|
+
if (sonnet) {
|
|
229
|
+
const major = Number(sonnet[1]);
|
|
230
|
+
const minor = sonnet[2] !== undefined ? Number(sonnet[2]) : 0;
|
|
231
|
+
return major > 4 || (major === 4 && minor >= 6);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Haiku 4.5 and earlier: manual extended thinking only
|
|
235
|
+
if (id.includes('haiku')) return false;
|
|
236
|
+
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function mapAnthropicManualBudget(normalized) {
|
|
241
|
+
return Math.max(1024, Math.round((normalized / 100) * ANTHROPIC_MANUAL_BUDGET_MAX));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function mapAnthropicEffort(normalized, modelKey) {
|
|
245
|
+
if (!usesAnthropicAdaptiveThinking(modelKey)) {
|
|
246
|
+
return {
|
|
247
|
+
thinking: {
|
|
248
|
+
type: 'enabled',
|
|
249
|
+
budget_tokens: mapAnthropicManualBudget(normalized)
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const desired = levelFromBands(normalized, ANTHROPIC_BANDS);
|
|
255
|
+
const level = pickNearestLevel(desired, ANTHROPIC_LEVELS, ANTHROPIC_LEVELS);
|
|
256
|
+
return {
|
|
257
|
+
thinking: { type: 'adaptive', display: 'summarized' },
|
|
258
|
+
output_config: { effort: level }
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
173
262
|
function mapDeepSeekEffort(normalized) {
|
|
174
263
|
// DeepSeek V4 has no adaptive mode (only enabled/disabled + low|high|max)
|
|
175
264
|
if (normalized === -1) return null;
|
|
@@ -276,15 +365,17 @@ function mapEffort(providerFamily, effort, modelKey) {
|
|
|
276
365
|
if (isMiniMax(modelKey)) {
|
|
277
366
|
return mapMiniMaxEffort(normalized);
|
|
278
367
|
}
|
|
368
|
+
// Non-reasoning Grok 4.20 has no reasoning_effort control
|
|
369
|
+
if (modelKey === GROK420_NON_REASONING) {
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
279
372
|
const desired = levelFromBands(normalized, OPENAI_BANDS);
|
|
280
373
|
const level = pickNearestLevel(desired, OPENAI_LEVELS, supportedOpenAILevels(modelKey));
|
|
281
374
|
return { reasoning_effort: level };
|
|
282
375
|
}
|
|
283
376
|
|
|
284
377
|
if (providerFamily === 'anthropic') {
|
|
285
|
-
|
|
286
|
-
const level = pickNearestLevel(desired, ANTHROPIC_LEVELS, ANTHROPIC_LEVELS);
|
|
287
|
-
return { output_config: { effort: level } };
|
|
378
|
+
return mapAnthropicEffort(normalized, modelKey);
|
|
288
379
|
}
|
|
289
380
|
|
|
290
381
|
if (providerFamily === 'google') {
|
|
@@ -355,8 +446,11 @@ module.exports = {
|
|
|
355
446
|
applyUnifiedEffort,
|
|
356
447
|
hasNativeEffort,
|
|
357
448
|
resolveProviderFamily,
|
|
449
|
+
resolveGrok420ModelKey,
|
|
450
|
+
isGrok420Alias,
|
|
358
451
|
isDeepSeekV4,
|
|
359
452
|
isMiniMax,
|
|
453
|
+
usesAnthropicAdaptiveThinking,
|
|
360
454
|
levelFromBands,
|
|
361
455
|
pickNearestLevel,
|
|
362
456
|
OPENAI_BANDS,
|
|
@@ -367,4 +461,8 @@ module.exports = {
|
|
|
367
461
|
ANTHROPIC_LEVELS,
|
|
368
462
|
GEMINI_LEVELS,
|
|
369
463
|
DEEPSEEK_LEVELS,
|
|
464
|
+
ANTHROPIC_MANUAL_BUDGET_MAX,
|
|
465
|
+
GROK420_ALIAS,
|
|
466
|
+
GROK420_REASONING,
|
|
467
|
+
GROK420_NON_REASONING,
|
|
370
468
|
};
|
package/index.d.ts
CHANGED
|
@@ -286,28 +286,14 @@ export declare class ModelMix {
|
|
|
286
286
|
|
|
287
287
|
// Anthropic
|
|
288
288
|
fable5(args?: ModelAttachArgs): this;
|
|
289
|
-
fable5think(args?: ModelAttachArgs): this;
|
|
290
289
|
opus5(args?: ModelAttachArgs): this;
|
|
291
|
-
opus5think(args?: ModelAttachArgs): this;
|
|
292
|
-
opus48think(args?: ModelAttachArgs): this;
|
|
293
|
-
opus47think(args?: ModelAttachArgs): this;
|
|
294
|
-
opus46think(args?: ModelAttachArgs): this;
|
|
295
290
|
opus48(args?: ModelAttachArgs): this;
|
|
296
291
|
opus47(args?: ModelAttachArgs): this;
|
|
297
292
|
opus46(args?: ModelAttachArgs): this;
|
|
298
|
-
opus41(args?: ModelAttachArgs): this;
|
|
299
|
-
opus41think(args?: ModelAttachArgs): this;
|
|
300
293
|
sonnet5(args?: ModelAttachArgs): this;
|
|
301
|
-
sonnet5think(args?: ModelAttachArgs): this;
|
|
302
|
-
sonnet4(args?: ModelAttachArgs): this;
|
|
303
|
-
sonnet4think(args?: ModelAttachArgs): this;
|
|
304
294
|
sonnet46(args?: ModelAttachArgs): this;
|
|
305
|
-
sonnet46think(args?: ModelAttachArgs): this;
|
|
306
295
|
sonnet45(args?: ModelAttachArgs): this;
|
|
307
|
-
sonnet45think(args?: ModelAttachArgs): this;
|
|
308
|
-
haiku35(args?: ModelAttachArgs): this;
|
|
309
296
|
haiku45(args?: ModelAttachArgs): this;
|
|
310
|
-
haiku45think(args?: ModelAttachArgs): this;
|
|
311
297
|
|
|
312
298
|
// Google
|
|
313
299
|
gemini25flash(args?: ModelAttachArgs): this;
|
|
@@ -327,10 +313,8 @@ export declare class ModelMix {
|
|
|
327
313
|
grok45(args?: ModelAttachArgs): this;
|
|
328
314
|
grok43(args?: ModelAttachArgs): this;
|
|
329
315
|
grok420multiAgent(args?: ModelAttachArgs): this;
|
|
330
|
-
|
|
316
|
+
/** Non-reasoning by default; with `.effort(20+)` / `-1` uses the reasoning model at request time. */
|
|
331
317
|
grok420(args?: ModelAttachArgs): this;
|
|
332
|
-
grok41think(args?: ModelAttachArgs): this;
|
|
333
|
-
grok41(args?: ModelAttachArgs): this;
|
|
334
318
|
|
|
335
319
|
// Multi-provider
|
|
336
320
|
qwen3(args?: ModelAttachArgs): this;
|
|
@@ -338,10 +322,10 @@ export declare class ModelMix {
|
|
|
338
322
|
qwen37plus(args?: ModelAttachArgs): this;
|
|
339
323
|
qwen38max(args?: ModelAttachArgs): this;
|
|
340
324
|
hermes3(args?: ModelAttachArgs): this;
|
|
341
|
-
|
|
325
|
+
kimiK26(args?: ModelAttachArgs): this;
|
|
342
326
|
kimiK27Code(args?: ModelAttachArgs): this;
|
|
343
327
|
kimiK3(args?: ModelAttachArgs): this;
|
|
344
|
-
|
|
328
|
+
kimiK25(args?: ModelAttachArgs): this;
|
|
345
329
|
lmstudio(model?: string, args?: ModelAttachArgs): this;
|
|
346
330
|
minimaxM25(args?: ModelAttachArgs): this;
|
|
347
331
|
minimaxM27(args?: ModelAttachArgs): this;
|
|
@@ -352,7 +336,6 @@ export declare class ModelMix {
|
|
|
352
336
|
deepseekV4Flash(args?: ModelAttachArgs): this;
|
|
353
337
|
GLM51(args?: ModelAttachArgs): this;
|
|
354
338
|
GLM52(args?: ModelAttachArgs): this;
|
|
355
|
-
GLM5(args?: ModelAttachArgs): this;
|
|
356
339
|
|
|
357
340
|
addText(text: string, options?: RoleOptions): this;
|
|
358
341
|
addTextFromFile(filePath: string, options?: RoleOptions): this;
|
package/index.js
CHANGED
|
@@ -23,7 +23,8 @@ const {
|
|
|
23
23
|
const {
|
|
24
24
|
normalizeEffort,
|
|
25
25
|
applyUnifiedEffort,
|
|
26
|
-
resolveProviderFamily
|
|
26
|
+
resolveProviderFamily,
|
|
27
|
+
resolveGrok420ModelKey
|
|
27
28
|
} = require('./effort');
|
|
28
29
|
|
|
29
30
|
const DEFAULT_RETRYABLE_STATUS_CODES = [408, 425, 429, 500, 502, 503, 504, 529];
|
|
@@ -72,11 +73,8 @@ const MODEL_PRICING = {
|
|
|
72
73
|
'claude-opus-4-8': [5.00, 25.00],
|
|
73
74
|
'claude-opus-4-7': [5.00, 25.00],
|
|
74
75
|
'claude-opus-4-6': [5.00, 25.00],
|
|
75
|
-
'claude-opus-4-1-20250805': [15.00, 75.00],
|
|
76
76
|
'claude-sonnet-4-6': [3.00, 15.00],
|
|
77
77
|
'claude-sonnet-4-5-20250929': [3.00, 15.00],
|
|
78
|
-
'claude-sonnet-4-20250514': [3.00, 15.00],
|
|
79
|
-
'claude-3-5-haiku-20241022': [0.80, 4.00],
|
|
80
78
|
'claude-haiku-4-5-20251001': [1.00, 5.00],
|
|
81
79
|
// Google
|
|
82
80
|
'gemini-3.1-pro-preview':[2.00, 12.00],
|
|
@@ -91,10 +89,9 @@ const MODEL_PRICING = {
|
|
|
91
89
|
'grok-4.5': [2.00, 6.00],
|
|
92
90
|
'grok-4.3': [1.25, 2.50],
|
|
93
91
|
'grok-4.20-multi-agent-0309': [1.25, 2.50],
|
|
92
|
+
'grok-4.20-0309': [1.25, 2.50],
|
|
94
93
|
'grok-4.20-0309-reasoning': [1.25, 2.50],
|
|
95
94
|
'grok-4.20-0309-non-reasoning': [1.25, 2.50],
|
|
96
|
-
'grok-4-1-fast-reasoning': [0.20, 0.50],
|
|
97
|
-
'grok-4-1-fast-non-reasoning': [0.20, 0.50],
|
|
98
95
|
// Fireworks
|
|
99
96
|
'accounts/fireworks/models/deepseek-v4-flash': [0.14, 0.28],
|
|
100
97
|
'accounts/fireworks/models/deepseek-v4-pro': [1.74, 3.48],
|
|
@@ -110,12 +107,10 @@ const MODEL_PRICING = {
|
|
|
110
107
|
'accounts/fireworks/models/qwen3p7-plus': [0.40, 1.60],
|
|
111
108
|
'qwen/qwen3.7-plus': [0.32, 1.28],
|
|
112
109
|
'qwen/qwen3.8-max': [2.00, 6.00],
|
|
113
|
-
'fireworks/glm-5': [1.00, 3.20],
|
|
114
110
|
// MiniMax
|
|
115
111
|
'MiniMax-M2.5': [0.30, 1.20],
|
|
116
112
|
'MiniMax-M2.7': [0.30, 1.20],
|
|
117
113
|
'MiniMax-M3': [0.30, 1.20],
|
|
118
|
-
'fireworks/minimax-m2p5': [0.30, 1.20],
|
|
119
114
|
'minimax/minimax-m2.7': [0.30, 1.20],
|
|
120
115
|
'minimax/minimax-m3': [0.30, 1.20],
|
|
121
116
|
'MiniMaxAI/MiniMax-M3': [0.30, 1.20],
|
|
@@ -392,29 +387,9 @@ class ModelMix {
|
|
|
392
387
|
fable5({ options = {}, config = {} } = {}) {
|
|
393
388
|
return this.attach('claude-fable-5', new MixAnthropic({ options, config }));
|
|
394
389
|
}
|
|
395
|
-
fable5think({ options = {}, config = {} } = {}) {
|
|
396
|
-
options = { ...MixAnthropic.maxEffortThinkingOptions, ...options };
|
|
397
|
-
return this.attach('claude-fable-5', new MixAnthropic({ options, config }));
|
|
398
|
-
}
|
|
399
390
|
opus5({ options = {}, config = {} } = {}) {
|
|
400
391
|
return this.attach('claude-opus-5', new MixAnthropic({ options, config }));
|
|
401
392
|
}
|
|
402
|
-
opus5think({ options = {}, config = {} } = {}) {
|
|
403
|
-
options = { ...MixAnthropic.maxEffortThinkingOptions, ...options };
|
|
404
|
-
return this.attach('claude-opus-5', new MixAnthropic({ options, config }));
|
|
405
|
-
}
|
|
406
|
-
opus48think({ options = {}, config = {} } = {}) {
|
|
407
|
-
options = { ...MixAnthropic.thinkingOptions, ...options };
|
|
408
|
-
return this.attach('claude-opus-4-8', new MixAnthropic({ options, config }));
|
|
409
|
-
}
|
|
410
|
-
opus47think({ options = {}, config = {} } = {}) {
|
|
411
|
-
options = { ...MixAnthropic.thinkingOptions, ...options };
|
|
412
|
-
return this.attach('claude-opus-4-7', new MixAnthropic({ options, config }));
|
|
413
|
-
}
|
|
414
|
-
opus46think({ options = {}, config = {} } = {}) {
|
|
415
|
-
options = { ...MixAnthropic.thinkingOptions, ...options };
|
|
416
|
-
return this.attach('claude-opus-4-6', new MixAnthropic({ options, config }));
|
|
417
|
-
}
|
|
418
393
|
opus48({ options = {}, config = {} } = {}) {
|
|
419
394
|
return this.attach('claude-opus-4-8', new MixAnthropic({ options, config }));
|
|
420
395
|
}
|
|
@@ -424,52 +399,18 @@ class ModelMix {
|
|
|
424
399
|
opus46({ options = {}, config = {} } = {}) {
|
|
425
400
|
return this.attach('claude-opus-4-6', new MixAnthropic({ options, config }));
|
|
426
401
|
}
|
|
427
|
-
opus41({ options = {}, config = {} } = {}) {
|
|
428
|
-
return this.attach('claude-opus-4-1-20250805', new MixAnthropic({ options, config }));
|
|
429
|
-
}
|
|
430
|
-
opus41think({ options = {}, config = {} } = {}) {
|
|
431
|
-
options = { ...MixAnthropic.thinkingOptions, ...options };
|
|
432
|
-
return this.attach('claude-opus-4-1-20250805', new MixAnthropic({ options, config }));
|
|
433
|
-
}
|
|
434
402
|
sonnet5({ options = {}, config = {} } = {}) {
|
|
435
403
|
return this.attach('claude-sonnet-5', new MixAnthropic({ options, config }));
|
|
436
404
|
}
|
|
437
|
-
sonnet5think({ options = {}, config = {} } = {}) {
|
|
438
|
-
options = { ...MixAnthropic.thinkingOptions, ...options };
|
|
439
|
-
return this.attach('claude-sonnet-5', new MixAnthropic({ options, config }));
|
|
440
|
-
}
|
|
441
|
-
sonnet4({ options = {}, config = {} } = {}) {
|
|
442
|
-
return this.attach('claude-sonnet-4-20250514', new MixAnthropic({ options, config }));
|
|
443
|
-
}
|
|
444
|
-
sonnet4think({ options = {}, config = {} } = {}) {
|
|
445
|
-
options = { ...MixAnthropic.thinkingOptions, ...options };
|
|
446
|
-
return this.attach('claude-sonnet-4-20250514', new MixAnthropic({ options, config }));
|
|
447
|
-
}
|
|
448
405
|
sonnet46({ options = {}, config = {} } = {}) {
|
|
449
406
|
return this.attach('claude-sonnet-4-6', new MixAnthropic({ options, config }));
|
|
450
407
|
}
|
|
451
|
-
sonnet46think({ options = {}, config = {} } = {}) {
|
|
452
|
-
options = { ...MixAnthropic.thinkingOptions, ...options };
|
|
453
|
-
return this.attach('claude-sonnet-4-6', new MixAnthropic({ options, config }));
|
|
454
|
-
}
|
|
455
|
-
|
|
456
408
|
sonnet45({ options = {}, config = {} } = {}) {
|
|
457
409
|
return this.attach('claude-sonnet-4-5-20250929', new MixAnthropic({ options, config }));
|
|
458
410
|
}
|
|
459
|
-
sonnet45think({ options = {}, config = {} } = {}) {
|
|
460
|
-
options = { ...MixAnthropic.thinkingOptions, ...options };
|
|
461
|
-
return this.attach('claude-sonnet-4-5-20250929', new MixAnthropic({ options, config }));
|
|
462
|
-
}
|
|
463
|
-
haiku35({ options = {}, config = {} } = {}) {
|
|
464
|
-
return this.attach('claude-3-5-haiku-20241022', new MixAnthropic({ options, config }));
|
|
465
|
-
}
|
|
466
411
|
haiku45({ options = {}, config = {} } = {}) {
|
|
467
412
|
return this.attach('claude-haiku-4-5-20251001', new MixAnthropic({ options, config }));
|
|
468
413
|
}
|
|
469
|
-
haiku45think({ options = {}, config = {} } = {}) {
|
|
470
|
-
options = { ...MixAnthropic.thinkingOptions, ...options };
|
|
471
|
-
return this.attach('claude-haiku-4-5-20251001', new MixAnthropic({ options, config }));
|
|
472
|
-
}
|
|
473
414
|
gemini25flash({ options = {}, config = {} } = {}) {
|
|
474
415
|
return this.attach('gemini-2.5-flash', new MixGoogle({ options, config }));
|
|
475
416
|
}
|
|
@@ -510,17 +451,9 @@ class ModelMix {
|
|
|
510
451
|
grok420multiAgent({ options = {}, config = {} } = {}) {
|
|
511
452
|
return this.attach('grok-4.20-multi-agent-0309', new MixGrok({ options, config }));
|
|
512
453
|
}
|
|
513
|
-
|
|
514
|
-
return this.attach('grok-4.20-0309-reasoning', new MixGrok({ options, config }));
|
|
515
|
-
}
|
|
454
|
+
/** Non-reasoning by default; with `.effort(20+)` / `-1` resolves to the reasoning model at request time. */
|
|
516
455
|
grok420({ options = {}, config = {} } = {}) {
|
|
517
|
-
return this.attach('grok-4.20-0309
|
|
518
|
-
}
|
|
519
|
-
grok41think({ options = {}, config = {} } = {}) {
|
|
520
|
-
return this.attach('grok-4-1-fast-reasoning', new MixGrok({ options, config }));
|
|
521
|
-
}
|
|
522
|
-
grok41({ options = {}, config = {} } = {}) {
|
|
523
|
-
return this.attach('grok-4-1-fast-non-reasoning', new MixGrok({ options, config }));
|
|
456
|
+
return this.attach('grok-4.20-0309', new MixGrok({ options, config }));
|
|
524
457
|
}
|
|
525
458
|
|
|
526
459
|
qwen3({ options = {}, config = {}, mix = { together: true, cerebras: false } } = {}) {
|
|
@@ -556,7 +489,7 @@ class ModelMix {
|
|
|
556
489
|
return this;
|
|
557
490
|
}
|
|
558
491
|
|
|
559
|
-
|
|
492
|
+
kimiK26({ options = {}, config = {}, mix = { fireworks: true } } = {}) {
|
|
560
493
|
mix = { ...this.mix, ...mix };
|
|
561
494
|
if (mix.fireworks) this.attach('accounts/fireworks/models/kimi-k2p6', new MixFireworks({ options, config }));
|
|
562
495
|
if (mix.openrouter) this.attach('moonshotai/kimi-k2.6', new MixOpenRouter({ options, config }));
|
|
@@ -577,7 +510,7 @@ class ModelMix {
|
|
|
577
510
|
return this;
|
|
578
511
|
}
|
|
579
512
|
|
|
580
|
-
|
|
513
|
+
kimiK25({ options = {}, config = {}, mix = { together: true } } = {}) {
|
|
581
514
|
mix = { ...this.mix, ...mix };
|
|
582
515
|
if (mix.together) this.attach('moonshotai/Kimi-K2.5', new MixTogether({ options, config }));
|
|
583
516
|
if (mix.fireworks) this.attach('accounts/fireworks/models/kimi-k2p5', new MixFireworks({ options, config }));
|
|
@@ -593,7 +526,6 @@ class ModelMix {
|
|
|
593
526
|
minimaxM25({ options = {}, config = {}, mix = { minimax: true } } = {}) {
|
|
594
527
|
mix = { ...this.mix, ...mix };
|
|
595
528
|
if (mix.minimax) this.attach('MiniMax-M2.5', new MixMiniMax({ options, config }));
|
|
596
|
-
if (mix.fireworks) this.attach('fireworks/minimax-m2p5', new MixFireworks({ options, config }));
|
|
597
529
|
return this;
|
|
598
530
|
}
|
|
599
531
|
|
|
@@ -661,12 +593,6 @@ class ModelMix {
|
|
|
661
593
|
return this;
|
|
662
594
|
}
|
|
663
595
|
|
|
664
|
-
GLM5({ options = {}, config = {}, mix = { fireworks: true } } = {}) {
|
|
665
|
-
mix = { ...this.mix, ...mix };
|
|
666
|
-
if (mix.fireworks) this.attach('fireworks/glm-5', new MixFireworks({ options, config }));
|
|
667
|
-
return this;
|
|
668
|
-
}
|
|
669
|
-
|
|
670
596
|
addText(text, { role = "user" } = {}) {
|
|
671
597
|
const content = [{
|
|
672
598
|
type: "text",
|
|
@@ -1007,7 +933,7 @@ class ModelMix {
|
|
|
1007
933
|
|
|
1008
934
|
async execute({ config = {}, options = {} } = {}) {
|
|
1009
935
|
if (!this.models || this.models.length === 0) {
|
|
1010
|
-
throw new Error("No models specified. Use methods like .gpt5(), .
|
|
936
|
+
throw new Error("No models specified. Use methods like .gpt5(), .sonnet46() first.");
|
|
1011
937
|
}
|
|
1012
938
|
|
|
1013
939
|
return this.limiter.schedule(async () => {
|
|
@@ -1065,9 +991,17 @@ class ModelMix {
|
|
|
1065
991
|
}
|
|
1066
992
|
};
|
|
1067
993
|
|
|
994
|
+
// Grok 4.20 alias → reasoning / non-reasoning from unified effort
|
|
995
|
+
const resolvedModelKey = resolveGrok420ModelKey(
|
|
996
|
+
currentModelKey,
|
|
997
|
+
currentConfig.effort,
|
|
998
|
+
currentOptions
|
|
999
|
+
);
|
|
1000
|
+
currentOptions.model = resolvedModelKey;
|
|
1001
|
+
|
|
1068
1002
|
// Unified effort → native provider fields (skipped if native already set)
|
|
1069
1003
|
const providerFamily = resolveProviderFamily(providerInstance);
|
|
1070
|
-
applyUnifiedEffort(currentOptions, currentConfig, providerFamily,
|
|
1004
|
+
applyUnifiedEffort(currentOptions, currentConfig, providerFamily, resolvedModelKey);
|
|
1071
1005
|
|
|
1072
1006
|
if (currentConfig.debug >= 1) {
|
|
1073
1007
|
const isPrimary = i === 0;
|
|
@@ -1077,7 +1011,7 @@ class ModelMix {
|
|
|
1077
1011
|
: ' (fallback)';
|
|
1078
1012
|
// Extract provider name from class name (e.g., "MixOpenRouter" -> "openrouter")
|
|
1079
1013
|
const providerName = providerInstance.constructor.name.replace(/^Mix/, '').toLowerCase();
|
|
1080
|
-
const header = `\n${prefix} [${providerName}:${
|
|
1014
|
+
const header = `\n${prefix} [${providerName}:${resolvedModelKey}] #${originalIndex + 1}${suffix}`;
|
|
1081
1015
|
|
|
1082
1016
|
if (currentConfig.debug >= 2) {
|
|
1083
1017
|
console.log(`${header}\n${ModelMix.formatInputSummary(this.messages, currentConfig.system, currentConfig.debug)}`);
|
|
@@ -1122,7 +1056,7 @@ class ModelMix {
|
|
|
1122
1056
|
if (currentConfig.debug >= 1) {
|
|
1123
1057
|
const nextAttempt = attempt + 2;
|
|
1124
1058
|
const totalAttempts = retries + 1;
|
|
1125
|
-
console.log(`↺ Retrying [${
|
|
1059
|
+
console.log(`↺ Retrying [${resolvedModelKey}] due to status ${statusCode} (${nextAttempt}/${totalAttempts})`);
|
|
1126
1060
|
}
|
|
1127
1061
|
|
|
1128
1062
|
const delay = Math.min(baseDelayMs * Math.pow(2, attempt), maxDelayMs);
|
|
@@ -1134,7 +1068,7 @@ class ModelMix {
|
|
|
1134
1068
|
const elapsedMs = Date.now() - startTime;
|
|
1135
1069
|
|
|
1136
1070
|
if (result.tokens) {
|
|
1137
|
-
result.tokens.cost = ModelMix.calculateCost(
|
|
1071
|
+
result.tokens.cost = ModelMix.calculateCost(resolvedModelKey, result.tokens);
|
|
1138
1072
|
const elapsedSec = elapsedMs / 1000;
|
|
1139
1073
|
result.tokens.speed = elapsedSec > 0 ? Math.round(result.tokens.output / elapsedSec) : 0;
|
|
1140
1074
|
}
|
|
@@ -2217,19 +2151,6 @@ class MixKimi extends MixOpenAI {
|
|
|
2217
2151
|
|
|
2218
2152
|
class MixAnthropic extends MixCustom {
|
|
2219
2153
|
|
|
2220
|
-
static thinkingOptions = {
|
|
2221
|
-
thinking: {
|
|
2222
|
-
"type": "enabled",
|
|
2223
|
-
"budget_tokens": 1638
|
|
2224
|
-
},
|
|
2225
|
-
temperature: 1
|
|
2226
|
-
};
|
|
2227
|
-
|
|
2228
|
-
static maxEffortThinkingOptions = {
|
|
2229
|
-
output_config: { effort: 'max' },
|
|
2230
|
-
thinking: { display: 'summarized' }
|
|
2231
|
-
};
|
|
2232
|
-
|
|
2233
2154
|
/**
|
|
2234
2155
|
* Opus 4.7+ and Claude 5 family reject sampling params (temperature/top_p/top_k).
|
|
2235
2156
|
* See: https://platform.claude.com/docs/en/about-claude/models/migration-guide
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "modelmix",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.7.2",
|
|
4
4
|
"description": "🧬 Reliable interface with automatic fallback for AI LLMs.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -72,6 +72,6 @@
|
|
|
72
72
|
"test:live": "mocha test/live.test.js --timeout 10000 --require test/setup.js",
|
|
73
73
|
"test:live.mcp": "mocha test/live.mcp.js --timeout 60000 --require test/setup.js",
|
|
74
74
|
"test:tokens": "mocha test/tokens.test.js --timeout 10000 --require test/setup.js",
|
|
75
|
-
"test:offline": "mocha test/json.test.js test/fallback.test.js test/templates.test.js test/images.test.js test/bottleneck.test.js test/tokens.test.js test/history.test.js test/anthropic.test.js test/effort.test.js --timeout 10000 --require test/setup.js"
|
|
75
|
+
"test:offline": "mocha test/json.test.js test/fallback.test.js test/templates.test.js test/images.test.js test/bottleneck.test.js test/tokens.test.js test/history.test.js test/anthropic.test.js test/effort.test.js test/grok.test.js --timeout 10000 --require test/setup.js"
|
|
76
76
|
}
|
|
77
77
|
}
|
package/skills/modelmix/SKILL.md
CHANGED
|
@@ -114,7 +114,7 @@ ModelMix.new({ config: { effort: 80 } })
|
|
|
114
114
|
| DeepSeek V4 | off | `low`↑ | `high`↑ | `high`↑ | `max`↑ | — |
|
|
115
115
|
| MiniMax M3 | off | adaptive | adaptive | adaptive | adaptive | adaptive |
|
|
116
116
|
|
|
117
|
-
\* Gemini bands: 0–24 / 25–49 / 50–74 / 75–100. DeepSeek `↑` = thinking on; `off` = thinking disabled. MiniMax `off`/`adaptive` = `thinking.disabled` / `thinking.type=adaptive`. Gemini 2.5 maps 0–100 to `thinkingBudget`. `-1` = adaptive/dynamic when available, else no-op. Levels clamp per model. `*think()`
|
|
117
|
+
\* Gemini bands: 0–24 / 25–49 / 50–74 / 75–100. 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`. `-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.
|
|
118
118
|
|
|
119
119
|
## Available Model Shorthands
|
|
120
120
|
|
|
@@ -122,21 +122,21 @@ ModelMix.new({ config: { effort: 80 } })
|
|
|
122
122
|
`gpt52()` `gpt52chat()` `gpt51()` `gpt5()` `gpt5mini()` `gpt5nano()` `gpt45()` `gpt41()` `gpt41mini()` `gpt41nano()` `o3()` `o4mini()`
|
|
123
123
|
|
|
124
124
|
### Anthropic
|
|
125
|
-
`fable5()` `opus5()` `opus48()` `opus47()` `opus46()` `
|
|
125
|
+
`fable5()` `opus5()` `opus48()` `opus47()` `opus46()` `sonnet5()` `sonnet46()` `sonnet45()` `haiku45()`
|
|
126
126
|
|
|
127
|
-
|
|
127
|
+
Use `.effort(n)` (or `config.effort`) to enable Anthropic thinking — e.g. `.effort(100).opus5()`.
|
|
128
128
|
|
|
129
129
|
### Google
|
|
130
130
|
`gemini3pro()` `gemini3flash()` `gemini36flash()` `gemini35flash()` `gemini25pro()` `gemini25flash()`
|
|
131
131
|
|
|
132
132
|
### Grok
|
|
133
|
-
`grok45()` `grok43()` `grok420multiAgent()` `grok420()`
|
|
133
|
+
`grok45()` `grok43()` `grok420multiAgent()` `grok420()`
|
|
134
134
|
|
|
135
135
|
### Perplexity
|
|
136
136
|
`sonar()` `sonarPro()`
|
|
137
137
|
|
|
138
138
|
### Together
|
|
139
|
-
`qwen36plus()` `GLM52()` `
|
|
139
|
+
`qwen36plus()` `GLM52()` `kimiK25()` `kimiK26()` `gptOss()`
|
|
140
140
|
|
|
141
141
|
### Moonshot
|
|
142
142
|
`kimiK3()` — requires `MOONSHOT_API_KEY`; use `{ mix: { moonshot: false, openrouter: true } }` for OpenRouter.
|
|
@@ -145,7 +145,7 @@ Thinking variants: append `think` — e.g. `fable5think()` `opus5think()` `opus4
|
|
|
145
145
|
`minimaxM25()` `minimaxM27()` `minimaxM3()`
|
|
146
146
|
|
|
147
147
|
### Fireworks
|
|
148
|
-
`qwen36plus()` `qwen37plus()` `deepseekV4Flash()` `deepseekV4Pro()` `
|
|
148
|
+
`qwen36plus()` `qwen37plus()` `deepseekV4Flash()` `deepseekV4Pro()` `kimiK26()`
|
|
149
149
|
|
|
150
150
|
### Cerebras
|
|
151
151
|
`GLM46()`
|
|
@@ -154,7 +154,7 @@ Thinking variants: append `think` — e.g. `fable5think()` `opus5think()` `opus4
|
|
|
154
154
|
`qwen38max()` `GLM45()`
|
|
155
155
|
|
|
156
156
|
### Multi-provider (auto-fallback across free/paid tiers)
|
|
157
|
-
`hermes3()` `
|
|
157
|
+
`hermes3()` `kimiK25()`
|
|
158
158
|
|
|
159
159
|
### Local
|
|
160
160
|
`lmstudio()` — for LM Studio local models
|
|
@@ -286,7 +286,8 @@ const code = await ModelMix.new()
|
|
|
286
286
|
|
|
287
287
|
```javascript
|
|
288
288
|
const raw = await ModelMix.new()
|
|
289
|
-
.
|
|
289
|
+
.effort(100)
|
|
290
|
+
.sonnet45()
|
|
290
291
|
.addText("Solve this step by step: 2+2*3")
|
|
291
292
|
.raw();
|
|
292
293
|
// raw.message, raw.think, raw.tokens, raw.toolCalls, raw.response
|
|
@@ -428,7 +429,7 @@ For full debug output, also set: `DEBUG=ModelMix* node script.js`
|
|
|
428
429
|
```javascript
|
|
429
430
|
const model = ModelMix.new()
|
|
430
431
|
.gptOss()
|
|
431
|
-
.
|
|
432
|
+
.kimiK25()
|
|
432
433
|
.hermes3()
|
|
433
434
|
.addText("What is the capital of France?");
|
|
434
435
|
console.log(await model.message());
|
|
@@ -451,7 +452,7 @@ const model = ModelMix.new({
|
|
|
451
452
|
minimax: false, // default: false
|
|
452
453
|
fireworks: false // default: false
|
|
453
454
|
}
|
|
454
|
-
}).
|
|
455
|
+
}).kimiK25();
|
|
455
456
|
```
|
|
456
457
|
|
|
457
458
|
## Agent Usage Rules
|
|
@@ -463,8 +464,7 @@ const model = ModelMix.new({
|
|
|
463
464
|
- When using MCP tools or `addTool()`, set `max_history` to at least 3 — tool call/response pairs consume history slots.
|
|
464
465
|
- Use `.json()` for structured output instead of parsing text manually. Use descriptor objects `{ description, required, enum, default, nullable }` for richer schema control.
|
|
465
466
|
- Use `.message()` for simple text, `.raw()` when you need tokens/thinking/toolCalls.
|
|
466
|
-
- For thinking
|
|
467
|
-
- For cross-provider reasoning intensity, use unified `effort` (`-1` or `0`–`100`) via `config.effort` or `.effort(n)` — never put it in `options`. Native fields win if already set.
|
|
467
|
+
- For Anthropic thinking, use unified `effort` (`-1` or `0`–`100`) via `config.effort` or `.effort(n)` — e.g. `.effort(100).opus5()`. Never put `effort` in `options`. Native fields win if already set.
|
|
468
468
|
- Template placeholders use `{key}` syntax in both system prompts and user messages.
|
|
469
469
|
- The library uses CommonJS internally but supports ESM import via `{ ModelMix }`.
|
|
470
470
|
- GPT-5+ models automatically use `max_completion_tokens` instead of `max_tokens`.
|
package/test/anthropic.test.js
CHANGED
|
@@ -12,15 +12,15 @@ describe('Anthropic Model Registration Tests', () => {
|
|
|
12
12
|
expect(model.models[0].provider).to.be.instanceOf(MixAnthropic);
|
|
13
13
|
});
|
|
14
14
|
|
|
15
|
-
it('should
|
|
16
|
-
const model = ModelMix.new();
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
expect(model.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
expect(
|
|
23
|
-
expect(
|
|
15
|
+
it('should apply max effort thinking via .effort(100).fable5()', () => {
|
|
16
|
+
const model = ModelMix.new().effort(100).fable5();
|
|
17
|
+
const { applyUnifiedEffort } = require('../effort.js');
|
|
18
|
+
|
|
19
|
+
expect(model.config.effort).to.equal(100);
|
|
20
|
+
const options = { model: 'claude-fable-5' };
|
|
21
|
+
applyUnifiedEffort(options, model.config, 'anthropic', 'claude-fable-5');
|
|
22
|
+
expect(options.output_config).to.deep.equal({ effort: 'max' });
|
|
23
|
+
expect(options.thinking).to.deep.equal({ type: 'adaptive', display: 'summarized' });
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
it('should register Claude Opus 5', () => {
|
|
@@ -32,15 +32,15 @@ describe('Anthropic Model Registration Tests', () => {
|
|
|
32
32
|
expect(model.models[0].provider).to.be.instanceOf(MixAnthropic);
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
-
it('should
|
|
36
|
-
const model = ModelMix.new();
|
|
37
|
-
|
|
35
|
+
it('should apply max effort thinking via .effort(100).opus5()', () => {
|
|
36
|
+
const model = ModelMix.new().effort(100).opus5();
|
|
37
|
+
const { applyUnifiedEffort } = require('../effort.js');
|
|
38
38
|
|
|
39
|
-
expect(model.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
expect(
|
|
43
|
-
expect(
|
|
39
|
+
expect(model.config.effort).to.equal(100);
|
|
40
|
+
const options = { model: 'claude-opus-5' };
|
|
41
|
+
applyUnifiedEffort(options, model.config, 'anthropic', 'claude-opus-5');
|
|
42
|
+
expect(options.output_config).to.deep.equal({ effort: 'max' });
|
|
43
|
+
expect(options.thinking).to.deep.equal({ type: 'adaptive', display: 'summarized' });
|
|
44
44
|
});
|
|
45
45
|
|
|
46
46
|
describe('Sampling params (temperature/top_p/top_k)', () => {
|
|
@@ -53,7 +53,6 @@ describe('Anthropic Model Registration Tests', () => {
|
|
|
53
53
|
expect(MixAnthropic.rejectsSamplingParams('anthropic/claude-opus-5')).to.equal(true);
|
|
54
54
|
|
|
55
55
|
expect(MixAnthropic.rejectsSamplingParams('claude-opus-4-6')).to.equal(false);
|
|
56
|
-
expect(MixAnthropic.rejectsSamplingParams('claude-opus-4-1-20250805')).to.equal(false);
|
|
57
56
|
expect(MixAnthropic.rejectsSamplingParams('claude-sonnet-4-6')).to.equal(false);
|
|
58
57
|
expect(MixAnthropic.rejectsSamplingParams('claude-haiku-4-5-20251001')).to.equal(false);
|
|
59
58
|
});
|
|
@@ -143,16 +142,14 @@ describe('Anthropic Model Registration Tests', () => {
|
|
|
143
142
|
expect(model.models[0].provider).to.be.instanceOf(MixAnthropic);
|
|
144
143
|
});
|
|
145
144
|
|
|
146
|
-
it('should
|
|
147
|
-
const model = ModelMix.new();
|
|
148
|
-
|
|
145
|
+
it('should apply adaptive thinking via .effort(100).opus48()', () => {
|
|
146
|
+
const model = ModelMix.new().effort(100).opus48();
|
|
147
|
+
const { applyUnifiedEffort } = require('../effort.js');
|
|
149
148
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
expect(
|
|
153
|
-
|
|
154
|
-
budget_tokens: 1638
|
|
155
|
-
});
|
|
149
|
+
const options = { model: 'claude-opus-4-8' };
|
|
150
|
+
applyUnifiedEffort(options, model.config, 'anthropic', 'claude-opus-4-8');
|
|
151
|
+
expect(options.thinking).to.deep.equal({ type: 'adaptive', display: 'summarized' });
|
|
152
|
+
expect(options.output_config).to.deep.equal({ effort: 'max' });
|
|
156
153
|
});
|
|
157
154
|
|
|
158
155
|
it('should register Claude Sonnet 5', () => {
|
|
@@ -164,16 +161,14 @@ describe('Anthropic Model Registration Tests', () => {
|
|
|
164
161
|
expect(model.models[0].provider).to.be.instanceOf(MixAnthropic);
|
|
165
162
|
});
|
|
166
163
|
|
|
167
|
-
it('should
|
|
168
|
-
const model = ModelMix.new();
|
|
169
|
-
|
|
164
|
+
it('should apply adaptive thinking via .effort(100).sonnet5()', () => {
|
|
165
|
+
const model = ModelMix.new().effort(100).sonnet5();
|
|
166
|
+
const { applyUnifiedEffort } = require('../effort.js');
|
|
170
167
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
expect(
|
|
174
|
-
|
|
175
|
-
budget_tokens: 1638
|
|
176
|
-
});
|
|
168
|
+
const options = { model: 'claude-sonnet-5' };
|
|
169
|
+
applyUnifiedEffort(options, model.config, 'anthropic', 'claude-sonnet-5');
|
|
170
|
+
expect(options.thinking).to.deep.equal({ type: 'adaptive', display: 'summarized' });
|
|
171
|
+
expect(options.output_config).to.deep.equal({ effort: 'max' });
|
|
177
172
|
});
|
|
178
173
|
|
|
179
174
|
describe('Thinking block extraction', () => {
|
package/test/effort.test.js
CHANGED
|
@@ -81,9 +81,31 @@ describe('Unified effort scale', () => {
|
|
|
81
81
|
expect(mapEffort('openai', 10, 'gpt-oss-120b')).to.deep.equal({ reasoning_effort: 'low' });
|
|
82
82
|
});
|
|
83
83
|
|
|
84
|
-
it('maps Anthropic
|
|
85
|
-
expect(mapEffort('anthropic', 10)).to.deep.equal({
|
|
86
|
-
|
|
84
|
+
it('maps Anthropic adaptive models to thinking + output_config.effort', () => {
|
|
85
|
+
expect(mapEffort('anthropic', 10, 'claude-opus-5')).to.deep.equal({
|
|
86
|
+
thinking: { type: 'adaptive', display: 'summarized' },
|
|
87
|
+
output_config: { effort: 'low' }
|
|
88
|
+
});
|
|
89
|
+
expect(mapEffort('anthropic', 90, 'claude-fable-5')).to.deep.equal({
|
|
90
|
+
thinking: { type: 'adaptive', display: 'summarized' },
|
|
91
|
+
output_config: { effort: 'max' }
|
|
92
|
+
});
|
|
93
|
+
expect(mapEffort('anthropic', 50, 'claude-sonnet-4-6')).to.deep.equal({
|
|
94
|
+
thinking: { type: 'adaptive', display: 'summarized' },
|
|
95
|
+
output_config: { effort: 'high' }
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('maps Anthropic manual models to thinking.type=enabled + budget_tokens', () => {
|
|
100
|
+
expect(mapEffort('anthropic', 50, 'claude-sonnet-4-5-20250929')).to.deep.equal({
|
|
101
|
+
thinking: { type: 'enabled', budget_tokens: 8192 }
|
|
102
|
+
});
|
|
103
|
+
expect(mapEffort('anthropic', 100, 'claude-haiku-4-5-20251001')).to.deep.equal({
|
|
104
|
+
thinking: { type: 'enabled', budget_tokens: 16384 }
|
|
105
|
+
});
|
|
106
|
+
expect(mapEffort('anthropic', 0, 'claude-sonnet-4-5-20250929')).to.deep.equal({
|
|
107
|
+
thinking: { type: 'enabled', budget_tokens: 1024 }
|
|
108
|
+
});
|
|
87
109
|
});
|
|
88
110
|
|
|
89
111
|
it('maps Anthropic adaptive to thinking.type=adaptive', () => {
|
|
@@ -223,6 +245,14 @@ describe('Unified effort scale', () => {
|
|
|
223
245
|
const options = {};
|
|
224
246
|
applyUnifiedEffort(options, { effort: 90 }, 'anthropic', 'claude-opus-5');
|
|
225
247
|
expect(options.output_config).to.deep.equal({ effort: 'max' });
|
|
248
|
+
expect(options.thinking).to.deep.equal({ type: 'adaptive', display: 'summarized' });
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('applies Anthropic manual thinking for Sonnet 4.5', () => {
|
|
252
|
+
const options = {};
|
|
253
|
+
applyUnifiedEffort(options, { effort: 50 }, 'anthropic', 'claude-sonnet-4-5-20250929');
|
|
254
|
+
expect(options.thinking).to.deep.equal({ type: 'enabled', budget_tokens: 8192 });
|
|
255
|
+
expect(options.output_config).to.equal(undefined);
|
|
226
256
|
});
|
|
227
257
|
|
|
228
258
|
it('skips Anthropic mapping when output_config.effort is set', () => {
|
|
@@ -306,16 +336,29 @@ describe('Unified effort scale', () => {
|
|
|
306
336
|
expect(request.reasoning).to.deep.equal({ effort: 'none' });
|
|
307
337
|
});
|
|
308
338
|
|
|
309
|
-
it('Anthropic
|
|
310
|
-
const model = ModelMix.new(
|
|
311
|
-
expect(model.
|
|
339
|
+
it('Anthropic config.effort maps through .effort().opus5()', () => {
|
|
340
|
+
const model = ModelMix.new().effort(100).opus5();
|
|
341
|
+
expect(model.config.effort).to.equal(100);
|
|
312
342
|
|
|
313
343
|
const options = {
|
|
314
344
|
...model.models[0].provider.options,
|
|
315
345
|
model: 'claude-opus-5'
|
|
316
346
|
};
|
|
317
|
-
applyUnifiedEffort(options,
|
|
347
|
+
applyUnifiedEffort(options, model.config, 'anthropic', 'claude-opus-5');
|
|
318
348
|
expect(options.output_config.effort).to.equal('max');
|
|
349
|
+
expect(options.thinking).to.deep.equal({ type: 'adaptive', display: 'summarized' });
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it('Anthropic .effort(50).sonnet45() maps to manual budget_tokens', () => {
|
|
353
|
+
const model = ModelMix.new().effort(50).sonnet45();
|
|
354
|
+
expect(model.config.effort).to.equal(50);
|
|
355
|
+
|
|
356
|
+
const options = {
|
|
357
|
+
...model.models[0].provider.options,
|
|
358
|
+
model: 'claude-sonnet-4-5-20250929'
|
|
359
|
+
};
|
|
360
|
+
applyUnifiedEffort(options, model.config, 'anthropic', 'claude-sonnet-4-5-20250929');
|
|
361
|
+
expect(options.thinking).to.deep.equal({ type: 'enabled', budget_tokens: 8192 });
|
|
319
362
|
});
|
|
320
363
|
|
|
321
364
|
it('MixGoogle generationConfig includes thinkingConfig from options', async () => {
|
package/test/grok.test.js
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
const { expect } = require('chai');
|
|
2
2
|
const { ModelMix } = require('../index.js');
|
|
3
|
+
const {
|
|
4
|
+
resolveGrok420ModelKey,
|
|
5
|
+
GROK420_ALIAS,
|
|
6
|
+
GROK420_REASONING,
|
|
7
|
+
GROK420_NON_REASONING
|
|
8
|
+
} = require('../effort.js');
|
|
3
9
|
|
|
4
10
|
describe('Grok Model Registration Tests', () => {
|
|
5
11
|
const grokModels = [
|
|
6
12
|
{ method: 'grok45', key: 'grok-4.5' },
|
|
7
13
|
{ method: 'grok43', key: 'grok-4.3' },
|
|
8
14
|
{ method: 'grok420multiAgent', key: 'grok-4.20-multi-agent-0309' },
|
|
9
|
-
{ method: '
|
|
10
|
-
{ method: 'grok420', key: 'grok-4.20-0309-non-reasoning' }
|
|
15
|
+
{ method: 'grok420', key: GROK420_ALIAS }
|
|
11
16
|
];
|
|
12
17
|
|
|
13
18
|
for (const grokModel of grokModels) {
|
|
@@ -20,3 +25,34 @@ describe('Grok Model Registration Tests', () => {
|
|
|
20
25
|
});
|
|
21
26
|
}
|
|
22
27
|
});
|
|
28
|
+
|
|
29
|
+
describe('Grok 4.20 effort → model resolution', () => {
|
|
30
|
+
it('uses non-reasoning when effort is unset', () => {
|
|
31
|
+
expect(resolveGrok420ModelKey(GROK420_ALIAS, undefined)).to.equal(GROK420_NON_REASONING);
|
|
32
|
+
expect(resolveGrok420ModelKey(GROK420_ALIAS, null)).to.equal(GROK420_NON_REASONING);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('uses non-reasoning for OpenAI none band (0–19)', () => {
|
|
36
|
+
expect(resolveGrok420ModelKey(GROK420_ALIAS, 0)).to.equal(GROK420_NON_REASONING);
|
|
37
|
+
expect(resolveGrok420ModelKey(GROK420_ALIAS, 19)).to.equal(GROK420_NON_REASONING);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('uses reasoning for effort 20–100 and -1', () => {
|
|
41
|
+
expect(resolveGrok420ModelKey(GROK420_ALIAS, 20)).to.equal(GROK420_REASONING);
|
|
42
|
+
expect(resolveGrok420ModelKey(GROK420_ALIAS, 50)).to.equal(GROK420_REASONING);
|
|
43
|
+
expect(resolveGrok420ModelKey(GROK420_ALIAS, 100)).to.equal(GROK420_REASONING);
|
|
44
|
+
expect(resolveGrok420ModelKey(GROK420_ALIAS, -1)).to.equal(GROK420_REASONING);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('honors native reasoning_effort over config.effort', () => {
|
|
48
|
+
expect(resolveGrok420ModelKey(GROK420_ALIAS, 0, { reasoning_effort: 'high' }))
|
|
49
|
+
.to.equal(GROK420_REASONING);
|
|
50
|
+
expect(resolveGrok420ModelKey(GROK420_ALIAS, 100, { reasoning_effort: 'none' }))
|
|
51
|
+
.to.equal(GROK420_NON_REASONING);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('leaves non-alias keys unchanged', () => {
|
|
55
|
+
expect(resolveGrok420ModelKey(GROK420_REASONING, 50)).to.equal(GROK420_REASONING);
|
|
56
|
+
expect(resolveGrok420ModelKey('grok-4.3', 50)).to.equal('grok-4.3');
|
|
57
|
+
});
|
|
58
|
+
});
|
package/test/history.test.js
CHANGED
|
@@ -522,7 +522,7 @@ describe('Conversation History Tests', () => {
|
|
|
522
522
|
const model = ModelMix.new({
|
|
523
523
|
config: { debug: false, max_history: 10 }
|
|
524
524
|
});
|
|
525
|
-
model.
|
|
525
|
+
model.effort(100).opus5();
|
|
526
526
|
|
|
527
527
|
model.addText('2+2?');
|
|
528
528
|
nock('https://api.anthropic.com')
|
package/test/live.test.js
CHANGED
|
@@ -98,7 +98,7 @@ describe('Live Integration Tests', function () {
|
|
|
98
98
|
});
|
|
99
99
|
|
|
100
100
|
it('should return structured JSON with Sonnet 4.6 thinking', async function () {
|
|
101
|
-
const model = ModelMix.new(setup).
|
|
101
|
+
const model = ModelMix.new(setup).effort(100).sonnet46();
|
|
102
102
|
|
|
103
103
|
model.addText('Generate information about a fictional city.');
|
|
104
104
|
|
|
@@ -192,22 +192,10 @@ describe('Live Integration Tests', function () {
|
|
|
192
192
|
expect(response.toLowerCase()).to.include('gptoss test successful');
|
|
193
193
|
});
|
|
194
194
|
|
|
195
|
-
it('should work with Grok 4.1 model', async function () {
|
|
196
|
-
const model = ModelMix.new(setup).grok41();
|
|
197
|
-
|
|
198
|
-
model.addText('Say "grok41 test successful" and nothing else.');
|
|
199
|
-
|
|
200
|
-
const response = await model.message();
|
|
201
|
-
console.log(`Grok 4.1 response: ${response}`);
|
|
202
|
-
|
|
203
|
-
expect(response).to.be.a('string');
|
|
204
|
-
expect(response.toLowerCase()).to.include('grok41 test successful');
|
|
205
|
-
});
|
|
206
|
-
|
|
207
195
|
const grokSeriesTests = [
|
|
208
196
|
{ name: 'Grok 4.3', factory: (m) => m.grok43(), token: 'grok43' },
|
|
209
|
-
{ name: 'Grok 4.20 reasoning', factory: (m) => m.
|
|
210
|
-
{ name: 'Grok 4.20 non-reasoning', factory: (m) => m.grok420(), token: '
|
|
197
|
+
{ name: 'Grok 4.20 reasoning', factory: (m) => m.effort(50).grok420(), token: 'grok420' },
|
|
198
|
+
{ name: 'Grok 4.20 non-reasoning', factory: (m) => m.grok420(), token: 'grok420nr' }
|
|
211
199
|
];
|
|
212
200
|
|
|
213
201
|
for (const grokModel of grokSeriesTests) {
|
|
@@ -254,7 +242,7 @@ describe('Live Integration Tests', function () {
|
|
|
254
242
|
describe('JSON Structured Output for New Models', function () {
|
|
255
243
|
|
|
256
244
|
it('should return structured JSON with KimiK25 Thinking', async function () {
|
|
257
|
-
const model = ModelMix.new(setup).
|
|
245
|
+
const model = ModelMix.new(setup).kimiK25();
|
|
258
246
|
|
|
259
247
|
model.addText('Generate information about a fictional vehicle.');
|
|
260
248
|
|
|
@@ -275,8 +263,8 @@ describe('Live Integration Tests', function () {
|
|
|
275
263
|
expect(result.features).to.be.an('array');
|
|
276
264
|
});
|
|
277
265
|
|
|
278
|
-
it('should return structured JSON with
|
|
279
|
-
const model = ModelMix.new(setup).
|
|
266
|
+
it('should return structured JSON with Grok 4.3', async function () {
|
|
267
|
+
const model = ModelMix.new(setup).grok43();
|
|
280
268
|
|
|
281
269
|
model.addText('Generate information about a fictional technology.');
|
|
282
270
|
|
|
@@ -287,7 +275,7 @@ describe('Live Integration Tests', function () {
|
|
|
287
275
|
power: "1000 qubits"
|
|
288
276
|
});
|
|
289
277
|
|
|
290
|
-
console.log(`
|
|
278
|
+
console.log(`Grok 4.3 JSON result:`, result);
|
|
291
279
|
|
|
292
280
|
expect(result).to.be.an('object');
|
|
293
281
|
expect(result).to.have.property('name');
|