@lobehub/lobehub 2.0.8 → 2.0.10

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 (69) hide show
  1. package/.github/workflows/sync.yml +3 -3
  2. package/CHANGELOG.md +50 -0
  3. package/changelog/v2.json +14 -0
  4. package/locales/ar/models.json +53 -0
  5. package/locales/ar/setting.json +2 -0
  6. package/locales/ar/subscription.json +2 -0
  7. package/locales/bg-BG/models.json +32 -0
  8. package/locales/bg-BG/setting.json +2 -0
  9. package/locales/bg-BG/subscription.json +2 -0
  10. package/locales/de-DE/models.json +33 -0
  11. package/locales/de-DE/setting.json +2 -0
  12. package/locales/de-DE/subscription.json +2 -0
  13. package/locales/en-US/subscription.json +2 -0
  14. package/locales/es-ES/models.json +65 -0
  15. package/locales/es-ES/setting.json +2 -0
  16. package/locales/es-ES/subscription.json +2 -0
  17. package/locales/fa-IR/models.json +31 -0
  18. package/locales/fa-IR/setting.json +2 -0
  19. package/locales/fa-IR/subscription.json +2 -0
  20. package/locales/fr-FR/models.json +34 -0
  21. package/locales/fr-FR/setting.json +2 -0
  22. package/locales/fr-FR/subscription.json +2 -0
  23. package/locales/it-IT/models.json +37 -0
  24. package/locales/it-IT/setting.json +2 -0
  25. package/locales/it-IT/subscription.json +2 -0
  26. package/locales/ja-JP/models.json +20 -0
  27. package/locales/ja-JP/setting.json +2 -0
  28. package/locales/ja-JP/subscription.json +2 -0
  29. package/locales/ko-KR/models.json +51 -0
  30. package/locales/ko-KR/setting.json +2 -0
  31. package/locales/ko-KR/subscription.json +2 -0
  32. package/locales/nl-NL/models.json +9 -0
  33. package/locales/nl-NL/setting.json +2 -0
  34. package/locales/nl-NL/subscription.json +2 -0
  35. package/locales/pl-PL/models.json +33 -0
  36. package/locales/pl-PL/setting.json +2 -0
  37. package/locales/pl-PL/subscription.json +2 -0
  38. package/locales/pt-BR/models.json +49 -0
  39. package/locales/pt-BR/setting.json +2 -0
  40. package/locales/pt-BR/subscription.json +2 -0
  41. package/locales/ru-RU/models.json +45 -0
  42. package/locales/ru-RU/setting.json +2 -0
  43. package/locales/ru-RU/subscription.json +2 -0
  44. package/locales/tr-TR/models.json +23 -0
  45. package/locales/tr-TR/setting.json +2 -0
  46. package/locales/tr-TR/subscription.json +2 -0
  47. package/locales/vi-VN/models.json +49 -0
  48. package/locales/vi-VN/setting.json +2 -0
  49. package/locales/vi-VN/subscription.json +2 -0
  50. package/locales/zh-CN/subscription.json +2 -0
  51. package/locales/zh-TW/setting.json +2 -0
  52. package/locales/zh-TW/subscription.json +2 -0
  53. package/package.json +1 -1
  54. package/packages/model-bank/src/aiModels/cerebras.ts +2 -22
  55. package/packages/model-bank/src/aiModels/google.ts +1 -44
  56. package/packages/model-bank/src/aiModels/nvidia.ts +12 -16
  57. package/packages/model-bank/src/aiModels/siliconcloud.ts +20 -0
  58. package/packages/model-bank/src/aiModels/volcengine.ts +69 -0
  59. package/packages/model-bank/src/aiModels/wenxin.ts +41 -38
  60. package/packages/model-bank/src/aiModels/zenmux.ts +4 -4
  61. package/packages/model-bank/src/aiModels/zhipu.ts +58 -28
  62. package/packages/model-bank/src/types/aiModel.ts +29 -0
  63. package/packages/model-runtime/src/core/usageConverters/utils/computeChatCost.test.ts +2 -2
  64. package/packages/model-runtime/src/providers/google/createImage.test.ts +12 -12
  65. package/packages/model-runtime/src/providers/openrouter/index.test.ts +102 -0
  66. package/packages/model-runtime/src/providers/openrouter/index.ts +19 -7
  67. package/packages/model-runtime/src/providers/vercelaigateway/index.test.ts +47 -0
  68. package/packages/model-runtime/src/providers/vercelaigateway/index.ts +7 -1
  69. package/src/locales/default/subscription.ts +2 -0
