modelmix 5.0.1 → 5.0.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
@@ -70,7 +70,7 @@ const model = await ModelMix.new(setup)
70
70
  .sonnet5() // (main model) Anthropic claude-sonnet-5
71
71
  .gpt56luna() // (fallback 2) OpenAI gpt-5.6-luna
72
72
  .gemini36flash({ config: { temperature: 0 } }) // (fallback 3) Google gemini-36-flash
73
- .grok43() // (fallback 4) Grok grok-4.3
73
+ .grok46() // (fallback 4) Grok grok-4.6
74
74
  .addText("What's your name?");
75
75
 
76
76
  console.log(await model.message());
@@ -124,7 +124,7 @@ 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`. 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.
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`. Grok 4.6 clamps 0–39 / 40–59 / 60–79 / 80–100 to `low` / `medium` / `high` / `xhigh`; without effort it uses the native `high` default. `-1` = provider adaptive/dynamic when available, else no-op. Levels clamp to what each model supports.
128
128
 
129
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
 
@@ -192,6 +192,7 @@ Here's a comprehensive list of available methods:
192
192
  | `gemini35flash()` | Google | gemini-3.5-flash | [\$0.75/\$4.50][3] |
193
193
  | `gemini35flashLite()`| Google | gemini-3.5-flash-lite | [\$0.30/\$2.50][3] |
194
194
  | `gemini31flashLite()`| Google | gemini-3.1-flash-lite-preview | [\$0.25/\$1.50][3] |
195
+ | `grok46()` | Grok | grok-4.6 | [\$2.00/\$6.00][6] |
195
196
  | `grok45()` | Grok | grok-4.5 | [\$2.00/\$6.00][6] |
196
197
  | `grok43()` | Grok | grok-4.3 | [\$1.25/\$2.50][6] |
197
198
  | `grok420multiAgent()`| Grok | grok-4.20-multi-agent-0309 | [\$1.25/\$2.50][6] |
package/demo/grok.js CHANGED
@@ -13,9 +13,9 @@ const mmix = new ModelMix({
13
13
  });
14
14
 
15
15
 
16
- const r = await mmix.grok43()
16
+ const r = await mmix.grok46()
17
17
  .addText('hi there!')
18
18
  .addText('do you like cats?')
19
19
  .raw();
20
20
 
21
- console.log(r);
21
+ console.log(r);
package/demo/images.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { ModelMix } from '../index.js';
2
2
  try { process.loadEnvFile(); } catch {}
3
3
 
4
- const model = ModelMix.new({ config: { max_history: 2, debug: 2 } }).grok43()
4
+ const model = ModelMix.new({ config: { max_history: 2, debug: 2 } }).grok46()
5
5
  // model.addImageFromUrl('https://pbs.twimg.com/media/F6-GsjraAAADDGy?format=jpg');
6
6
  model.addImage('./img.png');
7
7
  model.addText('in one word, which is the main color of the image?');
8
8
 
9
- console.log(await model.json({ color: "string" }));
9
+ console.log(await model.json({ color: "string" }));
package/demo/short.js CHANGED
@@ -13,7 +13,7 @@ const mmix = await ModelMix.new(setup)
13
13
  .gpt56luna() // (fallback 1) OpenAI gpt-5.6-luna
14
14
  .gemini36flash({ config: { temperature: 0 } }) // (fallback 2) Google gemini-3.6-flash
15
15
  .gpt41nano() // (fallback 3) OpenAI gpt-4.1-nano
16
- .grok43() // (fallback 4) Grok grok-4.3
16
+ .grok46() // (fallback 4) Grok grok-4.6
17
17
  .addText("What's your name?");
18
18
 
