modelmix 4.5.30 → 4.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/MODELS.md CHANGED
@@ -344,6 +344,15 @@ All providers inherit from `MixCustom` base class which provides common function
344
344
  - **Output Format**: Same as OpenAI
345
345
  - **Special Notes**: Uses OpenAI-compatible format
346
346
 
347
+ ### Moonshot (MixKimi)
348
+ - **Base URL**: `https://api.moonshot.ai/v1/chat/completions`
349
+ - **Input Format**: Same as OpenAI
350
+ - **Output Format**: Same as OpenAI
351
+ - **Special Notes**:
352
+ - Requires `MOONSHOT_API_KEY` environment variable
353
+ - Available model: `kimi-k3`
354
+ - K3 always uses thinking and requires fixed sampling parameters to be omitted
355
+
347
356
  ### MiniMax (MixMiniMax)
348
357
  - **Base URL**: `https://api.minimax.io/v1/chat/completions`
349
358
  - **Input Format**: Same as OpenAI
package/README.md CHANGED
@@ -32,6 +32,7 @@ Only the API keys you plan to use are required.
32
32
  ANTHROPIC_API_KEY="sk-ant-..."
33
33
  OPENAI_API_KEY="sk-proj-..."
34
34
  OPENROUTER_API_KEY="sk-or-..."
35
+ MOONSHOT_API_KEY="your-moonshot-key..."
35
36
  MINIMAX_API_KEY="your-minimax-key..."
36
37
  NVIDIA_API_KEY="nvapi-..."
37
38
  ...
@@ -136,6 +137,9 @@ Here's a comprehensive list of available methods:
136
137
 
137
138
  | Method | Provider | Model | Price (I/O) per 1 M tokens |
138
139
  | ------------------- | ---------- | ---------------------------- | -------------------------- |
140
+ | `gpt56sol()` | OpenAI | gpt-5.6-sol | [\$5.00 / \$30.00][1] |
141
+ | `gpt56terra()` | OpenAI | gpt-5.6-terra | [\$2.50 / \$15.00][1] |
142
+ | `gpt56luna()` | OpenAI | gpt-5.6-luna | [\$1.00 / \$6.00][1] |
139
143
  | `gpt55()` | OpenAI | gpt-5.5 | [\$5.00 / \$30.00][1] |
140
144
  | `gpt54()` | OpenAI | gpt-5.4 | [\$2.50 / \$15.00][1] |
141
145
  | `gpt54mini()` | OpenAI | gpt-5.4-mini | [\$0.75 / \$4.50][1] |
@@ -172,6 +176,7 @@ Here's a comprehensive list of available methods:
172
176
  | `sonar()` | Perplexity | sonar | [\$1.00 / \$1.00][4] |
173
177
  | `sonarPro()` | Perplexity | sonar-pro | [\$3.00 / \$15.00][4] |
174
178
  | `hermes3()` | Lambda | Hermes-3-Llama-3.1-405B-FP8 | [\$0.80 / \$0.80][8] |
179
+ | `kimiK3()` | Moonshot | kimi-k3 | [\$3.00 / \$15.00][11] |
175
180
  | `kimiK25think()` | Together | Kimi-K2.5 | [\$0.50 / \$2.80][7] |
176
181
  | `kimiK26think()` | Fireworks | models/kimi-k2p6 | [\$0.95 / \$4.00][10] |
177
182
 
@@ -185,10 +190,12 @@ Here's a comprehensive list of available methods:
185
190
  [8]: https://lambda.ai/inference "Lambda Pricing"
186
191
  [9]: https://platform.minimax.io/docs/api-reference/anthropic-api-compatible-cache#supported-models-and-pricing "MiniMax Pricing"
187
192
  [10]: https://fireworks.ai/pricing#serverless-pricing "Fireworks Pricing"
193
+ [11]: https://platform.kimi.ai/docs/guide/kimi-k3-pricing "Kimi K3 Pricing"
188
194
 
189
195
  Each method accepts optional `options`, `config`, and (for multi-provider methods) `mix` parameters to customize behavior.
190
196
  For NVIDIA on DeepSeek V4 Pro, use `deepseekV4Pro({ mix: { nvidia: true } })`.
191
197
  For Together on Qwen 3.6 Plus, use `qwen36plus({ mix: { fireworks: false, together: true } })`.
