modelmix 4.7.0 → 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 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
 
@@ -194,7 +194,7 @@ Here's a comprehensive list of available methods:
194
194
  | `grok45()` | Grok | grok-4.5 | [\$2.00 / \$6.00][6] |
195
195
  | `grok43()` | Grok | grok-4.3 | [\$1.25 / \$2.50][6] |
196
196
  | `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] |
197
+ | `grok420()` | Grok | grok-4.20-0309 (†) | [\$1.25 / \$2.50][6] |
198
198
  | `qwen36plus()` | Fireworks/Together | qwen3p6-plus / Qwen3.6-Plus | [\$0.50 / \$3.00][10] |
199
199
  | `qwen37plus()` | Fireworks | models/qwen3p7-plus | [\$0.40 / \$1.60][10] |
200
200
  | `qwen38max()` | OpenRouter | qwen/qwen3.8-max | [\$2.00 / \$6.00][12] |
@@ -208,8 +208,8 @@ Here's a comprehensive list of available methods:
208
208
  | `sonarPro()` | Perplexity | sonar-pro | [\$3.00 / \$15.00][4] |
209
209
  | `hermes3()` | Lambda | Hermes-3-Llama-3.1-405B-FP8 | [\$0.80 / \$0.80][8] |
210
210
  | `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] |
211
+ | `kimiK25()` | Together | Kimi-K2.5 | [\$0.50 / \$2.80][7] |
212
+ | `kimiK26()` | Fireworks | models/kimi-k2p6 | [\$0.95 / \$4.00][10] |
213
213
 
214
214
  [1]: https://platform.openai.com/docs/pricing "Pricing | OpenAI"
215
215
  [2]: https://docs.anthropic.com/en/docs/about-claude/pricing "Pricing - Anthropic"
@@ -225,6 +225,7 @@ Here's a comprehensive list of available methods:
225
225
  [12]: https://openrouter.ai/qwen/qwen3.8-max "OpenRouter Pricing"
226
226
 
227
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.
228
229
  For NVIDIA on DeepSeek V4 Flash/Pro, use `deepseekV4Flash({ mix: { nvidia: true } })` or `deepseekV4Pro({ mix: { nvidia: true } })`.
229
230
  For Together on Qwen 3.6 Plus, use `qwen36plus({ mix: { fireworks: false, together: true } })`.
230
231
  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
