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 +3 -2
- package/demo/grok.js +2 -2
- package/demo/images.js +2 -2
- package/demo/short.js +2 -2
- package/effort.js +1 -0
- package/index.d.ts +1 -0
- package/index.js +22 -4
- package/package.json +1 -1
- package/skills/modelmix/SKILL.md +2 -2
- package/test/grok.test.js +74 -0
- package/test/live.mcp.js +2 -2
- package/test/live.test.js +7 -7
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
|
-
.
|
|
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
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 } }).
|
|
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
|
-
.
|
|
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 =
|
|
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
|
|
591
|
-
|
|
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
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`. 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.
|
|
237
|
-
const model = ModelMix.new(setup).
|
|
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.
|
|
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.
|
|
220
|
-
const model = ModelMix.new(setup).
|
|
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.
|
|
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.
|
|
267
|
-
const model = ModelMix.new(setup).
|
|
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.
|
|
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');
|