198
+ For OpenRouter instead of Moonshot's native API, use `kimiK3({ mix: { moonshot: false, openrouter: true } })`.
192
199
 
193
200
  ```javascript
194
201
  const result = await ModelMix.new({
package/demo/default.env CHANGED
@@ -7,4 +7,5 @@ XAI_API_KEY="xai-..."
7
7
  CEREBRAS_API_KEY="csk-..."
8
8
  GEMINI_API_KEY="AIza..."
9
9
  LAMBDA_API_KEY="secret_..."
10
+ MOONSHOT_API_KEY="your-moonshot-key..."
10
11
  BRAVE_API_KEY="BSA0..._fm"
package/index.js CHANGED
@@ -37,6 +37,9 @@ const MODEL_PRICING = {
37
37
  // OpenAI
38
38
  'gpt-realtime-mini': [0.60, 2.40],
39
39
  'gpt-realtime': [4.00, 16.00],
40
+ 'gpt-5.6-sol': [5.00, 30.00],
41
+ 'gpt-5.6-terra': [2.50, 15.00],
42
+ 'gpt-5.6-luna': [1.00, 6.00],
40
43
  'gpt-5.5-pro': [30.00, 180.00],
41
44
  'gpt-5.5': [5.00, 30.00],
42
45
  'gpt-5.4': [2.50, 15.00],
@@ -73,6 +76,7 @@ const MODEL_PRICING = {
73
76
  'gemini-3.1-pro-preview':[2.00, 12.00],
74
77
  'gemini-3-pro-preview': [2.00, 12.00],
75
78
  'gemini-3-flash-preview': [0.50, 3.00],
79
+ 'gemini-3.5-flash': [0.75, 4.50],
76
80
  'gemini-2.5-pro': [1.25, 10.00],
77
81
  'gemini-2.5-flash': [0.30, 2.50],
78
82
  'gemini-3.1-flash-lite-preview': [0.25, 1.50],
@@ -114,6 +118,9 @@ const MODEL_PRICING = {
114
118
  // Kimi K2.5 (Together/Fireworks/OpenRouter)
115
119
  'moonshotai/Kimi-K2.5': [0.50, 2.80],
116
120
  'moonshotai/kimi-k2.5': [0.50, 2.80],
121
+ // Kimi K3
122
+ 'kimi-k3': [3.00, 15.00],
123
+ 'moonshotai/kimi-k3': [3.00, 15.00],
117
124
  // DeepSeek V3.2 (OpenRouter)
118
125
  'deepseek/deepseek-v3.2': [0.56, 1.68],
119
126
  // GLM 4.7 (OpenRouter/Cerebras)
@@ -326,6 +333,15 @@ class ModelMix {
326
333
  gpt55pro({ options = {}, config = {} } = {}) {
327
334
  return this.attach('gpt-5.5-pro', new MixOpenAIResponses({ options, config }));
328
335
  }
336
+ gpt56sol({ options = {}, config = {} } = {}) {
337
+ return this.attach('gpt-5.6-sol', new MixOpenAIResponses({ options, config }));
338
+ }
339
+ gpt56terra({ options = {}, config = {} } = {}) {
340
+ return this.attach('gpt-5.6-terra', new MixOpenAIResponses({ options, config }));
341
+ }
342
+ gpt56luna({ options = {}, config = {} } = {}) {
343
+ return this.attach('gpt-5.6-luna', new MixOpenAIResponses({ options, config }));
344
+ }
329
345
  gptRealtime({ options = {}, config = {} } = {}) {
330
346
  return this.attach('gpt-realtime', new MixOpenAIWebSocket({ options, config }));
331
347
  }
@@ -510,6 +526,13 @@ class ModelMix {
510
526
  return this;
511
527
  }
512
528
 
529
+ kimiK3({ options = {}, config = {}, mix = { moonshot: true, openrouter: false } } = {}) {
530
+ mix = { ...this.mix, ...mix };
531
+ if (mix.moonshot) this.attach('kimi-k3', new MixKimi({ options, config }));
532
+ if (mix.openrouter) this.attach('moonshotai/kimi-k3', new MixOpenRouter({ options, config }));
533
+ return this;
534
+ }
535
+
513
536
  kimiK25think({ options = {}, config = {}, mix = { together: true } } = {}) {
514
537
  mix = { ...this.mix, ...mix };
515
538
  if (mix.together) this.attach('moonshotai/Kimi-K2.5', new MixTogether({ options, config }));
@@ -1066,8 +1089,9 @@ class ModelMix {
1066
1089
  }
1067
1090
 
1068
1091
  if (result.toolCalls && result.toolCalls.length > 0) {
1069
-
1070
- if (result.message) {
1092
+ if (result.assistantMessage) {
1093
+ this.messages.push(result.assistantMessage);
1094
+ } else if (result.message) {
1071
1095
  if (result.signature) {
1072
1096
  this.messages.push({
1073
1097
  role: "assistant", content: [{
@@ -1081,7 +1105,9 @@ class ModelMix {
1081
1105
  }
1082
1106
  }
1083
1107
 
1084
- this.messages.push({ role: "assistant", content: null, tool_calls: result.toolCalls });
1108
+ if (!result.assistantMessage) {
1109
+ this.messages.push({ role: "assistant", content: null, tool_calls: result.toolCalls });
1110
+ }
1085
1111
 
1086
1112
  const toolResults = await this.processToolCalls(result.toolCalls);
1087
1113
  for (const toolResult of toolResults) {
@@ -1140,7 +1166,9 @@ class ModelMix {
1140
1166
  this.messages = [];
1141
1167
  } else if (result.message) {
1142
1168
  // Persist assistant response for multi-turn conversations
1143
- if (result.signature) {
1169
+ if (result.assistantMessage) {
1170
+ this.messages.push(result.assistantMessage);
1171
+ } else if (result.signature) {
1144
1172
  this.messages.push({
1145
1173
  role: "assistant", content: [{
1146
1174
  type: "thinking",
@@ -1640,7 +1668,12 @@ class MixOpenAI extends MixCustom {
1640
1668
  for (const message of messages) {
1641
1669
 
1642
1670
  if (message.tool_calls) {
1643
- results.push({ role: 'assistant', tool_calls: message.tool_calls })
1671
+ results.push({
1672
+ role: 'assistant',
1673
+ content: message.content ?? null,
1674
+ ...(message.reasoning_content && { reasoning_content: message.reasoning_content }),
1675
+ tool_calls: message.tool_calls
1676
+ })
1644
1677
  continue;
1645
1678
  }
1646
1679
 
@@ -2089,6 +2122,46 @@ class MixOpenRouter extends MixOpenAI {
2089
2122
  }
2090
2123
  }
2091
2124
 
2125
+ class MixKimi extends MixOpenAI {
2126
+ getDefaultConfig(customConfig) {
2127
+ if (!process.env.MOONSHOT_API_KEY) {
2128
+ throw new Error('Moonshot API key not found. Please provide it in config or set MOONSHOT_API_KEY environment variable.');
2129
+ }
2130
+
2131
+ return MixCustom.prototype.getDefaultConfig.call(this, {
2132
+ url: 'https://api.moonshot.ai/v1/chat/completions',
2133
+ apiKey: process.env.MOONSHOT_API_KEY,
2134
+ ...customConfig
2135
+ });
2136
+ }
2137
+
2138
+ async create({ config = {}, options = {} } = {}) {
2139
+ if (Object.hasOwn(options, 'max_tokens')) {
2140
+ options.max_completion_tokens = options.max_tokens;
2141
+ delete options.max_tokens;
2142
+ }
2143
+
2144
+ delete options.temperature;
2145
+ delete options.top_p;
2146
+ delete options.n;
2147
+ delete options.presence_penalty;
2148
+ delete options.frequency_penalty;
2149
+
2150
+ return super.create({ config, options });
2151
+ }
2152
+
2153
+ extractDelta(data) {
2154
+ return data?.choices?.[0]?.delta?.content || '';
2155
+ }
2156
+
2157
+ processResponse(response) {
2158
+ return {
2159
+ ...super.processResponse(response),
2160
+ assistantMessage: response.data?.choices?.[0]?.message
2161
+ };
2162
+ }
2163
+ }
2164
+
2092
2165
  class MixAnthropic extends MixCustom {
2093
2166
 
2094
2167
  static thinkingOptions = {
@@ -2918,4 +2991,4 @@ class MixGoogle extends MixCustom {
2918
2991
  }
2919
2992
  }
2920
2993
 
2921
- module.exports = { MixCustom, ModelMix, MixAnthropic, MixMiniMax, MixMiMo, MixOpenAI, MixOpenAIResponses, MixOpenAIWebSocket, MixOpenRouter, MixPerplexity, MixOllama, MixLMStudio, MixGroq, MixTogether, MixGrok, MixCerebras, MixGoogle, MixFireworks, MixNVIDIA };
2994
+ module.exports = { MixCustom, ModelMix, MixAnthropic, MixKimi, MixMiniMax, MixMiMo, MixOpenAI, MixOpenAIResponses, MixOpenAIWebSocket, MixOpenRouter, MixPerplexity, MixOllama, MixLMStudio, MixGroq, MixTogether, MixGrok, MixCerebras, MixGoogle, MixFireworks, MixNVIDIA };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "modelmix",
3
- "version": "4.5.30",
3
+ "version": "4.6.2",
4
4
  "description": "🧬 Reliable interface with automatic fallback for AI LLMs.",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: modelmix
3
- description: Instructions for using the ModelMix Node.js library to interact with multiple AI LLM providers through a unified interface. Use when writing code that calls AI models (OpenAI, Anthropic, Google, Groq, Perplexity, Grok, MiniMax, Fireworks, Together, Lambda, Cerebras, OpenRouter, Ollama, LM Studio), chaining models with fallback, getting structured JSON from LLMs, adding MCP tools, streaming responses, managing multi-provider AI workflows, round-robin load balancing, or rate limiting API requests in Node.js. Also use when the user mentions "modelmix", "ModelMix", asks to "call an LLM", "query a model", "add AI to my app", or wants to integrate any supported provider.
3
+ description: Instructions for using the ModelMix Node.js library to interact with multiple AI LLM providers through a unified interface. Use when writing code that calls AI models (OpenAI, Anthropic, Google, Groq, Perplexity, Grok, Moonshot, MiniMax, Fireworks, Together, Lambda, Cerebras, OpenRouter, Ollama, LM Studio), chaining models with fallback, getting structured JSON from LLMs, adding MCP tools, streaming responses, managing multi-provider AI workflows, round-robin load balancing, or rate limiting API requests in Node.js. Also use when the user mentions "modelmix", "ModelMix", asks to "call an LLM", "query a model", "add AI to my app", or wants to integrate any supported provider.
4
4
  metadata:
5
5
  tags: [llm, ai, openai, anthropic, google, groq, perplexity, grok, mcp, streaming, json-output]
6
6
  ---
@@ -112,6 +112,9 @@ Thinking variants: append `think` — e.g. `fable5think()` `opus48think()` `opus
112
112
  ### Together
