modelmix 4.7.0 → 4.7.4

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
@@ -89,7 +89,7 @@ console.log(ETH.price);
89
89
  ```javascript
90
90
  ModelMix.new()
91
91
  .gptOss()
92
- .kimiK25think()
92
+ .kimiK25()
93
93
  .hermes3()
94
94
  .addText('What is the capital of France?');
95
95
  ```
@@ -126,7 +126,7 @@ ModelMix.new().effort(-1).minimaxM3().addText('...').message();
126
126
 
127
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
128
 
129
- Migration: former Anthropic `*think()` shorthands are removed — use `.effort(100).opus5()` (or any 0–100 / `-1`) instead.
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.
130
130
 
131
131
  ## 🔧 Model Context Protocol (MCP) Integration
132
132
 
@@ -190,11 +190,12 @@ Here's a comprehensive list of available methods:
190
190
  | `gemini31pro()` | Google | gemini-3.1-pro-preview | [\$2.00 / \$12.00][3] |
191
191
  | `gemini36flash()` | Google | gemini-3.6-flash | [\$1.50 / \$7.50][3] |
192
192
  | `gemini35flash()` | Google | gemini-3.5-flash | [\$0.75 / \$4.50][3] |
193
+ | `gemini35flashLite()`| Google | gemini-3.5-flash-lite | [\$0.30 / \$2.50][3] |
193
194
  | `gemini31flashLite()`| Google | gemini-3.1-flash-lite-preview | [\$0.25 / \$1.50][3] |
194
195
  | `grok45()` | Grok | grok-4.5 | [\$2.00 / \$6.00][6] |
195
196
  | `grok43()` | Grok | grok-4.3 | [\$1.25 / \$2.50][6] |
196
197
  | `grok420multiAgent()`| Grok | grok-4.20-multi-agent-0309 | [\$1.25 / \$2.50][6] |
197
- | `grok420[think]()` | Grok | grok-4.20-0309 | [\$1.25 / \$2.50][6] |
198
+ | `grok420()` | Grok | grok-4.20-0309 (†) | [\$1.25 / \$2.50][6] |
198
199
  | `qwen36plus()` | Fireworks/Together | qwen3p6-plus / Qwen3.6-Plus | [\$0.50 / \$3.00][10] |
199
200
  | `qwen37plus()` | Fireworks | models/qwen3p7-plus | [\$0.40 / \$1.60][10] |
200
201
  | `qwen38max()` | OpenRouter | qwen/qwen3.8-max | [\$2.00 / \$6.00][12] |
@@ -208,8 +209,8 @@ Here's a comprehensive list of available methods:
208
209
  | `sonarPro()` | Perplexity | sonar-pro | [\$3.00 / \$15.00][4] |
209
210
  | `hermes3()` | Lambda | Hermes-3-Llama-3.1-405B-FP8 | [\$0.80 / \$0.80][8] |
210
211
  | `kimiK3()` | Moonshot | kimi-k3 | [\$3.00 / \$15.00][11] |
211
- | `kimiK25think()` | Together | Kimi-K2.5 | [\$0.50 / \$2.80][7] |
212
- | `kimiK26think()` | Fireworks | models/kimi-k2p6 | [\$0.95 / \$4.00][10] |
212
+ | `kimiK25()` | Together | Kimi-K2.5 | [\$0.50 / \$2.80][7] |
213
+ | `kimiK26()` | Fireworks | models/kimi-k2p6 | [\$0.95 / \$4.00][10] |
213
214
 
214
215
  [1]: https://platform.openai.com/docs/pricing "Pricing | OpenAI"
215
216
  [2]: https://docs.anthropic.com/en/docs/about-claude/pricing "Pricing - Anthropic"
@@ -225,6 +226,7 @@ Here's a comprehensive list of available methods:
225
226
  [12]: https://openrouter.ai/qwen/qwen3.8-max "OpenRouter Pricing"
226
227
 
227
228
  Each method accepts optional `options`, `config`, and (for multi-provider methods) `mix` parameters to customize behavior.
