modelmix 5.1.10 → 5.1.15

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 (45) hide show
  1. package/README.md +42 -10
  2. package/demo/free.js +1 -3
  3. package/demo/short.js +15 -13
  4. package/effort.js +13 -7
  5. package/http-client.js +6 -6
  6. package/index.d.ts +13 -6
  7. package/index.js +204 -144
  8. package/lib/abort-signal.js +57 -0
  9. package/lib/model-chain.js +2 -2
  10. package/lib/provider-api-key.js +9 -0
  11. package/lib/providers/anthropic.js +24 -22
  12. package/lib/providers/base.js +21 -28
  13. package/lib/providers/google.js +12 -8
  14. package/lib/providers/openai-compatible.js +19 -48
  15. package/lib/providers/openai-options.js +16 -0
  16. package/lib/providers/openai.js +48 -47
  17. package/lib/providers.js +5 -2
  18. package/lib/token-usage.js +19 -4
  19. package/mcp-tools.js +5 -2
  20. package/package.json +3 -2
  21. package/plugins/rlm/index.d.ts +1 -0
  22. package/plugins/rlm/lib/budget.js +2 -7
  23. package/plugins/rlm/lib/isolated-vm-sandbox.js +8 -1
  24. package/plugins/rlm/lib/planner-prompt.js +1 -7
  25. package/plugins/rlm/lib/plugin.js +24 -11
  26. package/plugins/rlm/lib/validation.js +14 -0
  27. package/plugins/rlm/lib/variable-descriptors.js +1 -5
  28. package/plugins/rlm/lib/worker-catalog.js +1 -5
  29. package/plugins/rlm/test/isolated-vm-sandbox.test.js +23 -0
  30. package/pnpm-workspace.yaml +10 -5
  31. package/schema.js +0 -28
  32. package/skills/modelmix/SKILL.md +34 -12
  33. package/test/abort.test.js +517 -0
  34. package/test/anthropic.test.js +77 -1
  35. package/test/bottleneck.test.js +2 -2
  36. package/test/effort.test.js +24 -1
  37. package/test/fallback.test.js +101 -1
  38. package/test/glm.test.js +2 -6
  39. package/test/kimi.test.js +2 -6
  40. package/test/live.test.js +10 -5
  41. package/test/muse.test.js +24 -7
  42. package/test/provider-expansion.test.js +17 -0
  43. package/test/public-api.test.js +2 -2
  44. package/test/qwen.test.js +2 -3
  45. package/test/tokens.test.js +23 -8
