modelmix 5.1.12 → 5.1.16
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 +30 -9
- package/demo/gemini.js +3 -3
- package/demo/short.js +1 -1
- package/effort.js +2 -1
- package/http-client.js +6 -6
- package/index.d.ts +11 -6
- package/index.js +136 -73
- package/lib/abort-signal.js +57 -0
- package/lib/model-chain.js +1 -1
- package/lib/providers/anthropic.js +2 -2
- package/lib/providers/base.js +17 -6
- package/lib/providers/google.js +9 -2
- package/lib/providers/openai-compatible.js +8 -8
- package/lib/providers/openai.js +32 -10
- package/lib/token-usage.js +1 -0
- package/mcp-tools.js +5 -2
- package/package.json +2 -2
- package/plugins/rlm/index.d.ts +1 -0
- package/plugins/rlm/lib/isolated-vm-sandbox.js +8 -1
- package/plugins/rlm/lib/plugin.js +23 -5
- package/plugins/rlm/test/isolated-vm-sandbox.test.js +23 -0
- package/skills/modelmix/SKILL.md +30 -12
- package/test/abort.test.js +517 -0
- package/test/effort.test.js +13 -0
- package/test/fallback.test.js +2 -2
- package/test/live.mcp.js +6 -6
- package/test/live.test.js +11 -6
- package/test/tokens.test.js +9 -6
package/README.md
CHANGED
|
@@ -88,13 +88,33 @@ const setup = {
|
|
|
88
88
|
const model = await ModelMix.new(setup)
|
|
89
89
|
.sonnet5() // (main model) Anthropic claude-sonnet-5
|
|
90
90
|
.gpt56luna() // (fallback 2) OpenAI gpt-5.6-luna
|
|
91
|
-
.
|
|
91
|
+
.gemini38flash() // (fallback 3) Google gemini-3.8-flash
|
|
92
92
|
.grok46() // (fallback 4) Grok grok-4.6
|
|
93
93
|
.addText("What's your name?");
|
|
94
94
|
|
|
95
95
|
console.log(await model.message());
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
+
**Cancel one execution with AbortSignal**
|
|
99
|
+
|
|
100
|
+
Pass the signal directly to the terminal method. ModelMix propagates it through
|
|
101
|
+
queued work, retries, fallbacks, provider transports, plugins, child executions,
|
|
102
|
+
and tool calls:
|
|
103
|
+
|
|
104
|
+
```javascript
|
|
105
|
+
const controller = new AbortController();
|
|
106
|
+
const response = model.message(controller.signal);
|
|
107
|
+
|
|
108
|
+
controller.abort(new Error('Request cancelled'));
|
|
109
|
+
await response;
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Use `raw(signal)`, `stream(callback, signal)`, `block(options, signal)`, or
|
|
113
|
+
`json(example, description, jsonOptions, signal)` for the other output modes.
|
|
114
|
+
`execute()` accepts it as the top-level `signal` field. Never put a signal in
|
|
115
|
+
ModelMix or provider `config`/`options`; those locations are rejected because
|
|
116
|
+
they are reusable and may become provider payload fields.
|
|
117
|
+
|
|
98
118
|
The same ordered chain can be attached by passing model shortcuts directly to
|
|
99
119
|
`chain()`. Add `@effort` to override unified effort for one model; entries
|
|
100
120
|
without it inherit the chain effort, or use the provider default when the chain
|
|
@@ -102,7 +122,7 @@ has no configured effort:
|
|
|
102
122
|
|
|
103
123
|
```javascript
|
|
104
124
|
const model = ModelMix.new(setup)
|
|
105
|
-
.chain('sonnet5', 'gpt56luna@20', '
|
|
125
|
+
.chain('sonnet5', 'gpt56luna@20', 'gemini38flash@-1')
|
|
106
126
|
.addText("What's your name?");
|
|
107
127
|
|
|
108
128
|
console.log(await model.message());
|
|
@@ -165,6 +185,7 @@ ModelMix provides convenient shorthand methods for quickly accessing different A
|
|
|
165
185
|
| `sonnet46()` | Anthropic | claude-sonnet-4-6 | [\$3.00][2] | [\$15.00][2] |
|
|
166
186
|
| `haiku45()` | Anthropic | claude-haiku-4-5-20251001 | [\$1.00][2] | [\$5.00][2] |
|
|
167
187
|
| `gemini31pro()` | Google | gemini-3.1-pro-preview | [\$2.00][3] | [\$12.00][3] |
|
|
188
|
+
| `gemini38flash()` | Google | gemini-3.8-flash | [\$0.75][3] | [\$3.75][3] |
|
|
168
189
|
| `gemini37flash()` | Google | gemini-3.7-flash | [\$0.75][3] | [\$3.75][3] |
|
|
169
190
|
| `gemini36flash()` | Google | gemini-3.6-flash | [\$0.75][3] | [\$3.75][3] |
|
|
170
191
|
| `gemini35flash()` | Google | gemini-3.5-flash | [\$0.75][3] | [\$4.50][3] |
|
|
@@ -200,7 +221,7 @@ ModelMix provides convenient shorthand methods for quickly accessing different A
|
|
|
200
221
|
| `kimiK25()` | Together | Kimi-K2.5 | [\$0.50][7] | [\$2.80][7] |
|
|
201
222
|
| `kimiK26()` | Fireworks | models/kimi-k2p6 | [\$0.95][10] | [\$4.00][10] |
|
|
202
223
|
|
|
203
|
-
Gemini 3.7 Flash and 3.6 Flash use Google's introductory standard pricing through December 31, 2026; standard rates double on January 1, 2027.
|
|
224
|
+
Gemini 3.8 Flash, 3.7 Flash, and 3.6 Flash use Google's introductory standard pricing through December 31, 2026; standard rates double on January 1, 2027.
|
|
204
225
|
|
|
205
226
|
`museGlimmer30b()` uses Fireworks by default. OpenRouter, NVIDIA NIM, and Together are available through `mix.openrouter`, `mix.nvidia`, and `mix.together`.
|
|
206
227
|
|
|
@@ -271,7 +292,7 @@ ModelMix.new().effort(-1).minimaxM3().addText('...').message();
|
|
|
271
292
|
|
|
272
293
|
### Provider-specific behavior
|
|
273
294
|
|
|
274
|
-
- **Gemini:** Gemini 3+ uses bands 0–24 / 25–49 / 50–74 / 75–100. Gemini 3.7 Flash
|
|
295
|
+
- **Gemini:** Gemini 3+ uses bands 0–24 / 25–49 / 50–74 / 75–100. Gemini 3.8 Flash and 3.7 Flash clamp these bands to `low` / `low` / `medium` / `high`; `-1` leaves their native `medium` default unchanged. Gemini 2.5 maps 0–100 to `thinkingBudget`.
|
|
275
296
|
- **GPT-5.6:** `100` maps to `max`; 80–99 remains `xhigh`.
|
|
276
297
|
- **Qwen 3.8 27B and Flash:** 0–39 / 40–79 / 80–100 map to `low` / `medium` / `xhigh`; `-1` leaves the native `xhigh` default unchanged. Qwen 3.8 Flash is the managed production version based on the open-weight Flash-Next architecture.
|
|
277
298
|
- **GLM 5.3 and GLM 5.3 Flash:** reasoning is mandatory; 0–39 / 40–79 / 80–100 map to `low` / `high` / `max`; `-1` leaves the native `max` default unchanged.
|
|
@@ -939,7 +960,7 @@ const model = ModelMix.new()
|
|
|
939
960
|
.addText('Summarize this request.');
|
|
940
961
|
```
|
|
941
962
|
|
|
942
|
-
A plugin may edit `context.request`, call `next()`, or return a complete ModelMix result itself.
|
|
963
|
+
A plugin may edit `context.request`, call `next()`, or return a complete ModelMix result itself. The current execution signal is available as `context.signal`, and child executions created with `context.invoke()` inherit it automatically. Local tool callbacks receive the same signal as their second argument. A plugin can also choose plugin inheritance:
|
|
943
964
|
|
|
944
965
|
```javascript
|
|
945
966
|
const child = await context.invoke({
|
|
@@ -1050,8 +1071,8 @@ new ModelMix(args = { options: {}, config: {} })
|
|
|
1050
1071
|
- `assign(keyValues)`: Assigns EJS data for messages and system prompts.
|
|
1051
1072
|
- `assignKey(key, value)`: Assigns one EJS data value.
|
|
1052
1073
|
- `assignKeyFromFile(key, filePath)`: Renders an EJS file through `include` and assigns its output to one key.
|
|
1053
|
-
- `message()`: Sends the message and returns the response.
|
|
1054
|
-
- `raw()`: Sends the message and returns the complete response data including:
|
|
1074
|
+
- `message(signal?)`: Sends the message and returns the response.
|
|
1075
|
+
- `raw(signal?)`: Sends the message and returns the complete response data including:
|
|
1055
1076
|
- `message`: The text response from the model
|
|
1056
1077
|
- `think`: Reasoning/thinking content (if available)
|
|
1057
1078
|
- `toolCalls`: Array of tool calls made by the model (if any)
|
|
@@ -1069,8 +1090,8 @@ new ModelMix(args = { options: {}, config: {} })
|
|
|
1069
1090
|
|
|
1070
1091
|
if (profile.flagged) throw new Error('Profile rejected by moderation');
|
|
1071
1092
|
```
|
|
1072
|
-
- `stream(callback)`: Sends the message and streams the response, invoking the callback with each streamed part.
|
|
1073
|
-
- `json(schemaExample, descriptions = {}, options = {})`: Forces the model to return a response in a specific JSON format.
|
|
1093
|
+
- `stream(callback, signal?)`: Sends the message and streams the response, invoking the callback with each streamed part.
|
|
1094
|
+
- `json(schemaExample, descriptions = {}, options = {}, signal?)`: Forces the model to return a response in a specific JSON format.
|
|
1074
1095
|
- `schemaExample`: Example of the JSON structure to be returned. Top-level arrays are auto-wrapped for better LLM compatibility.
|
|
1075
1096
|
- `descriptions`: Descriptions for each field — can be strings or descriptor objects with `{ description, required, enum, default }`.
|
|
1076
1097
|
- `options`: `{ addSchema: true, addExample: false, addNote: false }`
|
package/demo/gemini.js
CHANGED
|
@@ -12,9 +12,9 @@ const mmix = new ModelMix({
|
|
|
12
12
|
}
|
|
13
13
|
});
|
|
14
14
|
|
|
15
|
-
// Using Gemini 3.
|
|
16
|
-
console.log("\n" + '--------|
|
|
17
|
-
const flash = await mmix.
|
|
15
|
+
// Using Gemini 3.8 Flash with the built-in method
|
|
16
|
+
console.log("\n" + '--------| gemini38flash() |--------');
|
|
17
|
+
const flash = await mmix.gemini38flash()
|
|
18
18
|
.addText('Hi there! Do you like cats?')
|
|
19
19
|
.message();
|
|
20
20
|
|
package/demo/short.js
CHANGED
|
@@ -12,7 +12,7 @@ const mmix = await ModelMix.new(setup)
|
|
|
12
12
|
.fable51({ mix: { openrouter: true } }) // (main + provider fallback) Anthropic/OpenRouter Claude Fable 5.1
|
|
13
13
|
.sonnet46() // (fallback 2) Anthropic claude-sonnet-4-6
|
|
14
14
|
.gpt56luna({ mix: { openrouter: true } }) // (fallback 3 + provider fallback) OpenAI/OpenRouter gpt-5.6-luna
|
|
15
|
-
.
|
|
15
|
+
.gemini38flash() // (fallback 4) Google gemini-3.8-flash
|
|
16
16
|
.gpt5nano({ mix: { openrouter: true } }) // (fallback 5 + provider fallback) OpenAI/OpenRouter gpt-5-nano
|
|
17
17
|
.grok46() // (fallback 6) Grok grok-4.6
|
|
18
18
|
.qwen35397b() // (fallback 7) OpenRouter qwen/qwen3.5-397b-a17b
|
package/effort.js
CHANGED
|
@@ -96,6 +96,7 @@ const MINIMAX_BANDS = [
|
|
|
96
96
|
|
|
97
97
|
/** Exact model → supported Gemini thinkingLevel values */
|
|
98
98
|
const GEMINI_MODEL_LEVELS = {
|
|
99
|
+
'gemini-3.8-flash': ['low', 'medium', 'high'],
|
|
99
100
|
'gemini-3.7-flash': ['low', 'medium', 'high'],
|
|
100
101
|
'gemini-3-pro-preview': ['low', 'high'],
|
|
101
102
|
'gemini-3.1-pro-preview': ['low', 'medium', 'high'],
|
|
@@ -320,7 +321,7 @@ function mapAdaptiveEffort(providerFamily, modelKey) {
|
|
|
320
321
|
return { thinking: { type: 'adaptive' } };
|
|
321
322
|
}
|
|
322
323
|
if (providerFamily === 'google') {
|
|
323
|
-
if (modelKey === 'gemini-3.7-flash') return null;
|
|
324
|
+
if (modelKey === 'gemini-3.8-flash' || modelKey === 'gemini-3.7-flash') return null;
|
|
324
325
|
// Gemini dynamic thinking: thinkingBudget -1 (2.5 official; accepted on 3.x as dynamic)
|
|
325
326
|
return { thinkingConfig: { thinkingBudget: -1 } };
|
|
326
327
|
}
|
package/http-client.js
CHANGED
|
@@ -45,8 +45,8 @@ async function buildHttpError(url, response) {
|
|
|
45
45
|
return error;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
async function fetchJsonResponse(url, { method = 'POST', headers = {}, body } = {}) {
|
|
49
|
-
const response = await fetch(url, { method, headers, body });
|
|
48
|
+
async function fetchJsonResponse(url, { method = 'POST', headers = {}, body, signal } = {}) {
|
|
49
|
+
const response = await fetch(url, { method, headers, body, signal });
|
|
50
50
|
if (!response.ok) {
|
|
51
51
|
throw await buildHttpError(url, response);
|
|
52
52
|
}
|
|
@@ -58,8 +58,8 @@ async function fetchJsonResponse(url, { method = 'POST', headers = {}, body } =
|
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
async function fetchBinaryResponse(url, { method = 'GET', headers = {}, body } = {}) {
|
|
62
|
-
const response = await fetch(url, { method, headers, body });
|
|
61
|
+
async function fetchBinaryResponse(url, { method = 'GET', headers = {}, body, signal } = {}) {
|
|
62
|
+
const response = await fetch(url, { method, headers, body, signal });
|
|
63
63
|
if (!response.ok) {
|
|
64
64
|
throw await buildHttpError(url, response);
|
|
65
65
|
}
|
|
@@ -71,8 +71,8 @@ async function fetchBinaryResponse(url, { method = 'GET', headers = {}, body } =
|
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
async function fetchStreamResponse(url, { method = 'POST', headers = {}, body } = {}) {
|
|
75
|
-
const response = await fetch(url, { method, headers, body });
|
|
74
|
+
async function fetchStreamResponse(url, { method = 'POST', headers = {}, body, signal } = {}) {
|
|
75
|
+
const response = await fetch(url, { method, headers, body, signal });
|
|
76
76
|
if (!response.ok) {
|
|
77
77
|
throw await buildHttpError(url, response);
|
|
78
78
|
}
|
package/index.d.ts
CHANGED
|
@@ -259,6 +259,7 @@ export interface PluginExecutionContext {
|
|
|
259
259
|
outputMode: ModelMixOutputMode;
|
|
260
260
|
};
|
|
261
261
|
execution: Readonly<PluginExecutionMetadata>;
|
|
262
|
+
signal?: AbortSignal;
|
|
262
263
|
invoke(input: ChildInvocation): Promise<ModelMixResult>;
|
|
263
264
|
}
|
|
264
265
|
|
|
@@ -312,7 +313,8 @@ export interface ToolDefinition {
|
|
|
312
313
|
}
|
|
313
314
|
|
|
314
315
|
export type ToolCallback = (
|
|
315
|
-
args: Record<string, unknown
|
|
316
|
+
args: Record<string, unknown>,
|
|
317
|
+
signal?: AbortSignal
|
|
316
318
|
) => unknown | Promise<unknown>;
|
|
317
319
|
|
|
318
320
|
export interface ToolWithCallback {
|
|
@@ -343,6 +345,7 @@ export interface ProviderConstructorArgs {
|
|
|
343
345
|
export interface CreateArgs {
|
|
344
346
|
config?: ModelMixConfig;
|
|
345
347
|
options?: ModelMixOptions;
|
|
348
|
+
signal?: AbortSignal;
|
|
346
349
|
outputMode?: ModelMixOutputMode;
|
|
347
350
|
}
|
|
348
351
|
|
|
@@ -479,6 +482,7 @@ export declare class ModelMix {
|
|
|
479
482
|
|
|
480
483
|
// Google
|
|
481
484
|
gemini31pro(args?: ModelAttachArgs): this;
|
|
485
|
+
gemini38flash(args?: ModelAttachArgs): this;
|
|
482
486
|
gemini37flash(args?: ModelAttachArgs): this;
|
|
483
487
|
gemini36flash(args?: ModelAttachArgs): this;
|
|
484
488
|
gemini35flash(args?: ModelAttachArgs): this;
|
|
@@ -533,15 +537,16 @@ export declare class ModelMix {
|
|
|
533
537
|
addImageFromUrl(url: string, options?: RoleOptions): Promise<this>;
|
|
534
538
|
processImages(): Promise<void>;
|
|
535
539
|
|
|
536
|
-
message(): Promise<string>;
|
|
540
|
+
message(signal?: AbortSignal): Promise<string>;
|
|
537
541
|
json<T = unknown>(
|
|
538
542
|
schemaExample?: T | T[] | null,
|
|
539
543
|
schemaDescription?: SchemaDescription,
|
|
540
|
-
options?: JsonMethodOptions
|
|
544
|
+
options?: JsonMethodOptions,
|
|
545
|
+
signal?: AbortSignal
|
|
541
546
|
): Promise<T>;
|
|
542
|
-
block(options?: BlockOptions): Promise<string>;
|
|
543
|
-
raw(): Promise<ModelMixResult>;
|
|
544
|
-
stream(callback: StreamCallback): Promise<ModelMixResult>;
|
|
547
|
+
block(options?: BlockOptions, signal?: AbortSignal): Promise<string>;
|
|
548
|
+
raw(signal?: AbortSignal): Promise<ModelMixResult>;
|
|
549
|
+
stream(callback: StreamCallback, signal?: AbortSignal): Promise<ModelMixResult>;
|
|
545
550
|
|
|
546
551
|
assignKeyFromFile(key: string, filePath: string): this;
|
|
547
552
|
groupByRoles(messages: ChatMessage[]): ChatMessage[];
|