229
+ † `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.
228
230
  For NVIDIA on DeepSeek V4 Flash/Pro, use `deepseekV4Flash({ mix: { nvidia: true } })` or `deepseekV4Pro({ mix: { nvidia: true } })`.
229
231
  For Together on Qwen 3.6 Plus, use `qwen36plus({ mix: { fireworks: false, together: true } })`.
230
232
  For OpenRouter on Qwen 3.7 Plus, use `qwen37plus({ mix: { fireworks: false, openrouter: true } })`.
package/demo/free.js CHANGED
@@ -3,7 +3,7 @@ try { process.loadEnvFile(); } catch {}
3
3
 
4
4
  const ai = ModelMix.new({ config: { debug: 2 } })
5
5
  .gptOss()
6
- .kimiK25think()
6
+ .kimiK25()
7
7
  .hermes3()
8
8
  .addText('What is the capital of France?');
9
9
 
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)}`);
@@ -332,6 +365,10 @@ function mapEffort(providerFamily, effort, modelKey) {
332
365
  if (isMiniMax(modelKey)) {
333
366
  return mapMiniMaxEffort(normalized);
334
367
  }
368
+ // Non-reasoning Grok 4.20 has no reasoning_effort control
369
+ if (modelKey === GROK420_NON_REASONING) {
370
+ return null;
371
+ }
335
372
  const desired = levelFromBands(normalized, OPENAI_BANDS);
336
373
  const level = pickNearestLevel(desired, OPENAI_LEVELS, supportedOpenAILevels(modelKey));
337
374
  return { reasoning_effort: level };
@@ -409,6 +446,8 @@ module.exports = {
409
446
  applyUnifiedEffort,
410
447
  hasNativeEffort,
411
448
  resolveProviderFamily,
449
+ resolveGrok420ModelKey,
450
+ isGrok420Alias,
412
451
  isDeepSeekV4,
413
452
  isMiniMax,
414
453
  usesAnthropicAdaptiveThinking,
@@ -423,4 +462,7 @@ module.exports = {
423
462
  GEMINI_LEVELS,
424
463
  DEEPSEEK_LEVELS,
425
464
  ANTHROPIC_MANUAL_BUDGET_MAX,
465
+ GROK420_ALIAS,
466
+ GROK420_REASONING,
467
+ GROK420_NON_REASONING,
426
468
  };
package/index.d.ts CHANGED
@@ -302,6 +302,7 @@ export declare class ModelMix {
302
302
  gemini3flash(args?: ModelAttachArgs): this;
303
303
  gemini36flash(args?: ModelAttachArgs): this;
304
304
  gemini35flash(args?: ModelAttachArgs): this;
305
+ gemini35flashLite(args?: ModelAttachArgs): this;
305
306
  gemini31flashLite(args?: ModelAttachArgs): this;
306
307
  gemini25pro(args?: ModelAttachArgs): this;
307
308
 
@@ -313,7 +314,7 @@ export declare class ModelMix {
313
314
  grok45(args?: ModelAttachArgs): this;
314
315
  grok43(args?: ModelAttachArgs): this;
315
316
  grok420multiAgent(args?: ModelAttachArgs): this;
316
- grok420think(args?: ModelAttachArgs): this;
317
+ /** Non-reasoning by default; with `.effort(20+)` / `-1` uses the reasoning model at request time. */
317
318
  grok420(args?: ModelAttachArgs): this;
318
319
 
319
320
  // Multi-provider
@@ -322,10 +323,10 @@ export declare class ModelMix {
322
323
  qwen37plus(args?: ModelAttachArgs): this;
323
324
  qwen38max(args?: ModelAttachArgs): this;
324
325
  hermes3(args?: ModelAttachArgs): this;
325
- kimiK26think(args?: ModelAttachArgs): this;
326
+ kimiK26(args?: ModelAttachArgs): this;
326
327
  kimiK27Code(args?: ModelAttachArgs): this;
327
328
  kimiK3(args?: ModelAttachArgs): this;
328
- kimiK25think(args?: ModelAttachArgs): this;
329
+ kimiK25(args?: ModelAttachArgs): this;
329
330
  lmstudio(model?: string, args?: ModelAttachArgs): this;
330
331
  minimaxM25(args?: ModelAttachArgs): this;
331
332
  minimaxM27(args?: ModelAttachArgs): 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];
@@ -81,6 +82,7 @@ const MODEL_PRICING = {
81
82
  'gemini-3-flash-preview': [0.50, 3.00],
82
83
  'gemini-3.6-flash': [1.50, 7.50],
83
84
  'gemini-3.5-flash': [0.75, 4.50],
85
+ 'gemini-3.5-flash-lite': [0.30, 2.50],
84
86
  'gemini-2.5-pro': [1.25, 10.00],
85
87
  'gemini-2.5-flash': [0.30, 2.50],
86
88
  'gemini-3.1-flash-lite-preview': [0.25, 1.50],
@@ -88,6 +90,7 @@ const MODEL_PRICING = {
88
90
  'grok-4.5': [2.00, 6.00],
89
91
  'grok-4.3': [1.25, 2.50],
90
92
  'grok-4.20-multi-agent-0309': [1.25, 2.50],
93
+ 'grok-4.20-0309': [1.25, 2.50],
91
94
  'grok-4.20-0309-reasoning': [1.25, 2.50],
92
95
  'grok-4.20-0309-non-reasoning': [1.25, 2.50],
93
96
  // Fireworks
@@ -427,6 +430,9 @@ class ModelMix {
427
430
  gemini35flash({ options = {}, config = {} } = {}) {
428
431
  return this.attach('gemini-3.5-flash', new MixGoogle({ options, config }));
429
432
  }
433
+ gemini35flashLite({ options = {}, config = {} } = {}) {
434
+ return this.attach('gemini-3.5-flash-lite', new MixGoogle({ options, config }));
435
+ }
430
436
  gemini31flashLite({ options = {}, config = {} } = {}) {
431
437
  return this.attach('gemini-3.1-flash-lite-preview', new MixGoogle({ options, config }));
432
438
  }
@@ -449,11 +455,9 @@ class ModelMix {
449
455
  grok420multiAgent({ options = {}, config = {} } = {}) {
450
456
  return this.attach('grok-4.20-multi-agent-0309', new MixGrok({ options, config }));
451
457
  }
452
- grok420think({ options = {}, config = {} } = {}) {
453
- return this.attach('grok-4.20-0309-reasoning', new MixGrok({ options, config }));
454
- }
458
+ /** Non-reasoning by default; with `.effort(20+)` / `-1` resolves to the reasoning model at request time. */
455
459
  grok420({ options = {}, config = {} } = {}) {
456
- return this.attach('grok-4.20-0309-non-reasoning', new MixGrok({ options, config }));
460
+ return this.attach('grok-4.20-0309', new MixGrok({ options, config }));
457
461
  }
458
462
 
459
463
  qwen3({ options = {}, config = {}, mix = { together: true, cerebras: false } } = {}) {
@@ -489,7 +493,7 @@ class ModelMix {
489
493
  return this;
490
494
  }
491
495
 
492
- kimiK26think({ options = {}, config = {}, mix = { fireworks: true } } = {}) {
496
+ kimiK26({ options = {}, config = {}, mix = { fireworks: true } } = {}) {
493
497
  mix = { ...this.mix, ...mix };
494
498
  if (mix.fireworks) this.attach('accounts/fireworks/models/kimi-k2p6', new MixFireworks({ options, config }));
495
499
  if (mix.openrouter) this.attach('moonshotai/kimi-k2.6', new MixOpenRouter({ options, config }));
@@ -510,7 +514,7 @@ class ModelMix {
510
514
  return this;
511
515
  }
512
516
 
513
- kimiK25think({ options = {}, config = {}, mix = { together: true } } = {}) {
517
+ kimiK25({ options = {}, config = {}, mix = { together: true } } = {}) {
514
518
  mix = { ...this.mix, ...mix };
515
519
  if (mix.together) this.attach('moonshotai/Kimi-K2.5', new MixTogether({ options, config }));
516
520
  if (mix.fireworks) this.attach('accounts/fireworks/models/kimi-k2p5', new MixFireworks({ options, config }));
@@ -991,9 +995,17 @@ class ModelMix {
991
995
  }
992
996
  };
993
997
 
998
+ // Grok 4.20 alias → reasoning / non-reasoning from unified effort
999
+ const resolvedModelKey = resolveGrok420ModelKey(
1000
+ currentModelKey,
1001
+ currentConfig.effort,
1002
+ currentOptions
1003
+ );
1004
+ currentOptions.model = resolvedModelKey;
1005
+
994
1006
  // Unified effort → native provider fields (skipped if native already set)
995
1007
  const providerFamily = resolveProviderFamily(providerInstance);
996
- applyUnifiedEffort(currentOptions, currentConfig, providerFamily, currentModelKey);
1008
+ applyUnifiedEffort(currentOptions, currentConfig, providerFamily, resolvedModelKey);
997
1009
 
998
1010
  if (currentConfig.debug >= 1) {
999
1011
  const isPrimary = i === 0;
@@ -1003,7 +1015,7 @@ class ModelMix {
1003
1015
  : ' (fallback)';
1004
1016
  // Extract provider name from class name (e.g., "MixOpenRouter" -> "openrouter")
1005
1017
  const providerName = providerInstance.constructor.name.replace(/^Mix/, '').toLowerCase();
1006
- const header = `\n${prefix} [${providerName}:${currentModelKey}] #${originalIndex + 1}${suffix}`;
1018
+ const header = `\n${prefix} [${providerName}:${resolvedModelKey}] #${originalIndex + 1}${suffix}`;
1007
1019
 
1008
1020
  if (currentConfig.debug >= 2) {
1009
1021
  console.log(`${header}\n${ModelMix.formatInputSummary(this.messages, currentConfig.system, currentConfig.debug)}`);
@@ -1048,7 +1060,7 @@ class ModelMix {
1048
1060
  if (currentConfig.debug >= 1) {
1049
1061
  const nextAttempt = attempt + 2;
1050
1062
  const totalAttempts = retries + 1;
1051
- console.log(`↺ Retrying [${currentModelKey}] due to status ${statusCode} (${nextAttempt}/${totalAttempts})`);
1063
+ console.log(`↺ Retrying [${resolvedModelKey}] due to status ${statusCode} (${nextAttempt}/${totalAttempts})`);
1052
1064
  }
1053
1065
 
1054
1066
  const delay = Math.min(baseDelayMs * Math.pow(2, attempt), maxDelayMs);
@@ -1060,7 +1072,7 @@ class ModelMix {
1060
1072
  const elapsedMs = Date.now() - startTime;
1061
1073
 
1062
1074
  if (result.tokens) {
1063
- result.tokens.cost = ModelMix.calculateCost(currentModelKey, result.tokens);
1075
+ result.tokens.cost = ModelMix.calculateCost(resolvedModelKey, result.tokens);
1064
1076
  const elapsedSec = elapsedMs / 1000;
1065
1077
  result.tokens.speed = elapsedSec > 0 ? Math.round(result.tokens.output / elapsedSec) : 0;
1066
1078
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "modelmix",
3
- "version": "4.7.0",
3
+ "version": "4.7.4",
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
  }
@@ -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`. 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 Anthropic `*think()` methods are removed — use `.effort(n).<model>()`.
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
 
@@ -127,16 +127,16 @@ ModelMix.new({ config: { effort: 80 } })
127
127
  Use `.effort(n)` (or `config.effort`) to enable Anthropic thinking — e.g. `.effort(100).opus5()`.
128
128
 
129
129
  ### Google
130
- `gemini3pro()` `gemini3flash()` `gemini36flash()` `gemini35flash()` `gemini25pro()` `gemini25flash()`
130
+ `gemini3pro()` `gemini3flash()` `gemini36flash()` `gemini35flash()` `gemini35flashLite()` `gemini31flashLite()` `gemini25pro()` `gemini25flash()`
131
131
 
132
132
  ### Grok
133
- `grok45()` `grok43()` `grok420multiAgent()` `grok420()` `grok420think()`
133
+ `grok45()` `grok43()` `grok420multiAgent()` `grok420()`
134
134
 
135
135
  ### Perplexity
136
136
  `sonar()` `sonarPro()`
137
137
 
138
138
  ### Together
139
- `qwen36plus()` `GLM52()` `kimiK25think()` `gptOss()`
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 @@ Use `.effort(n)` (or `config.effort`) to enable Anthropic thinking — e.g. `.ef
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 @@ Use `.effort(n)` (or `config.effort`) to enable Anthropic thinking — e.g. `.ef
154
154
  `qwen38max()` `GLM45()`
155
155
 
156
156
  ### Multi-provider (auto-fallback across free/paid tiers)
157
- `hermes3()` `kimiK25think()`
157
+ `hermes3()` `kimiK25()`
158
158
 
159
159
  ### Local
160
160
  `lmstudio()` — for LM Studio local models
@@ -429,7 +429,7 @@ For full debug output, also set: `DEBUG=ModelMix* node script.js`
429
429
  ```javascript
430
430
  const model = ModelMix.new()
431
431
  .gptOss()
432
- .kimiK25think()
432
+ .kimiK25()
433
433
  .hermes3()
434
434
  .addText("What is the capital of France?");
435
435
  console.log(await model.message());
@@ -452,7 +452,7 @@ const model = ModelMix.new({
452
452
  minimax: false, // default: false
453
453
  fireworks: false // default: false
454
454
  }
455
- }).kimiK25think();
455
+ }).kimiK25();
456
456
  ```
457
457
 
458
458
  ## Agent Usage Rules
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: 'grok420think', key: 'grok-4.20-0309-reasoning' },
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/live.test.js CHANGED
@@ -194,8 +194,8 @@ describe('Live Integration Tests', function () {
194
194
 
195
195
  const grokSeriesTests = [
196
196
  { name: 'Grok 4.3', factory: (m) => m.grok43(), token: 'grok43' },
197
- { name: 'Grok 4.20 reasoning', factory: (m) => m.grok420think(), token: 'grok420think' },
198
- { name: 'Grok 4.20 non-reasoning', factory: (m) => m.grok420(), token: 'grok420' }
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' }
199
199
  ];
200
200
 
201
201
  for (const grokModel of grokSeriesTests) {
@@ -242,7 +242,7 @@ describe('Live Integration Tests', function () {
242
242
  describe('JSON Structured Output for New Models', function () {
243
243
 
244
244
  it('should return structured JSON with KimiK25 Thinking', async function () {
245
- const model = ModelMix.new(setup).kimiK25think();
245
+ const model = ModelMix.new(setup).kimiK25();
246
246
 
247
247
  model.addText('Generate information about a fictional vehicle.');
248
248
 
@@ -108,15 +108,18 @@ describe('Token Usage Tracking', () => {
108
108
  it('should register Gemini Flash shortcuts with Google provider', function () {
109
109
  const model = ModelMix.new()
110
110
  .gemini36flash()
111
- .gemini35flash();
111
+ .gemini35flash()
112
+ .gemini35flashLite();
112
113
 
113
114
  expect(model.models.map(({ key }) => key)).to.deep.equal([
114
115
  'gemini-3.6-flash',
115
- 'gemini-3.5-flash'
116
+ 'gemini-3.5-flash',
117
+ 'gemini-3.5-flash-lite'
116
118
  ]);
117
119
  expect(model.models.every(({ provider }) => provider instanceof MixGoogle)).to.equal(true);
118
120
  expect(ModelMix.calculateCost('gemini-3.6-flash', { input: 1_000_000, output: 1_000_000 })).to.equal(9);
119
121
  expect(ModelMix.calculateCost('gemini-3.5-flash', { input: 1_000_000, output: 1_000_000 })).to.equal(5.25);
122
+ expect(ModelMix.calculateCost('gemini-3.5-flash-lite', { input: 1_000_000, output: 1_000_000 })).to.equal(2.8);
120
123
  });
121
124
 
122
125
  it('should register MiMo shortcuts with native and OpenRouter providers', function () {