19
- console.log(await mmix.message());
19
+ console.log(await mmix.message());
package/effort.js CHANGED
@@ -35,6 +35,7 @@ const GEMINI_BANDS = [
35
35
 
36
36
  /** Exact model → supported OpenAI reasoning_effort values */
37
37
  const OPENAI_MODEL_LEVELS = {
38
+ 'grok-4.6': ['low', 'medium', 'high', 'xhigh'],
38
39
  'gpt-5': ['minimal', 'low', 'medium', 'high'],
39
40
  'gpt-5-mini': ['minimal', 'low', 'medium', 'high'],
40
41
  'gpt-5-nano': ['minimal', 'low', 'medium', 'high'],
package/index.d.ts CHANGED
@@ -406,6 +406,7 @@ export declare class ModelMix {
406
406
  sonar(args?: ModelAttachArgs): this;
407
407
 
408
408
  // Grok
409
+ grok46(args?: ModelAttachArgs): this;
409
410
  grok45(args?: ModelAttachArgs): this;
410
411
  grok43(args?: ModelAttachArgs): this;
411
412
  grok420multiAgent(args?: ModelAttachArgs): this;
package/index.js CHANGED
@@ -259,6 +259,21 @@ const GPT56_LONG_CONTEXT_PRICING = Object.freeze({
259
259
  outputMultiplier: 1.5
260
260
  });
261
261
 
262
+ const GROK46_LONG_CONTEXT_PRICING = Object.freeze({
263
+ inputThreshold: 200_000,
264
+ inputMultiplier: 2,
265
+ outputMultiplier: 2,
266
+ inclusive: true
267
+ });
268
+
269
+ function usesLongContextRates(pricing, inputTokens) {
270
+ const longContext = pricing.longContext;
271
+ if (!longContext) return false;
272
+ return longContext.inclusive
273
+ ? inputTokens >= longContext.inputThreshold
274
+ : inputTokens > longContext.inputThreshold;
275
+ }
276
+
262
277
  const MODEL_PRICING = {
263
278
  // OpenAI
264
279
  'gpt-realtime-mini': { input: 0.60, cachedInput: 0.06, output: 2.40 },
@@ -307,6 +322,7 @@ const MODEL_PRICING = {
307
322
  'gemini-2.5-flash': { input: 0.30, output: 2.50 },
308
323
  'gemini-3.1-flash-lite-preview': { input: 0.25, output: 1.50 },
309
324
  // Grok
325
+ 'grok-4.6': { input: 2.00, cachedInput: 0.50, output: 6.00, longContext: GROK46_LONG_CONTEXT_PRICING },
310
326
  'grok-4.5': { input: 2.00, output: 6.00 },
311
327
  'grok-4.3': { input: 1.25, output: 2.50 },
312
328
  'grok-4.20-multi-agent-0309': { input: 1.25, output: 2.50 },
@@ -535,7 +551,7 @@ class ModelMix {
535
551
 
536
552
  const normalized = ModelMix.normalizeTokenUsage(tokens);
537
553
  const longContext = pricing.longContext;
538
- const useLongContextRates = longContext && normalized.input > longContext.inputThreshold;
554
+ const useLongContextRates = usesLongContextRates(pricing, normalized.input);
539
555
  const inputMultiplier = useLongContextRates ? longContext.inputMultiplier : 1;
540
556
  const outputMultiplier = useLongContextRates ? longContext.outputMultiplier : 1;
541
557
  const {
@@ -587,9 +603,8 @@ class ModelMix {
587
603
 
588
604
  const normalized = ModelMix.normalizeTokenUsage(tokens);
589
605
  const longContext = pricing.longContext;
590
- const inputMultiplier = longContext && normalized.input > longContext.inputThreshold
591
- ? longContext.inputMultiplier
592
- : 1;
606
+ const useLongContextRates = usesLongContextRates(pricing, normalized.input);
607
+ const inputMultiplier = useLongContextRates ? longContext.inputMultiplier : 1;
593
608
  const cachedInputPerMillion = pricing.cachedInput ?? pricing.input;
594
609
  const cacheWritePerMillion = pricing.cacheWrite ?? pricing.input;
595
610
  const cacheWrite1hPerMillion = pricing.cacheWrite1h ?? cacheWritePerMillion;
@@ -844,6 +859,9 @@ class ModelMix {
844
859
  return this.attach('sonar', new MixPerplexity({ options, config }));
845
860
  }
846
861
 
862
+ grok46({ options = {}, config = {} } = {}) {
863
+ return this.attach('grok-4.6', new MixGrok({ options, config }));
864
+ }
847
865
  grok45({ options = {}, config = {} } = {}) {
848
866
  return this.attach('grok-4.5', new MixGrok({ options, config }));
849
867
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "modelmix",
3
- "version": "5.0.1",
3
+ "version": "5.0.2",
4
4
  "description": "🧬 Reliable interface with automatic fallback for AI LLMs.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -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 `*think()` methods are removed — use `.effort(n).<model>()`. Kimi: `kimiK25()` / `kimiK26()`. Grok 4.20: `.grok420()` non-reasoning; `.effort(20+|-1).grok420()` selects reasoning.
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`. Grok 4.6 maps 0–39 / 40–59 / 60–79 / 80–100 to `low` / `medium` / `high` / `xhigh`; without effort it uses native `high`. `-1` = adaptive/dynamic when available, else no-op. Levels clamp per model. Former `*think()` methods are removed — use `.effort(n).<model>()`. Kimi: `kimiK25()` / `kimiK26()`. Grok 4.20: `.grok420()` non-reasoning; `.effort(20+|-1).grok420()` selects reasoning.
118
118
 
119
119
  ## Available Model Shorthands
120
120
 
@@ -130,7 +130,7 @@ Use `.effort(n)` (or `config.effort`) to enable Anthropic thinking — e.g. `.ef
130
130
  `gemini3pro()` `gemini3flash()` `gemini36flash()` `gemini35flash()` `gemini35flashLite()` `gemini31flashLite()` `gemini25pro()` `gemini25flash()`
131
131
 
132
132
  ### Grok
133
- `grok45()` `grok43()` `grok420multiAgent()` `grok420()`
133
+ `grok46()` `grok45()` `grok43()` `grok420multiAgent()` `grok420()`
134
134
 
135
135
  ### Perplexity
136
136
  `sonar()` `sonarPro()`
package/test/grok.test.js CHANGED
@@ -2,6 +2,7 @@ const { expect } = require('chai');
2
2
  const nock = require('nock');
3
3
  const { ModelMix, MixGrok } = require('../index.js');
4
4
  const {
5
+ mapEffort,
5
6
  resolveGrok420ModelKey,
6
7
  GROK420_ALIAS,
7
8
  GROK420_REASONING,
@@ -10,6 +11,7 @@ const {
10
11
 
11
12
  describe('Grok Model Registration Tests', () => {
12
13
  const grokModels = [
14
+ { method: 'grok46', key: 'grok-4.6' },
13
15
  { method: 'grok45', key: 'grok-4.5' },
14
16
  { method: 'grok43', key: 'grok-4.3' },
15
17
  { method: 'grok420multiAgent', key: 'grok-4.20-multi-agent-0309' },
@@ -25,6 +27,78 @@ describe('Grok Model Registration Tests', () => {
25
27
  expect(model.models[0].key).to.equal(grokModel.key);
26
28
  });
27
29
  }
30
+
31
+ it('forwards options and config through grok46()', () => {
32
+ const options = { reasoning_effort: 'xhigh' };
33
+ const config = { max_history: 3 };
34
+ const model = ModelMix.new().grok46({ options, config });
35
+
36
+ expect(model.models[0].provider).to.be.instanceOf(MixGrok);
37
+ expect(model.models[0].provider.options).to.deep.equal(options);
38
+ expect(model.models[0].provider.config).to.include(config);
39
+ });
40
+
41
+ it('maps unified effort to Grok 4.6 supported levels', () => {
42
+ expect(mapEffort('openai', 0, 'grok-4.6')).to.deep.equal({ reasoning_effort: 'low' });
43
+ expect(mapEffort('openai', 39, 'grok-4.6')).to.deep.equal({ reasoning_effort: 'low' });
44
+ expect(mapEffort('openai', 40, 'grok-4.6')).to.deep.equal({ reasoning_effort: 'medium' });
45
+ expect(mapEffort('openai', 60, 'grok-4.6')).to.deep.equal({ reasoning_effort: 'high' });
46
+ expect(mapEffort('openai', 100, 'grok-4.6')).to.deep.equal({ reasoning_effort: 'xhigh' });
47
+ expect(mapEffort('openai', -1, 'grok-4.6')).to.equal(null);
48
+ });
49
+
50
+ it('sends a supported Grok 4.6 reasoning effort', async () => {
51
+ const originalApiKey = process.env.XAI_API_KEY;
52
+ process.env.XAI_API_KEY = 'test-key';
53
+ const api = nock('https://api.x.ai')
54
+ .post('/v1/chat/completions', body => {
55
+ expect(body.model).to.equal('grok-4.6');
56
+ expect(body.reasoning_effort).to.equal('low');
57
+ return true;
58
+ })
59
+ .reply(200, {
60
+ choices: [{ message: { content: 'ok' } }],
61
+ usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }
62
+ });
63
+
64
+ try {
65
+ const response = await ModelMix.new()
66
+ .effort(0)
67
+ .grok46({ config: { apiKey: 'test-key' } })
68
+ .addText('Hi')
69
+ .message();
70
+
71
+ expect(response).to.equal('ok');
72
+ api.done();
73
+ } finally {
74
+ if (originalApiKey === undefined) delete process.env.XAI_API_KEY;
75
+ else process.env.XAI_API_KEY = originalApiKey;
76
+ }
77
+ });
78
+
79
+ it('calculates Grok 4.6 cache and long-context costs', () => {
80
+ expect(ModelMix.calculateCostBreakdown('grok-4.6', {
81
+ input: 1_000_000,
82
+ output: 1_000_000,
83
+ cached: 1_000_000
84
+ })).to.deep.equal({
85
+ uncachedInput: 0,
86
+ cachedInput: 1,
87
+ cacheWrite: 0,
88
+ cacheWrite5m: 0,
89
+ cacheWrite1h: 0,
90
+ output: 12,
91
+ total: 13
92
+ });
93
+ expect(ModelMix.calculateCost('grok-4.6', {
94
+ input: 199_999,
95
+ output: 1_000_000
96
+ })).to.equal(6.399998);
97
+ expect(ModelMix.calculateCost('grok-4.6', {
98
+ input: 200_000,
99
+ output: 1_000_000
100
+ })).to.equal(12.8);
101
+ });
28
102
  });
29
103
 
30
104
  describe('Grok 4.20 effort → model resolution', () => {
package/test/live.mcp.js CHANGED
@@ -233,8 +233,8 @@ describe('Live MCP Integration Tests', function () {
233
233
 
234
234
  describe('Advanced MCP Tool Integration', function () {
235
235
 
236
- it('should use multiple MCP tools with Grok 4.3', async function () {
237
- const model = ModelMix.new(setup).grok43();
236
+ it('should use multiple MCP tools with Grok 4.6', async function () {
237
+ const model = ModelMix.new(setup).grok46();
238
238
 
239
239
  // Add multiple tools
240
240
  model.addTools([
package/test/live.test.js CHANGED
@@ -193,7 +193,7 @@ describe('Live Integration Tests', function () {
193
193
  });
194
194
 
195
195
  const grokSeriesTests = [
196
- { name: 'Grok 4.3', factory: (m) => m.grok43(), token: 'grok43' },
196
+ { name: 'Grok 4.6', factory: (m) => m.grok46(), token: 'grok46' },
197
197
  { name: 'Grok 4.20 reasoning', factory: (m) => m.effort(50).grok420(), token: 'grok420' },
198
198
  { name: 'Grok 4.20 non-reasoning', factory: (m) => m.grok420(), token: 'grok420nr' }
199
199
  ];
@@ -216,8 +216,8 @@ describe('Live Integration Tests', function () {
216
216
 
217
217
  describe('Image Processing with JSON Output', function () {
218
218
 
219
- it('should process images and return JSON with Grok 4.3', async function () {
220
- const model = ModelMix.new(setup).grok43();
219
+ it('should process images and return JSON with Grok 4.6', async function () {
220
+ const model = ModelMix.new(setup).grok46();
221
221
 
222
222
  model.addImageFromUrl(blueSquareBase64)
223
223
  .addText('Analyze this image and provide details in JSON format.');
@@ -228,7 +228,7 @@ describe('Live Integration Tests', function () {
228
228
  description: "string"
229
229
  });
230
230
 
231
- console.log(`Grok 4.3 image JSON result:`, result);
231
+ console.log(`Grok 4.6 image JSON result:`, result);
232
232
 
233
233
  expect(result).to.be.an('object');
234
234
  expect(result).to.have.property('color');
@@ -263,8 +263,8 @@ describe('Live Integration Tests', function () {
263
263
  expect(result.features).to.be.an('array');
264
264
  });
265
265
 
266
- it('should return structured JSON with Grok 4.3', async function () {
267
- const model = ModelMix.new(setup).grok43();
266
+ it('should return structured JSON with Grok 4.6', async function () {
267
+ const model = ModelMix.new(setup).grok46();
268
268
 
269
269
  model.addText('Generate information about a fictional technology.');
270
270
 
@@ -275,7 +275,7 @@ describe('Live Integration Tests', function () {
275
275
  power: "1000 qubits"
276
276
  });
277
277
 
278
- console.log(`Grok 4.3 JSON result:`, result);
278
+ console.log(`Grok 4.6 JSON result:`, result);
279
279
 
280
280
  expect(result).to.be.an('object');
281
281
  expect(result).to.have.property('name');