modelmix 5.1.18 → 5.1.20
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 -1
- package/demo/short.js +1 -0
- package/effort.js +1 -0
- package/index.d.ts +1 -0
- package/index.js +3 -0
- package/lib/model-chain.js +1 -1
- package/lib/providers/openai-options.js +1 -1
- package/lib/token-usage.js +5 -4
- package/package.json +9 -10
- package/skills/modelmix/SKILL.md +3 -3
- package/test/effort.test.js +9 -0
- package/test/fallback.test.js +40 -5
- package/test/tokens.test.js +8 -0
package/README.md
CHANGED
|
@@ -158,6 +158,7 @@ ModelMix provides convenient shorthand methods for quickly accessing different A
|
|
|
158
158
|
|
|
159
159
|
| Method | Provider | Model | Input / 1M | Output / 1M |
|
|
160
160
|
| --- | --- | --- | ---: | ---: |
|
|
161
|
+
| `gpt6astra()` | OpenAI | gpt-6-astra | [\$10.00][1] | [\$50.00][1] |
|
|
161
162
|
| `gpt56sol()` | OpenAI | gpt-5.6-sol | [\$5.00][1] | [\$30.00][1] |
|
|
162
163
|
| `gpt56terra()` | OpenAI | gpt-5.6-terra | [\$2.00][1] | [\$12.00][1] |
|
|
163
164
|
| `gpt56luna()` | OpenAI | gpt-5.6-luna | [\$0.20][1] | [\$1.20][1] |
|
|
@@ -230,7 +231,7 @@ Gemini 3.8 Flash, 3.7 Flash, and 3.6 Flash use Google's introductory standard pr
|
|
|
230
231
|
|
|
231
232
|
`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.
|
|
232
233
|
|
|
233
|
-
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.
|
|
234
|
+
Every textual GPT-5 and GPT-6 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.
|
|
234
235
|
|
|
235
236
|
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.
|
|
236
237
|
|
|
@@ -302,6 +303,7 @@ ModelMix.new().effort(-1).minimaxM3().addText('...').message();
|
|
|
302
303
|
### Provider-specific behavior
|
|
303
304
|
|
|
304
305
|
- **Gemini:** Gemini 3+ uses bands 0–24 / 25–49 / 50–74 / 75–100. Gemini 3.8 Flash and 3.7 Flash clamp these bands to `low` / `low` / `medium` / `high`; `-1` leaves their native `medium` default unchanged. Gemini 2.5 maps 0–100 to `thinkingBudget`.
|
|
306
|
+
- **GPT-6 Astra:** 0–39 maps to `low`, 40–59 to `medium`, 60–79 to `high`, 80–99 to `xhigh`, and 100 to `max`. [Model details](https://developers.openai.com/api/docs/models/gpt-6-astra). Cache reads cost $1.00 and cache writes $12.50 per 1M tokens; requests over 272K input tokens apply 2× input/cache and 1.5× output rates.
|
|
305
307
|
- **GPT-5.6:** `100` maps to `max`; 80–99 remains `xhigh`.
|
|
306
308
|
- **Qwen 3.8 27B and Flash:** 0–39 / 40–79 / 80–100 map to `low` / `medium` / `xhigh`; `-1` leaves the native `xhigh` default unchanged. Qwen 3.8 Flash is the managed production version based on the open-weight Flash-Next architecture.
|
|
307
309
|
- **GLM 5.3 and GLM 5.3 Flash:** reasoning is mandatory; 0–39 / 40–79 / 80–100 map to `low` / `high` / `max`; `-1` leaves the native `max` default unchanged.
|
package/demo/short.js
CHANGED
|
@@ -27,6 +27,7 @@ const mmix = await ModelMix.new(setup)
|
|
|
27
27
|
.museSpark13() // (fallback 16) OpenRouter meta/muse-spark-1.3
|
|
28
28
|
.museSpark12() // (fallback 17) OpenRouter meta/muse-spark-1.2
|
|
29
29
|
.museSpark13c() // (fallback 18) OpenRouter meta/muse-spark-1.3-contributor
|
|
30
|
+
.gpt6astra() // (fallback 19) OpenAI gpt-6-astra
|
|
30
31
|
.addText("What's your name?");
|
|
31
32
|
|
|
32
33
|
console.log(await mmix.message());
|
package/effort.js
CHANGED
|
@@ -42,6 +42,7 @@ const GLM53_BANDS = [
|
|
|
42
42
|
|
|
43
43
|
/** Exact model → supported OpenAI reasoning_effort values */
|
|
44
44
|
const OPENAI_MODEL_LEVELS = {
|
|
45
|
+
'gpt-6-astra': ['low', 'medium', 'high', 'xhigh', 'max'],
|
|
45
46
|
'gpt-5.6-sol': ['none', 'low', 'medium', 'high', 'xhigh', 'max'],
|
|
46
47
|
'gpt-5.6-terra': ['none', 'low', 'medium', 'high', 'xhigh', 'max'],
|
|
47
48
|
'gpt-5.6-luna': ['none', 'low', 'medium', 'high', 'xhigh', 'max'],
|
package/index.d.ts
CHANGED
|
@@ -456,6 +456,7 @@ export declare class ModelMix {
|
|
|
456
456
|
gpt54pro(args?: ModelAttachArgs): this;
|
|
457
457
|
gpt55(args?: ModelAttachArgs): this;
|
|
458
458
|
gpt55pro(args?: ModelAttachArgs): this;
|
|
459
|
+
gpt6astra(args?: ModelAttachArgs): this;
|
|
459
460
|
gpt56sol(args?: ModelAttachArgs): this;
|
|
460
461
|
gpt56terra(args?: ModelAttachArgs): this;
|
|
461
462
|
gpt56luna(args?: ModelAttachArgs): this;
|
package/index.js
CHANGED
|
@@ -494,6 +494,9 @@ class ModelMix {
|
|
|
494
494
|
gpt55pro(args = {}) {
|
|
495
495
|
return this._attachOpenAIWithOpenRouter('gpt-5.5-pro', MixOpenAIResponses, args);
|
|
496
496
|
}
|
|
497
|
+
gpt6astra(args = {}) {
|
|
498
|
+
return this._attachOpenAIWithOpenRouter('gpt-6-astra', MixOpenAIResponses, args);
|
|
499
|
+
}
|
|
497
500
|
gpt56sol(args = {}) {
|
|
498
501
|
return this._attachOpenAIWithOpenRouter('gpt-5.6-sol', MixOpenAIResponses, args);
|
|
499
502
|
}
|
package/lib/model-chain.js
CHANGED
|
@@ -3,7 +3,7 @@ const { normalizeEffort } = require('../effort');
|
|
|
3
3
|
const CHAIN_MODEL_SHORTCUTS = new Set([
|
|
4
4
|
'gpt5', 'gpt5mini', 'gpt5nano',
|
|
5
5
|
'gpt51', 'gpt52', 'gpt54', 'gpt54mini', 'gpt54nano', 'gpt54pro',
|
|
6
|
-
'gpt55', 'gpt55pro', 'gpt56sol', 'gpt56terra', 'gpt56luna',
|
|
6
|
+
'gpt6astra', 'gpt55', 'gpt55pro', 'gpt56sol', 'gpt56terra', 'gpt56luna',
|
|
7
7
|
'gptRealtime', 'gptRealtimeMini', 'gpt53codex', 'gpt53chat', 'gptOss',
|
|
8
8
|
'fable51', 'fable50', 'fable5', 'opus50', 'opus5', 'opus48', 'opus47', 'opus46',
|
|
9
9
|
'sonnet50', 'sonnet5', 'sonnet46', 'sonnet45', 'haiku45',
|
|
@@ -3,7 +3,7 @@ function normalizeOpenAIOptions(options) {
|
|
|
3
3
|
delete options.max_tokens;
|
|
4
4
|
delete options.temperature;
|
|
5
5
|
}
|
|
6
|
-
if (options.model?.includes('gpt-5')) {
|
|
6
|
+
if (options.model?.includes('gpt-5') || /^(?:openai\/)?gpt-6-astra(?:-|$)/.test(options.model || '')) {
|
|
7
7
|
if (options.max_tokens) {
|
|
8
8
|
options.max_completion_tokens = options.max_tokens;
|
|
9
9
|
delete options.max_tokens;
|
package/lib/token-usage.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
const OPENAI_LONG_CONTEXT_PRICING = Object.freeze({
|
|
2
2
|
inputThreshold: 272_000,
|
|
3
3
|
inputMultiplier: 2,
|
|
4
4
|
outputMultiplier: 1.5
|
|
@@ -21,11 +21,12 @@ function usesLongContextRates(pricing, inputTokens) {
|
|
|
21
21
|
|
|
22
22
|
const MODEL_PRICING = {
|
|
23
23
|
// OpenAI
|
|
24
|
+
'gpt-6-astra': { input: 10.00, cachedInput: 1.00, cacheWrite: 12.50, output: 50.00, longContext: OPENAI_LONG_CONTEXT_PRICING },
|
|
24
25
|
'gpt-realtime-mini': { input: 0.60, cachedInput: 0.06, output: 2.40 },
|
|
25
26
|
'gpt-realtime': { input: 4.00, cachedInput: 0.40, output: 16.00 },
|
|
26
|
-
'gpt-5.6-sol': { input: 5.00, cachedInput: 0.50, cacheWrite: 6.25, output: 30.00, longContext:
|
|
27
|
-
'gpt-5.6-terra': { input: 2.00, cachedInput: 0.20, cacheWrite: 2.50, output: 12.00, longContext:
|
|
28
|
-
'gpt-5.6-luna': { input: 0.20, cachedInput: 0.02, cacheWrite: 0.25, output: 1.20, longContext:
|
|
27
|
+
'gpt-5.6-sol': { input: 5.00, cachedInput: 0.50, cacheWrite: 6.25, output: 30.00, longContext: OPENAI_LONG_CONTEXT_PRICING },
|
|
28
|
+
'gpt-5.6-terra': { input: 2.00, cachedInput: 0.20, cacheWrite: 2.50, output: 12.00, longContext: OPENAI_LONG_CONTEXT_PRICING },
|
|
29
|
+
'gpt-5.6-luna': { input: 0.20, cachedInput: 0.02, cacheWrite: 0.25, output: 1.20, longContext: OPENAI_LONG_CONTEXT_PRICING },
|
|
29
30
|
'gpt-5.5-pro': { input: 30.00, output: 180.00 },
|
|
30
31
|
'gpt-5.5': { input: 5.00, cachedInput: 0.50, output: 30.00 },
|
|
31
32
|
'gpt-5.4': { input: 2.50, cachedInput: 0.25, output: 15.00 },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "modelmix",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.20",
|
|
4
4
|
"description": "🧬 Reliable interface with automatic fallback for AI LLMs.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -24,20 +24,20 @@
|
|
|
24
24
|
"fallback",
|
|
25
25
|
"kimi",
|
|
26
26
|
"mythos",
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
27
|
+
"gpt6",
|
|
28
|
+
"astra",
|
|
29
|
+
"muse",
|
|
30
|
+
"spark",
|
|
31
|
+
"opus",
|
|
32
32
|
"fable",
|
|
33
33
|
"sonnet",
|
|
34
34
|
"openrouter",
|
|
35
35
|
"gemini",
|
|
36
|
+
"qwen",
|
|
36
37
|
"glm",
|
|
37
38
|
"lmstudio",
|
|
38
39
|
"deepseek",
|
|
39
40
|
"oss",
|
|
40
|
-
"k26",
|
|
41
41
|
"reasoning",
|
|
42
42
|
"minimax",
|
|
43
43
|
"thinking",
|
|
@@ -79,6 +79,5 @@
|
|
|
79
79
|
"test:plugins": "mocha test/plugins.test.js --timeout 10000 --require test/setup.js",
|
|
80
80
|
"test:rlm": "mocha plugins/rlm/test/**/*.test.js --timeout 10000 --require test/setup.js",
|
|
81
81
|
"test:offline": "mocha test/abort.test.js 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 test/moderation.test.js test/plugins.test.js plugins/rlm/test/**/*.test.js --timeout 10000 --require test/setup.js"
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
}
|
|
82
|
+
}
|
|
83
|
+
}
|
package/skills/modelmix/SKILL.md
CHANGED
|
@@ -143,7 +143,7 @@ ModelMix.new({ config: { effort: 80 } })
|
|
|
143
143
|
| DeepSeek V4 | off | `low`↑ | `high`↑ | `high`↑ | `max`↑ | — |
|
|
144
144
|
| MiniMax M3 | off | adaptive | adaptive | adaptive | adaptive | adaptive |
|
|
145
145
|
|
|
146
|
-
\* GPT-5.6 maps `100` to `max`; 80–99 remains `xhigh`. Qwen 3.8 27B and Flash map 0–39 / 40–79 / 80–100 to `low` / `medium` / `xhigh`; Qwen 3.8 Flash is the managed production version based on Flash-Next. GLM 5.3 and GLM 5.3 Flash require reasoning and map those bands to `low` / `high` / `max`. Gemini bands: 0–24 / 25–49 / 50–74 / 75–100. Gemini 3.8 Flash and 3.7 Flash support only `low` / `medium` / `high`, so the first two bands clamp to `low`; `-1` keeps their native `medium` default. 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.
|
|
146
|
+
\* GPT-6 Astra maps 0–39 / 40–59 / 60–79 / 80–99 / 100 to `low` / `medium` / `high` / `xhigh` / `max`. GPT-5.6 maps `100` to `max`; 80–99 remains `xhigh`. Qwen 3.8 27B and Flash map 0–39 / 40–79 / 80–100 to `low` / `medium` / `xhigh`; Qwen 3.8 Flash is the managed production version based on Flash-Next. GLM 5.3 and GLM 5.3 Flash require reasoning and map those bands to `low` / `high` / `max`. Gemini bands: 0–24 / 25–49 / 50–74 / 75–100. Gemini 3.8 Flash and 3.7 Flash support only `low` / `medium` / `high`, so the first two bands clamp to `low`; `-1` keeps their native `medium` default. 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.
|
|
147
147
|
|
|
148
148
|
## Available Model Shorthands
|
|
149
149
|
|
|
@@ -151,9 +151,9 @@ ModelMix.new({ config: { effort: 80 } })
|
|
|
151
151
|
|
|
152
152
|
Use `ModerationMix.new().openai()` with `.raw()` to classify text and images through OpenAI's Moderations endpoint. Read the results from `raw.moderation`. `ModerationMix` accepts moderation providers as ordered fallbacks, rejects generative providers, and does not generate text or support streaming.
|
|
153
153
|
|
|
154
|
-
`gpt56sol()` `gpt56terra()` `gpt56luna()` `gpt55()` `gpt55pro()` `gpt54()` `gpt54mini()` `gpt54nano()` `gpt54pro()` `gpt53codex()` `gpt53chat()` `gpt52()` `gpt51()` `gpt5()` `gpt5mini()` `gpt5nano()` `gptRealtime()` `gptRealtimeMini()` `gptOss()`
|
|
154
|
+
`gpt6astra()` `gpt56sol()` `gpt56terra()` `gpt56luna()` `gpt55()` `gpt55pro()` `gpt54()` `gpt54mini()` `gpt54nano()` `gpt54pro()` `gpt53codex()` `gpt53chat()` `gpt52()` `gpt51()` `gpt5()` `gpt5mini()` `gpt5nano()` `gptRealtime()` `gptRealtimeMini()` `gptOss()`
|
|
155
155
|
|
|
156
|
-
Every textual GPT-5 shortcut registers only the official OpenAI model by default. Pass `mix: { openrouter: true }` to `ModelMix.new()` or to an individual shortcut to append its `openai/*` OpenRouter route as a fallback. `gpt53chat()` uses `gpt-5.3-chat-latest` officially and `openai/gpt-5.3-chat` through OpenRouter. Both API keys are required when that fallback is enabled. Realtime shortcuts remain official-only.
|
|
156
|
+
Every textual GPT-5 and GPT-6 shortcut registers only the official OpenAI model by default. Pass `mix: { openrouter: true }` to `ModelMix.new()` or to an individual shortcut to append its `openai/*` OpenRouter route as a fallback. `gpt53chat()` uses `gpt-5.3-chat-latest` officially and `openai/gpt-5.3-chat` through OpenRouter. Both API keys are required when that fallback is enabled. Realtime shortcuts remain official-only.
|
|
157
157
|
|
|
158
158
|
### Anthropic
|
|
159
159
|
`fable51()` `fable50()` `opus50()` `opus48()` `opus47()` `opus46()` `sonnet5()` `sonnet46()` `sonnet45()` `haiku45()`
|
package/test/effort.test.js
CHANGED
|
@@ -73,6 +73,15 @@ describe('Unified effort scale', () => {
|
|
|
73
73
|
expect(mapEffort('openai', 100)).to.deep.equal({ reasoning_effort: 'xhigh' });
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
+
it('clamps GPT-6 Astra effort to its supported range', () => {
|
|
77
|
+
for (const key of ['gpt-6-astra', 'openai/gpt-6-astra']) {
|
|
78
|
+
for (const [effort, level] of [[0, 'low'], [39, 'low'], [40, 'medium'], [60, 'high'], [80, 'xhigh'], [99, 'xhigh'], [100, 'max']]) {
|
|
79
|
+
expect(mapEffort('openai', effort, key)).to.deep.equal({ reasoning_effort: level });
|
|
80
|
+
}
|
|
81
|
+
expect(mapEffort('openai', -1, key)).to.equal(null);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
76
85
|
it('maps GPT-5.6 maximum unified effort to max', () => {
|
|
77
86
|
expect(mapEffort('openai', 99, 'gpt-5.6-luna')).to.deep.equal({ reasoning_effort: 'xhigh' });
|
|
78
87
|
for (const model of ['gpt-5.6-sol', 'gpt-5.6-terra', 'gpt-5.6-luna']) {
|
package/test/fallback.test.js
CHANGED
|
@@ -57,6 +57,7 @@ describe('Provider Fallback Chain Tests', () => {
|
|
|
57
57
|
it('should register every supported OpenAI text shortcut with its OpenRouter fallback', () => {
|
|
58
58
|
expect(ModelMix.new().mix.openrouter).to.equal(false);
|
|
59
59
|
const shortcuts = [
|
|
60
|
+
['gpt6astra', 'gpt-6-astra', 'openai/gpt-6-astra', MixOpenAIResponses],
|
|
60
61
|
['gpt5', 'gpt-5', 'openai/gpt-5', MixOpenAI],
|
|
61
62
|
['gpt5mini', 'gpt-5-mini', 'openai/gpt-5-mini', MixOpenAI],
|
|
62
63
|
['gpt5nano', 'gpt-5-nano', 'openai/gpt-5-nano', MixOpenAI],
|
|
@@ -101,13 +102,13 @@ describe('Provider Fallback Chain Tests', () => {
|
|
|
101
102
|
});
|
|
102
103
|
|
|
103
104
|
it('should append OpenRouter GPT fallbacks in chain() only when enabled globally', () => {
|
|
104
|
-
const official = ModelMix.new().chain('
|
|
105
|
-
expect(official.models.map(({ key }) => key)).to.deep.equal(['gpt-
|
|
105
|
+
const official = ModelMix.new().chain('gpt6astra@100');
|
|
106
|
+
expect(official.models.map(({ key }) => key)).to.deep.equal(['gpt-6-astra']);
|
|
106
107
|
|
|
107
|
-
const routed = ModelMix.new({ mix: { openrouter: true } }).chain('
|
|
108
|
+
const routed = ModelMix.new({ mix: { openrouter: true } }).chain('gpt6astra@100');
|
|
108
109
|
expect(routed.models.map(({ key }) => key)).to.deep.equal([
|
|
109
|
-
'gpt-
|
|
110
|
-
'openai/gpt-
|
|
110
|
+
'gpt-6-astra',
|
|
111
|
+
'openai/gpt-6-astra'
|
|
111
112
|
]);
|
|
112
113
|
expect(routed.models.map(({ provider }) => provider.config.effort)).to.deep.equal([100, 100]);
|
|
113
114
|
});
|
|
@@ -140,6 +141,40 @@ describe('Provider Fallback Chain Tests', () => {
|
|
|
140
141
|
expect(openRouterRequest).to.not.have.property('temperature');
|
|
141
142
|
});
|
|
142
143
|
|
|
144
|
+
it('should fallback from the official GPT-6 Astra endpoint to OpenRouter', async () => {
|
|
145
|
+
let openRouterRequest;
|
|
146
|
+
model.effort(100).gpt6astra({ mix: { openrouter: true } }).addText('Hello');
|
|
147
|
+
|
|
148
|
+
nock('https://api.openai.com')
|
|
149
|
+
.post('/v1/responses', body => {
|
|
150
|
+
expect(body.model).to.equal('gpt-6-astra');
|
|
151
|
+
expect(body.reasoning).to.deep.equal({ effort: 'max' });
|
|
152
|
+
expect(body.max_output_tokens).to.equal(8192);
|
|
153
|
+
expect(body).to.not.have.property('temperature');
|
|
154
|
+
return true;
|
|
155
|
+
})
|
|
156
|
+
.reply(503, { error: 'Service unavailable' });
|
|
157
|
+
|
|
158
|
+
nock('https://openrouter.ai')
|
|
159
|
+
.post('/api/v1/chat/completions', body => {
|
|
160
|
+
openRouterRequest = body;
|
|
161
|
+
return true;
|
|
162
|
+
})
|
|
163
|
+
.reply(200, {
|
|
164
|
+
choices: [{
|
|
165
|
+
message: {
|
|
166
|
+
role: 'assistant',
|
|
167
|
+
content: 'Hello from GPT-6 Astra through OpenRouter!'
|
|
168
|
+
}
|
|
169
|
+
}]
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
expect(await model.message()).to.equal('Hello from GPT-6 Astra through OpenRouter!');
|
|
173
|
+
expect(openRouterRequest.model).to.equal('openai/gpt-6-astra');
|
|
174
|
+
expect(openRouterRequest.max_completion_tokens).to.equal(8192);
|
|
175
|
+
expect(openRouterRequest).to.not.have.property('temperature');
|
|
176
|
+
});
|
|
177
|
+
|
|
143
178
|
it('should keep the default fable51 chain on Anthropic', () => {
|
|
144
179
|
model.chain('fable51@80');
|
|
145
180
|
|
package/test/tokens.test.js
CHANGED
|
@@ -337,6 +337,14 @@ describe('Token Usage Tracking', () => {
|
|
|
337
337
|
expect(model.models[3].provider).to.be.instanceOf(MixOpenRouter);
|
|
338
338
|
});
|
|
339
339
|
|
|
340
|
+
it('should account for Astra cache usage and the long-context boundary', function () {
|
|
341
|
+
for (const key of ['gpt-6-astra', 'openai/gpt-6-astra']) {
|
|
342
|
+
const tokens = { input: 272_000, cached: 100_000, cacheWrite: 20_000, output: 1_000 };
|
|
343
|
+
expect(ModelMix.calculateCost(key, tokens)).to.be.closeTo(1.92, 1e-10);
|
|
344
|
+
expect(ModelMix.calculateCost(key, { ...tokens, input: 272_001 })).to.be.closeTo(3.81502, 1e-10);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
|
|
340
348
|
it('should register GPT-5.6 shortcuts with OpenAI Responses provider', function () {
|
|
341
349
|
const model = ModelMix.new({ mix: { openrouter: true } })
|
|
342
350
|
.gpt56sol()
|