113
113
  `qwen36plus()` `GLM52()` `kimiK25think()` `gptOss()`
114
114
 
115
+ ### Moonshot
116
+ `kimiK3()` — requires `MOONSHOT_API_KEY`; use `{ mix: { moonshot: false, openrouter: true } }` for OpenRouter.
117
+
115
118
  ### MiniMax
116
119
  `minimaxM25()` `minimaxM27()` `minimaxM3()`
117
120
 
@@ -471,7 +474,7 @@ const model = ModelMix.new({
471
474
 
472
475
  ## Available Provider Classes
473
476
 
474
- `MixOpenAI` `MixAnthropic` `MixGoogle` `MixPerplexity` `MixGroq` `MixTogether` `MixGrok` `MixOpenRouter` `MixOllama` `MixLMStudio` `MixCustom` `MixCerebras` `MixFireworks` `MixMiniMax` `MixLambda`
477
+ `MixOpenAI` `MixAnthropic` `MixGoogle` `MixPerplexity` `MixGroq` `MixTogether` `MixGrok` `MixOpenRouter` `MixOllama` `MixLMStudio` `MixCustom` `MixCerebras` `MixFireworks` `MixKimi` `MixMiniMax` `MixLambda`
475
478
 
476
479
  ## Troubleshooting
477
480
 
package/test/kimi.test.js CHANGED
@@ -1,5 +1,6 @@
1
1
  const { expect } = require('chai');
2
- const { ModelMix, MixTogether } = require('../index.js');
2
+ const nock = require('nock');
3
+ const { ModelMix, MixKimi, MixOpenRouter, MixTogether } = require('../index.js');
3
4
 
4
5
  describe('Kimi Model Registration Tests', () => {
5
6
  it('should register Together Kimi K2.7 Code by default', () => {
@@ -10,4 +11,146 @@ describe('Kimi Model Registration Tests', () => {
10
11
  expect(model.models[0].key).to.equal('moonshotai/Kimi-K2.7-Code');
11
12
  expect(model.models[0].provider).to.be.instanceOf(MixTogether);
12
13
  });
14
+
15
+ it('should register Kimi K3 with the native Moonshot provider by default', () => {
16
+ const originalMoonshotApiKey = process.env.MOONSHOT_API_KEY;
17
+ process.env.MOONSHOT_API_KEY = 'test-moonshot-key';
18
+
19
+ try {
20
+ const model = ModelMix.new().kimiK3();
21
+
22
+ expect(model.models).to.have.length(1);
23
+ expect(model.models[0].key).to.equal('kimi-k3');
24
+ expect(model.models[0].provider).to.be.instanceOf(MixKimi);
25
+ expect(model.models[0].provider.config.url).to.equal('https://api.moonshot.ai/v1/chat/completions');
26
+ } finally {
27
+ if (originalMoonshotApiKey === undefined) delete process.env.MOONSHOT_API_KEY;
28
+ else process.env.MOONSHOT_API_KEY = originalMoonshotApiKey;
29
+ }
30
+ });
31
+
32
+ it('should support Kimi K3 through OpenRouter when requested', () => {
33
+ const originalOpenRouterApiKey = process.env.OPENROUTER_API_KEY;
34
+ process.env.OPENROUTER_API_KEY = 'test-openrouter-key';
35
+
36
+ try {
37
+ const model = ModelMix.new().kimiK3({ mix: { moonshot: false, openrouter: true } });
38
+
39
+ expect(model.models).to.have.length(1);
40
+ expect(model.models[0].key).to.equal('moonshotai/kimi-k3');
41
+ expect(model.models[0].provider).to.be.instanceOf(MixOpenRouter);
42
+ } finally {
43
+ if (originalOpenRouterApiKey === undefined) delete process.env.OPENROUTER_API_KEY;
44
+ else process.env.OPENROUTER_API_KEY = originalOpenRouterApiKey;
45
+ }
46
+ });
47
+
48
+ it('should adapt Kimi K3 requests to its fixed sampling API', async () => {
49
+ const originalMoonshotApiKey = process.env.MOONSHOT_API_KEY;
50
+ process.env.MOONSHOT_API_KEY = 'test-moonshot-key';
51
+
52
+ try {
53
+ const provider = new MixKimi();
54
+ let requestBody;
55
+ nock('https://api.moonshot.ai')
56
+ .post('/v1/chat/completions', body => {
57
+ requestBody = body;
58
+ return true;
59
+ })
60
+ .reply(200, {
61
+ choices: [{ message: { content: 'Done' } }]
62
+ });
63
+
64
+ await provider.create({
65
+ config: { system: 'You are an assistant.' },
66
+ options: {
67
+ model: 'kimi-k3',
68
+ messages: [{ role: 'user', content: 'Hello' }],
69
+ max_tokens: 1000,
70
+ temperature: 0.5,
71
+ top_p: 0.9,
72
+ n: 2,
73
+ presence_penalty: 0.2,
74
+ frequency_penalty: 0.3
75
+ }
76
+ });
77
+
78
+ expect(requestBody.max_completion_tokens).to.equal(1000);
79
+ expect(requestBody).to.not.have.property('max_tokens');
80
+ expect(requestBody).to.not.have.property('temperature');
81
+ expect(requestBody).to.not.have.property('top_p');
82
+ expect(requestBody).to.not.have.property('n');
83
+ expect(requestBody).to.not.have.property('presence_penalty');
84
+ expect(requestBody).to.not.have.property('frequency_penalty');
85
+ } finally {
86
+ if (originalMoonshotApiKey === undefined) delete process.env.MOONSHOT_API_KEY;
87
+ else process.env.MOONSHOT_API_KEY = originalMoonshotApiKey;
88
+ }
89
+ });
90
+
91
+ it('should preserve Kimi K3 reasoning content in tool-call continuations', async () => {
92
+ const originalMoonshotApiKey = process.env.MOONSHOT_API_KEY;
93
+ process.env.MOONSHOT_API_KEY = 'test-moonshot-key';
94
+
95
+ try {
96
+ let continuationBody;
97
+ nock('https://api.moonshot.ai')
98
+ .post('/v1/chat/completions')
99
+ .reply(200, {
100
+ choices: [{
101
+ message: {
102
+ role: 'assistant',
103
+ content: 'I will calculate that.',
104
+ reasoning_content: 'I need the calculator tool.',
105
+ tool_calls: [{
106
+ id: 'call_calculate',
107
+ type: 'function',
108
+ function: {
109
+ name: 'calculate',
110
+ arguments: '{"expression":"2 + 2"}'
111
+ }
112
+ }]
113
+ }
114
+ }]
115
+ })
116
+ .post('/v1/chat/completions', body => {
117
+ continuationBody = body;
118
+ return true;
119
+ })
120
+ .reply(200, {
121
+ choices: [{ message: { role: 'assistant', content: 'The answer is 4.' } }]
122
+ });
123
+
124
+ const model = ModelMix.new().kimiK3();
125
+ model.addTool({
126
+ name: 'calculate',
127
+ description: 'Evaluates an expression.',
128
+ inputSchema: {
129
+ type: 'object',
130
+ properties: { expression: { type: 'string' } },
131
+ required: ['expression']
132
+ }
133
+ }, ({ expression }) => expression === '2 + 2' ? 4 : null);
134
+ model.addText('What is 2 + 2?');
135
+
136
+ expect(await model.message()).to.equal('The answer is 4.');
137
+ const assistantMessage = continuationBody.messages.find(message => message.role === 'assistant');
138
+ expect(assistantMessage).to.deep.equal({
139
+ role: 'assistant',
140
+ content: 'I will calculate that.',
141
+ reasoning_content: 'I need the calculator tool.',
142
+ tool_calls: [{
143
+ id: 'call_calculate',
144
+ type: 'function',
145
+ function: {
146
+ name: 'calculate',
147
+ arguments: '{"expression":"2 + 2"}'
148
+ }
149
+ }]
150
+ });
151
+ } finally {
152
+ if (originalMoonshotApiKey === undefined) delete process.env.MOONSHOT_API_KEY;
153
+ else process.env.MOONSHOT_API_KEY = originalMoonshotApiKey;
154
+ }
155
+ });
13
156
  });
@@ -88,6 +88,23 @@ describe('Token Usage Tracking', () => {
88
88
  expect(model.models[1].provider).to.be.instanceOf(MixOpenAIResponses);
89
89
  });
90
90
 
91
+ it('should register GPT-5.6 shortcuts with OpenAI Responses provider', function () {
92
+ const model = ModelMix.new()
93
+ .gpt56sol()
94
+ .gpt56terra()
95
+ .gpt56luna();
96
+
97
+ expect(model.models.map(({ key }) => key)).to.deep.equal([
98
+ 'gpt-5.6-sol',
99
+ 'gpt-5.6-terra',
100
+ 'gpt-5.6-luna'
101
+ ]);
102
+ expect(model.models.every(({ provider }) => provider instanceof MixOpenAIResponses)).to.equal(true);
103
+ expect(ModelMix.calculateCost('gpt-5.6-sol', { input: 1_000_000, output: 1_000_000 })).to.equal(35);
104
+ expect(ModelMix.calculateCost('gpt-5.6-terra', { input: 1_000_000, output: 1_000_000 })).to.equal(17.5);
105
+ expect(ModelMix.calculateCost('gpt-5.6-luna', { input: 1_000_000, output: 1_000_000 })).to.equal(7);
106
+ });
107
+
91
108
  it('should register Gemini 3.5 Flash shortcut with Google provider', function () {
92
109
  const model = ModelMix.new()
93
110
  .gemini35flash();
@@ -95,6 +112,7 @@ describe('Token Usage Tracking', () => {
95
112
  expect(model.models).to.have.length(1);
96
113
  expect(model.models[0].key).to.equal('gemini-3.5-flash');
97
114
  expect(model.models[0].provider).to.be.instanceOf(MixGoogle);
115
+ expect(ModelMix.calculateCost('gemini-3.5-flash', { input: 1_000_000, output: 1_000_000 })).to.equal(5.25);
98
116
  });
99
117
 
100
118
  it('should register MiMo shortcuts with native and OpenRouter providers', function () {