modelmix 5.0.2 → 5.0.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.
Files changed (39) hide show
  1. package/README.md +121 -7
  2. package/RLM_PLUGIN_SPEC.md +465 -0
  3. package/demo/gemini.js +3 -4
  4. package/demo/moderation.js +17 -0
  5. package/demo/short.js +1 -1
  6. package/effort.js +2 -0
  7. package/index.d.ts +107 -1
  8. package/index.js +443 -46
  9. package/package.json +4 -2
  10. package/plugins/rlm/index.d.ts +194 -0
  11. package/plugins/rlm/index.js +25 -0
  12. package/plugins/rlm/lib/budget.js +153 -0
  13. package/plugins/rlm/lib/isolated-vm-sandbox.js +90 -0
  14. package/plugins/rlm/lib/markdown.js +156 -0
  15. package/plugins/rlm/lib/planner-prompt.js +137 -0
  16. package/plugins/rlm/lib/plugin.js +203 -0
  17. package/plugins/rlm/lib/runtime.js +146 -0
  18. package/plugins/rlm/lib/variable-descriptors.js +228 -0
  19. package/plugins/rlm/lib/worker-catalog.js +70 -0
  20. package/plugins/rlm/package.json +32 -0
  21. package/plugins/rlm/prompts/partials/processing-rules.md +8 -0
  22. package/plugins/rlm/prompts/planner.md +53 -0
  23. package/plugins/rlm/test/budget.test.js +86 -0
  24. package/plugins/rlm/test/fixtures/book.md +24 -0
  25. package/plugins/rlm/test/isolated-vm-sandbox.test.js +114 -0
  26. package/plugins/rlm/test/markdown.test.js +64 -0
  27. package/plugins/rlm/test/planner-template.test.js +140 -0
  28. package/plugins/rlm/test/plugin-contract.test.js +182 -0
  29. package/plugins/rlm/test/rlm-e2e.test.js +338 -0
  30. package/plugins/rlm/test/variable-descriptors.test.js +170 -0
  31. package/plugins/rlm/test/worker-catalog.test.js +104 -0
  32. package/pnpm-workspace.yaml +6 -0
  33. package/skills/modelmix/SKILL.md +26 -4
  34. package/test/effort.test.js +14 -1
  35. package/test/live.mcp.js +6 -6
  36. package/test/live.test.js +2 -2
  37. package/test/moderation.test.js +135 -0
  38. package/test/plugins.test.js +356 -0
  39. package/test/tokens.test.js +37 -5
