@lobehub/chat 1.92.2 → 1.92.3

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/CHANGELOG.md CHANGED
@@ -2,6 +2,39 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 1.92.3](https://github.com/lobehub/lobe-chat/compare/v1.92.2...v1.92.3)
6
+
7
+ <sup>Released on **2025-06-08**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: Fix client s3 getObject throw error.
12
+
13
+ #### 💄 Styles
14
+
15
+ - **misc**: Support OpenRouter Claude 4 reasoning.
16
+
17
+ <br/>
18
+
19
+ <details>
20
+ <summary><kbd>Improvements and Fixes</kbd></summary>
21
+
22
+ #### What's fixed
23
+
24
+ - **misc**: Fix client s3 getObject throw error, closes [#8009](https://github.com/lobehub/lobe-chat/issues/8009) ([b91ca8c](https://github.com/lobehub/lobe-chat/commit/b91ca8c))
25
+
26
+ #### Styles
27
+
28
+ - **misc**: Support OpenRouter Claude 4 reasoning, closes [#8087](https://github.com/lobehub/lobe-chat/issues/8087) ([039be1d](https://github.com/lobehub/lobe-chat/commit/039be1d))
29
+
30
+ </details>
31
+
32
+ <div align="right">
33
+
34
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
35
+
36
+ </div>
37
+
5
38
  ### [Version 1.92.2](https://github.com/lobehub/lobe-chat/compare/v1.92.1...v1.92.2)
6
39
 
7
40
  <sup>Released on **2025-06-07**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,16 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "fixes": [
5
+ "Fix client s3 getObject throw error."
6
+ ],
7
+ "improvements": [
8
+ "Support OpenRouter Claude 4 reasoning."
9
+ ]
10
+ },
11
+ "date": "2025-06-08",
12
+ "version": "1.92.3"
13
+ },
2
14
  {
3
15
  "children": {
4
16
  "improvements": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.92.2",
3
+ "version": "1.92.3",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -121,7 +121,7 @@
121
121
  "dependencies": {
122
122
  "@ant-design/icons": "^5.6.1",
123
123
  "@ant-design/pro-components": "^2.8.7",
124
- "@anthropic-ai/sdk": "^0.51.0",
124
+ "@anthropic-ai/sdk": "^0.53.0",
125
125
  "@auth/core": "^0.38.0",
126
126
  "@aws-sdk/client-bedrock-runtime": "^3.821.0",
127
127
  "@aws-sdk/client-s3": "^3.821.0",
@@ -660,6 +660,50 @@ const openrouterChatModels: AIChatModelCard[] = [
660
660
  },
661
661
  type: 'chat',
662
662
  },
663
+ {
664
+ abilities: {
665
+ functionCall: true,
666
+ reasoning: true,
667
+ vision: true,
668
+ },
669
+ contextWindowTokens: 200_000,
670
+ description:
671
+ 'Claude Sonnet 4 可以产生近乎即时的响应或延长的逐步思考,用户可以清晰地看到这些过程。API 用户还可以对模型思考的时间进行细致的控制',
672
+ displayName: 'Claude Sonnet 4',
673
+ id: 'anthropic/claude-sonnet-4',
674
+ maxOutput: 64_000,
675
+ pricing: {
676
+ input: 3,
677
+ output: 15,
678
+ },
679
+ releasedAt: '2025-05-23',
680
+ settings: {
681
+ extendParams: ['enableReasoning', 'reasoningBudgetToken'],
682
+ },
683
+ type: 'chat',
684
+ },
685
+ {
686
+ abilities: {
687
+ functionCall: true,
688
+ reasoning: true,
689
+ vision: true,
690
+ },
691
+ contextWindowTokens: 200_000,
692
+ description:
693
+ 'Claude Opus 4 是 Anthropic 用于处理高度复杂任务的最强大模型。它在性能、智能、流畅性和理解力方面表现卓越。',
694
+ displayName: 'Claude Opus 4',
695
+ id: 'anthropic/claude-opus-4',
696
+ maxOutput: 32_000,
697
+ pricing: {
698
+ input: 15,
699
+ output: 75,
700
+ },
701
+ releasedAt: '2025-05-23',
702
+ settings: {
703
+ extendParams: ['enableReasoning', 'reasoningBudgetToken'],
704
+ },
705
+ type: 'chat',
706
+ },
663
707
  {
664
708
  abilities: {
665
709
  functionCall: true,
@@ -1,3 +1,4 @@
1
+ import OpenRouterModels from '@/config/aiModels/openrouter';
1
2
  import type { ChatModelCard } from '@/types/llm';
2
3
 
3
4
  import { ModelProvider } from '../types';
@@ -14,12 +15,27 @@ export const LobeOpenRouterAI = createOpenAICompatibleRuntime({
14
15
  baseURL: 'https://openrouter.ai/api/v1',
15
16
  chatCompletion: {
16
17
  handlePayload: (payload) => {
17
- const { thinking } = payload;
18
+ const { thinking, model, max_tokens } = payload;
18
19
 
19
20
  let reasoning: OpenRouterReasoning = {};
21
+
20
22
  if (thinking?.type === 'enabled') {
23
+ const modelConfig = OpenRouterModels.find((m) => m.id === model);
24
+ const defaultMaxOutput = modelConfig?.maxOutput;
25
+
26
+ // 配置优先级:用户设置 > 模型配置 > 硬编码默认值
27
+ const getMaxTokens = () => {
28
+ if (max_tokens) return max_tokens;
29
+ if (defaultMaxOutput) return defaultMaxOutput;
30
+ return undefined;
31
+ };
32
+
33
+ const maxTokens = getMaxTokens() || 32_000; // Claude Opus 4 has minimum maxOutput
34
+
21
35
  reasoning = {
22
- max_tokens: thinking.budget_tokens,
36
+ max_tokens: thinking?.budget_tokens
37
+ ? Math.min(thinking.budget_tokens, maxTokens - 1)
38
+ : 1024,
23
39
  };
24
40
  }
25
41
 
@@ -43,7 +59,7 @@ export const LobeOpenRouterAI = createOpenAICompatibleRuntime({
43
59
  models: async ({ client }) => {
44
60
  const modelsPage = (await client.models.list()) as any;
45
61
  const modelList: OpenRouterModelCard[] = modelsPage.data;
46
-
62
+
47
63
  const modelsExtraInfo: OpenRouterModelExtraInfo[] = [];
48
64
  try {
49
65
  const response = await fetch('https://openrouter.ai/api/frontend/models');
@@ -54,49 +70,45 @@ export const LobeOpenRouterAI = createOpenAICompatibleRuntime({
54
70
  } catch (error) {
55
71
  console.error('Failed to fetch OpenRouter frontend models:', error);
56
72
  }
57
-
73
+
58
74
  // 解析模型能力
59
75
  const baseModels = await processMultiProviderModelList(modelList);
60
-
76
+
61
77
  // 合并 OpenRouter 获取的模型信息
62
- return baseModels.map((baseModel) => {
63
- const model = modelList.find(m => m.id === baseModel.id);
64
- const extraInfo = modelsExtraInfo.find(
65
- (m) => m.slug.toLowerCase() === baseModel.id.toLowerCase(),
66
- );
67
-
68
- if (!model) return baseModel;
69
-
70
- return {
71
- ...baseModel,
72
- contextWindowTokens: model.context_length,
73
- description: model.description,
74
- displayName: model.name,
75
- functionCall:
76
- baseModel.functionCall ||
77
- model.description.includes('function calling') ||
78
- model.description.includes('tools') ||
79
- extraInfo?.endpoint?.supports_tool_parameters ||
80
- false,
81
- maxTokens:
82
- typeof model.top_provider.max_completion_tokens === 'number'
83
- ? model.top_provider.max_completion_tokens
84
- : undefined,
85
- pricing: {
86
- input: formatPrice(model.pricing.prompt),
87
- output: formatPrice(model.pricing.completion),
88
- },
89
- reasoning:
90
- baseModel.reasoning ||
91
- extraInfo?.endpoint?.supports_reasoning ||
92
- false,
93
- releasedAt: new Date(model.created * 1000).toISOString().split('T')[0],
94
- vision:
95
- baseModel.vision ||
96
- model.architecture.modality.includes('image') ||
97
- false,
98
- };
99
- }).filter(Boolean) as ChatModelCard[];
78
+ return baseModels
79
+ .map((baseModel) => {
80
+ const model = modelList.find((m) => m.id === baseModel.id);
81
+ const extraInfo = modelsExtraInfo.find(
82
+ (m) => m.slug.toLowerCase() === baseModel.id.toLowerCase(),
83
+ );
84
+
85
+ if (!model) return baseModel;
86
+
87
+ return {
88
+ ...baseModel,
89
+ contextWindowTokens: model.context_length,
90
+ description: model.description,
91
+ displayName: model.name,
92
+ functionCall:
93
+ baseModel.functionCall ||
94
+ model.description.includes('function calling') ||
95
+ model.description.includes('tools') ||
96
+ extraInfo?.endpoint?.supports_tool_parameters ||
97
+ false,
98
+ maxTokens:
99
+ typeof model.top_provider.max_completion_tokens === 'number'
100
+ ? model.top_provider.max_completion_tokens
101
+ : undefined,
102
+ pricing: {
103
+ input: formatPrice(model.pricing.prompt),
104
+ output: formatPrice(model.pricing.completion),
105
+ },
106
+ reasoning: baseModel.reasoning || extraInfo?.endpoint?.supports_reasoning || false,
107
+ releasedAt: new Date(model.created * 1000).toISOString().split('T')[0],
108
+ vision: baseModel.vision || model.architecture.modality.includes('image') || false,
109
+ };
110
+ })
111
+ .filter(Boolean) as ChatModelCard[];
100
112
  },
101
113
  provider: ModelProvider.OpenRouter,
102
114
  });
@@ -84,14 +84,14 @@ describe('BrowserS3Storage', () => {
84
84
  expect(result).toBeUndefined();
85
85
  });
86
86
 
87
- it('should throw error when get operation fails', async () => {
88
- const mockError = new Error('Storage error');
89
- (get as any).mockRejectedValue(mockError);
90
-
91
- await expect(storage.getObject('test-key')).rejects.toThrow(
92
- 'Failed to get object (key=test-key): Storage error',
93
- );
94
- });
87
+ // it('should throw error when get operation fails', async () => {
88
+ // const mockError = new Error('Storage error');
89
+ // (get as any).mockRejectedValue(mockError);
90
+ //
91
+ // await expect(storage.getObject('test-key')).rejects.toThrow(
92
+ // 'Failed to get object (key=test-key): Storage error',
93
+ // );
94
+ // });
95
95
  });
96
96
 
97
97
  describe('deleteObject', () => {
@@ -38,7 +38,8 @@ export class BrowserS3Storage {
38
38
 
39
39
  return new File([res.data], res!.name, { type: res?.type });
40
40
  } catch (e) {
41
- throw new Error(`Failed to get object (key=${key}): ${(e as Error).message}`);
41
+ console.log(`Failed to get object (key=${key}):`, e);
42
+ return undefined;
42
43
  }
43
44
  };
44
45