modelmix 5.1.19 → 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 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
  }
@@ -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;
@@ -1,4 +1,4 @@
1
- const GPT56_LONG_CONTEXT_PRICING = Object.freeze({
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: GPT56_LONG_CONTEXT_PRICING },
27
- 'gpt-5.6-terra': { input: 2.00, cachedInput: 0.20, cacheWrite: 2.50, output: 12.00, longContext: GPT56_LONG_CONTEXT_PRICING },
28
- 'gpt-5.6-luna': { input: 0.20, cachedInput: 0.02, cacheWrite: 0.25, output: 1.20, longContext: GPT56_LONG_CONTEXT_PRICING },
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.19",
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",
@@ -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
- "packageManager": "pnpm@11.18.0+sha512.33d83c77da82f49fba836925c6f1b841181ec3132b670639bd012f7075f5c7cf634c5f870147c19aae7478fac01df09d8892e880454896edd23ee9b33757563c"
84
- }
82
+ }
83
+ }
@@ -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()`
@@ -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']) {
@@ -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('gpt56sol@100');
105
- expect(official.models.map(({ key }) => key)).to.deep.equal(['gpt-5.6-sol']);
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('gpt56sol@100');
108
+ const routed = ModelMix.new({ mix: { openrouter: true } }).chain('gpt6astra@100');
108
109
  expect(routed.models.map(({ key }) => key)).to.deep.equal([
109
- 'gpt-5.6-sol',
110
- 'openai/gpt-5.6-sol'
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
 
@@ -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()