modelmix 5.1.9 → 5.1.12
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 +18 -6
- package/demo/free.js +1 -3
- package/demo/short.js +15 -13
- package/effort.js +13 -7
- package/index.d.ts +3 -0
- package/index.js +71 -71
- package/lib/model-chain.js +2 -2
- package/lib/provider-api-key.js +9 -0
- package/lib/providers/anthropic.js +22 -20
- package/lib/providers/base.js +4 -22
- package/lib/providers/google.js +3 -6
- package/lib/providers/openai-compatible.js +11 -40
- package/lib/providers/openai-options.js +16 -0
- package/lib/providers/openai.js +18 -39
- package/lib/providers.js +5 -2
- package/lib/token-usage.js +19 -4
- package/package.json +2 -1
- package/plugins/rlm/lib/budget.js +2 -7
- package/plugins/rlm/lib/planner-prompt.js +1 -7
- package/plugins/rlm/lib/plugin.js +1 -6
- package/plugins/rlm/lib/validation.js +14 -0
- package/plugins/rlm/lib/variable-descriptors.js +1 -5
- package/plugins/rlm/lib/worker-catalog.js +1 -5
- package/pnpm-workspace.yaml +10 -5
- package/schema.js +0 -28
- package/skills/modelmix/SKILL.md +10 -6
- package/test/anthropic.test.js +77 -1
- package/test/bottleneck.test.js +2 -2
- package/test/effort.test.js +24 -1
- package/test/fallback.test.js +101 -1
- package/test/glm.test.js +2 -6
- package/test/kimi.test.js +2 -6
- package/test/live.test.js +1 -1
- package/test/muse.test.js +24 -7
- package/test/provider-expansion.test.js +17 -0
- package/test/public-api.test.js +2 -2
- package/test/qwen.test.js +2 -3
- package/test/tokens.test.js +23 -8
package/test/effort.test.js
CHANGED
|
@@ -77,6 +77,7 @@ describe('Unified effort scale', () => {
|
|
|
77
77
|
expect(mapEffort('openai', 99, 'gpt-5.6-luna')).to.deep.equal({ reasoning_effort: 'xhigh' });
|
|
78
78
|
for (const model of ['gpt-5.6-sol', 'gpt-5.6-terra', 'gpt-5.6-luna']) {
|
|
79
79
|
expect(mapEffort('openai', 100, model)).to.deep.equal({ reasoning_effort: 'max' });
|
|
80
|
+
expect(mapEffort('openai', 100, `openai/${model}`)).to.deep.equal({ reasoning_effort: 'max' });
|
|
80
81
|
}
|
|
81
82
|
});
|
|
82
83
|
|
|
@@ -87,6 +88,8 @@ describe('Unified effort scale', () => {
|
|
|
87
88
|
|
|
88
89
|
it('clamps OpenAI to model-supported levels', () => {
|
|
89
90
|
expect(mapEffort('openai', 10, 'gpt-5.3-codex')).to.deep.equal({ reasoning_effort: 'low' });
|
|
91
|
+
expect(mapEffort('openai', 10, 'openai/gpt-5.3-codex')).to.deep.equal({ reasoning_effort: 'low' });
|
|
92
|
+
expect(mapEffort('openai', 0, 'openai/gpt-5')).to.deep.equal({ reasoning_effort: 'minimal' });
|
|
90
93
|
expect(mapEffort('openai', 10, 'gpt-oss-120b')).to.deep.equal({ reasoning_effort: 'low' });
|
|
91
94
|
});
|
|
92
95
|
|
|
@@ -132,6 +135,16 @@ describe('Unified effort scale', () => {
|
|
|
132
135
|
});
|
|
133
136
|
});
|
|
134
137
|
|
|
138
|
+
it('maps Muse Spark 1.2 Contributor to every supported reasoning level', () => {
|
|
139
|
+
const key = 'meta/muse-spark-1.2-contributor';
|
|
140
|
+
expect(mapEffort('openai', 0, key)).to.deep.equal({ reasoning_effort: 'minimal' });
|
|
141
|
+
expect(mapEffort('openai', 20, key)).to.deep.equal({ reasoning_effort: 'low' });
|
|
142
|
+
expect(mapEffort('openai', 40, key)).to.deep.equal({ reasoning_effort: 'medium' });
|
|
143
|
+
expect(mapEffort('openai', 60, key)).to.deep.equal({ reasoning_effort: 'high' });
|
|
144
|
+
expect(mapEffort('openai', 80, key)).to.deep.equal({ reasoning_effort: 'xhigh' });
|
|
145
|
+
expect(mapEffort('openai', -1, key)).to.equal(null);
|
|
146
|
+
});
|
|
147
|
+
|
|
135
148
|
it('maps Qwen 3.8 27B to its supported reasoning levels', () => {
|
|
136
149
|
const key = 'qwen/qwen3.8-27b';
|
|
137
150
|
expect(mapEffort('openai', 0, key)).to.deep.equal({ reasoning_effort: 'low' });
|
|
@@ -171,7 +184,7 @@ describe('Unified effort scale', () => {
|
|
|
171
184
|
thinking: { type: 'adaptive', display: 'summarized' },
|
|
172
185
|
output_config: { effort: 'low' }
|
|
173
186
|
});
|
|
174
|
-
expect(mapEffort('anthropic', 90, 'claude-fable-5')).to.deep.equal({
|
|
187
|
+
expect(mapEffort('anthropic', 90, 'claude-fable-5-1')).to.deep.equal({
|
|
175
188
|
thinking: { type: 'adaptive', display: 'summarized' },
|
|
176
189
|
output_config: { effort: 'max' }
|
|
177
190
|
});
|
|
@@ -181,6 +194,16 @@ describe('Unified effort scale', () => {
|
|
|
181
194
|
});
|
|
182
195
|
});
|
|
183
196
|
|
|
197
|
+
it('maps OpenRouter Claude Fable 5.1 to Anthropic effort levels', () => {
|
|
198
|
+
const key = 'anthropic/claude-fable-5.1';
|
|
199
|
+
expect(mapEffort('openai', 0, key)).to.deep.equal({ reasoning_effort: 'low' });
|
|
200
|
+
expect(mapEffort('openai', 20, key)).to.deep.equal({ reasoning_effort: 'medium' });
|
|
201
|
+
expect(mapEffort('openai', 40, key)).to.deep.equal({ reasoning_effort: 'high' });
|
|
202
|
+
expect(mapEffort('openai', 60, key)).to.deep.equal({ reasoning_effort: 'xhigh' });
|
|
203
|
+
expect(mapEffort('openai', 80, key)).to.deep.equal({ reasoning_effort: 'max' });
|
|
204
|
+
expect(mapEffort('openai', -1, key)).to.equal(null);
|
|
205
|
+
});
|
|
206
|
+
|
|
184
207
|
it('maps Anthropic manual models to thinking.type=enabled + budget_tokens', () => {
|
|
185
208
|
expect(mapEffort('anthropic', 50, 'claude-sonnet-4-5-20250929')).to.deep.equal({
|
|
186
209
|
thinking: { type: 'enabled', budget_tokens: 8192 }
|
package/test/fallback.test.js
CHANGED
|
@@ -3,7 +3,14 @@ const sinon = require('sinon');
|
|
|
3
3
|
const nock = require('nock');
|
|
4
4
|
const { EventEmitter } = require('events');
|
|
5
5
|
const Module = require('module');
|
|
6
|
-
const {
|
|
6
|
+
const {
|
|
7
|
+
MixCustom,
|
|
8
|
+
MixGoogle,
|
|
9
|
+
MixOpenAI,
|
|
10
|
+
MixOpenAIResponses,
|
|
11
|
+
MixOpenRouter,
|
|
12
|
+
ModelMix
|
|
13
|
+
} = require('../index.js');
|
|
7
14
|
|
|
8
15
|
describe('Provider Fallback Chain Tests', () => {
|
|
9
16
|
|
|
@@ -47,6 +54,99 @@ describe('Provider Fallback Chain Tests', () => {
|
|
|
47
54
|
expect(model.models[1].provider.config.effort).to.equal(20);
|
|
48
55
|
});
|
|
49
56
|
|
|
57
|
+
it('should register every supported OpenAI text shortcut with its OpenRouter fallback', () => {
|
|
58
|
+
expect(ModelMix.new().mix.openrouter).to.equal(false);
|
|
59
|
+
const shortcuts = [
|
|
60
|
+
['gpt5', 'gpt-5', 'openai/gpt-5', MixOpenAI],
|
|
61
|
+
['gpt5mini', 'gpt-5-mini', 'openai/gpt-5-mini', MixOpenAI],
|
|
62
|
+
['gpt5nano', 'gpt-5-nano', 'openai/gpt-5-nano', MixOpenAI],
|
|
63
|
+
['gpt51', 'gpt-5.1', 'openai/gpt-5.1', MixOpenAIResponses],
|
|
64
|
+
['gpt52', 'gpt-5.2', 'openai/gpt-5.2', MixOpenAIResponses],
|
|
65
|
+
['gpt53codex', 'gpt-5.3-codex', 'openai/gpt-5.3-codex', MixOpenAIResponses],
|
|
66
|
+
['gpt53chat', 'gpt-5.3-chat-latest', 'openai/gpt-5.3-chat', MixOpenAIResponses],
|
|
67
|
+
['gpt54', 'gpt-5.4', 'openai/gpt-5.4', MixOpenAIResponses],
|
|
68
|
+
['gpt54mini', 'gpt-5.4-mini', 'openai/gpt-5.4-mini', MixOpenAIResponses],
|
|
69
|
+
['gpt54nano', 'gpt-5.4-nano', 'openai/gpt-5.4-nano', MixOpenAIResponses],
|
|
70
|
+
['gpt54pro', 'gpt-5.4-pro', 'openai/gpt-5.4-pro', MixOpenAIResponses],
|
|
71
|
+
['gpt55', 'gpt-5.5', 'openai/gpt-5.5', MixOpenAIResponses],
|
|
72
|
+
['gpt55pro', 'gpt-5.5-pro', 'openai/gpt-5.5-pro', MixOpenAIResponses],
|
|
73
|
+
['gpt56sol', 'gpt-5.6-sol', 'openai/gpt-5.6-sol', MixOpenAIResponses],
|
|
74
|
+
['gpt56terra', 'gpt-5.6-terra', 'openai/gpt-5.6-terra', MixOpenAIResponses],
|
|
75
|
+
['gpt56luna', 'gpt-5.6-luna', 'openai/gpt-5.6-luna', MixOpenAIResponses]
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
for (const [shortcut, officialKey, openRouterKey, OfficialProvider] of shortcuts) {
|
|
79
|
+
const official = ModelMix.new()[shortcut]();
|
|
80
|
+
expect(official.models.map(({ key }) => key)).to.deep.equal([officialKey]);
|
|
81
|
+
expect(official.models[0].provider).to.be.instanceOf(OfficialProvider);
|
|
82
|
+
|
|
83
|
+
const routed = ModelMix.new()[shortcut]({ mix: { openrouter: true } });
|
|
84
|
+
expect(routed.models.map(({ key }) => key)).to.deep.equal([officialKey, openRouterKey]);
|
|
85
|
+
expect(routed.models[0].provider).to.be.instanceOf(OfficialProvider);
|
|
86
|
+
expect(routed.models[1].provider).to.be.instanceOf(MixOpenRouter);
|
|
87
|
+
|
|
88
|
+
const globallyRouted = ModelMix.new({ mix: { openrouter: true } })[shortcut]();
|
|
89
|
+
expect(globallyRouted.models.map(({ key }) => key)).to.deep.equal([officialKey, openRouterKey]);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const locallyDisabled = ModelMix.new({ mix: { openrouter: true } })
|
|
93
|
+
.gpt56sol({ mix: { openrouter: false } });
|
|
94
|
+
expect(locallyDisabled.models.map(({ key }) => key)).to.deep.equal(['gpt-5.6-sol']);
|
|
95
|
+
|
|
96
|
+
const inherited = ModelMix.new({ mix: { openrouter: true } }).new().gpt5();
|
|
97
|
+
expect(inherited.models.map(({ key }) => key)).to.deep.equal([
|
|
98
|
+
'gpt-5',
|
|
99
|
+
'openai/gpt-5'
|
|
100
|
+
]);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should append OpenRouter GPT fallbacks in chain() only when enabled globally', () => {
|
|
104
|
+
const official = ModelMix.new().chain('gpt56sol@100');
|
|
105
|
+
expect(official.models.map(({ key }) => key)).to.deep.equal(['gpt-5.6-sol']);
|
|
106
|
+
|
|
107
|
+
const routed = ModelMix.new({ mix: { openrouter: true } }).chain('gpt56sol@100');
|
|
108
|
+
expect(routed.models.map(({ key }) => key)).to.deep.equal([
|
|
109
|
+
'gpt-5.6-sol',
|
|
110
|
+
'openai/gpt-5.6-sol'
|
|
111
|
+
]);
|
|
112
|
+
expect(routed.models.map(({ provider }) => provider.config.effort)).to.deep.equal([100, 100]);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should fallback from the official GPT-5.6 Sol endpoint to OpenRouter', async () => {
|
|
116
|
+
let openRouterRequest;
|
|
117
|
+
model.gpt56sol({ mix: { openrouter: true } }).addText('Hello');
|
|
118
|
+
|
|
119
|
+
nock('https://api.openai.com')
|
|
120
|
+
.post('/v1/responses')
|
|
121
|
+
.reply(503, { error: 'Service unavailable' });
|
|
122
|
+
|
|
123
|
+
nock('https://openrouter.ai')
|
|
124
|
+
.post('/api/v1/chat/completions', body => {
|
|
125
|
+
openRouterRequest = body;
|
|
126
|
+
return true;
|
|
127
|
+
})
|
|
128
|
+
.reply(200, {
|
|
129
|
+
choices: [{
|
|
130
|
+
message: {
|
|
131
|
+
role: 'assistant',
|
|
132
|
+
content: 'Hello from GPT-5.6 Sol through OpenRouter!'
|
|
133
|
+
}
|
|
134
|
+
}]
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
expect(await model.message()).to.equal('Hello from GPT-5.6 Sol through OpenRouter!');
|
|
138
|
+
expect(openRouterRequest.model).to.equal('openai/gpt-5.6-sol');
|
|
139
|
+
expect(openRouterRequest.max_completion_tokens).to.equal(8192);
|
|
140
|
+
expect(openRouterRequest).to.not.have.property('temperature');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should keep the default fable51 chain on Anthropic', () => {
|
|
144
|
+
model.chain('fable51@80');
|
|
145
|
+
|
|
146
|
+
expect(model.models.map(({ key }) => key)).to.deep.equal(['claude-fable-5-1']);
|
|
147
|
+
expect(model.models[0].provider.config.effort).to.equal(80);
|
|
148
|
+
});
|
|
149
|
+
|
|
50
150
|
it('should reject invalid chains before attaching any model', () => {
|
|
51
151
|
expect(() => model.chain('gpt56luna', 'unknownModel'))
|
|
52
152
|
.to.throw('Unknown model shortcut "unknownModel" in chain().');
|
package/test/glm.test.js
CHANGED
|
@@ -2,16 +2,12 @@ const { expect } = require('chai');
|
|
|
2
2
|
const { ModelMix, MixFireworks, MixOpenRouter, MixTogether } = require('../index.js');
|
|
3
3
|
|
|
4
4
|
describe('GLM Model Registration Tests', () => {
|
|
5
|
-
it('should register Together GLM 5.2
|
|
5
|
+
it('should register only Together GLM 5.2 by default', () => {
|
|
6
6
|
const model = ModelMix.new();
|
|
7
7
|
model.GLM52();
|
|
8
8
|
|
|
9
|
-
expect(model.models.map(({ key }) => key)).to.deep.equal([
|
|
10
|
-
'zai-org/GLM-5.2',
|
|
11
|
-
'z-ai/glm-5.2'
|
|
12
|
-
]);
|
|
9
|
+
expect(model.models.map(({ key }) => key)).to.deep.equal(['zai-org/GLM-5.2']);
|
|
13
10
|
expect(model.models[0].provider).to.be.instanceOf(MixTogether);
|
|
14
|
-
expect(model.models[1].provider).to.be.instanceOf(MixOpenRouter);
|
|
15
11
|
});
|
|
16
12
|
|
|
17
13
|
it('should register every requested GLM 5.2 provider', () => {
|
package/test/kimi.test.js
CHANGED
|
@@ -3,16 +3,12 @@ const nock = require('nock');
|
|
|
3
3
|
const { ModelMix, MixFireworks, MixKimi, MixOpenRouter, MixTogether } = require('../index.js');
|
|
4
4
|
|
|
5
5
|
describe('Kimi Model Registration Tests', () => {
|
|
6
|
-
it('should register Together Kimi K2.7 Code
|
|
6
|
+
it('should register only Together Kimi K2.7 Code by default', () => {
|
|
7
7
|
const model = ModelMix.new();
|
|
8
8
|
model.kimiK27Code();
|
|
9
9
|
|
|
10
|
-
expect(model.models.map(({ key }) => key)).to.deep.equal([
|
|
11
|
-
'moonshotai/Kimi-K2.7-Code',
|
|
12
|
-
'moonshotai/kimi-k2.7-code'
|
|
13
|
-
]);
|
|
10
|
+
expect(model.models.map(({ key }) => key)).to.deep.equal(['moonshotai/Kimi-K2.7-Code']);
|
|
14
11
|
expect(model.models[0].provider).to.be.instanceOf(MixTogether);
|
|
15
|
-
expect(model.models[1].provider).to.be.instanceOf(MixOpenRouter);
|
|
16
12
|
});
|
|
17
13
|
|
|
18
14
|
it('should register every requested Kimi K2.7 Code provider', () => {
|
package/test/live.test.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const { expect } = require('chai');
|
|
2
|
-
const { ModelMix, MixOpenAI, MixAnthropic
|
|
2
|
+
const { ModelMix, MixOpenAI, MixAnthropic } = require('../index.js');
|
|
3
3
|
const nock = require('nock');
|
|
4
4
|
|
|
5
5
|
const blueSquareBase64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAMAAABHPGVmAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDkuMS1jMDAzIDc5Ljk2OTBhODdmYywgMjAyNS8wMy8wNi0yMDo1MDoxNiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDI2LjkgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6REM2QzQ3NEQ2Q0I5MTFGMDlBRTVGQzcwQjMyMkY4MDciIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6REM2QzQ3NEU2Q0I5MTFGMDlBRTVGQzcwQjMyMkY4MDciPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpEQzZDNDc0QjZDQjkxMUYwOUFFNUZDNzBCMzIyRjgwNyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpEQzZDNDc0QzZDQjkxMUYwOUFFNUZDNzBCMzIyRjgwNyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PvArxh0AAAAGUExURQAA/wAAAHtivz4AAAAjSURBVHja7MGBAAAAAMOg+VNf4QBVAQAAAAAAAAAAAI8JMAAndAABi7SX2gAAAABJRU5ErkJggg==';
|
package/test/muse.test.js
CHANGED
|
@@ -7,16 +7,14 @@ const {
|
|
|
7
7
|
MixTogether
|
|
8
8
|
} = require('../index.js');
|
|
9
9
|
|
|
10
|
-
describe('Muse
|
|
11
|
-
it('registers Fireworks
|
|
10
|
+
describe('Muse Model Registration Tests', () => {
|
|
11
|
+
it('registers only Fireworks by default', () => {
|
|
12
12
|
const model = ModelMix.new().museGlimmer30b();
|
|
13
13
|
|
|
14
14
|
expect(model.models.map(({ key }) => key)).to.deep.equal([
|
|
15
|
-
'accounts/fireworks/models/muse-glimmer-30b'
|
|
16
|
-
'meta/muse-glimmer-30b'
|
|
15
|
+
'accounts/fireworks/models/muse-glimmer-30b'
|
|
17
16
|
]);
|
|
18
17
|
expect(model.models[0].provider).to.be.instanceOf(MixFireworks);
|
|
19
|
-
expect(model.models[1].provider).to.be.instanceOf(MixOpenRouter);
|
|
20
18
|
});
|
|
21
19
|
|
|
22
20
|
it('registers every supported provider in fallback order', () => {
|
|
@@ -54,8 +52,27 @@ describe('Muse Glimmer Model Registration Tests', () => {
|
|
|
54
52
|
const model = ModelMix.new().chain('museGlimmer30b');
|
|
55
53
|
|
|
56
54
|
expect(model.models.map(({ key }) => key)).to.deep.equal([
|
|
57
|
-
'accounts/fireworks/models/muse-glimmer-30b'
|
|
58
|
-
'meta/muse-glimmer-30b'
|
|
55
|
+
'accounts/fireworks/models/muse-glimmer-30b'
|
|
59
56
|
]);
|
|
60
57
|
});
|
|
58
|
+
|
|
59
|
+
it('registers Muse Spark 1.2 Contributor through OpenRouter', () => {
|
|
60
|
+
const model = ModelMix.new().museSpark12Contributor();
|
|
61
|
+
|
|
62
|
+
expect(model.models).to.have.length(1);
|
|
63
|
+
expect(model.models[0].key).to.equal('meta/muse-spark-1.2-contributor');
|
|
64
|
+
expect(model.models[0].provider).to.be.instanceOf(MixOpenRouter);
|
|
65
|
+
expect(ModelMix.calculateCost('meta/muse-spark-1.2-contributor', {
|
|
66
|
+
input: 1_000_000,
|
|
67
|
+
cached: 250_000,
|
|
68
|
+
output: 1_000_000
|
|
69
|
+
})).to.equal(0.2755);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('supports Muse Spark 1.2 Contributor in chain()', () => {
|
|
73
|
+
const model = ModelMix.new().chain('museSpark12Contributor');
|
|
74
|
+
|
|
75
|
+
expect(model.models).to.have.length(1);
|
|
76
|
+
expect(model.models[0].key).to.equal('meta/muse-spark-1.2-contributor');
|
|
77
|
+
});
|
|
61
78
|
});
|
|
@@ -9,6 +9,23 @@ const {
|
|
|
9
9
|
} = require('../index.js');
|
|
10
10
|
|
|
11
11
|
describe('Provider expansion regressions', () => {
|
|
12
|
+
it('should keep OpenRouter fallbacks disabled by default', () => {
|
|
13
|
+
const originalMiniMaxApiKey = process.env.MINIMAX_API_KEY;
|
|
14
|
+
process.env.MINIMAX_API_KEY = 'test-minimax-key';
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const gptOss = ModelMix.new().gptOss();
|
|
18
|
+
const minimax = ModelMix.new().minimaxM27();
|
|
19
|
+
|
|
20
|
+
expect(gptOss.models.some(({ provider }) => provider instanceof MixOpenRouter)).to.equal(false);
|
|
21
|
+
expect(minimax.models.map(({ key }) => key)).to.deep.equal(['MiniMax-M2.7']);
|
|
22
|
+
expect(minimax.models[0].provider).to.be.instanceOf(MixMiniMax);
|
|
23
|
+
} finally {
|
|
24
|
+
if (originalMiniMaxApiKey === undefined) delete process.env.MINIMAX_API_KEY;
|
|
25
|
+
else process.env.MINIMAX_API_KEY = originalMiniMaxApiKey;
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
12
29
|
it('should retain every enabled GPT OSS provider, including shared model keys', () => {
|
|
13
30
|
const model = ModelMix.new().gptOss({
|
|
14
31
|
mix: {
|
package/test/public-api.test.js
CHANGED
|
@@ -14,6 +14,7 @@ describe('public module boundary', () => {
|
|
|
14
14
|
'MixGroq',
|
|
15
15
|
'MixKimi',
|
|
16
16
|
'MixLMStudio',
|
|
17
|
+
'MixLambda',
|
|
17
18
|
'MixMiMo',
|
|
18
19
|
'MixMiniMax',
|
|
19
20
|
'MixModeration',
|
|
@@ -57,8 +58,7 @@ describe('public module boundary', () => {
|
|
|
57
58
|
['MiMo', config => new api.MixMiMo({ config })],
|
|
58
59
|
['Perplexity', config => new api.MixPerplexity({ config })],
|
|
59
60
|
['Grok', config => new api.MixGrok({ config })],
|
|
60
|
-
['Lambda', config => api.
|
|
61
|
-
.hermes3({ config }).models[0].provider],
|
|
61
|
+
['Lambda', config => new api.MixLambda({ config })],
|
|
62
62
|
['Groq', config => new api.MixGroq({ config })],
|
|
63
63
|
['Together', config => new api.MixTogether({ config })],
|
|
64
64
|
['Cerebras', config => new api.MixCerebras({ config })],
|
package/test/qwen.test.js
CHANGED
|
@@ -67,13 +67,12 @@ describe('Qwen Model Registration Tests', () => {
|
|
|
67
67
|
})).to.equal(4.08576);
|
|
68
68
|
});
|
|
69
69
|
|
|
70
|
-
it('should register Fireworks Qwen 3.8 Max
|
|
70
|
+
it('should register only Fireworks Qwen 3.8 Max by default', () => {
|
|
71
71
|
const model = ModelMix.new();
|
|
72
72
|
model.qwen38max();
|
|
73
73
|
|
|
74
74
|
expect(model.models.map(({ key }) => key)).to.deep.equal([
|
|
75
|
-
'accounts/fireworks/models/qwen3p8-2p4t-a95b'
|
|
76
|
-
'qwen/qwen3.8-max'
|
|
75
|
+
'accounts/fireworks/models/qwen3p8-2p4t-a95b'
|
|
77
76
|
]);
|
|
78
77
|
expect(ModelMix.calculateCost('accounts/fireworks/models/qwen3p8-2p4t-a95b', {
|
|
79
78
|
input: 1_000_000,
|
package/test/tokens.test.js
CHANGED
|
@@ -321,32 +321,47 @@ describe('Token Usage Tracking', () => {
|
|
|
321
321
|
});
|
|
322
322
|
|
|
323
323
|
it('should register GPT-5.5 shortcuts with OpenAI Responses provider', function () {
|
|
324
|
-
const model = ModelMix.new()
|
|
324
|
+
const model = ModelMix.new({ mix: { openrouter: true } })
|
|
325
325
|
.gpt55()
|
|
326
326
|
.gpt55pro();
|
|
327
327
|
|
|
328
|
-
expect(model.models).to.
|
|
329
|
-
|
|
330
|
-
|
|
328
|
+
expect(model.models.map(({ key }) => key)).to.deep.equal([
|
|
329
|
+
'gpt-5.5',
|
|
330
|
+
'openai/gpt-5.5',
|
|
331
|
+
'gpt-5.5-pro',
|
|
332
|
+
'openai/gpt-5.5-pro'
|
|
333
|
+
]);
|
|
331
334
|
expect(model.models[0].provider).to.be.instanceOf(MixOpenAIResponses);
|
|
332
|
-
expect(model.models[1].provider).to.be.instanceOf(
|
|
335
|
+
expect(model.models[1].provider).to.be.instanceOf(MixOpenRouter);
|
|
336
|
+
expect(model.models[2].provider).to.be.instanceOf(MixOpenAIResponses);
|
|
337
|
+
expect(model.models[3].provider).to.be.instanceOf(MixOpenRouter);
|
|
333
338
|
});
|
|
334
339
|
|
|
335
340
|
it('should register GPT-5.6 shortcuts with OpenAI Responses provider', function () {
|
|
336
|
-
const model = ModelMix.new()
|
|
341
|
+
const model = ModelMix.new({ mix: { openrouter: true } })
|
|
337
342
|
.gpt56sol()
|
|
338
343
|
.gpt56terra()
|
|
339
344
|
.gpt56luna();
|
|
340
345
|
|
|
341
346
|
expect(model.models.map(({ key }) => key)).to.deep.equal([
|
|
342
347
|
'gpt-5.6-sol',
|
|
348
|
+
'openai/gpt-5.6-sol',
|
|
343
349
|
'gpt-5.6-terra',
|
|
344
|
-
'gpt-5.6-
|
|
350
|
+
'openai/gpt-5.6-terra',
|
|
351
|
+
'gpt-5.6-luna',
|
|
352
|
+
'openai/gpt-5.6-luna'
|
|
345
353
|
]);
|
|
346
|
-
expect(model.models.
|
|
354
|
+
expect(model.models[0].provider).to.be.instanceOf(MixOpenAIResponses);
|
|
355
|
+
expect(model.models[1].provider).to.be.instanceOf(MixOpenRouter);
|
|
356
|
+
expect(model.models[2].provider).to.be.instanceOf(MixOpenAIResponses);
|
|
357
|
+
expect(model.models[3].provider).to.be.instanceOf(MixOpenRouter);
|
|
358
|
+
expect(model.models[4].provider).to.be.instanceOf(MixOpenAIResponses);
|
|
359
|
+
expect(model.models[5].provider).to.be.instanceOf(MixOpenRouter);
|
|
347
360
|
expect(ModelMix.calculateCost('gpt-5.6-sol', { input: 1_000_000, output: 1_000_000 })).to.equal(55);
|
|
361
|
+
expect(ModelMix.calculateCost('openai/gpt-5.6-sol', { input: 1_000_000, output: 1_000_000 })).to.equal(55);
|
|
348
362
|
expect(ModelMix.calculateCost('gpt-5.6-terra', { input: 1_000_000, output: 1_000_000 })).to.equal(22);
|
|
349
363
|
expect(ModelMix.calculateCost('gpt-5.6-luna', { input: 1_000_000, output: 1_000_000 })).to.equal(2.2);
|
|
364
|
+
expect(ModelMix.calculateCost('openai/gpt-5.3-chat', { input: 1_000_000, output: 1_000_000 })).to.equal(15.75);
|
|
350
365
|
});
|
|
351
366
|
|
|
352
367
|
it('should calculate GPT-5.6 cache reads and writes at their actual rates', function () {
|