modelmix 4.6.6 → 4.6.8

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.
@@ -0,0 +1,359 @@
1
+ const { expect } = require('chai');
2
+ const {
3
+ normalizeEffort,
4
+ mapEffort,
5
+ applyUnifiedEffort,
6
+ hasNativeEffort,
7
+ resolveProviderFamily,
8
+ levelFromBands,
9
+ OPENAI_BANDS,
10
+ ANTHROPIC_BANDS,
11
+ GEMINI_BANDS,
12
+ } = require('../effort.js');
13
+ const {
14
+ ModelMix,
15
+ MixOpenAI,
16
+ MixOpenAIResponses,
17
+ MixAnthropic,
18
+ MixGoogle,
19
+ MixPerplexity,
20
+ } = require('../index.js');
21
+
22
+ describe('Unified effort scale', () => {
23
+ describe('normalizeEffort', () => {
24
+ it('accepts -1 and 0..100 integers', () => {
25
+ expect(normalizeEffort(-1)).to.equal(-1);
26
+ expect(normalizeEffort(0)).to.equal(0);
27
+ expect(normalizeEffort(50)).to.equal(50);
28
+ expect(normalizeEffort(100)).to.equal(100);
29
+ });
30
+
31
+ it('rejects invalid values', () => {
32
+ expect(() => normalizeEffort(1.5)).to.throw(/Invalid effort/);
33
+ expect(() => normalizeEffort(101)).to.throw(/Invalid effort/);
34
+ expect(() => normalizeEffort(-2)).to.throw(/Invalid effort/);
35
+ expect(() => normalizeEffort('medium')).to.throw(/Invalid effort/);
36
+ expect(() => normalizeEffort(null)).to.throw(/Invalid effort/);
37
+ });
38
+ });
39
+
40
+ describe('band mapping', () => {
41
+ it('maps OpenAI bands', () => {
42
+ expect(levelFromBands(0, OPENAI_BANDS)).to.equal('none');
43
+ expect(levelFromBands(19, OPENAI_BANDS)).to.equal('none');
44
+ expect(levelFromBands(20, OPENAI_BANDS)).to.equal('low');
45
+ expect(levelFromBands(40, OPENAI_BANDS)).to.equal('medium');
46
+ expect(levelFromBands(60, OPENAI_BANDS)).to.equal('high');
47
+ expect(levelFromBands(80, OPENAI_BANDS)).to.equal('xhigh');
48
+ expect(levelFromBands(100, OPENAI_BANDS)).to.equal('xhigh');
49
+ });
50
+
51
+ it('maps Anthropic bands', () => {
52
+ expect(levelFromBands(0, ANTHROPIC_BANDS)).to.equal('low');
53
+ expect(levelFromBands(20, ANTHROPIC_BANDS)).to.equal('medium');
54
+ expect(levelFromBands(40, ANTHROPIC_BANDS)).to.equal('high');
55
+ expect(levelFromBands(60, ANTHROPIC_BANDS)).to.equal('xhigh');
56
+ expect(levelFromBands(80, ANTHROPIC_BANDS)).to.equal('max');
57
+ });
58
+
59
+ it('maps Gemini bands', () => {
60
+ expect(levelFromBands(0, GEMINI_BANDS)).to.equal('minimal');
61
+ expect(levelFromBands(25, GEMINI_BANDS)).to.equal('low');
62
+ expect(levelFromBands(50, GEMINI_BANDS)).to.equal('medium');
63
+ expect(levelFromBands(75, GEMINI_BANDS)).to.equal('high');
64
+ });
65
+ });
66
+
67
+ describe('mapEffort', () => {
68
+ it('maps OpenAI effort to reasoning_effort', () => {
69
+ expect(mapEffort('openai', 10)).to.deep.equal({ reasoning_effort: 'none' });
70
+ expect(mapEffort('openai', 50)).to.deep.equal({ reasoning_effort: 'medium' });
71
+ expect(mapEffort('openai', 90)).to.deep.equal({ reasoning_effort: 'xhigh' });
72
+ });
73
+
74
+ it('sets OpenAI adaptive only when supported (otherwise no-op)', () => {
75
+ expect(mapEffort('openai', -1)).to.equal(null);
76
+ expect(mapEffort('openai', -1, 'gpt-5.2')).to.equal(null);
77
+ });
78
+
79
+ it('clamps OpenAI to model-supported levels', () => {
80
+ expect(mapEffort('openai', 10, 'gpt-5.3-codex')).to.deep.equal({ reasoning_effort: 'low' });
81
+ expect(mapEffort('openai', 10, 'gpt-oss-120b')).to.deep.equal({ reasoning_effort: 'low' });
82
+ });
83
+
84
+ it('maps Anthropic effort to output_config.effort', () => {
85
+ expect(mapEffort('anthropic', 10)).to.deep.equal({ output_config: { effort: 'low' } });
86
+ expect(mapEffort('anthropic', 90)).to.deep.equal({ output_config: { effort: 'max' } });
87
+ });
88
+
89
+ it('maps Anthropic adaptive to thinking.type=adaptive', () => {
90
+ expect(mapEffort('anthropic', -1)).to.deep.equal({ thinking: { type: 'adaptive' } });
91
+ });
92
+
93
+ it('maps Gemini 3+ thinkingLevel', () => {
94
+ expect(mapEffort('google', 10, 'gemini-3.6-flash')).to.deep.equal({
95
+ thinkingConfig: { thinkingLevel: 'minimal' }
96
+ });
97
+ expect(mapEffort('google', 80, 'gemini-3.6-flash')).to.deep.equal({
98
+ thinkingConfig: { thinkingLevel: 'high' }
99
+ });
100
+ });
101
+
102
+ it('clamps Gemini levels for models with fewer steps', () => {
103
+ expect(mapEffort('google', 10, 'gemini-3-pro-preview')).to.deep.equal({
104
+ thinkingConfig: { thinkingLevel: 'low' }
105
+ });
106
+ });
107
+
108
+ it('maps Gemini adaptive (-1) to thinkingBudget -1', () => {
109
+ expect(mapEffort('google', -1, 'gemini-3.6-flash')).to.deep.equal({
110
+ thinkingConfig: { thinkingBudget: -1 }
111
+ });
112
+ expect(mapEffort('google', -1, 'gemini-2.5-flash')).to.deep.equal({
113
+ thinkingConfig: { thinkingBudget: -1 }
114
+ });
115
+ });
116
+
117
+ it('maps Gemini 2.5 to thinkingBudget', () => {
118
+ expect(mapEffort('google', 0, 'gemini-2.5-flash')).to.deep.equal({
119
+ thinkingConfig: { thinkingBudget: 0 }
120
+ });
121
+ expect(mapEffort('google', 100, 'gemini-2.5-flash')).to.deep.equal({
122
+ thinkingConfig: { thinkingBudget: 24576 }
123
+ });
124
+ expect(mapEffort('google', 50, 'gemini-2.5-pro')).to.deep.equal({
125
+ thinkingConfig: { thinkingBudget: 16384 }
126
+ });
127
+ });
128
+
129
+ it('maps DeepSeek V4 effort to thinking + reasoning_effort', () => {
130
+ const key = 'accounts/fireworks/models/deepseek-v4-flash';
131
+ expect(mapEffort('openai', 0, key)).to.deep.equal({
132
+ thinking: { type: 'disabled' }
133
+ });
134
+ expect(mapEffort('openai', 30, key)).to.deep.equal({
135
+ reasoning_effort: 'low',
136
+ thinking: { type: 'enabled' }
137
+ });
138
+ expect(mapEffort('openai', 50, key)).to.deep.equal({
139
+ reasoning_effort: 'high',
140
+ thinking: { type: 'enabled' }
141
+ });
142
+ expect(mapEffort('openai', 100, 'deepseek-ai/DeepSeek-V4-Pro')).to.deep.equal({
143
+ reasoning_effort: 'max',
144
+ thinking: { type: 'enabled' }
145
+ });
146
+ // No adaptive control on DeepSeek → no-op
147
+ expect(mapEffort('openai', -1, 'deepseek/deepseek-v4-flash')).to.equal(null);
148
+ });
149
+
150
+ it('maps MiniMax thinking adaptive/disabled', () => {
151
+ expect(mapEffort('openai', -1, 'MiniMax-M3')).to.deep.equal({
152
+ thinking: { type: 'adaptive' }
153
+ });
154
+ expect(mapEffort('openai', 0, 'minimax/minimax-m3')).to.deep.equal({
155
+ thinking: { type: 'disabled' }
156
+ });
157
+ expect(mapEffort('openai', 50, 'MiniMaxAI/MiniMax-M3')).to.deep.equal({
158
+ thinking: { type: 'adaptive' }
159
+ });
160
+ });
161
+
162
+ it('returns null for unsupported families', () => {
163
+ expect(mapEffort(null, 50)).to.equal(null);
164
+ });
165
+ });
166
+
167
+ describe('applyUnifiedEffort / native wins', () => {
168
+ it('applies OpenAI mapping when native absent', () => {
169
+ const options = {};
170
+ applyUnifiedEffort(options, { effort: 50 }, 'openai', 'gpt-5.2');
171
+ expect(options.reasoning_effort).to.equal('medium');
172
+ });
173
+
174
+ it('applies MiniMax adaptive from config.effort -1', () => {
175
+ const options = {};
176
+ applyUnifiedEffort(options, { effort: -1 }, 'openai', 'MiniMax-M3');
177
+ expect(options.thinking).to.deep.equal({ type: 'adaptive' });
178
+ });
179
+
180
+ it('applies DeepSeek mapping on Fireworks model key', () => {
181
+ const options = {};
182
+ applyUnifiedEffort(
183
+ options,
184
+ { effort: 100 },
185
+ 'openai',
186
+ 'accounts/fireworks/models/deepseek-v4-flash'
187
+ );
188
+ expect(options.reasoning_effort).to.equal('max');
189
+ expect(options.thinking).to.deep.equal({ type: 'enabled' });
190
+ });
191
+
192
+ it('applies DeepSeek disabled thinking without reasoning_effort', () => {
193
+ const options = {};
194
+ applyUnifiedEffort(
195
+ options,
196
+ { effort: 10 },
197
+ 'openai',
198
+ 'deepseek/deepseek-v4-flash'
199
+ );
200
+ expect(options.thinking).to.deep.equal({ type: 'disabled' });
201
+ expect(options.reasoning_effort).to.equal(undefined);
202
+ });
203
+
204
+ it('skips DeepSeek mapping when thinking is already set', () => {
205
+ const options = { thinking: { type: 'disabled' } };
206
+ applyUnifiedEffort(
207
+ options,
208
+ { effort: 100 },
209
+ 'openai',
210
+ 'accounts/fireworks/models/deepseek-v4-flash'
211
+ );
212
+ expect(options.thinking).to.deep.equal({ type: 'disabled' });
213
+ expect(options.reasoning_effort).to.equal(undefined);
214
+ });
215
+
216
+ it('skips OpenAI mapping when reasoning_effort is set', () => {
217
+ const options = { reasoning_effort: 'none' };
218
+ applyUnifiedEffort(options, { effort: 90 }, 'openai', 'gpt-5.2');
219
+ expect(options.reasoning_effort).to.equal('none');
220
+ });
221
+
222
+ it('applies Anthropic mapping when native absent', () => {
223
+ const options = {};
224
+ applyUnifiedEffort(options, { effort: 90 }, 'anthropic', 'claude-opus-5');
225
+ expect(options.output_config).to.deep.equal({ effort: 'max' });
226
+ });
227
+
228
+ it('skips Anthropic mapping when output_config.effort is set', () => {
229
+ const options = { output_config: { effort: 'low', format: { type: 'json_schema' } } };
230
+ applyUnifiedEffort(options, { effort: 90 }, 'anthropic', 'claude-opus-5');
231
+ expect(options.output_config.effort).to.equal('low');
232
+ expect(options.output_config.format).to.deep.equal({ type: 'json_schema' });
233
+ });
234
+
235
+ it('merges Anthropic adaptive without wiping display, drops budget_tokens', () => {
236
+ const options = {
237
+ thinking: { type: 'enabled', budget_tokens: 1638, display: 'summarized' }
238
+ };
239
+ applyUnifiedEffort(options, { effort: -1 }, 'anthropic', 'claude-sonnet-4-6');
240
+ expect(options.thinking).to.deep.equal({ type: 'adaptive', display: 'summarized' });
241
+ });
242
+
243
+ it('skips Google mapping when thinkingConfig is set', () => {
244
+ const options = { thinkingConfig: { thinkingLevel: 'low' } };
245
+ applyUnifiedEffort(options, { effort: 90 }, 'google', 'gemini-3.6-flash');
246
+ expect(options.thinkingConfig.thinkingLevel).to.equal('low');
247
+ });
248
+
249
+ it('does nothing when config.effort is undefined', () => {
250
+ const options = {};
251
+ applyUnifiedEffort(options, {}, 'openai', 'gpt-5.2');
252
+ expect(options).to.deep.equal({});
253
+ });
254
+
255
+ it('hasNativeEffort detects provider fields', () => {
256
+ expect(hasNativeEffort('openai', { reasoning_effort: 'high' })).to.equal(true);
257
+ expect(hasNativeEffort('openai', {})).to.equal(false);
258
+ expect(hasNativeEffort('anthropic', { output_config: { effort: 'max' } })).to.equal(true);
259
+ expect(hasNativeEffort('google', { thinkingBudget: 1024 })).to.equal(true);
260
+ });
261
+ });
262
+
263
+ describe('resolveProviderFamily', () => {
264
+ it('resolves known providers', () => {
265
+ expect(resolveProviderFamily(new MixOpenAI())).to.equal('openai');
266
+ expect(resolveProviderFamily(new MixOpenAIResponses())).to.equal('openai');
267
+ expect(resolveProviderFamily(new MixAnthropic())).to.equal('anthropic');
268
+ expect(resolveProviderFamily(new MixGoogle())).to.equal('google');
269
+ expect(resolveProviderFamily(new MixPerplexity())).to.equal(null);
270
+ });
271
+ });
272
+
273
+ describe('ModelMix API surface', () => {
274
+ it('accepts config.effort', () => {
275
+ const model = ModelMix.new({ config: { effort: 25 } });
276
+ expect(model.config.effort).to.equal(25);
277
+ });
278
+
279
+ it('accepts config.effort on model shorthand', () => {
280
+ const model = ModelMix.new().deepseekV4Flash({ config: { effort: 100 } });
281
+ expect(model.models[0].provider.config.effort).to.equal(100);
282
+ });
283
+
284
+ it('supports fluent .effort()', () => {
285
+ const model = ModelMix.new().effort(-1);
286
+ expect(model.config.effort).to.equal(-1);
287
+ });
288
+
289
+ it('lets .new({ config: { effort } }) override inherited config.effort', () => {
290
+ const base = ModelMix.new({ config: { effort: 20 } });
291
+ const child = base.new({ config: { effort: 80 } });
292
+ expect(child.config.effort).to.equal(80);
293
+ expect(base.config.effort).to.equal(20);
294
+ });
295
+
296
+ it('rejects invalid fluent effort', () => {
297
+ expect(() => ModelMix.new().effort(150)).to.throw(/Invalid effort/);
298
+ });
299
+ });
300
+
301
+ describe('provider request wiring', () => {
302
+ it('OpenAI Responses request uses mapped reasoning_effort', () => {
303
+ const options = { model: 'gpt-5.2', messages: [] };
304
+ applyUnifiedEffort(options, { effort: 15 }, 'openai', 'gpt-5.2');
305
+ const request = MixOpenAIResponses.buildResponsesRequest(options, {});
306
+ expect(request.reasoning).to.deep.equal({ effort: 'none' });
307
+ });
308
+
309
+ it('Anthropic *think() native effort wins over config.effort', () => {
310
+ const model = ModelMix.new({ config: { effort: 20 } }).opus5think();
311
+ expect(model.models[0].provider.options.output_config.effort).to.equal('max');
312
+
313
+ const options = {
314
+ ...model.models[0].provider.options,
315
+ model: 'claude-opus-5'
316
+ };
317
+ applyUnifiedEffort(options, { effort: 20 }, 'anthropic', 'claude-opus-5');
318
+ expect(options.output_config.effort).to.equal('max');
319
+ });
320
+
321
+ it('MixGoogle generationConfig includes thinkingConfig from options', async () => {
322
+ const google = new MixGoogle({ config: { apiKey: 'test-key' } });
323
+ let capturedBody;
324
+ const originalFetch = global.fetch;
325
+ const responseBody = JSON.stringify({
326
+ candidates: [{ content: { parts: [{ text: 'ok' }] } }],
327
+ usageMetadata: { promptTokenCount: 1, candidatesTokenCount: 1 }
328
+ });
329
+ global.fetch = async (url, init) => {
330
+ capturedBody = JSON.parse(init.body);
331
+ return {
332
+ ok: true,
333
+ status: 200,
334
+ headers: new Headers({ 'content-type': 'application/json' }),
335
+ text: async () => responseBody,
336
+ json: async () => JSON.parse(responseBody)
337
+ };
338
+ };
339
+
340
+ try {
341
+ await google.create({
342
+ config: { system: 'sys' },
343
+ options: {
344
+ model: 'gemini-3.6-flash',
345
+ max_tokens: 100,
346
+ messages: [{ role: 'user', content: [{ type: 'text', text: 'hi' }] }],
347
+ thinkingConfig: { thinkingLevel: 'low' }
348
+ }
349
+ });
350
+ } finally {
351
+ global.fetch = originalFetch;
352
+ }
353
+
354
+ expect(capturedBody.generationConfig.thinkingConfig).to.deep.equal({
355
+ thinkingLevel: 'low'
356
+ });
357
+ });
358
+ });
359
+ });
@@ -456,6 +456,116 @@ describe('Conversation History Tests', () => {
456
456
  expect(capturedBody.messages[2].role).to.equal('user');
457
457
  });
