modelmix 4.7.4 → 5.0.0
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 +210 -89
- package/demo/demo.js +4 -4
- package/demo/prompt.md +2 -2
- package/http-client.js +15 -2
- package/index.d.ts +100 -11
- package/index.js +896 -273
- package/package.json +4 -1
- package/skills/modelmix/SKILL.md +75 -18
- package/test/README.md +10 -9
- package/test/anthropic.test.js +146 -9
- package/test/effort.test.js +2 -2
- package/test/fallback.test.js +192 -2
- package/test/fixtures/account-details.txt +4 -0
- package/test/fixtures/choice-options.txt +6 -0
- package/test/fixtures/choice-template.txt +2 -0
- package/test/fixtures/system-rules.txt +1 -0
- package/test/fixtures/system-template.txt +2 -0
- package/test/fixtures/template.txt +4 -11
- package/test/grok.test.js +28 -1
- package/test/history.test.js +2 -2
- package/test/live.mcp.js +20 -20
- package/test/live.test.js +11 -11
- package/test/templates.test.js +471 -281
- package/test/tokens.test.js +409 -16
package/README.md
CHANGED
|
@@ -163,54 +163,54 @@ Here's a comprehensive list of available methods:
|
|
|
163
163
|
|
|
164
164
|
| Method | Provider | Model | Price (I/O) per 1 M tokens |
|
|
165
165
|
| ------------------- | ---------- | ---------------------------- | -------------------------- |
|
|
166
|
-
| `gpt56sol()` | OpenAI | gpt-5.6-sol | [\$5.00
|
|
167
|
-
| `gpt56terra()` | OpenAI | gpt-5.6-terra | [\$2.00
|
|
168
|
-
| `gpt56luna()` | OpenAI | gpt-5.6-luna | [\$0.20
|
|
169
|
-
| `gpt55()` | OpenAI | gpt-5.5 | [\$5.00
|
|
170
|
-
| `gpt54()` | OpenAI | gpt-5.4 | [\$2.50
|
|
171
|
-
| `gpt54mini()` | OpenAI | gpt-5.4-mini | [\$0.75
|
|
172
|
-
| `gpt54nano()` | OpenAI | gpt-5.4-nano | [\$0.20
|
|
173
|
-
| `gpt53codex()` | OpenAI | gpt-5.3-codex | [\$1.25
|
|
174
|
-
| `gpt52()` | OpenAI | gpt-5.2 | [\$1.75
|
|
175
|
-
| `gpt51()` | OpenAI | gpt-5.1 | [\$1.25
|
|
176
|
-
| `gpt5mini()` | OpenAI | gpt-5-mini | [\$0.25
|
|
177
|
-
| `gpt5nano()` | OpenAI | gpt-5-nano | [\$0.05
|
|
178
|
-
| `gpt41()` | OpenAI | gpt-4.1 | [\$2.00
|
|
179
|
-
| `gpt41mini()` | OpenAI | gpt-4.1-mini | [\$0.40
|
|
180
|
-
| `gpt41nano()` | OpenAI | gpt-4.1-nano | [\$0.10
|
|
181
|
-
| `gptOss()` | Together | gpt-oss-120B | [\$0.15
|
|
182
|
-
| `fable5()` | Anthropic | claude-fable-5 | [\$10.00
|
|
183
|
-
| `opus5()` | Anthropic | claude-opus-5 | [\$5.00
|
|
184
|
-
| `opus48()` | Anthropic | claude-opus-4-8 | [\$5.00
|
|
185
|
-
| `opus47()` | Anthropic | claude-opus-4-7 | [\$5.00
|
|
186
|
-
| `opus46()` | Anthropic | claude-opus-4-6 | [\$5.00
|
|
187
|
-
| `sonnet5()` | Anthropic | claude-sonnet-5 | [\$3.00
|
|
188
|
-
| `sonnet46()` | Anthropic | claude-sonnet-4-6 | [\$3.00
|
|
189
|
-
| `haiku45()` | Anthropic | claude-haiku-4-5-20251001 | [\$1.00
|
|
190
|
-
| `gemini31pro()` | Google | gemini-3.1-pro-preview | [\$2.00
|
|
191
|
-
| `gemini36flash()`
|
|
192
|
-
| `gemini35flash()`
|
|
193
|
-
| `gemini35flashLite()`| Google
|
|
194
|
-
| `gemini31flashLite()`| Google | gemini-3.1-flash-lite-preview | [\$0.25
|
|
195
|
-
| `grok45()` | Grok | grok-4.5 | [\$2.00
|
|
196
|
-
| `grok43()` | Grok | grok-4.3 | [\$1.25
|
|
197
|
-
| `grok420multiAgent()`| Grok | grok-4.20-multi-agent-0309 | [\$1.25
|
|
198
|
-
| `grok420()` | Grok | grok-4.20-0309 (†) | [\$1.25
|
|
199
|
-
| `qwen36plus()` | Fireworks
|
|
200
|
-
| `qwen37plus()` | Fireworks | models/qwen3p7-plus | [\$0.40
|
|
201
|
-
| `qwen38max()` | OpenRouter | qwen/qwen3.8-max | [\$2.00
|
|
202
|
-
| `deepseekV4Flash()` | Fireworks | models/deepseek-v4-flash
|
|
203
|
-
| `deepseekV4Pro()` | Fireworks | models/deepseek-v4-pro | [\$1.74
|
|
204
|
-
| `GLM52()` | Together | zai-org/GLM-5.2 | [\$1.40
|
|
205
|
-
| `GLM51()` | Fireworks | models/glm-5p1 | [\$1.05
|
|
206
|
-
| `minimaxM3()` | MiniMax | MiniMax-M3 | [\$0.30
|
|
207
|
-
| `minimaxM27()` | MiniMax | MiniMax-M2.7 | [\$0.30
|
|
208
|
-
| `sonar()` | Perplexity | sonar | [\$1.00
|
|
209
|
-
| `sonarPro()` | Perplexity | sonar-pro | [\$3.00
|
|
210
|
-
| `hermes3()` | Lambda | Hermes-3-Llama-3.1-405B-FP8 | [\$0.80
|
|
211
|
-
| `kimiK3()` | Moonshot | kimi-k3 | [\$3.00
|
|
212
|
-
| `kimiK25()` | Together | Kimi-K2.5 | [\$0.50
|
|
213
|
-
| `kimiK26()` | Fireworks | models/kimi-k2p6 | [\$0.95
|
|
166
|
+
| `gpt56sol()` | OpenAI | gpt-5.6-sol | [\$5.00/\$30.00][1] |
|
|
167
|
+
| `gpt56terra()` | OpenAI | gpt-5.6-terra | [\$2.00/\$12.00][1] |
|
|
168
|
+
| `gpt56luna()` | OpenAI | gpt-5.6-luna | [\$0.20/\$1.20][1] |
|
|
169
|
+
| `gpt55()` | OpenAI | gpt-5.5 | [\$5.00/\$30.00][1] |
|
|
170
|
+
| `gpt54()` | OpenAI | gpt-5.4 | [\$2.50/\$15.00][1] |
|
|
171
|
+
| `gpt54mini()` | OpenAI | gpt-5.4-mini | [\$0.75/\$4.50][1] |
|
|
172
|
+
| `gpt54nano()` | OpenAI | gpt-5.4-nano | [\$0.20/\$1.25][1] |
|
|
173
|
+
| `gpt53codex()` | OpenAI | gpt-5.3-codex | [\$1.25/\$14.00][1] |
|
|
174
|
+
| `gpt52()` | OpenAI | gpt-5.2 | [\$1.75/\$14.00][1] |
|
|
175
|
+
| `gpt51()` | OpenAI | gpt-5.1 | [\$1.25/\$10.00][1] |
|
|
176
|
+
| `gpt5mini()` | OpenAI | gpt-5-mini | [\$0.25/\$2.00][1] |
|
|
177
|
+
| `gpt5nano()` | OpenAI | gpt-5-nano | [\$0.05/\$0.40][1] |
|
|
178
|
+
| `gpt41()` | OpenAI | gpt-4.1 | [\$2.00/\$8.00][1] |
|
|
179
|
+
| `gpt41mini()` | OpenAI | gpt-4.1-mini | [\$0.40/\$1.60][1] |
|
|
180
|
+
| `gpt41nano()` | OpenAI | gpt-4.1-nano | [\$0.10/\$0.40][1] |
|
|
181
|
+
| `gptOss()` | Together | gpt-oss-120B | [\$0.15/\$0.60][7] |
|
|
182
|
+
| `fable5()` | Anthropic | claude-fable-5 | [\$10.00/\$50.00][2] |
|
|
183
|
+
| `opus5()` | Anthropic | claude-opus-5 | [\$5.00/\$25.00][2] |
|
|
184
|
+
| `opus48()` | Anthropic | claude-opus-4-8 | [\$5.00/\$25.00][2] |
|
|
185
|
+
| `opus47()` | Anthropic | claude-opus-4-7 | [\$5.00/\$25.00][2] |
|
|
186
|
+
| `opus46()` | Anthropic | claude-opus-4-6 | [\$5.00/\$25.00][2] |
|
|
187
|
+
| `sonnet5()` | Anthropic | claude-sonnet-5 | [\$3.00/\$15.00][2] |
|
|
188
|
+
| `sonnet46()` | Anthropic | claude-sonnet-4-6 | [\$3.00/\$15.00][2] |
|
|
189
|
+
| `haiku45()` | Anthropic | claude-haiku-4-5-20251001 | [\$1.00/\$5.00][2] |
|
|
190
|
+
| `gemini31pro()` | Google | gemini-3.1-pro-preview | [\$2.00/\$12.00][3] |
|
|
191
|
+
| `gemini36flash()` | Google | gemini-3.6-flash | [\$1.50/\$7.50][3] |
|
|
192
|
+
| `gemini35flash()` | Google | gemini-3.5-flash | [\$0.75/\$4.50][3] |
|
|
193
|
+
| `gemini35flashLite()`| Google | gemini-3.5-flash-lite | [\$0.30/\$2.50][3] |
|
|
194
|
+
| `gemini31flashLite()`| Google | gemini-3.1-flash-lite-preview | [\$0.25/\$1.50][3] |
|
|
195
|
+
| `grok45()` | Grok | grok-4.5 | [\$2.00/\$6.00][6] |
|
|
196
|
+
| `grok43()` | Grok | grok-4.3 | [\$1.25/\$2.50][6] |
|
|
197
|
+
| `grok420multiAgent()`| Grok | grok-4.20-multi-agent-0309 | [\$1.25/\$2.50][6] |
|
|
198
|
+
| `grok420()` | Grok | grok-4.20-0309 (†) | [\$1.25/\$2.50][6] |
|
|
199
|
+
| `qwen36plus()` | Fireworks | qwen3p6-plus | [\$0.50/\$3.00][10] |
|
|
200
|
+
| `qwen37plus()` | Fireworks | models/qwen3p7-plus | [\$0.40/\$1.60][10] |
|
|
201
|
+
| `qwen38max()` | OpenRouter | qwen/qwen3.8-max | [\$2.00/\$6.00][12] |
|
|
202
|
+
| `deepseekV4Flash()` | Fireworks | models/deepseek-v4-flash | [\$0.14/\$0.28][10] |
|
|
203
|
+
| `deepseekV4Pro()` | Fireworks | models/deepseek-v4-pro | [\$1.74/\$3.48][10] |
|
|
204
|
+
| `GLM52()` | Together | zai-org/GLM-5.2 | [\$1.40/\$4.40][7] |
|
|
205
|
+
| `GLM51()` | Fireworks | models/glm-5p1 | [\$1.05/\$3.50][10] |
|
|
206
|
+
| `minimaxM3()` | MiniMax | MiniMax-M3 | [\$0.30/\$1.20][9] |
|
|
207
|
+
| `minimaxM27()` | MiniMax | MiniMax-M2.7 | [\$0.30/\$1.20][9] |
|
|
208
|
+
| `sonar()` | Perplexity | sonar | [\$1.00/\$1.00][4] |
|
|
209
|
+
| `sonarPro()` | Perplexity | sonar-pro | [\$3.00/\$15.00][4] |
|
|
210
|
+
| `hermes3()` | Lambda | Hermes-3-Llama-3.1-405B-FP8 | [\$0.80/\$0.80][8] |
|
|
211
|
+
| `kimiK3()` | Moonshot | kimi-k3 | [\$3.00/\$15.00][11] |
|
|
212
|
+
| `kimiK25()` | Together | Kimi-K2.5 | [\$0.50/\$2.80][7] |
|
|
213
|
+
| `kimiK26()` | Fireworks | models/kimi-k2p6 | [\$0.95/\$4.00][10] |
|
|
214
214
|
|
|
215
215
|
[1]: https://platform.openai.com/docs/pricing "Pricing | OpenAI"
|
|
216
216
|
[2]: https://docs.anthropic.com/en/docs/about-claude/pricing "Pricing - Anthropic"
|
|
@@ -226,11 +226,6 @@ Here's a comprehensive list of available methods:
|
|
|
226
226
|
[12]: https://openrouter.ai/qwen/qwen3.8-max "OpenRouter Pricing"
|
|
227
227
|
|
|
228
228
|
Each method accepts optional `options`, `config`, and (for multi-provider methods) `mix` parameters to customize behavior.
|
|
229
|
-
† `grok420()` resolves to `grok-4.20-0309-non-reasoning` by default, or `grok-4.20-0309-reasoning` when `.effort(20+)` / `-1` (or native non-`none` `reasoning_effort`) is set.
|
|
230
|
-
For NVIDIA on DeepSeek V4 Flash/Pro, use `deepseekV4Flash({ mix: { nvidia: true } })` or `deepseekV4Pro({ mix: { nvidia: true } })`.
|
|
231
|
-
For Together on Qwen 3.6 Plus, use `qwen36plus({ mix: { fireworks: false, together: true } })`.
|
|
232
|
-
For OpenRouter on Qwen 3.7 Plus, use `qwen37plus({ mix: { fireworks: false, openrouter: true } })`.
|
|
233
|
-
For OpenRouter instead of Moonshot's native API, use `kimiK3({ mix: { moonshot: false, openrouter: true } })`.
|
|
234
229
|
|
|
235
230
|
```javascript
|
|
236
231
|
const result = await ModelMix.new({
|
|
@@ -244,7 +239,9 @@ const result = await ModelMix.new({
|
|
|
244
239
|
|
|
245
240
|
## 🔄 Templates
|
|
246
241
|
|
|
247
|
-
ModelMix
|
|
242
|
+
ModelMix renders system prompts and user messages with [EJS](https://ejs.co/). Templates can be inline or stored in external files, and support variables, conditionals, loops, and relative includes.
|
|
243
|
+
|
|
244
|
+
Templates are executable JavaScript and must be controlled by the developer. Pass untrusted content only as template data, never as the template source.
|
|
248
245
|
|
|
249
246
|
### Core methods
|
|
250
247
|
|
|
@@ -252,16 +249,16 @@ ModelMix includes a simple but powerful templating system. You can write your sy
|
|
|
252
249
|
| --- | --- |
|
|
253
250
|
| `setSystemFromFile(path)` | Load the system prompt from a file |
|
|
254
251
|
| `addTextFromFile(path)` | Load a user message from a file |
|
|
255
|
-
| `replace({ key: value })` |
|
|
256
|
-
| `replaceKeyFromFile(key, path)` |
|
|
252
|
+
| `replace({ key: value })` | Add EJS template data |
|
|
253
|
+
| `replaceKeyFromFile(key, path)` | Add a file's raw contents as template data |
|
|
257
254
|
|
|
258
255
|
### Basic example with `replace`
|
|
259
256
|
|
|
260
257
|
```javascript
|
|
261
258
|
const gpt = ModelMix.new().gpt52();
|
|
262
259
|
|
|
263
|
-
gpt.addText('Write a short story about a
|
|
264
|
-
gpt.replace({
|
|
260
|
+
gpt.addText('Write a short story about a <%- animal %> that lives in <%- place %>.');
|
|
261
|
+
gpt.replace({ animal: 'cat', place: 'a haunted castle' });
|
|
265
262
|
|
|
266
263
|
console.log(await gpt.message());
|
|
267
264
|
```
|
|
@@ -272,15 +269,15 @@ Instead of writing long prompts inline, keep them in separate Markdown files. Th
|
|
|
272
269
|
|
|
273
270
|
**`prompts/system.md`**
|
|
274
271
|
```markdown
|
|
275
|
-
You are
|
|
276
|
-
Always respond in
|
|
272
|
+
You are <%- role %>, an expert in <%- topic %>.
|
|
273
|
+
Always respond in <%- language %>.
|
|
277
274
|
```
|
|
278
275
|
|
|
279
276
|
**`prompts/task.md`**
|
|
280
277
|
```markdown
|
|
281
278
|
Analyze the following and provide 3 key insights:
|
|
282
279
|
|
|
283
|
-
|
|
280
|
+
<%- content %>
|
|
284
281
|
```
|
|
285
282
|
|
|
286
283
|
**`app.js`**
|
|
@@ -291,16 +288,16 @@ gpt.setSystemFromFile('./prompts/system.md');
|
|
|
291
288
|
gpt.addTextFromFile('./prompts/task.md');
|
|
292
289
|
|
|
293
290
|
gpt.replace({
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
291
|
+
role: 'a senior analyst',
|
|
292
|
+
topic: 'market trends',
|
|
293
|
+
language: 'Spanish',
|
|
294
|
+
content: 'Bitcoin surpassed $100,000 in December 2024...'
|
|
298
295
|
});
|
|
299
296
|
|
|
300
297
|
console.log(await gpt.message());
|
|
301
298
|
```
|
|
302
299
|
|
|
303
|
-
### Injecting file contents
|
|
300
|
+
### Injecting file contents as template data
|
|
304
301
|
|
|
305
302
|
Use `replaceKeyFromFile` when the replacement value itself is a large text stored in a file.
|
|
306
303
|
|
|
@@ -308,7 +305,7 @@ Use `replaceKeyFromFile` when the replacement value itself is a large text store
|
|
|
308
305
|
```markdown
|
|
309
306
|
Summarize the following article in 3 bullet points:
|
|
310
307
|
|
|
311
|
-
|
|
308
|
+
<%- article %>
|
|
312
309
|
```
|
|
313
310
|
|
|
314
311
|
**`app.js`**
|
|
@@ -316,7 +313,7 @@ Summarize the following article in 3 bullet points:
|
|
|
316
313
|
const gpt = ModelMix.new().gpt5mini();
|
|
317
314
|
|
|
318
315
|
gpt.addTextFromFile('./prompts/summarize.md');
|
|
319
|
-
gpt.replaceKeyFromFile('
|
|
316
|
+
gpt.replaceKeyFromFile('article', './data/article.md');
|
|
320
317
|
|
|
321
318
|
console.log(await gpt.message());
|
|
322
319
|
```
|
|
@@ -327,17 +324,17 @@ Combine all methods to build reusable, file-based prompt pipelines:
|
|
|
327
324
|
|
|
328
325
|
**`prompts/system.md`**
|
|
329
326
|
```markdown
|
|
330
|
-
You are
|
|
327
|
+
You are <%- role %>. Follow these rules:
|
|
331
328
|
- Be concise
|
|
332
329
|
- Use examples when possible
|
|
333
|
-
- Respond in
|
|
330
|
+
- Respond in <%- language %>
|
|
334
331
|
```
|
|
335
332
|
|
|
336
333
|
**`prompts/review.md`**
|
|
337
334
|
```markdown
|
|
338
335
|
Review the following code and suggest improvements:
|
|
339
336
|
|
|
340
|
-
|
|
337
|
+
<%- code %>
|
|
341
338
|
```
|
|
342
339
|
|
|
343
340
|
**`app.js`**
|
|
@@ -347,12 +344,63 @@ const gpt = ModelMix.new().gpt5mini();
|
|
|
347
344
|
gpt.setSystemFromFile('./prompts/system.md');
|
|
348
345
|
gpt.addTextFromFile('./prompts/review.md');
|
|
349
346
|
|
|
350
|
-
gpt.replace({
|
|
351
|
-
gpt.replaceKeyFromFile('
|
|
347
|
+
gpt.replace({ role: 'a senior code reviewer', language: 'English' });
|
|
348
|
+
gpt.replaceKeyFromFile('code', './src/utils.js');
|
|
352
349
|
|
|
353
350
|
console.log(await gpt.message());
|
|
354
351
|
```
|
|
355
352
|
|
|
353
|
+
### EJS output and control flow
|
|
354
|
+
|
|
355
|
+
Use `<%- value %>` for raw prompt content and `<%= value %>` only when XML escaping is intentional. Missing variables and missing files throw immediately.
|
|
356
|
+
|
|
357
|
+
```ejs
|
|
358
|
+
<% if (user.active) { %>
|
|
359
|
+
Review these roles:
|
|
360
|
+
<% user.roles.forEach(role => { %>
|
|
361
|
+
- <%- role %>
|
|
362
|
+
<% }) %>
|
|
363
|
+
<% } %>
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Random prompt choices
|
|
367
|
+
|
|
368
|
+
Use a `choice` block to include exactly one prompt variant. When no weights are present, every option has the same probability:
|
|
369
|
+
|
|
370
|
+
```ejs
|
|
371
|
+
<% choice %>
|
|
372
|
+
<% option %>
|
|
373
|
+
Use emojis.
|
|
374
|
+
<% option %>
|
|
375
|
+
Use few emojis.
|
|
376
|
+
<% option %>
|
|
377
|
+
Do not use emojis.
|
|
378
|
+
<% /choice %>
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
Add a positive weight after every `option` when the probabilities should differ:
|
|
382
|
+
|
|
383
|
+
```ejs
|
|
384
|
+
<% choice %>
|
|
385
|
+
<% option 20 %>
|
|
386
|
+
Use emojis.
|
|
387
|
+
<% option 40 %>
|
|
388
|
+
Use few emojis.
|
|
389
|
+
<% option 40 %>
|
|
390
|
+
Do not use emojis.
|
|
391
|
+
<% /choice %>
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Weights are relative and do not need to total 100. A block must either give every option a weight or omit all weights. Directives must be on their own lines; choices can be nested and can also appear inside relative includes. Each new request makes a new selection, while retries, provider fallbacks, and tool continuations keep the original selection.
|
|
395
|
+
|
|
396
|
+
File templates can include files relative to their own path:
|
|
397
|
+
|
|
398
|
+
```ejs
|
|
399
|
+
<%- include('shared/rules.md') %>
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
Content supplied through `replace()` or `replaceKeyFromFile()` is rendered once as data. EJS tags inside that content are not executed recursively.
|
|
403
|
+
|
|
356
404
|
## 🧩 JSON Structured Output
|
|
357
405
|
|
|
358
406
|
The `json` method forces the model to return a structured JSON response. You define the shape with an example object and optionally describe each field.
|
|
@@ -541,12 +589,29 @@ Every response from `raw()` now includes a `tokens` object with the following st
|
|
|
541
589
|
```javascript
|
|
542
590
|
{
|
|
543
591
|
tokens: {
|
|
544
|
-
input:
|
|
545
|
-
output:
|
|
546
|
-
total:
|
|
547
|
-
cached:
|
|
548
|
-
|
|
549
|
-
|
|
592
|
+
input: 1200, // Total input tokens, including cache reads and writes
|
|
593
|
+
output: 50, // Number of output tokens
|
|
594
|
+
total: 1250, // Total tokens used
|
|
595
|
+
cached: 1024, // Input tokens read from cache
|
|
596
|
+
cacheWrite: 0, // Input tokens written to cache
|
|
597
|
+
cacheWrite5m: 0, // Anthropic writes using the 5-minute TTL
|
|
598
|
+
cacheWrite1h: 0, // Anthropic writes using the 1-hour TTL
|
|
599
|
+
uncachedInput: 176, // max(0, input - cached - cacheWrite)
|
|
600
|
+
cacheHitRate: 0.8533, // cached / input, rounded to 4 decimals
|
|
601
|
+
cacheSavings: 0.00018432, // USD saved by cache reads
|
|
602
|
+
cacheWritePremium: 0, // Extra USD paid to write this cache entry
|
|
603
|
+
breakEvenHits: 0, // Full future hits needed to recover that premium
|
|
604
|
+
cost: 0.00011568, // Total estimated cost in USD
|
|
605
|
+
costBreakdown: {
|
|
606
|
+
uncachedInput: 0.0000352,
|
|
607
|
+
cachedInput: 0.00002048,
|
|
608
|
+
cacheWrite: 0,
|
|
609
|
+
cacheWrite5m: 0,
|
|
610
|
+
cacheWrite1h: 0,
|
|
611
|
+
output: 0.00006,
|
|
612
|
+
total: 0.00011568
|
|
613
|
+
},
|
|
614
|
+
speed: 42 // Output tokens per second (int)
|
|
550
615
|
}
|
|
551
616
|
}
|
|
552
617
|
```
|
|
@@ -558,10 +623,66 @@ After calling `message()` or `json()`, use `lastRaw` to access the complete resp
|
|
|
558
623
|
```javascript
|
|
559
624
|
const text = await model.message();
|
|
560
625
|
console.log(model.lastRaw.tokens);
|
|
561
|
-
//
|
|
626
|
+
// Same normalized token and cost structure returned by raw()
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
`cached` aggregates cache reads reported by the provider, while `cacheWrite` aggregates cache writes. Anthropic additionally exposes `cacheWrite5m` and `cacheWrite1h` because those writes cost 1.25× and 2× the normal input rate, respectively. `cacheSavings` compares cache reads with the normal input rate, `cacheWritePremium` compares writes with that rate, and `breakEvenHits` estimates how many complete future hits recover the current write premium. For Anthropic, `input` is normalized to include uncached input, cache reads, and cache writes. Missing usage or pricing categories return `0`. The `speed` field is the generation speed measured in output tokens per second (integer).
|
|
630
|
+
|
|
631
|
+
### GPT-5.6 prompt caching
|
|
632
|
+
|
|
633
|
+
GPT-5.6 supports implicit or explicit caching through `prompt_cache_options`. Put the explicit breakpoint at the end of the stable prefix; the provider only caches prompts with at least 1,024 tokens.
|
|
634
|
+
|
|
635
|
+
```javascript
|
|
636
|
+
const model = ModelMix.new()
|
|
637
|
+
.gpt56luna({
|
|
638
|
+
options: {
|
|
639
|
+
prompt_cache_key: 'support-agent-v1',
|
|
640
|
+
prompt_cache_options: { mode: 'explicit', ttl: '30m' }
|
|
641
|
+
}
|
|
642
|
+
})
|
|
643
|
+
.addText(longStableInstructions, {
|
|
644
|
+
cache: { breakpoint: true }
|
|
645
|
+
})
|
|
646
|
+
.addText('Answer this variable request.');
|
|
647
|
+
|
|
648
|
+
const result = await model.raw();
|
|
649
|
+
console.log(result.tokens.cached, result.tokens.cacheWrite, result.tokens.cost);
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
The provider-neutral `cache: { breakpoint: true }` option is accepted by `addTextFromFile()`, `addImage()`, `addImageFromUrl()`, and `addImageFromBuffer()`. Responses-native `input_text`, `input_image`, and `input_file` blocks preserve the native `prompt_cache_breakpoint` field when supplied directly through `options.messages`.
|
|
653
|
+
|
|
654
|
+
GPT-5.6 uses `prompt_cache_options.ttl`; `prompt_cache_retention` remains available for earlier OpenAI models. ModelMix rejects the incompatible control instead of silently dropping it. For GPT-5.6 requests over 272K input tokens, the cost calculation applies the documented 2× input and 1.5× output multipliers to the complete request, including cache reads and writes.
|
|
655
|
+
|
|
656
|
+
GPT-5.6 prices per 1M tokens:
|
|
657
|
+
|
|
658
|
+
| Model | Input | Cached input | Cache write | Output |
|
|
659
|
+
| --- | ---: | ---: | ---: | ---: |
|
|
660
|
+
| `gpt-5.6-sol` | $5.00 | $0.50 | $6.25 | $30.00 |
|
|
661
|
+
| `gpt-5.6-terra` | $2.00 | $0.20 | $2.50 | $12.00 |
|
|
662
|
+
| `gpt-5.6-luna` | $0.20 | $0.02 | $0.25 | $1.20 |
|
|
663
|
+
|
|
664
|
+
### Cross-provider cache fallback
|
|
665
|
+
|
|
666
|
+
Neutral breakpoints are translated at the last moment by each provider adapter. Native request policies remain scoped to their model, so they cannot leak into a fallback request:
|
|
667
|
+
|
|
668
|
+
```javascript
|
|
669
|
+
const model = ModelMix.new()
|
|
670
|
+
.gpt56luna({
|
|
671
|
+
options: {
|
|
672
|
+
prompt_cache_key: 'support-agent-v1',
|
|
673
|
+
prompt_cache_options: { mode: 'explicit', ttl: '30m' }
|
|
674
|
+
}
|
|
675
|
+
})
|
|
676
|
+
.haiku45({
|
|
677
|
+
options: {
|
|
678
|
+
cache_control: { type: 'ephemeral', ttl: '1h' }
|
|
679
|
+
}
|
|
680
|
+
})
|
|
681
|
+
.addText(longStableInstructions, { cache: { breakpoint: true } })
|
|
682
|
+
.addText('Answer this variable request.');
|
|
562
683
|
```
|
|
563
684
|
|
|
564
|
-
|
|
685
|
+
GPT-5.6 receives `prompt_cache_breakpoint`; Anthropic receives `cache_control`; older OpenAI models and providers without an equivalent omit the marker. When a neutral explicit breakpoint is present for Anthropic, its model-scoped `cache_control` becomes that block's policy instead of adding an automatic breakpoint after the variable suffix.
|
|
565
686
|
|
|
566
687
|
## 🐛 Enabling Debug Mode
|
|
567
688
|
|
|
@@ -675,18 +796,18 @@ new ModelMix(args = { options: {}, config: {} })
|
|
|
675
796
|
|
|
676
797
|
- `setSystem(text)`: Sets the system prompt.
|
|
677
798
|
- `setSystemFromFile(filePath)`: Sets the system prompt from a file.
|
|
678
|
-
- `addText(text, config = { role: "user" })`: Adds a text message.
|
|
679
|
-
- `addTextFromFile(filePath, config = { role: "user" })`: Adds a text message from a file.
|
|
680
|
-
- `addImage(filePath, config = { role: "user" })`: Adds an image message from a file path.
|
|
681
|
-
- `addImageFromUrl(url, config = { role: "user" })`: Adds an image message from URL.
|
|
682
|
-
- `replace(keyValues)`:
|
|
683
|
-
- `replaceKeyFromFile(key, filePath)`:
|
|
799
|
+
- `addText(text, config = { role: "user", cache? })`: Adds a text message.
|
|
800
|
+
- `addTextFromFile(filePath, config = { role: "user", cache? })`: Adds a text message from a file.
|
|
801
|
+
- `addImage(filePath, config = { role: "user", cache? })`: Adds an image message from a file path.
|
|
802
|
+
- `addImageFromUrl(url, config = { role: "user", cache? })`: Adds an image message from URL.
|
|
803
|
+
- `replace(keyValues)`: Adds EJS data for messages and system prompts.
|
|
804
|
+
- `replaceKeyFromFile(key, filePath)`: Adds raw file contents as an EJS data value.
|
|
684
805
|
- `message()`: Sends the message and returns the response.
|
|
685
806
|
- `raw()`: Sends the message and returns the complete response data including:
|
|
686
807
|
- `message`: The text response from the model
|
|
687
808
|
- `think`: Reasoning/thinking content (if available)
|
|
688
809
|
- `toolCalls`: Array of tool calls made by the model (if any)
|
|
689
|
-
- `tokens`:
|
|
810
|
+
- `tokens`: Normalized token counts (`input`, `output`, `total`, `cached`, `cacheWrite`, `cacheWrite5m`, `cacheWrite1h`, `uncachedInput`, `cacheHitRate`), cache economics (`cacheSavings`, `cacheWritePremium`, `breakEvenHits`), plus `cost`, `costBreakdown` (USD), and `speed` (output tokens/sec)
|
|
690
811
|
- `response`: The raw API response
|
|
691
812
|
- `stream(callback)`: Sends the message and streams the response, invoking the callback with each streamed part.
|
|
692
813
|
- `json(schemaExample, descriptions = {}, options = {})`: Forces the model to return a response in a specific JSON format.
|
|
@@ -829,4 +950,4 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
|
829
950
|
|
|
830
951
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
831
952
|
|
|
832
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
953
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/demo/demo.js
CHANGED
|
@@ -6,7 +6,7 @@ const mmix = new ModelMix({
|
|
|
6
6
|
temperature: 0.5,
|
|
7
7
|
},
|
|
8
8
|
config: {
|
|
9
|
-
system: 'You are
|
|
9
|
+
system: 'You are <%- name %> from Melmac.',
|
|
10
10
|
max_history: 2,
|
|
11
11
|
bottleneck: { maxConcurrent: 1 },
|
|
12
12
|
debug: 3,
|
|
@@ -22,12 +22,12 @@ const pplxSettings = {
|
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
mmix.replace({
|
|
25
|
+
mmix.replace({ name: 'ALF' });
|
|
26
26
|
|
|
27
27
|
console.log("\n" + '--------| gpt51() |--------');
|
|
28
28
|
const gptArgs = { options: { reasoning_effort: "none", verbosity: "low" } };
|
|
29
|
-
const gpt = mmix.gpt51(gptArgs).addText("Have you ever eaten a
|
|
30
|
-
gpt.replace({
|
|
29
|
+
const gpt = mmix.gpt51(gptArgs).addText("Have you ever eaten a <%- animal %>?");
|
|
30
|
+
gpt.replace({ animal: 'cat' });
|
|
31
31
|
await gpt.json({ time: '24:00:00', message: 'Hello' }, { time: 'Time in format HH:MM:SS' });
|
|
32
32
|
|
|
33
33
|
console.log("\n" + '--------| sonnet45() |--------');
|
package/demo/prompt.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Writing Prompt:
|
|
1
|
+
# Writing Prompt: <%- storyTitle %>
|
|
2
2
|
|
|
3
3
|
## Setting
|
|
4
4
|
A small, quiet town where nothing unusual ever happens.
|
|
@@ -22,4 +22,4 @@ Write a short story (500-1000 words) that explores:
|
|
|
22
22
|
### Additional Elements to Consider
|
|
23
23
|
- The reactions of other townspeople
|
|
24
24
|
- Any changes in the town's atmosphere after the package's arrival
|
|
25
|
-
- Your character's internal conflict between curiosity and duty
|
|
25
|
+
- Your character's internal conflict between curiosity and duty
|
package/http-client.js
CHANGED
|
@@ -4,6 +4,19 @@ function headersToObject(headers) {
|
|
|
4
4
|
return Object.fromEntries(headers.entries());
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
function sanitizeUrl(url) {
|
|
8
|
+
try {
|
|
9
|
+
const parsed = new URL(url);
|
|
10
|
+
parsed.username = '';
|
|
11
|
+
parsed.password = '';
|
|
12
|
+
parsed.search = '';
|
|
13
|
+
parsed.hash = '';
|
|
14
|
+
return parsed.toString();
|
|
15
|
+
} catch {
|
|
16
|
+
return String(url).replace(/[?#].*$/, '');
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
7
20
|
async function parseResponseBody(response) {
|
|
8
21
|
try {
|
|
9
22
|
return await response.json();
|
|
@@ -24,7 +37,7 @@ async function parseJsonBody(response) {
|
|
|
24
37
|
|
|
25
38
|
async function buildHttpError(url, response) {
|
|
26
39
|
const details = await parseResponseBody(response);
|
|
27
|
-
const error = new Error(`Request to ${url} failed with status code ${response.status}`);
|
|
40
|
+
const error = new Error(`Request to ${sanitizeUrl(url)} failed with status code ${response.status}`);
|
|
28
41
|
error.isHttpError = true;
|
|
29
42
|
error.statusCode = response.status;
|
|
30
43
|
error.details = details;
|
|
@@ -64,7 +77,7 @@ async function fetchStreamResponse(url, { method = 'POST', headers = {}, body }
|
|
|
64
77
|
throw await buildHttpError(url, response);
|
|
65
78
|
}
|
|
66
79
|
if (!response.body) {
|
|
67
|
-
throw new Error(`Request to ${url} did not return a readable stream`);
|
|
80
|
+
throw new Error(`Request to ${sanitizeUrl(url)} did not return a readable stream`);
|
|
68
81
|
}
|
|
69
82
|
return {
|
|
70
83
|
data: Readable.fromWeb(response.body),
|