@@ -313,7 +313,7 @@ export declare class ModelMix {
313
313
  grok45(args?: ModelAttachArgs): this;
314
314
  grok43(args?: ModelAttachArgs): this;
315
315
  grok420multiAgent(args?: ModelAttachArgs): this;
316
- grok420think(args?: ModelAttachArgs): this;
316
+ /** Non-reasoning by default; with `.effort(20+)` / `-1` uses the reasoning model at request time. */
317
317
  grok420(args?: ModelAttachArgs): this;
318
318
 
319
319
  // Multi-provider
@@ -322,10 +322,10 @@ export declare class ModelMix {
322
322
  qwen37plus(args?: ModelAttachArgs): this;
323
323
  qwen38max(args?: ModelAttachArgs): this;
324
324
  hermes3(args?: ModelAttachArgs): this;
325
- kimiK26think(args?: ModelAttachArgs): this;
325
+ kimiK26(args?: ModelAttachArgs): this;
326
326
  kimiK27Code(args?: ModelAttachArgs): this;
327
327
  kimiK3(args?: ModelAttachArgs): this;
328
- kimiK25think(args?: ModelAttachArgs): this;
328
+ kimiK25(args?: ModelAttachArgs): this;
329
329
  lmstudio(model?: string, args?: ModelAttachArgs): this;
330
330
  minimaxM25(args?: ModelAttachArgs): this;
331
331
  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];
@@ -88,6 +89,7 @@ const MODEL_PRICING = {
88
89
  'grok-4.5': [2.00, 6.00],
89
90
  'grok-4.3': [1.25, 2.50],
90
91
  'grok-4.20-multi-agent-0309': [1.25, 2.50],
92
+ 'grok-4.20-0309': [1.25, 2.50],
91
93
  'grok-4.20-0309-reasoning': [1.25, 2.50],
92
94
  'grok-4.20-0309-non-reasoning': [1.25, 2.50],
93
95
  // Fireworks
@@ -449,11 +451,9 @@ class ModelMix {
449
451
  grok420multiAgent({ options = {}, config = {} } = {}) {
450
452
  return this.attach('grok-4.20-multi-agent-0309', new MixGrok({ options, config }));
451
453
  }
452
- grok420think({ options = {}, config = {} } = {}) {
453
- return this.attach('grok-4.20-0309-reasoning', new MixGrok({ options, config }));
454
- }
454
+ /** Non-reasoning by default; with `.effort(20+)` / `-1` resolves to the reasoning model at request time. */
455
455
  grok420({ options = {}, config = {} } = {}) {
456
- return this.attach('grok-4.20-0309-non-reasoning', new MixGrok({ options, config }));
456
+ return this.attach('grok-4.20-0309', new MixGrok({ options, config }));
457
457
  }
458
458
 
459
459
  qwen3({ options = {}, config = {}, mix = { together: true, cerebras: false } } = {}) {
@@ -489,7 +489,7 @@ class ModelMix {
489
489
  return this;
490
490
  }
491
491
 
492
- kimiK26think({ options = {}, config = {}, mix = { fireworks: true } } = {}) {
492
+ kimiK26({ options = {}, config = {}, mix = { fireworks: true } } = {}) {
493
493
  mix = { ...this.mix, ...mix };
494
494
  if (mix.fireworks) this.attach('accounts/fireworks/models/kimi-k2p6', new MixFireworks({ options, config }));
495
495
  if (mix.openrouter) this.attach('moonshotai/kimi-k2.6', new MixOpenRouter({ options, config }));
@@ -510,7 +510,7 @@ class ModelMix {
510
510
  return this;
511
511
  }
512
512
 
513
- kimiK25think({ options = {}, config = {}, mix = { together: true } } = {}) {
513
+ kimiK25({ options = {}, config = {}, mix = { together: true } } = {}) {
514
514
  mix = { ...this.mix, ...mix };
515
515
  if (mix.together) this.attach('moonshotai/Kimi-K2.5', new MixTogether({ options, config }));
516
516
  if (mix.fireworks) this.attach('accounts/fireworks/models/kimi-k2p5', new MixFireworks({ options, config }));
@@ -991,9 +991,17 @@ class ModelMix {
991
991
  }
992
992
  };
993
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
+
994
1002
  // Unified effort → native provider fields (skipped if native already set)
995
1003
  const providerFamily = resolveProviderFamily(providerInstance);
996
- applyUnifiedEffort(currentOptions, currentConfig, providerFamily, currentModelKey);
1004
+ applyUnifiedEffort(currentOptions, currentConfig, providerFamily, resolvedModelKey);
997
1005
 
998
1006
  if (currentConfig.debug >= 1) {
999
1007
  const isPrimary = i === 0;
@@ -1003,7 +1011,7 @@ class ModelMix {
1003
1011
  : ' (fallback)';
1004
1012
  // Extract provider name from class name (e.g., "MixOpenRouter" -> "openrouter")
1005
1013
  const providerName = providerInstance.constructor.name.replace(/^Mix/, '').toLowerCase();
1006
- const header = `\n${prefix} [${providerName}:${currentModelKey}] #${originalIndex + 1}${suffix}`;
1014
+ const header = `\n${prefix} [${providerName}:${resolvedModelKey}] #${originalIndex + 1}${suffix}`;
1007
1015
 
1008
1016
  if (currentConfig.debug >= 2) {
1009
1017
  console.log(`${header}\n${ModelMix.formatInputSummary(this.messages, currentConfig.system, currentConfig.debug)}`);
@@ -1048,7 +1056,7 @@ class ModelMix {
1048
1056
  if (currentConfig.debug >= 1) {
1049
1057
  const nextAttempt = attempt + 2;
1050
1058
  const totalAttempts = retries + 1;
1051
- console.log(`↺ Retrying [${currentModelKey}] due to status ${statusCode} (${nextAttempt}/${totalAttempts})`);
1059
+ console.log(`↺ Retrying [${resolvedModelKey}] due to status ${statusCode} (${nextAttempt}/${totalAttempts})`);
1052
1060
  }
1053
1061
 
1054
1062
  const delay = Math.min(baseDelayMs * Math.pow(2, attempt), maxDelayMs);
@@ -1060,7 +1068,7 @@ class ModelMix {
1060
1068
  const elapsedMs = Date.now() - startTime;
1061
1069
 
1062
1070
  if (result.tokens) {
1063
- result.tokens.cost = ModelMix.calculateCost(currentModelKey, result.tokens);
1071
+ result.tokens.cost = ModelMix.calculateCost(resolvedModelKey, result.tokens);
1064
1072
  const elapsedSec = elapsedMs / 1000;
1065
1073
  result.tokens.speed = elapsedSec > 0 ? Math.round(result.tokens.output / elapsedSec) : 0;
1066
1074
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "modelmix",
3
- "version": "4.7.0",
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
  }
@@ -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
 
@@ -130,13 +130,13 @@ Use `.effort(n)` (or `config.effort`) to enable Anthropic thinking — e.g. `.ef
130
130
  `gemini3pro()` `gemini3flash()` `gemini36flash()` `gemini35flash()` `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