458
458
 
459
+ it('should replay omitted thinking blocks with empty string (not null) on Anthropic', async () => {
460
+ // Opus 5 / Sonnet 5 / Fable 5 default to display "omitted":
461
+ // thinking blocks arrive as thinking: "" + signature.
462
+ // Replaying thinking: null causes Anthropic 400 invalid_request_error.
463
+ const model = ModelMix.new({
464
+ config: { debug: false, max_history: 10 }
465
+ });
466
+ model.opus5();
467
+
468
+ model.addText('Capital of France?');
469
+ nock('https://api.anthropic.com')
470
+ .post('/v1/messages')
471
+ .reply(200, {
472
+ content: [{
473
+ type: 'thinking',
474
+ thinking: '',
475
+ signature: 'sig-omitted-turn1'
476
+ }, {
477
+ type: 'text',
478
+ text: 'Paris.'
479
+ }]
480
+ });
481
+ await model.message();
482
+
483
+ expect(model.messages[1].content[0]).to.deep.equal({
484
+ type: 'thinking',
485
+ thinking: '',
486
+ signature: 'sig-omitted-turn1'
487
+ });
488
+ expect(model.messages[1].content[0].thinking).to.be.a('string');
489
+ expect(model.messages[1].content[0].thinking).to.not.equal(null);
490
+
491
+ let capturedBody;
492
+ model.addText('Capital of Germany?');
493
+ nock('https://api.anthropic.com')
494
+ .post('/v1/messages', (body) => {
495
+ capturedBody = body;
496
+ return true;
497
+ })
498
+ .reply(200, {
499
+ content: [{
500
+ type: 'thinking',
501
+ thinking: '',
502
+ signature: 'sig-omitted-turn2'
503
+ }, {
504
+ type: 'text',
505
+ text: 'Berlin.'
506
+ }]
507
+ });
508
+ await model.message();
509
+
510
+ const assistantMsg = capturedBody.messages.find(m => m.role === 'assistant');
511
+ expect(assistantMsg).to.exist;
512
+ expect(assistantMsg.content[0]).to.deep.equal({
513
+ type: 'thinking',
514
+ thinking: '',
515
+ signature: 'sig-omitted-turn1'
516
+ });
517
+ expect(assistantMsg.content[0].thinking).to.equal('');
518
+ expect(assistantMsg.content[1].text).to.equal('Paris.');
519
+ });
520
+
521
+ it('should replay summarized thinking blocks unchanged on Anthropic', async () => {
522
+ const model = ModelMix.new({
523
+ config: { debug: false, max_history: 10 }
524
+ });
525
+ model.opus5think();
526
+
527
+ model.addText('2+2?');
528
+ nock('https://api.anthropic.com')
529
+ .post('/v1/messages')
530
+ .reply(200, {
531
+ content: [{
532
+ type: 'thinking',
533
+ thinking: 'Simple arithmetic.',
534
+ signature: 'sig-summarized-turn1'
535
+ }, {
536
+ type: 'text',
537
+ text: '4'
538
+ }]
539
+ });
540
+ await model.message();
541
+
542
+ let capturedBody;
543
+ model.addText('3+3?');
544
+ nock('https://api.anthropic.com')
545
+ .post('/v1/messages', (body) => {
546
+ capturedBody = body;
547
+ return true;
548
+ })
549
+ .reply(200, {
550
+ content: [{
551
+ type: 'thinking',
552
+ thinking: 'Also simple.',
553
+ signature: 'sig-summarized-turn2'
554
+ }, {
555
+ type: 'text',
556
+ text: '6'
557
+ }]
558
+ });
559
+ await model.message();
560
+
561
+ const assistantMsg = capturedBody.messages.find(m => m.role === 'assistant');
562
+ expect(assistantMsg.content[0]).to.deep.equal({
563
+ type: 'thinking',
564
+ thinking: 'Simple arithmetic.',
565
+ signature: 'sig-summarized-turn1'
566
+ });
567
+ });
568
+
459
569
  it('should maintain history when using Google provider', async () => {
460
570
  const model = ModelMix.new({
461
571
  config: { debug: false, max_history: 10 }
package/test/live.mcp.js CHANGED
@@ -139,6 +139,48 @@ describe('Live MCP Integration Tests', function () {
139
139
  }
140
140
  });
141
141
 
142
+ it('should use custom MCP tools with Claude Opus 5', async function () {
143
+ // Opus 5 rejects temperature (deprecated); omit it from options.
144
+ const model = ModelMix.new({ config: setup.config }).opus5();
145
+
146
+ model.addTool({
147
+ name: "get_current_time",
148
+ description: "Get the current date and time",
149
+ inputSchema: {
150
+ type: "object",
151
+ properties: {
152
+ timezone: {
153
+ type: "string",
154
+ description: "Timezone (optional, defaults to UTC)"
155
+ }
156
+ }
157
+ }
158
+ }, async ({ timezone = 'UTC' }) => {
159
+ const now = new Date();
160
+ if (timezone === 'UTC') {
161
+ return `Current time (UTC): ${now.toISOString()}`;
162
+ }
163
+ return `Current time (${timezone}): ${now.toLocaleString('en-US', { timeZone: timezone })}`;
164
+ });
165
+
166
+ model.setSystem('You are a helpful assistant that can tell time. Use the get_current_time tool when asked about time.');
167
+ model.addText('What time is it right now?');
168
+
169
+ try {
170
+ const response = await withRetry(() => model.message(), { retries: 2, baseDelayMs: 1500 });
171
+ console.log(`Claude Opus 5 with MCP tools: ${response}`);
172
+
173
+ expect(response).to.be.a('string');
174
+ expect(response.toLowerCase()).to.match(/(time|clock|hour|minute|second|am|pm|utc|\d{4})/);
175
+ } catch (error) {
176
+ console.error('Full error:', error);
177
+ if (error.response && error.response.data) {
178
+ console.error('Response data:', JSON.stringify(error.response.data, null, 2));
179
+ }
180
+ throw error;
181
+ }
182
+ });
183
+
142
184
  it('should use custom MCP tools with Gemini 3 Flash', async function () {
143
185
  const model = ModelMix.new(setup).gemini3flash();
144
186
 
@@ -517,7 +559,7 @@ describe('Live MCP Integration Tests', function () {
517
559
 
518
560
  it('should work with same MCP tools across different Anthropic models', async function () {
519
561
  const models = [
520
- { name: 'Sonnet 4', model: ModelMix.new(setup).sonnet46() },
562
+ { name: 'Opus 5', model: ModelMix.new({ config: setup.config }).opus5() },
521
563
  { name: 'Sonnet 4.6', model: ModelMix.new(setup).sonnet46() },
522
564
  { name: 'Haiku 4.5', model: ModelMix.new(setup).haiku45() }
523
565
  ];
package/test/setup.js CHANGED
@@ -27,6 +27,8 @@ process.env.OPENAI_API_KEY = process.env.OPENAI_API_KEY || 'sk-proj-test-dummy-k
27
27
  process.env.PPLX_API_KEY = process.env.PPLX_API_KEY || 'pplx-test-dummy-key-for-testing-purposes';
28
28
  process.env.GROQ_API_KEY = process.env.GROQ_API_KEY || 'gsk_test-dummy-key-for-testing-purposes';
29
29
  process.env.TOGETHER_API_KEY = process.env.TOGETHER_API_KEY || '49a96test-dummy-key-for-testing-purposes';
30
+ process.env.FIREWORKS_API_KEY = process.env.FIREWORKS_API_KEY || 'fw-test-dummy-key-for-testing-purposes';
31
+ process.env.OPENROUTER_API_KEY = process.env.OPENROUTER_API_KEY || 'sk-or-test-dummy-key-for-testing-purposes';
30
32
  process.env.XAI_API_KEY = process.env.XAI_API_KEY || 'xai-test-dummy-key-for-testing-purposes';
31
33
  process.env.CEREBRAS_API_KEY = process.env.CEREBRAS_API_KEY || 'csk-test-dummy-key-for-testing-purposes';
32
34
  process.env.NVIDIA_API_KEY = process.env.NVIDIA_API_KEY || 'nvapi-test-dummy-key-for-testing-purposes';