@@ -0,0 +1,57 @@
1
+ function assertAbortSignal(signal) {
2
+ if (signal === undefined) return;
3
+ if (!(signal instanceof AbortSignal)) {
4
+ throw new TypeError('signal must be an AbortSignal.');
5
+ }
6
+ signal.throwIfAborted();
7
+ }
8
+
9
+ function assertNoStoredSignal(value, label) {
10
+ if (value && Object.prototype.hasOwnProperty.call(value, 'signal')) {
11
+ throw new TypeError(`${label}.signal is not supported; pass the AbortSignal to the execution method.`);
12
+ }
13
+ }
14
+
15
+ function throwIfAborted(signal) {
16
+ if (signal) signal.throwIfAborted();
17
+ }
18
+
19
+ function validateProviderExecution(provider, { config, options, signal }) {
20
+ assertAbortSignal(signal);
21
+ assertNoStoredSignal(provider.config, 'provider.config');
22
+ assertNoStoredSignal(provider.options, 'provider.options');
23
+ assertNoStoredSignal(config, 'config');
24
+ assertNoStoredSignal(options, 'options');
25
+ }
26
+
27
+ function raceWithSignal(promise, signal) {
28
+ if (!signal) return promise;
29
+ throwIfAborted(signal);
30
+ let onAbort;
31
+ const aborted = new Promise((_, reject) => {
32
+ onAbort = () => reject(signal.reason);
33
+ signal.addEventListener('abort', onAbort, { once: true });
34
+ });
35
+ return Promise.race([promise, aborted]).finally(() => {
36
+ signal.removeEventListener('abort', onAbort);
37
+ });
38
+ }
39
+
40
+ function sleepWithSignal(ms, signal) {
41
+ if (!signal) return new Promise(resolve => setTimeout(resolve, ms));
42
+ throwIfAborted(signal);
43
+ let timeout;
44
+ const sleep = new Promise(resolve => {
45
+ timeout = setTimeout(resolve, ms);
46
+ });
47
+ return raceWithSignal(sleep, signal).finally(() => clearTimeout(timeout));
48
+ }
49
+
50
+ module.exports = {
51
+ assertAbortSignal,
52
+ assertNoStoredSignal,
53
+ raceWithSignal,
54
+ sleepWithSignal,
55
+ throwIfAborted,
56
+ validateProviderExecution
57
+ };
@@ -5,12 +5,12 @@ const CHAIN_MODEL_SHORTCUTS = new Set([
5
5
  'gpt51', 'gpt52', 'gpt54', 'gpt54mini', 'gpt54nano', 'gpt54pro',
6
6
  'gpt55', 'gpt55pro', 'gpt56sol', 'gpt56terra', 'gpt56luna',
7
7
  'gptRealtime', 'gptRealtimeMini', 'gpt53codex', 'gpt53chat', 'gptOss',
8
- 'fable50', 'fable5', 'opus50', 'opus5', 'opus48', 'opus47', 'opus46',
8
+ 'fable51', 'fable50', 'fable5', 'opus50', 'opus5', 'opus48', 'opus47', 'opus46',
9
9
  'sonnet50', 'sonnet5', 'sonnet46', 'sonnet45', 'haiku45',
10
10
  'gemini31pro', 'gemini37flash', 'gemini36flash', 'gemini35flash',
11
11
  'gemini35flashLite', 'gemini31flashLite', 'sonarPro', 'sonar',
12
12
  'grok46', 'grok45', 'grok43', 'grok420multiAgent', 'grok420',
13
- 'museGlimmer30b',
13
+ 'museGlimmer30b', 'museSpark12Contributor',
14
14
  'qwen35397b', 'qwen36plus', 'qwen37plus', 'qwen38max', 'qwen3827b', 'qwen38flash',
15
15
  'hermes470b', 'hermes4405b', 'hermes3',
16
16
  'kimiK26', 'kimiK27Code', 'kimiK3', 'kimiK25',
@@ -0,0 +1,9 @@
1
+ function requireProviderApiKey(customConfig, environmentVariable, providerName) {
2
+ const apiKey = customConfig.apiKey || process.env[environmentVariable];
3
+ if (!apiKey) {
4
+ throw new Error(`${providerName} API key not found. Please provide it in config or set ${environmentVariable} environment variable.`);
5
+ }
6
+ return apiKey;
7
+ }
8
+
9
+ module.exports = { requireProviderApiKey };
@@ -4,6 +4,25 @@ const {
4
4
  stripContentCacheMetadata,
5
5
  hasNeutralCacheBreakpoint
6
6
  } = require('../content-cache');
7
+ const { requireProviderApiKey } = require('../provider-api-key');
8
+
9
+ function rejectsAnthropicSamplingParams(model = '') {
10
+ const id = String(model).toLowerCase();
11
+ if (!id.includes('claude')) return false;
12
+ if (id.includes('mythos') || id.includes('fable')) return true;
13
+
14
+ const opus = id.match(/claude-opus-(\d+)(?:-(\d+))?/);
15
+ if (opus) {
16
+ const major = Number(opus[1]);
17
+ const minor = opus[2] !== undefined ? Number(opus[2]) : 0;
18
+ return major > 4 || (major === 4 && minor >= 7);
19
+ }
20
+
21
+ const sonnet = id.match(/claude-sonnet-(\d+)/);
22
+ if (sonnet) return Number(sonnet[1]) >= 5;
23
+
24
+ return false;
25
+ }
7
26
 
8
27
  function createAnthropicProviders({ ModelMix, MixCustom, log }) {
9
28
  class MixAnthropic extends MixCustom {
@@ -30,28 +49,11 @@ function createAnthropicProviders({ ModelMix, MixCustom, log }) {
30
49
  * See: https://platform.claude.com/docs/en/about-claude/models/migration-guide
31
50
  */
32
51
  static rejectsSamplingParams(model = '') {
33
- const id = String(model).toLowerCase();
34
- if (!id.includes('claude')) return false;
35
- if (id.includes('mythos') || id.includes('fable')) return true;
36
-
37
- const opus = id.match(/claude-opus-(\d+)(?:-(\d+))?/);
38
- if (opus) {
39
- const major = Number(opus[1]);
40
- const minor = opus[2] !== undefined ? Number(opus[2]) : 0;
41
- return major > 4 || (major === 4 && minor >= 7);
42
- }
43
-
44
- const sonnet = id.match(/claude-sonnet-(\d+)/);
45
- if (sonnet) return Number(sonnet[1]) >= 5;
46
-
47
- return false;
52
+ return rejectsAnthropicSamplingParams(model);
48
53
  }
49
54
 
50
55
  getDefaultConfig(customConfig) {
51
- const apiKey = customConfig.apiKey || process.env.ANTHROPIC_API_KEY;
52
- if (!apiKey) {
53
- throw new Error('Anthropic API key not found. Please provide it in config or set ANTHROPIC_API_KEY environment variable.');
54
- }
56
+ const apiKey = requireProviderApiKey(customConfig, 'ANTHROPIC_API_KEY', 'Anthropic');
55
57
 
56
58
  return super.getDefaultConfig({
57
59
  url: 'https://api.anthropic.com/v1/messages',
@@ -60,7 +62,7 @@ function createAnthropicProviders({ ModelMix, MixCustom, log }) {
60
62
  });
61
63
  }
62
64
 
63
- async create({ config = {}, options = {} } = {}) {
65
+ async create({ config = {}, options = {}, signal } = {}) {
64
66
 
65
67
  delete options.response_format;
66
68
 
@@ -83,7 +85,7 @@ function createAnthropicProviders({ ModelMix, MixCustom, log }) {
83
85
  options.system = config.system;
84
86
 
85
87
  try {
86
- return await super.create({ config: requestConfig, options });
88
+ return await super.create({ config: requestConfig, options, signal });
87
89
  } catch (error) {
88
90
  // Log the error details for debugging
89
91
  if (error.response && error.response.data) {
@@ -334,4 +336,4 @@ function createAnthropicProviders({ ModelMix, MixCustom, log }) {
334
336
  return { MixAnthropic };
335
337
  }
336
338
 
337
- module.exports = createAnthropicProviders;
339
+ module.exports = { createAnthropicProviders, rejectsAnthropicSamplingParams };
@@ -11,10 +11,19 @@ const {
11
11
  stripContentCacheMetadata
12
12
  } = require('../content-cache');
13
13
  const { configForDebug, redactSecret } = require('../provider-debug');
14
+ const { requireProviderApiKey } = require('../provider-api-key');
15
+ const { normalizeOpenAIOptions } = require('./openai-options');
16
+ const {
17
+ assertNoStoredSignal,
18
+ throwIfAborted,
19
+ validateProviderExecution
20
+ } = require('../abort-signal');
14
21
 
15
22
  function createBaseProviders({ ModelMix }) {
16
23
  class MixCustom {
17
24
  constructor({ config = {}, options = {}, headers = {} } = {}) {
25
+ assertNoStoredSignal(config, 'config');
26
+ assertNoStoredSignal(options, 'options');
18
27
  this.config = this.getDefaultConfig(config);
19
28
  this.options = this.getDefaultOptions(options);
20
29
  this.headers = this.getDefaultHeaders(headers);
@@ -67,7 +76,8 @@ function createBaseProviders({ ModelMix }) {
67
76
  return buildRequestBodyAndHeaders(options, headers);
68
77
  }
69
78
 
70
- async create({ config = {}, options = {} } = {}) {
79
+ async create({ config = {}, options = {}, signal } = {}) {
80
+ validateProviderExecution(this, { config, options, signal });
71
81
  try {
72
82
  this.sanitizeCacheOptions(options);
73
83
  if (Array.isArray(options.messages)) {
@@ -88,19 +98,22 @@ function createBaseProviders({ ModelMix }) {
88
98
  }
89
99
 
90
100
  if (options.stream) {
91
- return this.processStream(await fetchStreamResponse(this.config.url, {
101
+ return await this.processStream(await fetchStreamResponse(this.config.url, {
92
102
  method: 'POST',
93
103
  headers: request.headers,
94
- body: request.body
104
+ body: request.body,
105
+ signal
95
106
  }));
96
107
  } else {
97
108
  return this.processResponse(await fetchJsonResponse(this.config.url, {
98
109
  method: 'POST',
99
110
  headers: request.headers,
100
- body: request.body
111
+ body: request.body,
112
+ signal
101
113
  }));
102
114
  }
103
115
  } catch (error) {
116
+ throwIfAborted(signal);
104
117
  throw this.handleError(error);
105
118
  }
106
119
  }
@@ -274,10 +287,7 @@ function createBaseProviders({ ModelMix }) {
274
287
  }
275
288
 
276
289
  getDefaultConfig(customConfig) {
277
- const apiKey = customConfig.apiKey || process.env.OPENAI_API_KEY;
278
- if (!apiKey) {
279
- throw new Error('OpenAI API key not found. Please provide it in config or set OPENAI_API_KEY environment variable.');
280
- }
290
+ const apiKey = requireProviderApiKey(customConfig, 'OPENAI_API_KEY', 'OpenAI');
281
291
 
282
292
  return super.getDefaultConfig({
283
293
  url: 'https://api.openai.com/v1/chat/completions',
@@ -286,24 +296,9 @@ function createBaseProviders({ ModelMix }) {
286
296
  });
287
297
  }
288
298
 
289
- async create({ config = {}, options = {} } = {}) {
290
-
291
- // Remove max_tokens and temperature for o1/o3 models
292
- if (options.model?.startsWith('o')) {
293
- delete options.max_tokens;
294
- delete options.temperature;
295
- }
296
-
297
- // Use max_completion_tokens and remove temperature for GPT-5 models
298
- if (options.model?.includes('gpt-5')) {
299
- if (options.max_tokens) {
300
- options.max_completion_tokens = options.max_tokens;
301
- delete options.max_tokens;
302
- }
303
- delete options.temperature;
304
- }
305
-
306
- return super.create({ config, options });
299
+ async create({ config = {}, options = {}, signal } = {}) {
300
+ normalizeOpenAIOptions(options);
301
+ return super.create({ config, options, signal });
307
302
  }
308
303
 
309
304
  static convertMessages(messages, config) {
@@ -387,10 +382,8 @@ function createBaseProviders({ ModelMix }) {
387
382
  }
388
383
  }
389
384
 
390
- // Solo incluir tools si el array no está vacío
391
385
  if (toolsArray.length > 0) {
392
386
  options.tools = toolsArray;
393
- // options.tool_choice = "auto";
394
387
  }
395
388
 
396
389
  return options;
@@ -1,10 +1,15 @@
1
1
  const { fetchJsonResponse } = require('../../http-client');
2
2
  const { configForDebug } = require('../provider-debug');
3
+ const { requireProviderApiKey } = require('../provider-api-key');
4
+ const {
5
+ throwIfAborted,
6
+ validateProviderExecution
7
+ } = require('../abort-signal');
3
8
 
4
9
  function createGoogleProviders({ ModelMix, MixCustom }) {
5
10
  class MixGoogle extends MixCustom {
6
11
  getDefaultConfig(customConfig) {
7
- const apiKey = customConfig.apiKey || process.env.GEMINI_API_KEY;
12
+ const apiKey = requireProviderApiKey(customConfig, 'GEMINI_API_KEY', 'Gemini');
8
13
  return super.getDefaultConfig({
9
14
  url: 'https://generativelanguage.googleapis.com/v1beta/models',
10
15
  apiKey,
@@ -19,7 +24,7 @@ function createGoogleProviders({ ModelMix, MixCustom }) {
19
24
  };
20
25
  }
21
26
 
22
- static convertMessages(messages, config) {
27
+ static convertMessages(messages) {
23
28
  return messages.map(message => {
24
29
 
25
30
  // Handle assistant messages with tool_calls (content is null)
@@ -121,11 +126,8 @@ function createGoogleProviders({ ModelMix, MixCustom }) {
121
126
  }, []);
122
127
  }
123
128
 
124
- async create({ config = {}, options = {} } = {}) {
125
- if (!this.config.apiKey) {
126
- throw new Error('Gemini API key not found. Please provide it in config or set GEMINI_API_KEY environment variable.');
127
- }
128
-
129
+ async create({ config = {}, options = {}, signal } = {}) {
130
+ validateProviderExecution(this, { config, options, signal });
129
131
  const generateContentApi = options.stream ? 'streamGenerateContent' : 'generateContent';
130
132
 
131
133
  const fullUrl = `${this.config.url}/${options.model}:${generateContentApi}?key=${this.config.apiKey}`;
@@ -190,10 +192,12 @@ function createGoogleProviders({ ModelMix, MixCustom }) {
190
192
  return this.processResponse(await fetchJsonResponse(fullUrl, {
191
193
  method: 'POST',
192
194
  headers: this.headers,
193
- body: JSON.stringify(payload)
195
+ body: JSON.stringify(payload),
196
+ signal
194
197
  }));
195
198
  }
196
199
  } catch (error) {
200
+ throwIfAborted(signal);
197
201
  throw this.handleError(error);
198
202
  }
199
203
  }
@@ -2,14 +2,12 @@ const {
2
2
  GROK420_REASONING,
3
3
  GROK420_NON_REASONING
4
4
  } = require('../../effort');
5
+ const { requireProviderApiKey } = require('../provider-api-key');
5
6
 
6
7
  function createCompatibleProviders({ MixCustom, MixOpenAI }) {
7
8
  class MixMiniMax extends MixOpenAI {
8
9
  getDefaultConfig(customConfig) {
9
- const apiKey = customConfig.apiKey || process.env.MINIMAX_API_KEY;
10
- if (!apiKey) {
11
- throw new Error('MiniMax API key not found. Please provide it in config or set MINIMAX_API_KEY environment variable.');
12
- }
10
+ const apiKey = requireProviderApiKey(customConfig, 'MINIMAX_API_KEY', 'MiniMax');
13
11
 
14
12
  return MixCustom.prototype.getDefaultConfig.call(this, {
15
13
  url: 'https://api.minimax.io/v1/chat/completions',
@@ -29,10 +27,7 @@ function createCompatibleProviders({ MixCustom, MixOpenAI }) {
29
27
 
30
28
  class MixMiMo extends MixOpenAI {
31
29
  getDefaultConfig(customConfig) {
32
- const apiKey = customConfig.apiKey || process.env.MIMO_API_KEY;
33
- if (!apiKey) {
34
- throw new Error('MiMo API key not found. Please provide it in config or set MIMO_API_KEY environment variable.');
35
- }
30
+ const apiKey = requireProviderApiKey(customConfig, 'MIMO_API_KEY', 'MiMo');
36
31
 
37
32
  return MixCustom.prototype.getDefaultConfig.call(this, {
38
33
  url: 'https://api.xiaomimimo.com/v1/chat/completions',
@@ -53,10 +48,7 @@ function createCompatibleProviders({ MixCustom, MixOpenAI }) {
53
48
 
54
49
  class MixPerplexity extends MixCustom {
55
50
  getDefaultConfig(customConfig) {
56
- const apiKey = customConfig.apiKey || process.env.PPLX_API_KEY;
57
- if (!apiKey) {
58
- throw new Error('Perplexity API key not found. Please provide it in config or set PPLX_API_KEY environment variable.');
59
- }
51
+ const apiKey = requireProviderApiKey(customConfig, 'PPLX_API_KEY', 'Perplexity');
60
52
 
61
53
  return super.getDefaultConfig({
62
54
  url: 'https://api.perplexity.ai/chat/completions',
@@ -65,7 +57,7 @@ function createCompatibleProviders({ MixCustom, MixOpenAI }) {
65
57
  });
66
58
  }
67
59
 
68
- async create({ config = {}, options = {} } = {}) {
60
+ async create({ config = {}, options = {}, signal } = {}) {
69
61
 
70
62
  if (config.schema) {
71
63
  options.response_format = {
@@ -74,7 +66,7 @@ function createCompatibleProviders({ MixCustom, MixOpenAI }) {
74
66
  };
75
67
  }
76
68
 
77
- return super.create({ config, options });
69
+ return super.create({ config, options, signal });
78
70
  }
79
71
  }
80
72
 
@@ -133,10 +125,7 @@ function createCompatibleProviders({ MixCustom, MixOpenAI }) {
133
125
 
134
126
  class MixGrok extends MixOpenAI {
135
127
  getDefaultConfig(customConfig) {
136
- const apiKey = customConfig.apiKey || process.env.XAI_API_KEY;
137
- if (!apiKey) {
138
- throw new Error('Grok API key not found. Please provide it in config or set XAI_API_KEY environment variable.');
139
- }
128
+ const apiKey = requireProviderApiKey(customConfig, 'XAI_API_KEY', 'Grok');
140
129
 
141
130
  return super.getDefaultConfig({
142
131
  url: 'https://api.x.ai/v1/chat/completions',
@@ -145,20 +134,17 @@ function createCompatibleProviders({ MixCustom, MixOpenAI }) {
145
134
  });
146
135
  }
147
136
 
148
- async create({ config = {}, options = {} } = {}) {
137
+ async create({ config = {}, options = {}, signal } = {}) {
149
138
  if (options.model === GROK420_REASONING || options.model === GROK420_NON_REASONING) {
150
139
  delete options.reasoning_effort;
151
140
  }
152
- return super.create({ config, options });
141
+ return super.create({ config, options, signal });
153
142
  }
154
143
  }
155
144
 
156
145
  class MixLambda extends MixCustom {
157
146
  getDefaultConfig(customConfig) {
158
- const apiKey = customConfig.apiKey || process.env.LAMBDA_API_KEY;
159
- if (!apiKey) {
160
- throw new Error('Lambda API key not found. Please provide it in config or set LAMBDA_API_KEY environment variable.');
161
- }
147
+ const apiKey = requireProviderApiKey(customConfig, 'LAMBDA_API_KEY', 'Lambda');
162
148
 
163
149
  return super.getDefaultConfig({
164
150
  url: 'https://api.lambda.ai/v1/chat/completions',
@@ -176,14 +162,14 @@ function createCompatibleProviders({ MixCustom, MixOpenAI }) {
176
162
  });
177
163
  }
178
164
 
179
- create({ config = {}, options = {} } = {}) {
165
+ create({ config = {}, options = {}, signal } = {}) {
180
166
  if (config.schema) {
181
167
  options.response_format = {
182
168
  type: 'json_schema',
183
169
  json_schema: { schema: config.schema }
184
170
  };
185
171
  }
186
- return super.create({ config, options });
172
+ return super.create({ config, options, signal });
187
173
  }
188
174
 
189
175
  static extractThink(data) {
@@ -235,10 +221,7 @@ function createCompatibleProviders({ MixCustom, MixOpenAI }) {
235
221
 
236
222
  class MixGroq extends MixCustom {
237
223
  getDefaultConfig(customConfig) {
238
- const apiKey = customConfig.apiKey || process.env.GROQ_API_KEY;
239
- if (!apiKey) {
240
- throw new Error('Groq API key not found. Please provide it in config or set GROQ_API_KEY environment variable.');
241
- }
224
+ const apiKey = requireProviderApiKey(customConfig, 'GROQ_API_KEY', 'Groq');
242
225
 
243
226
  return super.getDefaultConfig({
244
227
  url: 'https://api.groq.com/openai/v1/chat/completions',
@@ -250,10 +233,7 @@ function createCompatibleProviders({ MixCustom, MixOpenAI }) {
250
233
 
251
234
  class MixTogether extends MixCustom {
252
235
  getDefaultConfig(customConfig) {
253
- const apiKey = customConfig.apiKey || process.env.TOGETHER_API_KEY;
254
- if (!apiKey) {
255
- throw new Error('Together API key not found. Please provide it in config or set TOGETHER_API_KEY environment variable.');
256
- }
236
+ const apiKey = requireProviderApiKey(customConfig, 'TOGETHER_API_KEY', 'Together');
257
237
 
258
238
  return super.getDefaultConfig({
259
239
  url: 'https://api.together.xyz/v1/chat/completions',
@@ -272,10 +252,7 @@ function createCompatibleProviders({ MixCustom, MixOpenAI }) {
272
252
 
273
253
  class MixCerebras extends MixCustom {
274
254
  getDefaultConfig(customConfig) {
275
- const apiKey = customConfig.apiKey || process.env.CEREBRAS_API_KEY;
276
- if (!apiKey) {
277
- throw new Error('Cerebras API key not found. Please provide it in config or set CEREBRAS_API_KEY environment variable.');
278
- }
255
+ const apiKey = requireProviderApiKey(customConfig, 'CEREBRAS_API_KEY', 'Cerebras');
279
256
 
280
257
  return super.getDefaultConfig({
281
258
  url: 'https://api.cerebras.ai/v1/chat/completions',
@@ -284,18 +261,15 @@ function createCompatibleProviders({ MixCustom, MixOpenAI }) {
284
261
  });
285
262
  }
286
263
 
287
- create({ config = {}, options = {} } = {}) {
264
+ create({ config = {}, options = {}, signal } = {}) {
288
265
  delete options.response_format;
289
- return super.create({ config, options });
266
+ return super.create({ config, options, signal });
290
267
  }
291
268
  }
292
269
 
293
270
  class MixFireworks extends MixCustom {
294
271
  getDefaultConfig(customConfig) {
295
- const apiKey = customConfig.apiKey || process.env.FIREWORKS_API_KEY;
296
- if (!apiKey) {
297
- throw new Error('Fireworks API key not found. Please provide it in config or set FIREWORKS_API_KEY environment variable.');
298
- }
272
+ const apiKey = requireProviderApiKey(customConfig, 'FIREWORKS_API_KEY', 'Fireworks');
299
273
 
300
274
  return super.getDefaultConfig({
301
275
  url: 'https://api.fireworks.ai/inference/v1/chat/completions',
@@ -307,10 +281,7 @@ function createCompatibleProviders({ MixCustom, MixOpenAI }) {
307
281
 
308
282
  class MixNVIDIA extends MixCustom {
309
283
  getDefaultConfig(customConfig) {
310
- const apiKey = customConfig.apiKey || process.env.NVIDIA_API_KEY;
311
- if (!apiKey) {
312
- throw new Error('NVIDIA API key not found. Please provide it in config or set NVIDIA_API_KEY environment variable.');
313
- }
284
+ const apiKey = requireProviderApiKey(customConfig, 'NVIDIA_API_KEY', 'NVIDIA');
314
285
 
315
286
  return super.getDefaultConfig({
316
287
  url: 'https://integrate.api.nvidia.com/v1/chat/completions',
@@ -0,0 +1,16 @@
1
+ function normalizeOpenAIOptions(options) {
2
+ if (/^o\d/.test(options.model || '')) {
3
+ delete options.max_tokens;
4
+ delete options.temperature;
5
+ }
6
+ if (options.model?.includes('gpt-5')) {
7
+ if (options.max_tokens) {
8
+ options.max_completion_tokens = options.max_tokens;
9
+ delete options.max_tokens;
10
+ }
11
+ delete options.temperature;
12
+ }
13
+ return options;
14
+ }
15
+
16
+ module.exports = { normalizeOpenAIOptions };