@@ -0,0 +1,17 @@
1
+ import { ModerationMix } from '../index.js';
2
+ try { process.loadEnvFile(); } catch {}
3
+
4
+ const username = 'player_name';
5
+ const avatarUrl = 'https://example.com/avatar.png';
6
+
7
+ const { moderation: [profileModeration] } = await ModerationMix.new()
8
+ .openai()
9
+ .addText(username)
10
+ .addImageFromUrl(avatarUrl)
11
+ .raw();
12
+
13
+ console.log({
14
+ allowed: !profileModeration.flagged,
15
+ categories: profileModeration.categories,
16
+ scores: profileModeration.category_scores
17
+ });
package/demo/short.js CHANGED
@@ -11,7 +11,7 @@ const setup = {
11
11
  const mmix = await ModelMix.new(setup)
12
12
  .sonnet46() // (main model) Anthropic claude-sonnet-4-6
13
13
  .gpt56luna() // (fallback 1) OpenAI gpt-5.6-luna
14
- .gemini36flash({ config: { temperature: 0 } }) // (fallback 2) Google gemini-3.6-flash
14
+ .gemini37flash() // (fallback 2) Google gemini-3.7-flash
15
15
  .gpt41nano() // (fallback 3) OpenAI gpt-4.1-nano
16
16
  .grok46() // (fallback 4) Grok grok-4.6
17
17
  .addText("What's your name?");
package/effort.js CHANGED
@@ -70,6 +70,7 @@ const MINIMAX_BANDS = [
70
70
 
71
71
  /** Exact model → supported Gemini thinkingLevel values */
72
72
  const GEMINI_MODEL_LEVELS = {
73
+ 'gemini-3.7-flash': ['low', 'medium', 'high'],
73
74
  'gemini-3-pro-preview': ['low', 'high'],
74
75
  'gemini-3.1-pro-preview': ['low', 'medium', 'high'],
75
76
  'gemini-2.5-pro': ['low', 'medium', 'high'],
@@ -290,6 +291,7 @@ function mapAdaptiveEffort(providerFamily, modelKey) {
290
291
  return { thinking: { type: 'adaptive' } };
291
292
  }
292
293
  if (providerFamily === 'google') {
294
+ if (modelKey === 'gemini-3.7-flash') return null;
293
295
  // Gemini dynamic thinking: thinkingBudget -1 (2.5 official; accepted on 3.x as dynamic)
294
296
  return { thinkingConfig: { thinkingBudget: -1 } };
295
297
  }
package/index.d.ts CHANGED
@@ -161,6 +161,8 @@ export interface TokenCostBreakdown {
161
161
  export interface TokenUsage {
162
162
  input: number;
163
163
  output: number;
164
+ /** Internal reasoning tokens billed at the output rate when reported separately. */
165
+ thinking: number;
164
166
  total: number;
165
167
  cached: number;
166
168
  cacheWrite: number;
@@ -187,9 +189,86 @@ export interface ModelMixResult {
187
189
  tokens?: TokenUsage;
188
190
  response?: unknown;
189
191
  assistantMessage?: ChatMessage;
192
+ execution?: PluginExecutionMetadata;
193
+ moderation?: ModerationResult[];
190
194
  [key: string]: unknown;
191
195
  }
192
196
 
197
+ export interface ModerationCategories {
198
+ harassment: boolean;
199
+ 'harassment/threatening': boolean;
200
+ hate: boolean;
201
+ 'hate/threatening': boolean;
202
+ illicit: boolean | null;
203
+ 'illicit/violent': boolean | null;
204
+ 'self-harm': boolean;
205
+ 'self-harm/instructions': boolean;
206
+ 'self-harm/intent': boolean;
207
+ sexual: boolean;
208
+ 'sexual/minors': boolean;
209
+ violence: boolean;
210
+ 'violence/graphic': boolean;
211
+ }
212
+
213
+ export type ModerationCategoryScores = Record<keyof ModerationCategories, number>;
214
+ export type ModerationAppliedInputTypes = Record<keyof ModerationCategories, Array<'text' | 'image'>>;
215
+
216
+ export interface ModerationResult {
217
+ flagged: boolean;
218
+ categories: ModerationCategories;
219
+ category_scores: ModerationCategoryScores;
220
+ category_applied_input_types: ModerationAppliedInputTypes;
221
+ }
222
+
223
+ export type ModelMixOutputMode = 'message' | 'json' | 'block' | 'raw' | 'stream';
224
+
225
+ export interface PluginExecutionMetadata {
226
+ executionId: string;
227
+ parentExecutionId: string | null;
228
+ depth: number;
229
+ }
230
+
231
+ export type PluginInheritancePolicy =
232
+ | 'inherit'
233
+ | 'none'
234
+ | { include: string[] }
235
+ | { exclude: string[] };
236
+
237
+ export interface ChildInvocation {
238
+ system?: string;
239
+ systemFile?: string;
240
+ assign?: Record<string, unknown>;
241
+ messages: ChatMessage[];
242
+ tools?: ToolWithCallback[];
243
+ options?: ModelMixOptions;
244
+ config?: ModelMixConfig;
245
+ mix?: ModelMixMixFlags;
246
+ model?: ModelMix;
247
+ plugins?: PluginInheritancePolicy;
248
+ history?: false;
249
+ outputMode?: ModelMixOutputMode;
250
+ }
251
+
252
+ export interface PluginExecutionContext {
253
+ request: {
254
+ system: string;
255
+ messages: ChatMessage[];
256
+ options: ModelMixOptions;
257
+ config: ModelMixConfig;
258
+ outputMode: ModelMixOutputMode;
259
+ };
260
+ execution: Readonly<PluginExecutionMetadata>;
261
+ invoke(input: ChildInvocation): Promise<ModelMixResult>;
262
+ }
263
+
264
+ export interface ModelMixPlugin {
265
+ name: string;
266
+ execute(
267
+ context: PluginExecutionContext,
268
+ next: () => Promise<ModelMixResult>
269
+ ): Promise<ModelMixResult>;
270
+ }
271
+
193
272
  export interface StreamChunk {
194
273
  response: unknown;
195
274
  message: string;
@@ -263,6 +342,7 @@ export interface ProviderConstructorArgs {
263
342
  export interface CreateArgs {
264
343
  config?: ModelMixConfig;
265
344
  options?: ModelMixOptions;
345
+ outputMode?: ModelMixOutputMode;
266
346
  }
267
347
 
268
348
  export type ProviderFamily =
@@ -279,6 +359,7 @@ export declare class ModelMix {
279
359
  tools: Record<string, ToolDefinition[]>;
280
360
  toolClient: Record<string, unknown>;
281
361
  mcp: Record<string, unknown>;
362
+ plugins: ModelMixPlugin[];
282
363
  options: ModelMixOptions;
283
364
  config: ModelMixConfig;
284
365
  mix: ModelMixMixFlags;
@@ -288,6 +369,7 @@ export declare class ModelMix {
288
369
  constructor(setup?: ModelMixSetup);
289
370
 
290
371
  static new(setup?: ModelMixSetup): ModelMix;
372
+ use(plugin: ModelMixPlugin): this;
291
373
  static formatJSON(obj: unknown): string;
292
374
  static formatMessage(message: unknown): unknown;
293
375
  static truncate(str: string, maxLen?: number): string;
@@ -296,6 +378,7 @@ export declare class ModelMix {
296
378
  tokens: {
297
379
  input: number;
298
380
  output: number;
381
+ thinking?: number;
299
382
  total?: number;
300
383
  cached?: number;
301
384
  cacheWrite?: number;
@@ -308,6 +391,7 @@ export declare class ModelMix {
308
391
  tokens: {
309
392
  input: number;
310
393
  output: number;
394
+ thinking?: number;
311
395
  total?: number;
312
396
  cached?: number;
313
397
  cacheWrite?: number;
@@ -320,6 +404,7 @@ export declare class ModelMix {
320
404
  tokens: {
321
405
  input: number;
322
406
  output: number;
407
+ thinking?: number;
323
408
  total?: number;
324
409
  cached?: number;
325
410
  cacheWrite?: number;
@@ -330,6 +415,7 @@ export declare class ModelMix {
330
415
  static normalizeTokenUsage(tokens?: {
331
416
  input?: number;
332
417
  output?: number;
418
+ thinking?: number;
333
419
  total?: number;
334
420
  cached?: number;
335
421
  cacheWrite?: number;
@@ -395,6 +481,7 @@ export declare class ModelMix {
395
481
  gemini31pro(args?: ModelAttachArgs): this;
396
482
  gemini3pro(args?: ModelAttachArgs): this;
397
483
  gemini3flash(args?: ModelAttachArgs): this;
484
+ gemini37flash(args?: ModelAttachArgs): this;
398
485
  gemini36flash(args?: ModelAttachArgs): this;
399
486
  gemini35flash(args?: ModelAttachArgs): this;
400
487
  gemini35flashLite(args?: ModelAttachArgs): this;
@@ -455,7 +542,7 @@ export declare class ModelMix {
455
542
 
456
543
  assignKeyFromFile(key: string, filePath: string): this;
457
544
  groupByRoles(messages: ChatMessage[]): ChatMessage[];
458
- prepareMessages(): Promise<void>;
545
+ prepareMessages(): Promise<ChatMessage[]>;
459
546
  readFile(filePath: string, options?: { encoding?: BufferEncoding | null }): string | Buffer;
460
547
  execute(args?: CreateArgs): Promise<ModelMixResult>;
461
548
  processToolCalls(toolCalls: ToolCall[]): Promise<
@@ -511,7 +598,14 @@ export declare class MixCustom {
511
598
  }
512
599
 
513
600
  export declare class MixOpenAI extends MixCustom {}
601
+ export declare class MixModeration extends MixCustom {}
514
602
  export declare class MixOpenAIResponses extends MixOpenAI {}
603
+ export declare class MixOpenAIModeration extends MixModeration {
604
+ static messagesToModerationInput(messages?: ChatMessage[]): Array<
605
+ | { type: 'text'; text: string }
606
+ | { type: 'image_url'; image_url: { url: string } }
607
+ >;
608
+ }
515
609
  export declare class MixOpenAIWebSocket extends MixOpenAIResponses {}
516
610
  export declare class MixOpenRouter extends MixOpenAI {}
517
611
  export declare class MixKimi extends MixOpenAI {}
@@ -530,6 +624,18 @@ export declare class MixFireworks extends MixCustom {}
530
624
  export declare class MixNVIDIA extends MixCustom {}
531
625
  export declare class MixGoogle extends MixCustom {}
532
626
 
627
+ export declare class ModerationMix extends ModelMix {
628
+ constructor(setup?: Omit<ModelMixSetup, 'mix'>);
629
+ static new(setup?: Omit<ModelMixSetup, 'mix'>): ModerationMix;
630
+ new(setup?: Omit<ModelMixSetup, 'mix'>): ModerationMix;
631
+ attach(key: string, provider: MixModeration): this;
632
+ openai(args?: ModelAttachArgs): this;
633
+ message(): Promise<never>;
634
+ json(): Promise<never>;
635
+ block(): Promise<never>;
636
+ stream(): Promise<never>;
637
+ }
638
+
533
639
  /** Normalize unified effort to integer -1 or 0..100. */
534
640
  export function normalizeEffort(value: unknown): EffortValue;
535
641