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 +9 -0
- package/README.md +7 -0
- package/demo/default.env +1 -0
- package/index.js +79 -6
- package/package.json +1 -1
- package/skills/modelmix/SKILL.md +5 -2
- package/test/kimi.test.js +144 -1
- package/test/tokens.test.js +18 -0
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
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
|
-
|
|
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
|
-
|
|
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.
|
|
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({
|
|
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
package/skills/modelmix/SKILL.md
CHANGED
|
@@ -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
|
|
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
|
});
|
package/test/tokens.test.js
CHANGED
|
@@ -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 () {
|