@lobehub/chat 1.136.13 → 1.137.1

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 (190) hide show
  1. package/.cursor/rules/add-setting-env.mdc +175 -0
  2. package/.cursor/rules/db-migrations.mdc +25 -0
  3. package/.env.example +7 -0
  4. package/CHANGELOG.md +50 -0
  5. package/Dockerfile +3 -2
  6. package/Dockerfile.database +15 -3
  7. package/Dockerfile.pglite +3 -2
  8. package/changelog/v1.json +18 -0
  9. package/docs/development/database-schema.dbml +1 -0
  10. package/docs/self-hosting/advanced/feature-flags.mdx +25 -15
  11. package/docs/self-hosting/advanced/feature-flags.zh-CN.mdx +25 -15
  12. package/docs/self-hosting/environment-variables/basic.mdx +12 -0
  13. package/docs/self-hosting/environment-variables/basic.zh-CN.mdx +12 -0
  14. package/locales/ar/setting.json +8 -0
  15. package/locales/bg-BG/setting.json +8 -0
  16. package/locales/de-DE/setting.json +8 -0
  17. package/locales/en-US/setting.json +8 -0
  18. package/locales/es-ES/setting.json +8 -0
  19. package/locales/fa-IR/setting.json +8 -0
  20. package/locales/fr-FR/setting.json +8 -0
  21. package/locales/it-IT/setting.json +8 -0
  22. package/locales/ja-JP/setting.json +8 -0
  23. package/locales/ko-KR/setting.json +8 -0
  24. package/locales/nl-NL/setting.json +8 -0
  25. package/locales/pl-PL/setting.json +8 -0
  26. package/locales/pt-BR/setting.json +8 -0
  27. package/locales/ru-RU/setting.json +8 -0
  28. package/locales/tr-TR/setting.json +8 -0
  29. package/locales/vi-VN/setting.json +8 -0
  30. package/locales/zh-CN/setting.json +8 -0
  31. package/locales/zh-TW/setting.json +8 -0
  32. package/package.json +1 -1
  33. package/packages/agent-runtime/examples/tools-calling.ts +4 -3
  34. package/packages/agent-runtime/src/core/__tests__/runtime.test.ts +559 -29
  35. package/packages/agent-runtime/src/core/runtime.ts +171 -43
  36. package/packages/agent-runtime/src/types/instruction.ts +32 -6
  37. package/packages/agent-runtime/src/types/runtime.ts +2 -2
  38. package/packages/agent-runtime/src/types/state.ts +1 -8
  39. package/packages/agent-runtime/vitest.config.mts +14 -0
  40. package/packages/const/src/settings/image.ts +8 -0
  41. package/packages/const/src/settings/index.ts +3 -0
  42. package/packages/context-engine/src/__tests__/pipeline.test.ts +485 -0
  43. package/packages/context-engine/src/base/__tests__/BaseProcessor.test.ts +381 -0
  44. package/packages/context-engine/src/base/__tests__/BaseProvider.test.ts +392 -0
  45. package/packages/context-engine/src/processors/__tests__/MessageCleanup.test.ts +346 -0
  46. package/packages/context-engine/src/processors/__tests__/ToolCall.test.ts +552 -0
  47. package/packages/database/migrations/0038_add_image_user_settings.sql +1 -0
  48. package/packages/database/migrations/meta/0038_snapshot.json +7580 -0
  49. package/packages/database/migrations/meta/_journal.json +7 -0
  50. package/packages/database/src/core/migrations.json +6 -0
  51. package/packages/database/src/models/user.ts +3 -1
  52. package/packages/database/src/schemas/user.ts +1 -0
  53. package/packages/file-loaders/src/loaders/docx/index.test.ts +0 -1
  54. package/packages/file-loaders/src/loaders/excel/__snapshots__/index.test.ts.snap +30 -0
  55. package/packages/file-loaders/src/loaders/excel/index.test.ts +8 -0
  56. package/packages/file-loaders/src/loaders/pptx/index.test.ts +25 -0
  57. package/packages/file-loaders/src/utils/parser-utils.test.ts +155 -0
  58. package/packages/file-loaders/vitest.config.mts +8 -0
  59. package/packages/model-runtime/CLAUDE.md +5 -0
  60. package/packages/model-runtime/docs/test-coverage.md +706 -0
  61. package/packages/model-runtime/src/core/ModelRuntime.test.ts +231 -0
  62. package/packages/model-runtime/src/core/RouterRuntime/createRuntime.ts +1 -1
  63. package/packages/model-runtime/src/core/openaiCompatibleFactory/createImage.test.ts +799 -0
  64. package/packages/model-runtime/src/core/openaiCompatibleFactory/index.test.ts +188 -4
  65. package/packages/model-runtime/src/core/openaiCompatibleFactory/index.ts +41 -10
  66. package/packages/model-runtime/src/core/streams/openai/__snapshots__/responsesStream.test.ts.snap +439 -0
  67. package/packages/model-runtime/src/core/streams/openai/openai.test.ts +789 -0
  68. package/packages/model-runtime/src/core/streams/openai/responsesStream.test.ts +551 -0
  69. package/packages/model-runtime/src/core/usageConverters/utils/computeChatCost.test.ts +230 -0
  70. package/packages/model-runtime/src/core/usageConverters/utils/computeImageCost.test.ts +334 -37
  71. package/packages/model-runtime/src/providerTestUtils.ts +148 -145
  72. package/packages/model-runtime/src/providers/ai302/index.test.ts +60 -0
  73. package/packages/model-runtime/src/providers/ai302/index.ts +9 -4
  74. package/packages/model-runtime/src/providers/ai360/index.test.ts +1213 -1
  75. package/packages/model-runtime/src/providers/ai360/index.ts +9 -4
  76. package/packages/model-runtime/src/providers/aihubmix/index.test.ts +73 -0
  77. package/packages/model-runtime/src/providers/aihubmix/index.ts +6 -9
  78. package/packages/model-runtime/src/providers/akashchat/index.test.ts +433 -3
  79. package/packages/model-runtime/src/providers/akashchat/index.ts +12 -7
  80. package/packages/model-runtime/src/providers/anthropic/generateObject.test.ts +183 -29
  81. package/packages/model-runtime/src/providers/anthropic/generateObject.ts +40 -24
  82. package/packages/model-runtime/src/providers/azureai/index.test.ts +102 -0
  83. package/packages/model-runtime/src/providers/baichuan/index.test.ts +416 -26
  84. package/packages/model-runtime/src/providers/baichuan/index.ts +23 -20
  85. package/packages/model-runtime/src/providers/bedrock/index.test.ts +420 -2
  86. package/packages/model-runtime/src/providers/cerebras/index.test.ts +465 -0
  87. package/packages/model-runtime/src/providers/cerebras/index.ts +8 -3
  88. package/packages/model-runtime/src/providers/cohere/index.test.ts +1074 -1
  89. package/packages/model-runtime/src/providers/cohere/index.ts +8 -3
  90. package/packages/model-runtime/src/providers/cometapi/index.test.ts +439 -3
  91. package/packages/model-runtime/src/providers/cometapi/index.ts +8 -3
  92. package/packages/model-runtime/src/providers/deepseek/index.test.ts +116 -1
  93. package/packages/model-runtime/src/providers/deepseek/index.ts +8 -3
  94. package/packages/model-runtime/src/providers/fireworksai/index.test.ts +264 -3
  95. package/packages/model-runtime/src/providers/fireworksai/index.ts +8 -3
  96. package/packages/model-runtime/src/providers/giteeai/index.test.ts +325 -3
  97. package/packages/model-runtime/src/providers/giteeai/index.ts +23 -6
  98. package/packages/model-runtime/src/providers/github/index.test.ts +532 -3
  99. package/packages/model-runtime/src/providers/github/index.ts +8 -3
  100. package/packages/model-runtime/src/providers/groq/index.test.ts +344 -31
  101. package/packages/model-runtime/src/providers/groq/index.ts +8 -3
  102. package/packages/model-runtime/src/providers/higress/index.test.ts +142 -0
  103. package/packages/model-runtime/src/providers/higress/index.ts +8 -3
  104. package/packages/model-runtime/src/providers/huggingface/index.test.ts +612 -1
  105. package/packages/model-runtime/src/providers/huggingface/index.ts +9 -4
  106. package/packages/model-runtime/src/providers/hunyuan/index.test.ts +365 -1
  107. package/packages/model-runtime/src/providers/hunyuan/index.ts +9 -3
  108. package/packages/model-runtime/src/providers/infiniai/index.test.ts +71 -0
  109. package/packages/model-runtime/src/providers/internlm/index.test.ts +369 -2
  110. package/packages/model-runtime/src/providers/internlm/index.ts +10 -5
  111. package/packages/model-runtime/src/providers/jina/index.test.ts +164 -3
  112. package/packages/model-runtime/src/providers/jina/index.ts +8 -3
  113. package/packages/model-runtime/src/providers/lmstudio/index.test.ts +182 -3
  114. package/packages/model-runtime/src/providers/lmstudio/index.ts +8 -3
  115. package/packages/model-runtime/src/providers/mistral/index.test.ts +779 -27
  116. package/packages/model-runtime/src/providers/mistral/index.ts +8 -3
  117. package/packages/model-runtime/src/providers/modelscope/index.test.ts +232 -1
  118. package/packages/model-runtime/src/providers/modelscope/index.ts +8 -3
  119. package/packages/model-runtime/src/providers/moonshot/index.test.ts +489 -2
  120. package/packages/model-runtime/src/providers/moonshot/index.ts +8 -3
  121. package/packages/model-runtime/src/providers/nebius/index.test.ts +381 -3
  122. package/packages/model-runtime/src/providers/nebius/index.ts +8 -3
  123. package/packages/model-runtime/src/providers/newapi/index.test.ts +667 -3
  124. package/packages/model-runtime/src/providers/newapi/index.ts +6 -3
  125. package/packages/model-runtime/src/providers/nvidia/index.test.ts +168 -1
  126. package/packages/model-runtime/src/providers/nvidia/index.ts +12 -7
  127. package/packages/model-runtime/src/providers/ollama/index.test.ts +797 -1
  128. package/packages/model-runtime/src/providers/ollama/index.ts +8 -0
  129. package/packages/model-runtime/src/providers/ollamacloud/index.test.ts +411 -0
  130. package/packages/model-runtime/src/providers/ollamacloud/index.ts +8 -3
  131. package/packages/model-runtime/src/providers/openai/index.test.ts +171 -2
  132. package/packages/model-runtime/src/providers/openai/index.ts +8 -3
  133. package/packages/model-runtime/src/providers/openrouter/index.test.ts +1647 -95
  134. package/packages/model-runtime/src/providers/openrouter/index.ts +12 -7
  135. package/packages/model-runtime/src/providers/qiniu/index.test.ts +294 -1
  136. package/packages/model-runtime/src/providers/qiniu/index.ts +8 -3
  137. package/packages/model-runtime/src/providers/search1api/index.test.ts +1131 -11
  138. package/packages/model-runtime/src/providers/search1api/index.ts +10 -4
  139. package/packages/model-runtime/src/providers/sensenova/index.test.ts +1069 -1
  140. package/packages/model-runtime/src/providers/sensenova/index.ts +8 -3
  141. package/packages/model-runtime/src/providers/siliconcloud/index.test.ts +196 -0
  142. package/packages/model-runtime/src/providers/siliconcloud/index.ts +8 -3
  143. package/packages/model-runtime/src/providers/spark/index.test.ts +293 -1
  144. package/packages/model-runtime/src/providers/spark/index.ts +8 -3
  145. package/packages/model-runtime/src/providers/stepfun/index.test.ts +322 -3
  146. package/packages/model-runtime/src/providers/stepfun/index.ts +8 -3
  147. package/packages/model-runtime/src/providers/tencentcloud/index.test.ts +182 -3
  148. package/packages/model-runtime/src/providers/tencentcloud/index.ts +8 -3
  149. package/packages/model-runtime/src/providers/togetherai/index.test.ts +359 -4
  150. package/packages/model-runtime/src/providers/togetherai/index.ts +12 -5
  151. package/packages/model-runtime/src/providers/v0/index.test.ts +341 -0
  152. package/packages/model-runtime/src/providers/v0/index.ts +20 -6
  153. package/packages/model-runtime/src/providers/vercelaigateway/index.test.ts +710 -0
  154. package/packages/model-runtime/src/providers/vercelaigateway/index.ts +19 -13
  155. package/packages/model-runtime/src/providers/vllm/index.test.ts +45 -1
  156. package/packages/model-runtime/src/providers/volcengine/index.test.ts +75 -0
  157. package/packages/model-runtime/src/providers/wenxin/index.test.ts +144 -1
  158. package/packages/model-runtime/src/providers/wenxin/index.ts +8 -3
  159. package/packages/model-runtime/src/providers/xai/index.test.ts +105 -1
  160. package/packages/model-runtime/src/providers/xinference/index.test.ts +70 -1
  161. package/packages/model-runtime/src/providers/zeroone/index.test.ts +327 -3
  162. package/packages/model-runtime/src/providers/zeroone/index.ts +23 -6
  163. package/packages/model-runtime/src/providers/zhipu/index.test.ts +908 -236
  164. package/packages/model-runtime/src/providers/zhipu/index.ts +8 -3
  165. package/packages/model-runtime/src/types/structureOutput.ts +5 -1
  166. package/packages/model-runtime/vitest.config.mts +7 -1
  167. package/packages/types/src/aiChat.ts +20 -2
  168. package/packages/types/src/serverConfig.ts +7 -1
  169. package/packages/types/src/tool/index.ts +1 -0
  170. package/packages/types/src/tool/tool.ts +33 -0
  171. package/packages/types/src/user/settings/image.ts +3 -0
  172. package/packages/types/src/user/settings/index.ts +3 -0
  173. package/src/app/[variants]/(main)/settings/_layout/SettingsContent.tsx +3 -0
  174. package/src/app/[variants]/(main)/settings/hooks/useCategory.tsx +8 -3
  175. package/src/app/[variants]/(main)/settings/image/index.tsx +74 -0
  176. package/src/components/FormInput/FormSliderWithInput.tsx +40 -0
  177. package/src/components/FormInput/index.ts +1 -0
  178. package/src/envs/image.ts +27 -0
  179. package/src/features/Conversation/Messages/Assistant/index.tsx +1 -1
  180. package/src/features/Conversation/Messages/User/index.tsx +2 -2
  181. package/src/hooks/useFetchAiImageConfig.ts +12 -17
  182. package/src/locales/default/setting.ts +8 -0
  183. package/src/server/globalConfig/index.ts +5 -0
  184. package/src/server/routers/lambda/aiChat.ts +2 -0
  185. package/src/store/global/initialState.ts +1 -0
  186. package/src/store/image/slices/generationConfig/action.test.ts +17 -0
  187. package/src/store/image/slices/generationConfig/action.ts +18 -21
  188. package/src/store/image/slices/generationConfig/initialState.ts +3 -2
  189. package/src/store/user/slices/common/action.ts +1 -0
  190. package/src/store/user/slices/settings/selectors/settings.ts +3 -0
