modelmix 4.6.0 → 4.6.4
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 +13 -8
- package/demo/default.env +1 -0
- package/index.js +70 -6
- package/package.json +1 -1
- package/skills/modelmix/SKILL.md +6 -3
- package/test/kimi.test.js +144 -1
- package/test/tokens.test.js +8 -4
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
|
...
|
|
@@ -48,7 +49,7 @@ try { process.loadEnvFile(); } catch {}
|
|
|
48
49
|
|
|
49
50
|
// Get structured JSON responses
|
|
50
51
|
const model = ModelMix.new()
|
|
51
|
-
.
|
|
52
|
+
.opus48() // Anthropic claude-opus-4-8
|
|
52
53
|
.addText("Name and capital of 3 South American countries.");
|
|
53
54
|
|
|
54
55
|
const outputExample = { countries: [{ name: "", capital: "" }] };
|
|
@@ -66,9 +67,9 @@ const setup = {
|
|
|
66
67
|
};
|
|
67
68
|
|
|
68
69
|
const model = await ModelMix.new(setup)
|
|
69
|
-
.
|
|
70
|
-
.
|
|
71
|
-
.
|
|
70
|
+
.sonnet5() // (main model) Anthropic claude-sonnet-5
|
|
71
|
+
.gpt56luna() // (fallback 2) OpenAI gpt-5.6-luna
|
|
72
|
+
.gemini36flash({ config: { temperature: 0 } }) // (fallback 3) Google gemini-36-flash
|
|
72
73
|
.grok43() // (fallback 4) Grok grok-4.3
|
|
73
74
|
.addText("What's your name?");
|
|
74
75
|
|
|
@@ -113,7 +114,7 @@ BRAVE_API_KEY="BSA0..._fm"
|
|
|
113
114
|
```
|
|
114
115
|
|
|
115
116
|
```javascript
|
|
116
|
-
const mmix = ModelMix.new({ config: { max_history: 10 } }).
|
|
117
|
+
const mmix = ModelMix.new({ config: { max_history: 10 } }).gpt56sol();
|
|
117
118
|
mmix.setSystem('You are an assistant and today is ' + new Date().toISOString());
|
|
118
119
|
|
|
119
120
|
// Add web search capability through MCP
|
|
@@ -160,7 +161,8 @@ Here's a comprehensive list of available methods:
|
|
|
160
161
|
| `sonnet46[think]()` | Anthropic | claude-sonnet-4-6 | [\$3.00 / \$15.00][2] |
|
|
161
162
|
| `haiku45[think]()` | Anthropic | claude-haiku-4-5-20251001 | [\$1.00 / \$5.00][2] |
|
|
162
163
|
| `gemini31pro()` | Google | gemini-3.1-pro-preview | [\$2.00 / \$12.00][3] |
|
|
163
|
-
| `
|
|
164
|
+
| `gemini36flash()` | Google | gemini-3.6-flash | [\$1.50 / \$7.50][3] |
|
|
165
|
+
| `gemini35flash()` | Google | gemini-3.5-flash | [\$0.75 / \$4.50][3] |
|
|
164
166
|
| `gemini31flashLite()`| Google | gemini-3.1-flash-lite-preview | [\$0.25 / \$1.50][3] |
|
|
165
167
|
| `grok43()` | Grok | grok-4.3 | [\$1.25 / \$2.50][6] |
|
|
166
168
|
| `grok420multiAgent()`| Grok | grok-4.20-multi-agent-0309 | [\$1.25 / \$2.50][6] |
|
|
@@ -175,6 +177,7 @@ Here's a comprehensive list of available methods:
|
|
|
175
177
|
| `sonar()` | Perplexity | sonar | [\$1.00 / \$1.00][4] |
|
|
176
178
|
| `sonarPro()` | Perplexity | sonar-pro | [\$3.00 / \$15.00][4] |
|
|
177
179
|
| `hermes3()` | Lambda | Hermes-3-Llama-3.1-405B-FP8 | [\$0.80 / \$0.80][8] |
|
|
180
|
+
| `kimiK3()` | Moonshot | kimi-k3 | [\$3.00 / \$15.00][11] |
|
|
178
181
|
| `kimiK25think()` | Together | Kimi-K2.5 | [\$0.50 / \$2.80][7] |
|
|
179
182
|
| `kimiK26think()` | Fireworks | models/kimi-k2p6 | [\$0.95 / \$4.00][10] |
|
|
180
183
|
|
|
@@ -188,10 +191,12 @@ Here's a comprehensive list of available methods:
|
|
|
188
191
|
[8]: https://lambda.ai/inference "Lambda Pricing"
|
|
189
192
|
[9]: https://platform.minimax.io/docs/api-reference/anthropic-api-compatible-cache#supported-models-and-pricing "MiniMax Pricing"
|
|
190
193
|
[10]: https://fireworks.ai/pricing#serverless-pricing "Fireworks Pricing"
|
|
194
|
+
[11]: https://platform.kimi.ai/docs/guide/kimi-k3-pricing "Kimi K3 Pricing"
|
|
191
195
|
|
|
192
196
|
Each method accepts optional `options`, `config`, and (for multi-provider methods) `mix` parameters to customize behavior.
|
|
193
197
|
For NVIDIA on DeepSeek V4 Pro, use `deepseekV4Pro({ mix: { nvidia: true } })`.
|
|
194
198
|
For Together on Qwen 3.6 Plus, use `qwen36plus({ mix: { fireworks: false, together: true } })`.
|
|
199
|
+
For OpenRouter instead of Moonshot's native API, use `kimiK3({ mix: { moonshot: false, openrouter: true } })`.
|
|
195
200
|
|
|
196
201
|
```javascript
|
|
197
202
|
const result = await ModelMix.new({
|
|
@@ -246,7 +251,7 @@ Analyze the following and provide 3 key insights:
|
|
|
246
251
|
|
|
247
252
|
**`app.js`**
|
|
248
253
|
```javascript
|
|
249
|
-
const gpt = ModelMix.new().
|
|
254
|
+
const gpt = ModelMix.new().gpt56luna();
|
|
250
255
|
|
|
251
256
|
gpt.setSystemFromFile('./prompts/system.md');
|
|
252
257
|
gpt.addTextFromFile('./prompts/task.md');
|
|
@@ -326,7 +331,7 @@ await model.json(schemaExample, schemaDescription, options)
|
|
|
326
331
|
|
|
327
332
|
```javascript
|
|
328
333
|
const model = ModelMix.new()
|
|
329
|
-
.
|
|
334
|
+
.gpt56luna()
|
|
330
335
|
.addText('Name and capital of 3 South American countries.');
|
|
331
336
|
|
|
332
337
|
const result = await model.json({ countries: [{ name: "", capital: "" }] });
|
package/demo/default.env
CHANGED
package/index.js
CHANGED
|
@@ -76,6 +76,7 @@ const MODEL_PRICING = {
|
|
|
76
76
|
'gemini-3.1-pro-preview':[2.00, 12.00],
|
|
77
77
|
'gemini-3-pro-preview': [2.00, 12.00],
|
|
78
78
|
'gemini-3-flash-preview': [0.50, 3.00],
|
|
79
|
+
'gemini-3.6-flash': [1.50, 7.50],
|
|
79
80
|
'gemini-3.5-flash': [0.75, 4.50],
|
|
80
81
|
'gemini-2.5-pro': [1.25, 10.00],
|
|
81
82
|
'gemini-2.5-flash': [0.30, 2.50],
|
|
@@ -118,6 +119,9 @@ const MODEL_PRICING = {
|
|
|
118
119
|
// Kimi K2.5 (Together/Fireworks/OpenRouter)
|
|
119
120
|
'moonshotai/Kimi-K2.5': [0.50, 2.80],
|
|
120
121
|
'moonshotai/kimi-k2.5': [0.50, 2.80],
|
|
122
|
+
// Kimi K3
|
|
123
|
+
'kimi-k3': [3.00, 15.00],
|
|
124
|
+
'moonshotai/kimi-k3': [3.00, 15.00],
|
|
121
125
|
// DeepSeek V3.2 (OpenRouter)
|
|
122
126
|
'deepseek/deepseek-v3.2': [0.56, 1.68],
|
|
123
127
|
// GLM 4.7 (OpenRouter/Cerebras)
|
|
@@ -445,6 +449,9 @@ class ModelMix {
|
|
|
445
449
|
gemini3flash({ options = {}, config = {} } = {}) {
|
|
446
450
|
return this.attach('gemini-3-flash-preview', new MixGoogle({ options, config }));
|
|
447
451
|
}
|
|
452
|
+
gemini36flash({ options = {}, config = {} } = {}) {
|
|
453
|
+
return this.attach('gemini-3.6-flash', new MixGoogle({ options, config }));
|
|
454
|
+
}
|
|
448
455
|
gemini35flash({ options = {}, config = {} } = {}) {
|
|
449
456
|
return this.attach('gemini-3.5-flash', new MixGoogle({ options, config }));
|
|
450
457
|
}
|
|
@@ -523,6 +530,13 @@ class ModelMix {
|
|
|
523
530
|
return this;
|
|
524
531
|
}
|
|
525
532
|
|
|
533
|
+
kimiK3({ options = {}, config = {}, mix = { moonshot: true, openrouter: false } } = {}) {
|
|
534
|
+
mix = { ...this.mix, ...mix };
|
|
535
|
+
if (mix.moonshot) this.attach('kimi-k3', new MixKimi({ options, config }));
|
|
536
|
+
if (mix.openrouter) this.attach('moonshotai/kimi-k3', new MixOpenRouter({ options, config }));
|
|
537
|
+
return this;
|
|
538
|
+
}
|
|
539
|
+
|
|
526
540
|
kimiK25think({ options = {}, config = {}, mix = { together: true } } = {}) {
|
|
527
541
|
mix = { ...this.mix, ...mix };
|
|
528
542
|
if (mix.together) this.attach('moonshotai/Kimi-K2.5', new MixTogether({ options, config }));
|
|
@@ -1079,8 +1093,9 @@ class ModelMix {
|
|
|
1079
1093
|
}
|
|
1080
1094
|
|
|
1081
1095
|
if (result.toolCalls && result.toolCalls.length > 0) {
|
|
1082
|
-
|
|
1083
|
-
|
|
1096
|
+
if (result.assistantMessage) {
|
|
1097
|
+
this.messages.push(result.assistantMessage);
|
|
1098
|
+
} else if (result.message) {
|
|
1084
1099
|
if (result.signature) {
|
|
1085
1100
|
this.messages.push({
|
|
1086
1101
|
role: "assistant", content: [{
|
|
@@ -1094,7 +1109,9 @@ class ModelMix {
|
|
|
1094
1109
|
}
|
|
1095
1110
|
}
|
|
1096
1111
|
|
|
1097
|
-
|
|
1112
|
+
if (!result.assistantMessage) {
|
|
1113
|
+
this.messages.push({ role: "assistant", content: null, tool_calls: result.toolCalls });
|
|
1114
|
+
}
|
|
1098
1115
|
|
|
1099
1116
|
const toolResults = await this.processToolCalls(result.toolCalls);
|
|
1100
1117
|
for (const toolResult of toolResults) {
|
|
@@ -1153,7 +1170,9 @@ class ModelMix {
|
|
|
1153
1170
|
this.messages = [];
|
|
1154
1171
|
} else if (result.message) {
|
|
1155
1172
|
// Persist assistant response for multi-turn conversations
|
|
1156
|
-
if (result.
|
|
1173
|
+
if (result.assistantMessage) {
|
|
1174
|
+
this.messages.push(result.assistantMessage);
|
|
1175
|
+
} else if (result.signature) {
|
|
1157
1176
|
this.messages.push({
|
|
1158
1177
|
role: "assistant", content: [{
|
|
1159
1178
|
type: "thinking",
|
|
@@ -1653,7 +1672,12 @@ class MixOpenAI extends MixCustom {
|
|
|
1653
1672
|
for (const message of messages) {
|
|
1654
1673
|
|
|
1655
1674
|
if (message.tool_calls) {
|
|
1656
|
-
results.push({
|
|
1675
|
+
results.push({
|
|
1676
|
+
role: 'assistant',
|
|
1677
|
+
content: message.content ?? null,
|
|
1678
|
+
...(message.reasoning_content && { reasoning_content: message.reasoning_content }),
|
|
1679
|
+
tool_calls: message.tool_calls
|
|
1680
|
+
})
|
|
1657
1681
|
continue;
|
|
1658
1682
|
}
|
|
1659
1683
|
|
|
@@ -2102,6 +2126,46 @@ class MixOpenRouter extends MixOpenAI {
|
|
|
2102
2126
|
}
|
|
2103
2127
|
}
|
|
2104
2128
|
|
|
2129
|
+
class MixKimi extends MixOpenAI {
|
|
2130
|
+
getDefaultConfig(customConfig) {
|
|
2131
|
+
if (!process.env.MOONSHOT_API_KEY) {
|
|
2132
|
+
throw new Error('Moonshot API key not found. Please provide it in config or set MOONSHOT_API_KEY environment variable.');
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
return MixCustom.prototype.getDefaultConfig.call(this, {
|
|
2136
|
+
url: 'https://api.moonshot.ai/v1/chat/completions',
|
|
2137
|
+
apiKey: process.env.MOONSHOT_API_KEY,
|
|
2138
|
+
...customConfig
|
|
2139
|
+
});
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
async create({ config = {}, options = {} } = {}) {
|
|
2143
|
+
if (Object.hasOwn(options, 'max_tokens')) {
|
|
2144
|
+
options.max_completion_tokens = options.max_tokens;
|
|
2145
|
+
delete options.max_tokens;
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
delete options.temperature;
|
|
2149
|
+
delete options.top_p;
|
|
2150
|
+
delete options.n;
|
|
2151
|
+
delete options.presence_penalty;
|
|
2152
|
+
delete options.frequency_penalty;
|
|
2153
|
+
|
|
2154
|
+
return super.create({ config, options });
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
extractDelta(data) {
|
|
2158
|
+
return data?.choices?.[0]?.delta?.content || '';
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
processResponse(response) {
|
|
2162
|
+
return {
|
|
2163
|
+
...super.processResponse(response),
|
|
2164
|
+
assistantMessage: response.data?.choices?.[0]?.message
|
|
2165
|
+
};
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2105
2169
|
class MixAnthropic extends MixCustom {
|
|
2106
2170
|
|
|
2107
2171
|
static thinkingOptions = {
|
|
@@ -2931,4 +2995,4 @@ class MixGoogle extends MixCustom {
|
|
|
2931
2995
|
}
|
|
2932
2996
|
}
|
|
2933
2997
|
|
|
2934
|
-
module.exports = { MixCustom, ModelMix, MixAnthropic, MixMiniMax, MixMiMo, MixOpenAI, MixOpenAIResponses, MixOpenAIWebSocket, MixOpenRouter, MixPerplexity, MixOllama, MixLMStudio, MixGroq, MixTogether, MixGrok, MixCerebras, MixGoogle, MixFireworks, MixNVIDIA };
|
|
2998
|
+
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
|
---
|
|
@@ -101,7 +101,7 @@ If `sonnet46` fails, it automatically tries `gpt52`, then `gemini3flash`.
|
|
|
101
101
|
Thinking variants: append `think` — e.g. `fable5think()` `opus48think()` `opus47think()` `opus46think()` `sonnet5think()` `sonnet46think()` `sonnet45think()` `sonnet4think()` `opus41think()` `haiku45think()`
|
|
102
102
|
|
|
103
103
|
### Google
|
|
104
|
-
`gemini3pro()` `gemini3flash()` `gemini35flash()` `gemini25pro()` `gemini25flash()`
|
|
104
|
+
`gemini3pro()` `gemini3flash()` `gemini36flash()` `gemini35flash()` `gemini25pro()` `gemini25flash()`
|
|
105
105
|
|
|
106
106
|
### Grok
|
|
107
107
|
`grok43()` `grok420multiAgent()` `grok420()` `grok420think()` `grok41()` `grok41think()`
|
|
@@ -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
|
@@ -105,13 +105,17 @@ describe('Token Usage Tracking', () => {
|
|
|
105
105
|
expect(ModelMix.calculateCost('gpt-5.6-luna', { input: 1_000_000, output: 1_000_000 })).to.equal(7);
|
|
106
106
|
});
|
|
107
107
|
|
|
108
|
-
it('should register Gemini
|
|
108
|
+
it('should register Gemini Flash shortcuts with Google provider', function () {
|
|
109
109
|
const model = ModelMix.new()
|
|
110
|
+
.gemini36flash()
|
|
110
111
|
.gemini35flash();
|
|
111
112
|
|
|
112
|
-
expect(model.models).to.
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
expect(model.models.map(({ key }) => key)).to.deep.equal([
|
|
114
|
+
'gemini-3.6-flash',
|
|
115
|
+
'gemini-3.5-flash'
|
|
116
|
+
]);
|
|
117
|
+
expect(model.models.every(({ provider }) => provider instanceof MixGoogle)).to.equal(true);
|
|
118
|
+
expect(ModelMix.calculateCost('gemini-3.6-flash', { input: 1_000_000, output: 1_000_000 })).to.equal(9);
|
|
115
119
|
expect(ModelMix.calculateCost('gemini-3.5-flash', { input: 1_000_000, output: 1_000_000 })).to.equal(5.25);
|
|
116
120
|
});
|
|
117
121
|
|