@@ -12,7 +12,7 @@ const noImageErrorType = 'ProviderNoImageGenerated';
12
12
  const invalidErrorType = 'InvalidProviderAPIKey';
13
13
 
14
14
  // Mock the console.error to avoid polluting test output
15
- vi.spyOn(console, 'error').mockImplementation(() => {});
15
+ vi.spyOn(console, 'error').mockImplementation(() => { });
16
16
 
17
17
  let mockClient: GoogleGenAI;
18
18
 
@@ -361,7 +361,7 @@ describe('createGoogleImage', () => {
361
361
  vi.spyOn(mockClient.models, 'generateContent').mockResolvedValue(mockContentResponse as any);
362
362
 
363
363
  const payload: CreateImagePayload = {
364
- model: 'gemini-2.5-flash-image-preview:image',
364
+ model: 'gemini-2.5-flash-image:image',
365
365
  params: {
366
366
  prompt: 'Create a beautiful sunset landscape',
367
367
  },
@@ -378,7 +378,7 @@ describe('createGoogleImage', () => {
378
378
  parts: [{ text: 'Create a beautiful sunset landscape' }],
379
379
  },
380
380
  ],
381
- model: 'gemini-2.5-flash-image-preview',
381
+ model: 'gemini-2.5-flash-image',
382
382
  config: {
383
383
  responseModalities: ['Image'],
384
384
  },
@@ -414,7 +414,7 @@ describe('createGoogleImage', () => {
414
414
  vi.spyOn(mockClient.models, 'generateContent').mockResolvedValue(mockContentResponse as any);
415
415
 
416
416
  const payload: CreateImagePayload = {
417
- model: 'gemini-2.5-flash-image-preview:image',
417
+ model: 'gemini-2.5-flash-image:image',
418
418
  params: {
419
419
  prompt: 'Add a red rose to this image',
420
420
  imageUrl: `data:image/png;base64,${inputImageBase64}`,
@@ -440,7 +440,7 @@ describe('createGoogleImage', () => {
440
440
  ],
441
441
  },
442
442
  ],
443
- model: 'gemini-2.5-flash-image-preview',
443
+ model: 'gemini-2.5-flash-image',
444
444
  config: {
445
445
  responseModalities: ['Image'],
446
446
  },
@@ -482,7 +482,7 @@ describe('createGoogleImage', () => {
482
482
  vi.spyOn(mockClient.models, 'generateContent').mockResolvedValue(mockContentResponse as any);
483
483
 
484
484
  const payload: CreateImagePayload = {
485
- model: 'gemini-2.5-flash-image-preview:image',
485
+ model: 'gemini-2.5-flash-image:image',
486
486
  params: {
487
487
  prompt: 'Change the background to blue sky',
488
488
  imageUrl: 'https://example.com/image.jpg',
@@ -511,7 +511,7 @@ describe('createGoogleImage', () => {
511
511
  ],
512
512
  },
513
513
  ],
514
- model: 'gemini-2.5-flash-image-preview',
514
+ model: 'gemini-2.5-flash-image',
515
515
  config: {
516
516
  responseModalities: ['Image'],
517
517
  },
@@ -545,7 +545,7 @@ describe('createGoogleImage', () => {
545
545
  vi.spyOn(mockClient.models, 'generateContent').mockResolvedValue(mockContentResponse as any);
546
546
 
547
547
  const payload: CreateImagePayload = {
548
- model: 'gemini-2.5-flash-image-preview:image',
548
+ model: 'gemini-2.5-flash-image:image',
549
549
  params: {
550
550
  prompt: 'Generate a colorful abstract pattern',
551
551
  imageUrl: null,
@@ -563,7 +563,7 @@ describe('createGoogleImage', () => {
563
563
  parts: [{ text: 'Generate a colorful abstract pattern' }],
564
564
  },
565
565
  ],
566
- model: 'gemini-2.5-flash-image-preview',
566
+ model: 'gemini-2.5-flash-image',
567
567
  config: {
568
568
  responseModalities: ['Image'],
569
569
  },
@@ -594,7 +594,7 @@ describe('createGoogleImage', () => {
594
594
  );
595
595
 
596
596
  const payload: CreateImagePayload = {
597
- model: 'gemini-2.5-flash-image-preview:image',
597
+ model: 'gemini-2.5-flash-image:image',
598
598
  params: {
599
599
  prompt: 'Create inappropriate content',
600
600
  },
@@ -619,7 +619,7 @@ describe('createGoogleImage', () => {
619
619
  );
620
620
 
621
621
  const payload: CreateImagePayload = {
622
- model: 'gemini-2.5-flash-image-preview:image',
622
+ model: 'gemini-2.5-flash-image:image',
623
623
  params: {
624
624
  prompt: 'Generate an image',
625
625
  },
@@ -637,7 +637,7 @@ describe('createGoogleImage', () => {
637
637
  it('should throw error for unsupported image URL format', async () => {
638
638
  // Arrange
639
639
  const payload: CreateImagePayload = {
640
- model: 'gemini-2.5-flash-image-preview:image',
640
+ model: 'gemini-2.5-flash-image:image',
641
641
  params: {
642
642
  prompt: 'Edit this image',
643
643
  imageUrl: 'ftp://example.com/image.jpg',
@@ -34,6 +34,7 @@ beforeEach(() => {
34
34
  });
35
35
 
36
36
  afterEach(() => {
37
+ vi.unstubAllGlobals();
37
38
  vi.clearAllMocks();
38
39
  });
39
40
 
@@ -333,6 +334,107 @@ describe('LobeOpenRouterAI - custom features', () => {
333
334
  expect.anything(),
334
335
  );
335
336
  });
337
+
338
+ it('should map thinkingLevel to reasoning effort', async () => {
339
+ await instance.chat({
340
+ messages: [{ content: 'Think level', role: 'user' }],
341
+ model: 'openai/gpt-4',
342
+ thinkingLevel: 'medium',
343
+ } as any);
344
+
345
+ expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
346
+ expect.objectContaining({ reasoning: { effort: 'medium' } }),
347
+ expect.anything(),
348
+ );
349
+ });
350
+ });
351
+
352
+ describe('models mapping', () => {
353
+ it('should map extendParams for gpt-5.x reasoning and verbosity', async () => {
354
+ const mockModels = [
355
+ {
356
+ architecture: { input_modalities: ['text'] },
357
+ created: 1_700_000_000,
358
+ description: 'Test model',
359
+ id: 'openai/gpt-5.2-mini',
360
+ name: 'openai/gpt-5.2-mini',
361
+ pricing: { completion: '0.00001', prompt: '0.00001' },
362
+ supported_parameters: ['reasoning'],
363
+ top_provider: { context_length: 8192, max_completion_tokens: 1024 },
364
+ },
365
+ {
366
+ architecture: { input_modalities: ['text'] },
367
+ created: 1_700_000_000,
368
+ description: 'Test model',
369
+ id: 'openai/gpt-5.1-mini',
370
+ name: 'openai/gpt-5.1-mini',
371
+ pricing: { completion: '0.00001', prompt: '0.00001' },
372
+ supported_parameters: ['reasoning'],
373
+ top_provider: { context_length: 8192, max_completion_tokens: 1024 },
374
+ },
375
+ ];
376
+
377
+ vi.stubGlobal(
378
+ 'fetch',
379
+ vi.fn().mockResolvedValue({
380
+ ok: true,
381
+ json: async () => ({ data: mockModels }),
382
+ } as any),
383
+ );
384
+
385
+ const models = await params.models();
386
+ const gpt52 = models.find((m) => m.id === 'openai/gpt-5.2-mini');
387
+ const gpt51 = models.find((m) => m.id === 'openai/gpt-5.1-mini');
388
+
389
+ expect(gpt52?.settings?.extendParams).toEqual(
390
+ expect.arrayContaining(['gpt5_2ReasoningEffort', 'textVerbosity']),
391
+ );
392
+ expect(gpt51?.settings?.extendParams).toEqual(
393
+ expect.arrayContaining(['gpt5_1ReasoningEffort', 'textVerbosity']),
394
+ );
395
+ });
396
+
397
+ it('should map thinkingLevel for gemini-3 flash/pro reasoning', async () => {
398
+ const mockModels = [
399
+ {
400
+ architecture: { input_modalities: ['text'] },
401
+ created: 1_700_000_000,
402
+ description: 'Test model',
403
+ id: 'google/gemini-3-pro',
404
+ name: 'google/gemini-3-pro',
405
+ pricing: { completion: '0.00001', prompt: '0.00001' },
406
+ supported_parameters: ['reasoning'],
407
+ top_provider: { context_length: 8192, max_completion_tokens: 1024 },
408
+ },
409
+ {
410
+ architecture: { input_modalities: ['text'] },
411
+ created: 1_700_000_000,
412
+ description: 'Test model',
413
+ id: 'google/gemini-3-flash',
414
+ name: 'google/gemini-3-flash',
415
+ pricing: { completion: '0.00001', prompt: '0.00001' },
416
+ supported_parameters: ['reasoning'],
417
+ top_provider: { context_length: 8192, max_completion_tokens: 1024 },
418
+ },
419
+ ];
420
+
421
+ vi.stubGlobal(
422
+ 'fetch',
423
+ vi.fn().mockResolvedValue({
424
+ ok: true,
425
+ json: async () => ({ data: mockModels }),
426
+ } as any),
427
+ );
428
+
429
+ const models = await params.models();
430
+ const geminiPro = models.find((m) => m.id === 'google/gemini-3-pro');
431
+ const geminiFlash = models.find((m) => m.id === 'google/gemini-3-flash');
432
+
433
+ expect(geminiPro?.settings?.extendParams).toEqual(expect.arrayContaining(['thinkingLevel2']));
434
+ expect(geminiFlash?.settings?.extendParams).toEqual(
435
+ expect.arrayContaining(['thinkingLevel']),
436
+ );
437
+ });
336
438
  });
337
439
 
338
440
  describe('models', () => {
@@ -17,11 +17,11 @@ export const params = {
17
17
  chatCompletion: {
18
18
  handlePayload: (payload) => {
19
19
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
20
- const { reasoning_effort, thinking, reasoning: _reasoning, ...rest } = payload;
20
+ const { reasoning_effort, thinking, reasoning: _reasoning, thinkingLevel, ...rest } = payload;
21
21
 
22
22
  let reasoning: OpenRouterReasoning | undefined;
23
23
 
24
- if (thinking?.type || thinking?.budget_tokens !== undefined || reasoning_effort) {
24
+ if (thinking?.type || thinking?.budget_tokens !== undefined || reasoning_effort || thinkingLevel) {
25
25
  if (thinking?.type === 'disabled') {
26
26
  reasoning = { enabled: false };
27
27
  } else if (thinking?.budget_tokens !== undefined) {
@@ -31,6 +31,9 @@ export const params = {
31
31
  } else if (reasoning_effort) {
32
32
  reasoning = { effort: reasoning_effort };
33
33
  }
34
+ else if (thinkingLevel) {
35
+ reasoning = { effort: thinkingLevel };
36
+ }
34
37
  }
35
38
 
36
39
  return {
@@ -126,11 +129,14 @@ export const params = {
126
129
  if (model.description && model.description.includes('`reasoning` `enabled`')) {
127
130
  extendParams.push('enableReasoning');
128
131
  }
129
- if (hasReasoning && model.id.includes('gpt-5')) {
130
- extendParams.push('gpt5ReasoningEffort');
131
- }
132
- if (hasReasoning && model.id.includes('openai') && !model.id.includes('gpt-5')) {
133
- extendParams.push('reasoningEffort');
132
+ if (hasReasoning && model.id.includes('gpt-5.2')) {
133
+ extendParams.push('gpt5_2ReasoningEffort', 'textVerbosity');
134
+ } else if (hasReasoning && model.id.includes('gpt-5.1')) {
135
+ extendParams.push('gpt5_1ReasoningEffort', 'textVerbosity');
136
+ } else if (hasReasoning && model.id.includes('gpt-5')) {
137
+ extendParams.push('gpt5ReasoningEffort', 'textVerbosity');
138
+ } else if (hasReasoning && model.id.includes('openai')) {
139
+ extendParams.push('reasoningEffort', 'textVerbosity');
134
140
  }
135
141
  if (hasReasoning && model.id.includes('claude')) {
136
142
  extendParams.push('enableReasoning', 'reasoningBudgetToken');
@@ -141,6 +147,12 @@ export const params = {
141
147
  if (hasReasoning && model.id.includes('gemini-2.5')) {
142
148
  extendParams.push('reasoningBudgetToken');
143
149
  }
150
+ if (hasReasoning && model.id.includes('gemini-3-pro')) {
151
+ extendParams.push('thinkingLevel2');
152
+ }
153
+ if (hasReasoning && model.id.includes('gemini-3-flash')) {
154
+ extendParams.push('thinkingLevel');
155
+ }
144
156
  return extendParams.length > 0 ? { settings: { extendParams } } : {};
145
157
  })(),
146
158
  };
@@ -245,6 +245,53 @@ describe('LobeVercelAIGatewayAI - custom features', () => {
245
245
  expect(Array.isArray(model?.pricing?.units)).toBe(true);
246
246
  });
247
247
 
248
+ it('should map extendParams for gpt-5.x reasoning models', async () => {
249
+ const mockModelData: VercelAIGatewayModelCard[] = [
250
+ {
251
+ id: 'openai/gpt-5.2-mini',
252
+ name: 'GPT-5.2 Mini',
253
+ pricing: { input: 0.000_003, output: 0.000_015 },
254
+ tags: ['reasoning'],
255
+ type: 'chat',
256
+ },
257
+ {
258
+ id: 'openai/gpt-5.1-mini',
259
+ name: 'GPT-5.1 Mini',
260
+ pricing: { input: 0.000_003, output: 0.000_015 },
261
+ tags: ['reasoning'],
262
+ type: 'chat',
263
+ },
264
+ {
265
+ id: 'openai/gpt-5-mini',
266
+ name: 'GPT-5 Mini',
267
+ pricing: { input: 0.000_003, output: 0.000_015 },
268
+ tags: ['reasoning'],
269
+ type: 'chat',
270
+ },
271
+ ];
272
+
273
+ const mockClient = {
274
+ models: {
275
+ list: vi.fn().mockResolvedValue({ data: mockModelData }),
276
+ },
277
+ };
278
+
279
+ const models = await params.models({ client: mockClient as any });
280
+ const gpt52 = models.find((m) => m.id === 'openai/gpt-5.2-mini');
281
+ const gpt51 = models.find((m) => m.id === 'openai/gpt-5.1-mini');
282
+ const gpt5 = models.find((m) => m.id === 'openai/gpt-5-mini');
283
+
284
+ expect(gpt52?.settings?.extendParams).toEqual(
285
+ expect.arrayContaining(['gpt5_2ReasoningEffort', 'textVerbosity']),
286
+ );
287
+ expect(gpt51?.settings?.extendParams).toEqual(
288
+ expect.arrayContaining(['gpt5_1ReasoningEffort', 'textVerbosity']),
289
+ );
290
+ expect(gpt5?.settings?.extendParams).toEqual(
291
+ expect.arrayContaining(['gpt5ReasoningEffort', 'textVerbosity']),
292
+ );
293
+ });
294
+
248
295
  it('should handle models with missing pricing', async () => {
249
296
  const mockModelData: VercelAIGatewayModelCard[] = [
250
297
  {
@@ -125,9 +125,15 @@ export const params = {
125
125
  // Merge all applicable extendParams for settings
126
126
  ...(() => {
127
127
  const extendParams: string[] = [];
128
- if (tags.includes('reasoning') && m.id.includes('gpt-5')) {
128
+ if (tags.includes('reasoning') && m.id.includes('gpt-5') && !m.id.includes('gpt-5.1') && !m.id.includes('gpt-5.2')) {
129
129
  extendParams.push('gpt5ReasoningEffort', 'textVerbosity');
130
130
  }
131
+ if (tags.includes('reasoning') && m.id.includes('gpt-5.1') && !m.id.includes('gpt-5.2')) {
132
+ extendParams.push('gpt5_1ReasoningEffort', 'textVerbosity');
133
+ }
134
+ if (tags.includes('reasoning') && m.id.includes('gpt-5.2')) {
135
+ extendParams.push('gpt5_2ReasoningEffort', 'textVerbosity');
136
+ }
131
137
  if (tags.includes('reasoning') && m.id.includes('openai') && !m.id.includes('gpt-5')) {
132
138
  extendParams.push('reasoningEffort', 'textVerbosity');
133
139
  }
@@ -160,6 +160,8 @@ export default {
160
160
  'models.output': 'Output',
161
161
  'models.title': 'Models',
162
162
  'payDiffPrice': 'Pay Difference',
163
+ 'payDiffPriceApprox': 'Approx.',
164
+ 'payDiffPriceTip': 'Actual amount subject to payment page',
163
165
  'payment.error.actions.billing': 'Billing Management',
164
166
  'payment.error.actions.home': 'Back to Home',
165
167
  'payment.error.desc':