@@ -4,7 +4,7 @@ import { ModelProvider } from 'model-bank';
4
4
  import { Mock, afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
5
 
6
6
  import { testProvider } from '../../providerTestUtils';
7
- import { LobeBaichuanAI } from './index';
7
+ import { LobeBaichuanAI, params } from './index';
8
8
 
9
9
  testProvider({
10
10
  Runtime: LobeBaichuanAI,
@@ -12,11 +12,17 @@ testProvider({
12
12
  defaultBaseURL: 'https://api.baichuan-ai.com/v1',
13
13
  chatDebugEnv: 'DEBUG_BAICHUAN_CHAT_COMPLETION',
14
14
  chatModel: 'hunyuan-lite',
15
+ invalidErrorType: 'InvalidProviderAPIKey',
16
+ bizErrorType: 'ProviderBizError',
15
17
  test: {
16
18
  skipAPICall: true,
19
+ skipErrorHandle: true,
17
20
  },
18
21
  });
19
22
 
23
+ // Mock the console.error to avoid polluting test output
24
+ vi.spyOn(console, 'error').mockImplementation(() => {});
25
+
20
26
  let instance: LobeOpenAICompatibleRuntime;
21
27
 
22
28
  beforeEach(() => {
@@ -32,39 +38,423 @@ afterEach(() => {
32
38
  vi.clearAllMocks();
33
39
  });
34
40
 
35
- describe('specific LobeBaichuanAI tests', () => {
36
- it(`should call API with corresponding options`, async () => {
37
- // Arrange
38
- const mockStream = new ReadableStream();
39
- const mockResponse = Promise.resolve(mockStream);
40
-
41
- (instance['client'].chat.completions.create as Mock).mockResolvedValue(mockResponse);
41
+ describe('LobeBaichuanAI - custom features', () => {
42
+ describe('Debug Configuration', () => {
43
+ it('should disable debug by default', () => {
44
+ delete process.env.DEBUG_BAICHUAN_CHAT_COMPLETION;
45
+ const result = params.debug.chatCompletion();
46
+ expect(result).toBe(false);
47
+ });
42
48
 
43
- // Act
44
- const result = await instance.chat({
45
- max_tokens: 1024,
46
- messages: [{ content: 'Hello', role: 'user' }],
47
- model: 'open-mistral-7b',
48
- temperature: 0.7,
49
- stream: true,
50
- top_p: 1,
49
+ it('should enable debug when env is set', () => {
50
+ process.env.DEBUG_BAICHUAN_CHAT_COMPLETION = '1';
51
+ const result = params.debug.chatCompletion();
52
+ expect(result).toBe(true);
53
+ delete process.env.DEBUG_BAICHUAN_CHAT_COMPLETION;
51
54
  });
55
+ });
52
56
 
53
- // Assert
54
- expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
55
- {
57
+ describe('handlePayload - custom features', () => {
58
+ it('should call API with corresponding options', async () => {
59
+ // Arrange
60
+ const mockStream = new ReadableStream();
61
+ const mockResponse = Promise.resolve(mockStream);
62
+
63
+ (instance['client'].chat.completions.create as Mock).mockResolvedValue(mockResponse);
64
+
65
+ // Act
66
+ const result = await instance.chat({
56
67
  max_tokens: 1024,
57
68
  messages: [{ content: 'Hello', role: 'user' }],
58
69
  model: 'open-mistral-7b',
70
+ temperature: 0.7,
59
71
  stream: true,
60
- stream_options: {
61
- include_usage: true,
62
- },
63
- temperature: 0.35,
64
72
  top_p: 1,
73
+ });
74
+
75
+ // Assert
76
+ expect(instance['client'].chat.completions.create).toHaveBeenCalledWith(
77
+ {
78
+ max_tokens: 1024,
79
+ messages: [{ content: 'Hello', role: 'user' }],
80
+ model: 'open-mistral-7b',
81
+ stream: true,
82
+ stream_options: {
83
+ include_usage: true,
84
+ },
85
+ temperature: 0.35,
86
+ top_p: 1,
87
+ },
88
+ { headers: { Accept: '*/*' } },
89
+ );
90
+ expect(result).toBeInstanceOf(Response);
91
+ });
92
+
93
+ it('should add web_search tool when enabledSearch is true', async () => {
94
+ await instance.chat({
95
+ messages: [{ content: 'Hello', role: 'user' }],
96
+ model: 'Baichuan3-Turbo',
97
+ enabledSearch: true,
98
+ });
99
+
100
+ const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
101
+ expect(calledPayload.tools).toBeDefined();
102
+ expect(calledPayload.tools?.some((tool: any) => tool.type === 'web_search')).toBe(true);
103
+ expect(
104
+ calledPayload.tools?.find((tool: any) => tool.type === 'web_search')?.web_search?.enable,
105
+ ).toBe(true);
106
+ });
107
+
108
+ it('should use default search_mode performance_first', async () => {
109
+ delete process.env.BAICHUAN_SEARCH_MODE;
110
+
111
+ await instance.chat({
112
+ messages: [{ content: 'Hello', role: 'user' }],
113
+ model: 'Baichuan3-Turbo',
114
+ enabledSearch: true,
115
+ });
116
+
117
+ const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
118
+ const webSearchTool = calledPayload.tools?.find((tool: any) => tool.type === 'web_search');
119
+ expect(webSearchTool?.web_search?.search_mode).toBe('performance_first');
120
+ });
121
+
122
+ it('should use custom search_mode from env', async () => {
123
+ process.env.BAICHUAN_SEARCH_MODE = 'quality_first';
124
+
125
+ await instance.chat({
126
+ messages: [{ content: 'Hello', role: 'user' }],
127
+ model: 'Baichuan3-Turbo',
128
+ enabledSearch: true,
129
+ });
130
+
131
+ const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
132
+ const webSearchTool = calledPayload.tools?.find((tool: any) => tool.type === 'web_search');
133
+ expect(webSearchTool?.web_search?.search_mode).toBe('quality_first');
134
+
135
+ delete process.env.BAICHUAN_SEARCH_MODE;
136
+ });
137
+
138
+ it('should not add web_search tool when enabledSearch is false', async () => {
139
+ await instance.chat({
140
+ messages: [{ content: 'Hello', role: 'user' }],
141
+ model: 'Baichuan3-Turbo',
142
+ enabledSearch: false,
143
+ });
144
+
145
+ const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
146
+ expect(calledPayload.tools).toBeUndefined();
147
+ });
148
+
149
+ it('should not add web_search tool when enabledSearch is undefined', async () => {
150
+ await instance.chat({
151
+ messages: [{ content: 'Hello', role: 'user' }],
152
+ model: 'Baichuan3-Turbo',
153
+ });
154
+
155
+ const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
156
+ expect(calledPayload.tools).toBeUndefined();
157
+ });
158
+
159
+ it('should normalize temperature - 0 stays 0', async () => {
160
+ await instance.chat({
161
+ messages: [{ content: 'Hello', role: 'user' }],
162
+ model: 'Baichuan3-Turbo',
163
+ temperature: 0,
164
+ });
165
+
166
+ const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
167
+ expect(calledPayload.temperature).toBe(0);
168
+ });
169
+
170
+ it('should normalize temperature by halving it', async () => {
171
+ await instance.chat({
172
+ messages: [{ content: 'Hello', role: 'user' }],
173
+ model: 'Baichuan3-Turbo',
174
+ temperature: 0.5,
175
+ });
176
+
177
+ const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
178
+ expect(calledPayload.temperature).toBe(0.25);
179
+ });
180
+
181
+ it('should normalize temperature to 1 when it is 2', async () => {
182
+ await instance.chat({
183
+ messages: [{ content: 'Hello', role: 'user' }],
184
+ model: 'Baichuan3-Turbo',
185
+ temperature: 2,
186
+ });
187
+
188
+ const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
189
+ expect(calledPayload.temperature).toBe(1);
190
+ });
191
+
192
+ it('should normalize temperature with undefined value', async () => {
193
+ await instance.chat({
194
+ messages: [{ content: 'Hello', role: 'user' }],
195
+ model: 'Baichuan3-Turbo',
196
+ temperature: undefined,
197
+ });
198
+
199
+ const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
200
+ expect(calledPayload.temperature).toBeUndefined();
201
+ });
202
+
203
+ it('should merge tools with web_search when enabledSearch is true', async () => {
204
+ await instance.chat({
205
+ messages: [{ content: 'Hello', role: 'user' }],
206
+ model: 'Baichuan3-Turbo',
207
+ enabledSearch: true,
208
+ tools: [{ type: 'function', function: { name: 'existing_tool' } }],
209
+ });
210
+
211
+ const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
212
+ expect(calledPayload.tools).toHaveLength(2);
213
+ expect(calledPayload.tools?.some((tool: any) => tool.type === 'function')).toBe(true);
214
+ expect(calledPayload.tools?.some((tool: any) => tool.type === 'web_search')).toBe(true);
215
+ });
216
+
217
+ it('should preserve existing tools when enabledSearch is false', async () => {
218
+ await instance.chat({
219
+ messages: [{ content: 'Hello', role: 'user' }],
220
+ model: 'Baichuan3-Turbo',
221
+ enabledSearch: false,
222
+ tools: [
223
+ { type: 'function', function: { name: 'tool1' } },
224
+ { type: 'function', function: { name: 'tool2' } },
225
+ ],
226
+ });
227
+
228
+ const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
229
+ expect(calledPayload.tools).toHaveLength(2);
230
+ expect(calledPayload.tools?.every((tool: any) => tool.type === 'function')).toBe(true);
231
+ });
232
+
233
+ it('should preserve other payload properties', async () => {
234
+ await instance.chat({
235
+ messages: [{ content: 'Hello', role: 'user' }],
236
+ model: 'Baichuan3-Turbo',
237
+ temperature: 0.8,
238
+ max_tokens: 500,
239
+ top_p: 0.95,
240
+ });
241
+
242
+ const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];
243
+ expect(calledPayload.messages).toEqual([{ content: 'Hello', role: 'user' }]);
244
+ expect(calledPayload.model).toBe('Baichuan3-Turbo');
245
+ expect(calledPayload.temperature).toBe(0.4);
246
+ expect(calledPayload.max_tokens).toBe(500);
247
+ expect(calledPayload.top_p).toBe(0.95);
248
+ });
249
+ });
250
+
251
+ describe('models', () => {
252
+ const mockClient = {
253
+ models: {
254
+ list: vi.fn(),
65
255
  },
66
- { headers: { Accept: '*/*' } },
67
- );
68
- expect(result).toBeInstanceOf(Response);
256
+ };
257
+
258
+ beforeEach(() => {
259
+ vi.clearAllMocks();
260
+ });
261
+
262
+ it('should fetch and process models correctly', async () => {
263
+ mockClient.models.list.mockResolvedValue({
264
+ data: [
265
+ {
266
+ function_call: true,
267
+ max_input_length: 32768,
268
+ max_tokens: 4096,
269
+ model: 'Baichuan3-Turbo',
270
+ model_show_name: 'Baichuan3-Turbo',
271
+ },
272
+ {
273
+ function_call: false,
274
+ max_input_length: 4096,
275
+ max_tokens: 2048,
276
+ model: 'Baichuan2-Turbo',
277
+ model_show_name: 'Baichuan2-Turbo',
278
+ },
279
+ ],
280
+ });
281
+
282
+ const models = await params.models({ client: mockClient as any });
283
+
284
+ expect(models).toHaveLength(2);
285
+ expect(models[0]).toMatchObject({
286
+ id: 'Baichuan3-Turbo',
287
+ displayName: 'Baichuan3-Turbo',
288
+ contextWindowTokens: 32768,
289
+ maxOutput: 4096,
290
+ functionCall: true,
291
+ enabled: false,
292
+ reasoning: false,
293
+ vision: false,
294
+ });
295
+ expect(models[1]).toMatchObject({
296
+ id: 'Baichuan2-Turbo',
297
+ displayName: 'Baichuan2-Turbo',
298
+ contextWindowTokens: 4096,
299
+ maxOutput: 2048,
300
+ functionCall: false,
301
+ enabled: false,
302
+ reasoning: false,
303
+ vision: false,
304
+ });
305
+ });
306
+
307
+ it('should merge with known model list for enabled status and abilities', async () => {
308
+ mockClient.models.list.mockResolvedValue({
309
+ data: [
310
+ {
311
+ function_call: true,
312
+ max_input_length: 32768,
313
+ max_tokens: 4096,
314
+ model: 'Baichuan3-Turbo',
315
+ model_show_name: 'Baichuan3 Turbo',
316
+ },
317
+ ],
318
+ });
319
+
320
+ const models = await params.models({ client: mockClient as any });
321
+
322
+ expect(models).toHaveLength(1);
323
+ // Should have properties from API
324
+ expect(models[0].id).toBe('Baichuan3-Turbo');
325
+ expect(models[0].displayName).toBe('Baichuan3 Turbo');
326
+ expect(models[0].contextWindowTokens).toBe(32768);
327
+ expect(models[0].maxOutput).toBe(4096);
328
+ expect(models[0].functionCall).toBe(true);
329
+ });
330
+
331
+ it('should handle case-insensitive model matching', async () => {
332
+ mockClient.models.list.mockResolvedValue({
333
+ data: [
334
+ {
335
+ function_call: true,
336
+ max_input_length: 32768,
337
+ max_tokens: 4096,
338
+ model: 'BAICHUAN3-TURBO',
339
+ model_show_name: 'Baichuan3 Turbo Upper',
340
+ },
341
+ ],
342
+ });
343
+
344
+ const models = await params.models({ client: mockClient as any });
345
+
346
+ expect(models).toHaveLength(1);
347
+ expect(models[0].id).toBe('BAICHUAN3-TURBO');
348
+ // Should match with lowercase comparison
349
+ expect(models[0].displayName).toBe('Baichuan3 Turbo Upper');
350
+ });
351
+
352
+ it('should preserve abilities from known model list', async () => {
353
+ mockClient.models.list.mockResolvedValue({
354
+ data: [
355
+ {
356
+ function_call: true,
357
+ max_input_length: 32768,
358
+ max_tokens: 4096,
359
+ model: 'Baichuan3-Turbo',
360
+ model_show_name: 'Baichuan3-Turbo',
361
+ },
362
+ ],
363
+ });
364
+
365
+ const models = await params.models({ client: mockClient as any });
366
+
367
+ expect(models).toHaveLength(1);
368
+ // Models should have ability properties
369
+ expect(models[0]).toHaveProperty('functionCall');
370
+ expect(models[0]).toHaveProperty('vision');
371
+ expect(models[0]).toHaveProperty('reasoning');
372
+ expect(models[0]).toHaveProperty('enabled');
373
+ });
374
+
375
+ it('should handle empty model list', async () => {
376
+ mockClient.models.list.mockResolvedValue({
377
+ data: [],
378
+ });
379
+
380
+ const models = await params.models({ client: mockClient as any });
381
+
382
+ expect(models).toEqual([]);
383
+ });
384
+
385
+ it('should handle models not in known model list', async () => {
386
+ mockClient.models.list.mockResolvedValue({
387
+ data: [
388
+ {
389
+ function_call: false,
390
+ max_input_length: 8192,
391
+ max_tokens: 2048,
392
+ model: 'unknown-model-id',
393
+ model_show_name: 'Unknown Model',
394
+ },
395
+ ],
396
+ });
397
+
398
+ const models = await params.models({ client: mockClient as any });
399
+
400
+ expect(models).toHaveLength(1);
401
+ expect(models[0]).toMatchObject({
402
+ id: 'unknown-model-id',
403
+ displayName: 'Unknown Model',
404
+ contextWindowTokens: 8192,
405
+ maxOutput: 2048,
406
+ functionCall: false,
407
+ enabled: false,
408
+ reasoning: false,
409
+ vision: false,
410
+ });
411
+ });
412
+
413
+ it('should handle model with all capabilities', async () => {
414
+ mockClient.models.list.mockResolvedValue({
415
+ data: [
416
+ {
417
+ function_call: true,
418
+ max_input_length: 128000,
419
+ max_tokens: 8192,
420
+ model: 'Baichuan4-Pro',
421
+ model_show_name: 'Baichuan4 Pro',
422
+ },
423
+ ],
424
+ });
425
+
426
+ const models = await params.models({ client: mockClient as any });
427
+
428
+ expect(models).toHaveLength(1);
429
+ expect(models[0]).toMatchObject({
430
+ id: 'Baichuan4-Pro',
431
+ displayName: 'Baichuan4 Pro',
432
+ contextWindowTokens: 128000,
433
+ maxOutput: 8192,
434
+ functionCall: true,
435
+ });
436
+ });
437
+
438
+ it('should filter out null or undefined models', async () => {
439
+ mockClient.models.list.mockResolvedValue({
440
+ data: [
441
+ {
442
+ function_call: true,
443
+ max_input_length: 32768,
444
+ max_tokens: 4096,
445
+ model: 'Baichuan3-Turbo',
446
+ model_show_name: 'Baichuan3-Turbo',
447
+ },
448
+ null,
449
+ undefined,
450
+ ],
451
+ });
452
+
453
+ const models = await params.models({ client: mockClient as any });
454
+
455
+ // Should only have the valid model
456
+ expect(models).toHaveLength(1);
457
+ expect(models[0].id).toBe('Baichuan3-Turbo');
458
+ });
69
459
  });
70
460
  });
@@ -1,7 +1,10 @@
1
1
  import type { ChatModelCard } from '@lobechat/types';
2
2
  import { ModelProvider } from 'model-bank';
3
3
 
4
- import { createOpenAICompatibleRuntime } from '../../core/openaiCompatibleFactory';
4
+ import {
5
+ OpenAICompatibleFactoryOptions,
6
+ createOpenAICompatibleRuntime,
7
+ } from '../../core/openaiCompatibleFactory';
5
8
  import { resolveParameters } from '../../core/parameterResolver';
6
9
  import { ChatStreamPayload } from '../../types';
7
10
 
@@ -13,7 +16,7 @@ export interface BaichuanModelCard {
13
16
  model_show_name: string;
14
17
  }
15
18
 
16
- export const LobeBaichuanAI = createOpenAICompatibleRuntime({
19
+ export const params = {
17
20
  baseURL: 'https://api.baichuan-ai.com/v1',
18
21
  chatCompletion: {
19
22
  handlePayload: (payload: ChatStreamPayload) => {
@@ -51,24 +54,24 @@ export const LobeBaichuanAI = createOpenAICompatibleRuntime({
51
54
  const modelsPage = (await client.models.list()) as any;
52
55
  const modelList: BaichuanModelCard[] = modelsPage.data;
53
56
 
54
- return modelList
55
- .map((model) => {
56
- const knownModel = LOBE_DEFAULT_MODEL_LIST.find(
57
- (m) => model.model.toLowerCase() === m.id.toLowerCase(),
58
- );
57
+ return modelList.filter(Boolean).map((model) => {
58
+ const knownModel = LOBE_DEFAULT_MODEL_LIST.find(
59
+ (m) => model.model.toLowerCase() === m.id.toLowerCase(),
60
+ );
59
61
 
60
- return {
61
- contextWindowTokens: model.max_input_length,
62
- displayName: model.model_show_name,
63
- enabled: knownModel?.enabled || false,
64
- functionCall: model.function_call,
65
- id: model.model,
66
- maxTokens: model.max_tokens,
67
- reasoning: knownModel?.abilities?.reasoning || false,
68
- vision: knownModel?.abilities?.vision || false,
69
- };
70
- })
71
- .filter(Boolean) as ChatModelCard[];
62
+ return {
63
+ contextWindowTokens: model.max_input_length,
64
+ displayName: model.model_show_name,
65
+ enabled: knownModel?.enabled || false,
66
+ functionCall: model.function_call,
67
+ id: model.model,
68
+ maxOutput: model.max_tokens,
69
+ reasoning: knownModel?.abilities?.reasoning || false,
70
+ vision: knownModel?.abilities?.vision || false,
71
+ };
72
+ }) as ChatModelCard[];
72
73
  },
73
74
  provider: ModelProvider.Baichuan,
74
- });
75
+ } satisfies OpenAICompatibleFactoryOptions;
76
+
77
+ export const LobeBaichuanAI = createOpenAICompatibleRuntime(params);