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/README.md
CHANGED
|
@@ -119,10 +119,9 @@ console.log(ETH.price);
|
|
|
119
119
|
|
|
120
120
|
**This example uses providers with free quotas (Groq, Cerebras, and Together). OpenRouter is disabled because its GPT-OSS 120B route is no longer free. If one model runs out of quota, ModelMix automatically falls back to the next model in the chain.**
|
|
121
121
|
```javascript
|
|
122
|
-
ModelMix.new(
|
|
122
|
+
ModelMix.new()
|
|
123
123
|
.gptOss()
|
|
124
124
|
.kimiK25()
|
|
125
|
-
.hermes3()
|
|
126
125
|
.addText('What is the capital of France?');
|
|
127
126
|
```
|
|
128
127
|
|
|
@@ -143,15 +142,20 @@ ModelMix provides convenient shorthand methods for quickly accessing different A
|
|
|
143
142
|
| `gpt56terra()` | OpenAI | gpt-5.6-terra | [\$2.00][1] | [\$12.00][1] |
|
|
144
143
|
| `gpt56luna()` | OpenAI | gpt-5.6-luna | [\$0.20][1] | [\$1.20][1] |
|
|
145
144
|
| `gpt55()` | OpenAI | gpt-5.5 | [\$5.00][1] | [\$30.00][1] |
|
|
145
|
+
| `gpt55pro()` | OpenAI | gpt-5.5-pro | [\$30.00][1] | [\$180.00][1] |
|
|
146
146
|
| `gpt54()` | OpenAI | gpt-5.4 | [\$2.50][1] | [\$15.00][1] |
|
|
147
147
|
| `gpt54mini()` | OpenAI | gpt-5.4-mini | [\$0.75][1] | [\$4.50][1] |
|
|
148
148
|
| `gpt54nano()` | OpenAI | gpt-5.4-nano | [\$0.20][1] | [\$1.25][1] |
|
|
149
|
-
| `
|
|
149
|
+
| `gpt54pro()` | OpenAI | gpt-5.4-pro | [\$30.00][1] | [\$180.00][1] |
|
|
150
|
+
| `gpt53codex()` | OpenAI | gpt-5.3-codex | [\$1.75][1] | [\$14.00][1] |
|
|
151
|
+
| `gpt53chat()` | OpenAI | gpt-5.3-chat-latest | [\$1.75][1] | [\$14.00][1] |
|
|
150
152
|
| `gpt52()` | OpenAI | gpt-5.2 | [\$1.75][1] | [\$14.00][1] |
|
|
151
153
|
| `gpt51()` | OpenAI | gpt-5.1 | [\$1.25][1] | [\$10.00][1] |
|
|
154
|
+
| `gpt5()` | OpenAI | gpt-5 | [\$1.25][1] | [\$10.00][1] |
|
|
152
155
|
| `gpt5mini()` | OpenAI | gpt-5-mini | [\$0.25][1] | [\$2.00][1] |
|
|
153
156
|
| `gpt5nano()` | OpenAI | gpt-5-nano | [\$0.05][1] | [\$0.40][1] |
|
|
154
|
-
| `gptOss()` |
|
|
157
|
+
| `gptOss()` | Multi-provider | gpt-oss-120B | [\$0.15][7] | [\$0.60][7] |
|
|
158
|
+
| `fable51()` | Anthropic | claude-fable-5-1 | [\$10.00][2] | [\$50.00][2] |
|
|
155
159
|
| `fable5()` | Anthropic | claude-fable-5 | [\$10.00][2] | [\$50.00][2] |
|
|
156
160
|
| `opus5()` | Anthropic | claude-opus-5 | [\$5.00][2] | [\$25.00][2] |
|
|
157
161
|
| `opus48()` | Anthropic | claude-opus-4-8 | [\$5.00][2] | [\$25.00][2] |
|
|
@@ -172,6 +176,7 @@ ModelMix provides convenient shorthand methods for quickly accessing different A
|
|
|
172
176
|
| `grok420multiAgent()` | Grok | grok-4.20-multi-agent-0309 | [\$1.25][6] | [\$2.50][6] |
|
|
173
177
|
| `grok420()` | Grok | grok-4.20-0309 (†) | [\$1.25][6] | [\$2.50][6] |
|
|
174
178
|
| `museGlimmer30b()` | Fireworks | models/muse-glimmer-30b | [\$0.35][17] | [\$1.50][17] |
|
|
179
|
+
| `museSpark12Contributor()` | OpenRouter | meta/muse-spark-1.2-contributor | [\$0.10][22] | [\$0.20][22] |
|
|
175
180
|
| `qwen35397b()` | OpenRouter | qwen/qwen3.5-397b-a17b | [\$0.385][14] | [\$2.45][14] |
|
|
176
181
|
| `qwen36plus()` | OpenRouter | qwen/qwen3.6-plus | [\$0.325][18] | [\$1.95][18] |
|
|
177
182
|
| `qwen37plus()` | Fireworks | models/qwen3p7-plus | [\$0.40][10] | [\$1.60][10] |
|
|
@@ -197,9 +202,13 @@ ModelMix provides convenient shorthand methods for quickly accessing different A
|
|
|
197
202
|
|
|
198
203
|
Gemini 3.7 Flash and 3.6 Flash use Google's introductory standard pricing through December 31, 2026; standard rates double on January 1, 2027.
|
|
199
204
|
|
|
200
|
-
`museGlimmer30b()` uses Fireworks
|
|
205
|
+
`museGlimmer30b()` uses Fireworks by default. OpenRouter, NVIDIA NIM, and Together are available through `mix.openrouter`, `mix.nvidia`, and `mix.together`.
|
|
201
206
|
|
|
202
|
-
|
|
207
|
+
`fable51()` uses the official Anthropic API by default (`claude-fable-5-1`). Pass `mix: { openrouter: true }` to append [`anthropic/claude-fable-5.1`][21] as its fallback.
|
|
208
|
+
|
|
209
|
+
Every textual GPT-5 shortcut in the table uses the official OpenAI API by default. Pass `mix: { openrouter: true }` to `ModelMix.new()` or to an individual shortcut to append the matching [`openai/*` OpenRouter route][23] as its fallback. `gpt53chat()` maps the official `gpt-5.3-chat-latest` alias to `openai/gpt-5.3-chat`. Realtime shortcuts remain official-only because they use OpenAI's WebSocket transport.
|
|
210
|
+
|
|
211
|
+
OpenRouter fallbacks are disabled globally by default and are appended only with `mix.openrouter: true`. Shortcuts whose primary provider is OpenRouter, such as `qwen36plus()`, are unaffected. The multi-provider shortcuts also expose the current catalog alternatives: `gptOss()` supports NVIDIA and Fireworks; `qwen37plus()` supports Together; `kimiK27Code()` supports Fireworks and OpenRouter; `kimiK3()` supports Fireworks, OpenRouter, and Together; `GLM52()` supports Fireworks and OpenRouter; and both MiniMax shortcuts support Fireworks. `minimaxM27()` keeps every explicitly enabled provider in its fallback chain.
|
|
203
212
|
|
|
204
213
|
[1]: https://platform.openai.com/docs/pricing "Pricing | OpenAI"
|
|
205
214
|
[2]: https://docs.anthropic.com/en/docs/about-claude/pricing "Pricing - Anthropic"
|
|
@@ -221,6 +230,9 @@ The multi-provider shortcuts also expose the current catalog alternatives: `gptO
|
|
|
221
230
|
[18]: https://openrouter.ai/qwen/qwen3.6-plus "Qwen 3.6 Plus on OpenRouter"
|
|
222
231
|
[19]: https://openrouter.ai/qwen/qwen3.8-flash "Qwen3.8 Flash on OpenRouter"
|
|
223
232
|
[20]: https://openrouter.ai/z-ai/glm-5.3-flash "GLM 5.3 Flash on OpenRouter"
|
|
233
|
+
[21]: https://openrouter.ai/anthropic/claude-fable-5.1 "Claude Fable 5.1 on OpenRouter"
|
|
234
|
+
[22]: https://openrouter.ai/meta/muse-spark-1.2-contributor "Muse Spark 1.2 Contributor on OpenRouter"
|
|
235
|
+
[23]: https://openrouter.ai/openai "OpenAI Models on OpenRouter"
|
|
224
236
|
|
|
225
237
|
Each method accepts optional `options`, `config`, and (for multi-provider methods) `mix` parameters to customize behavior.
|
|
226
238
|
|
package/demo/free.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { ModelMix } from '../index.js';
|
|
2
2
|
try { process.loadEnvFile(); } catch {}
|
|
3
3
|
|
|
4
|
-
const ai = ModelMix.new({ config: { debug: 2 }
|
|
4
|
+
const ai = ModelMix.new({ config: { debug: 2 } })
|
|
5
5
|
.gptOss()
|
|
6
6
|
.kimiK25()
|
|
7
|
-
.hermes3()
|
|
8
7
|
.addText('What is the capital of France?');
|
|
9
8
|
|
|
10
9
|
const response = await ai.message();
|
|
11
10
|
console.log('Response from a free-tier provider:', response);
|
|
12
|
-
|
package/demo/short.js
CHANGED
|
@@ -9,19 +9,21 @@ const setup = {
|
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
const mmix = await ModelMix.new(setup)
|
|
12
|
-
.
|
|
13
|
-
.
|
|
14
|
-
.
|
|
15
|
-
.
|
|
16
|
-
.
|
|
17
|
-
.
|
|
18
|
-
.
|
|
19
|
-
.
|
|
20
|
-
.
|
|
21
|
-
.
|
|
22
|
-
.
|
|
23
|
-
.
|
|
24
|
-
.
|
|
12
|
+
.fable51({ mix: { openrouter: true } }) // (main + provider fallback) Anthropic/OpenRouter Claude Fable 5.1
|
|
13
|
+
.sonnet46() // (fallback 2) Anthropic claude-sonnet-4-6
|
|
14
|
+
.gpt56luna({ mix: { openrouter: true } }) // (fallback 3 + provider fallback) OpenAI/OpenRouter gpt-5.6-luna
|
|
15
|
+
.gemini37flash() // (fallback 4) Google gemini-3.7-flash
|
|
16
|
+
.gpt5nano({ mix: { openrouter: true } }) // (fallback 5 + provider fallback) OpenAI/OpenRouter gpt-5-nano
|
|
17
|
+
.grok46() // (fallback 6) Grok grok-4.6
|
|
18
|
+
.qwen35397b() // (fallback 7) OpenRouter qwen/qwen3.5-397b-a17b
|
|
19
|
+
.qwen3827b() // (fallback 8) OpenRouter qwen/qwen3.8-27b
|
|
20
|
+
.qwen38flash() // (fallback 9) OpenRouter qwen/qwen3.8-flash
|
|
21
|
+
.GLM53() // (fallback 10) OpenRouter z-ai/glm-5.3
|
|
22
|
+
.GLM53Flash() // (fallback 11) OpenRouter z-ai/glm-5.3-flash
|
|
23
|
+
.museGlimmer30b({ mix: { fireworks: false, openrouter: true } }) // (fallback 12) OpenRouter meta/muse-glimmer-30b
|
|
24
|
+
.museSpark12Contributor() // (fallback 13) OpenRouter meta/muse-spark-1.2-contributor
|
|
25
|
+
.hermes470b() // (fallback 14) OpenRouter nousresearch/hermes-4-70b
|
|
26
|
+
.hermes4405b() // (fallback 15) OpenRouter nousresearch/hermes-4-405b
|
|
25
27
|
.addText("What's your name?");
|
|
26
28
|
|
|
27
29
|
console.log(await mmix.message());
|
package/effort.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
const OPENAI_LEVELS = ['none', 'low', 'medium', 'high', 'xhigh'];
|
|
10
|
-
const OPENAI_LEVEL_LADDER = [
|
|
10
|
+
const OPENAI_LEVEL_LADDER = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'];
|
|
11
11
|
const ANTHROPIC_LEVELS = ['low', 'medium', 'high', 'xhigh', 'max'];
|
|
12
12
|
const GEMINI_LEVELS = ['minimal', 'low', 'medium', 'high'];
|
|
13
13
|
|
|
@@ -45,9 +45,11 @@ const OPENAI_MODEL_LEVELS = {
|
|
|
45
45
|
'gpt-5.6-sol': ['none', 'low', 'medium', 'high', 'xhigh', 'max'],
|
|
46
46
|
'gpt-5.6-terra': ['none', 'low', 'medium', 'high', 'xhigh', 'max'],
|
|
47
47
|
'gpt-5.6-luna': ['none', 'low', 'medium', 'high', 'xhigh', 'max'],
|
|
48
|
+
'anthropic/claude-fable-5.1': ['low', 'medium', 'high', 'xhigh', 'max'],
|
|
48
49
|
'meta/muse-glimmer-30b': ['low', 'medium', 'high'],
|
|
49
50
|
'accounts/fireworks/models/muse-glimmer-30b': ['low', 'medium', 'high', 'xhigh'],
|
|
50
51
|
'meta-models/Muse-Glimmer-30B': ['low', 'medium', 'high', 'xhigh'],
|
|
52
|
+
'meta/muse-spark-1.2-contributor': ['minimal', 'low', 'medium', 'high', 'xhigh'],
|
|
51
53
|
'accounts/fireworks/models/qwen3p8-2p4t-a95b': ['none', 'low', 'medium', 'high'],
|
|
52
54
|
'qwen/qwen3.8-27b': ['low', 'medium', 'xhigh'],
|
|
53
55
|
'qwen/qwen3.8-flash': ['low', 'medium', 'xhigh'],
|
|
@@ -87,7 +89,6 @@ const DEEPSEEK_BANDS = [
|
|
|
87
89
|
* MiniMax M3 (OpenAI-compatible): thinking.type disabled | adaptive.
|
|
88
90
|
* @see https://platform.minimax.io/docs/api-reference/text-openai-api
|
|
89
91
|
*/
|
|
90
|
-
const MINIMAX_LEVELS = ['disabled', 'adaptive'];
|
|
91
92
|
const MINIMAX_BANDS = [
|
|
92
93
|
[0, 19, 'disabled'],
|
|
93
94
|
[20, 100, 'adaptive'],
|
|
@@ -203,8 +204,11 @@ function pickNearestLevel(desired, ladder, supported) {
|
|
|
203
204
|
}
|
|
204
205
|
|
|
205
206
|
function supportedOpenAILevels(modelKey) {
|
|
206
|
-
|
|
207
|
-
|
|
207
|
+
const canonicalModelKey = typeof modelKey === 'string' && modelKey.startsWith('openai/')
|
|
208
|
+
? modelKey.slice('openai/'.length)
|
|
209
|
+
: modelKey;
|
|
210
|
+
if (canonicalModelKey && OPENAI_MODEL_LEVELS[canonicalModelKey]) {
|
|
211
|
+
return OPENAI_MODEL_LEVELS[canonicalModelKey];
|
|
208
212
|
}
|
|
209
213
|
return OPENAI_LEVELS;
|
|
210
214
|
}
|
|
@@ -400,9 +404,11 @@ function mapEffort(providerFamily, effort, modelKey) {
|
|
|
400
404
|
const supported = supportedOpenAILevels(modelKey);
|
|
401
405
|
const desired = modelKey === 'z-ai/glm-5.3' || modelKey === 'z-ai/glm-5.3-flash'
|
|
402
406
|
? levelFromBands(normalized, GLM53_BANDS)
|
|
403
|
-
:
|
|
404
|
-
?
|
|
405
|
-
:
|
|
407
|
+
: modelKey === 'anthropic/claude-fable-5.1'
|
|
408
|
+
? levelFromBands(normalized, ANTHROPIC_BANDS)
|
|
409
|
+
: normalized === 100 && supported.includes('max')
|
|
410
|
+
? 'max'
|
|
411
|
+
: levelFromBands(normalized, OPENAI_BANDS);
|
|
406
412
|
const level = pickNearestLevel(desired, OPENAI_LEVEL_LADDER, supported);
|
|
407
413
|
return { reasoning_effort: level };
|
|
408
414
|
}
|
package/index.d.ts
CHANGED
|
@@ -76,6 +76,7 @@ export interface ModelMixConfig {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
export interface ModelMixMixFlags {
|
|
79
|
+
anthropic?: boolean;
|
|
79
80
|
openrouter?: boolean;
|
|
80
81
|
cerebras?: boolean;
|
|
81
82
|
groq?: boolean;
|
|
@@ -462,6 +463,7 @@ export declare class ModelMix {
|
|
|
462
463
|
gptOss(args?: ModelAttachArgs): this;
|
|
463
464
|
|
|
464
465
|
// Anthropic
|
|
466
|
+
fable51(args?: ModelAttachArgs): this;
|
|
465
467
|
fable50(args?: ModelAttachArgs): this;
|
|
466
468
|
fable5(args?: ModelAttachArgs): this;
|
|
467
469
|
opus50(args?: ModelAttachArgs): this;
|
|
@@ -497,6 +499,7 @@ export declare class ModelMix {
|
|
|
497
499
|
|
|
498
500
|
// Multi-provider
|
|
499
501
|
museGlimmer30b(args?: ModelAttachArgs): this;
|
|
502
|
+
museSpark12Contributor(args?: ModelAttachArgs): this;
|
|
500
503
|
qwen35397b(args?: ModelAttachArgs): this;
|
|
501
504
|
qwen36plus(args?: ModelAttachArgs): this;
|
|
502
505
|
qwen37plus(args?: ModelAttachArgs): this;
|
package/index.js
CHANGED
|
@@ -144,7 +144,7 @@ class ModelMix {
|
|
|
144
144
|
if (this.config.effort !== undefined && this.config.effort !== null) {
|
|
145
145
|
this.config.effort = normalizeEffort(this.config.effort);
|
|
146
146
|
}
|
|
147
|
-
const freeMix = { openrouter:
|
|
147
|
+
const freeMix = { openrouter: false, cerebras: true, groq: true, together: false, lambda: false };
|
|
148
148
|
this.mix = { ...freeMix, ...mix };
|
|
149
149
|
|
|
150
150
|
this.limiter = new Bottleneck(this.config.bottleneck);
|
|
@@ -434,47 +434,59 @@ class ModelMix {
|
|
|
434
434
|
return this;
|
|
435
435
|
}
|
|
436
436
|
|
|
437
|
-
|
|
438
|
-
|
|
437
|
+
_attachOpenAIWithOpenRouter(officialKey, Provider, {
|
|
438
|
+
options = {},
|
|
439
|
+
config = {},
|
|
440
|
+
mix = {},
|
|
441
|
+
openRouterKey = `openai/${officialKey}`
|
|
442
|
+
} = {}) {
|
|
443
|
+
mix = { ...this.mix, ...mix };
|
|
444
|
+
this.attach(officialKey, new Provider({ options, config }));
|
|
445
|
+
if (mix.openrouter) this.attach(openRouterKey, new MixOpenRouter({ options, config }));
|
|
446
|
+
return this;
|
|
439
447
|
}
|
|
440
|
-
|
|
441
|
-
|
|
448
|
+
|
|
449
|
+
gpt5(args = {}) {
|
|
450
|
+
return this._attachOpenAIWithOpenRouter('gpt-5', MixOpenAI, args);
|
|
451
|
+
}
|
|
452
|
+
gpt5mini(args = {}) {
|
|
453
|
+
return this._attachOpenAIWithOpenRouter('gpt-5-mini', MixOpenAI, args);
|
|
454
|
+
}
|
|
455
|
+
gpt5nano(args = {}) {
|
|
456
|
+
return this._attachOpenAIWithOpenRouter('gpt-5-nano', MixOpenAI, args);
|
|
442
457
|
}
|
|
443
|
-
|
|
444
|
-
return this.
|
|
458
|
+
gpt51(args = {}) {
|
|
459
|
+
return this._attachOpenAIWithOpenRouter('gpt-5.1', MixOpenAIResponses, args);
|
|
445
460
|
}
|
|
446
|
-
|
|
447
|
-
return this.
|
|
461
|
+
gpt52(args = {}) {
|
|
462
|
+
return this._attachOpenAIWithOpenRouter('gpt-5.2', MixOpenAIResponses, args);
|
|
448
463
|
}
|
|
449
|
-
|
|
450
|
-
return this.
|
|
464
|
+
gpt54(args = {}) {
|
|
465
|
+
return this._attachOpenAIWithOpenRouter('gpt-5.4', MixOpenAIResponses, args);
|
|
451
466
|
}
|
|
452
|
-
|
|
453
|
-
return this.
|
|
467
|
+
gpt54mini(args = {}) {
|
|
468
|
+
return this._attachOpenAIWithOpenRouter('gpt-5.4-mini', MixOpenAIResponses, args);
|
|
454
469
|
}
|
|
455
|
-
|
|
456
|
-
return this.
|
|
470
|
+
gpt54nano(args = {}) {
|
|
471
|
+
return this._attachOpenAIWithOpenRouter('gpt-5.4-nano', MixOpenAIResponses, args);
|
|
457
472
|
}
|
|
458
|
-
|
|
459
|
-
return this.
|
|
460
|
-
}
|
|
461
|
-
gpt54pro({ options = {}, config = {} } = {}) {
|
|
462
|
-
return this.attach('gpt-5.4-pro', new MixOpenAIResponses({ options, config }));
|
|
473
|
+
gpt54pro(args = {}) {
|
|
474
|
+
return this._attachOpenAIWithOpenRouter('gpt-5.4-pro', MixOpenAIResponses, args);
|
|
463
475
|
}
|
|
464
|
-
gpt55(
|
|
465
|
-
return this.
|
|
476
|
+
gpt55(args = {}) {
|
|
477
|
+
return this._attachOpenAIWithOpenRouter('gpt-5.5', MixOpenAIResponses, args);
|
|
466
478
|
}
|
|
467
|
-
gpt55pro(
|
|
468
|
-
return this.
|
|
479
|
+
gpt55pro(args = {}) {
|
|
480
|
+
return this._attachOpenAIWithOpenRouter('gpt-5.5-pro', MixOpenAIResponses, args);
|
|
469
481
|
}
|
|
470
|
-
gpt56sol(
|
|
471
|
-
return this.
|
|
482
|
+
gpt56sol(args = {}) {
|
|
483
|
+
return this._attachOpenAIWithOpenRouter('gpt-5.6-sol', MixOpenAIResponses, args);
|
|
472
484
|
}
|
|
473
|
-
gpt56terra(
|
|
474
|
-
return this.
|
|
485
|
+
gpt56terra(args = {}) {
|
|
486
|
+
return this._attachOpenAIWithOpenRouter('gpt-5.6-terra', MixOpenAIResponses, args);
|
|
475
487
|
}
|
|
476
|
-
gpt56luna(
|
|
477
|
-
return this.
|
|
488
|
+
gpt56luna(args = {}) {
|
|
489
|
+
return this._attachOpenAIWithOpenRouter('gpt-5.6-luna', MixOpenAIResponses, args);
|
|
478
490
|
}
|
|
479
491
|
gptRealtime({ options = {}, config = {} } = {}) {
|
|
480
492
|
return this.attach('gpt-realtime', new MixOpenAIWebSocket({ options, config }));
|
|
@@ -482,11 +494,14 @@ class ModelMix {
|
|
|
482
494
|
gptRealtimeMini({ options = {}, config = {} } = {}) {
|
|
483
495
|
return this.attach('gpt-realtime-mini', new MixOpenAIWebSocket({ options, config }));
|
|
484
496
|
}
|
|
485
|
-
gpt53codex(
|
|
486
|
-
return this.
|
|
487
|
-
}
|
|
488
|
-
gpt53chat(
|
|
489
|
-
return this.
|
|
497
|
+
gpt53codex(args = {}) {
|
|
498
|
+
return this._attachOpenAIWithOpenRouter('gpt-5.3-codex', MixOpenAIResponses, args);
|
|
499
|
+
}
|
|
500
|
+
gpt53chat(args = {}) {
|
|
501
|
+
return this._attachOpenAIWithOpenRouter('gpt-5.3-chat-latest', MixOpenAIResponses, {
|
|
502
|
+
...args,
|
|
503
|
+
openRouterKey: 'openai/gpt-5.3-chat'
|
|
504
|
+
});
|
|
490
505
|
}
|
|
491
506
|
gptOss({ options = {}, config = {}, mix = {} } = {}) {
|
|
492
507
|
mix = { ...this.mix, ...mix };
|
|
@@ -504,6 +519,12 @@ class ModelMix {
|
|
|
504
519
|
fable5(args = {}) {
|
|
505
520
|
return this.fable50(args);
|
|
506
521
|
}
|
|
522
|
+
fable51({ options = {}, config = {}, mix = {} } = {}) {
|
|
523
|
+
mix = { anthropic: true, ...this.mix, ...mix };
|
|
524
|
+
if (mix.anthropic) this.attach('claude-fable-5-1', new MixAnthropic({ options, config }));
|
|
525
|
+
if (mix.openrouter) this.attach('anthropic/claude-fable-5.1', new MixOpenRouter({ options, config }));
|
|
526
|
+
return this;
|
|
527
|
+
}
|
|
507
528
|
opus50({ options = {}, config = {} } = {}) {
|
|
508
529
|
return this.attach('claude-opus-5', new MixAnthropic({ options, config }));
|
|
509
530
|
}
|
|
@@ -585,6 +606,10 @@ class ModelMix {
|
|
|
585
606
|
return this;
|
|
586
607
|
}
|
|
587
608
|
|
|
609
|
+
museSpark12Contributor({ options = {}, config = {} } = {}) {
|
|
610
|
+
return this.attach('meta/muse-spark-1.2-contributor', new MixOpenRouter({ options, config }));
|
|
611
|
+
}
|
|
612
|
+
|
|
588
613
|
qwen35397b({ options = {}, config = {} } = {}) {
|
|
589
614
|
return this.attach('qwen/qwen3.5-397b-a17b', new MixOpenRouter({ options, config }));
|
|
590
615
|
}
|
|
@@ -628,7 +653,7 @@ class ModelMix {
|
|
|
628
653
|
return this.attach('nousresearch/hermes-4-405b', new MixOpenRouter({ options, config }));
|
|
629
654
|
}
|
|
630
655
|
|
|
631
|
-
hermes3({ options = {}, config = {}, mix = {} } = {}) {
|
|
656
|
+
hermes3({ options = {}, config = {}, mix = { openrouter: true } } = {}) {
|
|
632
657
|
mix = { ...this.mix, ...mix };
|
|
633
658
|
if (mix.lambda) this.attach('Hermes-3-Llama-3.1-405B-FP8', new MixLambda({ options, config }));
|
|
634
659
|
if (mix.openrouter) this.attach('nousresearch/hermes-3-llama-3.1-405b:free', new MixOpenRouter({ options, config }));
|
|
@@ -673,7 +698,7 @@ class ModelMix {
|
|
|
673
698
|
}
|
|
674
699
|
|
|
675
700
|
|
|
676
|
-
minimaxM27({ options = {}, config = {}, mix = {
|
|
701
|
+
minimaxM27({ options = {}, config = {}, mix = { minimax: true } } = {}) {
|
|
677
702
|
mix = { ...this.mix, ...mix };
|
|
678
703
|
if (mix.nvidia) this.attach('minimaxai/minimax-m2.7', new MixNVIDIA({ options, config }));
|
|
679
704
|
if (mix.fireworks) this.attach('accounts/fireworks/models/minimax-m2p7', new MixFireworks({ options, config }));
|
|
@@ -786,22 +811,23 @@ class ModelMix {
|
|
|
786
811
|
return this;
|
|
787
812
|
}
|
|
788
813
|
|
|
789
|
-
|
|
814
|
+
_addImageSource(source, { role = "user", cache } = {}) {
|
|
790
815
|
const contentCache = normalizeContentCache(cache);
|
|
791
816
|
this.messages.push({
|
|
792
817
|
role,
|
|
793
818
|
content: [{
|
|
794
819
|
type: "image",
|
|
795
|
-
source
|
|
796
|
-
type: "buffer",
|
|
797
|
-
data: buffer
|
|
798
|
-
},
|
|
820
|
+
source,
|
|
799
821
|
...(contentCache !== undefined && { cache: contentCache })
|
|
800
822
|
}]
|
|
801
823
|
});
|
|
802
824
|
return this;
|
|
803
825
|
}
|
|
804
826
|
|
|
827
|
+
addImageFromBuffer(buffer, { role = "user", cache } = {}) {
|
|
828
|
+
return this._addImageSource({ type: "buffer", data: buffer }, { role, cache });
|
|
829
|
+
}
|
|
830
|
+
|
|
805
831
|
addImage(filePath, { role = "user", cache } = {}) {
|
|
806
832
|
const absolutePath = path.resolve(filePath);
|
|
807
833
|
|
|
@@ -809,19 +835,7 @@ class ModelMix {
|
|
|
809
835
|
throw new Error(`Image file not found: ${filePath}`);
|
|
810
836
|
}
|
|
811
837
|
|
|
812
|
-
|
|
813
|
-
this.messages.push({
|
|
814
|
-
role,
|
|
815
|
-
content: [{
|
|
816
|
-
type: "image",
|
|
817
|
-
source: {
|
|
818
|
-
type: "file",
|
|
819
|
-
data: filePath
|
|
820
|
-
},
|
|
821
|
-
...(contentCache !== undefined && { cache: contentCache })
|
|
822
|
-
}]
|
|
823
|
-
});
|
|
824
|
-
return this;
|
|
838
|
+
return this._addImageSource({ type: "file", data: filePath }, { role, cache });
|
|
825
839
|
}
|
|
826
840
|
|
|
827
841
|
addImageFromUrl(url, { role = "user", cache } = {}) {
|
|
@@ -845,17 +859,7 @@ class ModelMix {
|
|
|
845
859
|
};
|
|
846
860
|
}
|
|
847
861
|
|
|
848
|
-
|
|
849
|
-
this.messages.push({
|
|
850
|
-
role,
|
|
851
|
-
content: [{
|
|
852
|
-
type: "image",
|
|
853
|
-
source,
|
|
854
|
-
...(contentCache !== undefined && { cache: contentCache })
|
|
855
|
-
}]
|
|
856
|
-
});
|
|
857
|
-
|
|
858
|
-
return this;
|
|
862
|
+
return this._addImageSource(source, { role, cache });
|
|
859
863
|
}
|
|
860
864
|
|
|
861
865
|
async processImages() {
|
|
@@ -1096,10 +1100,6 @@ class ModelMix {
|
|
|
1096
1100
|
const shouldNotGroup = ModelMix.hasToolInteraction(currentMessage);
|
|
1097
1101
|
|
|
1098
1102
|
if (index === 0 || currentMessage.role !== messages[index - 1].role || shouldNotGroup) {
|
|
1099
|
-
// acc.push({
|
|
1100
|
-
// role: currentMessage.role,
|
|
1101
|
-
// content: currentMessage.content
|
|
1102
|
-
// });
|
|
1103
1103
|
acc.push(currentMessage);
|
|
1104
1104
|
} else {
|
|
1105
1105
|
acc[acc.length - 1].content = acc[acc.length - 1].content.concat(currentMessage.content);
|
|
@@ -1865,4 +1865,4 @@ class ModelMix {
|
|
|
1865
1865
|
log
|
|
1866
1866
|
}));
|
|
1867
1867
|
|
|
1868
|
-
module.exports = { MixCustom, ModelMix, ModerationMix, MixModeration, MixAnthropic, MixKimi, MixMiniMax, MixMiMo, MixOpenAI, MixOpenAIResponses, MixOpenAIModeration, MixOpenAIWebSocket, MixOpenRouter, MixPerplexity, MixOllama, MixLMStudio, MixGroq, MixTogether, MixGrok, MixCerebras, MixGoogle, MixFireworks, MixNVIDIA, normalizeEffort, applyUnifiedEffort, resolveProviderFamily };
|
|
1868
|
+
module.exports = { MixCustom, ModelMix, ModerationMix, MixModeration, MixAnthropic, MixKimi, MixMiniMax, MixMiMo, MixOpenAI, MixOpenAIResponses, MixOpenAIModeration, MixOpenAIWebSocket, MixOpenRouter, MixPerplexity, MixOllama, MixLambda, MixLMStudio, MixGroq, MixTogether, MixGrok, MixCerebras, MixGoogle, MixFireworks, MixNVIDIA, normalizeEffort, applyUnifiedEffort, resolveProviderFamily };
|
package/lib/model-chain.js
CHANGED
|
@@ -5,12 +5,12 @@ const CHAIN_MODEL_SHORTCUTS = new Set([
|
|
|
5
5
|
'gpt51', 'gpt52', 'gpt54', 'gpt54mini', 'gpt54nano', 'gpt54pro',
|
|
6
6
|
'gpt55', 'gpt55pro', 'gpt56sol', 'gpt56terra', 'gpt56luna',
|
|
7
7
|
'gptRealtime', 'gptRealtimeMini', 'gpt53codex', 'gpt53chat', 'gptOss',
|
|
8
|
-
'fable50', 'fable5', 'opus50', 'opus5', 'opus48', 'opus47', 'opus46',
|
|
8
|
+
'fable51', 'fable50', 'fable5', 'opus50', 'opus5', 'opus48', 'opus47', 'opus46',
|
|
9
9
|
'sonnet50', 'sonnet5', 'sonnet46', 'sonnet45', 'haiku45',
|
|
10
10
|
'gemini31pro', 'gemini37flash', 'gemini36flash', 'gemini35flash',
|
|
11
11
|
'gemini35flashLite', 'gemini31flashLite', 'sonarPro', 'sonar',
|
|
12
12
|
'grok46', 'grok45', 'grok43', 'grok420multiAgent', 'grok420',
|
|
13
|
-
'museGlimmer30b',
|
|
13
|
+
'museGlimmer30b', 'museSpark12Contributor',
|
|
14
14
|
'qwen35397b', 'qwen36plus', 'qwen37plus', 'qwen38max', 'qwen3827b', 'qwen38flash',
|
|
15
15
|
'hermes470b', 'hermes4405b', 'hermes3',
|
|
16
16
|
'kimiK26', 'kimiK27Code', 'kimiK3', 'kimiK25',
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
function requireProviderApiKey(customConfig, environmentVariable, providerName) {
|
|
2
|
+
const apiKey = customConfig.apiKey || process.env[environmentVariable];
|
|
3
|
+
if (!apiKey) {
|
|
4
|
+
throw new Error(`${providerName} API key not found. Please provide it in config or set ${environmentVariable} environment variable.`);
|
|
5
|
+
}
|
|
6
|
+
return apiKey;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
module.exports = { requireProviderApiKey };
|
|
@@ -4,6 +4,25 @@ const {
|
|
|
4
4
|
stripContentCacheMetadata,
|
|
5
5
|
hasNeutralCacheBreakpoint
|
|
6
6
|
} = require('../content-cache');
|
|
7
|
+
const { requireProviderApiKey } = require('../provider-api-key');
|
|
8
|
+
|
|
9
|
+
function rejectsAnthropicSamplingParams(model = '') {
|
|
10
|
+
const id = String(model).toLowerCase();
|
|
11
|
+
if (!id.includes('claude')) return false;
|
|
12
|
+
if (id.includes('mythos') || id.includes('fable')) return true;
|
|
13
|
+
|
|
14
|
+
const opus = id.match(/claude-opus-(\d+)(?:-(\d+))?/);
|
|
15
|
+
if (opus) {
|
|
16
|
+
const major = Number(opus[1]);
|
|
17
|
+
const minor = opus[2] !== undefined ? Number(opus[2]) : 0;
|
|
18
|
+
return major > 4 || (major === 4 && minor >= 7);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const sonnet = id.match(/claude-sonnet-(\d+)/);
|
|
22
|
+
if (sonnet) return Number(sonnet[1]) >= 5;
|
|
23
|
+
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
7
26
|
|
|
8
27
|
function createAnthropicProviders({ ModelMix, MixCustom, log }) {
|
|
9
28
|
class MixAnthropic extends MixCustom {
|
|
@@ -30,28 +49,11 @@ function createAnthropicProviders({ ModelMix, MixCustom, log }) {
|
|
|
30
49
|
* See: https://platform.claude.com/docs/en/about-claude/models/migration-guide
|
|
31
50
|
*/
|
|
32
51
|
static rejectsSamplingParams(model = '') {
|
|
33
|
-
|
|
34
|
-
if (!id.includes('claude')) return false;
|
|
35
|
-
if (id.includes('mythos') || id.includes('fable')) return true;
|
|
36
|
-
|
|
37
|
-
const opus = id.match(/claude-opus-(\d+)(?:-(\d+))?/);
|
|
38
|
-
if (opus) {
|
|
39
|
-
const major = Number(opus[1]);
|
|
40
|
-
const minor = opus[2] !== undefined ? Number(opus[2]) : 0;
|
|
41
|
-
return major > 4 || (major === 4 && minor >= 7);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const sonnet = id.match(/claude-sonnet-(\d+)/);
|
|
45
|
-
if (sonnet) return Number(sonnet[1]) >= 5;
|
|
46
|
-
|
|
47
|
-
return false;
|
|
52
|
+
return rejectsAnthropicSamplingParams(model);
|
|
48
53
|
}
|
|
49
54
|
|
|
50
55
|
getDefaultConfig(customConfig) {
|
|
51
|
-
const apiKey = customConfig
|
|
52
|
-
if (!apiKey) {
|
|
53
|
-
throw new Error('Anthropic API key not found. Please provide it in config or set ANTHROPIC_API_KEY environment variable.');
|
|
54
|
-
}
|
|
56
|
+
const apiKey = requireProviderApiKey(customConfig, 'ANTHROPIC_API_KEY', 'Anthropic');
|
|
55
57
|
|
|
56
58
|
return super.getDefaultConfig({
|
|
57
59
|
url: 'https://api.anthropic.com/v1/messages',
|
|
@@ -334,4 +336,4 @@ function createAnthropicProviders({ ModelMix, MixCustom, log }) {
|
|
|
334
336
|
return { MixAnthropic };
|
|
335
337
|
}
|
|
336
338
|
|
|
337
|
-
module.exports = createAnthropicProviders;
|
|
339
|
+
module.exports = { createAnthropicProviders, rejectsAnthropicSamplingParams };
|
package/lib/providers/base.js
CHANGED
|
@@ -11,6 +11,8 @@ const {
|
|
|
11
11
|
stripContentCacheMetadata
|
|
12
12
|
} = require('../content-cache');
|
|
13
13
|
const { configForDebug, redactSecret } = require('../provider-debug');
|
|
14
|
+
const { requireProviderApiKey } = require('../provider-api-key');
|
|
15
|
+
const { normalizeOpenAIOptions } = require('./openai-options');
|
|
14
16
|
|
|
15
17
|
function createBaseProviders({ ModelMix }) {
|
|
16
18
|
class MixCustom {
|
|
@@ -274,10 +276,7 @@ function createBaseProviders({ ModelMix }) {
|
|
|
274
276
|
}
|
|
275
277
|
|
|
276
278
|
getDefaultConfig(customConfig) {
|
|
277
|
-
const apiKey = customConfig
|
|
278
|
-
if (!apiKey) {
|
|
279
|
-
throw new Error('OpenAI API key not found. Please provide it in config or set OPENAI_API_KEY environment variable.');
|
|
280
|
-
}
|
|
279
|
+
const apiKey = requireProviderApiKey(customConfig, 'OPENAI_API_KEY', 'OpenAI');
|
|
281
280
|
|
|
282
281
|
return super.getDefaultConfig({
|
|
283
282
|
url: 'https://api.openai.com/v1/chat/completions',
|
|
@@ -287,22 +286,7 @@ function createBaseProviders({ ModelMix }) {
|
|
|
287
286
|
}
|
|
288
287
|
|
|
289
288
|
async create({ config = {}, options = {} } = {}) {
|
|
290
|
-
|
|
291
|
-
// Remove max_tokens and temperature for o1/o3 models
|
|
292
|
-
if (options.model?.startsWith('o')) {
|
|
293
|
-
delete options.max_tokens;
|
|
294
|
-
delete options.temperature;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Use max_completion_tokens and remove temperature for GPT-5 models
|
|
298
|
-
if (options.model?.includes('gpt-5')) {
|
|
299
|
-
if (options.max_tokens) {
|
|
300
|
-
options.max_completion_tokens = options.max_tokens;
|
|
301
|
-
delete options.max_tokens;
|
|
302
|
-
}
|
|
303
|
-
delete options.temperature;
|
|
304
|
-
}
|
|
305
|
-
|
|
289
|
+
normalizeOpenAIOptions(options);
|
|
306
290
|
return super.create({ config, options });
|
|
307
291
|
}
|
|
308
292
|
|
|
@@ -387,10 +371,8 @@ function createBaseProviders({ ModelMix }) {
|
|
|
387
371
|
}
|
|
388
372
|
}
|
|
389
373
|
|
|
390
|
-
// Solo incluir tools si el array no está vacío
|
|
391
374
|
if (toolsArray.length > 0) {
|
|
392
375
|
options.tools = toolsArray;
|
|
393
|
-
// options.tool_choice = "auto";
|
|
394
376
|
}
|
|
395
377
|
|
|
396
378
|
return options;
|
package/lib/providers/google.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
const { fetchJsonResponse } = require('../../http-client');
|
|
2
2
|
const { configForDebug } = require('../provider-debug');
|
|
3
|
+
const { requireProviderApiKey } = require('../provider-api-key');
|
|
3
4
|
|
|
4
5
|
function createGoogleProviders({ ModelMix, MixCustom }) {
|
|
5
6
|
class MixGoogle extends MixCustom {
|
|
6
7
|
getDefaultConfig(customConfig) {
|
|
7
|
-
const apiKey = customConfig
|
|
8
|
+
const apiKey = requireProviderApiKey(customConfig, 'GEMINI_API_KEY', 'Gemini');
|
|
8
9
|
return super.getDefaultConfig({
|
|
9
10
|
url: 'https://generativelanguage.googleapis.com/v1beta/models',
|
|
10
11
|
apiKey,
|
|
@@ -19,7 +20,7 @@ function createGoogleProviders({ ModelMix, MixCustom }) {
|
|
|
19
20
|
};
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
static convertMessages(messages
|
|
23
|
+
static convertMessages(messages) {
|
|
23
24
|
return messages.map(message => {
|
|
24
25
|
|
|
25
26
|
// Handle assistant messages with tool_calls (content is null)
|
|
@@ -122,10 +123,6 @@ function createGoogleProviders({ ModelMix, MixCustom }) {
|
|
|
122
123
|
}
|
|
123
124
|
|
|
124
125
|
async create({ config = {}, options = {} } = {}) {
|
|
125
|
-
if (!this.config.apiKey) {
|
|
126
|
-
throw new Error('Gemini API key not found. Please provide it in config or set GEMINI_API_KEY environment variable.');
|
|
127
|
-
}
|
|
128
|
-
|
|
129
126
|
const generateContentApi = options.stream ? 'streamGenerateContent' : 'generateContent';
|
|
130
127
|
|
|
131
128
|
const fullUrl = `${this.config.url}/${options.model}:${generateContentApi}?key=${this.config.apiKey}`;
|